Alleksh 15 Опубликовано: 5 января 2019 Часть №1. Понимание системы. Сейчас я вам поведаю о том, как формируются слова в памяти. Для начала - создадим функцию DUMP, она будет принимать на вход адрес и два значения: ( addr n1 n2 -- ) DUMP Выводит n1 строк по n2 байт по адресу addr. Код: VARIABLE PARAM DECIMAL : DUMP 16 RADIX ! PARAM ! 0 DO CR DUP I PARAM @ * + DUP U. BS ." : " PARAM @ 0 DO DUP I + C@ DUP 16 < IF ." 0" THEN . LOOP PARAM @ 0 DO DUP I + C@ EMIT LOOP DROP LOOP ; Когда вы напишете в консоли ' %word% В стек запишется адрес исполняемой части слова, которую можно выполнить написав EXECUTE. Попробуем: Да, это работает отлично! Но как именно..?Инструкции RedCPU:http://www.eloraam.com/nonwp/redcpu.php Каждое созданное Вами слово будет начинаться с 0x22, что является инструкцией ENT. Каждая следующая инструкция после ENT будет указателем на исполняемую часть другого слова. Например:Примечание: 20 21 01... - мусор из ОЗУ. Главное - первые 7 байт. Как мы можем заметить - начинается она с 0x22. Что же значит DE 2D, F9 2D, 5C 05? А это - указатели на функции. Давайте посмотрим на адреса TEST, TEST2: Да! Всё записалось отлично!Примечание: Каждая цифра, адрес и т.п. записывается наоборот(записав 0x4488 в памяти окажется 0x88 0x44). У вас, возможно, мог возникнуть вопрос: -А что же находится по адресу 0x55C? Что же, давайте воспользуемся командой >NAME (она берёт из стека адрес функции и ищет её имя ) : А это - функция EXIT. Это не удивительно, так как далее находится всякий мусор, который нам явно не нужно исполнять. А теперь давайте теперь узнаем, как записываются числа и строки, ведь у них нет определенного адреса: Первая использует какую-то функцию по адресу 0xFE2, а вторая - 0x550. Давайте узнаем, что это: Как можно увидеть - используются специальные слова, которые определяют вхождение в строку и число. Так же нужно отметить, что строка заканчивается на 0x00. Давайте теперь, учитывая это, напишем декомпилятор: HEX VARIABLE NOW_ADDR : DECOMPILE OVER 1 + NOW_ADDR ! 0 DO CR NOW_ADDR @ U. BS ." : " NOW_ADDR @ @ DUP 550 = IF DROP NOW_ADDR @ 2 + @ . NOW_ADDR @ 4 + NOW_ADDR ! ELSE FE2 = IF NOW_ADDR @ BEGIN 1 + DUP C@ DUP EMIT ." " 0= UNTIL NOW_ADDR ! ELSE NOW_ADDR @ @ >NAME TYPE NOW_ADDR @ 2 + NOW_ADDR ! THEN THEN LOOP ; Давайте теперь попробуем декомпилировать функцию SAVE": Всё сработало отлично! Что это за "крякозябры" у do/loop? Давайте воспользуемся DUMP: Всё просто! Это - адреса, куда нужно переходить в том или ином случае. У DO - 1B2D, что является выходом. То есть, когда у нас первый итератор превышает другой - начинаем выполнять код по адресу 1B2D. У LOOP - 1B1B, что есть началом цикла(DUP). Если возникли какие-либо вопросы - пишите их в комментарии к этому посту, постараюсь ответить. Тема достаточно сложная для восприятия. В следующей части мы ознакомимся как взаимодействовать с периферией(дисководы, сортроны, мониторы и т.п.) напрямую(не используя стандартные команды). Часть №2. Ускорение заводов. Сейчас мы поговорим о том, как можно ускорять свои заводы, исполняя меньшее кол-во действий. Для начала Вам необходимо кое-что знать: Для того, чтобы подключиться к устройству - необходимо использовать команду " RBP! ", ID устройства должен находится в стеке. Взаимодействие проходит таким образом: На Вашем устройстве кусок памяти 0x300-0x3FF это 0x400-0x4FF на другом. И наоборот. То есть, изменяя байт 0x300 мы изменяем 0x400 на другом. Не нужно использовать во время диалога, т.к. любое отображение ставит в RBP! номер терминала, который указан в TERMADDR. Вот моя сеть: Попробуем написать функцию, которая отправит данные другому устройству: : SEND CR ." ENTER MESSAGE: " 400 100 ACCEPT DROP RBP! 400 300 OVER STRLEN MOVE ; Описание: На 0x100 находится TIB буфер(для строк), мы не пишем сразу в 0x300 т.к. сейчас мы подключены к матрице, следовательно будем изменять её. Попробуем отправить устройству с ID 15 нашу строку: И вот, о чудо, мы получили нашу строку: И так, вернёмся к периферии. Во первых - таблица ( эта и все последующие - взяты с http://minecrafting.ru/topic/6612/ ) : 0x01: столбец x 0x02: строка y 0x03: режим (0: спрятан, 1: сплошной, 2: мигающий) Буфер ввода с клавиатуры (16 байт, FIFO): 0x04: Указатель начала очереди 0x05: Указатель конца очереди 0x06: Значение клавиши в начале очереди Действия с областями: 0x07: Команда (1: заполнить, 2: выделить(инверсия цвета); 3: копировать) Координаты левого верхнего угла: 0x08: x координата области источника / Значение для заполнения 0x09: y (необходимо только для копирования) 0x0A: х координата области, требуемой для изменения 0x0B: y координата области, требуемой для изменения 0x0C: Ширина области 0x0D: Высота области Доступ к видеопамяти: 0x00: Номер строки для чтения/изменения 0x10-0x60: Содержимое строки? Это - память терминала (чтобы править, нужно использовать 0x300+addr).Зная всё это, мы можем понять, что, например, с монитором не всё так просто.Воспользуемся командой DECOMPILE, чтобы просмотреть, как работает EMIT:Всё просто - она подключается к терминалу по адресу TERMADDR, переключается на него.Записывает в 0x300 наш символ. Далее идёт смена координат на мониторе, это не столь важно. А не так и сложно, как показалось с первого взгляда, верно? Давайте посмотрим, как работают функции взаимодействия с дисководом. Воспользуемся таблицей: 0x00-0x7F: Буфер дисковода, размером в 1 сектор (128 байт). 0x80-0x81: Номер сектора 0x82: Команда для дисковода: (далее значения этого байта) 0: Ничего или удачное завершение предыдущей команды 1: Прочитать имя дискеты 2: Записать имя дискеты 3: Прочитать ID дискеты (серийный номер) 4: Прочитать сектор дискеты 5: Записать сектор дискеты 0xFF: Неудачное завершение предыдущей команды Вот исходный код функции SAVE": Она записывает на диск данные от 0x500 до HERE. Во первых - она использует слово DISKNAME". Давайте декомпилируем и его: Не так уж и сложно, записывает строку на 0x300-0x380 и записывает номер команды(2) в 0x382. Вернёмся к SAVE". Далее идёт подготовка к запуску цикла, он записывает каждые 128 байт от 500 до HERE используя DISKWS. Давайте посмотрим и на него: И снова, не всё так уж и сложно. Запись данных в 0x300-0x380, запись номера команды(5) в 0x382. Давайте перейдём к самому вкусному - сортроны. Таблица: 0x00: микрокоманда 1: Определить размер инвентаря 2: Прочитать содержимое слота 3: Извлечь 4: Фильтр 0x01: Количество предметов 0x02: Номер слота 0x04: Идентификатор предмета (4 байта!) 0x08: Повреждённость предмета 0x0A: Предел прочности предмета 0x0C: Исходящий цвет 0x0D: Входящий цвет Разберём на примере SORTPULL: И снова, запись адреса в RBP, запись номера слота в 0x302, кол-ва предметов в 0x301. Она использует еще и SORTCMD. Что же он делает? Во первых, что мы можем увидеть - снова запись адреса в RBP!. Ожидание исполнения команды, проверка на то, не случилось ли каких-либо ошибок при работе. Если случились - выводим Sorter Error выходя в диалоговый режим. Вы все ещё можете спросить - как это нам позволит ускорить заводы? Во первых - Вы можете убрать из SORTPULL последних две команды, если они Вам, очевидно, не нужны. И две первых, если Вы все ещё работаете с сортроном, к которому подключены. Давайте теперь попробуем написать свою команду для извлечения всех предметов из сундука. Заменим SORTCMD, уберём проверку на ошибки и переключение SORTADDR. Оставим ожидание выполнения. HEX : BETTER_SORTCMD DUP 300 C! 300 C@ = 5 TICKS ; : GET_ALL_ITEMS SORTADDR @ RBP! 1 BETTER_SORTCMD 302 @ 0 DO 40 301 C! I 302 ! 3 BETTER_SORTCMD LOOP ; Вторая функция, которая использует стандартные методы: : GET_ALL_ITEMS2 SORTSLOTS 0 DO 64 I SORTPULL DROP LOOP ; Вот, мы убрали переключение на SORTADDR, убрали проверки каждый раз в SORTCMD. Давайте теперь сравним две функции Наша GET_ALL_ITEMS справилась за 4.2 секунды. GET_ALL_ITEMS2 справилась за 6.9 секунд. Этот пример НЕ СОВЕРШЕНЕН, можно ещё лучше ускорить.ВАЖНО:Оставляйте проверку на ошибки в конце функции. Почему?Требование международных стандартов. Просто если что-то пойдёт не так, сортрон может зависнуть, или шину разорвёт на стыке чанков. Часть №3. Собственные структуры данных. И так, вам необходимо реализовать структуру, в которой необходимо хранить больше одного числа? Два числа? Число и строку? Число, строку, исполняемый код? Вам определённо нужно прочитать эту статью. Для начала стоит ознакомиться с некоторыми командами: HIDE - прячет последнее слово из словаря. REVEAL - восстанавливает спрятанное слово в словарь. HEADER - берёт следующую строку из буфера, создает слово с этим именем. ALLOT - берёт n со стека, выделяет n байт и возвращает начало выделенной памяти. , - выделяет 2 байта, записывает в них значение со стека. ,C - выделяет и записывает 1 байт. ,S - Аналогично ',' , только для строки. Адрес должен находиться в стеке. Давайте узнаем, как работает VARIABLE:Она вызывает функцию CREATE, выделяет 2 байта, записывает туда 0. Давайте узнаем, что же делает CREATE: Она вызывает HEADER, определение которой находится в таблице выше. Выделяет один байт, записывает в него 0x22.Примечание: Во второй части я рассказывал, что это инструкция ENT, которая определяет, что нужно брать по два байта, переходить по ним и исполнять. Выделяет еще два байта, записывает туда 0x534. Давайте узнаем, что это за функция: А это - функция DOVAR, которая записывает адрес следующего байта и возвращает нас в предыдущее слово. То есть, когда мы пишем VAR - исполняется функция DOVAR. DOVAR - то же, что и EXIT. Лишь оставляет адрес следующих двух байт. То есть, VAR @ можно заменить на 25FD @: Ладно, мы понимаем, как можно записывать переменные и строки. Но как записывать исполняемую часть? Давайте посмотри на исходный код слова " : ": Определение HEADER и HIDE нам известно. Но что делает " [ "? "Что за чёрт?" - можете спросить вы. Всё просто - в STATE хранится режим, в котором мы сейчас находимся. То есть, когда STATE = 0, мы находимся в режиме диалога. Если STATE = 1, мы находимся в режиме компиляции. Давайте рассмотрим слово " ; ", которое завершает компиляцию: Выделяет два байта, записывает в них адрес EXIT. Вызывает " ] ", что противоположно " [ ". Вызывает REVEAL, определение которого Вы можете найти в таблице выше. Теперь, понимая всё это, давайте напишем слово для генерации структур данных, обозначающих предмет. Для начала - разметка: 0x00 - 0x22, вхождение в слово. 0x01-0x02 - 0x0534(DOVAR). Будет записывать адрес RedPower id #1 в стек. 0x01-0x02 - RedPower id #1 0x03-0x04 - RedPower id #2 0x05-0xXX - наша строка, где на 0xXX будет храниться 0x00, что является концом строки. Давайте теперь напишем функцию, которая будет выделять память для всего этого: HEX : ADD_ITEM HEADER 22 ,C 534 , SORTSLOT@ DROP , , CR ." Enter string size: " 100 80 ACCEPT UATOI 1+ DUP 1+ ALLOT CR ." Enter item name: " OVER ACCEPT DROP ; Протестируем: Как можно увидеть по дампу - функция работает отлично. Чтобы получить нужные нам данные - достаточно отступить от RedPower id #1 на x байт вперёд: Всё работает отлично! А теперь - давайте создадим массив таких предметов. Вот и появилась первая проблема - размер строк не зарезервирован, то есть - он разный и невозможно подобрать определённое кол-во элементов в массиве, размер будет разным. У данной проблемы есть несколько решений: 1) Можно создать массив с определенным кол-вом выделенных байт, каждая структура данных будет указывать на место следующей, кол-во элементов указываем в начале массива. 2) Создать массив с определенным кол-вом элементов, у которых будет точный размер. Давайте реализуем первый. Разметка: 0x00-0x01 - размер массива(байт). 0x02-0x03 - кол-во элементов в массиве. 0x04-0xXX - элементы массива(где XX - размер массива в байтах + 2). Разметка элемента: 0x00-0x01 - указатель на место в памяти следующего элемента. 0x02-0x03 - RedPower id #1 0x04-0x05 - RedPower id #2 0x05-0xXX - наша строка, где на 0xXX будет храниться 0x00, что является концом строки. Реализация: : ALLOCATE_MASSIVE CREATE DUP ALLOT ! ; VARIABLE MASSIVE_ADDR VARIABLE ELEM_ADDR VARIABLE MASSIVE_SIZE : MASSIVE_GET_ELEMSIZE MASSIVE_ADDR @ 2 + @ ; : MASSIVE_SET_ELEMSIZE MASSIVE_ADDR @ 2 + ! ; : ADD_ELEMENT_TO_MASSIVE MASSIVE_ADDR ! MASSIVE_ADDR @ @ MASSIVE_SIZE ! MASSIVE_GET_ELEMSIZE 1+ MASSIVE_SET_ELEMSIZE MASSIVE_ADDR @ 4 + ELEM_ADDR ! MASSIVE_GET_ELEMSIZE 0<> IF MASSIVE_GET_ELEMSIZE 1- 0 DO ELEM_ADDR @ @ ELEM_ADDR @ ! LOOP THEN SORTSLOT@ DROP ELEM_ADDR @ 2 + ! ELEM_ADDR @ 4 + ! CR ." Enter string size: " 100 80 ACCEPT UATOI DUP 4 + ELEM_ADDR @ ! CR ." Enter string: " ELEM_ADDR @ OVER 6 + ACCEPT ; Попробуем инициализировать массив:Всё сработало отлично! Размер массива инициализирован. Попробуем добавить элемент "Diamond" в массив. Просмотрим дамп памяти массива: Поздравляю! Всё сработало отлично х2! Теперь по адресу 0xFF2 размещен указатель на 0x2FFF, где будет находится следующий элемент массива. Часть №4. Сеть и работа с дисками на примере чата. Что же может дать такая сеть из нескольких компьютеров? 1. Ускорение заводов. Ведь если несколько пк будут одновременно работать с сортронами - завод будет работать быстрее, верно? 2. Создание баз данных. Где это может быть полезно? К примеру - имеем мы сеть из 30 компьютеров, а нам нужно обновить базу данных предметов для каждого. Неужели в каждый добавлять одно и то же? В этом случае вполне полезны базы данных. Покажу использование сетей на примере чата. И так, давайте создадим чат!Примечание: Данный чат можно реализовать и через один пк, используя несколько мониторов. Идея: Есть два вида устройств - сервер и клиент. Сервер всего один, а клиентов - множество. Сначала - напишем сервер. Идея сервера: Получаем ID устройства в нашей сети, куда нужно отправлять данные. Получаем номер команды, данные. Отправляем по указанному ID ответ. Храним сообщения на диске. Команды сервера: 0 - отправить кол-во сообщений(записать в 0x400-0x401). 1 - отправить сообщение по номеру(берём из 0x403-0x404 номер, записываем в 0x301-0x3FF ответ). 2 - Получить сообщение - в 0x403-0x4FF находится сообщение. Разметка сервера: 0x400 - ID 0x401 - номер команды 0x402 - Можно ли начинать работу? 0x403-0x4FF - получаемые данные. 0x300 - завершена ли работа? 0x301-0x3FF - данные клиента. Реализация сервера. Для начала - напишем базу данных, в которой будем хранить сообщения: HEX : ADD_NEW_MESSAGE NOW_SIZE @ 1+ DUP NOW_SIZE ! BLOCK 70 MOVE FLUSH ; : GET_MESSAGE BLOCK ; Давайте её протестируем добавив несколько тестовых сообщений: Как можно увидеть, используя GET_MESSAGE - все работает отлично: Давайте теперь доработаем наш сервер: HEX : CONNECT_TO_ID 400 C@ RBP! ; : PROCESS_COMMAND_0 CONNECT_TO_ID NOW_SIZE @ 301 ! ; : PROCESS_COMMAND_1 403 C@ GET_MESSAGE CONNECT_TO_ID 301 70 MOVE ; : PROCESS_COMMAND_2 404 ADD_NEW_MESSAGE CONNECT_TO_ID ; : SWITCH_COMMAND 401 C@ DUP 0= IF DROP PROCESS_COMMAND_0 ELSE DUP 1 = IF DROP PROCESS_COMMAND_1 ELSE 2 = IF PROCESS_COMMAND_2 THEN THEN THEN ; : START_SERVER 400 3 0 FILL BEGIN 400 C@ 0<> IF BEGIN 402 C@ 0<> UNTIL SWITCH_COMMAND CONNECT_TO_ID 1 300 C! 400 3 0 FILL THEN KEY? UNTIL ; Наш сервер готов!Давайте теперь напишем клиент.Для начала напишем функции для отправки/получения сообщений: VARIABLE MYID : CONNECT_TO_SERVER 0 RBP! ; : SET_READY 1 302 C! ; : WAIT_WHILE_EXECUTED BEGIN TICK 400 C@ 0<> UNTIL ; : GET_SIZE CONNECT_TO_SERVER 0 301 C! MYID C@ 300 C! 0 400 C! SET_READY WAIT_WHILE_EXECUTED 401 @ ; : RECEIVE_MESSAGE CONNECT_TO_SERVER 1 301 C! 303 ! MYID C@ 300 C! 0 400 C! SET_READY WAIT_WHILE_EXECUTED 401 ; : SEND_MESSAGE CONNECT_TO_SERVER 2 301 C! MYID C@ 300 C! 0 400 C! 100 304 70 MOVE SET_READY WAIT_WHILE_EXECUTED ; : SEND 100 80 ACCEPT SEND_MESSAGE ; И так, код мы ввели.Давайте всё это подключим: MYID у первого клиента - 5. У второго - 6. Давайте протестируем: #5: #6: Всё сработало отлично! 2 4 Поделиться сообщением Ссылка на сообщение
WorldDominator 21 Опубликовано: 3 января 2020 Жесть. И для кого этот мод расчитан ? Такое ощущение, что читаю гайд по ассемблеру, хотя даже он по-понятнее будет (не в обиду гайду, просто видимо я тупой). 1 Поделиться сообщением Ссылка на сообщение
cryzalix 229 Опубликовано: 3 января 2020 Гайд классный, но ни черта не понятно с первого раза Х) Возможно, стоило бы начать с введения какого-то ну или ссылки на него, если оно уже есть... Поделиться сообщением Ссылка на сообщение
Endless 1496 Опубликовано: 3 января 2020 47 минут назад, KrisAlex сказал: Гайд классный, но ни черта не понятно с первого раза Х) Возможно, стоило бы начать с введения какого-то ну или ссылки на него, если оно уже есть... Высокоуровневые вещи, с бо́льшим уровнем абстракции от работы с памятью тут: А базовых вещей полно в интернетах. Поделиться сообщением Ссылка на сообщение