Разработка и ромхакинг > Программирование
Программирование редакторов для внесения каких-то изменений в ром.
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 КБ - загружено 272 раз.)
Последующий пост планируется с ютубным видосом с практическими занятиями с нашей шедевральной супер программой. Поэтому КВЕСТ ДЛЯ РОМХАКЕРОВ: необходим адрес для 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 КБ - загружено 263 раз.)
в итоге получилась у нас такая замечательная пол литра... эээ... в смысле палитра:
Похоже? :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, чтобы ничего там не растягивалось или не стягивалось.
Напоминаю! Это просто визуальная вставка для понимания работы! Код по разбору изображения будет в следующем посте.
Навигация
Перейти к полной версии