Результаты поиска по запросу «

i dx

»

Запрос:
Создатель поста:
Теги (через запятую):



Ikari Shinji Kaworu Nagisa trap anime #TRAP hosaka dx школьная форма баян? Anime Artist artist Anime Unsorted ...Evangelion Anime фэндомы 

Ikari Shinji,Shinji Ikari,Evangelion,Евангелион, Neon Genesis Evangelion, EVA,Anime,Аниме,фэндомы,Kaworu Nagisa,trap anime,TRAP,разное,hosaka dx,школьная форма,баян?,Ikari Shinji,Evangelion,Anime,fandoms,Nagisa Kaworu,trap anime,,,hosaka dx,,Anime Artist,Аниме арт, Аниме-арт,artist,Anime
Развернуть

Deus Ex Игры DX Deus Ex logo gif анимация сделал сам 

Развернуть

Sonic фэндомы TotesFleisch8 artist Dr. Eggman StH Персонажи redraw 

Яйкин adventure DX

Sonic,соник, Sonic the hedgehog, ,фэндомы,TotesFleisch8,Амир Ивашкевич, TF8,artist,Dr. Eggman,Доктор Эггман,StH Персонажи,redraw
Развернуть

D.O.G. Challenge (Drawing Old Games) Deus Ex Игры JC Denton Doom (игра) crossover 

Простите опоздуна, давно хотел отправить, в итоге делал всё на скорую руку и в кривом фотошопе
И да я знаю что туда ставятся ключи BUT I WANTED ORANGE!!
D.O.G. Challenge (Drawing Old Games),Deus Ex,Игры,JC Denton,Doom (игра),crossover
Развернуть

пароль математика наука Баян 

пароль,математика,наука,Баян,баян, боян, баяны, бояны, баянище, боянище

Развернуть

программирование geek OSDev Операционная система разработка ассемблер длиннопост песочница 

Урок ОСдева №6: минидрайвер флоппи-привода.

В предыдущем посте мы вычислили значения, нужные для работы с FAT12. Пора писать драйвер!

Начнём с постановки задачи. Что должен уметь драйвер FAT12 для первичного загрузчика?

Очень просто: загружать файлы. Больше ничего.


Для этого мы будем использовать прерывание BIOS. Кстати, про прерывания мы ещё не

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

BIOS. Подробно об этом говорить будем позднее, так как тема очень большая. Кроме того,

позже мы напишем полноценный драйвер, который будет работать с флоппи-приводом напрямую,

без посредства BIOS. Сделать это прямо сейчас мы не можем из-за ограничения по размеру

программы: первичный загрузчик должен занимать не больше 512 байт - полновесный драйвер

флоппи-привода в такой объём не влезет.


Прерывания вызываются командой int, после которой идёт номер. Стандартное

прерывание BIOS для работы с дисками - 13h. Соответственно, команда выглядит так: int 13h.

Как правило прерывания требуют передачи параметров через определённые регистры. Так,

int 13h нужен номер функции в AH. Прерывание 13h - это целый набор функций для работы

с различными видами съёмных и постоянных носителей. Нас интересует функция 2, чтение

секторов с диска. Она в свою очередь требует указать количество считываемых секторов в AL,

номер цилиндра в CH, номер начального сектора в CL, головку в DH, привод в DL и адрес

в памяти, куда будут считаны данные, в ES:BX.


Итак, ещё раз: в нашем случае int 13h вызывается со следующими параметрами:

AH = 2 (номер функции)

AL = число секторов

CH = номер цилиндра

CL = номер начального сектора

DH = номер головки

DL = 0 (номер привода)

ES:BX = сегмент:смещение области для загрузки


Прерывание int 13h у нас будет вызываться в процедуре read_sectors. Этой последней нужно

будет передать три параметра: LBA в AX, число секторов в CX и адрес для загрузки в ES:BX.

Что такое LBA мы уже знаем: это более современная линейная схема адресации секторов. К сожалению,

прерывание 13h работает с устаревшим форматом CHS, так что придётся делать конверсию

внутри процедуры.


Кстати, стоит сразу остановиться на том, как оформляются и что представляют собой процедуры

в TASM. В коде процедура выглядит так:


(имя процедуры) proc

     (тело процедуры)

(имя процедуры) endp


Обе эти инструкции нужны только компилятору и в исполняемый файл не попадают. В случае

"плоского" бинарного файла процедура окажется именно там, где она расположена в тексте

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

вызывается инструкцией call (имя процедуры). Предварительно, конечно, надо поместить

нужные параметры в нужные регистры.


Теперь немного о внутреннем устройстве FAT12. Для того, чтобы загрузить файл в память,

нам нужно проделать следующие вещи:


1. Загрузить в память Корневую Директорию диска.

2. Найти в КД запись, соответствующую файлу.

3. Считать из записи номер первого кластера файла в FAT.

4. Загрузить в память FAT.

5. Загрузить в память цепочку кластеров, которую занимает файл.


В этот раз мы ограничимся только первым пунктом, всё остальное будет в финальной статье

про первичный загрузчик. Давайте вспомним, как выглядела программа в конце прошлого поста:


.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_OEMnamedb 'BOOTDISK';0-7. Имя производителя. Может быть любым.

     BPB_bytespersecdw 512;8-9. Размер сектора в байтаx.

     BPB_secperclustdb 1;10. Количество секторов в кластере.

     BPB_reserveddw 1;11-12. Число зарезервированныx секторов (1, загрузочный).

     BPB_numFATsdb 2;13. Число FAT.

     BPB_RDentriesdw 224;14-15. Число записей Корневой Директории.

     BPB_sectotaldw 2880;16-17. Всего секторов на носителе.

     BPB_mediatypedb 0F0h;18. Тип носителя. 0F0 - 3,5-дюймовая дискета с 18 секторами в дорожке.

     BPB_FATsizedw 9;19-20. Размер FAT в сектораx.

     BPB_secpertrackdw 18;21-22. Число секторов в дорожке.

     BPB_numheadsdw 2;23-24. Число головок (поверxностей).

     BPB_hiddensecdd 0;25-28. Число скрытыx секторов перед загрузочным.

     BPB_sectotal32dd 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 jump;Указатель на инструкцию после retf.

                              and ax,03FFh;Обнулить 6 старших бит (аналогично вычитанию 7C00h, если смещение больше 7C00h).

                              push ax;Поместить в стек смещение.

                              retf;Дальний возврат для смены CS.


jump:                     mov byte ptr EBPB_drivenum,dl;BIOS должен вернуть номер загрузочного устройства в DL. Сохранить его в BPB.


                             mov ax,BPB_RDentries;Число записей КД

                             shl ax,5;*32 (размер записи в байтах) = размер КД в байтах.

                             div BPB_bytespersec;AX/размер сектора в байтах = размер КД в секторах.

                             mov cx,ax;Поместить его в CX (будет счетчиком для загрузки КД).

                             xor ax,ax;Обнулить AX.

                             mov al,byte ptr BPB_numFATs;Число FAT

                             mul BPB_FATsize;*размер FAT в секторах = общий размер всех FAT в секторах.

                             mov total_FATs_size,ax;Сохранить результат в переменной.

                             add ax,BPB_reserved;AX+число зарезервированных секторов = стартовый сектор КД.

                             mov datasector,ax;Стартовый сектор КД + размер КД в секторах =

                             add datasector,cx;= стартовый сектор области данных. Сохранить его в переменной.


                             cli

                             hlt


;ПЕРЕМЕННЫЕ==================================================================;

     total_FATs_size dw ?;Переменная для хранения общего размера FAT в секторах.

     datasector dw ?;Переменная для хранения номера стартового сектора области данных.


     org 1FEh;Заполняет память нулями до 511-го байта.

     dw 0AA55h;Байты 511 и 512. Признак загрузочного сектора.


CSEG ends

end begin


Добавим следующий код между add datasector,cx и cli:


                              mov bx,0200h

                              call read_sectors


Теперь загрузчик перед тем, как остановить процессор, вызывает процедуру read_sectors. Но передали ли

мы все нужные параметры? Напоминаю, в AX должен быть LBA первого загружаемого сектора. И в AX у нас

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

именно оно в CX и есть. Сегмент в ES у нас уже установлен, а смещение в BX мы явно задали перед

вызовом процедуры. Всё отлично! Осталась самая малость: написать саму процедуру.


Где-нибудь между hlt и переменными сделайте шаблон:


read_sectors proc

                              ;ПУСТО

read_sectors endp


Алгоритм работы в общих чертах представляется нам как-то так: перевести LBA в CHS, установить

значения регистров для int 13h, вызвать прерывание... Профит! Не будем медлить. Пишите:


read_sectors proc

                              div BPB_secpertrack;Разделить LBA в AX на число секторов в дорожке.

                              inc dl;Остаток + 1 = номер сектора, т.к. нумерация с 1.

                              mov cl,dl;Поместить номер сектора в CL для int 13h

                              xor dx,dx;Обнулить перед делением.

                              div BPB_numheads;Разделить результат на число головок.

                              shl dx,8;Остаток = номер головки, сдвинуть его в DH.

                              mov ch,al;Частное = номер дорожки, его поместить в CH для int 13h.

                              mov dl,0;DL = 0, флоппи-диск А.

                              mov ax,0201h;Функция 2 int 13h, загрузка секторов с диска. В AL - число секторов.

                              int 13h

                              ret

read_sectors endp


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

флоппи-диски! - имеют свойство не читаться с первого раза. На этот случай int 13h возвращает

статус операции в CF: если флаг обнулён - всё хорошо, если установлен - была ошибка чтения.

Во-вторых, даже в случае успеха мы загрузили только один сектор: значение в CX до сих пор не

используется. Начнём со второй проблемы:


read_sectors proc

                              mov bp,BPB_bytespersec;Размер сектора, понадобится внутри цикла.


main:                      pusha;Сохранить регистры общего назначения.

                              div BPB_secpertrack;Разделить LBA в AX на число секторов в дорожке.

                              inc dl;Остаток + 1 = номер сектора, т.к. нумерация с 1.

                              mov cl,dl;Поместить номер сектора в CL для int 13h

                              xor dx,dx;Обнулить перед делением.

                              div BPB_numheads;Разделить результат на число головок.

                              shl dx,8;Остаток = номер головки, сдвинуть его в DH.

                              mov ch,al;Частное = номер дорожки, его поместить в CH для int 13h.

                              mov dl,0;DL = 0, флоппи-диск А.

                              mov ax,0201h;Функция 2 int 13h, загрузка секторов с диска. В AL - число секторов.

                              int 13h

                              popa;Восстановить сохраненные регистры.


                              inc ax;Увеличить LBA.

                              add bx,bp;Сместить указатель загрузки на длину сектора.

                              loop main;Продолжить цикл.

                              ret;Завершить процедуру.

read_sectors endp


Процедура теперь организована в виде цикла со счётчиком в CX. Команда loop возвращает

указатель инструкции к указанной метке при условии, что CX не равен 0. CX при этом

уменьшается на 1. Обратите внимание, что в начале процедуры мы помещаем в BP

размер сектора в байтах, а блок кода из прошлой версии теперь обрамляется инструкциями

pusha и popa. Последнее нужно для того, чтобы после выполнения шага цикла вернуть вводные

значения в соответствующие регистры. Перед началом следующего шага LBA в AX увеличивается

на 1, а смещение области загрузки увеличивается на размер сектора. Время разобраться

с возможными ошибками чтения.


read_sectors proc

                              mov bp,BPB_bytespersec;Размер сектора, понадобится внутри цикла.

main:                      mov di,5;Число попыток чтения в случае ошибки.


load_sector:            pusha;Сохранить регистры общего назначения.

                              div BPB_secpertrack;Разделить LBA в AX на число секторов в дорожке.

                             inc dl;Остаток + 1 = номер сектора, т.к. нумерация с 1.

                             mov cl,dl;Поместить номер сектора в CL для int 13h

                             xor dx,dx;Обнулить перед делением.

                             div BPB_numheads;Разделить результат на число головок.

                             shl dx,8;Остаток = номер головки, сдвинуть его в DH.

                             mov ch,al;Частное = номер дорожки, его поместить в CH для int 13h.

                             mov dl,0;DL = 0, флоппи-диск А.

                             mov ax,0201h;Функция 2 int 13h, загрузка секторов с диска. В AL - число секторов.

                             int 13h

                             jnc sector_loaded;Если CF не установлен, сектор загружен успешно.


                             xor ax,ax;Функция 0 int 13h, сброс головок.

                             xor dl,dl;DL = номер привода, флоппи-диск А.

                             int 13h

                             popa;Восстановить сохраненные регистры.

                             sub di,1;Уменьшить счетчик попыток.

                             jnz load_sector;Если счетчик не обнулился, перейти к загрузке сектора.

                             ret


sector_loaded:        popa;Восстановить сохраненные регистры.

                             inc ax;Увеличить LBA.

                             add bx,bp;Сместить указатель загрузки на длину сектора.

                             loop main;Продолжить цикл.

                             ret;Завершить процедуру.

read_sectors endp


После вызова прерывания у нас теперь стоит jnc sector_loaded. Эта инструкция делает переход к

указанной метке, но только если флаг CF не установлен. Таким образом, к инициализации переменных

для следующего шага цикла мы попадаем только если предыдущий завершился успешно. Если же CF

установлен, начинается обработка ошибки. Функция 0 int 13h возвращает читающие головки привода к

0 сектору 0 дорожки, это должно уменьшить вероятность ошибки при следующем чтении. После

этого мы уменьшаем счётчик попыток на 1 и, если он не обнулился (инструкция jnz), делаем повторную

попытку. Теперь процедура почти готова. Остались финальные штрихи. Во-первых, сброс головок

тоже может пойти с ошибкой, и для пущей уверенности операцию стоит повторить несколько раз.

Во-вторых, было бы неплохо, если бы процедура обрабатывала ситуацию, когда все попытки чтения

завершились неудачей. Сейчас она просто завершается, как и в случае успеха. Начнём опять со второй задачи.


read_sectors proc

                             mov bp,BPB_bytespersec;Размер сектора, понадобится внутри цикла.

main:                     mov di,5;Число попыток чтения в случае ошибки.


load_sector:            pusha;Сохранить регистры общего назначения.

                             div BPB_secpertrack;Разделить LBA в AX на число секторов в дорожке.

                             inc dl;Остаток + 1 = номер сектора, т.к. нумерация с 1.

                             mov cl,dl;Поместить номер сектора в CL для int 13h

                             xor dx,dx;Обнулить перед делением.

                             div BPB_numheads;Разделить результат на число головок.

                             shl dx,8;Остаток = номер головки, сдвинуть его в DH.

                             test ah,ah;Проверить AH. Если больше нуля, что-то пошло не так.

                             jnz error;Т.к. на диске не может быть больше 255 дорожек, завершить с ошибкой.

                             mov ch,al;Частное = номер дорожки, его поместить в CH для int 13h.

                             mov dl,0;DL = 0, флоппи-диск А.

                             mov ax,0201h;Функция 2 int 13h, загрузка секторов с диска. В AL - число секторов.

                             int 13h

                             jnc sector_loaded;Если CF не установлен, сектор загружен успешно.


                             xor ax,ax;Функция 0 int 13h, сброс головок.

                             xor dl,dl;DL = номер привода, флоппи-диск А.

                             int 13h

                             popa;Восстановить сохраненные регистры.

                             sub di,1;Уменьшить счетчик попыток.

                             jnz load_sector;Если счетчик не обнулился, перейти к загрузке сектора.

                             ret


sector_loaded:        popa;Восстановить сохраненные регистры.

                             inc ax;Увеличить LBA.

                             add bx,bp;Сместить указатель загрузки на длину сектора.

                             loop main;Продолжить цикл.

                             ret;Завершить процедуру.


error:                     popa;Попытки кончились. Восстановить сохраненные регистры.

                             mov ax,07c0h;Сегмент загрузчика

                             mov es,ax;поместить в ES для int 10h.

                             mov ah,03h;Функция 3 прерывания 10h, получить позицию курсора в DH, DL.

                             xor bh,bh;BH = номер видеостраницы.

                             int 10h;DH = строка, DL = столбец.


                             mov ax,1300h;Функция 19 прерывания 10h, вывод строки. AL = режим вывода.

                             mov bx,0007h;BH = страница, BL = атрибуты символа.

                             mov cx,0010h;CX = длина строки.

                             mov bp,offset msg_DRE;ES:BP = указатель на строку.

                             int 10h;Вывести строку в DH,DL без обновления курсора.

                             cli;Запретить прерывания

                             hlt;и остановить процессор.

read_sectors endp


В первом сегменте кода после shl dx,8 у нас появилась проверка на ошибочность результата. Она

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

255, то что-то пошло не так. Программа переходит к метке error, после которой происходит

следующее: первый сегмент кода опустошает стек, а потом с помощью прерывания 10h

(прерывание для работы с дисплеем) считывает положение курсора на экране, а второй выводит сообщение

об ошибке и останавливает процессор. Строку с сообщением можно хранить рядом состальными переменными,

выглядит она так:


msg_DRE db 'Disk read error.' ;Сообщение об ошибке чтения с диска.


Осталась самая малость. Обработать повторные попытки сброса головок. Финальная версия прецедуры

будет выглядеть так:


read_sectors proc

                             mov bp,BPB_bytespersec;Размер сектора, понадобится внутри цикла.

main:                     mov di,5;Число попыток чтения в случае ошибки.


load_sector:            pusha;Сохранить регистры общего назначения.

                             div BPB_secpertrack;Разделить LBA в AX на число секторов в дорожке.

                             inc dl;Остаток + 1 = номер сектора, т.к. нумерация с 1.

                             mov cl,dl;Поместить номер сектора в CL для int 13h

                             xor dx,dx;Обнулить перед делением.

                             div BPB_numheads;Разделить результат на число головок.

                             shl dx,8;Остаток = номер головки, сдвинуть его в DH.

                             test ah,ah;Проверить AH. Если больше нуля, что-то пошло не так.

                             jnz error;Т.к. на диске не может быть больше 255 дорожек, завершить с ошибкой.

                             mov ch,al;Частное = номер дорожки, его поместить в CH для int 13h.

                             mov dl,0;DL = 0, флоппи-диск А.

                             mov ax,0201h;Функция 2 int 13h, загрузка секторов с диска. В AL - число секторов.

                             int 13h

                             jnc sector_loaded;Если CF не установлен, сектор загружен успешно.


                             mov cx,0003h;Счетчик попыток сброса головок.

reset:                     xor ax,ax;Функция 0 int 13h, сброс головок.

                             xor dl,dl;DL = номер привода, флоппи-диск А.

                             int 13h

                             jnc reload;Если не было ошибки - повторить попытку чтения сектора.

                             loop reset;Попробовать сбросить головки еще раз, если CX не обнулился.

error:                     popa;Попытки кончились. Восстановить сохраненные регистры.

                             jmp short disk_read_error;Сообщить об ошибке и завершить программу.


reload:                   popa;Восстановить сохраненные регистры.

                             sub di,1;Уменьшить счетчик попыток.

                             jnz load_sector;Если счетчик не обнулился, перейти к загрузке сектора.

                             jmp short disk_read_error;Сообщить об ошибке и завершить программу.


sector_loaded:        popa;Восстановить сохраненные регистры.

                             inc ax;Увеличить LBA.

                             add bx,bp;Сместить указатель загрузки на длину сектора.

                             loop main;Продолжить цикл.

                             ret;Завершить процедуру.


disk_read_error:      mov ax,07c0h;Сегмент загрузчика

                             mov es,ax;поместить в ES для int 10h.

                             mov ah,03h;Функция 3 прерывания 10h, получить позицию курсора в DH, DL.

                             xor bh,bh;BH = номер видеостраницы.

                             int 10h;DH = строка, DL = столбец.


                             mov ax,1300h;Функция 19 прерывания 10h, вывод строки. AL = режим вывода.

                             mov bx,0007h;BH = страница, BL = атрибуты символа.

                             mov cx,0010h;CX = длина строки.

                             mov bp,offset msg_DRE;ES:BP = указатель на строку.

                             int 10h;Вывести строку в DH,DL без обновления курсора.

                             cli;Запретить прерывания

                             hlt;и остановить процессор.

read_sectors endp


Поздравим себя! Теперь у нас есть процедура, считывающая данные с диска. Добавленный нами

вызов read_sectors в конце программы помещает КД диска в память сразу после самого

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

пользоваться КД и FAT и загружать файлы.


Развернуть

гонки гифки Игры gamedev TurboSloths 

TurboSloths Demo v2.0

  Приветствую реактор!

  У нашего проекта в стиме стала доступна 2х часовая демо версия! Что бы поиграть надо всего лишь... 

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

  А теперь коротко о том что доступно в демо:

  -Первая часть компании, в которой будет прокачка, сюжет, пара боссов и именные гонщики соперники

  -Быстрые гонки где сразу можно погрузиться в геймплей без долгой раскачки, для оценки режимов (кроме битв с боссами, они сюжетные)

  -Игра в SplitScreen с другом (хотя кого я обманываю) сидя на диване, или по RemovePlay 

Ах да, всё это безобразие будет проиходить под зажигательный рок!

  На данный момент у нас играют:

гонки,гиф анимация,гифки - ПРИКОЛЬНЫЕ gif анимашки,Игры,gamedev,TurboSloths,Turbo Sloths, TurboSloth, Turbo Sloth

  Ребята просто шикарные, двоих исполнителей поселил у себя в плей листе (Хотя кому это интересно?)

  Как то так, спасибо что прочитали (эту рекламную стену) этот пост, всем кто следил, а кого заинтересовал велком в тэг на реакторе! Надеюсь демо зацепит! А мы тем временем усердно доделавыем проект и движимся к релизу, на данный момент игра будет доступна в стиме и VKplay.

Развернуть

программирование geek OSDev Операционная система разработка ассемблер длиннопост песочница 

Урок ОСдева №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 поддерживает вложенные директории, и они ничем принципиально не отличаются

от корневой, так что всё нижеизложенное касается и их. 


Развернуть

Игровые новости Игры stalker 2 S.T.A.L.K.E.R. 

GSC не планируют выпускать S.T.A.L.K.E.R. 2 на PS5, Game Pass - одна из причин эксклюзивности консоли Xbox

Компании GSC Game World сообщила, что в настоящее время нет планов по выходу игры на PS5, добавив при этом, что Xbox Game Pass (в рамках которого игра выйдет на рынок) была одной из основных причин того, что игра будет выпущена как эксклюзив для консоли Xbox.

«На данный момент никаких планов», - сказал разработчик. «Мы обсудили возможности с Microsoft и увидели несколько хороших возможностей для бренда, включая Game Pass. Мы концентрируемся на версии Xbox Series X/S»

Игровые новости,Игры,stalker 2,S.T.A.L.K.E.R.

Развернуть

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

В первом сталкере тоже были довольно амбициозные обещания, игру смотреть надо
asd072 asd07229.04.202120:58ссылка
+14.5
Первый сталкер вышел крайне кастрированным по сравнению с теми золотыми горами, что обещали в начале и что в итоге мододелы своими руками так или иначе вернули.
ashot@camshot ashot@camshot29.04.202121:03ссылка
+35.3

my little pony фэндомы mlp art Queen Chrysalis minor 

HELLO, NEW FRIENDS! by DeusExEquus

my little pony,Мой маленький пони,фэндомы,mlp art,Queen Chrysalis,Королева Крисалис,minor,второстепенные персонажи

//You know what I love? Everything!

Развернуть
В этом разделе мы собираем самые смешные приколы (комиксы и картинки) по теме i dx (+1000 картинок)