| Разработка и ромхакинг > Программирование |
| Программирование редакторов для внесения каких-то изменений в ром. |
| (1/3) > >> |
| SeregaZ:
Тема создана с целью наиболее простым языком описать процесс создания своего какого-то инструмента для редактирования чего-либо в игре. Это не ромхакинг. Это предполагается, что ромхакер уже знает что где лежит в роме и в каком формате. И это что-то существующими доступными программами не редактируется, а хотелось бы сделать что-то свое, однокликовое, с блекджеком и легкодоступными женщинами. Будет для статьи использоваться не совсем удобное и знаменитое, а докучи еще и платное - PureBasic. Ну и, понятно, Windows. Как бы он тоже платный... главное что некоему Билли Г. что автору PB не рассказывать о существовании торрентов. https://rutracker.net/forum/viewtopic.php?t=6260033 Как понятно из названия - это Basic, только что с плюшками современными. Но в общем и целом те-же яйца, только в профиль. И нас интересует портабельная версия, чтобы ничего не устанавливать. Дальше теория. Код обычно выполняется сверху вниз построчно. Типа пробежался сверху вниз, закончился, и получились какие-то результаты. Однако мы хотим создать окно "Привет Мир!" и чтобы оно висело по середине экрана и радовало пользователя своим висением. Следовательно код должен будет бежать сверху вниз, дойти до момента создания окна, создания элементов внутри окна - то есть надписи "Привет Мир!", и после этого дальше начать по кругу гонять бесконечный цикл "ожидание событий" - без этого цикла окно мелькнет и исчезнет, а нам нужно продолжительное висение, пока пользователь не нажмет "закрыть окно". В этом самом бесконечном цикле есть разбор событий и нам нужно будет добавить событие "закрыть окно". Этот разбор разбирает какое это окно, где событие случилось, какой элемент внутри окна имеет событие, какое именно событие происходит - движение мышкой или может быть клик левой кнопки, или может правой. То есть получается у самого окна, у всех его элементов, у событий - есть свои уникальные номера, чтобы программа понимала что с ней творится. Можно прям так и объявлять: окно 0, кнопка 1, картинка 2... Как бы все будет работать, но визуально для глаз удобнее все-же давать "человеческие" названия окнам и кнопкам. типа окно #Window, кнопка #Button или если это скажем кнопка сохранения #ButtonSave... или даже #KnopkaSave чтобы вообще все было понятно. Эти названия друг за дружкой можно перечислять в верхней части кода, в разделе Enumeration. Визуально мы - человеки - будем понимать где какая кнопка, а с точки зрения программы это будут те-же самые цифры 0, 1, 2, 3... Дальше элементы внутри окна. Они называются гаджеты. Их там небольшой список и они различаются по внешнему виду и функционалу. Типа скажем текстовая строчка, или кнопка, или картинка, или ниспадающий список, или гаджет с отметкой галки (CheckBoxGadget)... У каждого свои задачи и свой набор событий. Типа кнопка может получить и обработать нажатие левой кнопки мышки (правой к сожалению нет, хотя порой очень надо). И так далее. По итогу код нашей программы для взлома пынтагона выглядит так: --- Код: --- Enumeration #Window #TextPrivetMir #KnopkaOK EndEnumeration If OpenWindow(#Window, 100, 100, 300, 100, "Я у мамы программист.", #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered) TextGadget(#TextPrivetMir, 10, 10, 100, 20, "Привет Мир!") ButtonGadget(#KnopkaOK, 10, 50, 50, 20, "OK") Repeat Select WaitWindowEvent() Case #PB_Event_Gadget Select EventGadget() Case #KnopkaOK If EventType() = #PB_EventType_LeftClick Debug "была нажата кнопка ОК" EndIf EndSelect Case #PB_Event_CloseWindow qiut = 1 EndSelect Until qiut = 1 EndIf End --- Конец кода --- То есть сначала наверху блок Enumeration, где перечислены окна и кнопки, которые мы будем использовать. Потом создание окна программы OpenWindow - где внутри указаны начальные координаты окна, ширина и высота, и заголовок в кавычках. Специальные параметры в конце в количестве два штуки говорят программе, что надо рисовать наше окно в центре экрана и чтобы была кнопка "свернуть программу", ну и крестик "закрыть программу". После создания окна идет рисование гаджетов - в данном случае текстового TextGadget и кнопки ButtonGadget. У них так-же как и у окна есть стартовые координаты, но теперь уже не экрана - а координаты на самом окне, высота и ширина, и надписи в кавычках. Потом начинается тот самый бесконечный цикл - Repeat и он крутится до строчки Until qiut = 1, то есть пока переменная qiut - выход, или закрытие программы, не будет ровняться единичке. Внутри этого цикла происходит разбор где произошло событие - то есть в данном случае перечислены два варианта: тело самой программы (типа пользователь нажал кнопку "закрыть") и какой-то гаджет (в данном случае кнопка). Что касается Debug - это просто вывод текста в специальное окошко. Оно нам нужно будет удостоверится, что событие - клик по кнопке ОК - действительно обрабатывается. На каждую команду типа OpenWindow или TextGadget можно будет в PureBasic навести мышкой, кликнуть и нажать F1 - вылезет подсказка с полным разбором где какой параметр за что отвечает. И даже примеры кода, которые можно будет скопировать в новое окошко и позапускать - посмотреть результаты. И да - везде где не попадя стоят If - If OpenWindow, If EventType()... PB это вообще очень не уверенный в себе язык программирования и поэтому лучше на всякий случай добавлять эти самые условия - If - Если. Если открылось окно, то... Если событие было кликом мышки, то... OpenWindow в принципе будет работать и без If, но мало ли в каких-либо случаях Windows не даст создать окно, а программа то захочет выполнятся дальше и дальше по коду идет рисование текстового гаджета - а где она его нарисует, если окно не создалось? Программа вылетит с ошибкой. А так у нас стоит If - окно не открылось? Ничего страшного. Просто завершаем работу, как будто программа не стартовала вовсе. Ну или можно вывесить сообщение, что дескать по какой-то причине мы не смогли создать окно, а следовательно кина не будет. Пока, надеюсь, все понятно :) п.с.: пошел на кухню и понял что понятно не всё. надо скопировать текст программы, вставить в PureBasic и чтобы не елозить по менюшкам - нажать F5. Добавлено позже: а как тут начать новый пост, чтобы он был новым постом, а не *добавлено позже ? |
| SeregaZ:
Работа с файлами. Для нас цифры это цифры. Типа написал ручкой на бумажке 500 и понятно что это 500. В случае записи в файл - тут не все так однозначно :) Там есть такое понятие как тип переменной - byte, word, long. В чем разница? Дело в размере, который будет занят подобным числом. Если нам надо записать число 13 - достаточно будет byte. А если 1300 - то байта уже не хватит. Понадобится word. А если нам надо записать какоенить 128 000 то даже word не хватит - и нужен будет long. Размеры сколько байт они занимают и какие у них лимиты чисел откуда и докуда - можно посмотреть в таблице. нас интересует верхняя часть, красным. Тип переменной и её размер мы вроде как уяснили. Теперь обсудим отображение этих чисел для глаз. С точки зрения компьютера то нет никакой разницы, а вот нам - человекам - надо кое что уточнить... В основном их используется 3 типа: обычные десятичные, как у нас, у людей: 8, 9, 10, 11, 12... число в хексе, обычно так выглядит в хекс редакторе: 8, 9, A, B, C (чтобы уточнить, что это именно хекс, то обычно пишут значок бакса, и потом уже число $8, $9, $A, $B, $C... либо, если аллергия на валюту: 0х08, 0х09, 0х0A, 0x0B, 0x0C) бинарный код, то есть нули и единички: 00001000, 00001001, 00001010, 00001011, 00001100 (чтобы уточнить что это в бинарном виде, то обычно пишут значок процента и потом уже число %00001000, %00001001, %00001010) Но и это еще не все. Запись переменных word и long может быть двух видов. Big-Endian и Little-Endian. Иначе говоря один порядок правильный, второй перевернутый. Какой из них какой - черт его знает :) PB, зараза, может читать и писать только в перевернутом. А обычно в файлах рома порядок правильный. Типа для примера long: в роме $12, $34, $56, $78 PB прочитает как $78, $56, $34, $12 Эту проблему с невозможностью писать в нужном для нас виде отметим, но пока отложим. Вернемся к ней попозже. Теперь переходим к практике. Во вложении будет прикреплен тестовой файл. Для понимания процесса хорошо бы чтоб на компьютере был установлен WinHex - чтобы можно было проверять правильность работы нашей программы. Работа с файлом может происходить двумя способами: открываем и работаем с файлом прямо на месте, то есть на жестком диске. открываем, читаем в память, закрываем, работаем в памяти, если надо сохранить, переписываем файл из памяти на жесткий диск. На первоначальном этапе думаю мы пойдем первым путем. Нам понадобятся команды открытия и закрытия файлов, чтения, записи и прыжка в нужное место файла. Из скрина выше нам известен адрес, где лежит наша переменная $12345678 - $1C и мы знаем что она long. Значит нам надо открыть файл, прыгнуть на этот адрес $1C и прочитать. Вроде задача не сложная. --- Код: --- Enumeration #File EndEnumeration If ReadFile(#File, "D:\SEGA\Forum\testfile.bin") FileSeek(#File, $1C) x = ReadLong(#File) CloseFile(#File) EndIf Debug x ; как десятичное. не интересно. Debug Hex(x, #PB_Long) ; в виде хекс. то что надо. --- Конец кода --- И нас ждет эпик фейл :) Так как число прочиталось в перевернутом виде. Вместо $12345678 мы видим $78563412. Чтобы выкрутится - можно сделать так: читать не как long - то есть 4 байта за раз - ReadLong, а читать 4 раза по одному байту - ReadAsciiCharacter, и просто потом их сдвигать в нужное место. Звучит конечно страшно и не понятно, но тут можно особо не вникать, так как код все сделает. Я всегда так делаю: если код не понятный, но работает - просто принимаешь на веру и радуешься :) --- Код: --- Enumeration #File EndEnumeration If ReadFile(#File, "D:\SEGA\Forum\testfile.bin") FileSeek(#File, $1C) ;x = ReadLong(#File) ; заккоментировали, и поэтому текст зеленый и не считается x = ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) CloseFile(#File) EndIf Debug x ; как десятичное. не интересно. Debug Hex(x, #PB_Long) ; в виде хекс. то что надо. --- Конец кода --- Что касается сдвига, а в данном случае x << 8 тут надо немного уточнить. 1 байт состоит из 8 битов. Эти самые биты можно увидеть отобразив число в бинарном виде. Например: --- Код: ---x = 120 ; это десятичный вид Debug Bin(x) --- Конец кода --- покажет как %1111000 - бинарный, или биты (на самом деле %01111000 просто нолики в начале съедает.) если мы это число "сдвигаем" << 8, то это означает добавляем нолики в конце. было %01111000 стало %0111100000000000 типа 120 в хекс это: $78 $78 << 8 = $7800 то-же самое если мы сдвигаем в другую сторону. например было число $789A $789A >> 8 = $78 С этим разобрались. Теперь усложним наш код и изменим ReadFile на OpenFile, так-же прочитаем из нужного места и перевернем наше число, и попытаемся его записать в другое нужное место. --- Код: --- Enumeration #File EndEnumeration If OpenFile(#File, "D:\SEGA\Forum\testfile.bin") FileSeek(#File, $1C) ;x = ReadLong(#File) ; заккоментировали, и поэтому текст зеленый и не считается x = ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) FileSeek(#File, $3C) WriteLong(#File, x) CloseFile(#File) EndIf --- Конец кода --- Запускаем, смотрим наш файл в хекс редакторе и видим, что там ошибка. Да, мы прыгнули в нужное место записи - $3C, но записывать надо было не стандартными методами PB - WriteLong - 4 байта за раз, а точно так-же разбив long на 4 байта по одной штуке и писать их по очереди. А то получается мы записали число задом наперед :) --- Код: ---Enumeration #File EndEnumeration If OpenFile(#File, "D:\SEGA\Forum\testfile.bin") FileSeek(#File, $1C) ;x = ReadLong(#File) ; заккоментировали, и поэтому текст зеленый и не считается x = ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) FileSeek(#File, $3C) ;WriteLong(#File, x) ; выкидываем y = x >> 24 WriteAsciiCharacter(#File, y) y = x >> 16 WriteAsciiCharacter(#File, y) y = x >> 8 WriteAsciiCharacter(#File, y) WriteAsciiCharacter(#File, x) CloseFile(#File) EndIf --- Конец кода --- Здесь как раз используется сдвиг в другую сторону x >> 8. И глядя на код вы можете сказать: - Но, позвольте! x >> 24 я согласен, что превратит $12345678 в $12, но ведь x >> 16 сделает не нужные нам $34, а $1234 - а это совершенно не правильно! Хорошее замечание. Придется ответить: - А тут все дело в команде записи - WriteAsciiCharacter - она записывает только 1 байт. Поэтому независимо от того, что в нашем числе сейчас сидит двухбайтовое $1234 - запишутся в файл только последние $34 - то что нам и надо. |
| SeregaZ:
дааааа кто понял - тот понял, а кто не понял - тот Филлип Киркоров. ой. то есть тот Yoti. Продолжаем упарываться. У нас есть отдаленное понимание как построить окно программы и как худо бедно читать и писать в файл. Пора совместить. Для начала думаю можно сделать так, что при запуске программы - она будет читать файл нашего "рома", показывать оригинальное значение $12345678 и будет иметь кнопку, жмакая которую мы будем производить вписывание этого самого числа куда нужно. --- Код: ---; перечисление окон, гаджетов и файлов Enumeration #Window #TextPrivetMir #TextHex #KnopkaSave #File EndEnumeration ; читать файл можно до создания окна. ; а докучи если файл не смог быть прочитан - окно можно даже не создавать ; а сразу написать, что шеф, все пропало! ; ReadFile - читаем, то есть файл недоступен для записи, а только читаем для безопасности. If ReadFile(#File, "D:\SEGA\Forum\testfile.bin") ; прыгаем на известный нам адрес $1C внутри файла FileSeek(#File, $1C) ; читаем в нужном виде нашу переменную long размером 4 байта x = ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) x << 8 x + ReadAsciiCharacter(#File) ; закрываем файл CloseFile(#File) Else ; условите "Иначе" ; то есть если программа не смогла найти или не смогла открыть наш файл рома ; выходит случилась ошибка ; выводим сообщение об ошибке и закрываем программу MessageRequester("Алярм!!!111расрас", "Ошибка! Я вся такая не смогла прочитать файл. Или не нашла его, или он открыт в другой программе. Черт знает что там случилось.") ; прыгаем в конец программы, чтобы не открывать окно. ; окно получается бесполезно - мы же не смогли прочитать оригинальный файл Goto marker EndIf ; создание окна If OpenWindow(#Window, 100, 100, 300, 100, "Я у мамы программист.", #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered) ; рисуем гаджеты на окне ; в х у нас сидит число. а числа текстовой гаджет показывать не умеет. ; ему надо перевести число в текст. это делается с помощью Str(x) ; текстовая переменная должна иметь значок доллара в конце ; типа x$. то есть х это число, а x$ это текст. x$ = Str(x) ; но это в десятичном виде. а нам надо в хекс TextGadget(#TextPrivetMir, 10, 10, 200, 20, "Оригинальное число: " + x$) x$ = Hex(x, #PB_Long) ; вот теперь в тексте x$ сидит число в виде хекс TextGadget(#TextHex, 10, 30, 200, 20, "В хекс виде: $" + x$) ButtonGadget(#KnopkaSave, 10, 60, 50, 20, "Вписать") ; бесконечный цикл обработки событий окна Repeat ; определяем с чем именно мы имеем дело Select WaitWindowEvent() ; событие касается какого-то гаджета Case #PB_Event_Gadget ; раз событие касается гаджетов, то надо перебрать какой именно гаджет имеет это событие Select EventGadget() Case #KnopkaSave ; оказывается гаджет кнопки If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; тут мы открываем файл для записи. если что-то пойдет не так - можно испортить файл. If OpenFile(#File, "D:\SEGA\Forum\testfile.bin") ; прыгаем в нужное для записи место FileSeek(#File, $3C) ; в х все еще сидит нужное нам число ; производим специальную операцию записи в нужном нам виде y = x >> 24 WriteAsciiCharacter(#File, y) y = x >> 16 WriteAsciiCharacter(#File, y) y = x >> 8 WriteAsciiCharacter(#File, y) WriteAsciiCharacter(#File, x) ; закрываем файл CloseFile(#File) Else ; если файл мы не смогли открыть - надо сообщить об ошибке MessageRequester("опять ошипка :(", "Сохранение файла не удалось. Или он открыт в другой программе, или еще какая катавасия случилась.") EndIf EndIf EndSelect ; событие касается закрытия окна Case #PB_Event_CloseWindow qiut = 1 EndSelect ; цикл крутится, пока не сработает закрытие окна и соответственно qiut станет равен 1 Until qiut = 1 EndIf ; метка для досрочного завершения программы, когда файла рома не нашлось marker: ; завершение программы End --- Конец кода --- |
| DrMefistO:
:( Не очень понимаю, кому это нужно. Материалов что по простым типам данных полно, что по Basic... Даже по написанию GUI... |
| Yoti:
DrMefistO, и это ещё не учитывая тот факт, что полезность изучения Бэйсика нынче крайне сомнительна. Ну и, насколько я помню по другим темам, он сам так ничего до конца и не дописал. Вечно ремарки "нужно добавить, нужно обновить" и т.д. А уже других учить пытается. :lol: |
| SeregaZ:
да я тоже не понимаю зачем ты делаешь свой плагин к иде. но ты ведь его делаешь :) значит оно кому-то нужно. в смысле весь этот бред с костылями - ида, питон, плагин, эмулятор... бред собачий :) делай сразу единую ОДНУ прогу, без костылей и ошибок. сразу чтоб в одном флаконе было и эмулятор, и весь разбор, и нормальное сохранение, и сразу компилер, и чтоб все происходило в памяти, без создания временных файлов на диске. (главное конечно чтоб ошибок в коде не рожало. типа как сейчас прозевал разницу .s и .w кажись... где-то там... забыл где. jsr наверное. где джамп сам на себя какой-то корявый. и еще что-то там, уже всё и не упомню...) Отвлеклись. Продолжаем упарываться. То что уже сделано, то есть общая конструкция - можно сказать это уже готовый патчер для рома. Типа нам что-то известно - адреса и значения, и этим проектом можно что-то там исправить по известным адресам, втуливая туда нужные исправленные значения. Однако продолжая тему GUI - хотелось бы сделать программу более вежливой, а именно добавить диалоги указания файла к примеру. Или сделать окошко для ввода ручками на теле программы значения какого-то параметра. И только потом уже делать сохранение. В начале определяемся с внешним видом окна. Берем в руки пейнт и рисуем что мы там хотим увидеть. Думаю должно быть что-то типа такого: На первоначальном этапе тип пусть так и остается long - 4 байта. А чтобы не держать файл рома открытым все это время - прочитаем его в образ в памяти и будем работать с ним оттуда, а не с жесткого диска. Типа если вдруг что-то пойдет не так, то оригинальный файл мы не повредим... точнее повредим конечно, но в момент записи. А до этого - чтение файла (ReadFile) будет вполне себе безопасным. Память. Чтобы с ней работать - нужно сначала её зарезервировать: AllocateMemory. Это как бы объяснить... у нас есть картина метр на метр. Чтобы с ней успешно работать - нужен стол в мастерской, размером не меньше метр на метр. Вот это резервирование и есть как бы создание стола, куда мы положим наш квадрат Малевича и будем с ним работать. А так запись и чтение происходит аналогично как в случае с файлом, только что главное нам не вылезьти за размеры этого стола, иначе наступит конец света. Так-же разница между работой с файлом и памятью заключается в том, что надо отдельно отслеживать позицию куда мы пишем или читаем из памяти. Если с файлом что-то читается, то позиция сама автоматически сдвигается дальше - это как глаз, который читает текст - то есть прочитал слово, а глаз сам прыгнул на начало следующего слова и читает дальше. Вот в случае с памятью это не работает и надо отдельно вести счетчик где мы читаем и работать уже с ним. Еще там будут нюансы с окошком диалога выбора пути до нужного файла. Там можно будет выставить ограничение на показ файлов - вместо *.* поставить что-то конкретное. Хочу поставить *.bin чтобы показывались только ромы, без всяких прочих других файлов типа exe, картинок, музыки... Для окошек ввода текста поставим ограничение на ввод больших букв (параметр #PB_String_UpperCase), чтобы было что-то типа 45A4C1 вместо 45a4c1... А окошку "значение из файла" дополнительно поставим флаг "только для чтения" #PB_String_ReadOnly. Хм... пока писал код - понял, что нужна отдельно кнопка "считать". Чтобы после ввода адреса в окошко её нажать, чтобы система прочитала из памяти по нужном адресу и вывела в окошке. Старость :) --- Код: ---; перечисление окон, гаджетов и файлов Enumeration #Window #TextPutDoRoma #StrokaPutDoRoma #KnopkaPutDoRoma #TextAdresParametra #TextAdrerParametra2 ; значок доллара будет #StrokaAdresParametra #TextZnachenieParametra #TextZnachenieParametra2 ; $ #StrokaZnachenieParametra #ProchitatZnachenieParametra #TextNovoeZnachenie #TextNovoeZnachenie2 ; $ #StrokaNovoeZnachenie #ZadatNovoeZnachenie #File EndEnumeration ; создание окна If OpenWindow(#Window, 100, 100, 410, 155, "Я у мамы программист.", #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered) ; рисуем гаджеты на окне ; текстовой с надписью путь до рома TextGadget(#TextPutDoRoma, 10, 10, 200, 20, "путь до рома:") ; строчка, куда мы будем указывать путь, выбранный через кнопку "обзор" StringGadget(#StrokaPutDoRoma, 10, 30, 330, 20, "", #PB_String_ReadOnly) ; сама кнопка обзор ButtonGadget(#KnopkaPutDoRoma, 350, 30, 50, 20, "обзор") ; адрес параметра TextGadget(#TextAdresParametra, 25, 70, 70, 20, "адр. парам.:") ; доллар. без америки никуда. 92 - чуть ниже чем 90, чтобы визуально доллар был на одной линии TextGadget(#TextAdrerParametra2, 10, 92, 20, 20, "$") ; само окошко ввода адреса StringGadget(#StrokaAdresParametra, 25, 90, 70, 20, "", #PB_String_UpperCase) ; значение из файла TextGadget(#TextZnachenieParametra, 135, 70, 70, 20, "значение:") ; доллар. TextGadget(#TextZnachenieParametra2, 120, 92, 20, 20, "$") ; окошко адреса из файла StringGadget(#StrokaZnachenieParametra, 135, 90, 70, 20, "", #PB_String_UpperCase | #PB_String_ReadOnly) ; кнопка прочитать ButtonGadget(#ProchitatZnachenieParametra, 80, 120, 70, 20, "прочитать") ; новое значение TextGadget(#TextNovoeZnachenie, 250, 70, 70, 20, "новое знач.:") ; доллар. TextGadget(#TextNovoeZnachenie2, 235, 92, 20, 20, "$") ; окошко нового значения StringGadget(#StrokaNovoeZnachenie, 250, 90, 70, 20, "", #PB_String_UpperCase) ; кнопка задать ButtonGadget(#ZadatNovoeZnachenie, 330, 90, 70, 20, "задать") ; бесконечный цикл обработки событий окна Repeat ; определяем с чем именно мы имеем дело Select WaitWindowEvent() ; событие касается какого-то гаджета Case #PB_Event_Gadget ; раз событие касается гаджетов, то надо перебрать какой именно гаджет имеет это событие Select EventGadget() Case #KnopkaPutDoRoma ;{ событие на кнопке указания пути до рома If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; открываем диалог указания пути до файла рома PutDoRoma$ = OpenFileRequester("укажите путь до файла рома", "", "Rom bin file | *.bin", 0) If PutDoRoma$ ; если путь все-таки был указан, то... If ReadFile(#File, PutDoRoma$) ; если файл прочитался, то... ; в начале сбрасываем память, а то может мы уже второй ром открываем. ; то есть возможно в памяти уже висит старый ром, ; а значит надо освободить старую память If *MemoryID FreeMemory(*MemoryID) *MemoryID = 0 EndIf ; далее зачистить окошки адреса и того старого значения. а так-же нового. SetGadgetText(#StrokaAdresParametra, "") SetGadgetText(#StrokaZnachenieParametra, "") SetGadgetText(#StrokaNovoeZnachenie, "") ; получаем размер файла length = Lof(#File) If length ; если размер файла больше нуля. ; эта проверка на случай если файл поврежден. ; резервируем память по размеру файла *MemoryID = AllocateMemory(length) If *MemoryID ; если память зарезервировалась, то все хорошо. играем дальше. ; читаем файл с жесткого диска в подготовленную память ; то есть файл - #File, в какую память - *MemoryID, размер сколько читать - length. ReadData(#File, *MemoryID, length) ; раз у нас есть чтение чего-то в память, значит делаем вывод что все хорошо ; указываем путь в окошке для пути SetGadgetText(#StrokaPutDoRoma, PutDoRoma$) Else ; какая-то ошибка с памятью. опять выводим окошко с ошибкой. MessageRequester("ошибка", "проблема с резервированием памяти.") EndIf Else ; выходит размер файла все-таки 0. значит ошибка. вывести предупреждение. MessageRequester("ошибка", "файл рома видимо поврежден и имеет размер 0 байт.") EndIf ; закрываем файл CloseFile(#File) Else ; выходит файл прочитать не смогли MessageRequester("ошибка", "файл не может быть прочитан. возможно занят другой программой.") EndIf EndIf EndIf ;} Case #ProchitatZnachenieParametra ;{ кнопка прочитать из памяти значение по какому-то адресу If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; надо удостоверится, что файл рома был считан и существует память, куда все записалось If length And *MemoryID ; надо удостоверися, что у нас есть адрес откуда читать Address$ = GetGadgetText(#StrokaAdresParametra) If Address$ ; надо перевести текст адреса в число ; и сравнить нет ли превышения размера рома length ; чтобы не начать читать за пределами нужной памяти ; к текстовому значению Address$ был прибавлен символ доллара, ; чтобы команда Val поняла, что текст это хекс, а не обычные десятичные числа Address = Val("$" + Address$) If Address < length ; все в порядке. адрес в нужных пределах рома ; просчитываем место в памяти, откуда следует читать mem = *MemoryID + Address ; читаем 4 байта из памяти по одному байту и сдвигаем итоговое значение Result = PeekA(mem) << 8 Result | PeekA(mem + 1) Result << 8 Result | PeekA(mem + 2) Result << 8 Result | PeekA(mem + 3) ; выводим в окошке на теле программы итоговое значение с переводом в хекс SetGadgetText(#StrokaZnachenieParametra, Hex(Result, #PB_Long)) Else ; превышение. выдать предупреждение. MessageRequester("ошибка", "вы пытаетесь прочитать за пределами файла рома.") EndIf Else ; нет адреса, откуда читать. MessageRequester("ошибка", "кто-то забыл указать адрес откуда нам читать (1C английскими).") EndIf Else ; ром еще видимо незагружен MessageRequester("ошибка", "ром еще не был загружен.") EndIf EndIf ;} Case #ZadatNovoeZnachenie ;{ кнопка внести новое значение в файл If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; надо удостоверится, что файл рома был считан и существует память, куда все записалось If length And *MemoryID ; надо удостоверися, что у нас есть адрес куда писать Address$ = GetGadgetText(#StrokaAdresParametra) If Address$ ; надо перевести текст адреса в число ; и сравнить нет ли превышения размера рома length ; чтобы не начать писать за пределами нужной памяти ; к текстовому значению Address$ был прибавлен символ доллара, ; чтобы команда Val поняла, что текст это хекс, а не обычные десятичные числа Address = Val("$" + Address$) If Address < length ; все в порядке. адрес в нужных пределах рома ; просчитываем место в памяти, куда следует писать mem = *MemoryID + Address ; проверяем есть ли какое-то число в окошке, которое нам надо записать в память Zapis$ = GetGadgetText(#StrokaNovoeZnachenie) ; по старой схеме переводим текст в число, с учетом что это хекс, а не десятичное Zapis = Val("$" + Zapis$) ; разбиваем значение на 4 байта first.a = Zapis >> 24 secnd.a = Zapis >> 16 third.a = Zapis >> 8 fourd.a = Zapis ; пишем в память PokeA(mem, first) PokeA(mem + 1, secnd) PokeA(mem + 2, third) PokeA(mem + 3, fourd) ; вроде как в памяти у нас уже сидит новое значение. ; значит надо перезаписать весь файл целиком ; читаем путь до рома из окошка в программе PutDoRoma2$ = GetGadgetText(#StrokaPutDoRoma) If PutDoRoma2$ ; перепроверяем что он действительно существует ; создаем файл по новой If CreateFile(#File, PutDoRoma2$) ; пишем туда образ памяти ; то есть куда - #File, какая именно память - *MemoryID, размер сколько писать - length WriteData(#File, *MemoryID, length) ; закрываем файл CloseFile(#File) MessageRequester("победа!", "вроде бы все записалось куда надо. надо открыть наш ром в хекс редакторе и посмотреть что там куда записалось.") Else ; файл создать не получилось MessageRequester("ошибка", "файл не создался. может быть открыт в другой программе или еще чего.") EndIf Else MessageRequester("ошибка", "нет пути до файла рома.") EndIf Else ; превышение. выдать предупреждение. MessageRequester("ошибка", "вы пытаетесь прочитать за пределами файла рома.") EndIf EndIf Else ; ром еще видимо незагружен MessageRequester("ошибка", "ром еще не был загружен.") EndIf EndIf ;} EndSelect ; событие касается закрытия окна Case #PB_Event_CloseWindow qiut = 1 EndSelect ; цикл крутится, пока не сработает закрытие окна и соответственно qiut станет равен 1 Until qiut = 1 EndIf ; завершение программы End --- Конец кода --- |
| Mr2:
--- Цитата: SeregaZ от 01 Май 2024, 22:25:36 ---делай сразу единую ОДНУ прогу, без костылей и ошибок. --- Конец цитаты --- Действительно, а если не можешь, строчи туторы по барсику. :rofl: |
| SeregaZ:
Поцоны, расходимся. Нас наебманули! Оказывается Бейсик-барсик не тащит :( И ничего в нем сделать невозможно... тото я думаю почему я ни один свой проект не закончил - оказывается язык не тот! Кто-ж знал? Где вы, атцы-ногебаторы с++, раньше были? Почему не подсказали мне - неучу? Так... а что значит не можешь? Усё я могу :) Просто не хочу... На самом деле пробно интересовался у продвинутых чуваков на тему создания эмулятора в виде длл, так как переписать полностью под мой язык я конечно-же не смогу. Вот дллку-эмулятор можно было бы свободно подключать к проектам, как это было с дллкой Sharpnull для GEMS - шикарная фигня! Доволен по самые помидоры! Поэтому с лету подключить существующий эмулятор с дебагом для иды не получилось. Типа я вижу что Ида и эмуль стартуют свои веб сервера, потом ида чото шлет эмулю... но повторить не получилось :) А с какой-то там шаред мемори... я хз чо это и откуда береться. Надо с буржуями советоваться, чтоб подсказали как этот эмуль с дебагом подключить. Правда там еще надо знать что от него хотеть - тут я тож пока не бум бум как получать от него адреса, которые эмуль найдет в процессе игры. Но одну часть марлезонского балета я все-таки сделал. Хотелось бы конечно лучше, так как кнопка разбора кода С работает у меня медленновато и это дико дико раздражает :) Задача была читать ром, разбирать код, и создавать асм файл, который без танцев с бубном, какие необходимы кое какому другому проекту, могли бы собираться православным ASM68K.exe. До флешроялю только не хватило эмуль с дебагом из идовского проекта. Ну и ассемблер свой тоже будет сделать тяжело, так как регулярок для разбора команд там надо будет даже не миллион - а миллиард :) Если стандартные я еще смогу добавить, то вот варианты с формулами, где всякие арифметические действия нужны внутри одного параметра - тут полный швах. Хотя надо опять таки поинтересоваться у братьев-буржуев - подозреваю там давно есть нужное решение и нужно только его спионерить. пример, правда я вот не помню распоследнее ли там залито или нет: https://www.emu-land.net/forum/index.php?action=dlattach;topic=88184.0;attach=273451 Так... опять пришли всякие флудеры, нафлудили пол страницы, отвлекли меня-любимого, от проекта по захватыванию вселенной. Сейчас наша супер мега программа читает и пишет long - 4 байтовые значения. Полагаю пришло время добавить некоторую универсальность и сделать переключатель 4-2-1 байт. Для этого нам понадобятся новые приблуды: OptionGadget и SetGadgetState. SetGadgetState - команда для отметки. То есть в ней мы указываем какой гаджет нужно обработать и вторым параметром 1 или 0 - произвести отметку или убрать отметку с гаджета. OptionGadget - это круглишок с текстом, который надо отметить мышкой. Когда выбирается какой-то пункт, то со старого отмеченного пункта точка отметки убирается, то есть происходит переключение. Чтобы система понимала, что эти 3 пункта это 3 варианта для одного какого-то случая - надо их размещать рядом: --- Код: --- OptionGadget(#LongTipParametra, 10, 140, 100, 20, "long (4 байта)") OptionGadget(#WordTipParametra, 120, 140, 100, 20, "word (2 байта)") OptionGadget(#ByteTipParametra, 240, 140, 100, 20, "byte (1 байта)") SetGadgetState(#LongTipParametra, 1) --- Конец кода --- Неправильный вариант: --- Код: --- OptionGadget(#LongTipParametra, 10, 140, 100, 20, "long (4 байта)") SetGadgetState(#LongTipParametra, 1) OptionGadget(#WordTipParametra, 120, 140, 100, 20, "word (2 байта)") OptionGadget(#ByteTipParametra, 240, 140, 100, 20, "byte (1 байта)") --- Конец кода --- Система будет считать это как два разных переключателя - одновариантный сверху и с двумя вариантами на выбор снизу. Чото я как-то замудрено написал :))) И наверное надо еще прояснить момент с Select. Чтобы не плодить миллион IF - Если - существует такая удобная фигня Select. Если А = 1, то сделать это... Как бы понятная конструкция. Проверяем какую-то переменную на соответствие её единице. Но что если у этой переменной может быть много значений, и в зависимости от числа нужно делать разные действия? Можно конечно сделать миллион IF: --- Код: ---If A = 1 ElseIf A = 2 ElseIf A = 3 ElseIf A = 4 ... --- Конец кода --- Не совсем удобно. Select докучи еще и может задавать целый сегмент значений: --- Код: ---Select A Case 1 Case 2 to 4 ; с двух до четырех Case 5 to 10, 14, 45 ; с пяти до десяти, и отдельно 14 и отдельно 45 Default ; все что не вошло в предыдущие пункты. 0, 11, 12, 13, с 15 до 44, с 46 и до бесконечности Endselect --- Конец кода --- Итак, с добавленными этими вариантами размера переменной код будет выглядеть так: --- Код: --- ; перечисление окон, гаджетов и файлов Enumeration #Window #TextPutDoRoma #StrokaPutDoRoma #KnopkaPutDoRoma #TextAdresParametra #TextAdrerParametra2 ; значок доллара будет #StrokaAdresParametra #TextZnachenieParametra #TextZnachenieParametra2 ; $ #StrokaZnachenieParametra #ProchitatZnachenieParametra #TextNovoeZnachenie #TextNovoeZnachenie2 ; $ #StrokaNovoeZnachenie #ZadatNovoeZnachenie #TextTipParametra #LongTipParametra #WordTipParametra #ByteTipParametra #File EndEnumeration ; создание окна If OpenWindow(#Window, 100, 100, 410, 200, "Я у мамы программист.", #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered) ; рисуем гаджеты на окне ; текстовой с надписью путь до рома TextGadget(#TextPutDoRoma, 10, 10, 200, 20, "путь до рома:") ; строчка, куда мы будем указывать путь, выбранный через кнопку "обзор" StringGadget(#StrokaPutDoRoma, 10, 30, 330, 20, "", #PB_String_ReadOnly) ; сама кнопка обзор ButtonGadget(#KnopkaPutDoRoma, 350, 30, 50, 20, "обзор") ; адрес параметра TextGadget(#TextAdresParametra, 25, 70, 70, 20, "адр. парам.:") ; доллар. без америки никуда. 92 - чуть ниже чем 90, чтобы визуально доллар был на одной линии TextGadget(#TextAdrerParametra2, 10, 92, 20, 20, "$") ; само окошко ввода адреса StringGadget(#StrokaAdresParametra, 25, 90, 70, 20, "", #PB_String_UpperCase) ; значение из файла TextGadget(#TextZnachenieParametra, 135, 70, 70, 20, "значение:") ; доллар. TextGadget(#TextZnachenieParametra2, 120, 92, 20, 20, "$") ; окошко значение с адреса из файла StringGadget(#StrokaZnachenieParametra, 135, 90, 70, 20, "", #PB_String_UpperCase | #PB_String_ReadOnly) ; строчка текста тип параметра TextGadget(#TextTipParametra, 10, 120, 70, 20, "тип:") OptionGadget(#LongTipParametra, 10, 140, 100, 20, "long (4 байта)") OptionGadget(#WordTipParametra, 120, 140, 100, 20, "word (2 байта)") OptionGadget(#ByteTipParametra, 240, 140, 100, 20, "byte (1 байта)") SetGadgetState(#LongTipParametra, 1) TipPeremennoy = 4 ; кнопка прочитать ButtonGadget(#ProchitatZnachenieParametra, 80, 170, 70, 20, "прочитать") ; новое значение TextGadget(#TextNovoeZnachenie, 250, 70, 70, 20, "новое знач.:") ; доллар. TextGadget(#TextNovoeZnachenie2, 235, 92, 20, 20, "$") ; окошко нового значения StringGadget(#StrokaNovoeZnachenie, 250, 90, 70, 20, "", #PB_String_UpperCase) ; кнопка задать ButtonGadget(#ZadatNovoeZnachenie, 330, 90, 70, 20, "задать") ; бесконечный цикл обработки событий окна Repeat ; определяем с чем именно мы имеем дело Select WaitWindowEvent() ; событие касается какого-то гаджета Case #PB_Event_Gadget ; раз событие касается гаджетов, то надо перебрать какой именно гаджет имеет это событие Select EventGadget() Case #KnopkaPutDoRoma ;{ событие на кнопке указания пути до рома If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; открываем диалог указания пути до файла рома PutDoRoma$ = OpenFileRequester("укажите путь до файла рома", "", "Rom bin file | *.bin", 0) If PutDoRoma$ ; если путь все-таки был указан, то... If ReadFile(#File, PutDoRoma$) ; если файл прочитался, то... ; в начале сбрасываем память, а то может мы уже второй ром открываем. ; то есть возможно в памяти уже висит старый ром, ; а значит надо освободить старую память If *MemoryID FreeMemory(*MemoryID) *MemoryID = 0 EndIf ; далее зачистить окошки адреса и того старого значения. а так-же нового. SetGadgetText(#StrokaAdresParametra, "") SetGadgetText(#StrokaZnachenieParametra, "") SetGadgetText(#StrokaNovoeZnachenie, "") ; получаем размер файла length = Lof(#File) If length ; если размер файла больше нуля. ; эта проверка на случай если файл поврежден. ; резервируем память по размеру файла *MemoryID = AllocateMemory(length) If *MemoryID ; если память зарезервировалась, то все хорошо. играем дальше. ; читаем файл с жесткого диска в подготовленную память ; то есть файл - #File, в какую память - *MemoryID, размер сколько читать - length. ReadData(#File, *MemoryID, length) ; раз у нас есть чтение чего-то в память, значит делаем вывод что все хорошо ; указываем путь в окошке для пути SetGadgetText(#StrokaPutDoRoma, PutDoRoma$) Else ; какая-то ошибка с памятью. опять выводим окошко с ошибкой. MessageRequester("ошибка", "проблема с резервированием памяти.") EndIf Else ; выходит размер файла все-таки 0. значит ошибка. вывести предупреждение. MessageRequester("ошибка", "файл рома видимо поврежден и имеет размер 0 байт.") EndIf ; закрываем файл CloseFile(#File) Else ; выходит файл прочитать не смогли MessageRequester("ошибка", "файл не может быть прочитан. возможно занят другой программой.") EndIf EndIf EndIf ;} Case #ProchitatZnachenieParametra ;{ кнопка прочитать из памяти значение по какому-то адресу If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; надо удостоверится, что файл рома был считан и существует память, куда все записалось If length And *MemoryID ; надо удостоверися, что у нас есть адрес откуда читать Address$ = GetGadgetText(#StrokaAdresParametra) If Address$ ; надо перевести текст адреса в число ; и сравнить нет ли превышения размера рома length ; чтобы не начать читать за пределами нужной памяти ; к текстовому значению Address$ был прибавлен символ доллара, ; чтобы команда Val поняла, что текст это хекс, а не обычные десятичные числа Address = Val("$" + Address$) If Address < length ; все в порядке. адрес в нужных пределах рома ; просчитываем место в памяти, откуда следует читать mem = *MemoryID + Address ; в зависимости от выбранного типа переменной будем читать различное количество байт Select TipPeremennoy Case 4 ; long ; читаем 4 байта из памяти по одному байту и сдвигаем итоговое значение Result = PeekA(mem) << 8 Result | PeekA(mem + 1) Result << 8 Result | PeekA(mem + 2) Result << 8 Result | PeekA(mem + 3) ; выводим в окошке на теле программы итоговое значение с переводом в хекс SetGadgetText(#StrokaZnachenieParametra, Hex(Result, #PB_Long)) Case 2 ; word ; читаем 2 байта из памяти по одному байту и сдвигаем итоговое значение Result = PeekA(mem) << 8 Result | PeekA(mem + 1) ; выводим в окошке на теле программы итоговое значение с переводом в хекс SetGadgetText(#StrokaZnachenieParametra, Hex(Result, #PB_Word)) Case 1 ; byte ; просто читаем 1 байт с нужного адреса Result = PeekA(mem) ; выводим в окошке на теле программы итоговое значение с переводом в хекс SetGadgetText(#StrokaZnachenieParametra, Hex(Result, #PB_Byte)) EndSelect Else ; превышение. выдать предупреждение. MessageRequester("ошибка", "вы пытаетесь прочитать за пределами файла рома.") EndIf Else ; нет адреса, откуда читать. MessageRequester("ошибка", "кто-то забыл указать адрес откуда нам читать (1C английскими).") EndIf Else ; ром еще видимо незагружен MessageRequester("ошибка", "ром еще не был загружен.") EndIf EndIf ;} Case #ZadatNovoeZnachenie ;{ кнопка внести новое значение в файл If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; надо удостоверится, что файл рома был считан и существует память, куда все записалось If length And *MemoryID ; надо удостоверися, что у нас есть адрес куда писать Address$ = GetGadgetText(#StrokaAdresParametra) If Address$ ; надо перевести текст адреса в число ; и сравнить нет ли превышения размера рома length ; чтобы не начать писать за пределами нужной памяти ; к текстовому значению Address$ был прибавлен символ доллара, ; чтобы команда Val поняла, что текст это хекс, а не обычные десятичные числа Address = Val("$" + Address$) If Address < length ; все в порядке. адрес в нужных пределах рома ; просчитываем место в памяти, куда следует писать mem = *MemoryID + Address ; проверяем есть ли какое-то число в окошке, которое нам надо записать в память Zapis$ = GetGadgetText(#StrokaNovoeZnachenie) ; по старой схеме переводим текст в число, с учетом что это хекс, а не десятичное Zapis = Val("$" + Zapis$) ; в зависимости от типа переменной запись будет происходить по разному Select TipPeremennoy Case 4 ; long ; разбиваем значение на 4 байта first.a = Zapis >> 24 secnd.a = Zapis >> 16 third.a = Zapis >> 8 fourd.a = Zapis ; пишем в память PokeA(mem, first) PokeA(mem + 1, secnd) PokeA(mem + 2, third) PokeA(mem + 3, fourd) Case 2 ; word ; разбиваем значение на 2 байта first.a = Zapis >> 8 secnd.a = Zapis PokeA(mem, first) PokeA(mem + 1, secnd) Case 1 ; byte PokeA(mem, Zapis) EndSelect ; вроде как в памяти у нас уже сидит новое значение. ; значит надо перезаписать весь файл целиком ; читаем путь до рома из окошка в программе PutDoRoma2$ = GetGadgetText(#StrokaPutDoRoma) If PutDoRoma2$ ; перепроверяем что он действительно существует ; создаем файл по новой If CreateFile(#File, PutDoRoma2$) ; пишем туда образ памяти ; то есть куда - #File, какая именно память - *MemoryID, размер сколько писать - length WriteData(#File, *MemoryID, length) ; закрываем файл CloseFile(#File) MessageRequester("победа!", "вроде бы все записалось куда надо. надо открыть наш ром в хекс редакторе и посмотреть что там куда записалось.") Else ; файл создать не получилось MessageRequester("ошибка", "файл не создался. может быть открыт в другой программе или еще чего.") EndIf Else MessageRequester("ошибка", "нет пути до файла рома.") EndIf Else ; превышение. выдать предупреждение. MessageRequester("ошибка", "вы пытаетесь прочитать за пределами файла рома.") EndIf EndIf Else ; ром еще видимо незагружен MessageRequester("ошибка", "ром еще не был загружен.") EndIf EndIf ;} Case #LongTipParametra ; переключим флаг, сигнализирующий какая переменная нам нужна TipPeremennoy = 4 ; стираем текст из окошка значения адреса SetGadgetText(#StrokaZnachenieParametra, "") Case #WordTipParametra TipPeremennoy = 2 ; стираем текст из окошка значения адреса SetGadgetText(#StrokaZnachenieParametra, "") Case #ByteTipParametra TipPeremennoy = 1 ; стираем текст из окошка значения адреса SetGadgetText(#StrokaZnachenieParametra, "") EndSelect ; событие касается закрытия окна Case #PB_Event_CloseWindow qiut = 1 EndSelect ; цикл крутится, пока не сработает закрытие окна и соответственно qiut станет равен 1 Until qiut = 1 EndIf ; завершение программы End --- Конец кода --- |
| Mr2:
--- Цитата: SeregaZ от 02 Май 2024, 17:53:59 ---Где вы, атцы-ногебаторы с++, раньше были? Почему не подсказали мне - неучу? --- Конец цитаты --- Тебе уважаемые люди объяснили что ты зря портишь бумагу, а ты не только не послушал, а ещё и нагрубил. :cop: В общем совет такой, пиши конкретный редактор, а не "какой-то". ;) |
| Werton:
--- Цитата: Yoti от 01 Май 2024, 20:46:14 ---и это ещё не учитывая тот факт, что полезность изучения Бэйсика нынче крайне сомнительна. --- Конец цитаты --- Тулза эта на самом деле не так плоха, все в одном яп, gui, 2д, 3д библиотеки, может на лету транслировать в Си и компилить С компилятором (не помню какой там в комплекте). Когда надо по быстрому на коленке на ведре с болтами запилить что-то и нет возможности разворачивать полноценные среды разработки, то почему бы и нет, а т.к. это бэйсик то учить там по сути ничего не надо :D |
| SeregaZ:
ты зачем все карты раскрыл? :) ну сидят они - не читали и осуждают и пусть себе сидят :) а ты взял и открыл им глаза на истину вселенной. блин. я расстроен :) |
| SeregaZ:
А. ромхакер не всегда программист софта. так-же и наоборот. а докучи еще бывают ромхакеры, которые совсем не ромхакеры, но делают вполне себе годные ромхаки :) Б. по плану происходит плавное вкатывание в процесс программирования от простого к сложному, от объяснения к практике. изначально я хотел делать тему, как когда-то 500 лет назад для особой хитрой программы для игры Lineage 2 :) я на форуме вешал программные квесты, где участники их пытались решать и от простых задач мы потихоньку двигались к сложным. так было бы интереснее, но зная "активность" форума... отказался от этой затеи. решил постить только готовый код с объяснениями, с нарастающей сложностью задачи. но увы... набежали тролли, развели флуд и информативные посты начинают тонуть :) Добавлено позже: ну и что касается слова Бейсик в заголовке. есть такая предвзятость у людей: - А, Бейсик? Так мы его в школе проходили. Дерьмо. Читать эту тему не буду. неверное отношение :) следует понимать не как: Бейсик это что-то забытое и не интересное. А как вполне себе рабочий и действенный инструмент для решения не супер сложных задач в ромхакинге. поэтому то там выше я и написал - кто понял тот понял, а кто не понял... улыбаемся и машем. |
| SeregaZ:
Как бы с втуливанием 4-2-1 байтных значений разобрались. А что если данных куда больше, чем одно значение? Для мегадрайвового случая есть такая замечательная штука как ASM68K.exe и в принципе можно втулить эти самые данные через неё. Создаем текстовой файл - например code.txt, и пишем туда следующий текст: --- Код: --- org 0 incbin "testfile.bin" ; оригинальный ром. org $1C ; это адрес, где сидит наше long значение $12345678 incbin "file.bin" ; файл с данными которые надо вписать. его сейчас конечно не существует, но предположим он как суслик - есть и занимает 10 байт к примеру. --- Конец кода --- После создаем build.bat файл, куда пишем что-то типа: --- Код: ---asm68k /p code.txt,NEW_ROM.bin pause --- Конец кода --- Что здесь что: org - это аналогично нашему PB'шному FileSeek - прыжок внутри файла по нужному адресу incbin - включить в итоговый файл данные из файла, который здесь будет указан asm68k с параметрами - собственно вызов ассемблера, который соберет файл и на выходе получится NEW_ROM.bin pause - черное дос-вида окошко не закроется после выполнения программы, а продолжит висеть, чтобы бы могли прочитать результат - успешно ли или может какая ошибка. и если ошибка, то где именно. В итоговом файле, начиная с указанного адреса $1C будут находится данные из файла file.bin. То есть это опять таки можно назвать своего рода патчером. Вот для расширения функционала нашей программы предлагаю это добавить. Что нового будет использовано в коде: гаджет Frame - визуальная рамка, чтобы разделить функционал программы. Типа сверху втуливание одного значения, а снизу вписывание целого файла. указатель на образ памяти и отдельная переменная для указания размера файла, который надо вписать в итоговый ром - *MemoryID2 и length2 Значит по схеме выше сначала вписываем файл в память, а после пишем память в память - хотя при такой записи могут быть нюансы, но о них позже. Так-же понадобится скачать и распаковать из архива файл file.bin в нашу папку с проектом - мы для тестов будет указывать именно его для вписывания в наш ром файл. --- Код: ---; перечисление окон, гаджетов и файлов Enumeration #Window #TextPutDoRoma #StrokaPutDoRoma #KnopkaPutDoRoma #TextAdresParametra #TextAdrerParametra2 ; значок доллара будет #StrokaAdresParametra #TextZnachenieParametra #TextZnachenieParametra2 ; $ #StrokaZnachenieParametra #ProchitatZnachenieParametra #TextNovoeZnachenie #TextNovoeZnachenie2 ; $ #StrokaNovoeZnachenie #ZadatNovoeZnachenie #TextTipParametra #LongTipParametra #WordTipParametra #ByteTipParametra #File #Frame #PatchText #PatchPut #PatchObzor #PatchTextAddres #PatchTextAddres2 #PatchAddres #PatchKnopka EndEnumeration ; создание окна If OpenWindow(#Window, 100, 100, 410, 350, "Я у мамы программист.", #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered) ; рисуем гаджеты на окне ; текстовой с надписью путь до рома TextGadget(#TextPutDoRoma, 10, 10, 200, 20, "путь до рома:") ; строчка, куда мы будем указывать путь, выбранный через кнопку "обзор" StringGadget(#StrokaPutDoRoma, 10, 30, 330, 20, "", #PB_String_ReadOnly) ; сама кнопка обзор ButtonGadget(#KnopkaPutDoRoma, 350, 30, 50, 20, "обзор") ; адрес параметра TextGadget(#TextAdresParametra, 25, 70, 70, 20, "адр. парам.:") ; доллар. без америки никуда. 92 - чуть ниже чем 90, чтобы визуально доллар был на одной линии TextGadget(#TextAdrerParametra2, 10, 92, 20, 20, "$") ; само окошко ввода адреса StringGadget(#StrokaAdresParametra, 25, 90, 70, 20, "", #PB_String_UpperCase) ; значение из файла TextGadget(#TextZnachenieParametra, 135, 70, 70, 20, "значение:") ; доллар. TextGadget(#TextZnachenieParametra2, 120, 92, 20, 20, "$") ; окошко значение с адреса из файла StringGadget(#StrokaZnachenieParametra, 135, 90, 70, 20, "", #PB_String_UpperCase | #PB_String_ReadOnly) ; строчка текста тип параметра TextGadget(#TextTipParametra, 10, 120, 70, 20, "тип:") OptionGadget(#LongTipParametra, 10, 140, 100, 20, "long (4 байта)") OptionGadget(#WordTipParametra, 120, 140, 100, 20, "word (2 байта)") OptionGadget(#ByteTipParametra, 240, 140, 100, 20, "byte (1 байта)") SetGadgetState(#LongTipParametra, 1) TipPeremennoy = 4 ; кнопка прочитать ButtonGadget(#ProchitatZnachenieParametra, 80, 170, 70, 20, "прочитать") ; новое значение TextGadget(#TextNovoeZnachenie, 250, 70, 70, 20, "новое знач.:") ; доллар. TextGadget(#TextNovoeZnachenie2, 235, 92, 20, 20, "$") ; окошко нового значения StringGadget(#StrokaNovoeZnachenie, 250, 90, 70, 20, "", #PB_String_UpperCase) ; кнопка задать ButtonGadget(#ZadatNovoeZnachenie, 330, 90, 70, 20, "задать") FrameGadget(#Frame, 10, 210, 390, 130, "данные") TextGadget(#PatchText, 25, 230, 100, 20, "файл с данными:") StringGadget(#PatchPut, 25, 250, 300, 20, "", #PB_String_ReadOnly) ButtonGadget(#PatchObzor, 335, 250, 50, 20, "обзор") TextGadget(#PatchTextAddres, 35, 280, 200, 20, "писать начиная с адреса:") TextGadget(#PatchTextAddres2, 25, 302, 20, 20, "$") StringGadget(#PatchAddres, 35, 300, 100, 20, "", #PB_String_UpperCase) ButtonGadget(#PatchKnopka, 150, 300, 100, 20, "вписать в ром") ; бесконечный цикл обработки событий окна Repeat ; определяем с чем именно мы имеем дело Select WaitWindowEvent() ; событие касается какого-то гаджета Case #PB_Event_Gadget ; раз событие касается гаджетов, то надо перебрать какой именно гаджет имеет это событие Select EventGadget() Case #KnopkaPutDoRoma ;{ событие на кнопке указания пути до рома If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; открываем диалог указания пути до файла рома PutDoRoma$ = OpenFileRequester("укажите путь до файла рома", "", "Rom bin file | *.bin", 0) If PutDoRoma$ ; если путь все-таки был указан, то... If ReadFile(#File, PutDoRoma$) ; если файл прочитался, то... ; в начале сбрасываем память, а то может мы уже второй ром открываем. ; то есть возможно в памяти уже висит старый ром, ; а значит надо освободить старую память If *MemoryID FreeMemory(*MemoryID) *MemoryID = 0 EndIf ; далее зачистить окошки адреса и того старого значения. а так-же нового. SetGadgetText(#StrokaAdresParametra, "") SetGadgetText(#StrokaZnachenieParametra, "") SetGadgetText(#StrokaNovoeZnachenie, "") ; получаем размер файла length = Lof(#File) If length ; если размер файла больше нуля. ; эта проверка на случай если файл поврежден. ; резервируем память по размеру файла *MemoryID = AllocateMemory(length) If *MemoryID ; если память зарезервировалась, то все хорошо. играем дальше. ; читаем файл с жесткого диска в подготовленную память ; то есть файл - #File, в какую память - *MemoryID, размер сколько читать - length. ReadData(#File, *MemoryID, length) ; раз у нас есть чтение чего-то в память, значит делаем вывод что все хорошо ; указываем путь в окошке для пути SetGadgetText(#StrokaPutDoRoma, PutDoRoma$) Else ; какая-то ошибка с памятью. опять выводим окошко с ошибкой. MessageRequester("ошибка", "проблема с резервированием памяти.") EndIf Else ; выходит размер файла все-таки 0. значит ошибка. вывести предупреждение. MessageRequester("ошибка", "файл рома видимо поврежден и имеет размер 0 байт.") EndIf ; закрываем файл CloseFile(#File) Else ; выходит файл прочитать не смогли MessageRequester("ошибка", "файл не может быть прочитан. возможно занят другой программой.") EndIf EndIf EndIf ;} Case #ProchitatZnachenieParametra ;{ кнопка прочитать из памяти значение по какому-то адресу If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; надо удостоверится, что файл рома был считан и существует память, куда все записалось If length And *MemoryID ; надо удостоверися, что у нас есть адрес откуда читать Address$ = GetGadgetText(#StrokaAdresParametra) If Address$ ; надо перевести текст адреса в число ; и сравнить нет ли превышения размера рома length ; чтобы не начать читать за пределами нужной памяти ; к текстовому значению Address$ был прибавлен символ доллара, ; чтобы команда Val поняла, что текст это хекс, а не обычные десятичные числа Address = Val("$" + Address$) If Address < length ; все в порядке. адрес в нужных пределах рома ; просчитываем место в памяти, откуда следует читать mem = *MemoryID + Address ; в зависимости от выбранного типа переменной будем читать различное количество байт Select TipPeremennoy Case 4 ; long ; читаем 4 байта из памяти по одному байту и сдвигаем итоговое значение Result = PeekA(mem) << 8 Result | PeekA(mem + 1) Result << 8 Result | PeekA(mem + 2) Result << 8 Result | PeekA(mem + 3) ; выводим в окошке на теле программы итоговое значение с переводом в хекс SetGadgetText(#StrokaZnachenieParametra, Hex(Result, #PB_Long)) Case 2 ; word ; читаем 2 байта из памяти по одному байту и сдвигаем итоговое значение Result = PeekA(mem) << 8 Result | PeekA(mem + 1) ; выводим в окошке на теле программы итоговое значение с переводом в хекс SetGadgetText(#StrokaZnachenieParametra, Hex(Result, #PB_Word)) Case 1 ; byte ; просто читаем 1 байт с нужного адреса Result = PeekA(mem) ; выводим в окошке на теле программы итоговое значение с переводом в хекс SetGadgetText(#StrokaZnachenieParametra, Hex(Result, #PB_Byte)) EndSelect Else ; превышение. выдать предупреждение. MessageRequester("ошибка", "вы пытаетесь прочитать за пределами файла рома.") EndIf Else ; нет адреса, откуда читать. MessageRequester("ошибка", "кто-то забыл указать адрес откуда нам читать (1C английскими).") EndIf Else ; ром еще видимо незагружен MessageRequester("ошибка", "ром еще не был загружен.") EndIf EndIf ;} Case #ZadatNovoeZnachenie ;{ кнопка внести новое значение в файл If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; надо удостоверится, что файл рома был считан и существует память, куда все записалось If length And *MemoryID ; надо удостоверися, что у нас есть адрес куда писать Address$ = GetGadgetText(#StrokaAdresParametra) If Address$ ; надо перевести текст адреса в число ; и сравнить нет ли превышения размера рома length ; чтобы не начать писать за пределами нужной памяти ; к текстовому значению Address$ был прибавлен символ доллара, ; чтобы команда Val поняла, что текст это хекс, а не обычные десятичные числа Address = Val("$" + Address$) If Address < length ; все в порядке. адрес в нужных пределах рома ; просчитываем место в памяти, куда следует писать mem = *MemoryID + Address ; проверяем есть ли какое-то число в окошке, которое нам надо записать в память Zapis$ = GetGadgetText(#StrokaNovoeZnachenie) ; по старой схеме переводим текст в число, с учетом что это хекс, а не десятичное Zapis = Val("$" + Zapis$) ; в зависимости от типа переменной запись будет происходить по разному Select TipPeremennoy Case 4 ; long ; разбиваем значение на 4 байта first.a = Zapis >> 24 secnd.a = Zapis >> 16 third.a = Zapis >> 8 fourd.a = Zapis ; пишем в память PokeA(mem, first) PokeA(mem + 1, secnd) PokeA(mem + 2, third) PokeA(mem + 3, fourd) Case 2 ; word ; разбиваем значение на 2 байта first.a = Zapis >> 8 secnd.a = Zapis PokeA(mem, first) PokeA(mem + 1, secnd) Case 1 ; byte PokeA(mem, Zapis) EndSelect ; вроде как в памяти у нас уже сидит новое значение. ; значит надо перезаписать весь файл целиком ; читаем путь до рома из окошка в программе PutDoRoma2$ = GetGadgetText(#StrokaPutDoRoma) If PutDoRoma2$ ; перепроверяем что он действительно существует ; создаем файл по новой If CreateFile(#File, PutDoRoma2$) ; пишем туда образ памяти ; то есть куда - #File, какая именно память - *MemoryID, размер сколько писать - length WriteData(#File, *MemoryID, length) ; закрываем файл CloseFile(#File) MessageRequester("победа!", "вроде бы все записалось куда надо. надо открыть наш ром в хекс редакторе и посмотреть что там куда записалось.") Else ; файл создать не получилось MessageRequester("ошибка", "файл не создался. может быть открыт в другой программе или еще чего.") EndIf Else MessageRequester("ошибка", "нет пути до файла рома.") EndIf Else ; превышение. выдать предупреждение. MessageRequester("ошибка", "вы пытаетесь прочитать за пределами файла рома.") EndIf EndIf Else ; ром еще видимо незагружен MessageRequester("ошибка", "ром еще не был загружен.") EndIf EndIf ;} Case #LongTipParametra ; переключим флаг, сигнализирующий какая переменная нам нужна TipPeremennoy = 4 ; стираем текст из окошка значения адреса SetGadgetText(#StrokaZnachenieParametra, "") Case #WordTipParametra TipPeremennoy = 2 ; стираем текст из окошка значения адреса SetGadgetText(#StrokaZnachenieParametra, "") Case #ByteTipParametra TipPeremennoy = 1 ; стираем текст из окошка значения адреса SetGadgetText(#StrokaZnachenieParametra, "") Case #PatchObzor ;{ указываем путь до файла с данными, который надо вписать в главный ром If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; открываем диалог указания пути до файла рома PutDoFile$ = OpenFileRequester("укажите путь до файла, который нужно вписать в ром", "", "Rom bin file | *.bin", 0) If PutDoFile$ ; если путь все-таки был указан, то... If ReadFile(#File, PutDoFile$) ; если файл прочитался, то... ; в начале сбрасываем память, а то может мы уже второй раз открываем. ; то есть возможно в памяти уже висит старый образ файла, ; а значит надо освободить старую память If *MemoryID2 FreeMemory(*MemoryID2) *MemoryID2 = 0 EndIf ; получаем размер файла length2 = Lof(#File) If length2 ; если размер файла больше нуля. ; эта проверка на случай если файл поврежден. ; резервируем память по размеру файла *MemoryID2 = AllocateMemory(length2) If *MemoryID2 ; если память зарезервировалась, то все хорошо. ; читаем файл с жесткого диска в подготовленную память ReadData(#File, *MemoryID2, length2) ; далее вписываем путь до файла в окошко пути. SetGadgetText(#PatchPut, PutDoFile$) Else ; какая-то ошибка с памятью. опять выводим окошко с ошибкой. MessageRequester("ошибка", "проблема с резервированием памяти.") EndIf Else ; выходит размер файла все-таки 0. значит ошибка. вывести предупреждение. MessageRequester("ошибка", "файл видимо поврежден и имеет размер 0 байт.") EndIf ; закрываем файл CloseFile(#File) Else ; выходит файл прочитать не смогли MessageRequester("ошибка", "файл не может быть прочитан. возможно занят другой программой.") EndIf EndIf EndIf ;} Case #PatchKnopka ;{ кнопка вписывания файла с данными в итоговый ром If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; проверить указан ли ром файл и записан ли он в память RomFile$ = GetGadgetText(#StrokaPutDoRoma) If RomFile$ And *MemoryID And length ; то есть существует и путь до файла рома и образ в памяти и размер больше 0 ; проверить указан ли путь до файла с данными PatchFile$ = GetGadgetText(#PatchPut) If PatchFile$ And *MemoryID2 And length2 ; то есть существует и путь до файла с данными и образ в памяти и размер больше 0 ; проверить есть ли у нас вбитый пользователем адрес куда начать писать файл StartAddress$ = GetGadgetText(#PatchAddres) If StartAddress$ ; переводим текст в число StartAddress = Val("$" + StartAddress$) ; расчет адреса в памяти нашей программы, куда надо вписать файл с данными mem = *MemoryID + StartAddress ; непосредственно вписывание в памяти CopyMemory(*MemoryID2, mem, length2) ; когда память уже подготовлена, то есть данные куда надо уже вписаны - создаем файл рома If CreateFile(#File, RomFile$) ; пишем туда образ памяти WriteData(#File, *MemoryID, length) CloseFile(#File) MessageRequester("опять победа, чо...", "все прошло успешно. перепроверяем что получилось в хекс редакторе.") Else ; ошибка создания файла рома MessageRequester("ошибка", "создать патченный ром файл не удалось. видимо этот файл открыт в другой программе.") EndIf Else ; адреса нет. выводим ошибку. MessageRequester("ашипка", "вы не указали стартовый адрес откуда начинать запись файла с данными в роме.") EndIf Else ; ошибка. нет или пути до рома или образ в памяти отсутствует MessageRequester("ошибка", "путь до файла с данными не указан, либо проблема с работой памяти программы, либо размер файла 0.") EndIf Else ; ошибка. нет или пути до рома или образ в памяти отсутствует MessageRequester("ошибка", "путь до файла рома не указан, либо проблема с работой памяти программы, либо размер файла 0.") EndIf EndIf ;} EndSelect ; событие касается закрытия окна Case #PB_Event_CloseWindow qiut = 1 EndSelect ; цикл крутится, пока не сработает закрытие окна и соответственно qiut станет равен 1 Until qiut = 1 EndIf ; завершение программы End --- Конец кода --- Теперь что касается нюансов вписывания память в память. Память для PB это такая нежная штука... чуть чих влево или вправо - все вылетает к чертовой бабушке :) Если наш файл патча маленький и мы пишем куда-то в середину файла рома, как в моем примере, то проблем не будет. Но вот если наш патч файл вылезет за пределы оригинального размера памяти рома, то этот самый вылет и произойдет. Этот нюанс работы в памятью надо иметь ввиду и продумывать заранее, а не как я :) То есть надо добавить всякие дополнительные проверки размеров. Типа рассчитывать начальный адрес + размер файла патча = не вылезет ли за пределы оригинального размера рома? Но это уже другая история :) Добавлено позже: ну и да... у нас должен получится новый ром файл с таким содержимым: |
| SeregaZ:
Прежде следующего "урока" думаю было бы не плохо обсудить менеджмент дизайна окна. В нашем случае уже как бы есть два раздельных сегмента функционала программы - верхний, где можно задать какое-то одно значение в роме и нижний, где можно вписать какой-то файл во внутрь рома. В случае если я хочу поменять их местами - скажем сегмент с памятью поместить наверх, а с отдельными значениями - вниз, то придется каждому гаджету менять вручную координаты. Как бы с одной стороны да, там в PB вроде как есть встроенный редактор форм, где мышкой можно таскать кнопки и прочие гаджеты и размещать как душе угодно, но я им не пользуюсь. Предпочитаю указывать координаты как переменные x и y, где внутри сегмента есть базовый стартовый гаджет с x и y, а остальные имеют координаты x + 10, y + 10 скажем, то есть рисуются относительно стартового гаджета на окне программы. Тогда эти самые сегменты функционала можно сразу двигать оптом, меняя координаты только в одном месте. Предлагаю от воды перейти к практике и рассмотреть эту мысль в коде. * пришлось код снести, так как не дает постануть вторую часть кода, с уже с добавленным элементом. чушь кароче с форумом :) лимит на количество строк в одном посте видимо. Вы можете возразить: - Простите, но ведь здесь нет никакой разницы! Что за бред вы тут втираете?!?!? - А вы попробуйте добавить еще один фрейм - рамку - Frame - которая бы визуально обрамляла верхнюю часть программы для красивости. придется двигать все, уже нарисованные гаджеты на окне программы. Теперь же, когда у нас все через переменные x и y - достаточно будет в одном месте вбить другие координаты и сразу весь этот блок сдвинется на нужное место. Как в коде ниже: --- Код: ---; перечисление окон, гаджетов и файлов Enumeration #Window #TextPutDoRoma #StrokaPutDoRoma #KnopkaPutDoRoma #Frame2 #TextAdresParametra #TextAdrerParametra2 ; значок доллара будет #StrokaAdresParametra #TextZnachenieParametra #TextZnachenieParametra2 ; $ #StrokaZnachenieParametra #ProchitatZnachenieParametra #TextNovoeZnachenie #TextNovoeZnachenie2 ; $ #StrokaNovoeZnachenie #ZadatNovoeZnachenie #TextTipParametra #LongTipParametra #WordTipParametra #ByteTipParametra #File #Frame #PatchText #PatchPut #PatchObzor #PatchTextAddres #PatchTextAddres2 #PatchAddres #PatchKnopka EndEnumeration ; создание окна If OpenWindow(#Window, 100, 100, 430, 370, "Я у мамы программист.", #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered) ; рисуем гаджеты на окне ; стартовые х и у, откуда будут танцевать все гаджеты на окне x = 10 y = 10 ; текстовой с надписью путь до рома TextGadget(#TextPutDoRoma, x, y, 200, 20, "путь до рома:") ; строчка, куда мы будем указывать путь, выбранный через кнопку "обзор" StringGadget(#StrokaPutDoRoma, x, y + 20, 330, 20, "", #PB_String_ReadOnly) ; сама кнопка обзор ButtonGadget(#KnopkaPutDoRoma, x + 340, y + 20, 50, 20, "обзор") ; рамка FrameGadget(#Frame2, x, y + 60, 410, 155, "отдельное значение") ; чтобы сдвинуть ниже и влево прибавляем к х и у x + 10 y + 20 ; адрес параметра TextGadget(#TextAdresParametra, x + 15, y + 60, 70, 20, "адр. парам.:") ; доллар. без америки никуда. 92 - чуть ниже чем 90, чтобы визуально доллар был на одной линии TextGadget(#TextAdrerParametra2, x, y + 82, 20, 20, "$") ; само окошко ввода адреса StringGadget(#StrokaAdresParametra, x + 15, y + 80, 70, 20, "", #PB_String_UpperCase) ; значение из файла TextGadget(#TextZnachenieParametra, x + 125, y + 60, 70, 20, "значение:") ; доллар. TextGadget(#TextZnachenieParametra2, x + 110, y + 82, 20, 20, "$") ; окошко значение с адреса из файла StringGadget(#StrokaZnachenieParametra, x + 125, y + 80, 70, 20, "", #PB_String_UpperCase | #PB_String_ReadOnly) ; строчка текста тип параметра TextGadget(#TextTipParametra, x, y + 110, 70, 20, "тип:") OptionGadget(#LongTipParametra, x, y + 130, 100, 20, "long (4 байта)") OptionGadget(#WordTipParametra, x + 110, y + 130, 100, 20, "word (2 байта)") OptionGadget(#ByteTipParametra, x + 230, y + 130, 100, 20, "byte (1 байта)") SetGadgetState(#LongTipParametra, 1) TipPeremennoy = 4 ; кнопка прочитать ButtonGadget(#ProchitatZnachenieParametra, x + 70, y + 160, 70, 20, "прочитать") ; новое значение TextGadget(#TextNovoeZnachenie, x + 240, y + 60, 70, 20, "новое знач.:") ; доллар. TextGadget(#TextNovoeZnachenie2, x + 225, y + 82, 20, 20, "$") ; окошко нового значения StringGadget(#StrokaNovoeZnachenie, x + 240, y + 80, 70, 20, "", #PB_String_UpperCase) ; кнопка задать ButtonGadget(#ZadatNovoeZnachenie, x + 320, y + 80, 70, 20, "задать") x - 10 ; возвращаем как было FrameGadget(#Frame, x, y + 200, 410, 130, "данные") TextGadget(#PatchText, x + 15, y + 220, 100, 20, "файл с данными:") StringGadget(#PatchPut, x + 15, y + 240, 300, 20, "", #PB_String_ReadOnly) ButtonGadget(#PatchObzor, x + 325, y + 240, 50, 20, "обзор") TextGadget(#PatchTextAddres, x + 25, y + 270, 200, 20, "писать начиная с адреса:") TextGadget(#PatchTextAddres2, x + 15, y + 292, 20, 20, "$") StringGadget(#PatchAddres, x + 25, y + 290, 100, 20, "", #PB_String_UpperCase) ButtonGadget(#PatchKnopka, x + 140, y + 290, 100, 20, "вписать в ром") ; бесконечный цикл обработки событий окна Repeat ; определяем с чем именно мы имеем дело Select WaitWindowEvent() ; событие касается какого-то гаджета Case #PB_Event_Gadget ; раз событие касается гаджетов, то надо перебрать какой именно гаджет имеет это событие Select EventGadget() Case #KnopkaPutDoRoma ;{ событие на кнопке указания пути до рома If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; открываем диалог указания пути до файла рома PutDoRoma$ = OpenFileRequester("укажите путь до файла рома", "", "Rom bin file | *.bin", 0) If PutDoRoma$ ; если путь все-таки был указан, то... If ReadFile(#File, PutDoRoma$) ; если файл прочитался, то... ; в начале сбрасываем память, а то может мы уже второй ром открываем. ; то есть возможно в памяти уже висит старый ром, ; а значит надо освободить старую память If *MemoryID FreeMemory(*MemoryID) *MemoryID = 0 EndIf ; далее зачистить окошки адреса и того старого значения. а так-же нового. SetGadgetText(#StrokaAdresParametra, "") SetGadgetText(#StrokaZnachenieParametra, "") SetGadgetText(#StrokaNovoeZnachenie, "") ; получаем размер файла length = Lof(#File) If length ; если размер файла больше нуля. ; эта проверка на случай если файл поврежден. ; резервируем память по размеру файла *MemoryID = AllocateMemory(length) If *MemoryID ; если память зарезервировалась, то все хорошо. играем дальше. ; читаем файл с жесткого диска в подготовленную память ; то есть файл - #File, в какую память - *MemoryID, размер сколько читать - length. ReadData(#File, *MemoryID, length) ; раз у нас есть чтение чего-то в память, значит делаем вывод что все хорошо ; указываем путь в окошке для пути SetGadgetText(#StrokaPutDoRoma, PutDoRoma$) Else ; какая-то ошибка с памятью. опять выводим окошко с ошибкой. MessageRequester("ошибка", "проблема с резервированием памяти.") EndIf Else ; выходит размер файла все-таки 0. значит ошибка. вывести предупреждение. MessageRequester("ошибка", "файл рома видимо поврежден и имеет размер 0 байт.") EndIf ; закрываем файл CloseFile(#File) Else ; выходит файл прочитать не смогли MessageRequester("ошибка", "файл не может быть прочитан. возможно занят другой программой.") EndIf EndIf EndIf ;} Case #ProchitatZnachenieParametra ;{ кнопка прочитать из памяти значение по какому-то адресу If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; надо удостоверится, что файл рома был считан и существует память, куда все записалось If length And *MemoryID ; надо удостоверися, что у нас есть адрес откуда читать Address$ = GetGadgetText(#StrokaAdresParametra) If Address$ ; надо перевести текст адреса в число ; и сравнить нет ли превышения размера рома length ; чтобы не начать читать за пределами нужной памяти ; к текстовому значению Address$ был прибавлен символ доллара, ; чтобы команда Val поняла, что текст это хекс, а не обычные десятичные числа Address = Val("$" + Address$) If Address < length ; все в порядке. адрес в нужных пределах рома ; просчитываем место в памяти, откуда следует читать mem = *MemoryID + Address ; в зависимости от выбранного типа переменной будем читать различное количество байт Select TipPeremennoy Case 4 ; long ; читаем 4 байта из памяти по одному байту и сдвигаем итоговое значение Result = PeekA(mem) << 8 Result | PeekA(mem + 1) Result << 8 Result | PeekA(mem + 2) Result << 8 Result | PeekA(mem + 3) ; выводим в окошке на теле программы итоговое значение с переводом в хекс SetGadgetText(#StrokaZnachenieParametra, Hex(Result, #PB_Long)) Case 2 ; word ; читаем 2 байта из памяти по одному байту и сдвигаем итоговое значение Result = PeekA(mem) << 8 Result | PeekA(mem + 1) ; выводим в окошке на теле программы итоговое значение с переводом в хекс SetGadgetText(#StrokaZnachenieParametra, Hex(Result, #PB_Word)) Case 1 ; byte ; просто читаем 1 байт с нужного адреса Result = PeekA(mem) ; выводим в окошке на теле программы итоговое значение с переводом в хекс SetGadgetText(#StrokaZnachenieParametra, Hex(Result, #PB_Byte)) EndSelect Else ; превышение. выдать предупреждение. MessageRequester("ошибка", "вы пытаетесь прочитать за пределами файла рома.") EndIf Else ; нет адреса, откуда читать. MessageRequester("ошибка", "кто-то забыл указать адрес откуда нам читать (1C английскими).") EndIf Else ; ром еще видимо незагружен MessageRequester("ошибка", "ром еще не был загружен.") EndIf EndIf ;} Case #ZadatNovoeZnachenie ;{ кнопка внести новое значение в файл If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; надо удостоверится, что файл рома был считан и существует память, куда все записалось If length And *MemoryID ; надо удостоверися, что у нас есть адрес куда писать Address$ = GetGadgetText(#StrokaAdresParametra) If Address$ ; надо перевести текст адреса в число ; и сравнить нет ли превышения размера рома length ; чтобы не начать писать за пределами нужной памяти ; к текстовому значению Address$ был прибавлен символ доллара, ; чтобы команда Val поняла, что текст это хекс, а не обычные десятичные числа Address = Val("$" + Address$) If Address < length ; все в порядке. адрес в нужных пределах рома ; просчитываем место в памяти, куда следует писать mem = *MemoryID + Address ; проверяем есть ли какое-то число в окошке, которое нам надо записать в память Zapis$ = GetGadgetText(#StrokaNovoeZnachenie) ; по старой схеме переводим текст в число, с учетом что это хекс, а не десятичное Zapis = Val("$" + Zapis$) ; в зависимости от типа переменной запись будет происходить по разному Select TipPeremennoy Case 4 ; long ; разбиваем значение на 4 байта first.a = Zapis >> 24 secnd.a = Zapis >> 16 third.a = Zapis >> 8 fourd.a = Zapis ; пишем в память PokeA(mem, first) PokeA(mem + 1, secnd) PokeA(mem + 2, third) PokeA(mem + 3, fourd) Case 2 ; word ; разбиваем значение на 2 байта first.a = Zapis >> 8 secnd.a = Zapis PokeA(mem, first) PokeA(mem + 1, secnd) Case 1 ; byte PokeA(mem, Zapis) EndSelect ; вроде как в памяти у нас уже сидит новое значение. ; значит надо перезаписать весь файл целиком ; читаем путь до рома из окошка в программе PutDoRoma2$ = GetGadgetText(#StrokaPutDoRoma) If PutDoRoma2$ ; перепроверяем что он действительно существует ; создаем файл по новой If CreateFile(#File, PutDoRoma2$) ; пишем туда образ памяти ; то есть куда - #File, какая именно память - *MemoryID, размер сколько писать - length WriteData(#File, *MemoryID, length) ; закрываем файл CloseFile(#File) MessageRequester("победа!", "вроде бы все записалось куда надо. надо открыть наш ром в хекс редакторе и посмотреть что там куда записалось.") Else ; файл создать не получилось MessageRequester("ошибка", "файл не создался. может быть открыт в другой программе или еще чего.") EndIf Else MessageRequester("ошибка", "нет пути до файла рома.") EndIf Else ; превышение. выдать предупреждение. MessageRequester("ошибка", "вы пытаетесь прочитать за пределами файла рома.") EndIf EndIf Else ; ром еще видимо незагружен MessageRequester("ошибка", "ром еще не был загружен.") EndIf EndIf ;} Case #LongTipParametra ; переключим флаг, сигнализирующий какая переменная нам нужна TipPeremennoy = 4 ; стираем текст из окошка значения адреса SetGadgetText(#StrokaZnachenieParametra, "") Case #WordTipParametra TipPeremennoy = 2 ; стираем текст из окошка значения адреса SetGadgetText(#StrokaZnachenieParametra, "") Case #ByteTipParametra TipPeremennoy = 1 ; стираем текст из окошка значения адреса SetGadgetText(#StrokaZnachenieParametra, "") Case #PatchObzor ;{ указываем путь до файла с данными, который надо вписать в главный ром If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; открываем диалог указания пути до файла рома PutDoFile$ = OpenFileRequester("укажите путь до файла, который нужно вписать в ром", "", "Rom bin file | *.bin", 0) If PutDoFile$ ; если путь все-таки был указан, то... If ReadFile(#File, PutDoFile$) ; если файл прочитался, то... ; в начале сбрасываем память, а то может мы уже второй раз открываем. ; то есть возможно в памяти уже висит старый образ файла, ; а значит надо освободить старую память If *MemoryID2 FreeMemory(*MemoryID2) *MemoryID2 = 0 EndIf ; получаем размер файла length2 = Lof(#File) If length2 ; если размер файла больше нуля. ; эта проверка на случай если файл поврежден. ; резервируем память по размеру файла *MemoryID2 = AllocateMemory(length2) If *MemoryID2 ; если память зарезервировалась, то все хорошо. ; читаем файл с жесткого диска в подготовленную память ReadData(#File, *MemoryID2, length2) ; далее вписываем путь до файла в окошко пути. SetGadgetText(#PatchPut, PutDoFile$) Else ; какая-то ошибка с памятью. опять выводим окошко с ошибкой. MessageRequester("ошибка", "проблема с резервированием памяти.") EndIf Else ; выходит размер файла все-таки 0. значит ошибка. вывести предупреждение. MessageRequester("ошибка", "файл видимо поврежден и имеет размер 0 байт.") EndIf ; закрываем файл CloseFile(#File) Else ; выходит файл прочитать не смогли MessageRequester("ошибка", "файл не может быть прочитан. возможно занят другой программой.") EndIf EndIf EndIf ;} Case #PatchKnopka ;{ кнопка вписывания файла с данными в итоговый ром If EventType() = #PB_EventType_LeftClick ; если событие клик левой кнопкой мышки ; проверить указан ли ром файл и записан ли он в память RomFile$ = GetGadgetText(#StrokaPutDoRoma) If RomFile$ And *MemoryID And length ; то есть существует и путь до файла рома и образ в памяти и размер больше 0 ; проверить указан ли путь до файла с данными PatchFile$ = GetGadgetText(#PatchPut) If PatchFile$ And *MemoryID2 And length2 ; то есть существует и путь до файла с данными и образ в памяти и размер больше 0 ; проверить есть ли у нас вбитый пользователем адрес куда начать писать файл StartAddress$ = GetGadgetText(#PatchAddres) If StartAddress$ ; переводим текст в число StartAddress = Val("$" + StartAddress$) ; расчет адреса в памяти нашей программы, куда надо вписать файл с данными mem = *MemoryID + StartAddress ; непосредственно вписывание в памяти CopyMemory(*MemoryID2, mem, length2) ; когда память уже подготовлена, то есть данные куда надо уже вписаны - создаем файл рома If CreateFile(#File, RomFile$) ; пишем туда образ памяти WriteData(#File, *MemoryID, length) CloseFile(#File) MessageRequester("опять победа, чо...", "все прошло успешно. перепроверяем что получилось в хекс редакторе.") Else ; ошибка создания файла рома MessageRequester("ошибка", "создать патченный ром файл не удалось. видимо этот файл открыт в другой программе.") EndIf Else ; адреса нет. выводим ошибку. MessageRequester("ашипка", "вы не указали стартовый адрес откуда начинать запись файла с данными в роме.") EndIf Else ; ошибка. нет или пути до рома или образ в памяти отсутствует MessageRequester("ошибка", "путь до файла с данными не указан, либо проблема с работой памяти программы, либо размер файла 0.") EndIf Else ; ошибка. нет или пути до рома или образ в памяти отсутствует MessageRequester("ошибка", "путь до файла рома не указан, либо проблема с работой памяти программы, либо размер файла 0.") EndIf EndIf ;} EndSelect ; событие касается закрытия окна Case #PB_Event_CloseWindow qiut = 1 EndSelect ; цикл крутится, пока не сработает закрытие окна и соответственно qiut станет равен 1 Until qiut = 1 EndIf ; завершение программы End --- Конец кода --- |
| SeregaZ:
Поскольку мы создаем супер-мега программу, то немешало бы добавить функционала по менеджменту данных. Типа это будет некий лист со списком адресов и типов данных - переменная это или может быть файл с данными. Типа сначала все данные заносятся в лист, и потом по клику собирается новый ром со всеми изменениями, то есть файл будет сохранятся один раз, а не как сейчас перезапись всего файла при изменении всего одного какого-то параметра или данных. Докучи еще можно добавить галок - если галка отмечена, то изменение в ром попадает, и если галки нет, то соответственно игнорируем эту строку. Так-же, чтобы этот самый лист не вбивать каждый раз вручную, то сделаем сохранение конфиг файла. И соответственно при старте программы - загрузку этого конфига. Так-же сам ром файл - думаю не следует изменять оригинал. Будет создаваться новый ром файл, с припиской, скажем "_NEW". И кнопки "вписать" будут не писать в файл, как раньше, а лишь перекидывать текущие адреса и значения в таблицу. Что нового используется в этой версии: ListIconGadget. это гаджет для создания таблиц. там нужен будет спец режим с галками на строчках. включается флагом #PB_ListIcon_CheckBoxes в параметрах. столбики в эту таблицу добавляются путем использования AddGadgetColumn, где указывается ширина и текст, что будет отображаться в заголовке столбика. так-же будет еще флаг #PB_ListIcon_FullRowSelect - селект будет отображаться целиком на всю строку, а не на конкретную ячейку таблицы. так-же еще будет флаг #PB_ListIcon_AlwaysShowSelection - то есть не смотря что фокус с гаджета уходит (мы мышкой елозим на другом участке окна), то селект на нужной строке все равно будет продолжать висеть для визуального указания на какой строке мы сейчас находимся. и остался еще #PB_ListIcon_GridLines - рисует полоски для ячеек в таблице. Chr. пишет символ по его коду. типа Chr(9) это табуляция. AddGadgetItem. добавляет элемент, а в данном случае строку, в гаджет со списком. ячейки в строке разделяются специальным символом. например: "ячейка 1" + Chr(10) + "ячейка 2". получается одной строкой, с разделением внутри Chr(10) мы можем задать текст сразу в двух ячейках. Str. переводит число в текст. Str(10) или Str($A) или Str(%1010) сделает из числа 10 - текст 10, который можно использовать с текстовыми переменными. CountGadgetItems. пересчитывает количество строк в гаджете. в данном случае в листе. FileSize. проверяет размер файла. если значение -1 = файла не существует. -2 это не файл, а папка. WriteStringN. пишет построчно текст в файл. и сдвигает вывод на следующую строчку. RunProgram. позволяет стартовать другие программы. здесь я хочу сделать функционал, чтобы после сборки нового рома он сразу запускался в эмуляторе. то есть будет запускаться эмулятор с указанием пути до нового ром файла для старта в эмуляторе. StringField. разбивает текст по какому-то символу или слову. типа скажем пробел. тем самым фразу "привет мир" можно разделить отдельно по словам "привет" и "мир". Итак, нужный функционал мы текстом описали - погнали гадить! ой... то есть кодить! ога... разбежался я :) форум не хочет принимать простыню с кодом. придется прикладывать в виде файла-проекта для PB. main.zip (8.77 КБ - загружено 271 раз.) Последующий пост планируется с ютубным видосом с практическими занятиями с нашей шедевральной супер программой. Поэтому КВЕСТ ДЛЯ РОМХАКЕРОВ: необходим адрес для Zero Tolerance, где лежит стартовое количество патронов для пистолета для персонажа-снайперши. Поэтому если кому не лень - было бы очень замечательно :) |
| SeregaZ:
только в конце заметил, что с цветом количества патронов что-то не то :) ну да и фиг с ним. главное главный замысел показал. в следующий раз уже будет рисование графики. |
| Sharpnull:
Я тоже всё хочу написать удобный патчер ромов с поддержкой Game Genie. Посмотрел интерфейс вашей программы на видео, эти задачи я бы решил через ассемблер, в котором есть возможность изменения отдельных байтов и вставка бинарных файлов. Для меня эти кнопочки и поля ввода неудобные, я бы сделал одно текстовое окно, в котором, например: --- Код: ---F9A: 30 ; Один байт изменяем FFFF: 01 02 03 ; Три байта 9ABCD: C:\z\1.bin ; Вставка файла --- Конец кода --- Чтобы не вставлять в файл, добавить комментарий (;). Это удобно хранить в текстовом файле и позже делать импорт/экспорт через copy-paste. Тогда левая половина программы у вас была бы одним текстовым полем с несколькими строками, а в правой список после парсинга. Ещё добавить перетаскивание файлов и другое. Примерно это я и хочу сделать. |
| SeregaZ:
Итак... начинаем медленное вкатывание в графоний. Данный пост будет состоять из двух частей. Первая - возможности PB, который не тащит и вторая - обсудим палитру Genesis/Megadrive. Хотя не думаю что там суть палитры будет разительно отличаться от других консолей. Кто помнит школьные годы - настоящие! А не эти вот все... с виндовсами, интернетом и дотой 2 - то там на бейсике учитель заставлял рисовать точки, линии, квадраты, круги... и внезапно на нашем бейсике это все работает! Plot, Line, Box, Circle... все свое родное и теплое. Рисовать PB может как в картинку, как в файл-картинку, в гаджет-канвас на окне программы, в текстуру. Нас интересует вывод в картинку и последующее отображение этой картинки в гаджете-картинке на окне программы. Каждая команда рисования имеет координаты x y куда собственно начинать рисовать элемент, имеет цвет RGB(0-255, 0-255, 0-255) и свои отдельные нюансы - типа если это линия, то координаты второй точки докуда надо рисовать линию, если циркле - радиус, и все такое прочее. Это самое прочее можно уточнить жмакнув на нужной команде рисования и нажав F1. Вот значит мы создаем новый проект, туда втуливаем болванку пустого окна программы, налепляем туда гаджет картинки и пишем код для рисования. Пока я думаю сделать длинный прямоугольник и в нем квадратами рисовать разные цвета. Типа как слово в Поле Чудес, только что вместо черного - разноцветные квадратики. Для разноцветности пусть будет рандом от 0 до 255 для каждого из трех сегментов цвета RGB. --- Код: --- Enumeration #Window #GadgetKartinka #SamaKartinka EndEnumeration ; в начале создаем само изображение, пока пустое. это будет черный длинный прямоугольник CreateImage(#SamaKartinka, 200, 20) ; стартуем сам процесс рисования ; ставим вывод, что рисовать в картинку If StartDrawing(ImageOutput(#SamaKartinka)) ; сначала черный фон на всю картинку Box(0, 0, 200, 20, RGB(0, 0, 0)) ; это черный цвет. в принципе можно было просто 0 написать вместо RGB(0, 0, 0) x = 0 ; в цикле стартуем прорисовку квадратиков с рандомным цветом, сдвигая координату Х вправо каждый раз For i = 1 To 10 Box(x, 0, 20, 20, RGB(Random(255, 0), Random(255, 0), Random(255, 0))) x + 20 Next ; остановка рисования StopDrawing() EndIf If OpenWindow(#Window, 100, 100, 220, 40, "Якубович под грибами", #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered) ; добавляем гаджет-картинку, с подготовленным изображением ImageGadget(#GadgetKartinka, 10, 10, 200, 20, ImageID(#SamaKartinka)) Repeat Select WaitWindowEvent() Case #PB_Event_CloseWindow qiut = 1 EndSelect Until qiut = 1 EndIf End --- Конец кода --- Уууууу... красоты получилось - не описуемой! Все хорошо, да только приставка наша такое не может :) Вместо абы какого RGB цвета там могут быть только огрызки. Вместо 0-255 для каждого значения RGB там могут быть всего 8 значений: 0, 36, 72, 109, 145, 181, 219, 255 (плюс минус. может на 1-2 различаться. каждый источник утверждает что только у них самые точные значения цветов и поскольку источников тьма то и цифры могут отличаться. скажем не 181, а 180, 182 и так далее) И как хочешь - так и выкручивайся. Печаль. А докучи там еще и не RGB - а BGR. Всего там могут быть 4 палитры по 15 цветов и шестнадцатый прозрачный. Если посмотреть наш любимый Zero Tolerance с помощью VDP просмоторщика, то палитра должна выглядеть так: Жмакнем в этом самом VDP виевере Dump Pal, сохраним этот файл и будем его читать в нашей программе и рисовать соответствующие цвета на картинке. Для хранения цветов думаю нам следует сделать сложный массив. Представим себе ексель таблицу. В нашей таблице будет 4 колонки и 16 строчек. Вот массив будет иметь эти самые 4 колонки и в каждой 16 ячеек, куда мы будем втуливать цвет. Сам цвет сначала надо будет конвертануть из Сеговского BGR в обычный компьютерный RGB, чтобы PB понял каким собственно цветом надо закрашивать нужный квадратик. ну и потрадиции форум взбрыкнул и незахотел принимать всю портянку кода - прикладываю в виде архива: pal.zip (2.25 КБ - загружено 262 раз.) в итоге получилась у нас такая замечательная пол литра... эээ... в смысле палитра: Похоже? :cool: |
| SeregaZ:
Итак... немного был занят захватом вселенной и пока злодейские планы не требуют моего непосредственного участия - продолжим. У нас есть чтение палитры. Это конечно замечательно, но чего-то хотелось бы еще. Приступим к рисованию графики с использованием этой самой палитры. Ранее у нас был патчер, с помощью которого мы что-то куда-то там вставляли. И нам было неизвестно точно что там было. Спойлер - это была иконка биосканнера из ZT. Попробуем её нарисовать? Значится графоний... эээ... это не просто. Для нас смертных файл с графикой это просто какая-то абракадабра - если посмотрим в хекс редакторе это что-то типа 12 34 56 78 90 AB CD EF 12 34 56... На самом деле это номера цветов в палитре. Тут не известно какая именно палитра из четырех используется (нам нужна вторая) - это номера цветов в палитре. В одном байте - например первый $12 - содержится информация о двух пикселях. То есть это число $12 надо двигать, как мы делали ранее, чтобы получить номер цвета 0-15 из палитры. --- Код: ---Value = $12 FirstPixel = Value >> 4 ; сдвиг на 4 бита. то есть из $12 мы получим $1 --- Конец кода --- То есть получается самый первый пиксель у нас с номером цвета 1 - это второй кубик в нашей палитре. Что-то там темносерый чтоль... Хорошо, первый то мы получим с помощью сдвига, а как второй пиксель получить? Тут нам понадобится применение маски к значению. --- Код: ---Value = $12 SecondPixel = Value & $F ; дааа, тарабарщина... --- Конец кода --- Получается берутся эти самые $12 на них накладывается маска $F - и тогда остается $2. То есть эта маска работает как отсечение всего, что больше $F. Таким образом мы получили два пикселя, с номерами цветов из палитры 2 и 3. Как известно графика на приставке состоит из тайлов - кубиков 8х8 пикселей. Если 1 байт на 2 пикселя - получается 8 х 8 = 64 / 2 = 32 байта на тайл. Это одно. Второе - мы видим просто строчки в хекс редакторе, а как из них построить такой тайл? Тут нам помогут циклы For, где мы будем крутить построчно и погоризонтально, и рисовать знакомой с детства командой Plot - точка. --- Код: --- ; счетчик сдвига в памяти k = 0 ; расчет докуда крутить по Х и по У endx = startx + 7 endy = starty + 7 ; сами циклы For y = starty To endy For x = startx To endx Step 2 ; перепрыгивать каждые 2 ; читаем значение из памяти Number = PeekA(memory + k) ; раскладываем число на 2 пикселя. по 4 бита на пиксель first = Number >> 4 ; знакомый сдвиг second = Number & $F ; новая херня - отсечение по маске. ; рисуем точки с нужными цветами Plot(x, y, PalNum(2)\ColorNum[first + 1]) ; надо было цвет втулить начиная с 0 Plot(x + 1, y, PalNum(2)\ColorNum[second + 1]) ; теперь приходится + 1 добавлять ; сдвиг по памяти дальше k + 1 Next Next --- Конец кода --- Получается порядок пикселей в тайле такой: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40.... и так далее 8 строчек - 8 пикселей в высоту. Это обычный порядок, но надо иметь ввиду, что такое не везде и в некоторых местах игр порядок может быть иной. Таким образом можно нарисовать один тайл (код не заработает, так как тут нет чтения из файла 01.bin с иконкой - он здесь для иллюстрации этих самых циклов прокрутки). Далее - надо знать в каком порядке идут тайлы и какой размер всего изображения. В нашем случае это 32х32 пикселя, и тайлы идут сверху вниз. 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16 Опять таки это обычный порядок. Но может быть и другой - зависит от игры и от того что курили афторы при разработке игры. Если открыть наш файл в хекс редакторе и уменьшить количество столбиков с 16 до 4, то будет более понятно как надо отрисовывать тайл: Решил сделать рисование тайла с помощью процедуры. Зачем нужны эти процедуры? Когда код повторяется много раз, и в нашем случае меняются стартовые координаты тайла и адрес в памяти, откуда читать цвета - то проще сделать процедуру, в которую мы будем слать нужные нам координаты и адрес в памяти, а она уже будет рисовать по нашему заказу. Адрес в памяти расчитывается прибавляя каждый раз по 32 байта - размер тайла. В итоге получилось что-то типа такого: |
| SeregaZ:
Лето и лень - убойные факторы для забрасывания темы. Однако через пень колоду хотелось бы перейти к процессу вставки изображения в нашу программу. На первом этапе это будет просто работа с буфером обмена и вставка в гаджет на теле программы. То есть предполагается что юзверь заранее взял в пейнте нарисовал картинку 32х32 пикселя, скопировал, а в нашей программе нажал вставить. Нам понадобятся новые элементы в нашем пазле: GetClipboardImage(#Image [, Depth]) Получить картинку из буфера обмена в #Image и задать для картинки битность, если надо 24 или 32. FreeImage(#Image) Освободить картинку в #Image, чтобы не происходило утечки памяти. Типа если мы делаем вставку два раза и не будем освобождать эту самую #Image - работать то будет, но вот с точки зрения использования памяти это не верно. Ведь первая картинка получается никуда не делась. Точнее её некий внутренний айди, номер, адрес или что-то там... IsImage(#Image) Чтобы случайно не убить нашу программу FreeImage - то сначала бы надо проверить, а привязана ли к #Image картинка? А то может это самый первый запуск и там пока еще пусто. ImageWidth(#Image) и ImageHeight(#Image) Помимо картинки нам нужно перепроверить размер картинки, чтобы был 32х32, то есть чтобы размеры не превышались и вставка происходила один в один. Можно конечно плевать на размер и использовать ResizeImage(), и там в принципе с флагом #PB_Image_Raw сделает более менее, без размазывания цветов, но все-таки правильней изначально брать 32х32, чтобы ничего там не растягивалось или не стягивалось. Напоминаю! Это просто визуальная вставка для понимания работы! Код по разбору изображения будет в следующем посте. |
| Навигация |
| Главная страница сообщений |
| Следующая страница |