Как мы делали мобильные java игры. Привет 2007 год
Всем привет. Ни для кого не секрет, что лет 15 назад появление кнопочных (тогда ещё) сотовых телефонов с цветными дисплеями, а также новыми мультимедийными возможностями изменило нашу жизнь. Игры на тех телефонах, по сравнению с современными, выглядели очень примитивно. Вот, например, игра Bounce на телефоне, которую многие помнят:
Фото взято из английской Википедии. Но всё же эти простые, с виду, игры должны были как-то создаваться. Для этого в начале 2000 года компанией Sun Microsystems (ныне Oracle) была начата разработка мобильной платформы J2ME, профилей MIDP и CLDC. Задачи ставились простые: развернуть возможность начинающим разработчикам быстро приступать к созданию новых игр и получению за это коммерческой выгоды.
Вспоминается чья-то недавняя фраза из комментариев на Пикабу:
- Что вы знаете о бесполезных профессиях? Расскажите об этом J2ME программисту!
Собственно, вопрос: а как эти самые игры разрабатывались, из чего они состоят?
Каждая игра состоит из системной информации, кода и ресурсов. Первая вещь чисто формальная, а вот вторая и третья нам интересна. Рассмотрим на примере всё той же игры Bounce от Nokia.
Напоминаю, что такие, даже устаревшие игры, могут до сих пор являться чей-то собственностью, поэтому разборка игр на ресурсы и декомпиляция кода не разрешается разработчиками. Материал для данной статьи был взят из интернета для учебных целей.
Итак, вот они, ресурсы:
Эти ресурсы (а конкретно в данном случае - спрайты, из которых составляется игровой мир) вшиты в сам пакет приложения jar. Сами по себе они ценности не представляют. Но байт-код игры, как и любой другой программы, выбирает по очереди эти ресурсы и использует их для создания привычной нам игровой картинки. Помимо ресурсов, в игре присутствуют ещё и уровни. Тут сложнее. Обычный уровень игры выглядит примерно так:
Да, выглядит не особо интересно. Но, если вглядеться, то видно, что разнообразие у значений небольшое - 00, 01, 1D, иногда 6E и прочее. К каждому из этих значений в коде игры ассоциирован свой объект (картинка, фигурка или сущность), которые потом игра расставляет в указанном порядке и формирует тем самым игровое поле или уровень.
Теперь, непосредственно, код. В файле игры содержатся т.н. классы, в них-то и описана сама структура игры и осуществляемые ей действия. Вот, например, таковые в архиве с игрой:
В данном случае игра не была подвергнута т.н. обфускации. Зачастую в 99% всех игр эти классы выглядят уже так:
Т.е. в процессе обфускации названия классов и всех переменных теряются и заменяются на набор букв по алфавиту. Это выполняет две функции: во-первых, уменьшается размер итоговой игры или программы (самая большая по количеству классов j2me программа, на моей памяти, это J2ME SDK - содержит 447 классов), во-вторых, обеспечивает защиту от вмешательства шаловливых ручек навроде моих - разобраться в этом наборе однотипных наименований уже очень сложно - и не всегда поймешь, что имел ввиду автор в том или ином классе. Знает это только лишь он сам. Но вернёмся к нашей игре. Если открыть любой класс, увидим мы немногим отличное от того, что увидели в файле уровня.
Т.е. можно видеть какие-то наименования методов, но в общей картине разобраться трудно. Чтобы превратить этот байт-код в какой-то понятный человеку язык, используется программа - декомпилятор.
- На тот момент, когда эти игры были в ходу и имели коммерческую ценность, декомпиляция наказывалась уголовно (т.к. она могла повлиять на полученную компанией коммерческую выгоду от игры). Сейчас же, думаю, вся возможная выгода уже получена, но на всякий случай если кто-то из разработчиков описанного в посте кода увидит его, прошу меня извинить за рассмотрение именно на этом примере.
Итак, декомпиляторы. Знакомьтесь, их был с десяток: JD, JAD, Mocha, DJ, JD-Core и выпущенный последним и на текущий момент самый совершенный: Fernflower. Файл fernflower.jar можно найти в сети. Чтобы получить исходный код из байт-кода, необходимо сам файл скормить декомпилятору. В данном случае делается это просто: создаётся папка и пишется в командной строке, что делать программе.
И мы получим примерно следующее:
Т.е. вполне читаемый код. В нём уже можно разобрать, что имеется ввиду под каждым действием и для чего нужна каждая переменная.
- Кстати, этот код всё же частично обфусцирован - например, видно переменные с именами var2, var3 и т.п. в оригинальном коде они часто называются типа CurrentBallX, CurrentHeight и др. для того, чтобы можно было понять суть этой переменной.
Но: иметь читаемый код, это не значит иметь рабочий код. В данном случае, с вероятностью в 99% (если программа сложнее HelloWorld) будут также содержаться ошибки декомпиляции. Вызваны они тем, что декомпилятор не всегда понимает, что имеется ввиду автором или самим компилятором. Хотя файл из fernflower на выходе имеет минимальное количество этих ошибок.
У нас теперь есть исходный код игры. На самом деле с этого этапа всё и начинается. Программист в оригинале пишет весь этот код "из головы", т.е. создает и забивает все переменные, классы и методы с нуля.
Теперь перенесёмся в 2007 год. Допустим, вы написали исходный код игры и решили собрать её, чтобы потом отправить на продажу на свой сайт, предложить компании-издателю и т.п. А для этого что вам нужно? Вам нужен компилятор кода и сборщик. В совокупности всё это называется SDK, как и на десктопных вариантах программирования. В качестве IDE с подключаемым java-компилятором можно было использовать Eclipse, NetBeans с дополнительными библиотеками под мобильную версию. А собирать потом или вручную, или внеся свои правки в сборочный скрипт. Но мы рассмотрим один из самых простых вариантов: Для J2ME тоже была своя SDK - которая называется WTK. Последняя версия 2.5.2 была выпущена в сентябре 2007 (том самом, который по мнению многих сгорел). Потом к ней было ещё небольшое дополнение. Вот главное окно программы приветствует нас:
Программа не имеет своего внутреннего редактора кода и на борту лишь простенький эмулятор (который нашу игру не потянет - т.к. написана она была под Nokia). Поэтому для корректной работы нам понадобится следующий софт:
- Сама WTK 2.5.2_01 (небольшой патч 2008 года)
- Java JDK и JRE версии 5.0 (под неё была рассчитана последняя версия WTK)
- Редактор кода (я использую Notepad++, можно блокнот или что удобнее)
- Эмулятор (лучше всего подходит KEmulator Lite v 0.9.8, разработка которого также была заброшена в 2008 году, но на текущий момент он самый проработанный из всех и тянет большинство моделей телефона).
Создаем проект: нажимаем New Project, указываем параметры приложения и путь к главному классу (с которого всё начинается):
Больше ничего не требуется. Окно конфигурации можно просто закрыть, теперь у нас в папке пользователя создана папочка для репозитория приложения:
Общий путь: %папка пользователя%\j2mewtk\2.5.2\apps\
Теперь, чтобы собрать игру, надо наши ресурсы (картинки и уровни, описание меню на разных языках - в крупных компаниях за разработку каждых из них отвечал свой человек) закинуть в папку res. В папку res идёт все кроме META-INF (её программа создаст сама) и, собственно, классов приложения. Классы закидываются в src с сохранением пути.
Содержимое папок src и res. Теперь пробуем собрать игру. Нажимаем кнопку "Build". И видим:
А вот и пошли наши первые ошибки. Сразу аж 12 штук. Теперь уже надо включать голову и разбирать, что же там не так. Начнём по порядку. Откроем строку с первой ошибкой (623) и видим следующее:
На этой самой строчке компилятор в операцию побитного И почему-то засунул японский иероглиф 'ソ'. Понятное дело, что такого быть не может: цифру нельзя объединить с иероглифом. Но с чем же можно? Получим числовой код иероглифа:
В числах это 65407. Именно это число и должно быть в процедуре. Выполним автозамену. Выясняется, что в остальных классах ошибки аналогичные. Итого для трёх разных классов в нескольких местах кода был неправильно распознан тип.
Пробуем собирать:
Теперь ошибок стало.. внимание.. 39! Но ничего страшного в этом нет. Это нормальная практика компилятора - сначала отсеиваются более важные ошибки, затем менее важные. Если разобраться в причине, выясняется, что ругается на то, что не удалось найти некоторые методы, находящиеся в классах, в пакетах, таких как как com.nokia.mid.sound, com.nokia.mid.ui и других. Собственно, а этих классов то тут быть и не должно - они уже содержатся в исполняемой среде (телефон Nokia).
Либо они добавлялись (в те самые золотые года) разработчиками платформ в IDE под конкретные модели телефонов. Кстати, отсюда пляшет несовместимость игр для j2me - именно по этой причине игры от Нокии могли не идти на Сименсе или Эриксоне (вторая причина - разные коды клавиш). Всё зависело от разработчиков и этих самых классов. Но и нам их надо где-то взять для успешной компиляции, т.к. выполнять замену на аналоги из JSR-135 слишком долго. Воспользуемся библиотеками от KEmulator: идём в папку программы, затем папка libs и там есть файл third-party.jar. Копируем его в папку libs нашей программы (в wtk), и удаляем лишнее от других моделей телефонов:
Получили маленький файлик весом в 4 Кб. Можно компилировать:
Теперь осталась одна единственная ошибочка. Снова лезем в исходник:
Компилятор ругается на эту самую строчку. Причем ошибка то и не совсем в ней самой. Тут делается следующее: буфер должен изъять из массива типа Vector какие-то значения и передать их в родительский метод drawImage. Ошибка возникает из-за несовместимости типов: метод drawImage принимает Image, int, int, int, а наш неправильный код, получается, пытается всунуть во 2 и 3 поле объект Integer. Исправляется это тем, что нам нужно забрать значение var2 и var3 из объекта Integer непосредственно. Делается это просто - добавить преобразование типов. Для этого есть метод intValue(). И теперь всё скомпилировалось без единой ошибки:
Поздравляю, мы получили то, что у нас и так было. Вернее, того, чего не было - работающий исходник. Чтобы теперь превратить это игру, к классам нужно добавить ресурсы и это дело собрать. Собирается всё в два клика: Project -> Package -> Create Package. Теперь появились файлы jar и jad в папке bin проекта. Но перед запуском надо сделать ещё кое-что: т.к. мы добавили библиотеки для компиляции, эти самые библиотеки из готового пакета нужно выпилить (в случае подписи приложения сертификатом это надо сделать до подписи). Удаляем библиотеки от nokia, иначе они будут переопределять методы исполняемой среды, а у нас в данном случае это просто интерфейсы, они нам для работы программы не нужны.
Только теперь можно готовый jar-файл запускать на эмуляторе. Закидываем получившуюся программу в KEmulator (или на свой телефон) и наконец-то можно играть:
Артефакты в данном случае вызваны эмулятором (неполной поддержкой тех самых нокиевских методов), а не искажением исходника. На телефоне графика работает отлично. Вот таким вот нехитрым образом производился процесс создания игр в те годы. Я немного застал те времена, хотя и не удалось применить полученные знания на практике ибо всё было вскоре замещено Android и iOS, сама суть при разработке осталось той же, но отдельные элементы реализуются иначе. Надеюсь, теперь вы представляете весь тот труд, что был потрачен на создание java-игр в ту эпоху. Но, всему своё время.
Исходники программы после написания поста были удалены и я не рекомендую получать их образом, описанным в посте.
Всем спасибо, с вами был Kekovsky.
IT минувших дней
1.1K постов7.2K подписчика
Правила сообщества
Запрещается добавлять новости о прошлогодних новинках, а также посты, не относящиеся к тематике "ретро в ИТ".
Желательно соблюдать правила приличия.