Автор Тема: Программирование редакторов для внесения каких-то изменений в ром.  (Прочитано 2071 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Тема создана с целью наиболее простым языком описать процесс создания своего какого-то инструмента для редактирования чего-либо в игре. Это не ромхакинг. Это предполагается, что ромхакер уже знает что где лежит в роме и в каком формате. И это что-то существующими доступными программами не редактируется, а хотелось бы сделать что-то свое, однокликовое, с блекджеком и легкодоступными женщинами.

Будет для статьи использоваться не совсем удобное и знаменитое, а докучи еще и платное - PureBasic. Ну и, понятно, Windows. Как бы он тоже платный... главное что некоему Билли Г. что автору PB не рассказывать о существовании торрентов.

Как понятно из названия - это 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.

Добавлено позже:
а как тут начать новый пост, чтобы он был новым постом, а не *добавлено позже ?
« Последнее редактирование: 01 Май 2024, 01:40:07 от SeregaZ »

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Работа с файлами.

Для нас цифры это цифры. Типа написал ручкой на бумажке 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 - то что нам и надо.

Оффлайн Yoti

  • Пользователь
  • Сообщений: 4480
  • Пол: Мужской
  • Не тро-гай ме-ня
    • Steam
    • Просмотр профиля
Сначала все темы с упоминанием слова "музыка" загадил, а теперь вот очередную ерунду плодит.

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
дааааа кто понял - тот понял, а кто не понял - тот Филлип Киркоров. ой. то есть тот 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

  • Пользователь
  • Сообщений: 1304
  • Пол: Мужской
  • Sega Mega Drive reversing
    • Просмотр профиля
:( Не очень понимаю, кому это нужно. Материалов что по простым типам данных полно, что по Basic... Даже по написанию GUI...

Оффлайн Yoti

  • Пользователь
  • Сообщений: 4480
  • Пол: Мужской
  • Не тро-гай ме-ня
    • Steam
    • Просмотр профиля
DrMefistO,
и это ещё не учитывая тот факт, что полезность изучения Бэйсика нынче крайне сомнительна. Ну и, насколько я помню по другим темам, он сам так ничего до конца и не дописал. Вечно ремарки "нужно добавить, нужно обновить" и т.д. А уже других учить пытается. :lol:

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
да я тоже не понимаю зачем ты делаешь свой плагин к иде. но ты ведь его делаешь :) значит оно кому-то нужно. в смысле весь этот бред с костылями - ида, питон, плагин, эмулятор... бред собачий :) делай сразу единую ОДНУ прогу, без костылей и ошибок. сразу чтоб в одном флаконе было и эмулятор, и весь разбор, и нормальное сохранение, и сразу компилер, и чтоб все происходило в памяти, без создания временных файлов на диске. (главное конечно чтоб ошибок в коде не рожало. типа как сейчас прозевал разницу .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
« Последнее редактирование: 01 Май 2024, 22:33:28 от SeregaZ »

Оффлайн Mr2

  • Пользователь
  • Сообщений: 1618
  • Пол: Мужской
  • Free roaming sorcerer
    • Facebook
    • Просмотр профиля
делай сразу единую ОДНУ прогу, без костылей и ошибок.
Действительно, а если не можешь, строчи туторы по барсику. :rofl:

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Поцоны, расходимся. Нас наебманули! Оказывается Бейсик-барсик не тащит :( И ничего в нем сделать невозможно... тото я думаю почему я ни один свой проект не закончил - оказывается язык не тот! Кто-ж знал? Где вы, атцы-ногебаторы с++, раньше были? Почему не подсказали мне - неучу?

Так... а что значит не можешь? Усё я могу :) Просто не хочу... На самом деле пробно интересовался у продвинутых чуваков на тему создания эмулятора в виде длл, так как переписать полностью под мой язык я конечно-же не смогу. Вот дллку-эмулятор можно было бы свободно подключать к проектам, как это было с дллкой 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

  • Пользователь
  • Сообщений: 1618
  • Пол: Мужской
  • Free roaming sorcerer
    • Facebook
    • Просмотр профиля
Где вы, атцы-ногебаторы с++, раньше были? Почему не подсказали мне - неучу?
Тебе уважаемые люди объяснили что ты зря портишь бумагу, а ты не только не послушал, а ещё и нагрубил. :cop:
В общем совет такой, пиши конкретный редактор, а не "какой-то". ;)

Оффлайн Werton

  • Пользователь
  • Сообщений: 876
  • Пол: Мужской
    • Youtube
    • Просмотр профиля
и это ещё не учитывая тот факт, что полезность изучения Бэйсика нынче крайне сомнительна.
Тулза эта на самом деле не так плоха, все в одном яп, gui, 2д, 3д библиотеки, может на лету транслировать в Си и компилить С компилятором (не помню какой там в комплекте). Когда надо по быстрому на коленке на ведре с болтами запилить что-то и нет возможности разворачивать полноценные среды разработки, то почему бы и нет, а т.к. это бэйсик то учить там по сути ничего не надо :D

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
ты зачем все карты раскрыл? :) ну сидят они - не читали и осуждают и пусть себе сидят :) а ты взял и открыл им глаза на истину вселенной. блин. я расстроен :)

Оффлайн perfect_genius

  • Пользователь
  • Сообщений: 1200
    • ВКонтакте
    • Steam
    • Просмотр профиля
Пишешь ты интересно, но
предполагается, что ромхакер уже знает что где лежит в роме и в каком формате. И это что-то существующими доступными программами не редактируется, а хотелось бы сделать что-то свое
почему же тогда материал для начинающих? Лучше бы отметить это в заголовке, типа [All] Создание редактора рома на Бэйсике для начинающих.
Так твой материал найдёт твоя целевая аудитория, а не твоя - пройдёт мимо и даже не будет иметь повода отвлекать тебя.

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
А. ромхакер не всегда программист софта. так-же и наоборот. а докучи еще бывают ромхакеры, которые совсем не ромхакеры, но делают вполне себе годные ромхаки :)
Б. по плану происходит плавное вкатывание в процесс программирования от простого к сложному, от объяснения к практике.

изначально я хотел делать тему, как когда-то 500 лет назад для особой хитрой программы для игры Lineage 2 :) я на форуме вешал программные квесты, где участники их пытались решать и от простых задач мы потихоньку двигались к сложным. так было бы интереснее, но зная "активность" форума... отказался от этой затеи. решил постить только готовый код с объяснениями, с нарастающей сложностью задачи.

но увы... набежали тролли, развели флуд и информативные посты начинают тонуть :)

Добавлено позже:
ну и что касается слова Бейсик в заголовке. есть такая предвзятость у людей:
 - А, Бейсик? Так мы его в школе проходили. Дерьмо. Читать эту тему не буду.
неверное отношение :) следует понимать не как: Бейсик это что-то забытое и не интересное. А как вполне себе рабочий и действенный инструмент для решения не супер сложных задач в ромхакинге. поэтому то там выше я и написал - кто понял тот понял, а кто не понял... улыбаемся и машем.

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Как бы с втуливанием 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

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Прежде следующего "урока" думаю было бы не плохо обсудить менеджмент дизайна окна. В нашем случае уже как бы есть два раздельных сегмента функционала программы - верхний, где можно задать какое-то одно значение в роме и нижний, где можно вписать какой-то файл во внутрь рома. В случае если я хочу поменять их местами - скажем сегмент с памятью поместить наверх, а с отдельными значениями - вниз, то придется каждому гаджету менять вручную координаты. Как бы с одной стороны да, там в 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

« Последнее редактирование: 04 Май 2024, 22:39:12 от SeregaZ »

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Поскольку мы создаем супер-мега программу, то немешало бы добавить функционала по менеджменту данных. Типа это будет некий лист со списком адресов и типов данных - переменная это или может быть файл с данными. Типа сначала все данные заносятся в лист, и потом по клику собирается новый ром со всеми изменениями, то есть файл будет сохранятся один раз, а не как сейчас перезапись всего файла при изменении всего одного какого-то параметра или данных. Докучи еще можно добавить галок - если галка отмечена, то изменение в ром попадает, и если галки нет, то соответственно игнорируем эту строку. Так-же, чтобы этот самый лист не вбивать каждый раз вручную, то сделаем сохранение конфиг файла. И соответственно при старте программы - загрузку этого конфига. Так-же сам ром файл - думаю не следует изменять оригинал. Будет создаваться новый ром файл, с припиской, скажем "_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 КБ - загружено 76 раз.)

Последующий пост планируется с ютубным видосом с практическими занятиями с нашей шедевральной супер программой. Поэтому КВЕСТ ДЛЯ РОМХАКЕРОВ: необходим адрес для Zero Tolerance, где лежит стартовое количество патронов для пистолета для персонажа-снайперши. Поэтому если кому не лень - было бы очень замечательно :)
« Последнее редактирование: 07 Май 2024, 03:53:51 от SeregaZ »

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
только в конце заметил, что с цветом количества патронов что-то не то :) ну да и фиг с ним. главное главный замысел показал.


в следующий раз уже будет рисование графики.

Оффлайн Sharpnull

  • Пользователь
  • Сообщений: 5144
    • Просмотр профиля
Я тоже всё хочу написать удобный патчер ромов с поддержкой Game Genie. Посмотрел интерфейс вашей программы на видео, эти задачи я бы решил через ассемблер, в котором есть возможность изменения отдельных байтов и вставка бинарных файлов. Для меня эти кнопочки и поля ввода неудобные, я бы сделал одно текстовое окно, в котором, например:
F9A: 30 ; Один байт изменяем
FFFF: 01 02 03 ; Три байта
9ABCD: C:\z\1.bin ; Вставка файла
Чтобы не вставлять в файл, добавить комментарий (;). Это удобно хранить в текстовом файле и позже делать импорт/экспорт через copy-paste. Тогда левая половина программы у вас была бы одним текстовым полем с несколькими строками, а в правой список после парсинга. Ещё добавить перетаскивание файлов и другое. Примерно это я и хочу сделать.

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Итак... начинаем медленное вкатывание в графоний. Данный пост будет состоять из двух частей. Первая - возможности 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 КБ - загружено 77 раз.)

в итоге получилась у нас такая замечательная пол литра... эээ... в смысле палитра:




Похоже?  :cool:

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Итак... немного был занят захватом вселенной и пока злодейские планы не требуют моего непосредственного участия - продолжим.

У нас есть чтение палитры. Это конечно замечательно, но чего-то хотелось бы еще. Приступим к рисованию графики с использованием этой самой палитры. Ранее у нас был патчер, с помощью которого мы что-то куда-то там вставляли. И нам было неизвестно точно что там было. Спойлер - это была иконка биосканнера из 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 байта - размер тайла.

В итоге получилось что-то типа такого:

« Последнее редактирование: 17 Октябрь 2024, 15:16:58 от SeregaZ »

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Лето и лень - убойные факторы для забрасывания темы.

Однако через пень колоду хотелось бы перейти к процессу вставки изображения в нашу программу. На первом этапе это будет просто работа с буфером обмена и вставка в гаджет на теле программы. То есть предполагается что юзверь заранее взял в пейнте нарисовал картинку 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, чтобы ничего там не растягивалось или не стягивалось.



Напоминаю! Это просто визуальная вставка для понимания работы! Код по разбору изображения будет в следующем посте.

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Итак в памяти у нас висит картинка, но она 24 битная и к нашим черным делам не подходит. Надо её извратить и посвятить в нашу веру. А значит переходим ко второму этапу извращений, а именно вписывания 24 битного изображения в 15 цветов нужной палитры.

Честно говоря я без понятия как это делается :) Мне дали кусочек кода погонять (привет моему буржуйскому товарищу Wilbert'у). Я без понятия что за магия там происходит - главное что происходит (причем там код в asm, но понятное дело что не наш 68к асм и тем не менее!). Суть в чем - указываем палитру, а после гоняем изображение попиксельно и алгоритм сам подбирает наиболее подходящий цвет. То есть берет 24 битный пиксель и подбирает какой-то из 15 цветов в нашей палитре, который ему соответствует наиболее близко. Сам этот код функции в проекте, к сожалению, будет без комментариев. Так как я не очень понимаю чего они там делают и уж тем более в асме - двойная тайна :)

Этот код объявлен как модуль (опять-таки хрен его знает в чем принципиальная разница между функцией и модулем - по мне те-же яйца, только в профиль) и соответственно вызов там несколько отличается. Что-то там два двоеточия нужны вроде как... ну не суть.

Новые команды, которые будут использованы:
Point(x, y)
Дает нам цвет в заданной точке. То есть если рисовать надо точкой Plot, то вот читать номер цвета надо Point.


Так... в общем пришлось все-таки сделать  счет цветов с 0, а не с 1 как я хотел. А то это этот самый модуль под подбору цветов брыкается. Так-что изменения в коде будут во многих местах по отношению к предыдущей версии. По итогу взял в гуглах иконку Тимоти Шалопайме и втулил в нашу супер мега программу.




Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Получается у нас в программе висит подготовленное изображение с сеговскими цветами. Осталось дело за малым - сохранить его в совместимом формате, который приставка поймет. Схема будет точно такая-же как при чтении сеговского файла иконки ранее - процедура, но только чтения цветов, а не рисования и её мы будем вызывать передвигая координаты по изображению сверху вниз и как доходит до низа, поднимаем вверх и сдвигаем вправо...
1 5 9  13
2 6 10 14
3 7 11 15
4 8 12 16

Так-же держим в уме, что в одном байте сидят два пикселя, а не один. То есть нам понадобится наш любимый сдвиг << на 4 байта влево. Так-же нам понадобится Point - чтение RGB номера цвета из изображения. Потом надо будет прогнать этот цвет по сеговской палитре в 16 цветов, чтобы узнать какой номер у этого цвета. То есть сохраняется в файле номер цвета, а не сам код цвета.

Вот поэтому, что два пикселя в одном байте и сидит это самое ограничение на 16 цветов в одной палитре. Ведь что такое байт - это 8 бит:
255 десятичное = $FF хекс = %11111111 бинарное
вот смотрим на этот самый хекс $FF - F и F, или бинарное %1111 и 1111, то есть это 15. если 0 тоже считать за человека - то и выходит как раз 16 значений - 16 цветов - точнее 16 номеров цветов - еще точнее 16 номеров в массиве, где лежат коды цветов палитры.

типа у нас была палитра с номерами цветов
1. синий, 2 красный, 3 серобуромалиновый...
после мы получаем цвет пикселя из изображения. и скажем он красный - то есть получается красный это номер 2 в палитре. сразу же смотрим соседний пиксель - он серобуромалиновый - то есть 3.

тогда получается байт в итоговом файле будет выглядеть как
$23
а получился он путем сдвига и сложения
2 << 4 + 3
или даже чтоб понятнее:
$2 << 4 = $20 (двойку сдвинули на 4 бита влево)
$20 + 3 = $23 (и прибавили 3)

Для эксперимента я вставлял ту-же самую иконку биосканнера и сохранял. После открыл в хекс редакторе оба файла и оказалось... что они совпадают! Надо-же! Так неожиданно! (ладно, отставить сарказм. на самом деле может быть так, что файлы с одним и тем-же изображением по итогу несовпадут. все дело в одинаковых цветах. если скажем в палитре нулевой цвет, который прозрачный был черным, и скажем номер 8 цвет тоже был черным, то при таком подборе цветов система, увидя черный цвет - сохранит его под 0 номером. все черные цвета будут 0 и тогда файлы не будут совпадать, ведь в оригинале там должны быть 8)

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
Домашнее задание про проекту:

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

В принципе в файле хелпа PB есть подобный пример, но там комментарии буржуинские и будет не совсем понятно. Нас интересует именно что не открытие файла и чтение с жесткого диска, а именно чтоб в память образ запилился.

Результаты отпостишь тут. Дам квест номер 2.

Оффлайн perfect_genius

  • Пользователь
  • Сообщений: 1200
    • ВКонтакте
    • Steam
    • Просмотр профиля
Интересно, кто у тебя (у меня тоже) молча скачивают файлы десятки раз и ничего не пишут тут?
Думаю, кроме всяких ботов ещё и нейросети - они шерстят весь интернет и им сейчас тупо не хватает уже новых данных :lol:
Так что мы с тобой обессмертились не только в кэше Гугла, но и в нейронах нейросетей.

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
ну я бы понял если там файлы с кодом-текстом, где машина может обработать - нахрена сотни скачиваний GEMS банков с мелодиями из темы с архивами мелодий игр? их же машина неосилит :) это очень специфический продукт - это даже не VGM. так что видимо китайские боты накрутили. просто от нечего делать.

Оффлайн dimidrol

  • Пользователь
  • Сообщений: 193
  • Пол: Мужской
  • Мод-хак Dune II Revenge/Requite
    • ВКонтакте
    • Просмотр профиля
Ну отсюда проджект я скачивал пару раз, но то не охота, то некогда азы барсика постигать, поэтому и молчком.

Оффлайн SeregaZ

  • Пользователь
  • Сообщений: 2538
  • Пол: Мужской
  • ливнул с форума
    • Youtube
    • Просмотр профиля
ты квест выполнил? где код?  >:(

Оффлайн Cyneprepou4uk

  • Пользователь
  • Сообщений: 206
  • Пол: Мужской
  • Самый лысый ромхакер
    • ВКонтакте
    • Просмотр профиля
https://www.romhacking.net/forum/index.php?topic=39581.0

Чел написал универсальную прогу для редактирования данных в играх.