Итак, мне удалось дойти до этого момента, и эмулятор начинает писать в неправильную область PPU, что конечно же портит всё выполнение.

Дошел до этого места
Дошел до этого места

С цветами я ещё не разобрался, так что стоит всего одна палитра на весь экран. Сейчас самое главное найти баг и в этой статье я опишу как его искал, но прежде хочу объяснить как я распределил память.

    uint8_t ctrl[0x100];
    uint8_t stack[0x300];
    uint8_t ram[RAM_MAX];
    uint8_t oam[0x800];
    uint8_t ppu[0x10000];
    uint8_t mem[0x8000];
    uint8_t chr[0x2000];

Как видно здесь много избыточного места, особенно для ppu. Всё дело в том, что мой эмулятор записывал в разные отличные от 0x2000-0x4000 области памяти и я поначалу думал, что всё нормально, хотя ясно же сказано было в документации, что у ppu для видео за экран отвечает область памяти от 0x2000 до 0x4000. Но этот баг я нашел за часов 6. Всё дело в неправильном понимании документации. У меня бывает с этим проблемы, я не так понимаю сказанное, а тут ещё и на английском.

Вот что было в документации.

operand is zeropage address; effective address is word in (LL, LL + 1) incremented by Y with carry: C.w($00LL) + Y

И вот carry я расценивал как CARRY FLAG, и добавлял единицу, если флаг был таков. Эта проблема стоила всех предыдущих дней, но мне нравилось искать в чем же баг, а такой баг очень сложно было искать. Вроде бы читает, пишет по адресу, но в чём дело? Почему не отображается экран?

Ладно, его я нашел. Теперь нужно найти ещё один подобный баг. Теперь, когда мы доходим до этого экрана, то эмулятор пытается записать в ppu по адресу 0x0908 байт 0xa0. Также выводиться отладочный код с регистрами перед завершением программы.

write to ppu 0908 = a0
	exit debug: A: a0 X: 03 Y: 03 P: c5 S: 01f4 PC: c8cb;

Наученный на этих багах, я решил найти сначала откуда читает байты и что показывает нам эмулятор fceux, но как на fceux выйти на нужный байт считывания, если функции могут запускаться в цикле и ошибка может возникать на 200 цикле.

Я открыл radare и перешел по этому смещению.

b100           lda (0x00),y

Перед тем как сохраниться в PPU_DATA, он считывает из адреса какое-то значение, в моём случае оно 0xa0. Я решил вывести дамп памяти перед падением и посмотреть что там находиться.

Очень странно
Очень странно

Очень странно, но у нас Y равно 0x03, а по смещению 0x03 нет ничего, но есть по смещению нулевому. LDA имеет 0xb1 опкод, смотрим в моем файле op_name.h и ищем опкод.

LDA INDIRECT Y
LDA INDIRECT Y

Пока всё совпадает, может я ошибся в коде, нужно глянуть.

Вроде нормально
Вроде нормально

Это пока ни о чём не говорит, надо смотреть макрос и сам indirect_y, вдруг там что-то.

LD ACTS
LD ACTS

Здесь два места на что стоит обратить внимание. get_addr это наш indirect_y, а read_from_address это уже читаем байт из памяти. Сначала исследуем indirect_y.

indirect y
indirect y

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

Извиняюсь за введение в заблуждение! Оказывается дамп памяти я приводил из другой области, не из ram. Вот что в ram находится.

дамп 0x0
дамп 0x0

Как мы видим, по нулевому адресу есть адрес 0x0591 + 0x03, который даёт нам по всей видимости неправильные данные. Посмотрим что там.

Нашел байты. По адресам 0x591 и 0x592 видимо наш адрес в PPU, который должен записаться.

dump 0x1
dump 0x1

Что же делать, распыляться? У нас есть две позиции в дампе, которые можно проверить. По всей видимости, что весьма вероятно, что 0xa0 байт правильный, а вот 0x908 надо проверить. Я поставил выход с дампом, если запись по адресу 0x591 будет иметь 0x09, посмотрим откуда это.

Отловил. Оказалось, что он берёт с zeropage по смещению 0x01, но мне кажется, что надо точно проверить всё ли я правильно прописал в функциях, поэтому для начала, перед тем как исследовать дальше, решил пройтись по всем функциям и убедиться, что названия функций указаны верно.

может быть здесь ошибки
может быть здесь ошибки

Я нашел места, где не было добавлена проверка на то, куда записываются данные, в область ram или в область кода. Я боялся, что сейчас ещё больше набагую код, но надо было везде всё исправить.

Исправил, но баг остался. Всё-таки где-то что-то не так, возможно я где-то сделал неправильную команду сложения, ведь у меня нет тестов :).

Когда он записывает 09 в область памяти, то мы видим, что до этого он читает байт из адреса 0x01, что ж, пойдём по следу бага.

a501           lda 0x01

Я поставил выход из программы, когда произведется запись по смещени 0x01 со значением 0x09.

Я нашел место в коде и видимо оно вызывается, когда начинается игра компьютера, нужно остледить что регистры все верны, поэтому я поставил breakpoint в fceux и тоже самое сделал в своем эмуляторе.

Я увидел интересную картину. Была запись такая.

lda #$00
ldx 00
sta ($74), x

И вместо того, чтобы sta записывать ноль, она записывала X, это случилось, потому что в коде стоял cpu->X.

Баг
Баг

Наконец-то я его нашел. Поиск был интересным и увлекательным. Я чувствовал, что я иду по следу и всё равно найду этот баг. Вот результат на видео того что получилось.

На этом статья закончена, впереди ещё много свершений. Нужно сделать цвета фона по тайлам, также сделать анимацию в самой игре, так как на видео видно, что персонажи стоят на месте.

Комментарии (2)


  1. bodyawm
    28.01.2025 14:10

    Вы - молодец


  1. FirstEgo
    28.01.2025 14:10

    Статья замечательная. Правда, когда я стал её читать, у меня появилось стойкое ощущение, что я сел на движущийся поезд...