Repository navigation
Deviations
Правило проекта - повторять оригинал буквально, включая странности. Поэтому список отступлений короткий, и каждое названо здесь вместе с причиной. Вторая половина страницы - обратное: места, которые выглядят ошибкой порта, но являются точной копией оригинала.
| # | Что | Почему |
|---|---|---|
| 1 |
keys.cfg вместо keys.dat / keys2.dat
|
коды VK не совпадают с кодами SDL |
| 2 | обход чекпоинтов ограничен | оригинал читал за границу таблицы |
| 3 | явный запас в сетке тайлов | оригинал полагался на соседний .bss
|
| 4 | возврат при неопознанном уровне | оригинал брал неинициализированный указатель |
| 5 | ассеты - файлы, а не ресурсы PE | вне Windows нет LoadBitmapW
|
| 6 | сенсорная панель | у КПК были аппаратные кнопки, у телефона нет |
| 7 | ключи командной строки | нужны для проверок, см. Build-and-CI |
| 8 | кадр по vsync, а не по WM_TIMER
|
в SDL2 нет SetTimer
|
Оригинал держал привязки в keys.dat и keys2.dat, кодируя каждую как «база
1000 + смещение 100» над кодами VK_*. Это Windows-коды, в SDL их нет:
SDLK_LEFT - это 1073741904, а VK_LEFT - 0x25. Переносить кодировку значило
бы тащить чужую таблицу соответствий, поэтому порт пишет свой keys.cfg - шесть
десятичных кодов SDL, по одному в строке (см. Menu-and-controls).
Счётчик красных пикселей в оригинале не ограничен ничем
(Level_LoadTileMap 0x000126a4, addeq r10,r10,#0x1 без сравнения), а таблицы
всего по 50 элементов - картинка с 51-м чекпоинтом писала бы за границу. Порт
ставит проверку на месте записи:
if (*p == Color_Pack16If16bpp(screen, 0x000000ffu)) {
found++;
if (found < JB_CHECKPOINT_N) {
jb_checkpoint_x[found] = col;
jb_checkpoint_y[found] = row;
}
}Обратите внимание: found считается по-прежнему без границы, ограничена только
запись. Так номера чекпоинтов остаются теми же, что в оригинале, даже если
их больше пятидесяти.
Заполняется 1020 строк, а рисовальщик читает 17 строк вперёд от cam_row, и
cam_row доходит до 0x3fe. Оригинал этим не смущался: за g_tileGrid 0x00031178 в .bss лежали другие переменные, и чтение уходило в них. Порт
объявляет запас явно:
#define JB_GRID_ALLOC_ROWS (JB_GRID_ROWS + JB_VISIBLE_ROWS + 2) /* 1020 + 17 + 2 */Поведение то же - за концом трассы читаются нули, - но теперь это гарантия, а не совпадение раскладки памяти. См. Levels.
Выбор карты - три независимых if, и третий покрывает всё больше единицы:
if (level == 0) src = map1->pixels;
if (level == 1) src = map2->pixels;
if (level > 1) src = map3->pixels;
if (src == 0) return;Последняя строка - добавление порта. В оригинале
(Level_LoadTileMap 0x00012644, ldrne r8,[sp,#0x0]) указатель при
несовпадении оставался неинициализированным, и функция шла читать мусор.
Достижимо это только отрицательным номером уровня, то есть на практике никогда,
но повторять чтение по случайному адресу смысла нет.
Оригинал грузил картинки через LoadBitmapW(g_hInstance, id)
(Game_Init 0x000113bc), звук и музыку - через hss.dll. Порт читает те же
номера из BITMAP/<id>.bmp, Sounds/, Musics/; папку можно переназначить
переменной JUMPYBALL_ASSETS. Номера ресурсов при этом сохранены как есть - см.
Reverse-engineering.
В оригинале управление - аппаратные кнопки КПК (g_gxKeyA, g_gxKeyStart и
прочие), поэтому экранной панели там нет и быть не может. Порт добавляет её в
jb_touch_sdl2.c, но знает она те же шесть действий, что и клавиатура, - новых
возможностей у игрока не появляется. Подробности раскладки - в
Menu-and-controls.
--level=, --screen=, --index=, --unlocked= и путь для дампа кадра -
целиком добавление порта, сделанное ради проверок: попасть в нужный экран одной
командой вместо прохождения игры. Масштаб окна первым позиционным аргументом -
тоже порт: у оригинала окно было размером с экран КПК. См. Build-and-CI.
Оригинал заводил SetTimer(hwnd, 10, 5, 0) и считал кадр по WM_TIMER каждые
5 мс. Порт крутит обычный цикл с SDL_RENDERER_PRESENTVSYNC, то есть кадры
приходят с частотой монитора. Физика от этого не меняется: и там и там шаг
считается из реального времени -
/* JumpyBall.exe WndProc 0x00020920 takes g_dtMs 0x00064968 as
GetTickCount() - DAT_0006496c and 0x00020974 scales it by 0.001. */
dt = (float)(ticks - prev_ticks) * 0.001f;- поэтому скорость мяча и таймер уровня одинаковы. Разница видна только там, где
результат обрезается в целое каждый кадр, например в накоплении угла вращения
spin = (int)(vel_z * dt * 350.0f + (float)spin): при 60 Гц округлений меньше, чем при 200 Гц. Единственная явная пауза в порте -Platform_DelayнаJB_LEVEL_CLEAR_PAUSE_MSпосле прохождения уровня; ограничителя кадров нет.
Stage_Begin умеет шесть тем оформления, но номер темы выводится из номера
уровня, а уровней три:
theme = level;
if (theme == 2) theme = 4; /* GRASS */
if (theme == 3) theme = 1; /* DESERT */Темы FOREST 2 и SKY 3 не получаются ни при каком level. Код их фонов и цветов
HUD перенесён полностью - выбрасывать ветку только потому, что до неё нет пути,
значило бы терять факт из дизассемблера. То же с проекцией трассы для этих тем -
см. Track-and-camera и Levels.
Самая частая реакция при чтении кода - «тут опечатка». Ниже - список мест, где
опечатки нет: так было в JumpyBall.exe или hss.dll, и порт повторяет это
намеренно.
| Место | Что смущает | Почему так |
|---|---|---|
jb_pl.z_axis_damped = 0 |
флаг никогда не меняется, ветки под ним мертвы | в оригинале это .bss-переменная, которую тоже никто не пишет |
| настройки - экран 4 | экрана 3 не существует | нумерация Menu_DrawFrame 0x0001a7d0
|
view_center_x + 0x14 как координата Y
|
смешаны оси | буквальный перенос мастера переназначения |
((MOD_BASEFREQ << 8) / 0xac44) |
делимое равно делителю | оба числа - 44100; выражение всегда даёт 0x100
|
MOD_CLOCK / p до умножения на 65536.0
|
потеря точности | целочисленное деление было в hssSpeaker::createTables 0x00103664
|
(max < 0) ? max + 1 : max) >> 1 |
max_unlocked не бывает отрицательным |
знаковый сдвиг в оригинале |
шаг строки от map1->w для всех трёх карт |
второй и третий уровни читаются шириной первого |
0x000126c0 читает ширину g_levelMap1 независимо от уровня |
Последняя строка таблицы - не косметика: из неё следует требование к картинкам
уровней. 333.bmp и 334.bmp обязаны иметь ту же ширину, что 309.bmp, иначе
карта поедет по диагонали. Подробности - в Levels.
Отдельно стоит jb_ctx.layout_mode = JB_LAYOUT_240x320. Оригинал умел две
раскладки - 240x320 и 176x220, - и весь код второй перенесён (видно по
выражениям вида 0xa0 - (layout_mode >> 1)), но порт всегда ставит первую.
Причина простая: окно теперь масштабируемое, подстраиваться под экран КПК не
нужно.
-
Сохранения прогресса.
max_unlockedживёт только в памяти; оригинал тоже не писал его на диск. На диск попадает единственный файл -keys.cfg. - Экрана результатов. Финиш - это пауза 1000 мс и возврат в меню выбора уровня, см. Levels.
-
Второй раскладки клавиш. Оригинал держал две (
keys.datиkeys2.dat), порт - одну.
Устройство порта
Сборка и происхождение