Разработка и ромхакинг > Программирование
Программирование редакторов для внесения каких-то изменений в ром.
<< < (2/3) > >>
SeregaZ:
Итак в памяти у нас висит картинка, но она 24 битная и к нашим черным делам не подходит. Надо её извратить и посвятить в нашу веру. А значит переходим ко второму этапу извращений, а именно вписывания 24 битного изображения в 15 цветов нужной палитры.

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

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

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


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



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

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

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

Результаты отпостишь тут. Дам квест номер 2.
SeregaZ:
ну я бы понял если там файлы с кодом-текстом, где машина может обработать - нахрена сотни скачиваний GEMS банков с мелодиями из темы с архивами мелодий игр? их же машина неосилит :) это очень специфический продукт - это даже не VGM. так что видимо китайские боты накрутили. просто от нечего делать.
dimidrol:
Ну отсюда проджект я скачивал пару раз, но то не охота, то некогда азы барсика постигать, поэтому и молчком.
SeregaZ:
ты квест выполнил? где код?  >:(
Cyneprepou4uk:
https://www.romhacking.net/forum/index.php?topic=39581.0

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

там тоже самое по сути - сначала нужно знать где что лежит, а потом уже придумывать как это редактировать. эту прогу не качал и осуждаю, но судя по скринам графония там не касаются. хотя и у меня только самое самое начало затронуто с графонием. и то причем спрайтовым. экраны в планах были, но все так и незасел наваять эти примеры для темы. но опять таки - нужно заранее знать где эти экраны лежат - где тайловые сеты, где тайловые карты, где палитры... в идеале то конечно было бы здорово сделать некий универсальный инструмент, где как пазлами выстраиваешь нужные кнопки, назначаешь им действия - типа эта кнопка выдает список врагов, эта кнопка выдает форму с кучей окошек с параметрами монстров, эта кнопка вызывает редактор иконок, эта кнопка редактор экранов... но это несколько не то. суть не в том, чтобы сделать готовый универсальный инструмент - суть научится программировать, чтобы самому такие не сложные редакторы делать, где все легко понятно и доступно, нежели вручную сидеть страдать над хекс редактором три дня. я например пришел в ужос при просмотре видоса, где с помощью хекс редактора изменяли положения гербов в Дюне. не помню чей видос был :) как бы с одной стороны - да, более низкий подход, требующий более обширные знания пользователя... но с другой это же просто пипец какие костыли! в моем случае все мышкой двигается, сохраняется и экспортируется и главное я остался доволен процентов на 70 от того как получилось. хотелось бы конечно чтобы каждый элемент на экране можно было бы двигать отдельно, а не большими блоками... но тут уж как смог :) все лучше, чем в хекс редакторе вручную...
SeregaZ:
Экраны.

Часть первая, теоретическая, всем известная. можно сразу переходить ко второй - практической части.

Если со спрайтами все понятно - мелкие фрагменты от 1х1 тайла до полноприводных 4х4, то с экранами маленько сложнее. Эти самые экраны состоят из трех частей, ладно, четырех:
1. палитра
2. тайловый сет
3. тайловая карта
4. режимы работы

С палитрой и отчасти с тайловым сетом мы встречались еще во времена спрайтов и там как бы все понятно, но что еще за тайловая карта? Это по сути матрица, с инструкциями как на ней расположены тайлы друг за другом. Там каждому тайлу на изображении дополнительно указываются его параметры:
номер тайла из тайлового сета
приоритет
палитра
зеркало по Х
зеркало по У

Эти параметры заключены в двух байтах. то есть для каждого тайла в тайловой карте выделено по 2 байта. Чтобы было понятнее надо эти два байта показать в бинарном виде:


Конечно на данном этапе возникнет вопрос: да нахрена это надо? просто загрузил PNG картинку и пользуйся! С точки зрения современного пользователя, насмотревшегося всяких 4к на ютубах это прям очень даже не понятно, но вот в те времена, когда трава была зеленее - железо только только начинало развиваться и поэтому тогдашние инженеры выкручивались как могли чтобы сделать и подешевле и чтоб возможности были вполне себе конкурентными, чтобы нинденду обниндендить. Вот и пришли они к тому, что приставка будет иметь 4 палитры - то есть 2 бита из тех 16 бит в тайловой карте (00, 01, 10, 11 - 0, 1, 2, 3 номера палитр) и 11 бит под тайловый номер (а 11 бит это %111 1111 1111 или $7FF в хекс или 2047 в десятичных - 2047 возможных тайла)

В принципе такая куча тайлов не так уж и мала и есть куда разгуляться... НО! под видеопамять у нас выделено всего 65 килобайт в приставке. Плюс еще помимо самих тайлов в этой-же памяти должны болтаться две тайловые карты под два слоя картинки и еще должна болтаться таблица спрайтов. Получается полноценно втулить изображение может нехватить памяти - ведь под каждый тайл надо по 32 байта (напомню: в одном байте 2 пикселя и поэтому 8 строк помножить на 8 столбиков = не 64, а 32). Вот с точки зрения спрайтов, где тайлы должны идти друг за другом - все плохо. А вот с точки зрения экранов - маленько получше, так как тут инженеры припасли трюки ушами. Во первых - кто сказал, что нумерация тайлов обязательно должна быть 1 тайл, 2 тайл, 3 тайл, 4 тайл, 5...... ? Можно оптимизировать изображение. Совсем необязательно чтобы у картинки были все тайлы уникальные. Можно взять и сделать сравнение тайлов и если 1 и 2 тайл одинаковые, то значит можно в тайловой карте для второго тайла указать то, что он будет использовать 1 номер тайла и картинка будет точно такая-же как и была, то есть ничего не потеряет. Тоже самое касается каждого тайла относительно всех остальных тайлов на изображении - надо прогнать по всей картинке и выкинуть повторяющиеся тайлы. Иными словами - эти тайловые карты позволяют нам сжимать изображение. В результате тайловый сет может занимать меньше памяти, чем ежели бы он был без этого "сжатия" напрямую. Второй трюк заключается в использовании зеркал, что может сжать тайловый сет еще больше, то есть тайл может быть перевернут на изображении - скажем по вертикали, или даже и по вертикали и по горизонтали.

Вот на флаге моей страны - Казахстана есть повторяющиеся элементы. Если в первом случае все тайлы уникальные это займет 32 тайла, а во втором повторяющиеся будут отсечены - их станет 14. А если еще проверить тайлы на зеркала, а в данном случае по вертикали - то можно еще 2 тайла отсечь и по итогу тайлов станет 12. Вот в этом суть использования тайловых карт.


С палитрой тоже все понятно - каждому тайлу в тайловой карте можно указать какую именно палитру он будет использовать.

А бит приоритета регулирует где этот тайл на экране будет находится по отношению к спрайту. Где-то было видео из Дюны, где автор рассказывал про этот приоритет и демонстрировал, как юнит мог заехать в заросли - то есть элемент ландшафта мог визуально спрятать юнита от глаз игрока (радару и компьютеру конечно пофиг - найдет и покарает в любом случае :)) Или если это игра-бродилка, то таким приоритетом можно было бы рисовать колонны в здании например, которые бы скрывали персонажа во время ходьбы, добавляя сцене глубины.

Что касается режимов работы экрана, то это касается размеров экранов в зависимости PAL и NTSC, а так-же выбранного режима - H40 или H32. Тут позволю себе копирнуть из интернетов:
NTSC:
Режим H40 — 320x224 пикселя (40x28 тайлов). Самый распространённый режим разрешения
Режим H32 — 256x224 пикселей (32x28 тайлов). Менее популярный режим разрешения
PAL:
Режим H40 — 320x240 пикселей (40x30 тайлов). Самый распространённый режим разрешения
Режим H32 — 256x240 пикселей (32x30 тайлов). Менее популярный режим разрешения
SeregaZ:
Часть вторая, практическая.

Несмотря что популярный размер это NTSC H40 это 320x224 пикселя - тайловая карта (обычно) бОльшего размера - 512х224, просто правой части экрана не видно. При условии конечно что это статичный экран, а не арена MK3, где можно ходить блудить туда сюда. И поэтому обычно тайловая карта экрана имеет больший размер в роме, но могут быть частные случаи типа как ZT - там тайловая карта ровно 40 тайлов в ширину. Поэтому там при загрузке нужны дополнительные танцы с бубном, со вписыванием такого 40 тайлов ширины экрана в 64 ширинный экран, с невидимой справа частью... но не суть. Мы пока про обычные, чаще встречаемые случаи. Поэтому размер экрана я сделал не 320, а 512 пикселей, чтобы тайловая карта рисовалась вся как есть. Так-же для первого, так сказать, опыта работы с экранами - тайловая карта не использует зеркала. Код с зеркалами мы добавим позже.

Что нового, что ранее не встречалось:
Битовые операции. Для того, чтобы вычленить из 2 байтового значения нужные нам параметры можно использовать сдвиг и маску. То есть к примеру было у нас значение и нам в нем нужна палитра:
%1010111110101111
Пришлось бы сдвигать значение вправо, а после накладывать маску %11. Как бы действия то все понятны... но не совсем удобно. Вот тут то и появляются эти самые битовые операции:

--- Код: ---;{ битовые операции
Macro SetBit(Var, Bit)   ; Установка бита.
  Var | (Bit)
EndMacro
Macro ClearBit(Var, Bit) ; Обнуление бита.
  Var & (~(Bit))
EndMacro
Macro TestBit(Var, Bit)  ; Проверить бит
  Bool(Var & (Bit))
EndMacro
Macro NumToBit(Num)      ; Позиция бита по его номеру. ; для вызова сет бит и клер бит.
  (1<<(Num))
EndMacro
Macro GetBits(Var, StartPos, EndPos) ; Прочитать от и до
  ((Var>>(StartPos))&(NumToBit((EndPos)-(StartPos)+1)-1))
EndMacro
;}
--- Конец кода ---

Опять таки в чем разница между процедурой и макросом - да черт её знает... главное что работает :) Теперь вместо сдвига и маски будем писать так:

--- Код: ---palnum    = GetBits(tmapvalue, 13, 14)
--- Конец кода ---
То есть берется значение tmapvalue и из него выковыривается значение из двух бит, начиная с 13 по 14 бит - то самое место, где лежит палитра. Точно по такой-же системе получаем номер нужного тайла в тайловом сете:
%1010111110101111

--- Код: ---tnum      = GetBits(tmapvalue, 0, 10)
--- Конец кода ---
То есть значение с 10 по 0 бит.

А зеркала и приоритет будем возвращать путем TestBit
%1010111110101111

--- Код: ---vertical  = TestBit(tmapvalue, NumToBit(12))
--- Конец кода ---
То есть вертикальное зеркало у нас лежит в 12 бите.

Дальше вроде все уже знакомое и встречалось ранее. Просто берем и как пазлы собираем нужные кусочки из прошлых проектов и втуливаем сюда. По итогу вышло примерно так:



SeregaZ:
тут это... затык  :lol:

вопрос по ромхакерской направленности. вспомнил что у меня есть тестовой ром, показывающий картинки, но он требует 40х28 тайловые карты. и соответственно в процессе работы конвертирует 40х28 в 64х28. в моем же случае карты уже 64х28. как их втулить то? уже и комментил функцию и чего только не делал - не хочет :)

там что-то типа:

--- Код: --- lea pic_map1,a0
move.l #$40000003,(a2) ; VDP PORT ADDRESS VRAM $C000 - MAP1
move.w #$0001,d3
bsr.s load_map_40x28


load_map_40x28: ; map 40x28 -> 64x28 conversion
moveq #27,d2
moveq #0,d4
@copy_n
moveq #63,d1 ; 39
@copy_l
move.w (a0)+,d0
add.w d3,d0
move.w d0,(a1)
dbf d1,@copy_l
moveq #0,d1 ; 23
@fill_l:
move.w d3,(a1)
dbf d1,@fill_l
dbf d2,@copy_n
rts

--- Конец кода ---

полагаю что-то вместо load_map_40x28 надо вписать, чтобы оно pic_map1 (64х28 карта) втулило в нужный кусочек памяти - там где тайловая карта должна болтаться.

у меня не выходит :(
Sharpnull:

--- Цитата: SeregaZ от 05 Ноябрь 2024, 20:48:32 ---соответственно в процессе работы конвертирует 40х28 в 64х28. в моем же случае карты уже 64х28. как их втулить то?
--- Конец цитаты ---
Попробуйте так:

--- Код: ---load_map_64x28:
moveq #27,d2
@copy_n
moveq #63,d1
@copy_l
move.w (a0)+,d0
add.w d3,d0
move.w d0,(a1)
dbf d1,@copy_l
dbf d2,@copy_n
rts
--- Конец кода ---
Т. е. я убрал цикл @fill_l, который добавлял тайлы для заполнения (d3, он же базовый для остальных).
UPD: Или короче:

--- Код: ---load_map_64x28:
move.l #64*28-1,d1
@copy_n
move.w (a0)+,d0
add.w d3,d0
move.w d0,(a1)
dbf d1,@copy_n
rts
--- Конец кода ---
UPD2: вместо move.l #64*28-1,d1 можно move.w #64*28-1,d1, потому что в dbf только 16 бит учитываются от регистра.
SeregaZ:
заработало, спасибо :)



Добавлено позже:
Было бы неплохо маленько добавить визуальное разделение на экране. Типа сделать вертикальную зеленую полоску границы экрана на 320 пикселях и добавить сетку тайлов 8х8 пикселей. Полупрозрачных причем.

Еще наверное надо добавить второе большое окно, где отображались бы эти самые зеркала и приоритет. Наверное сделаю тайл чуть ярче чем черный, если приоритет 1. И добавлю значек х или у если были зеркала.

Рисовать будем классической бейсиковской командой Line. Там не сложно сначала стартовые координаты точки откуда линия рисуется, дальше относительные координаты в какую сторону от старта двигаться. А чтобы сделать линию полупрозрачной - надо вместо RGB указывать RGBA, где четвертый параметр как раз таки прозрачность. Так-же режим рисования надо будет переключить на DrawingMode(#PB_2DDrawing_AlphaBlend)

Ну и поскольку отображение этих полосок мы сделаем включаемо-отключаемыми, то значит и перерисовка нам будет нужна несколько раз, а для этого чтобы не повторять один и тот-же код по писят раз в программе - сделаем рисование, то есть обновление гаджета, в процедуре. И будем вызывать эту процедуру когда нам надо - сразу после отключения-включения галок. Получить статус чекбокса можно с помощью GetGadgetState.

Ага... в процессе запиливания стало понятно что зеленый цвет на голубом фоне флага виден не очень. Значит надо сделать переключатель какого цвета будет граница экрана и самих тайлов. Типа зеленый я обычно леплю, но можно и черный и фиолетовый к примеру. И изначальный выбор зеленого цвета надо будет указать на гаджет зеленого путем использования SetGadgetState.

Ну и раз пошла такая пьянка, то хорошо бы сразу сделать кнопку со сборкой рома через ASM68K.exe, и потом запуск эмуля, где можно было бы поглядеть как оно будет выглядеть в итоге. Для чего понадобится RunProgram. Ну и для переменной EmulatorPath$ надо будет указать свой путь до эмулятора, иначе волшебства не произойдет.

Ну и может быть такой вариант, что сборка рома ASM68K.exe зафейлится и тогда мы запиливаем диалог ошибки с возможностью выбора запуска бат файла отдельно. Дело в том, что при запуске ASM68K.exe окошко мелькает и сразу-же закрывается, не давая прочитать любовные письма, которые он там пишет. Как бы PB имеет функции чтения чужого консольного окна, но затык в том, что ASM68K.exe не дает прочитать, скатина. Гранаты там не той системы и соответственно лог работы ассемблера не дается. С большинством же других консольных программ нет никаких проблем.

Что касается самих зеркал, то там используются те-же самые циклы, как при рисовании обычного тайла без зеркал. Просто мы меняем значения от и докуда крутить местами и в самом цикле указываем направление Step -1, то есть от большего к меньшему. Ну и в случае с Х зеркалом x - 1 для второго пикселя, вместо х + 1 как было при обычном тайле без зеркал.




В конечном итоге дизайн окна теперь выглядит так:

Yoti:

--- Цитата: SeregaZ от 05 Ноябрь 2024, 23:04:23 ---Ага... в процессе запиливания стало понятно что зеленый цвет на голубом фоне флага виден не очень. Значит надо сделать переключатель какого цвета будет
--- Конец цитаты ---
Почему просто не инвертировать цвет?
SeregaZ:
ну... проще все-таки обычная линия, нежели еще один цикл, где бы проверялась каждая точка на пути этой линии, инвертировался цвет этой точки и перерисовывался. нужен как можно простой код, чтобы новичку было легче вкатывать в тему. а там уже когда наш подозреваемый втянется в вопрос и откроет тайну вселенной, что программировать на самом деле не сильно сложно - то сам уже сможет и инвертирование и блек джек и легкодоступных женщин там напрограммировать.
SeregaZ:
в общем тяжело что-то там родил... алгоритм вставки такой:
1. 24 битное изображение 320х224 пикселя сначала перекрашиваем в 512 сеговских цветов.
2. измеряем вес пикселей, количество пикселей одинакового цвета на изображении и отбираем 16 с самым большим весом в палитру.
3. перерисовываем 512 цветное изображение в 16 подготовленную палитру.

Чтобы вставить картинку, надо в пейнте вырезать область размером 320х224 и нажать вставку на экране программы. Если результат нормальный - можно нажать сохранить, а потом кнопку тест и увидеть результат уже в эмуляторе.

Итоговый результат конечно такой себе :) В архиве как пример железный человек лежит. При его вставке - весь красный цвет идет по бороде и изображение преимущественно упарывается в синий оттенок. Палетквант из интырнетов сделает в тыщу раз лучше :)

Поскольку я совсем растерял навыки, а код брал из другого места, который до этого мои кривые руки на что-то там правили - вполне мог напортачить :) Надо было оригинальный буржуинский код найти и его копировать, а не мою недо-переделку. Я забыл уже все нюансы... так что местами в коде ашипки. Надо опять по новой вникать. И надо еще подумать над палитрой, чтобы в ней в любом случае присутствовал черный цвет как первый в палитре. Как раз в моей доделке оно и было... но я забыл как это включать :) Сейчас черный цвет может уплыть или не быть вовсе в палитре и поэтому правая часть окна может быть не черной, как изначально с Дюной.

Пока-что в изображении не выкидываются повторяющиеся тайлы, то есть изображение сохраняется целиком. Это я потом добавлю. А вот другие две вещи - было бы неплохо подумать, как сделать.
1. сжатие графики, по типу МК3. декомпрессор-компрессор как ехешку с форума - не предлагать. надо прям по детски объяснение как это работает, чтобы я мог сделать что-то похожее "по мотивам".
2. прямая запись в эмулятор образа рома. именно не как файл, а прям кусман памяти вписать в эмулятор с ромом, дабы не создавать всякие мусорные временные файлы на жестком диске (у меня просто пунктик на эту тему).

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

И да! Надо опять таки в коде исправить путь до своего эмулятора. Там сейчас мой путь и кнопка ТЕСТ работать не будет если не исправить. Можно конечно через ини файл сделать. Типа при первом запуске потребует путь, а после уже будет всегда знать где эмулятор лежит. Так-же путь до рома тоже надо будет подумать как доделать.
SeregaZ:
родил  :cool: . 2 дня убил, но вроде раздуплил. есть и код на PB с сжатием тайлового сета и на ASM разжатия и записи в VDP. ляпота! теперь можно подумать как втуливать образ памяти в эмулятор, чтобы не создавать временный файл рома на жестком диске. бегло глянул - пока не ясно. нашло пяток адресов, где вроде как ром сидит в памяти эмуля. надо будет тестить, вникать.

***

Итак. Теория выкидывания повторяющихся тайлов. Сейчас пишет тайлы прям друг за другом, как оно идет на изображении. Алгоритм будет такой:
1. создаем сложный массив, как ранее мы делали для палитры. там одна ячейка будет состоять из 4 ячеек-образов в памяти по 32 байта.
2. при создании тайлового сета следует изменить немного логику:
2а. самый первый тайл в любом случае пишем в наш сложный массив, в первую из четырех ячеек, как есть.
2б. запускаем процедуру переворота тайлов и пишем готовые результаты в 2-3-4 ячейки этого-же элемента массива. то есть с зеркалами по Х, У, и оба зеркала.
2в. начиная со второго тайла прогонять через сравнение памяти весь этот массив, добавляя если совпадений не было. недобавляя, но отмечая галку зеркал в тайловой карте - если какой-то из тайлов в массиве совпался с текущим.
Получается образ в памяти тайловой карты и тайловый сет надо будет делать одновременно.

Схемы переворотов тайлов были выше (картинку прикладывал). В теории тут все понятно, а вот с точки зрения циферок надо будет мудрить. Так как в 1 байте там по 2 пикселя, то есть каждый байт надо будет переворачивать. Где-то в старых проектах я это уже делал. Как найду где - скопирую оттуда и приложу к проекту. Таким образом у нас будет выкидывание повторяющихся тайлов. Хотя наверное в начале надо было сделать просто повторяющихся, а в следующей итерации кода - повторяющиеся и зеркальные, чтобы было проще в тему вникнуть. Сразу с обоими - как есть и зеркальными - код будет сложнее.

SeregaZ:
Итак, та часть где происходит переворачивание инфы. Сделал через "жесткий" массив, это там где
--- Код: ---Array[x]
--- Конец кода ---
То есть он как бы всегда одинаковый размер имеет.
Такой-же массив, но с круглыми скобками
--- Код: ---Array(х)
--- Конец кода ---
может изменять свой размер с помощью ReDim.
По итогу при запуске кода в окошке дебага будет высвечен текстом "тайл" без зеркал, и ниже с зеркалами для понимания, что должно происходить при зеркалировании.

--- Код: ---; сложная структура массива для тайлов, чтобы организовать выкидывание повторяющихся тайлов
Structure usedtilesstruct
  original.a[32] ; .a[32] - жесткий массив на 32 байта
  xmirr.a[32]
  ymirr.a[32]
  bothmirr.a[32]
EndStructure
Global Dim UsedTiles.usedtilesstruct(1120) ; .usedtilesstruct(0) - изменяемый массив со структурой


Procedure CreateMirrors(arrayitemnum.u)
 
  ; у нас есть оригинальный тайл - UsedTiles(arrayitemnum)\original
  ; нужно просто перевернуть его данные для зеркал
 
  ; самое простое - по вертикали.
  ; просто перенос 1 строчки в 8, 2 в 7, 3 в 6, и так далее
  For y = 0 To 31 Step 4    ; мотаем 8 строк
    For px = 0 To 3         ; по 4 байта в строчке
      UsedTiles(arrayitemnum)\ymirr[px + 28 - y] = UsedTiles(arrayitemnum)\original[px + y]
    Next
  Next
 
  ; по горизонтали.
  ; каждый байт надо переносить и докучи еще переворачивать внутри
  ; $12, $34, $56, $78 в $87, $65, $43, $21
  ; используются сдвиги >> и маска & $F
  For y = 0 To 31 Step 4    ; мотаем 8 строк
    For px = 0 To 3         ; мотаем 4 байта, то есть 8 пикселей или 8 столбиков
      UsedTiles(arrayitemnum)\xmirr[y + px] = UsedTiles(arrayitemnum)\original[y + 3 - px] >> 4
      UsedTiles(arrayitemnum)\xmirr[y + px] + (UsedTiles(arrayitemnum)\original[y + 3 - px] & $F) << 4
    Next
  Next
 
  ; оба зеркала
  ; надо повторить зеркалирование по вертикали,
  ; просто вместо original указать xmirr - уже отзеркаленное по Х в качестве входных данных
  For y = 0 To 31 Step 4    ; мотаем 8 строк
    For px = 0 To 3         ; по 4 байта в строчке
      UsedTiles(arrayitemnum)\bothmirr[px + 28 - y] = UsedTiles(arrayitemnum)\xmirr[px + y]
    Next
  Next 
 
EndProcedure

UsedTiles(0)\original[0] = $1 ;{ заполняем наш "тайл" ерундой от 1 до 32
UsedTiles(0)\original[1] = $2
UsedTiles(0)\original[2] = $3
UsedTiles(0)\original[3] = $4
UsedTiles(0)\original[4] = $5
UsedTiles(0)\original[5] = $6
UsedTiles(0)\original[6] = $7
UsedTiles(0)\original[7] = $8
UsedTiles(0)\original[8] = $9
UsedTiles(0)\original[9] = $10
UsedTiles(0)\original[10] = $11
UsedTiles(0)\original[11] = $12
UsedTiles(0)\original[12] = $13
UsedTiles(0)\original[13] = $14
UsedTiles(0)\original[14] = $15
UsedTiles(0)\original[15] = $16
UsedTiles(0)\original[16] = $17
UsedTiles(0)\original[17] = $18
UsedTiles(0)\original[18] = $19
UsedTiles(0)\original[19] = $20
UsedTiles(0)\original[20] = $21
UsedTiles(0)\original[21] = $22
UsedTiles(0)\original[22] = $23
UsedTiles(0)\original[23] = $24
UsedTiles(0)\original[24] = $25
UsedTiles(0)\original[25] = $26
UsedTiles(0)\original[26] = $27
UsedTiles(0)\original[27] = $28
UsedTiles(0)\original[28] = $29
UsedTiles(0)\original[29] = $30
UsedTiles(0)\original[30] = $31
UsedTiles(0)\original[31] = $32
;}

Debug "original:"
For i = 0 To 31 Step 4
  Debug RSet(Hex(UsedTiles(0)\original[i]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\original[i + 1]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\original[i + 2]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\original[i + 3]), 2, "0")
Next

CreateMirrors(arrayitemnum.u)

Debug ""
Debug "ymirr:"
For i = 0 To 31 Step 4
  Debug RSet(Hex(UsedTiles(0)\ymirr[i]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\ymirr[i + 1]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\ymirr[i + 2]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\ymirr[i + 3]), 2, "0")
Next

Debug ""
Debug "xmirr:"
For i = 0 To 31 Step 4
  Debug RSet(Hex(UsedTiles(0)\xmirr[i]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\xmirr[i + 1]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\xmirr[i + 2]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\xmirr[i + 3]), 2, "0")
Next

Debug ""
Debug "both:"
For i = 0 To 31 Step 4
  Debug RSet(Hex(UsedTiles(0)\bothmirr[i]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\bothmirr[i + 1]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\bothmirr[i + 2]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\bothmirr[i + 3]), 2, "0")
Next


--- Конец кода ---

Основной же код будет чуть позжее... надо допиливать  :blush: пока-что работает не так как надо...

Так-же наверное сразу докручу сжатие. Хотя над сжатием еще бы покумекать. Основную идею то я понял... типа читает по 2 байта. И если вдруг 3 раза подряд это будет одно и то-же значение, то это дело можно поджать.
Было: "12 34 12 34 12 34"
Стало: "80 03 12 34"
Типа первый бит в 80 - означает сжатие, дальше 003 - что три раза повторяются следующие 2 байта.


***

В очередной раз, пока спал - думал. Надо переосмыслить подход. Плясать не от тайлового сета, а от тайловой карты. Читать номер тайла, переворачивать его в нужные зеркала, согласно инфе из карты, искать совпадения в уже внесенных тайлах в том моем сложном массиве и вносить в массив, если не было. А если было - просто менять старый номер на новый и править зеркала, если там были зеркала. Как бы с одной стороны куча ненужной работы в виде временного переворачивания тайла из тайлового сета... с другой стороны это делается средствами винды и современным компьютером, то как бы и ср@ть все равно ведь будет быстро. Внутренний перфекционист конечно сейчас бунтует... В выкидывателе тайлов я делал по другому. Можно конечно взять все оттуда, но хочу переосмыслить.
SeregaZ:
Сжатие, что описывал выше, будет примерно выглядеть так:

--- Код: ---Enumeration
  #File
EndEnumeration

; RLE word compressor (16bit). PureBasic.
; вход: *src указывает на необработанные данные (tile set), size - размер в байтах
; выход: *dst - сжатые данные
; PureBasic RLE-word encoder with big endian output for Sega MD

Procedure WriteBEWord(*dst, pos, value)
  ; write 16bit big endian
  PokeA(*dst + pos, value >> 8)
  PokeA(*dst + pos + 1, value & $FF)
EndProcedure

Procedure ReadBEWord(*src, pos)
  ; Read 16-bit big-endian word from buffer (*src + pos)
  high = PeekA(*src + pos)
  low  = PeekA(*src + pos + 1)
  ProcedureReturn (high * 256) + low
EndProcedure

Procedure RLEWord_Encode(*src, size, *dst)
  i = 0
  o = 0

  ; first 2 bytes = decompressed size in bytes
  WriteBEWord(*dst, o, size) : o + 2

  While i < size
    ; Check for RLE run
    w = ReadBEWord(*src, i)
    count = 1
    j = i + 2
   
    ; Count consecutive identical words
    While j < size And ReadBEWord(*src, j) = w And count < $7FFF
      count + 1
      j + 2
    Wend
   
    If count >= 3  ; RLE encode if 3+ identical words
      WriteBEWord(*dst, o, $8000 | count) : o + 2
      WriteBEWord(*dst, o, w) : o + 2
      i = j
    Else
      ; RAW block - collect until we find 3+ identical words
      count = 1
      j = i + 2
     
      While j < size And count < $7FFF
        ; Check if we have 3 identical words ahead for RLE
        If j + 4 < size
          w1 = ReadBEWord(*src, j)
          w2 = ReadBEWord(*src, j + 2) 
          w3 = ReadBEWord(*src, j + 4)
          If w1 = w2 And w1 = w3
            Break  ; Stop RAW block before RLE run
          EndIf
        EndIf
        count + 1
        j + 2
      Wend
     
      WriteBEWord(*dst, o, count) : o + 2
     
      ; Copy raw words
      For k = 0 To count - 1
        WriteBEWord(*dst, o, ReadBEWord(*src, i + k * 2)) : o + 2
      Next
     
      i + count * 2
    EndIf
  Wend
 
  ; End marker
  ;WriteBEWord(*dst, o, 0) : o + 2
 
  ProcedureReturn o
 
EndProcedure

; Main program
error = 0

If ReadFile(#File, "tset.bin")
  ; получаем размер открытого файла
  tsetsize = Lof(#File)
  If tsetsize
    ; если размер больше 0 байт
   
    ; резервируем память нужного размера
    tsetmemory = AllocateMemory(tsetsize)
    tsetpakmem = AllocateMemory(tsetsize * 2) ; Может быть больше оригинала
    If tsetmemory And tsetpakmem
      ; если память зарезервировалась
     
      ; читаем из открытого файла в память
      ReadData(#File, tsetmemory, tsetsize)
    Else
      error = 1
    EndIf
  Else
    ; добавляем текст ошибки, на случай если файл тайл сета 0 байт - пустой
    MessageRequester("ошибка", "файл тайл сета по всей видимости пуст - 0 байт")
    error = 1
  EndIf
  CloseFile(#File)
Else
  ; добавляем текст ошибки, на случай если файла тайл сета нет
  MessageRequester("ошибка", "нет файла тайл сета")
  error = 1
EndIf

If error = 0
  Debug "Original size: " + Str(tsetsize)
  paksize = RLEWord_Encode(tsetmemory, tsetsize, tsetpakmem)
  Debug "Compressed size: " + Str(paksize)
 
  If CreateFile(#File, "tsetP.bin")
    WriteData(#File, tsetpakmem, paksize)
    CloseFile(#File)
    ;MessageRequester("Успех", "Сжатие завершено!")
  Else
    MessageRequester("Ошибка", "Не могу создать файл tsetP.bin")
  EndIf
 
  FreeMemory(tsetmemory)
  FreeMemory(tsetpakmem)
EndIf
--- Конец кода ---

Но смысла в этом не много... ибо если делать какие-то экраны для ромхаков, то ведь в разных играх по разному сжатие работает. Мне то для мув мейкера пойдет. А вот для ромхаков не очень... Так-же наверное надо сделать галку - нужно сжатие или не нужно. Так-же в самом асм коде надо будет добавить вилку - со сжатием тайловый сет или без и соответственно чтоб собиралось в ром или со сжатием или без, в зависимости от выбора. Еще правда с первыми двумя байтами - указанием размера разжатых данных - тоже надо покумекать... может оно и не нужно вовсе. С другой стороны - дополнительная проверка, а то ли мы разжали? Своеобразная контрольная сумма.
SeregaZ:
Добавил галку дизеринга, так-же раздуплил как нулевым номером в палитре заливать черный, независимо от того есть ли он на изображении или нет. Хотя еще надо потестить. Пока-что результаты с Железным Человеком такие:


https://i126.fastpic.org/big/2025/1113/6f/fc3053626229f26032e81789ef19c86f.png
Мой код из коробки, рожает по весу пикселей - жрет красный цвет накорню. С галкой дизеринга результат чуть лучше (понятно что одинаковых тайлов будет меньше, а изображение займет больше места в роме). Но палент квант, от которого я писаю кипятком, убил :) а контрольным в голову стал 4 палитровый вариант. Правда код импорта с 4 палитрами в сеговские цвета я недопилил, посему последнее изображение "сырое", но результат будет очень близкий. Наверное сделаю галки для импорта - в 1 палитру писать или в 4. Но если в 4, то тогда из палеткванта нужно будет сохранять два изображения - саму подготовленную картинку и палитру к ней.

Просьба не размещать с помощью тэга img изображение со стороной более 700 пикселей. Предупреждение №2. ghostdog3
Навигация
Главная страница сообщений
Следующая страница
Предыдущая страница

Перейти к полной версии