Русский
Русский
English
Статистика
Реклама

6502

Создание графики для nesdendy

22.04.2021 20:09:52 | Автор: admin

Предыдущие мои статьи рассказывают о том как начать программировать под денди на ассемблере. Мы научились отрисовывать спрайты и background, так же мельком обсудили что такое таблица атрибутов и таблица имен, так же мы разобрались как прочитать контроллер. В тех статьях Я использовал chr от игры super mario bros 2 потому как не художник, но все же для создания игры мне пришлось искать инструменты какое мне помогут в создание своей графики для игры. Под катом этапы разработки графики.


Довольно много времени ушло на то что бы найти инструмент под linux для создания таблицы имен, через какое то время я даже начал писать своё на php. Идея была проста, и уже был близок к завершению. Возможно конвертировать изображение bmp или png пройдя попиксельно. С лева на право 128 пикселей, и с верху в низ 256 строк. Далее определяем цвет каждого пикселя, а таких может быть всего 4-ре и в зависимости от цвета ставим соответствующие биты, каждый пиксель в файле chr описывается 2 битами, по следующему принципу:

  • цвет 0 = 00 = 00000000

  • цвет 1 = 01 = 00000001

  • цвет 2 = 10 = 00000010

  • цвет 3 = 11 = 00000011

То есть эти 2 бита всего лишь последние 2 бита порядкового номера цвета. При этом в спрайтах цвет 0 - прозрачный цвет. В background'e это цвет фона.

В chr буквально храниться 2 банка, 128 на 128 пикселей. Часто 1-банк используется для спрайтов (и называется левым), 2-й банк в свою очередь используется для спрайтов фона (ну или тайлов, Я могу ошибаться в терминологии). С этим разобрались, и вот как только Я заканчивал свой скрипт, то нашел уже готовый на node.js img2chr

Скрипт устанавливается довольно просто

$ npm install -g img2chr

У данного скрипта есть 2-ва параметра 1-й это файл картинки (я использую png), 2-й это файл куда надо сохранить chr

img2chr test.png test.chr

После этого мы можем подключать chr в коде и использовать его.

Немного об эмуляторе FCEUX

Попробовал несколько эмуляторов к моменту написания этой статьи, оказалось что более всего удобен FCEUX под Windows, запущенный из под wine. Он предоставляет дебагер, и может дампить nametable и attribute table что довольно удобно для правильного рендера уровней.

Как я сказал выше Я не художник, и тем более не умею красиво рисовать. Первым делом я создал изображение 128х256 пикселей расчертил по 8 пикселей ячейку, ошибся в первой ячейки но оказалось не критично.

Картинка с линиями

Зеленой линией я разделил картинку на две страницы. Далее я нарисовал грубые контуры будущих спрайтов.

Грубый набросок

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

Кулаки и удаленные спрайты

Далее я начал работу над сглаживанием контуров, приводя контуры к более геометрически правильному виду, удалив лишние пиксели, и там где не хватало добавил. Часть изображения начал раскрашивать уже в оттенке серого.

Контуры и цвета

Далее я продолжил раскраску спрайтов в соответствующие цвета

Цвета промежуточный вариант

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

Окончательный вариант

После этого мне оставалось одно запустить команду

img2png test.png t.chr

Импортировать себе в код и нарисовать фон и спрайты. Напомню что спрайты рисуются довольно просто последовательной записью в порт PPU $2004

  1. Y - координаты

  2. Номер спрайта

  3. Маска отображения

  4. X - координата

Типичный код отрисовки спрайта выглядит так

LDA #100 ; загружаем в акумулятор A значение 100STA $2004 ; сохраняенм значение координаты Y в порт $2004LDA #$01 ; спрайт под номером 1 (0-я строка, 1-й спрайт) STA $2004 ; записываем спрайт в портLDA #%00010110 ; маска STA $2004LDA #100 ; x координата STA $2004

И на экране был выведен мой спрайт. Что не могло не радовать. После этого я вывел все спрайты для отрисовки героя их вышло 10, конечно это очень большое количество спрайтов, так как мы можем достичь ограничений приставки например 64 спрайта одновременно на экране, но об этом мы поговорим в следующих статьях.

Предыдущие статьи:

  1. Программирование NES

  2. Считывание контроллера

Подробнее..

Перевод Как игре Pitfall для Atari удалось поместить 255 комнат в картридж на 4КБ

24.05.2021 10:16:39 | Автор: admin
Игры для Atari 2600 разрабатывались в условиях сильных ограничений. Когда Уоррен Робинетт продвигал идею, которая в дальнейшем станет игрой Adventure (в ней нужно исследовать мир из множества комнат и подбирать предметы, которые помогают игроку в пути), ему отказали, потому что посчитали, что её невозможно реализовать. И это было логично. Консоль появилась в конце 70-х; до Робинетта никто ещё не создавал игру с несколькими экранами. Это была эпоха Space Invaders и Pac Man, когда весь игровой мир постоянно находился у игрока перед глазами, поэтому то, что выпущенная в 1980 году Adventure состояла из 30 комнат, было весьма впечатляюще.


Первый экран игры Adventure. Игрок управляет точкой (которую Робинетт называл человеком).

Разработчикам даже пришлось объяснять эту концепцию в руководстве к игре:

Каждая область, показанная на экране телевизора, будет иметь один или несколько барьеров или стен, через которые вы НЕ можете проходить. Также там есть один или несколько проёмов. Чтобы перейти из одной области в соседнюю, выйдите с экрана телевизора через один из проёмов, и на экране появится соседняя область.

Наличие нескольких комнат было довольно большой инновацией, а то, что в Adventure удалось реализовать целых 30 комнат, стало настоящей революцией. Однако в созданной Дэвидом Крэйном и выпущенной в 1983 году Pitfall! таких комнат было 255, и каждая из них была более сложной (с точки зрения графики), чем любая комната Adventure. В статье я расскажу, как этого удалось добиться.

Примечание: в игре Superman было несколько комнат и её выпустили до Adventure, но она создавалась на основе кода Adventure.


Типичный экран Pitfall!

Но чтобы в полной мере осознать сложность реализации этого достижения, стоит рассказать о сложностях, с которыми сталкивались программисты игр для Atari. Сама консоль имела всего 128 байт ОЗУ. Это 1024 бит. Для сравнения: это предложение при кодировании в ASCII занимает больше места, не говоря уже о формате UTF, в котором оно на самом деле закодировано. Этого достаточно, чтобы показать, что в Atari было не так много памяти

Но это ведь не важно, в самом картридже ведь будет достаточно места? Ну, в какой-то мере да. В то время картриджи Atari 2600 обычно содержали 4 КБ ПЗУ, подавляющее большинство которого приходилось занимать кодом игры. Даже если опустить необходимость хранения кода, на каждую комнату можно выделить всего 16 байт, а ведь код всё равно нужно где-то хранить.

Примечание: адресуемое пространство в Atari 2600 составляло всего 2 КБ. Использование 4 КБ было возможно благодаря технике под названием переключение банков (bank switching).

Так как же Крэйн справился с такими ограничениями пространства при создании игры?

Процедурная генерация


Чтобы создать большой мир, не сохраняя много данных, нужно, чтобы его генерировал код.

Однако самая большая проблема такого решения заключается в том, что сгенерированные данные нужно куда-то сохранять. Именно так поступают игры типа Rogue и Minecraft. Они случайным образом генерируют миры, чтобы дать игрокам разнообразие, но после генерации данных они их сохраняют. Ограничения Atari не позволяли воспользоваться этой роскошью.

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

Описание комнаты


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

Байт, в котором хранится схема текущей комнаты, разделён на четыре части:

Биты 0-2: объекты


Первые три бита определяют типы создаваемых объектов. Эта система усложняется двумя аспектами, которые контролируют биты 3-5.

Во-первых, комната может содержать сокровище (если значения битов 3-5 равны 101). Если в ней есть сокровище, то обычный предмет, определяемый битами 0-2, не будет создан, и его место займёт соответствующее сокровище.

Во-вторых, существуют крокодилы (если значения битов 3-5 равны 100), при которых не создаются никакие другие объекты. Кроме того, если значения битов 0-2 равны 010, 011, 110 или 111, то создаётся лоза, позволяющая игроку раскачаться и перепрыгнуть через крокодилов. При всех других значениях лозы не будет и игроку придётся прыгать по головам крокодилов.

Примечание: я всегда записываю первым старший бит, поэтому 100 точнее было бы назвать битами с 5-й по 3-й.

Правила создания предметов и сокровищ:

Биты Предмет Сокровище
000 одно катящееся бревно деньги
001 два катящихся бревна серебро
010 два катящихся бревна золото
011 три катящихся бревна кольцо
100 одно неподвижное бревно деньги
101 три неподвижных бревна серебро
110 огонь золото
111 змея кольцо

(С этим было довольно сложно разобраться.)

Биты 3-5: тип ямы


Биты 3-5 контролируют тип ямы или ям, с которыми столкнётся игрок.

Биты Тип ямы
000 одна дыра в земле
001 три дыры в земле
010 ноль дыр в земле
011 ноль дыр в земле
100 крокодилы в воде
101 подвижная битумная яма с сокровищем
110 подвижная битумная яма
111 подвижные зыбучие пески

Подвижные битумные ямы без сокровища (биты 110) всегда имеют лозу, а если сокровище есть (биты 101), то над битумной ямой не будет лозы (благодарю Майка Ширальди за то, что сообщил мне это).

Биты 6-7: деревья


Биты 6 и 7 определяют паттерн деревьев. Это никак не влияет на геймплей, но даёт игроку ощущение смены локаций. Паттерны деревьев похожи друг на друга, поэтому я не буду вдаваться в подробности, но если вы хотите посмотреть, то они используются в комнатах 1, 2, 3 и 5, и имеют битовые паттерны 11, 10, 00 и 01.

Бит 7: подземная стена


Бит 7 также используется для того, чтобы управлять расположением подземной стены справа или слева. Он не управляет наличием стены, оно определяется в другой части кода, но если стена есть, то значение бита, равное 0, помещает её слева, а значение 1 справа.

Вот так единственный байт определяет структуру текущей комнаты. Но как я говорил, в памяти хранится только текущая комната. Это возможно благодаря способу, которым генерируются комнаты.

Регистры сдвига с линейной обратной связью


Описывающие комнату байты генерируются системой, которую Крэйн назвал полиномным счётчиком (polynomial counter); сегодня мы называем её регистром сдвига с линейной обратной связью (linear feedback shift register, LFSR).

LFSR это способ генерации псевдослучайных чисел по порождающему значению (seed): берётся двоичное число, выполняется логический сдвиг влево или вправо, а затем вычисляется входящий бит через линейную функцию исходных битов. Обычно этой функцией является последовательность из нескольких XOR.

В качестве примера давайте используем LFSR в Pitfall!

Когда игрок начинает игру, байт комнаты имеет шестнадцатеричное значение C4 (11000100 в двоичном виде, 196 в десятичном). Это порождающее значение (seed). Когда игрок переходит на одну комнату вправо, байт сдвигается влево, и младший бит (бит 0) становится XOR битов 3, 4, 5 и 7. Формула такова:

b0 b3' + b4' + b5' + b7'

Где + обозначает XOR, а апостроф бит в предыдущем состоянии. Этот паттерн имеет желательное свойство является LFSR максимальной длины, то есть создаёт каждое сочетание из 8 бит, за исключением одним нулей. Это позволяет миру Pitfall! и содержать максимальное количество комнат, и иметь равную вероятность любой битовой строки (повторюсь, за исключением нулей).

Примечание: + обозначает XOR, потому что сложение mod 2 эквивалентно операции XOR над битами.

То есть когда мы перемещаемся после первой комнаты вправо, байт меняет значение с 11000100 на 10001001. Все биты сдвигаются влево, а затем биту 0 присваивается значение 1, так как 1 = 0 + 0 + 0 + 1.

На ассемблере 6502 это было реализовано так:

; room' = room << 1 | (bit3 + bit4 + bit5 + bit7)LOOP_ROOM:  LDA ROOM  ASL  EOR ROOM  ASL  EOR ROOM  ASL  ASL  EOR ROOM  ASL  ROL ROOM  DEX  BPL LOOP_ROOM

Код целиком можно посмотреть здесь. Данный фрагмент начинается со строки 3012.

ROOM это байт, описывающий текущую комнату. Прежде чем переходить к тому, как он работает, важно обратить внимание на последние две строки и понять, почему всё это находится в цикле. Крэйн хотел, чтобы если Pitfall Harry (главный герой Pitfall!) находится в подземелье, то прохождение через комнату перемещало его через три комнаты. DEX выполняет декремент регистра X, а BPL выполняет ветвление, если результаты предыдущего вычисления не были отрицательными, поэтому Крэйн реализовал это поведение, задавая регистру X значение 2 перед вызовом этой подпроцедуры, если Гарри находится под землёй. В противном случае регистр X имеет значение 0 и зацикленность отсутствует.

Если точнее, ROOM это место в памяти, где находится байт, описывающий комнату.

Вот поэтому это цикл. Остальная часть кода, как часто бывает с ассемблером для Atari, довольно запутанна. Я пишу статью не про ассемблер 6502, поэтому не буду вдаваться в подробности, но, по сути, команды ASL (arithmetic shift left, арифметический сдвиг влево) перемещают биты в правильные позиции, а команды EOR (exclusive or, исключающее ИЛИ) выполняют XOR битов. В конце команда ROL (rotate left, вращение влево) сдвигает байт ROOM влево, записывая в бит 0 бит переноса. Этот бит переноса является результатом предыдущих EOR и ASL. Всё вышеописанное создаёт нужное поведение.

Если мы хотим увидеть каждую комнату, которую генерирует этот код, то можем воспользоваться приведённым ниже кодом ассемблера 6502, который обходит в цикле приведённый выше код, пока байт не вернётся к начальному значению, и сохраняет каждй сгенерированный байт по порядку в адреса с $00 по $FF (с 0 по 255).

  LDA #0          ; initialize address offset to 0  TAXdefine ROOM $00define SEED $C4  LDA #SEED  STA ROOMLOOP_ROOM:        ; do all the LFSR stuff  ASL  EOR ROOM  ASL  EOR ROOM  ASL  ASL  EOR ROOM  ASL  ROL ROOM  LDA ROOM  INX             ; increment address offset  STA $00,X       ; store generated byte  CMP #SEED       ; stop if we complete a cycle  BEQ STOP  JMP LOOP_ROOM   ; get next room byteSTOP:  BRK

Примечание: хороший эмулятор 6502 можно найти здесь.

Но всё это не даёт понимания того, почему дизайн Крэйна был настолько гениальным. Выше описывается происходящее, когда мы идём вправо, но что если мы пойдём влево, чтобы вернуться в предыдущую комнату? Восемь битов, описывающих эту комнату, никогда не сохраняются в памяти; в памяти хранится только текущая комната. Как Pitfall! реализует движение влево? При помощи этого LFSR:

b7 b4' + b5' + b6' + b0'

Примечание: я достаточно вольно обращаюсь с терминологией. Строго говоря, LFSR называется регистр, с которым производят действия, но здесь я буду называть этим термином формулу, вычисляющую входящий бит.

Этот LFSR примечателен тем, что является инверсией предыдущего. Каждый раз, когда игрок идёт влево, этот LFSR отменяет последнее действие, сделанное LFSR, когда игрок шёл вправо. Здесь и далее я буду называть этот LFSR левым LFSR, а предыдущий правым LFSR.

На ассемблере 6502 левый LFSR был реализован следующим образом:

; room' = room >> 1 | ((bit4 + bit5 + bit6 + bit0) * 128)LOOP_ROOM:  LDA ROOM  ASL  EOR ROOM  ASL  EOR ROOM  ASL  ASL  ROL  EOR ROOM  LSR  ROR ROOM  DEX  BPL LOOP_ROOM

Можно заметить, что этот LFSR тоже имеет метку LOOP_ROOM. Её мы взяли из дизассемблированного кода, потому что не знаем, как сам Крэйн назвал этот фрагмент кода, но то, что они имеют одинаковую метку это вполне нормально. Так получилось потому, что команды ветвления (например, BPL) могут выполнять смещение счётчика программ максимум на 255, а эти две метки разделены более чем тысячей команд. Чтобы перемещаться на более дальние расстояния, потребуется или JMP или JSR, то есть команды безусловных переходов.

Давайте минуту поразмыслим над тем, чего добился Крэйн. Он нашёл LFSR, который является инвертируемым и имеет максимальную длину. Это очень впечатляющий программный трюк. Но не верьте мне на слово, что эти два LFSR являются обратными друг другу, я докажу это вам.

Операции LFSR игры Pitfall инвертируемы. Доказательство:


Рассмотрим последовательность из восьми бит B = b7b6b5b4b3b2b1b0. Используем Br для обозначения B, к которому применён правый LFSR и Bl для обозначения B, к которому применён левый LFSR. Мы хотим показать, что Brl = Blr = B. То есть мы хотим показать, что результат применения сначала правого, а потом левого LFSR, или сначала левого, а потом правого, аналогичны отсутствию действий.

Примечание: формально мы покажем, что композиция двух функций в любом порядке равна функции тождественности, то есть две функции по определению являются обратными друг другу.

Чтобы показать, что Brl = B, вспомним, что такое правый LFSR:

b0 b3' + b4' + b5' + b7'

Применив это уравнение к B = b7b6b5b4b3b2b1b0, мы получим следующее:

Бит 7 Бит 6 Бит 5 Бит 4 Бит 3 Бит 2 Бит 1 Бит 0
B b7 b6 b5 b4 b3 b2 b1 b0
Br__ b6 b5 b4 b3 b2 b1 b0 b3 + b4 + b5 + b7

Тогда применив левый LFSR, который, как мы помним, имеет вид:

b7 b4' + b5' + b6' + b0'

к Br, мы получим:

Бит 7 Бит 6 Бит 5 Бит 4 Бит 3 Бит 2 Бит 1 Бит 0
B b7 b6 b5 b4 b3 b2 b1 b0
Br b6 b5 b4 b3 b2 b1 b0 b3 + b4 + b5 + b7
Brl___ 2(b3 + b4 + b5) + b7 = b7 b6 b5 b4 b3 b2 b1 b0

И это подтверждает факт, что Brl = B. Доказательство того, что Blr = B, почти такое же, поэтому я оставлю его в качестве задания для читателя*.

* Всегда ненавидел, когда так писали в учебниках.

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

Вот так Pitfall! создаёт свой мир. Компактная запись в сочетании с инвертируемым регистром сдвига с линейной обратной связью.



Постскриптум: как я во всём этом разобрался


Можно решить, что информация о столь важной для истории и популярной игры, как Pitfall!, широко распространена и доступна в Интернете. Но на самом деле это не так.

Многие знали, что в игре используется LFSR, но способ его реализации, насколько я знаю, не описан подробно нигде, за исключением самого ассемблера. Но когда я нашёл проанализированную и откомментированную версию ассемблера, то оказалось, что представленное там описание LFSR на самом деле неверно! По крайней мере, неверно описание левого LFSR. Но ошибка была довольно очевидна, поэтому было несложно понять, как он реализуется на самом деле.

Примечание: изначально в комментарии говорилось, что за декрементом LFSR следовал XOR с битом 1 вместо бита 0. Теперь эта ошибка исправлена.

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

Как же я это сделал? Я написал небольшую программу для генерации последовательности LFSR (программу на JavaScript, ссылка на которую дана выше) и сравнил её с комнатами. Проведение этого анализа для бита 7, управляющего стороной экрана, на которой отрисовывалась подземная стена, было простой задачей, как и биты 6 и 7, управляющие деревьями. Но всё остальное оказалось довольно монотонным. Бесценным ресурсом для меня стала эта карта.

Меня удивило, что, насколько я могу судить, мне довелось первым подробно описать способ рендеринга мира в игре Pitfall!, но в то же время я разочарован. Если вы не смотрели этот доклад на GDC о сохранении истории игр, то вам точно стоит это сделать. В отличие от истории многих других дисциплин, история ПО сохраняется не очень хорошо, несмотря на то, что сохранить её должно быть проще всего. У нас не сохранился оригинальный исходный код почти ни одной игры для Atari, NES, SNES, ColecoVision, и так далее. Дизассемблированный код бесценен, но всё-таки это не оригинал. И он не позволяет прочитать комментарии разработчиков.

Возможно, нам повезёт, и у Activision, Atari и Nintendo где-то в хранилищах есть оригинальный код, который они опубликуют в свободном доступе ради блага человечества, но я бы не особо на это надеялся. Каждый, кто может, должен работать над сохранением любого возможного фрагмента истории, потому что сама себя она не сохранит.

Подробнее..

Перевод Превращаем компьютер BBC Micro (1981 год) в устройство записи защищённых дисков за 40 000 долларов

24.07.2020 12:09:40 | Автор: admin

Введение


Одна из самых известных историй о защите гибких дисков связана с Dungeon Master. Эта игра, выпущенная в декабре 1987 года, сочетала в себе усложнённый формат физического диска (нечёткие биты) со скрытными проверками защиты, встроенными в сам геймплей.

Рекомендую прочитать эту статью, в которой представлен замечательный обзор гибких дисков, после которого идёт очень подробный обзор защиты нечёткими битами диска Dungeon Master для Atari ST. Есть также вот эта замечательная статья, которая более подробно рассказывает об историях, связанных с защитой Dungeon Master. В ней есть цитата одного из авторов Dungeon Master:

Мы получили преимущество, получив патент на схему защиты гибких дисков, в которой для записи дисков требовалось специализированное оборудование за 40 тысяч долларов. Без этого оборудования образ диска создать было невозможно, а само оборудование было выведено из эксплуатации.

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

Компьютер BBC Micro имел процессор 6502 на 2 МГц и для выполнения его простейших инструкций требовалось два такта, то есть 1 микросекунда. Есть ли надежда, что можно будет записать нечёткие биты в условиях таких ограничений? Посмотрим, как далеко нам удастся зайти. Эта работа будет называться проектом Oiled Otter.

Чтобы вы почувствовали дух времени, вот изображение аппарата дублирования гибких дисков 3,5". Удивительно, насколько он похож на фотокопировальный аппарат, только в приёмник вместо бумаги вставляются диски! Похоже, такую машину даже могла продать вам компания Advanced World Products.


Пользовательский порт BBC Micro


BBC Micro был известен своей превосходной расширяемостью, в том числе и с помощью так называемого пользовательского порта (user port). Этот порт управляется 6522 Versatile Interface Adapter, работающим с частотой 1 МГц. Сам порт имеет 8 контактов данных и 2 контакта управления. Эти контакты обеспечивали очень высокую степень контроля. Контакты данных можно было по отдельности настраивать как вводы и выводы, а логическим уровням выводов можно было задавать высокий или низкий сигнал.

Почему же нам важен user port? Мы попытаемся управлять дисковым приводом непосредственно через него. Устранив из уравнения контроллер гибких дисков, мы, возможно сможем от него избавиться и добиться более прямого контроля над дисковым приводом и передаваемыми потоками данных.


Кабель от user port к дисковому приводу

На изображении выше показан мой кабель, подключающий user port к дисковому приводу. Разъёмы стандартны, а соединяющие их провода это просто перемычки. Я искренне стремлюсь создать нечто, что можно было изготовить в те дни, поэтому не использую никакую дополнительную электронику.

Кабель устроен следующим образом:


Главный вывод, который мне удалось извлечь из этой схемы интерфейс дискового привода, вероятно, проще, чем можно было подумать. Мы можем управлять дисковым приводом и запрашивать его важное состояние всего на 8 контактах. Всё очень просто. Допустим, нам нужно раскрутить привод, тогда достаточно подать низкий сигнал на PB0 и PB1. Если вы хотите дождаться, пока диск довращается до начала дорожки, то нужно запрашивать логический уровень на PB6, пока мы не увидим изменение уровня сигнала с высокого на низкий. Для пошагового движения достаточно задавать логический уровень step in противоположно step out, а затем выполнять пульсацию низкого сигнала на контакте step.

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

Проблемы с электрикой


Обязательно краткое отступление о проблемах с электрикой, потому что я с ними столкнулся. При подключении случайных пар компонентов они могут заработать, но иногда требуются ухищрения. Вот интервал напряжений, изначально наблюдавшийся на контакте W/DATA привода:


Пытаемся записывать импульсы на привод с частотой FM в 250 кГц

Напряжение логики 1 равно примерно 3,4 В, а напряжение логики 0 примерно 1,5 В. Это серьёзная проблема! Приемлемые уровни напряжения TTL чётко заданы:

Входящий сигнал TTL считается низким, если имеет напряжение от 0 В до 0,8 В относительно заземляющего вывода, и высоким при напряжении от 2 В до VCC (5 В). Если на вход элемента TTL подаётся сигнал напряжения в интервале от 0,8 В до 2,0 В, то элемент не даёт конкретного ответа, а поэтому сигнал считается неопределённым.

Напряжение логики 0, равное 1,5 В, считается неопределённым, и не вызовет никаких действий. И в самом деле, мой привод с этим сигналом ничего не записывал.

Проблему удалось устранить снятием с привода оконечного резистора кабеля гибких дисков. Вот фотография моего привода, где сборка оконечных резисторов обведена красным:


Это замечательно решает проблему уровней напряжения, после чего всё работает. Похоже, что многие порты BBC Micro кроме дискового порта не имеют достаточно сил для управления кабелем с оконечными резисторами. Но постойте-ка вероятно, для установки этого резистора была какая-то причина? Да. Его снятие имеет два хитрости:

  • Обращайте внимание на длину кабелей. Без оконечного резистора длинные кабели подвержены искажениям сигнала.
  • Следите за уровнями напряжения на неподключенных проводах. Я наблюдал напряжение 1,32 В на контакте привода S/SEL (выбор стороны). Это ненормально, потому что такое значение тоже находится в интервале неопределённости TTL. Куда привод будет записывать данные? Может быть, на верхнюю сторону, может быть, на нижнюю. А может и вообще ни на одну из них! Проблема была решена подключением каждого значимого кабеля и подачей на них высокого или низкого сигнала.

Нужно повысить пропускную способность


Проблема, которую мы пока обходили стороной: как подавать сигнал на контакт W/DATA? Это жёсткий контакт. Он имеет высокую пропускную способность и обладает точными требованиями к таймингам. Давайте на секунду перестанем мечтать о нечётких битах с наносекундной точностью, и попробуем записать на привод простые FM-импульсы.

Большинство дисков для BBC Micro имеют формат FM (они же DFM, они же одиночной плотности), кодируемый с частотой 250 кГц. Запись дорожки FM на самом деле выполняется довольно просто. Нужно проверить, вращается ли привод и открыта ли створка записи. После этого каждые 4 микросекунд или выполняем пульсацию W/DATA на низкий, а потом обратно на высокий сигнал (бит 1), или не делаем этого (бит 0). Чаще всего каждый второй бит должен быть равен 1 (синхронизирующий бит для поддержания тайминга и синхронизации).

Управление W/DATA через процессор безнадёжная задача. 4 микросекунды это 8 тактов процессора; этого определённо не хватит для загрузки байта, его сдвига, записи 0, а потом 1 в логические уровни user port. Простой цикл скорее всего займёт 12 с лишним микросекунд, а это слишком много. Так что для достаточно быстрой записи W/DATA нам придётся воспользоваться возможностями чипа 6522 VIA.

Регистр сдвига 6522 VIA


Самый очевидный кандидат для нашей задачи это регистр сдвига. Регистр сдвига это 8-битный регистр. В соответствующем режиме загрузка регистра сдвига заставит чип последовательно передать 8 бит по одному из контактов user port. Это отлично биты обрабатываются параллельно с работой основного процессора, поэтому процессор может спокойно тратить время на создание нового набора битов, сдвиг которых нужно будет начать.

К сожалению, мне не удалось заставить работать эту схему. Единственный сдвиговый режим, имеющий потенциал достаточно быстрой работы это сдвиг с тактовой частотой системы. В спецификации Western Design Center 6522 есть хорошая схема:


Системный тактовый генератор VIA имеет частоту 1 МГц, поэтому тактовая частота сдвига будет сигналом 500 кГц, а разрешение выводимых битов равно 250 кГц. Этого как раз хватает. Однако я не разобрался, как заставить непрерывно и плавно работать тактовую частоту сдвига. Даже после попыток точного тайминга для перезагрузки регистра сдвига интервал контакта тактовой частоты сдвига всегда выглядела вот так:


Получается, что в единственном сдвиговом режиме, достаточно быстром для наших опытов, перезагрузка регистра сдвига вызывает задержку перед продолжением сдвига. Это нам не подходит.

Режим импульсного вывода 6522 VIA


Малоизвестной функцией 6522 является его режим импульсного вывода. Он описывается в спецификациях не всех вариаций 6522, но вот небольшая запись о нём в спецификации MOS Technology:


Наконец-то мы нашли спецификацию, точно описывающую его поведение. Этот режим очень интересен для нас, потому что одна операция записи в VIA обещает привести к двум отдельным действиям: на выходной контакт подаётся низкий логический сигнал, а спустя 1 такт (1 микросекунду) он возвращается к высокому сигналу без усилий с нашей стороны. Благодаря этому мы можем использовать его для управления выходным сигналом 250 кГц. Ресурсы процессора очень ограничены циклом решить задачу никак не получится, но линейный блок кода 6502 сможет справиться, например:

        \ &70 points to &FE60, aka. user 6522 VIA ORB register.        STA (&70),Y        \ 8 cycles, pulse output        STA (&70),Y        \ 8 cycles, pulse output        STA (&70),Y        \ 8 cycles, pulse output        LDA (&70),Y        \ 8 cycles, do not pulse output        STA (&70),Y        \ 8 cycles, pulse output        ...

Это сработает. На это у процессора как раз хватит мощи. 8 тактов это 4 микросекунды, то есть кратчайшее время между импульсами диска.

К сожалению, такая операция чрезвычайно активно задействует память. Для каждого закодированного бита FM требуется 2 байта линейного кода 6502. Каждый значимый бит данных это два бита FM, потому что каждый второй бит является синхронизирующим. Дорожка состоит из 3125 байт, поэтому потребуется 3125 * 8 * 2 * 2 == 100 кБ линейного кода. BBC Micro имеет 32 кБ ОЗУ, так что здесь нам не повезло. Можно записывать одиночные (маленькие) секторы, в том числе и мощные новые механизмы защиты диска. Но мы не сможем записывать большие (1024 байта) секторы или полные дорожки. Для правильной записи большого количества дисков требуются обе эти операции. Более того, разрешение таймингов равно 1 микросекунде, чего недостаточно для записи множества более сложных защит и поверхностей дисков.

Мы можем быть довольны, что получилось заставить работать хоть что-то, учитывая такие ограничения, но это решение нас не совсем устраивает.

Помощь от ничего не обещающего порта вывода


К счастью, я общаюсь с умными людьми, например, с Bitshifters Collective. (Сходите оценить их последнее демо Evil Influences!) В беседе в Slack Том Седдон (автор эмулятора b2) предложил использовать вывод порта RGB (?)

Кабель-переходник с видео на диск такое не встретишь на Amazon каждый день.

Поначалу я посмеялся над этой идеей, но чем больше размышлял, тем вероятной она мне казалась. В BBC Micro использует для таймингов видеочип 6845. Как и 6522, это капризный процессор, но, по крайней мере, его особенности хорошо разобраны благодаря демо Bitshifters, в которых нещадно эксплуатируется 6845. Кроме того, я занимался реверс-инжинирингом, чтобы заставить эмулятор jsbeeb правильно эмулировать Hitachi 6845. Давайте посмотрим в этом видео на работу Oiled Otter, а потом расскажем, что увидели:


Всё работает благодаря необычному конфигурированию чипа 6845. 6845 работает с частотой 1 МГц, а тайминг кадров настроен так, чтобы на кадр приходилась одна растровая строка на 32 микросекунд/32 байта. При выводе каждого кадра регистры видеопамяти 6845 перезаписываются для получения следующих 32 байтов из потенциально другого места. То есть каждые 32 микросекунд из таблицы паттернов вывода выбирается другой паттерн вывода. Мы сконфигурировали контакты RGB на передачу 8 пикселей в микросекунду, то есть 256 на паттерн вывода. Это даёт нам огромное количество различных возможных паттернов вывода. Но так как мы записываем фрагменты по 32 микросекунд дисковых кодировок FM, подходят нам только несколько паттернов. В 32 микросекунд мы можем уместить 8 импульсов/битов FM. 4 битов будут синхронизирующими, и обычно все они равны 1. 4 бита будут битами данных, и у них существует всего 16 комбинаций.

Например, если мы записываем полубайт данных 0x5, то 32-микросекундный вывод должен выглядеть так:


Видеоданные будут иметь вид 00FFFFFFFFFFFFFF00FFFFFF00FFFFFF00FFFFFFFFFFFFFF00FFFFFF00FFFFFF. Первый, второй, четвёртый и пятый 00 это синхронизирующие биты. Между синхронизирующими битами расположен битовый паттерн данных 0101, или 0x5.

Ограничения процессора и памяти хорошо сбалансированы. В конечном итоге, эта схема похожа на ту, которую бы мы примерили с регистром сдвига VIA, если бы она сработала: какой-то небольшой сопроцессор (видеочип) занимается передачей набора битов FM, а центральный процессор свободен для загрузки и предоставления следующего паттерна. Требования к памяти вполне разумны. Таблица из необходимых выходных фрагментов на 32 микросекунд благодаря специальному линейному режиму адресации вполне помещается в 1024 байтов. Список индексов поиска для всей дорожки примерно равен 12 кБ, то есть всё отлично помещается в 32 КБ ОЗУ BBC Micro.

Особенности BBC Micro / 6845


Последний символ/столбец 6845

Разумеется, чтобы заставить эту схему работать, нельзя не столкнуться с некоторыми забавными особенностями. Первая из них это особенность 6845, из-за которой он выводит чёрный цвет для последнего символа каждой растровой строки. Это проклятие разработчиков демо, а теперь, похоже, и исследователей дисков. Вот слайд из недавнего доклада, на котором я демонстрировал эту проблему:


Слева показан эффект из демо, испорченный вертикальными чёрными полосами, вызванными проблемой последнего чёрного символа/столбца. Несколько растровых строк 6845 размещаются в одном растровом проходе, и, к сожалению, ненамеренно появляются чёрные полосы. При управлении диском эффект намного хуже: чёрные полосы заменяются на нежелательные импульсы, которые записываются на диск.

Справа показано изображение решения: передаваемая на диск волна просто инвертирована. Теперь нормально, что последний столбец всегда будет чёрным (показан оранжевым контуром), потому что там всегда требуется нулевое значение. Строго говоря, это нарушает требования к таймингам некоторых дисковых приводов к пульсации низких сигналов W/DATA. Вот схема таймингов привода той эпохи, Mitsubishi M4852/M4853:


Согласно этой схеме, логический 0 должен удерживаться до 2100 нс. При инвертированной форме сигнала следует ожидать от 3000 нс и более. Однако приводы, которые у меня есть, волнуют только спадающие импульсы данных, а не их длительность. Это неудивительно. Можно было бы проделать пару трюков, чтобы избежать особенностей 6845 и обеспечить время длительности в соответствии со спецификацией, но это оказалось необязательным, а потому я этим не занимался.

Порча DRAM

Порча DRAM (DRAM decay) это кошмар. Она происходит, когда нам не удаётся вовремя обновить DRAM. Цитата из статьи Википедии про обновление памяти:

Этот процесс выполняется автоматически в фоновом режиме электронной схемой памяти и невидим для пользователя.

Это справедливо для современных систем, но не для BBC Micro. На BBC Micro обновление DRAM является побочным эффектом подсистемы видео. При нём используется то свойство, что стандартные экранные режимы итеративно обходят все строки DRAM за короткий промежуток времени. Вероятно, вы уже догадались, к чему всё идёт наш особый видеорежим, используемый для вывода кадров на 32 микросекунды, не является стандартным экранным режимом. Он не гарантирует, что обойдёт все строки DRAM, поэтому происходит порча DRAM! Порча DRAM это не шутки. Из-за незапланированной порчи DRAM я терял различные программы и содержимое дисков. Покажу вам ради смеха программу на BASIC, вызывающую порчу DRAM для самой себя всего за долю секунды:


Печально в порче DRAM то, что если она вас постигла, то вы запросто можете потерять данные.

С другой стороны, если вы ожидаете порчу DRAM, то обычно можете с лёгкостью её обойти. В случае Oiled Otter есть различные критически важные циклы, при которых видеочип 6845 находится в необычном состоянии. Для сохранения обновления DRAM при каждом из таких циклов работает ручной инкремент получения данных из памяти.

Открывшиеся возможности


Теперь, когда у нас есть работающая система записи на диск в обход контроллера гибких дисков, что мы можем с ней сделать? В показанном выше видео мы уже продемонстрировали её способность записи произвольных дисков с кодировкой FM.

Но в этом исследовании нам очень повезло. Благодаря недостаткам регистра сдвига VIA нам пришлось искать решение с контактами видеовыхода и мы получили доступ к гораздо более мелкому разрешению таймингов на контакте W/DATA. Мы используем MODE4 компьютера BBC Micro, задействующий пиксельную тактовую частоту 8 МГц. Это означает, что можно каждые 125 нс выводить чёрные или белые пиксели, переключая импульсы записи с разрешением 125 нс. Если бы мы хотели потратить чуть больше дополнительной памяти (которая у нас есть) на таблицы побольше, то могли бы использовать MODE0, задействующий пиксельную тактовую частоту 16 МГц, что обеспечивает разрешение 62,5 нс. Я убедился, что 125 нс вполне достаточно для всех протестированных дисковых защит, но здорово, что у нас осталось пространство для манёвра.

Защита длинными дорожками

Моя любимая дисковая защита защита длинными дорожками. Она была популярна во времена Amiga. Мне не кажется, что её когда-нибудь использовали на BBC Micro. Мне нравится защита длинными дорожками потому, что она очень фундаментальна: контроллер гибких дисков имеет большой допуск по изменению скоростей записи (потому что дисковые приводы вращаются с разными скоростями), но записывает он только на одной правильной скорости.

Более сложная защита длинными дорожками заключается в записи двух секторов на одну дорожку, при которой один из секторов записывается с большей скоростью. Проверка защиты от копирования заключается во времени, которое заняло считывание этих двух секторов. Сектор, записанный с большей скоростью, должен считываться значительно быстрее.

Может ли Oiled Otter записать такую дорожку? Да, и довольно легко. Учитывая разрешение вывода в 125 нс, можно легко создать несколько элементов таблицы вывода, похожими на обычные, но с вырезанными из каждой 1 микросекунды 125 наносекундами. Вот видео создания защиты длинными дорожками и проверки считывания диска:



Защита нечёткими битами

Наверно, нам давно пора вернуться к тому, с чего мы начали: к защите нечёткими битами. Может ли Oiled Otter создавать нечёткие биты на оборудовании 1981 года? Давайте попробуем. Вот изображение результатов пары считываний сектора после его записи командой FUZZ системы Oiled Otter.


Команда FUZZ записывает полубайт 0x8, а бит данных поступательно откладывается на инкременты по 125 нс. Это похоже на описание того, как записывались нечёткие биты Dungeon Master. Как видно из скриншота, байты данных 0x88 вскоре начинают считываться неверно и недетерминированным образом. Но дисперсия не на 100% случайна, как слабые биты (weak bit) дисперсия заключается в том, достаточно ли поздно записан бит 0x8, чтобы иметь вероятность быть пропущенным. Если он пропущен, мы всё равно можем увидеть, что в этом безумии есть паттерны и логика.

Представленные выше результаты являются применением принципов нечётких битов к данным, закодированным FM. В кодировке FM каждый бит данных перемежается синхронизирующим битом. Это приводит к тому, что иногда тактовые биты проникают в поток данных (см. байты 0xFF в первом прогоне скорее всего, это тактовые биты). Защита Dungeon Master использует нечёткие биты в сочетании с MFM. Это приводит к более простой ситуации, где нечёткие биты перемещаются между двумя валидными кодировками битов данных и не задевают синхронизирующие биты! Разумеется, Oiled Otter может записывать MFM, GCR и любую другую кодировку, которую только можно придумать. Всё это просто разные протоколы одного фундаментального примитива возможности передачи импульса приводу в любой момент времени с хорошим разрешением.

Чтобы правильно его оценить, приведём вид нечётких битов на диске с осциллографа. Максимумы довольно неравноерны, а когда два импульса очень близки друг к другу (1 микросекунда или около того, слишком близко по стандарту любой кодировки), распознанная приводом сила перемагничивания даже начинает ослабевать.


Миссия выполнена

Мы получили возможность записи дисковых импульсов с разрешением 125 нс. Этого совершенно достаточно для создания сложных дисковых защит, в том числе длинных дорожек, слабых битов и нечётких битов. Совсем неплохо для железа 1981 года со скоростью выполнения самой быстрой команды в 1 микросекунду!
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru