Это еще одна статья про демосцену, сайзкодинг, ассемблер, MS-DOS и ретрокодинг. То есть, о том, как ночами напролет добровольно и бесплатно писать бесполезный и очень трудоемкий код и поседеть в 30 лет. Но вдруг вам захочется?
Вступление
Привет, меня зовут bitl и я из тех, кто в детстве не наигрался с кодингом под DOS, и занимаюсь этим в 2020-х, просто ради удовольствия. Изучение "дедовских" технологий, переизобретение старых и придумывание новых методов, алгоритмов и трюков, связанных с ограничениями старого "железа", в общем, тем, что сегодня называется "ретрокодинг". Так сложилось, что я с детства угорел по демосцене, поэтому и сейчас мой интерес в первую очередь связан с этой субкультурой. А так как моим первым компьютером был IBM PC-совместимый ПК (486SX), то MS-DOS - это моя платформа для ностальгирования. Хотя, надо отметить, демосцена не ограничивается ни ретрокодингом, ни ПиСи.
Но сейчас речь пойдет про одно конкретное направление, а именно про "сайзкодинг" (sizecoding). В демосценерской тусовке к этому жанру относят крошечные программки, показывающие какой-нибудь видеоэффект (или несколько), иногда со звуковым сопровождением. В народе это называют "демками" (наряду с большими), но на демосцене это классифицируется (и называется) как "интро" (intro), поэтому далее я так и буду это называть. Под "сайзкодингом" обычно подразумевается создание интры размером не более 4кб, а основными категориями являются 128, 256, 512, 1024, 4096 байт. Хотя для настоящих наркоманов ценителей есть также категории 8, 16, 32, 64 байт (например, на ежегодном онлайн-конкурсе Lovebyte: https://lovebyte.party/#competitions).
И так как я специализируюсь на платформе MS-DOS, то и говорить мы будет сугубо про кодинг под DOS для PC. Хотя сайзкодинг существует и популярен и на ретро-платформах, вроде ZX Spectrum, Commodore-64, Amiga, Atari, БК 0010 и даже на игровых приставках. Ну и Linux, Windows и JavaScript - тоже не исключение. Отдельно можно отметить fantasy-консоли (TIC-80, PICO-8, и т.п.), но это другая песня.
Наиболее каноничной и популярной стала категория "256 байт", этакая золотая середина. И что характерно, всё это набрало обороты именно тогда, когда DOS давно перестал быть актуальной ОС для практического применения. Рост популярности пришелся на конец 90-х, тогда же появились и соответствующие конкурсы (compo) на демо-вечеринках (Demoparty), а также онлайн-конкурсы. С тех пор прошло четверть века, и уж и Windows давно не позволяет запускать DOS-программы, но ничего не закончилось, каждый год сайзкодеры дарят миру еще несколько шедевров, в чем вы сейчас сможете убедиться.
Примеры 256-байтных интро
Перед тем, как перейти к технической части, давайте полюбуемся на избранные образцы данного жанра, чтобы стало ясно, о чем вообще речь:
Да, это все самодостаточные программы для DOS размером 256 байт. Нет, никаких библиотек они не используют, только DOS, центральный процессор x86, сопроцессор x87, VGA-совместимая видеокарта. Но не пугайтесь, это все же выборка из топовых релизов 20-летия. Далеко не все работы в этой номинации такие крутые :) Среднестатистическая интро все же несколько попроще, что не мешает им быть симпатичными, интересными и побеждать на конкурсах (которых проводится не мало и по всему миру, и это не только онлайн-мероприятия, а настоящие компьютерные фестивали). Поэтому если вы вдруг решите попробовать свои силы на этом поле, и решите выставить работу на конкурс, то не стоит волноваться, вряд ли вам прийдется соревноваться с чем-то вроде представленных выше образчиков. Да и в целом, на демосцене не хейтят новичков (по крайней мере последние лет 15).
Зачем вообще это надо?
Теперь, когда мы в общих чертах разобрались - о чем эта статья, перед тем как перейти к ассемблеру, хотелось бы обозначить еще несколько моментов. Во-первых: создавать такие штуки не так уж и сложно. Вам не надо быть каким-то крутым кодером, программистом, хакером, математиком. Некоторые сайзкодеры вообще не являются профессиональными программистами (например, я). Во-вторых: это просто развлечение, сайзкодинг вряд ли даст вам навыки, применимые в нормальном программировании, хотя это и неплохая разминка для мозга. И да, это должно приносить радость! Создание миниатюрных интро - это что-то вроде тех хобби, когда люди строят корабли внутри бутылки, или вырезают фигурки на кончике карандаша. Или решения головоломки. В-третьих: вам не нужно глубокое знание ассемблера x86 и архитектуры MS-DOS, чтобы начать. Прокачать скилл в ассемблере можно по ходу, а что касательно системных заморочек DOS-a, нам не потребуется знать всё, так как мы не пишем какую-то системную утилиту или драйвер, а всего лишь хотим рисовать что-то на экране, и основная необходимая база будет описана в этой статье.
На кого рассчитана эта статья?
Данная статья задумывалась как попытка поделится опытом новичка в области сайзкодинга по свежему опыту, так сказать, пока не забыл. Забавно, но первую 256-байт интро я написал в 2022 году, хотя я активный участник демосцены с конца 90-х. Ранее я писал демо и 64кб (килобайт!) интро под DOS и Windows, писал трекерную музыку, делал сценерский электронный журнал (diskmag), но вот опыта написания чего-то на чистом ассемблере не имел. Хотя, конечно, я иногда писал всякие рутины (процедуры) на встроенном ассемблере Turbo Pascal, Delphi, Visual C++, но там мне не приходилось озадачиваться такими вещами, как выделение памяти, инициализация переменных, и т.п. Это все было на стороне высокоуровнего языка. Так что я понятия не имел - как это делать на чистом Ассемблере. Это казалось каким-то геморроем. А учебники по ассемблеру на меня навевали тоску своей сухостью еще со школьных времен :)
Но я все же имел некоторые навыки в программировании графики, общее понимание - как организована память в реальном режиме DOS, что такое регистры, что такое стек и т.п. Для того чтобы попробовать свои силы в сайзкодинге и "чисто-ассемблерном" программировании, мне не хватало поджопника некоторой базы, эдакого понятного фреймворка, с которого я бы мог начать. Так что, продолжая эту статью, я буду надеяться, что вы из таких же лентяев :)
Я не буду здесь пытаться научить вас придумывать видеоэффекты, объяснять, как работает тот или иной эффект или давать вводный курс булевой алгебры (может быть, если такие просьбы поступят, я сделаю это в следующей статье).
Здесь же я объясню и покажу - с чего вообще начать, когда у вас в руках только ассемблер для MS-DOS, приведу примеры базовой организации кода для крошечных интро, как работать с памятью, и вообще "чо как". Но, естественно, все применительно к интро. Писать так, например, игры, или какие-то прикладные программы - я не советую :)
Возможно, вы имели опыт программирования на ассемблере для ZX Spectrum, и давно хотели попробовать свои силы на PC-платформе? Тогда вы также пришли по адресу.
Начинаем писать свою первую интро
В конце 2021 года мне в "телегу" написал Vinnny, организатор DiHalt (ежегодного демопати в Нижнем Новгороде) и спросил, не хочу ли я чего-нибудь прислать (надо отдать ему должное, он ежегодно и неустанно пинает всех сценеров, хотя это и не всегда приносит результаты). На тот момент я уже давно ни черта не делал для демосцены, но, по счастью, как раз занимался раскопками на своих старых винтах, где у меня остались архивы еще с DOS-времён. Я установил DosBox и принялся ковыряться в старье. Пропустим тот фрагмент, где я так и не найдя ничего, что не стыдно было бы релизить на пати, накорябал с нуля на Паскале алгоритм простенького олдскульного эффекта ("интерференция"), решил - а не попробовать ли это переписать на чистый ассемблер? Ведь алгоритм достаточно простой и должен уместиться в 256 байт (в теории). Спойлер: все получилось, и хотя первое место интра не заняла, но зато я наконец нашел себе увлекательное занятие на годы вперед. Однако здесь мы её разбирать не будем, по крайней мере в этот раз.
Я использовал что было под рукой, старый Turbo Assembler 2.02 (1990 г.), он уже поддерживает инструкции 80486, а большего мне и не было надо (и до сих пор не пригодилось). Вы можете использовать и более современные ассемблеры (NASM, YASM), на самом деле без разницы, но те примеры кода, которые я буду приводить, компилируются TASM'ом (для других нужно местами немного подправлять синтаксис).
Естественно вам также понадобится собственно DOSBOX (если вы не собираетесь программировать из под реального DOS, что плохая идея, так как в процессе разработки вы вероятно будете "вешать" свой комп по сто раз в час, а перезапустить DosBox все же быстрее, чем ребутиться. Вместо официального DosBox 0.74-3 (который не обновлялся с 2019-го) можно использовать форк под именем DosBox-X, который более точно эмулирует DOS-компьютер и имеет графический интерфейс для настройки. Главное на что стоит обратить внимание это "циклы" (cycles), официальный DosBox по умолчанию имеет настройку "cycles=auto", которая работает не пойми как, в итоге вы получаете скорость 286-го ПК.
Можно поставить статическое значение cycles=60000, это примерно соответствует Pentium 100 MHz. Далее смотрите по обстоятельствам. Слишком большое значение будет сильно загружать ваш реальный процессор.
Вам скорее всего также понадобится файловый менеджер. Я использую DOS Navigator, в том числе и его встроенный текстовый редактор для написания кода. Я также пользуюсь HEX-редактором Qview, который имеет дизассемблер, чтобы анализировать скомпилированный код (оценивать размер инструкций и данных, медитировать, биться головой об стол и все такое).
Итак, допустим, вы обдумали свой видеоэффект (лучше, если вы его уже запрограммировали на языке высокого уровня, оптимизировали размер кода как только сумели, и даже переписали основные части на асм, используя встроенный ассемблер, и отладили). Для создания миниатюрных программ используется COM-формат (а не EXE), он содержит только ваш код (и данные, которые вы сами пропишите), ничего лишнего. Фактически это ваш ASM-код, транслированный в OP-коды процессора. Но все же, в исходнике вам надо прописать некоторые директивы для компилятора, соблюдая синтаксис и порядок. К вашему счастью, я с этим уже разобрался и рассказываю.
Пропуская некоторые итерации, показываю как выглядит код программы для TASM 2.02, которая после запуска инициализирует графический режим 13h (стандартный VGA 320х200, 256 цветов), входит в основной цикл, с условием выхода из него по ESC, восстанавливает текстовый режим (03h) и возвращает управление DOS-у:
comment + .
"My first intro"
comment end! +
.model tiny ;использовать модель памяти "tiny" (всегда для com-программы)
.code
.486
org 0100h ;указываем компилятору смещение для начала нашего кода
;первые 256 байт в сегменте DOS использует под свои цели
start:
mov ax, 13h ;записываем в регистр AX значение 0013h (AH=0, AL=13h)
int 10h ;вызываем функцию BIOS "10h" которая установит видеорежим
; MAIN LOOP
@mainloop:
in al, 60h ;считываем значение из порта клавиатуры в регистр al
dec al ;уменьшаем значение в регистре al на 1
jnz @mainloop ;возвращаемся на позицию "@mainloop" если результат не ноль
; EXIT
mov ax, 3h ;записываем в регистр AX значение 0003h (AH=0, AL=3h)
int 10h ;вызываем функцию BIOS 10h которая установит текстовый режим
int 20h ;вызывем функцию DOS которая возвращает управление системе
end start
Чтобы скомпилировать это в исполняемый COM-файл, надо выполнить две команды:tasm /m4 intro.asm
Первая создает obj-файл, опция /m4 означает, что надо сделать 4 прохода. Это нужно, чтобы компилятор не вставлял ненужные NOP-инструкции. Вообще, обычно достаточно и 2-х проходов, но я на всякий случай ставлю 4, чтоб уж наверняка :) Вторая команда создаст из obj-файла нужный нам COM. Опция /t говорит линкеру, что нам нужен именно COM (tiny).Чтобы каждый раз не писать эти команды, можно просто сделать bat-файл и запускать его. Ну или придумайте сами некую автоматизацию. Возможно, есть какие-то удобные IDE для DOS-ассемблера, но мне лень было искать.
tlink /t intro.obj
Чтобы долго не топтаться на месте, добавляя по капле функционала, давайте сразу напишем какую-никакую интру с простеньким эффектом, но который охватит сразу несколько задач, которые вам скорее всего придется решать.
Сделаем заполнение экрана пикселями с рандомной позицией с фильтром размытия в цветах оттенков серого (grayscale). Схема рендера одного кадра такая:
1. Рисуем 1000 пикселей белого цвета в случайных позициях экрана
2. Проходим алгоритмом размытия весь экран (усреднение по 4 соседним пикселям)
Делаем это в цикле. В теории получим этакий размытый TV-шум.
Нам понадобится:
1. Установить наш любимый режим VGA 320x200
2. Установить grayscale-палитру (VGA-режим является индексным, где мы имеем палитру из 256 цветов (с индексами 0..255), для каждого мы можем предустановить цвет RGB-компонентами, каждая в диапазоне 0..63. Но я надеюсь вы в курсе.
3. Выделить память для видео-буфера. Зачем нам буфер? В данном случае он нужен, потому что мы собираемся "размывать" (блюрить) содержимое кадра, что подразумевает чтение значений пикселей. Читать из видео-памяти (сегмента A000h) очень плохая идея. Это немедленно вызовет катастрофические тормоза. Не будем сейчас останавливаться на вопросе - почему, просто запомните, что мы ничего не читаем из видеопамяти, только записываем. И да, DosBox не воспроизведет проблему, ему все равно, но на реальном "железе" попытка "блюрить" прямо в видеопамяти обернется слайд-шоу. А так как мы порядочные демокодеры и пишем код который будет хорошо работать не только в DosBox, но и на настоящей DOS-машине, то будем делать все через буфер, хотя это и потребует дополнительных накладных расходов.
4. Далее. Нам потребуется генератор псевдо-случайных чисел, выдающий нам случайное число от 0 до 65535. В режиме 320х200 смещение в сегменте для пикселя лежит в диапазоне 0..63999, но мы будем рисовать и блюрить во все 64кб, то есть в 0..65535 байт, чтобы не усложнять. Для сайзкодинга это обычная практика.
5. Простенькая рутина усреднения по 4 соседним пикселям, вида:
for y=0 to 199 do
for x=0 to 319 do
buf[x,y] = (buf[x+1,y]+buf[x-1,y]+buf[x,y+1]+buf[x,y-1]) / 4;
6. Также нам надо будет копировать содержимое видеобуфера в видеопамять.
7. И возвращаемся в пункт 4, пока не нажат ESC.
Будем разбирать сразу в коде:
comment + .
"My first intro"
comment end! +
.model tiny
.code
.486
org 0100h
start:
mov ax, 13h ; устанавливаем VGA-режим
int 10h
;---------------------------------------------------------------------
mov ax, ds ; формируем адрес сегмента для экранного буфера
add ax, 1000h ; (разберем этот момент чуть позже)
mov ds, ax ; адрес сегмента буфера будем хранить в регистре DS
;---------------------------------------------------------------------
mov dx, 0a000h ; присваиваем регистру ES адрес сегмента видеопамяти
mov es, dx ; (присвоить ES константное значение напрямую нельзя)
;---------------------------------------------------------------------
mov bl, 63 ; устанавливаем grayscale-палитру для цветов 0..63
@pal: ; используем для этого функцию BIOS "1010h"
mov dh, bl ; dh = красный компонент
mov ch, bl ; ch = зеленый
mov cl, bl ; cl = синий
mov ax, 1010h
int 10h
dec bl ; bl - индекс цвета
jnz @pal
;---------------------------------------------------------------------
; ПОЕХАЛИ!
@mainloop: ; ГЛАВНЫЙ ЦИКЛ
;---------------------------------------------------------------------
mov cx, 1000 ; нарисуем в буфер точек с рандомными позициями
@dots:
in ax, 40h ; формируем псевдо-случайное 16-битное число
xadd bx, ax ; на основе значений системного таймера
xor ah, al ; (разберем этот момент позже)
mov di, ax ; это число послужит смещением
mov byte ptr ds:[di], 64 ; копируем байт со значением "64" в буфер
loop @dots ; делаем это 1000 раз
;---------------------------------------------------------------------
xor si, si ; Заблюрим буфер
xor cx, cx ; обнуляем CX
@blur: ; цикл в этом случае повторится 65536 раз
mov al, ds:[si-1] ; то есть мы пройдем попиксельно весь сегмент 64кб
add al, ds:[si+1]
add al, ds:[si-320]
add al, ds:[si+320] ; в al получили сумму 4-х соседних пикселей
shr al, 2 ; (2 битовых сдвига вправо - равносильно делению на 4)
mov ds:[si], al
inc si
loop @blur
;---------------------------------------------------------------------
xor di, di ; ок, давайте быстро скопиуем буфер в видеопамять
xor si, si ; напоминаю, буфер у нас DS, видеопамять ES
mov cx, 16000 ; Будем копировать сразу по 4 байта, DWORD'ами
rep movsd ; 320*200/4 = 16000
;---------------------------------------------------------------------
in al, 60h ; повторяем главный цикл пока не нажат ESC
dec al
jnz @mainloop
; EXIT
mov ax, 3 ; возвращаемся в текстовый режим
int 10h
int 20h ; выходим в DOS
end start
После компиляции у нас получится COM-файл размером ровно 100 байт (хех, я не подстраивался, так совпало), который показывает вот такой эффект:
Невесть что, но ведь у нас еще есть целых 156 байт чтобы добавить 3D с освещением, тенями и музыку! Но не в этот раз. Да и приведенный выше код нуждается в оптимизации. Мы попробуем его уменьшить, но сначала давайте разберем несколько фрагментов кода.
1. Мы лихо откуда-то взяли память для буфера. Хотя казалось бы, мы ничего не запрашивали у системы, а просто добавили к значению в сегменте DS число 1000h (4096 в десятичном виде) и решили что там будет наш буфер размером в 64 кб (справка: на старте COM-программы регистр DS, (как и ES, CS, SS, DX) содержит адрес сегмента, в котором находится наша программа).
Да, так можно. Память выше нашего программного сегмента (куда загружена наша COM-программа и где по умолчанию находится и стек) можно относительно безопасно использовать для любых целей нашего ненормального программирования.
Почему я добавил 1000h к адресу сегмента? 1000h = 4096, а адрес сегмента исчисляется в параграфах равных 16 байтам. В общем, почитайте Вики :) 4096*16 = 65536 байт. То есть ровно +64 кб. Таким образом мы используем для буфера память ровно после сегмента выделенного для нашей программы. Хотя такой точности и не требуется, главное не пересечься.
Мы могли бы сделать все культурно и попросить у DOS-a нужное количество памяти и он бы выдал нам адрес свободной области запрошенного размера, но это потребует лишние байты кода, а мы тут собрались не чтобы байтами раскидываться. Поэтому остается надеяться, что мы не влезем в область, которую использует какая-нибудь резидентная программа. Но, в конце концов, наш DOS-компьютер не управляет атомной станцией.
2. Далее. Что за фокусы с генерацией случайных чисел? Обычно, более менее порядочная функция random'a выглядит чуть сложнее трех строчек. Но мы не можем себе позволить такой роскоши. Так что лично я пришел к такому решению:
in ax, 40h ; читаем в AX из порта ввода-вывода 40h (таймер Intel 8253/8254)
xadd bx, ax ; обмениваем содержимое регистров AX<->BX,
; затем в BX кладем их сумму.
; В роли BX может быть ячейка памяти или другой свободный регистр.
xor ah, al ; ксорим верхние 8 бит на нижние 8 бит в регистре AX
; этот XOR не обязателен, но с ним рандом более качественный
Оптимизация размера
Надеюсь, остальной код вам более менее понятен. Тогда попробую продемонстрировать суть "сайзкодинга". Да, то что выше - это еще не он :) Поехали выкраивать байты!
start:
mov al, 13h ; заменим AX на AL, потому что на старте программы AL=0,AH=0
int 10h ; и нам достаточно присвоить 8-битное значение.
Это минус 1 байт
Стартовые значения регистров почти всегда эксплуатируются для оптимизации размера!
Стартовое значение регистра DX всегда равно значению регистров DS,ES,CS,SS
и обозначает адрес сегмента программы.
Так что мы сделаем так:
add dx, 1000h
mov ds, dx
еще минус 1 байт
Но можно сделать еще лучше:
add dh, 10h
mov ds, dx
Смысл не поменялся, но мы выгадали еще 1 байт
Нам нужно записать в сегментный регистр ES значение "0A000h".
напрямую mov es, 0a000h сделать нельзя, поэтому мы делали это
через регистр общего назначения DX
mov dx, 0a000h
mov es, dx
Однако через стек будет на 1 байт короче:
push 0a000h
pop es
Далее к инициализации палитры:
; на данном этапе мы еще не трогали регистр BX, а на старте программы
; он, как и AX, равен нулю.
mov bl, 63 ; так что мы можем не волноваться, здесь BH=0, BL=63
@pal:
mov dh, bl
mov ch, bl
mov cl, bl
mov ax, 1010h
int 10h
dec bx ; поэтому здесь мы можем смело заменить "dec BL" на "dec BX"
jnz @pal ; а все OP-коды dec/inc с 16-битными регистрами весят 1 байт
; тогда как с 8-битными (половинками регистров) - 2 байта
Минус еще 1 байт!
Идем в главный цикл, к фрагменту рисующему рандомные точки в буфер:
mov cx, 1000
@dots:
in ax, 40h
xadd bx, ax
xor ah, al
xchg di, ax ; заменили "mov" на "xchg". "Xchg" регистра AX с любым другим
; весит 1 байт, тогда как "mov" 2 байта.
; А по логике алгоритма данная замена ничего не портит
mov byte ptr ds:[di], 64
loop @dots
Сэкономили еще 1 байт.
К фрагменту выполняющему "блюр":
xor si, si
xor cx, cx <-- это обнуление CX не нужно, так как после предыдущего
цикла CX и так всегда обнулён. Удаляем эту строку
и выигрываем целых 2 байта :)
@blur:
mov al, ds:[si-1]
add al, ds:[si+1]
add al, ds:[si-320]
add al, ds:[si+320]
shr al, 2
mov ds:[si], al
inc si
loop @blur
Часть с копированием буфера в видеопамять:
xor di, di
xor si, si ; <-- это можно убрать, так как после "блюра" в нашей ситуации
mov cx, 16000 ; регистр SI всегда равен 0. Но здесь надо быть внимательным
rep movsd ; нельзя допускать ситуацию когда по смещению 65535
; происходит запись или чтение WORD, или DWORD! Будет взрыв!
Минус 2 байта
И на коде выхода в DOS мы срежем еще байтик:
mov ax, 3 ; выход в текстовый режим на самом деле не обязателен
int 10h ; никакие правила конкурсов сейчас этого не требуют,
; но я предпочитаю им жертвовать в последнюю очередь
ret ; а вот вместо вызова dos-функции "int 20h" используем "RET"
; это сработает (для COM-программы), если на момент выполнения
; этой инструкции у вас в вершине стека лежит 0000h
Итого, было 100 байт, стало 89. Так и живем.
Да, конечно, финальную глубокую оптимизацию размера следует делать на последнем этапе, когда вы уже добились визуального результата, который вам нравится, а размер COM-файла превышает лимит на 10-30 байт. Если же у вас перебор на сотню байт, то скорее всего нужно что-то переосмыслить глобально. Но мой опыт говорит о том, что нет ничего невозможного, главное не сдаваться. Зато когда вас осенит и вы вдруг уменьшите свою интру с 300 байт до желанных 256-ти, это будет такой праздник, что вам сразу захочется открыть шампанского (я обычно открываю бутылочку сухого красного).
Возвращаясь к выше написанному коду... На самом деле, для порядка нам следует добавить в главный цикл какой-то ограничитель скорости. Например, ожидание обратного хода вертикальной развертки (WaitVerticalRetrace). Для этого нужно перед кодом копирования буфера в видеопамять добавить следующий код:
mov dx, 3DAh ; Wait for vertical retrace
@w1:
in al, dx
test al, 8
jnz @w1
@w2:
in al, dx
test al, 8
jz @w2
Увы, это добавляет целых 13 байт. Но эту штуку можно урезать, удалив строки со 2-й по 5-ю. То есть оставив только w2-проверку. Функцию ограничения скорости рендера до 70 fps оставшийся код все равно будет выполнять исправно. Разбирать этот код мы тут не будем, просто примите за данность :)
Вот, теперь порядок.
Подведем итоги
Думаю, на этом стоит прерваться. На самом деле я хотел написать лаконичную и короткую статью, где изложить основные необходимые сведения в помощь начинающим сайзкодерам, а также тем, кто имеет опыт кодирования на 8-битных платформах, но хотел бы попробовать свои силы на платформе PC (DOS). Но оказалось, что я графоман многие моменты требуют более тщательного разбора. А может и нет. Напишите об этом в комментариях. В конце концов, смысл сайзкодинга в том, чтобы самостоятельно доходить до ручки ответов, совершать открытия, даже если это "велосипед", в этом и заключается основная радость демокодинга. Ну, кроме той, когда вашу интру показали на какой-нибудь пати, например, в Сиднее, Питтсбурге, или Питере, и вы выиграли маечку.
Но, если вдруг данная статья зайдёт, и будет какой-то фидбек, то почему бы нет, продолжим. А теперь полезные ссылки:
Полезные ссылки
Статьи на Хабре на схожую тему:
Ультра-маленькие демки под DOS
Как демо Memories умещается в 256 байт
Подробный разбор 64b intro: radar
Сайты и сообщества сайзкодеров:
http://www.sizecoding.org
https://discord.gg/pZE5rAQrHx
https://lovebyte.party/
https://nanogems.demozoo.org/
Некоторая документация по x86:
80x86 Integer Instruction Set (8088 - Pentium) (содержит инфу о тактах на инструкцию)
x86 Instruction Set Reference
Мои 256-512 байт интры (и другие):
https://demozoo.org/groups/10118/
или https://www.pouet.net/groups.php?which=1889&order=release
Демосценерские ресурсы (международные):
https://www.pouet.net/
https://www.demoparty.net/
https://demozoo.org/
Демосцена в России:
https://retroscene.org/
https://www.demoscene.ru/
https://chaosconstructions.ru/
http://www.dihalt.org.ru/
That's All, Folks!
Комментарии (50)
vassabi
08.01.2025 13:48скажите, а вы не пробовали портировать вот такое https://github.com/dmtrKovalenko/assembly-http-server (вебсервер в 200 строк на асме) на дос?
BiTL Автор
08.01.2025 13:48лично я - нет. И думаю, что портировать это в DOS будет сложнее, чем написать с нуля... Но вообще, есть энтузиасты... Даже Javascript + Canvas в DOS реализовали https://github.com/SuperIlu/DOjS (норкоманы чёртовы!)
BiTL Автор
08.01.2025 13:48P.S.: но так-то вебсервер для DOS существует http://rubbermallet.org/software.html (не в 200 строк на асм, но...)
cdriper
08.01.2025 13:48MS-DOS имеет крайне ограниченную сетевую функциональность и там нет API для сокетов, который нужен, чтобы написать web server
200 строчек на ассемблере под x64 по итогам компиляции легко могут перевалить по размеру за килобайт
a-cherepanov
08.01.2025 13:48Блин, ну вот оно же всего две строчки, коротеньких, но сколько в них детства и молодости.... Аж мурашки побежали...
mov ax, 13h ;
int 10h
Это как будто запах бабушкиных пирогов учуял внезапно, через 30 лет, просто гуляя по улице..
crawlingroof
08.01.2025 13:48и я про тоже )) озвучил прям запах почувствовал, манула по прерываниям
wr0
08.01.2025 13:48Мануалов (в бумажном виде) - видеть не доводилось. Только txt-файлы, в FIDO добытые, с BSD (могу уже ошибаться, четверть века назад дело было).
А вот как сам трейсил (в отладчике от Borland TurboPascal 7.0) - это уже не забывается
(хоть и было в прошлом тысячелетии).
BiTL Автор
08.01.2025 13:48А у меня доступа к FIDO не было, и жил я в некоторой кавказской республике, где все было очень грустно с компьютеризацией. Так что приходилось ездить за 400 км на ближайший приличный радиорынок, там за деньги записывали на дискеты всякий стаф, а позже продавали и CD с хламом выкачанным с BBS-ок и фидошных эхо. Купиш такой BBS#04 CD, и вот тебе и фидо на пару месяцев, там и сорцы всякие на Pascal'e, и коллекция MOD, S3M (и красивеньких плееров к ним), если повезёт - пару-тройку демок (вроде Panic, Second Reality, Crystal Dream...). Так и жили...
checkpoint
08.01.2025 13:48У Вас был стимул и много времени чтобы заниматься чем-то интересным. У тех, у кого был Fidonet, целыми днями отвисали в эхах бездарно тратя своё время на флеймы. Ну почти как сейчас на Хабре. ;)
BiTL Автор
08.01.2025 13:48Ну почему, были кодерские, сценерские эхи, где люди делились знаниями, сорцами, релизили свои поделки на BBS, делали чарты, конкурсы... Ну да, не без флеймов (мне попадались на тех CD-дисках и логи эх и irc-чатов, электронные журналы вроде Harm, Hacker. Но с временным лагом в 1-2 года). В то время как я сидел и кодил в стол, собирая инфу, доки, сорцы по крупицам. Лучше все же иметь фидонет/интернет, чем не иметь :)
ef_end_y
08.01.2025 13:48Я так понял тут 486 нужен только для xadd, а без него можно было бы и на 8086 запустить?
BiTL Автор
08.01.2025 13:48для 8086 надо будет еще movsd заменить на movsw,
и вместо "shr al, 2" сделать две инструкции:
shr al, 1
shr al, 1
ну и да, "xadd bx, ax" заменить на
xchg ax, bx
add bx, ax
Запустится на 8086, но будет 1 фпс в секунду, дай бог ) Все же тут упор сделан не на скорость.
OyminiRole1776
08.01.2025 13:48теперь нужен подробный гайд "как развлечь себя переписав ядро linux на чистом c"
BiTL Автор
08.01.2025 13:48А вот не надо. На сколько я знаю, в ядре Linux есть немало ассемблера, и он там не зря.
checkpoint
08.01.2025 13:48Нужно писать новое ядро, компактное, с ограниченным набором сисколов, и только для одной платформы - RISC-V.
wr0
08.01.2025 13:48Что касается sizeCoding-a:
Однобайтная команда retn (return near, 0C3h) выталкивает со стека слово 0h, а по адресу 0000 в сегменте памяти находится слово 0CD 20h, что на ассемблере есть int 20h, т.е. нормальное завершение программы.
Да, можно и самостоятельно вызвать int 20h, можно и функцию (на память, могу уже ошибиться) 4Ch (в AH, в AL - код, что станет значением параметра ErrorLevel). Здесь, кстати, и стек можно "не выравнивать", всё будет нормально.
По вопросу SizeCoding-же:
интересовался вопросами и безопасности, речь о вирусах. Информацию об этом получал, бОльшей частью, из файлов VirList, что шли вместе с антивирусом DrWeb.
У Aidstest тоже был какой-то подобный файл, но в продукте Игоря Данилова (DrWeb) этот файл
(с описанием вирусов) был наиболее содержательным.
Помню, сам осваивал работу с MCB (Memory Control Block), и научился прятать свой код в недрах самой OS, модифицируя и MCB. Писал, например, программу, что в помимо какого-то полезного действия оставляла в памяти код
("садился" новый обработчик на прерывание таймера, 1Ch, и выводил на экран 80х25 змейку,
что двигалась по текстовому экрану, "съедая" существовавший текст;
тёмные юзвери, конечно, пугались, но эта программка была безвредной. Запускалась - на ПК {286-ых} в компьютерном классе, на уроках информатики).
Да, и с MBR знакомился (с partition table, что содержалась в первом секторе накопителя ЖМД). Большого разнообразия BIOS-ов тогда ещё небыло, и получалось перезаписать сектор с MBR без warning-ов. Хотя, как помню, что находил и такой метод, как размещение в буфере клавиатуры (0х04хх) признаков нажатия клавиши "y", что принималось системой как согласие пользователя на запись в MBR, даже если стоял соответствующий флаг в BIOS.
И: с разновидностями вирусов я был достаточно хорошо знаком
(загрузочные, из MBR, файловые {com, exe, и даже bat были}, резидентные и нет, шифрующиеся и нет, даже свой полиморфик, файлово-загрузочный написал, хоть и не распространял).
Устойчивость системы в Real Time Mode была не такой высокой
(в защищённом режиме - в память другой программы, без прав супервизора, "не залезешь").
Да, помню демку DOD.com, что была из класса 4096. Там была музыка, как помню, для Adlib-совместимой карты (а SoundBlaster был совместим с Adlib). Тогда эти демки просто собирались в отдельном каталоге.
Те "винты", кстати, у меня целы (210MB - первый, может уже его и нет, но содержимое - должно быть на IDE-винте, одном из следующих).
MS-DOS там - 6.22, MS-Win 3.1, 3.11. Из HLL - Borland TurboPascal 7.0, бейсик-интерпретатор, и Borland TurboAssembler 4.0. А чтобы нормально писать на том-же Ассемблере, надо знать, как работает "железо".
andy_p
08.01.2025 13:48Да, size coding-ом занимался, когда bootExe вирус в загрузочном секторе делал. 1990 год.
vesper-bot
08.01.2025 13:48и функцию (на память, могу уже ошибиться) 4Ch
21го прерывания. Да есть такая, но сайзкодинг говорит, что её вызвать будет длиннее, чем int 20h (который для com-файла заменяется на ещё более короткий retn, о чем в статье есть), а значит, лучше не использовать. Как же, помню ещё - B4 4C CD 21
DuhovichSasha
08.01.2025 13:48Если у, человека есть время заниматься хобби по программированию и он работает значит у него основные программы уже не, развиваются и ему скучно
CrashLogger
08.01.2025 13:48На работе почти всегда скучные задачи. Увы, бизнесу не нужны шедевры, ему нужен конвеер. Поэтому и развлекаемся в свободное время своими хобби-проектами )
Wesha
08.01.2025 13:48DosBox 0.74-3 (который не обновлялся с 2019-го)
Ничего страшного, MS-DOS не обновляется с 2001-го!
BiTL Автор
08.01.2025 13:48ну как бы да, но нет... В DosBox 0.74-3 много ошибок в эмуляции железа (он эмулирует не только DOS, но и процессор, видео-карту, звуковую и т.д.), много таких "лояльных" моментов, которые на реальной машине вызовут зависание или тормоза. Многое уже исправлено в форках типа DosBox-x и Dosbox Staging.
Также в DosBox 0.74-3 нет поддежки процессоров выше Pentium. То есть уже MMX там нет (не говоря уже про SSE). Нет поддержки кое-каких звуковых чипов-синтезаторов. Кривая эмуляция Gravis Ultrasound. Вобщем много чего, что давно требует обновления.
Так что страшно, страшно... Мы не знаем что это. Если бы знали...
checkpoint
08.01.2025 13:48Я вот тоже сабя на праздниках развлекаю. Пишу программу Monitor для загрузки и запуска других программ на своей синтезируемой СнК. Задача оказалась нифига не простой, особенно сохранение контекста и восстановление после crash-а, инициализация crt и libc. Без ассемлерного кода - никак. Так глядишь RV32 асм изучу и освою linker.ld скриптинг. ;)
BiTL Автор
08.01.2025 13:48Вот кстати, буквально на днях прошел Dihalt 2025 Winter в Нижнем, и там была такая вот демка: https://www.youtube.com/watch?v=jIQhSY1BZxQ
checkpoint
08.01.2025 13:48Прикольно, спасибо. У меня получается что-то типа RPi Pico, но с польностью открытой аппаратурой и на открытой архитектуре. Нужно добавить всякой звукосинтезирующей аппаратуры в духе Pokey или SID и будет своя демосценнерская платформа. ;)
BiTL Автор
08.01.2025 13:48Вот такое бы вкорячить сразу https://picog.us/ :)
хотя, на самом деле каждый может сам себе эмуляцию любого звукового чипа закодить, я думаю, при нынешних-то мощностях ARM-процессоров.checkpoint
08.01.2025 13:48С Пикогусему у меня коллега балуется на 486-й машине.
PCM требует много памяти для сэмплов и звуковых файлов и он уже есть у меня в виде 4-х канального 12-битного DAC-а. Но это не спортивно, так как реализовано отдельной микросхемой на плате, а не синтезируемой аппаратурой вутри ПЛИС. Хочу заморочиться именно своей аппаратурой для формирования звука в формате высокоскоростного однобитового потока (DSD64).
melodictsk
08.01.2025 13:48Вот всех этих сайзкодеров палкой в мобильную разработку, пусть приложения ужимают.))))
vesper-bot
08.01.2025 13:48наоборот, мобильных разрабов в сайзкодинг :D
но есть нюанс - я читал, что приложения огромные из-за того, что то ли андроид, то ли гугл требуют от разрабов запихивать ассеты под несколько разных разрешений экрана, что раздувает объем приложения. Может, код там и сайзить некуда уже.
Antohin
08.01.2025 13:48Я знаю что будет - они покопаются в ресурсах прог, скажут что все зло от разрешений FHD (или уже QHD стандарт?) и ресурсов/текстур под них, задаунскейлят всё под VGA - и настанет щщастье :) /s
karelbrta
08.01.2025 13:48Я совсем чайник в ассемблере, попробовал традиционный Hello, world написать в NASM, так после компиляции у меня исполняемый файл получился 9,5 килобайт)
crawlingroof
Хабр - торт, понял половину в свете z80. Все так пишут что это так просто (не в упрек ниразу), но надо знать архитектуру, а тут еще прерывания dos. Было интересно, два раза перечитал, частично яснее, но не до конца. Спасибо.
BiTL Автор
ну я почти все необходимые прерывания здесь и привел. Нам же тут с файловой системой, или дисководом не работать :)
Еще с десяток портов ввода-вывода... Но я например на память их-то тоже не помню, при желании нагуглить можно. Или посмотреть чужие исходники (я свои почти всегда публикую вместе с COM-файлом).
PS: для тех, кто не хочет заморачиваться с особенностями архитектуры придумали "фантазийные консоли", там на Lua код, и никаких тебе прерываний и портов. Многим нравится.
EevanW
Архитектуру - да.
А прерывания можно и из справочника тянуть на начальном этапе. Зубков или Юров в помощь. Я особенно Зубкова люблю.
Принципы оптимизации из статьи станут понятны после прочтения учебника.
А вот ограничение скорости, опираясь на вертикальную развёртку это уже, конечно, специфика жанра. Такое только на профильных ресурсах искать. И с опытом.