Ни для кого не секрет, что теперь вы можете легко скачать эмулятор практически любой игровой консоли 80-90-х годов и играть в классические игры на своем компьютере, телефоне и многих других платформах.
Ромы этих же игр вы легко найдете в Интернете.
Часто люди скачивают их и даже не задумываются о том, как кто-то когда-то прочитал их с картриджа.
В этой статье я попытаюсь рассказать, как это было сделано в случае с NES/Famicom, которая была нам более известна как «Денди», и покажу, как это можно сделать самостоятельно.
Сразу скажу, меня уговорили сняться в целом многосерийном шоу на тему того, как устроены и работают игровые консоли.
Поэтому публикация сегодня идет сразу в двух вариантах: в виде видео и по старинке в виде статьи.
Кому как больше нравится, тем более что целевая аудитория у каждого варианта явно разная.
В статье я постараюсь раскрыть больше технических подробностей, когда видео будет носить скорее развлекательный характер.
Видео:
(Связь: www.youtube.com/watchЭv=gPSpk2gbAD4 )Статья:
Итак, как же работает картридж Famicom? Многие сразу скажут, что это всего лишь ПЗУ с параллельным доступом, и в ее чтении не должно быть ничего сложного, но это не совсем так.Во-первых, картридж содержит два типа памяти: с кодом игры и с изображениями из игры.
Каждый из них включается непосредственно в консольную шину данных.
Первый параллельно с оперативной памятью и процессором (CPU), а второй параллельно с видеопамятью и видеочипом (PPU).
Таким образом, картридж представляет собой что-то вроде оперативной памяти, куда уже загружена игра.
Давайте рассмотрим распиновку гнезда картриджа и принцип ее работы.
Вид на консоль сверху.
Слева — перед. → ЦП A0-A14 — контакты, через которые задается адрес чтения памяти процессора ЦП D0-D7 — контакты, через которые мы передаем данные памяти процессора → ППУ А0-А13 — контакты, через которые задается адрес чтения памяти ППУ ППУ Д0-Д7 — контакты, через которые передаем данные памяти ППУ → М2 — локальный тактовый сигнал, принимает высокий уровень при обращении к памяти ЦП → /РОМСЕЛЬ - логическое NAND между M2 и CPU A15, к которому нет прямого доступа → Процессор Чтение/Запись - определяет тип операции: высокий уровень - чтение, низкий - запись ← /IRQ - позволяет картриджу генерировать прерывание, внутри пульта подтягивается до +5В → ППУ/РД - переходит в низкий уровень, когда консоль читает память PPU → ППУ/ВР - переходит в низкий уровень, когда консоль записывает в память PPU → ППУ/А13 — просто инвертированный сигнал с ППУ А13 ← ЦИРАМ А10 — позволяет картриджу определить принцип зеркалирования видеопамяти в консоли ← CIRAM /CE - при низком уровне включает видеопамять внутри консоли → Звук (вход) - здесь звук с аудиочипа идет в картридж ← Звук (вход) - здесь звук исходит из картриджа в том виде, в котором мы его уже слышим * Земля и еда — без комментариев, напряжение 5 вольт Теперь более подробно, немного технической информации.
Объем памяти ЦП консоли варьируется в диапазоне от 0 И $FFFF (16 бит адресации).
На картридже обычно указаны адреса $8000-$FFFF .
Обратите внимание, что у нас нет контактов ЦП А15 , который должен отвечать на самый старший бит адреса.
Вместо этого есть /РОМСЕЛЬ , который становится низким только тогда, когда М2 и теоретический ЦП А15 в то же время занять высокий уровень.
Те.
когда консоль читает или записывает адреса $8000-$FFFF .
Поэтому его обычно можно напрямую подключить к ножке /CE ПЗУ.
Чтение или запись по выбору через Процессор Чтение/Запись .
Зачем нужно писать на картридж? Да, причин много, но об этом ниже.
Память PPU имеет адреса из 0 до 3 доллара США (14 бит адресации), что обычно относится к картриджу 0-$1FFF .
Именно в этом диапазоне хранятся образы, причем это может быть как ПЗУ, так и ОЗУ, но картридж сам определяет, какие адреса принадлежат ему, а какие внутренней части консоли, для чего он и используется.
ЦИРАМ/СЕ .
Обычно (почти всегда) он подключается напрямую к ППУ/А13 , т.е.
память консоли активируется при А13 равный единице - в диапазоне от $2000 до 3 доллара США .
Обратите внимание, что внутренняя память Famicom и NES приведена ниже.
$2000 и нет вообще, он должен быть в картридже.
PPU использует отдельные контакты для чтения и записи: ППУ/РД И ППУ/ВР .
Отдельно стоит упомянуть ЧИРАМ А10 - этот вывод определяет, как зеркалируется память в диапазоне между $2000 И $2FFF внутри консоли.
Обычно это важно определить в зависимости от того, движется ли игра вертикально или горизонтально.
В старых играх это было жестко запрограммировано с помощью перемычки на плате, в новых играх это обычно можно изменить программно во время игры.
Да, оригинальный Famicom также имел аудиовход и аудиовыход, что позволяло картриджу быть дополнительным источником звука.
Это использовалось редко, но позволяло сделать музыку в играх намного приятнее за счет дополнительных синтезаторов звука.
Эти контакты больше не были доступны на РЭШ.
В современных китайских «Денди» и других клонах они тоже не припаяны.
Конечно, вытащить звуковой чип из картриджа нет возможности.
На NES принцип работы ничем не отличается, хотя картриджи уже имеют 72 контакта: несколько идут напрямую в гнездо внизу консоли (никогда не использовалось ни в одной игре), плюс четыре идут на чип для защиты от пиратства .
Перейдем к практике.
Так что, вроде, ничего особо сложного нет. Вам просто нужно как-то прочитать все данные по всем адресам и сохранить их в NES-файл.
Для этого я решил взять два микроконтроллера ATMEGA64. Да, это очень избыточно, но мне просто нужно огромное количество ножек — в картридже их все равно 60. Хотя память CPU и PPU не обязательно считывать одновременно, и их можно было подключить к одним и тем же ножкам, но для первого эксперимента я решил их изолировать.
Более того, таким образом гораздо проще подключить плату; Двустороннюю делать вообще не хотелось.
Слот для картриджей можно купить, это стандартный торцевой разъем на 60 ног, но почему-то его везде можно было только заказать, поэтому я просто выпаял его из дешевого новоделового денди.
После сборки и печати корпуса устройство получилось вот такое:
Не буду вдаваться в подробности прошивки; принципы работы с памятью уже изложены выше, а исходный код будет в конце статьи.
Неужели все так просто? Увы, не совсем.
Срок жизни NES и Famicom был довольно долгим, и разработчики игр очень быстро (уже в 1985 году) столкнулись с тем, что при таком подходе в картридж можно впихнуть очень мало информации.
И вовсе не из-за небольшого размера, а потому, что адресное пространство для кода было ограничено этими самыми $8000-$FFFF, а это всего 32 килобайта.
В этот размер умещаются только самые простые игры, такие как «Battle City», «Ледолаз», «Утиная охота», «Тетрис», «Lode Runner».
Проще говоря, все то, что мы привыкли видеть на сборниках типа «9999999 в 1» с повторяющимися играми.
Поэтому картографы начали ставить в картриджи.
Это микросхемы, отвечающие за переключение банков памяти, в результате чего становится возможным существенно расширить адресное пространство.
Представьте, что по некоторому адресу хранится код первого уровня игры.
Проходишь через него, маппер переключает банк памяти, и в результате по абсолютно одному и тому же адресу читается код не первого, а второго уровня.
То же самое касается и видеопамяти.
Получается, чтобы сделать дамп картриджа, нужно заранее знать, какой маппер в нем стоит, и какие команды ему нужно подавать для переключения банков памяти.
И все это было бы еще проще, если бы все картриджи имели один и тот же маппер или если бы их было всего несколько.
Но существует несколько сотен различных картографов и способов их подключения.
Иногда обходились простой логической схемой, а иногда устанавливали весьма навороченные микросхемы с кучей регистров и дополнительными функциями.
При этом нередко брали какой-нибудь популярный маппер, но подключали его необычным способом, что радикально меняло принципы взаимодействия с ним.
Первопроходцам пришлось сбросить первый банк памяти, разобрать его и провести реверс-инжиниринг, чтобы выяснить, как получить доступ к остальным данным.
При этом в заголовке файла NES указывается общепринятый номер маппера, и полноценный эмулятор должен эмулировать не только саму консоль, но и весь этот зоопарк железа, установленного в картриджах.
Получается, что теоретически может появиться картридж, который будет не только сложно сбросить, но и который не будет эмулироваться ни одним существующим эмулятором.
Далеко ходить не придется: внутри наших популярных пиратских мультиигровых картриджей можно найти очень многое.
А китайцы до сих пор выпускают новые игры на собственном железе, что понять стало еще сложнее.
Кстати, в картриджах было всё.
Помимо ПЗУ и мапперов туда устанавливалась дополнительная оперативная память (иногда с батарейкой для возможности сохранения в игре), всевозможные счетчики времени, описанные выше синтезаторы звука и многое другое, включая модем.
Увы, в нашей стране в девяностые лицензионные картриджи было трудно найти, да и пираты особо не беспокоили, а игры с такими наворотами здесь не продавались.
Я решил реализовать хотя бы игры для чтения на самых популярных мапперах.
Клиентскую часть дампера я пишу на C#, поэтому просто описал интерфейс IMapper и класс, соответствующий каждому мапперу:
В каждом реализованы методы дампа данных.
Вот как выглядит метод чтения программной памяти игры на маппере MMC3:
Если кому интересно, описание этого маппера можно прочитать здесь: wiki.nesdev.com/w/index.php/MMC3 Я решил попробовать побыть в шкуре первопроходцев и сбросить картридж с таким необычным меню:public void DumpPrg(FamicomDumperConnection dumper, List<byte> data, int size) { dumper.WritePrg(0xA001, 0); byte banks = (byte)(size / 0x2000); for (byte bank = 0; bank < banks-2; bank += 2) { Console.Write("Reading PRG banks #{0} and #{1}.
", bank, bank+1); dumper.WritePrg(0x8000, 6); dumper.WritePrg(0x8001, bank); dumper.WritePrg(0x8000, 7); dumper.WritePrg(0x8001, (byte)(bank | 1)); data.AddRange(dumper.ReadPrg(0x8000, 0x4000)); Console.WriteLine("OK"); } Console.Write("Reading PRG banks #{0} and #{1}.
", banks-2, banks-1); data.AddRange(dumper.ReadPrg(0xC000, 0x4000)); Console.WriteLine("OK"); }
Для этого я сначала прочитал картридж так, как будто там нет маппера, запустил его на эмуляторе и начал разбирать.
Вскоре я нашел нужную мне инструкцию:
После этого я прочитал картридж еще раз, предварительно записав по адресу $B600, и получил полностью работоспособный ПЗУ .
Разумеется, в нем игры не запустятся, ведь для этого нужно заново переключать банки памяти.
И даже если я проследю за тем, что происходит, когда я выбираю игру в меню и читаю весь картридж, эмулятор, скорее всего, не сможет все это запустить.
Еще мне попался лицензионный картридж одной из самых знаковых игр того времени — «The Legend of Zelda».
Без проблем работает и с дампером и с Фамиком через простой пассивный переходник.
Нет смысла бросать эту игру; меня это интересовало и в другом смысле.
Этот картридж содержит дополнительную оперативную память и аккумулятор, что позволяет сохраняться в игре.
Эта память находится в диапазоне $6000-$7FFF. Я попробовал прочитать его и скормить эмулятору.
Он это понял без проблем.
После этого ради эксперимента я решил увеличить в нем количество сердечек и записать обратно в картридж.
Это сработало.
Оказалось, что это забавная возможность переносить сохранения между эмулятором и настоящей консолью.
Многие наверняка спросят, зачем я вообще за это взялся, когда в интернете можно найти практически любой ROM. Да, просто из любопытства и самообразования.
Было интересно посмотреть, что происходит внутри этих картриджей и как все это работает. Кроме того, они могут как читать, так и записывать картриджи.
Но об этом в следующий раз.
Ссылки на источники: github.com/ClusterM/famicom-dumper — сам дампер (исходники на C, разводка платы, 3D модели корпуса) github.com/ClusterM/famicom-dumper-client — клиент на C# Теги: #nes #famicom #Dendy #avr #dump #dumper #dumper #dumping #reverse Engineering
-
Ругайте Меня!
19 Oct, 24