Шаг 0x0A.
Остается последний шаг, чтобы связать результаты анализа снизу (от экрана к набору) и сверху (от набора к экрану) - понять, как адреса из предэкрана + смещение попадают в экран . Так как для этого копирования используется адресация через нулевую страницу, точку остановки поставить нельзя, поэтому можно только поставить ее на запись в экран и просто искать в окрестностях чтение из предэкрана .
ROM:840F JSR sub_856B ; Копирование 4х адресов в зону предэкран-вторичные индексы |
Этот кусок кода и есть функция- комбинатор . Она показывает связь между предэкраном и экраном . Новые важные переменные здесь $366 (x_shift_cycle)
- насколько сдвигаться за один шаг цикла считывания, похоже на разницу между строками, то есть ширину всего уровня;xcoord
- число повторов выборки, постоянно равно 5, от есть считывается по 4 тайла 5 раз = 20 раз - один столбец на экране. $A2-$A3
- пара ячеек, в которых написан изначальный индекс, от которого проводится измерение.
Разбираться откуда он берется пока не обязательно, можно посмотреть первый попавшийся и начать написание функции-комбинатора, которая будет делать то же самое, что и приведенный выше кусок кода на ассемблере.
Шаг 0x0B.
Реализация комбинатора на питоне: def get4Lines(begin,end, levelSet, a3a4, step, vertCount):
cycleBegin = begin
cycleEnd = end
a3a4ind = begin/4
line4 = []
for x in xrange(cycleBegin,cycleEnd):
if x%4==0:
a3a4ind += 1
b83,b85,b87,b89 = getLevelSetForCycle(levelSet, b8x_inds,(x+2)%4)
b93,b95,b97,b99 = getLevelSetForCycle(levelSet, b9x_inds,(x+2)%4)
line4.extend(makeLine(a3a4, a3a4ind, b83,b85,b87,b89, b93,b95,b97,b99, step, vertCount))
return line4
def makeLine(a3a4, a3a4BaseInd, b83,b85,b87,b89, b93,b95,b97,b99, step, vertCount):
a3a4ind = a3a4BaseInd
lines = []
for globalRepeat in xrange(vertCount): ##5(screen size)
predInd = a3a4[a3a4ind]
ind2a,ind2b = -1,-1
if predInd<128:
ind2a = b83[predInd]
ind2b = b87[predInd]
else:
ind2a = b85[predInd]
ind2b = b89[predInd]
res = [-1]*4
if ind2a<128:
res[0],res[1] = b93[ind2a], b97[ind2a]
else:
res[0],res[1] = b95[ind2a], b99[ind2a]
if ind2b<128:
res[2],res[3] = b93[ind2b], b97[ind2b]
else:
res[2],res[3] = b95[ind2b], b99[ind2b]
lines.extend(res)
a3a4ind+=step
return lines
b8x_inds = [[5,5,4,4], [7,7,6,6], [9,9,8,8], [11,11,10,10]]
b9x_inds = [[12,13,12,13], [14,15,14,15], [16,17,16,17], [18,19,18,19]]
def getLevelSetForCycle(levelSet,indsConst,loop):
inds = map (lambda v: v[loop], indsConst)
return map (lambda i: levelSet[i], inds)
Набор данных можно выхватить прямо из дампа памяти:
def prepareBinary(binaryDump, addrBegin, addrLen, outName): |
Дальше можно проверить отрисовку и убедиться, нарисовалась полоса уровня высотой в 20 клеток.
Шаг 0x0C.
Экспериментируя с параметрами A2A3
и значением vertCount
можно получать разные срезы уровня, например, такой:
По нему можно прикинуть, откуда начинается мусор, и как конец уровня связан с началом по высоте и вычислить правильное значение A2A3
соответствуюшее началу уровня (и в итоге заметить, что оно на 1 меньше, чем адрес в $47-$48
(это первое значение из набора ), то есть равно первому значению и набора - 1).
Также можно обратить внимание, что рядом с шириной уровня ($366) в ячейке $367 лежит и его ширина).
Тогда можно вывести полную схему работы комбинатора:
и написать универсальную функцию рисования:
def makeJob (binaryName, spritesName, addr, baseAddrSet, w,h, levelNo): |
(уровень определяется - дампом памяти, набором адресов, размерами (ширина и высота) и картой тайлов).
Этой функцией можно нарисовать любой уровень:
Уровень 1.
Уровень 2.
Уровень 3.
Уровень 4.
Уровень 5.
Уровень 6.
Уровень 7.
Уровень 8.
Уровень 9.
Уровень 10.
Код скрипта (использовался больше в интерактивном режиме)
//Если что-то долго ломать, то оно сломается.
Конец