ACNews
#59
02 ноября 2005 |
|
Увеличение памяти под код в EvoSDK - как расширить область памяти кода до очень больших объёмов.
Увеличение памяти под код в EvoSDK by Hippiman В своей предыдущей статье (Info Guide #11) я упоминал о том, что под код и переменные в EvoSDK доступно примерно 32K, а также о том, что этого объёма вполне хватает для небольших программ, но для более крупных приходится прибегать к различным ухищрениям (например, выносить часть данных в расширенную память). Но этого всё равно недостаточно. Код на C компилируется довольно размашисто, а это значит, что при написании большой игры в любом случае придётся как-то ужиматься. Я разработал способ, как можно расширить область памяти кода до очень больших объёмов. Кратко суть метода: 1. Часть кода, который не вмещается в основную память мы компилируем отдельно; 2. Преобразуем в бинарный файл; 3. Забрасываем на дискету; 4. В нужный момент загружаем этот файл в расширенную память; 5. Оттуда монтируем во 2-й слот (0x8000-Oxbfff); 6. Вызываем этот код из основной программы. По сути этот подход очень похож на динамически подключаемые библиотеки (.dll), но с кое-какими ограничениями. "Плюс" этого метода один, но очень большой: объём кода теперь не ограничен, можно писать очень много. "Минусов" несколько: сложность в использовании и подготовке, небольшое замедление работы всей программы, некоторые ограничения загружаемых модулей. Подготовка подключаемого модуля Первым делом нужно исправить /evosdk/_compile.bat. В этом файле нужно убрать или закомментировать строку rd /s /q %temp%, чтобы после компиляции проекта не удалялась директория с временными файлами, которые нам очень нужны. Следующим этапом будет извлечение адресов функций из файла out.map, который находится в директории _temp_. Для этого я использую вот такой маленький Perl скрипт: #!/usr/bin/perl use strict; my $str; my @arr; open FIL,"_temp_\out.map"; open OUT,">map.h"; while($str=<FIL>) { chomp $str; $str=~s/s*//; if(length($str)>0) { @arr=split(/ /,$str); if(substr($arr[2],0,1) eq "_") { print "#define " .$arr[2]." 0x". $arr[0]."n"; print OUT "#define " .$arr[2]." 0x". $arr[0]."n"; } } } На выходе этого скрипта получаем файл map.h, который нужно будет подключить к будущей библиотеке. В конечном итоге в компилируемой библиотеке должны быть подключены следующие хедеры: #include "..evosdkevo.h" #include "..evosdkstartup.h" #include "..Родительский npoektresources.h" + все необходимые дополнительные хедеры из родительского проекта. Теперь нам нужно научить дочерний проект понимать функции из родительского. Для этого пишем подобные конструкции для каждой функции, которую будем использовать. #define draw_image ((void(*)(u8,u8,u8))_draw_image) #define select_image ((void(*)(u8))_select_image) #define draw_tile_key ((void(*)(u8,u8,u16))_draw_tile_key) #define put_mem ((void(*)(u8,u16,u8))_put_mem) #define get_mem ((u8(*)(u8,u16))_get_mem) и т. д. После чего объявленные функции можно будет использовать в проекте так же, как и обычные. Далее пишем основной код подключаемого модуля. Однако чтобы не высчитывать после каждого изменения адрес входа в функцию main, все остальные функции лучше писать после неё (естественно, заранее объявив). Подготовка основной программы Логика работы основной программы с модулем такова: файл или файлы с дискеты загружаются в память, а потом в нужный момент подключаются во 2-е окно и вызываются по адресу 0x800A. В качестве примера подключения и вызова можно взять вот эту функцию: void manager(u8 page,u8 operation,void *a,u8 b) __naked { u8 c; put_mem(trigger_text_page,exchangedate_begin,operation); put_memw(trigger_text_page,exchangedate_begin+1,(u16)a); put_mem(trigger_text_page,exchangedate_begin+3,b); //------------------------------------ __asm push ix ld ix,#0 add ix,sp ld a,4 (ix) PUSH AF xor #0x7f LD BC, #Oxbff7 ld (_MEMSLOT2),a OUT (C), A POP AF call #0x800a pop ix ret __endasm; } page - номер страницы, в которой лежит бинарник, operation - идентификатор операции (менеджер с другой стороны распределяет входные параметры и вызывает нужную функцию в зависимости от этого идентификатора), A - бестиповый указатель - параметр, B - ещё один параметр. Обратите внимание на строки типа put_mem(trigger_text_page,exchangedate_begin,operation); Подключенная библиотека при вызове начинает вести себя как самостоятельная программа и творит какие-то непотребства со стеком, в результате чего вместо переданных данных функция получает что-то совсем другое. Однако это не влияет на последующую работоспособность всего проекта. Сильно копаться в этих процессах не стал. Если кому-то удастся разобраться, как красиво передавать параметры в дочернюю функцию, буду только рад. В конечном итоге я выбрал обходной путь. Если параметры передать нельзя, но очень нужно, то можно воспользоваться внешним буфером. Роль которого выполняет расширенная память. Подготовка EVO SDK Проблема в том, что если вы соберёте и запустите этот проект, то ничего не заработает, так как большинство функций EvoSDK, которые используют 2-е окно, не "чистят за собой". Не подключают туда-обратно страницу, которая была во 2-м окне до их вызова. Для правки, открываем put_get_mem_atm.h, lib_sprites.asm и lib_tiles.asm. Добавляем в начало и конец нужных фунций: В put_get_mem_atm.h : в начало : push ix ld ix,#0 add ix,sp в конец: pop af LD BC, #Oxbff7 ld (_MEMSLOT2),a out (c),a В lib_sprites.asm и lib_tiles.asm : в начало: ld a,(_memSlot2) push af в конец: pop af ld (_memSlot2),a ld bc,MEM_SLOT2 out (c),a Это творческий процесс, т. к. не все функции переключают страницы памяти и не все из них будут использоваться в вашей программе. Список функций, которые точно нужно править: _DOS_3D13, sprites_start, sprites_stop, draw_tile, draw_tile_key, draw_image, pal_select, swap_screen, все функции из put_get_mem_atm.h. Также замечу, что после внесения правок и пересборки библиотек EvoSDK может перестать работать. Это случится из-за превышения максимального размера библиотеки. В этом случае можно закомментировать какие-либо "ненужные" функции, например _sample_play в lib_sound.asm. Эта функция отвечает за covox, который я не использую. Сборка и компиляция Тут всё тоже не так просто. Для компиляции дочернего проекта нужно использовать SDCC версии 2.9. Более новые версии содержат баг, из-за которого не получается явно задавать указатели на функции, а следовательно библиотека получается "вещью в себе". Мы не сможем вызывать функции родительского проекта. Вызывать sdcc нужно с такими параметрами: sdcc -mz80 --code-loc 0x8000 --data-loc OxBCOO main.c -o %temp%out.ihx Код помещаем именно с адреса 0x8000, так как если поместить его с 0x0000, то все указатели на функции будут иметь ссылки начиная с этого адреса. А это нам не нужно, ведь с 0x0000 адреса у нас будет код основной программы. SDCC генерит скомпилированный код в Motorola hex формат. Для получения бинарника я пользуюсь утилитой hex2bin.exe (http://sourceforge.net/projects/hex2bin/ ). Но и это ещё не всё. Код в полученном файле, как мы и указывали при компиляции, начинается с адреса 0x8000, а остальное место заполнено мусором. Для обрезки бинарника я написал небольшую утилиту на java. Для желающих - вот её код: package cuter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author VSurjenko */ public class Cuter { public static void main(String[] args) { try { String filename,destfile; int addr,a; byte arr[]; byte arr2[]; if(args.length>0) { filename=args[0]; } else { System.out.println("Program needs 3 parameters:" "file name, destination file name and address of cutting."); return; } if(args.length>1) { destfile=args[1]; } else { destfile=filename.concat("_cuted"); } if(args.length>2) { addr= Integer.parseInt(args[2]); } else { addr=0x8000; } //------------------------------------ Path path = Paths.get(filename); Path path2 = Paths.get(destfile); arr=Files.readAllBytes(path); arr2=new byte[arr.length-addr]; for(a=0;a<arr.length-addr;a++) { arr2[a]=arr[a+addr]; } Files.write(path2, arr2); } catch (IOException ex) { Logger.getLogger(Cuter.class.getName()).log(Level.SEVERE, null, ex); System.out.println(ex.getLocalizedMessage()); } catch(Exception ex) { System.out.println(ex.getLocalizedMessage()); } } } Остальные могут либо придумать что-то сами, либо обратиться ко мне, я дам jar файл. Всё, теперь полученный бинарник готов к использованию в основной программе. В итоге полный процесс сборки всего проекта будет выглядеть так: 1. Компилируем основную программу, но не собираем образ; 2. Втягиваем из Out.map указатели на функции; 3. Компилируем дочернюю программу, преобразуем в бинарный файл из hex формата, обрезаем и забрасываем в директорию к основной программе; 4. Собираем весь проект в образ дискеты; 5. Тестируем. Теперь кратко об ограничениях дочернего модуля. 1. Все функции, которые могут менять страницу памяти во 2-м окне, должны находиться в родительском проекте. В противном случае они будут портить сами себя. Это значит, что функции работы с файлами в загружаемый модуль выносить нельзя; 2. Размер одного модуля ограничен 16К - одна страница. Это не должно быть большой проблемой. Всегда можно сделать несколько модулей и подключать нужный в данный момент. 3. Я так и не нашел способа "подружить" загружаемый модуль с глобальными переменными основной программы. Вам, скорее всего, придётся провести какое-то время с дебагером, но результат будет того стоить. Конечно, часто вызываемый код лучше не выносить в отдельный бинарник, но всегда найдутся объёмные функции, которые большую часть времени не используются. К примеру, код мануала от SpaceMerc Liberation занимает 8377 байт, я перенёс функцию вывода текста в отдельный бинарник, и вместе с функцией вызова и разросшийся библиотекой EvoSDK код стал занимать 6890 байт. Почти 1.5K чистой выгоды
Другие статьи номера:
Новости - John Silver переехал в собственную квартиру. Сейчас я копаюсь в старых бумагах, пытаюсь восстановить цепь событий. |
Волшебный Мир 2015 - отчет с фестиваля комиксов "Волшебный Мир 2015". |
Увеличение памяти под код в EvoSDK - как расширить область памяти кода до очень больших объёмов. |
Управление флаговым регистром - Управление флаговым регистром в процессоре Z80. |
Скрипт с 4-битными командами - скриптование в стиле форт-команд. |
Похожие статьи:
В этот день... 21 ноября