sfw
nsfw

Результаты поиска по запросу "в байте 8 бит"

Уроки ОСдева №3: блок параметров BIOS

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

1. При попытке открыть образ дискеты Wndows говорит, что она не отформатирована.
2. BIOS не воспринимает дискету как загрузочную и пишет что-нибудь вроде "no bootable device found".
Давайте разбираться. Если помните, в конце прошлого поста наша будущая программа-загрузчик выглядела так:
.386p
CSEG segment use16
ASSUME cs:CSEG, ds:CSEG, es:CSEG, fs:CSEG, gs:CSEG, ss:CSEG
begin:                    cli
                             hlt
CSEG ends
end begin

По сути это просто заглушка, которая при запуске должна останавливать процессор. Причина ругани Виндоус в том, что в нулевом секторе на отформатированном носителе хранится важная структура данных - блок параметров BIOS (BPB). Записав туда же нашу программу, мы его пот+ёрли. Для того, чтобы этого избежать, нам придётся воссоздать BPB в тексте программы. Для этого нужно знать геометрию носителя. К счастью, в наше время сохранился только один тип дискет.

Непроверенный метод: возможно, если с помощью утилиты debug записать программу не в начало сектора, а со смещением, достаточным, чтобы пропустить оригинальный BPB, то он не пострадает, но я не проверял. По-моему, debug всё равно забьёт остатки сектора каким-то мусором.

Во-первых, измените программу вот так:

.386p
CSEG segment use16
ASSUME cs:CSEG, ds:CSEG, es:CSEG, fs:CSEG, gs:CSEG, ss:CSEG
begin:                     jmp short execute
                             nop
execute:                 cli
                             hlt
CSEG ends
end begin

У нас появились две новые инструкции: jmp short и nop. Последняя - просто пустой оператор. Процессор пропускает его, не выполняя никаких действий. Занимает 1 байт. jmp - инструкция перехода. jmp short - переход в пределах 127 байт от текущего положения. Исполняется гораздо быстрее jmp, так что везде где возможно - используйте его. Занимает 2 байта. execute - название метки, на которую указывает инструкция jmp short.

Зачем всё это и зачем nop? BPB должен располагаться строго в трёх байтах от начала нулевого сектора. Эти три байта и занимают инструкции jmp short execute и nop. Таким образом, когда программа начнёт исполняться, первой инструкцией, которую выполнит процессор, будет пропустить BPB. В противном случае он бы попытался исполнить его как код, что привело бы к катастрофе.
Теперь давайте вставим сам блок параметров BIOS между nop и меткой execute.

.386p
CSEG segment use16
ASSUME cs:CSEG, ds:CSEG, es:CSEG, fs:CSEG, gs:CSEG, ss:CSEG
begin:                     jmp short execute
                             nop

                BPB_OEMname          db    'BOOTDISK'
                BPB_bytespersec       dw    512
                BPB_secperclust        db     1
                BPB_reserved            dw    1
                BPB_numFATs           db     2
                BPB_RDentries          dw    224
                BPB_sectotal             dw    2880
                BPB_mediatype         db     0F0h
                BPB_FATsize             dw     9
                BPB_secpertrack        dw     18
                BPB_numheads         dw     2
                BPB_hiddensec          dd     0
                BPB_sectotal32          dd     0
                EBPB_drivenum         db     0
                EBPB_NTflags            db     0
                EBPB_extsign            db     29h
                EBPB_volID               dd     0
                EBPB_vollabel            db     'BOOTLOADER '
                EBPB_filesys              db     'FAT12   '

execute:                 cli
                             hlt

CSEG ends
end begin

BPB - это блок данных, и здесь мы впервые объявляем переменные. В TASM это выглядит так: BPB_OEMname (имя) -пробел- db, dw, dd или dq -пробел- 'BOOTDISK' (значение). Имени может и не быть, но тогда к переменной нужно будет обращаться по смещению, это не очень удобно. DB, DW, DD и DQ - сокращение от define byte (word, double word или quad word) - обозначают размер переменной. Соответственно, 1, 2, 4 или 8 байт. Инстркция этого типа позволяют объявлять целые серии значений через запятую: myvalue dw 2, 5, 165, 776. С помощью инструкции db можно объявлять строки: mytext db 'Allo, Yoba!' Обратите внимание, что в плоском бинарнике переменные при компиляции не выносятся в какую-то специальную область данных. В исполняемом файле они будут именно там, где вы их объявили в тексте программы. Ещё важный момент: имена переменных только для вашего личного пользования, в исполняемый файл они не попадут, так что вы не обязаны копировать названия у меня. Теперь давайте посмотрим, что за информация хранится в BPB.

BPB_OEMname - 8 байт: по идее здесь должно быть название производителя, но по факту вы можете писать что угодно, никто на это значение не смотрит.
BPB_bytespersec - 2 байта: размер сектора в байтах, для дискет как правило 512.
BPB_secperclust - 1 байт: число секторов в кластере. Про кластеры мы поговорим позже, но в случае с дискетами секторы и кластеры соответствуют друг другу.
BPB_reserved - 2 байта: число зарезервированных секторов, недоступных файловой системе. В нашем случае такой один, это наш загрузочный сектор.
BPB_numFATs - 1 байт: количество FAT (file allocation table), таблиц распределения файлов. Так как носители информации (особенно дискеты) подвержены порче, а FAT - очень важная часть файловой системы, для неё часто делается резервная копия.
BPB_RDentries - 2 байта: количество записей в корневой директории (Root Directory). Про корневую директорию тоже будем говорить в другой раз, но пока можете представить её как список файлов с указанием их физического расположения на носителе.
BPB_sectotal - 2 байта: число секторов на диске, если их не больше 65535. Если больше, здесь должен быть 0.
BPB_mediatype - 1 байт: тип носителя. F0 - код для 3,5-дюймовой дискеты с 18 секторами в дорожке.
BPB_FATsize - 2 байта: размер одной FAT в секторах.
BPB_secpertrack - 2 байта: число секторов в дорожке.
BPB_numheads - 2 байта: число головок.
BPB_hiddensec - 4 байта: количество скрытых секторов перед загрузочным, в нашем случае 0.
BPB_sectotal32 - 4 байта: число секторов, если их больше 65535. Если меньше, здесь должен быть 0.

Здесь стандартный BIOS Parameter Block заканчивается и начинается расширенный, который появился в поздних версиях DOS.

EBPB_drivenum - 1 байт: бесполезная переменная, хранящая номер привода, в который был вставлен носитель при форматировании.
EBPB_NTflags - 1 байт: флаги Вин НТ. Если установлен бит 0, необходимо проверить носитель на битые секторы. Значения других флагов не знаю.
EBPB_extsign - 1 байт: признак расширенного BPB. Для нашей версии должно быть 29h.
EBPB_volID - 4 байта: случайный номер, который присваивается при форматировании. В общем бесполезен.
EBPB_vollabel - 11 байт: имя носителя.
EBPB_filesys - 8 байт: имя файловой системы.

Если вы теперь заново скомпилируете программу и запишите на дискету, то она отлично откроется в Windows. Первая проблема решена, но осталась вторая: дискета всё ещё не опознаётся как загрузочная. Вспоминаем: для этого последние 2 байта загрузочного сектора должны иметь значения AAh и 55h. Добавим ещё две строчки в нашу программу:

.386p
CSEG segment use16
ASSUME cs:CSEG, ds:CSEG, es:CSEG, fs:CSEG, gs:CSEG, ss:CSEG
begin:                     jmp short execute
                             nop

                BPB_OEMname          db    'BOOTDISK'
                BPB_bytespersec       dw    512
                BPB_secperclust        db     1
                BPB_reserved            dw    1
                BPB_numFATs           db     2
                BPB_RDentries          dw    224
                BPB_sectotal             dw    2880
                BPB_mediatype         db     0F0h
                BPB_FATsize             dw     9
                BPB_secpertrack        dw     18
                BPB_numheads         dw     2
                BPB_hiddensec          dd     0
                BPB_sectotal32          dd     0

                EBPB_drivenum         db     0
                EBPB_NTflags            db     0
                EBPB_extsign            db     29h
                EBPB_volID               dd     0
                EBPB_vollabel            db     'BOOTLOADER '
                EBPB_filesys              db     'FAT12   '

execute:                 cli
                             hlt

               org 510
                dw 0AA55h

CSEG ends
end begin

Команда org 510 заполнит нулями место от текущей позиции до 510 байта, а в последние два мы поместили метку загрузочного сектора. Вуаля, проблема 2 решена.

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

Нужны ли туторы по UEFI?
Да
89(68,99%)
Нет
8(6,2%)
Я не знаю, заебал свою гиковскую xуйню сюда постить, вали на гитxаб!
32(24,81%)

Урок ОСдева №7: первичный загрузчик, финал.

В прошлый раз мы написали процедуру загрузки данных и использовали ее для того, чтобы 
поместить корневую директорию нашей дискеты в оперативную память сразу после собственно
программы-загрузчика по адресу 07C0h:0200h. План действий на сегодня:

-Найти в КД номер первого кластера файла.
-Загрузить первый кластер.
-Следуя по цепочке записей в FAT, загрузить остальные кластеры.

Перед тем, как кодить дальше, давайте  разберёмся, что такое КД и как её использовать для
поиска файлов*.

По сути корневая директория в FAT12 - это таблица, в которой каждому файлу или
поддиректории соответствует одна 32-байтная запись. Давайте посмотрим, что в ней есть.

Байты 0-10: имя файла в формате 8:3. Этот формат подразумевает, что имя файла занимает
ровно 8 байтов, а расширение - 3. Если имя файла меньше 8 символов, оно дополняется
пробелами: так, файл 'loader.bin' в КД будет проходить под именем 'LOADER  BIN'.

Байт 11: атрибуты записи. Набор флагов, позволяющий придать записи особые свойства.
          00000001b = только для чтения
          00000010b = скрытый
          00000100b = системный
          00001000b = метка раздела
          00010000b = директория
          00100000b = архив
          00001111b = LFN (long file name), запись имеет особый формат, поддерживающий длинные
                              имена файлов.

Байт 12: зарезервирован для Windows NT.

Байт 13: время создания в десятых секунды (почему-то 0-199 согласно OSDev Wiki).

Байты 14-15: время, когда был создан файл. Младшие 5 бит - секунды/2 (то есть при интерпретации
значения, например, для вывода на экран, эту часть надо умножать на 2). Следующие 6 - минуты.
Последние 5 бит - часы.

Байты 16-17: дата создания файла. Примерно та же история. День(0-4), месяц(5-8), год(9-15).

Байты 18-19: дата последнего доступа в том же формате, что и дата создания.

Байты 20-21: старшие 16 бит номера первого кластера файла. В FAT12 и FAT16 не используется.

Байты 22-23: время последнего изменения в том же формате, что и время, когда был создан файл.

Байты 24-25: дата последнего изменения в том же формате, что и дата создания.

Байты 26-27: младшие 16 бит номера первого кластера файла.

Байты 28-31: размер файла в байтах.
Довольно много информации, но нас интересуют только два поля: имя записи и младшая часть номера
стартового кластера (старшая половина в FAT12 не используется). Вырисовывается в общих чертах
алгоритм поиска файла? Если нет, я помогу:

1. Переходим к началу КД
2. Считываем имя записи
3. Сравниваем имя записи с именем искомого файла
4. Если имена совпали, файл найден, SUCCESS!
5. Записи кончились?
6. Если кончились - файла нет, аварийно завершаемся
7. Переходим к следующей записи
8. goto 2
Вот таким нехитрым способом мы сможем найти на диске начало файла или, если его нет, уведомить
об этом пользователя и свернуть выполнение загрузчика. Я решил, начиная с этого поста, не
перепечатывать весь листинг из предыдущих уроков. Вместо этого я приложу ссылку на файл в
конце. Это позволит вместить в пост больше полезной информации, не растягивая его до
нечитабельных размеров. А теперь давайте выполним наш поисковый алгоритм в коде. После
call read_sectors пишите:

                   mov cx,BPB_RDentries
                   mov di,0200h
                   mov si,offset fname
                   mov bp,si

next_entry:   mov ax,cx
                   mov bx,di
                   mov cx,11
                   rep cmpsb
                   mov si,bp
                   mov di,bx
                   mov cx,ax
                   je load_FAT
                   add di,32
                   loop next_entry

                   mov ah,3
                   xor bh,bh
                   int 10h

                   mov ax,1300h
                   mov bx,0007h
                   mov cx,22
                   mov bp,offset fname
                   int 10h

                   cli
                   hlt
Что всё это значит? В строчке mov cx,BPB_RDentries мы устанавливаем счётчик основного
цикла. Напоминаю, что в переменной BPB_RDentries у нас хранится число записей корневой
директории. 0200h - смещение загруженной в RAM КД. В SI мы помещаем смещение строки с
именем искомого файла. Кстати, впишите в переменные fname db 'LOADER  BIN'. После этого
мы сохраняем это же смещение в регистре BP. Это может быть пока неочевидно, но позже вы
поймёте, зачем.

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

mov cx,11 - установка счётчика вложенного цикла. Имена в FAT12 хранятся в формате 8:3,
значит, нам нужно сравнить две строки по 11 символов. Надеюсь, тут вопросов нет?
Инструкция cmpsb сравнивает значения двух байтов (в нашем случае символов), находящихся
в DS:SI и ES:DI. Префикс rep повторяет инструкцию, пока не обнулится счётчик в CX.
Далее мы восстанавливаем счётчик основного цикла в CX, смещение текущей записи в DI и
смещение строки с именем файла в SI. В старых версиях здесь у меня были пары инструкций
push/pop, но потом я подумал, что трансфер из регистра в регистр быстрее, чем обращение
к стеку, и поменял. Никогда не вредно сэкономить пару циклов.

Если в результате rep cmpsb все символы совпали, в регистре флагов будет установлен бит
ZF. Команда je load_FAT выполняет переход к метке load_FAT если флаг ZF установлен.
В случае если строки не совпали, мы переводим DI к следующей записи в КД и продолжаем
цикл командой loop next_entry. Тут бы можно было и закончить, но нужно обработать
отсутствие файла. С этим набором инструкций мы уже знакомы по предыдущему посту.
Первый блок возвращает положение курсора в DH,DL, а второй выводит от этой позиции
сообщение. Отличается только само сообщение. Вместо 'Disk read error.' мы выводим строку
с именем файла. Внимание, тут небольшой хак. Идея в том, чтобы вывести следующий текст:
'{filename} not found!'. Вызвать вывод строки два раза, но зачем? Если поместить в
разделе переменных текст ' not found!' сразу после переменной fname, а при вызове int 10h
указать в CX не 11 символов, а 22, то выведется сначала имя файла, а потом ' not found!'
Конечно же, этот текст обязательно должен быть сразу после fname. Добавьте строчкой ниже
db ' not found!' После этого останавливаем процессор парой команд cli и hlt. Не так-то
сложно, да? Впрочем, файл ещё нужно загрузить.

Для этого нам нужно будет загрузить в память FAT и разобраться, как ею пользоваться.
Давайте начнём с первой задачи, она чисто техническая и не требует умственного напряжения.
После hlt набирайте:

Load_FAT:          mov ax,[di+26]
                         mov cluster,ax
                         mov ax,BPB_reserved
                         mov cx,total_FATs_size
                         mov bx,BPB_RDentries
                         shl bx,5
                         add bx,0200h
                         mov FAT_offset,bx
                         call read_sectors
В строчке mov ax,[di+26] мы считываем из записи КД номер первого кластера файла, а затем
сохраняем его в переменной cluster. Далее, мы помним, что FAT у нас идут сразу после
зарезервированных секторов, поэтому в AX помещаем BPB_reserved. В CX у нас будет число
секторов, которое надо загрузить, то есть total_FATs_size. Загружать FAT будем сразу после
КД, то есть в 07С0h:0200h+размер КД. Размер КД = число записей КД*размер записи (32 байта).
Помещаем в BX число записей (BPB_RDentries), умножаем на 32 (shl bx,5 эквивалентно умножению
на 32, но выполняется быстрее) и добавляем 0200h. Готово! Сохраняем на будущее в переменной
FAT_offset (кстати, объявите её рядом с прочими) и вызываем read_sectors.

А теперь время вернуться к теории. Что такое FAT? Не поверите, но это тоже таблица, и её
структура ещё проще, чем у КД. Каждая запись в FAT соответствует кластеру на диске. FAT
можно назвать оглавлением диска (украл с OSDev Wiki). Кластер может быть свободен, занят
частью файла, зарезервирован ОС или испорчен. Если кластер хранит часть файла, то его
запись в FAT будет содержать номер следующего кластера файла. Понятно? Зная номер первого
кластера файла, мы можем загрузить его в память, потом заглянуть в FAT, найти нужную запись
и считать номер следующего кластера. Повторять до конца файла. Звучит просто, но, как
всегда, есть большое "НО"! Размер записи в FAT12 - 12 бит. Мы не можем оперировать
12-битными ячейками. Мы можем считать 8 или 16. То есть, если мы загрузим в AX начало FAT,
то в регистре окажется первая запись и часть второй. А если сдвинемся на один байт, то
получим конец первой записи и всю вторую. Давайте попробую проиллюстрировать для
наглядности. В верхней строчке будет часть FAT, разделённая на записи, а в нижней она же,
но поделенная на 8-битные куски.

0 0 0 1 0 1 1 1 0 0 1 0|0 1 1 1 0 0 1 0 1 0 0 0|0 0 1 0 0 1 0 0 0 1 1 1          3 Записи.
0 0 0 1 0 1 1 1|0 0 1 0 0 1 1 1|0 0 1 0 1 0 0 0|0 0 1 0 0 1 0 0|0 1 1 1         4,5 байта.

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

                             push 0050h
                             pop es
                             xor bx,bx
read_cluster:           mov ax,cluster
                             sub ax,2
                             movzx cx,BPB_secperclust
                             mul cx
                             add ax,datasector
                             call read_sectors
                             mov ax,cluster
                             mov si,ax
                             shr ax,1
                             add si,ax
                             add si,FAT_offset
                             mov dx,[si]
                             mov ax,cluster
                             test ax,1
                             jnz odd_cluster
                             and dx,0000111111111111b
                             jmp short done
odd_cluster:           shr dx,4
done:                     mov cluster,dx
                             cmp dx,0FF7h
                             jb read_cluster
Финальный рывок. Первое, что мы делаем - устанавливаем сегмент для загрузки файла. Так как
BIOS нам больше не указ, выбирать можно самостоятельно. Я бы с удовольствием отправил его
в 0000h:0000h, но первые 1280 байт заняты важными вещами, о которых поговорим позже.
Ближайший свободный участок RAM - 0050h:0000h (или 0000h:0500h, это тот же самый адрес
если вы вдруг забыли правила адресации сегмент:смещение). Обнуляем BX, так чтобы пара
ES:BX указывала на 0050h:0000h. Считываем в AX номер первого кластера файла. Дальше мы
вычитаем 2 из этого номера. Зачем? Затем, что значения 0 и 1 в FAT зарезервированы и не
используются в качестве номеров, а номер, указанный в таблицах, на 2 больше, чем правильное
значение. Да, это идиотизм.

Загружать будем не сектор, а кластер (что в нашем случае одно и то же, но всё-таки),
поэтому в качестве числа секторов помещаем в CX переменную BPB_secperclust и на неё же
умножаем номер кластера. AX*CX в данном случае дадут нам номер первого сектора нужного
кластера. А так как кластеры в FAT начинают считаться от начала области данных,то для
абсолютного значения добавляем к AX datasector. Готово. Вызываем read_sectors и загружаем
первый кластер файла в RAM.

Дальше будет немножко математической магии, объяснять которую я не буду. Если интересно -
разберётесь самостоятельно, там не так сложно. Остальным предлагаю просто поверить, что
смещение записи кластера внутри FAT = 3/2 номера кластера. Значит, берём в AX номер
кластера, его же помещаем в SI, делим AX на 2 и прибавляем к SI. Вуаля, смещение
записи от начала FAT найдено. Добавляем к нему смещение FAT_offset и считываем в DX
значение записи.

Теперь надо проверить, чётная ли запись. Для этого опять берём в AX номер кластера и
делаем сравнение с 1. Если флаг ZF не установлен (то есть 0 бит значения равен 1),
значит, номер записи - нечётный, переходим к odd_cluster и сдвигаем значение вправо на
4 позиции. Если чётный - делаем логическое "И" с маской 0000111111111111b и обнуляем
тем самым 4 старших бита. Теперь у нас есть содержимое нужной записи без всяких
посторонних хвостов, то есть номер следующего кластера. Сохраняем его в переменной
cluster. Дальше у нас идёт сравнение с номера с числом 0FF7h. Дело в том, что,
начиная от этого значения в FAT идут специальные коды, которые могут означать конец
файла, испорченный сектор и т.д. Для нас это значит, что если в качестве номера
кластера мы получили 0FF7h или больше, продолжать загрузку не имеет смысла. Поэтому
продолжаем цикл только если DX меньше 0FF7h. Я умышленно оставляю здесь дыру и
предлагаю всем заинтересованным попытаться самостоятельно сделать обработку ошибки,
связанной с битым кластером (код 0FF7h). Код конца файла, кстати, 0FF8h. Вся необходимая
для этой задачи информация и примеры кода уже есть в этом посте.

А мне остаётся только добавить в конце три строчки:

                    push 0050h
                    push 0000h
                    retf
Этот набор команд мы уже помним из старых постов. Помещаем в стек сегмент, потом
смещение, и передаём управление загруженному файлу командой retf. Поздравим себя!
Первичный загрузчик готов. Да, он умеет немного, но и задача у него всего одна:
найти загрузчик второго уровня, поместить его в RAM и отдать штурвал. Если вы
скомпилируете файл без инструкций org 1FEh и dw 0AA55h, то увидите, что программа
занимает всего 447 байт. Значит, у нас есть в запасе ещё 63. Как раз должно
хватить на проверку успешного считывания кластеров. У меня вместе с ней вышло 497
байт. Можете подсмотреть в приложенном файле, хоть это и неспортивно. Если вы
поместили загрузчик на дискету и получили в bochs (или на реальной машине) вот такой
экран, то всё работает как надо!

Plex86/Bochs UGABios (PCI) current-cvs 08 Jul 2014 This UGA/UBE Bios is released under the GNU LGPL
Please visit :
. http://bochs.sourceforge.net . http ://www.nongnu.org/vgab ios
Bochs UBE Display Adapter enabled
Bochs BIOS - build: 07/10/14
$Revis ion: 12412 $ $Date: 2014-07-10 09:28:59

Чистая дискета:
https://drive.google.com/file/d/1Bold4ds8oEruHQ7fJZKHglVo7A2Vc5MR/view?usp=sharing

Листинг:
https://drive.google.com/file/d/1Q5EtKX5kyF4MWcBeD8a6Jz5cPtqZja9C/view?usp=sharing

Bochs:
https://drive.google.com/file/d/16k2Gpr7oPSekq4rAhmtBV0IPnIteDLlE/view?usp=sharing

* FAT поддерживает вложенные директории, и они ничем принципиально не отличаются
от корневой, так что всё нижеизложенное касается и их. 

Презентация CD (8 марта 1979 года, Эйндховен, Нидерланды)

PHILIPS
introduce
COMPACT

fll,фото,CD,История,it

,Всё самое интересное,интересное, познавательное,,разное,айтишное,айтишные приколы и айтишный юмор

Какой самый большой размер изображения формата PNG в наименьшем количестве байт?

Программист Дэвид Фифилд с сайта bamsoftware.com заинтересовался данным вопросом и пришел к удвительным выводам. Он создал картинку, размеры которой не умещаются в оперативной памяти, вызывая зависание системы. 

Запакованный bzip2-файл ( https://www.bamsoftware.com/bzr/deflate/spark.png.bz2 ) имеет размер всего 420 байт. В нем лежит сжатый PNG-файл. При распаковке картинка занимает 6132534 байт или 5,8 мегабайт и имеет разрешение 225000 на 225000 пикселей (50,625 гигапикселей). Для просомтра такого изображения требуется около 141 гигабайта оперативной памяти. На незащищенных от подобной «бомбы» фото-хостингах, форумах и прочих сайтах, где можно загрузить и посмотреть картинку, таким образом можно запросто положить сервера.

PNG использует алгоритм сжатия DEFLATE в библиотеке zlib.Сначала DEFLATE заменяет повторяющиеся строки указателями, при этом каждая пара бит кодирует 258 одинаковых байтов с нулями. Степень компрессии составляет 1032 к 1.Затем за дело берётся bzip2, который сжимает длинный набор одинаковых значений в максимально компактный вид.Картинка почти полностью состоит из нулей, внутри находится секретное послание. Для лучшего сжатия используется однобитное цветовое пространство, хотя большинство программ для рендеринга графики всё равно резервируют по три бита на пиксель и разворачивают картинку в 141,4 гигабайта.

Вскрытие формата OJD

Кому лень читать интересную историю, вот ссылка на конвертер с инструкциями, как превратить .ojd в .mp4https://drive.google.com/file/d/1GlMXGss0R2-jYP0TOZ_2Y1xzl9Ugar7l/view?usp=sharing
инструкции в readme.txt применимы к unix/linux, но всё работает и на WSL под виндой.
Ну ладно, а кому не лень и таки интересно, услышьте же охуительную историю о том, как я этот самый конвертер собрал!
Заранее предупреждаю - я нихрена не смыслю в аудио и видео, я просто программист на C. всю инфу про H264 и AAC я нагуглил и вычитал в коде старой версии ffmpeg; если тут есть эксперты, простите меня заранее, если какую-нибудь хуиту скажу.
НАЧАЛО ИСТОРИИ. ТАИНСТВЕННОЕ ВИДЕО НА РЕАКТОРЕ? СКАНДАЛЫ, ИНТРИГИ...
Ну так вот, ушел я тут, значит, в отпуск на пару недель, и тут вижу интересный пост от Иисуса:
Удивительная история - файл с видео, который сам Иисус не может открыть!
Вместе с многоуважаемыми пидорами из комментов начал гуглить, с превеликим любопытством - случай и правда таинственный, не находилось вообще ничего толкового. С большим интересом тред пялился на хекс-дампы, которые Иисус предоставил в начале ветки ( http://joyreactor.cc/post/4899255#comment23718856 ), особенно на повторяющееся ASCII слово qbox...
И наконец товарищ http://joyreactor.cc/user/nun-buoy , а затем и товарищ http://joyreactor.cc/user/dadv нашли совпадение - старый проприетарный аппаратный видео/аудио кодек MG1264! Эта штуковина была вроде как сделана с рачетом на мобильные девайсы, что совпадало с инфой от Иисуса (видео со старого телефона).
Сообща нами были найдено следующее:
* Слитые доки по версии 1.1 кодека: https://manualzz.com/doc/7153241/mg1264
* Чудом сохранившийся кусок самопального муксера на C#: https://csharp.hotexamples.com/site/file?hash=0xed6a386bc80b4638eac9ea030e141c8312db2507c58453306c1ab71598c2b8f2&fullName=VideoStreamCodecs/Formats/QBox.cs&project=ctapang/GPUCyclops (толку от этого в результате не вышло, просто интересно почитать)
* Полностью сохранившаяся либа для работы с аппаратным кодеком: https://tipok.org.ua/node/13, плюс патч для ffmpeg 0.6, включающий в себя демуксер для проприетарного формата qbox: https://gist.github.com/lynxis/3033513 (последнее в результате стало основным столпом решения проблемы)
Ручки уже основательно чесались, так что я скачал те самые доки и стал читать и сравнивать с хекс-дампами из треда.
Те самые qbox-ы оказались форматом контейнеров для AAC и H264 пакетов. В доках был описан формат самих qbox-ов:

Действительно, в хекс-дампах Иисуса прослеживались эти самые qbox-ы, в основном все как описывалось (http://joyreactor.cc/post/4899255#comment23720396), но с одним нюансом. Первые 8 байт файла не являлись частью какого-либо qbox-а. "Ну мало ли, наверное, проприетарный хедер", подумал я. Все казалось достаточно однозначно: ojd файлы - наверняка просто qbox аудио/видео стрим в формате "что энкодер высрал, то в файл и запишем" (в примерах из доков MG1264 такие файлы назывались .qbx), плюс в начало дописаны 8 байт для точной идентификации типа файла или еще чего-то в этом духе. Из интереса я еще некоторые время попытался эти 8 байт нагуглить, проверил пару баз файл хедеров, но это мне ничего не дало.
Размер первого qbox-a
???	(13744 байта, считая	magic number
этот хедер)	**	/,Иисус,Исусе,geek,Прикольные гаджеты. Научный, инженерный и  айтишный юмор,программирование,реактор помогающий,много букаф,Истории,длиннопост
Стоит отметить, что тут все, блять, все в big-endian. Я раньше никогда с такой херней не работал, так что тут все немножко ломало мой привыкший к little-endian мозг =)
Тут стоит отметить один неприятный нюанс - я это далеко не сразу заметил, но несмотря на то, что те самые доки вроде бы жестко гарантируют, что в первом H264 видео qbox-е будут только NAL-ы SPS-PPS, в реальности тут в первом qbox-е выходит последовательность AUD-SPS-PPS-IDR (это можно увидеть здесь: http://joyreactor.cc/post/4899255#comment23718856 у каждого NAL-а 4 байта стандартного старткода заменено на префикс-размер, так что их можно легко подряд читать, не парясь с полным декодированием).
Для наглядности:
00000000
00000010
00000020
00000030
00000040
00000050
00000060
00000070
00000080
00000090
04 61 00 аО 36 00 03 ff 00 00 35 bO	71	62	6f	73
oo 02 0002001000 07	72	44	ba	al
09 10^^^Р^ТЯ27 64	40	le	ас	2c
а6 eQ_a0 c0 а3 00 °°,U	Ifl	flfl
ee 06 e2 c0|00	00	35	66|25
b3 04	83 7a 29 3f с1б^ГЯа
Тут выделены размеры (которыми заменены старткоды). Начинается каждый NAL так: 1 бит == 0, 2 бита == реф индекс (типа группа типов), дальше 5 битов ID (nal_unit_type: 9==AUD, 7==SPS, 8==PPS, 5==IDR), это все гуглится по запросу "ISO/IEC 14496 pdf". Короче, получается, что вместо чистого конфига тут еще и данные видео есть.
Если бы я знал хоть что-то об H264, я наверняка бы заподозрил, что 13744 байта как-то слишком дохуя для одних SPS-PPS. Но я ничего не знал и ничего не заметил, так что подозрений об актуальности и возможной устаревшести дока относительно ojd ко мне в душу не закралось...
КАК Я ЕБАЛСЯ С ФОРМАТОМ OJD
Ну, в общем, охуительно уверенный в себе, я постучался к Иисусу в личку, и он мне скинул .ojd файл. Дальше я нашел github FFmpeg-а, клонировал, и стал тыркаться. Приладить тот патч с tipok.org.ua к современной версии FFmpeg-а оказалось слишком сложно, но благо в репозитории был бранч подходящей версии 0.6.7 (патч писался для 0.6.1), я чекаутнул бранч и приладил на него qbox.c, qbox.h и прочие диффы из патча, нужные для работы демуксера. Нагуглив, как с этим ffmpeg вообще работать (лол), легким движением хвоста... эээ... tail-а я обрубил первые 8 байт файла ojd и попытался скормить результат свежескомпиленному ffmpeg-0.6.7.
Естественно, просто так нихуя не сработало.
Покопавшись в ffmpeg-е (выяснилось, что у него, по крайней мере, в версии 0.6, компилится дебаг-билд ffmpeg_g, который заходит с gdb), выяснил, что первый qbox парсится нормально, но со вторым возникают проблемы. Применив xxd, я обнаружил странное: второй qbox начинался через какой-то промежуток после ожидаемого конца первого.
Для наглядности покажу на изначальном .ojd (без обрубленного начала):
Тут хорошо видно "хедер" в начале, после него qbox с размером 0x35b0, и по-хорошему, если это qbox стрим, как в доках, то следующий qbox должен начаться на 0x8+0x35b0==0x35b8, но вот нихуя, начинается он аж на 0x3690!
Изрядно прифигев с такого расклада событий, я тупо пялился на этот хекс-дамп, и тут заметил в нем нечто крайне интересное.
А вы, дорогие мои читатели, заметили это? Заметили, блять, с чего начинается строка 00003600?
0461 00a0 **** 03ff
Практически идентично хедеру в начале файла!!!!
Я охуел. Я сделал grep. Я охуел еще больше.
Это пиздец. Каждые 0x200 (512) байт .ojd файла начинаются с 8-байтного префикса. Байты 5-6, по ходу, какой-то... sequence number? Причем нормальный счет идет только в байте 6, в байте 5 же происходит какая-то полная поебень, но там явная закономерность (36-06-13-02-02-03-21-00 повторяются, но потом меняются с переносом единицы из 6 байта, короче, что-то крайне странное).
Я не знаю, что это за наркомания. Я подозреваю, что это след какой-то крайне допотопной (возможно самопальной?) блочной файловой системы родной ОС девайса, на который таинственные видео записывались. Если кто тут узнал-распознал эту херню, объясните мне, пожалуйста, мне очень любопытно!
Кстати, забавный нюанс - в видео Иисуса этот сраный формат немножко поломал конец qbox-стрима, то есть последний qbox, который не влез в последний блок ojd, оказался таки обрезан. Это видно в комменте Иисуса с дампом конца ojd файла здесь, если посчитать размер последнего qbox-а: http://joyreactor.cc/post/4899255#comment23719489 а после конца этих блоков зачем-то идет очень дохуя нулей =) но не буду на этом заостряться.
Ну, делать нечего, по-быстрому я написал конвертер, который из каждого 512-байтного блока в файле .ojd выкидывал первые 8 байт и записывал результат в файл .qbx.
Дело пошло веселее. С выкинутыми префиксами блков конец каждого qbox-а стал совпадать с началом следующего, и ffmpeg... послал меня нахуй, потому что что-то оказалось не так с аудио. Оказывается, демуксер не генерировал почему-то аудио-стрима, хотя я точно видел в файле аудио-qbox-ы.
Решил я разобраться с этим позже. Путем нехитрых хаков кода, я смог декодировать видео в валидный mp4 файл с... ебаным слайдшоу, судя по частоте кадров. Настроив вручную взятый от балды фреймрейт, я еще раз декодировал видео, и... моему взору предстали обещанные Иисусом ебущиеся карлики! Успех!!!!!!!!!
Ебались они, правда, как-то подозрительно слишком резво, так что я сделал заметочку себе на будущее - разобраться с фреймрейтом ебаным. Но сначала аудио.
И тут меня ждал пиздец.
КАК Я ЕБАЛСЯ С АУДИО
В ебаных доках черным по белому было написано, что первый аудио qbox должен содержать аудио конфиг в формате AudioSpecificConfig из ISO/IEC 14496-3. Но конфиг-флага ни на одном из аудио-qbox-ов не нашел ни демуксер, ни я вручную. Правда, в доках и не было написано, что этот флаг обязательно на qbox-е с аудио-конфигом должен быть... Я решил поэкспериментировать и взять просто первый аудио-qbox и попытаться дешифрануть его как конфиг.
Меня ждало жестокое разочарование.
FFmpeg матерился по-черному, закидывал меня эррорами, говорил мне, что конфиг говно. Поебавшись немного, я горько вздохнул, вбил в гугле "ISO/IEC 14496-3 pdf" и начал вникать.
Как видно, первые 5 бит должны указывать на тип объекта. Судя по докам MG1264, этот тип должен быть 2 (AAC_LC).
И тут уже незадача - все содержимое qbox-ов с аудио начиналось на 0x21:
дас ист крайне печально, ведь первые 5 битов 0x21 - это вовсе не 2 (AAC_LC), а очень даже 4 (AAC_LTP), который тот билд ffmpeg-а вообще не поддерживал.
Вообще, рассматривая эти qbox-ы, я невольно задумался - а есть ли среди них вообще этот самый AudioSpecificConfig? Даже если проигнорировать первые 5 битов, дальше все равно получалась лютая хуита. В доках было четко сказано, что девайс поддерживает 2-канальное стерео AAC-LC аудио с sample rate 48000, 44100, 32000, 24000, или 22050 Hz.
Крепко задумавшись, я продолжил гуглить и штудировать ISO/IEC 14496-3, как вдруг до меня дошло, что все эти пакеты прекрасно подходят под определение AAC raw data block типа ChannelPairElement!
Здесь ID_CPE==1. CPE - это просто пакеты для двухканального стерео-аудио (это подтвердил и код ffmpeg). Я чувствовал, что я на верном пути!
Но мне пришлось смириться с горькой правдой - конфига аудио (AudioSpecificConfig) в этом стриме qbox, по ходу, нет вообще. Возможно, девайс, с которого это было записано, поддерживал только одну настройку аудио, и qbox с аудио из файла ojd специально выкинули? Или же расчет был на то, что какие-то декодеры поумнее ffmpeg 0.6 смогут и без конфига тут разобраться? Мистика, конечно...
Ну, раз конфига нет, то сделаем сами, хуле. Будет у нас свой AudioSpecificConfig с блекджеком и шлюхами. Сначала я просто хакал ffmpeg, а потом уже дописал фальшивый аудио qbox в начало файла. Но это я чуть позже покажу.
Итак, мы имеем audioObjectType==2, 3<=samplingFrequencyIndex<=7, channelConfiguration==2. Если что, кстати, последние 2 значения - индексы в такие массивы (из кода ffmpeg 0.6.7):
После некоторых попыток стал я пробовать различные комбинации и... нихрена. FFmpeg выплевывал тонну ошибок (но файл какой-то все-таки выдавал). В чистое AAC не конвертировалось, а при декодировании в raw audio (wav) мои уши встречал оглушительный пердеж. После некоторой ебли, мне показалось, что на частоте 22050 ошибок вроде выдается меньше, и стал с ней тыркаться.
Дальше я взял в руки gdb и стал разбираться, откуда идут ошибки. FFmpeg декодирует каждый channel_pair_element так: сначала общий конфиг, потом левый канал, потом правый. В пакете с данными они идут друг за другом последовательно (в табличке channel_pair_element это видно - там это называется individual_channel_stream).
Удивительно, но все ошибки выдавались при декодировании правого канала.
Почесал я репу, похакал я код ffmpeg-овского декодера aac, чтобы, значит, он игнорил ошибки со второго канала, и с каждого qbox-а брал только один экземпляр данных для первого канала.
Получившееся в результате аудио было... странным. В правом ухе оглушительно пердел сатана. Но в левом, кажется, слышались стоны ебущихся карликов!!!!!!
...но стонали они как-то... ускоренно? Я задумался и сел вчитываться в код ffmpeg. Скорее всего, второй канал не выходило распарсить, потому что плохо парсились данные первого канала. Если его недопарсить до конца, или же наоборот перепарсить, то не попадешь на данные второго канала. В конце первого канала шли спектральные данные, которые парсились каким-то очень сложным кодом... Но на его парсинг влиял sample rate.
Отсюда следует очевидное умозаключение - sample rate неправильный. Но я, как было написано раньше, свято верил слитым докам MG1264, в которых было написано, что самый маленький sample rate для аудио, который поддерживает кодек - 22050 герц.
Но в какой-то момент я от безнадеги таки взял и попробовал 16000 герц (индекс 8).
И ебать-колотить, что же я увидел? Конвертацию аудио без ошибок от ffmpeg! И что же я услышал? Громкие, четкие, стерео стоны ебущихся карликов!!!! Я смог!!!!!
В конвертер для qbox-ов добавилась новая фича - дописывать в начало стрима аудио-qbox с найденным мною конфигом.

КАК Я ЕБАЛСЯ С ФРЕЙМРЕЙТОМ
Последней моей задачей было сделать конвертацию видео с нормальным фреймрейтом. Аудио каким-то образом отконвертилось идеально, но чтобы подогнать к нему по времени видео, пришлось подбирать вручную - выяснилось, что там был фреймрейт 25. Но откуда брался изначальный фреймрейт, который был похож на ебаное слайдшоу? Оказалось, что все дело было в демуксере, который зачем-то рескейлил дефолтные 90000 герц базовой шкалы тайминга на какую-то хуйню. Исправив сей огрех и пошаманив немножко с кодом таймстампов, я получил нормальную комбинацию аудио с видео, нормальный mp4 файлик, который уже можно было отправить заждавшемуся Иисусу!
ЗАКЛЮЧЕНИЕ
Ебать это было весело. Я дохрена нового для себя узнал про мир аудио и видео энкодингов. И теперь умею немножко работать с ffmpeg! Ну и в кои-то веки есть что интересного рассказать реактору =) всем бобра, я пошел допроебывать свой отпуск!

Отличный комментарий!

Урок ОСдева №5: подготовка к работе с файловой системой FAT12.

В прошлый раз мы инициализировали сегментные регистры и обеспечили загрузчику безопасную среду
обитания. Сегодня будем готовить почву для работы с файловой системой FAT12. Для начала стоит
поподробнее ознакомиться с её структурой. В FAT12 пространство носителя можно разделить на
несколько областей. Они могут быть разного размера, но идут всегда в следующем порядке:

ЗАГРУЗОЧНЫЙ СЕКТОР - ЗАРЕЗЕРВИРОВАНО - FAT - КД - ОБЛАСТЬ ДАННЫX

Наша задача - вычислить начало и размер каждой области на нашем носителе. Эта информация понадобится
для загрузки файлов. Важный момент: при работе с контроллером флоппи-привода мы оперируем секторами,
а не байтами. То есть, когда я пишу "размер", я имею в виду количество секторов, занятыx
областью.

Загрузочный сектор - место, где обитает наша программа. Это всегда сектор
номер 1 на носителе, и занимает он ровно 1 сектор. Было несложно.

Далее, зарезервированныx секторов у нас нет. Вернее, есть один, загрузочный. Общее число
зарезервированныx секторов включая загрузочный можно найти в переменой BPB_reserved блока
параметров BIOS.

FAT, таблица распределения файлов. Будет чуть сложнее. Размер FAT в секторах xранится в переменной
BPB_FATsize. Но, как я уже писал ранее, часто на диске может быть дублирующая FAT на случай
повреждения данныx. Количество FAT на диске указано в переменной BPB_numFATs. Для вычисления
общего размера всех FAT на диске нам нужно умножить размер FAT на число FAT.

Дальше у нас идёт корневая директория. Это набор записей о размещении файлов. Размер записи КД
в FAT12 - 32 байта. Количество записей указано в переменной BPB_RDentries. Берём размер записи
и умножаем на число записей. Всё? Нет. Так мы получим размер в байтах, его нужно перевести
в секторы. Для этого резльтат делится на размер сектора в байтах, который хранится в переменной
BPB_bytespersec.

Вот теперь всё. Вспомним, как выглядела программа в конце прошлого поста:

.386p
CSEG segment use16
ASSUME cs:CSEG, ds:CSEG, es:CSEG, fs:CSEG, gs:CSEG, ss:CSEG

begin:                    jmp short execute                    ;Точка входа. Перейти к исполняемой части.
                            nop                                         ;Пустой оператор. Заполняет 3-й байт перед BPB.



;БЛОК ПАРАМЕТРОВ BIOS======================================================================;

;=======================================;
;Блок параметров BIOS, 33 байта.;
;Здесь хранятся характеристики;
;носителя. Должен быть в 3 байтах;
;от начала загрузочного сектора.;
;=======================================;
          BPB_OEMname db 'BOOTDISK'          ;0-7. Имя производителя. Может быть любым.
          BPB_bytespersec dw 512                  ;8-9. Размер сектора в байтаx.
          BPB_secperclust db 1                        ;10. Количество секторов в кластере.
          BPB_reserved dw 1                          ;11-12. Число зарезервированныx секторов (1, загрузочный).
          BPB_numFATs db 2                          ;13. Число FAT.
          BPB_RDentries dw 224                     ;14-15. Число записей Корневой Директории.
          BPB_sectotal dw 2880                      ;16-17. Всего секторов на носителе.
          BPB_mediatype db 0F0h                   ;18. Тип носителя. 0F0 - 3,5-дюймовая дискета с 18 секторами в дорожке.
          BPB_FATsize dw 9                           ;19-20. Размер FAT в сектораx.
          BPB_secpertrack dw 18                    ;21-22. Число секторов в дорожке.
          BPB_numheads dw 2                        ;23-24. Число головок (поверxностей).
          BPB_hiddensec dd 0                        ;25-28. Число скрытыx секторов перед загрузочным.
          BPB_sectotal32 dd 0                        ;29-32. Число секторов, если иx больше 65535.

;===============================================;
;Расширенный блок параметров BIOS, 26 байт.;
;Этот раздел используется в DOS 4.0.;
;===============================================;
          EBPB_drivenumdb 0                         ;0. Номер привода.
          EBPB_NTflagsdb 0;1. Флаги в Windows NT. Бит 0 - флаг необxодимости проверки диска. Бит 1 - флаг необходимости диагностики поверхности.
          EBPB_extsigndb 29h;2. Признак расшренного BPB по версии DOS 4.0.
          EBPB_volIDdd 0;3-6. "Серийный номер". Любое случайное число или ноль, без разницы.
          EBPB_vollabeldb 'BOOTLOADER ';7-17. Название диска. Устарело.
          EBPB_filesysdb 'FAT12   ';18-25. Имя файловой системы.



;ИСПОЛНЯЕМЫЙ БЛОК========================================================================;

;Шаг 1. Исправить значения сегментных регистров.
execute:
         ;DS, ES, FS, GS.
                   mov ax,07C0h                    ;Сегмент загрузчика.
                   mov ds,ax                          ;Поместить это значение во все сегментные регистры.
                   mov es,ax
                   mov fs,ax
                   mov gs,ax

          ;СЕГМЕНТ СТЕКА.
                   cli                                      ;Запретить прерывания перед переносом стека.
                   mov ss,ax                           ;Поместить в SS адрес сегмента загрузчика.
                   mov sp,0FFFFh                   ;Указатель стека - на конец сегмента.
                   sti                                      ;Разрешить прерывания.

          ;СЕГМЕНТ КОДА.
                   push ax                              ;Поместить в стек сегмент.
                   mov ax,offset stop               ;Указатель на инструкцию после retf.
                   and ax,03FFh                      ;Обнулить 6 старших бит (аналогично вычитанию 7C00h, если смещение больше 7C00h).
                   push ax                              ;Поместить в стек смещение.
                   retf                                    ;Дальний возврат для смены CS.

stop:            cli
                   hlt

          org 1FEh;Заполняет память нулями до 511-го байта.
          dw 0AA55h;Байты 511 и 512. Признак загрузочного сектора.

CSEG ends
end begin


Как всегда я написал максимально подробные комментарии к каждому действию. Теперь после retf добавьте
следующий код вместо cli и hlt:

stop:           mov byte ptr EBPB_drivenum,dl

                  mov ax,BPB_RDentries
                  shl ax,5
                  div BPB_bytespersec
                  mov cx,ax
                  xor ax,ax
                  mov al,byte ptr BPB_numFATs
                  mul BPB_FATsize
                  mov total_FATs_size,ax
                  add ax,BPB_reserved
                  mov datasector,ax
                  add datasector,cx

                  cli
                  hlt


Давайте разбираться. С инструкцией mov мы уже знакомы, так что первая строка должна быть понятна:
команда помещает содержимое регистра DL в переменную EBPB_drivenum. Но что за byte ptr?
Это префикс смены разрядности. Так как мы работаем в 16-битном режиме, TASM предполагает, что
и разрадность всех ипользуемых ячеек памяти - 16 бит. Если мы хотим работать с 8-битной
переменной, её разрядность нужно указать вот таким способом.

И зачем вообще мы сохраняем DL как номер привода, с которого была загружена программа? Дело в
том, что по идее BIOS должна вернуть его в DL. В принципе, доверять этому значению не стоит,
и использовать его мы не будем, так что делать это не обязательно. Просто я педант.

Далее команда mov ax,BPB_RDentries считывает в AX число записей в корневой директории,
а команда shl ax,5 умножает его на 32. Команды shl и shr сдвигает биты числа влево и, соответственно,
вправо (сокращение от shift left и shift right). Сдвиг числа влево на 1 эквивалентен умножению
на 2. Сдвиг на 5 эквивалентен умножению на 32. На старых процессорах сдвиг выполнялся быстрее,
чем умножние или деление, на новых эти команды, кажется, выполняются с одинаковой скоростью.

div BPB_bytespersec делит результат предыдущей операции на число байтов в секторе. Вы наверное
заметили, что регистр ax в команде нигде не указан: операция DIV всегда выполняется на этом
регистре. В результате деления мы получаем чсло секторов, которые занимает КД. mov cx,ax
сохраняет результат в cx, а xor ax,ax обнуляет ax, выполняя на нём "исключающее или" с ним же.

mov al,byte ptr BPB_numFATs считывает в регистр al количество FAT на диске. Кстати! Регистров
al и dl не было в списке, который я приводил на прошлом уроке. Сейчас поясню.
Четыре регистра общего назначения ax,bx,cx и dx делятся на две 8-битные половины.
ax на al и ah, bx на bl и bh, ... l в данном слуае значит low, то есть младшие 8 бит.
h, соответственно, старшие high. Так вот, получив число FAT в al, мы умножаем его на
BPB_FATsize (размер FAT в секторах). Обратите внимание, операция умножения выполняется
на всём регистре ax, а значение мы поместили в al. Для этого мы и обнуляли ax операцией
раньше. Получив в результате общий размер всех FAT на диске, сохраняем его в переменной
total_FATs_size.

Добавив к ax BPB_reserved, получим общий размер FAT и зарезервированных секторов.
Сохраним его в переменной datasector, а затем прибавим к ней cx, в котором хранится
размер КД. Теперь в datasector хранится общий размер КД, FAT и зарезервированных
секторов, то есть номер сектора, с которого начинается область данных. Обратите внимание,
с точки зрения быстродействия правильнее было бы сначала сложить ax и cx, а уже потом
сохранить результат в переменной, так как обращения к памяти занимают намного больше
времени, чем операции надрегистрами. Зачем я сделал именно так, станет понятно в
следующий раз. А на сегодня всё! Сегодня мы вычислили важные значения, которые помогут
в дальнейшем, и познакомились в общих чертах со структурой FAT12.

В качестве ДЗ предлагаю самостоятельно объявить использованные нами переменные total_FATs_size и
datasector. Обе 16-битного формата. Переменные можно объявлять где угодно до тех пор, пока они не
встревают в исполняемый код. Например, можно вставить между dw 0AA55h и CSEG ends

Отличный комментарий!

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

ву пм висксеу
ГРОФФУ пришлось вернуться & ледяные пустоши по СРОЧНЫМ СЕМЕЙНЫМ ДЕЛАМ, и Я ПОЗВАЛ йОФФА... НУ,
г А, ОТЛиЧНО.
ВОТ и ОН... эээ... ПОТЕРЯНиыи БРАТ-ВЛиЗНЕЦ ГРОФФА, ЙОФФ/
Г ДА, КОГДА Я ВЫЛ М ЕЩЕ МАЛЬЧИШКОЙ, МЕНЯ ОТПРАЗили В ЮЖНЫЕ ЗЕМЛИ СЛУЖИТЬ ОРУЖЕНОСЦЕМ У ОДНОГО РЫЦАРЯ. ОН ОВУЧАЛ У
"Чтобы запомнить, сколько футов в миле, вам нужно всего лишь пять помидоров (томатов)
Потому что "five to-mate-oes" звучит как пять, два, восемь, О, а в миле как раз 5280 футов"

"Чтобы запомнить, сколько метров в километре, достаточно запомнить "1000"
потому что система измерения в остальном мире не была изобрела пьяным математиком, кидающим кубик "

Отличный комментарий!

То есть, блять, у них еще и в миле не тысяча футов? Пиздец
США	Остальные страны
Непонятные американские горки	Всё логично
Дюймов	Фута	Ярдов	Унций
в	в	в	в
футе	ярде	миле	фунте
в	в	в	в
метре километре килограмме тонне,Буквы на белом фоне,Метрическая система,английская система,мера измерения,единицы измерения
Здесь мы собираем самые интересные картинки, арты, комиксы, мемасики по теме в байте 8 бит (+1000 постов - в байте 8 бит)