How to write software under NedoOS ================================== NedoOS support software development in assembler, C, Pascal and Nedolang. This article is about assembler development and contains examples and explanation how the system works. In addition to examples, NedoOS provides a large set of functions that do not need to be written from scratch, as well as the environment for automatic code compilation. Initial setup ------------- Initial configuration of development environment for Windows: 1. Download everything from [http://nedoos.ru/svn/](http://nedoos.ru/svn/) to a directory without spaces in the name. 2. Change to the directory and folder `src/`. 3. Execute `mkevo.bat`. It will build the entire system (there are currently 70 packages), an image of the `sd_nedo.vhd` for SD card is created and everything of the current relase root directory `/` is written to it. The image is locates in us/ directory\ for the emulator. How does it work inside: 1. `mkevo.bat` writes the configuration files for the ZX Evo to the `src/_sdk/syssets.asm` file 2. `mkevo.bat` runs `make.bat` with the _noneedtrd_ parameter (as we don’t make need TRD image of a floppy disk) 3. `make.bat` creates the `release/` directory if necessary 4. `make.bat` builds the fatfs4os package (FAT file system) 5. `make.bat` builds the kernel package, builds `nedoos.$c` 6. `make.bat` in a loop builds all packages by calling `build.bat` for each of them, and after building the package is copied to release/ received `*.com`, `*.ext`, `*.txt`, `*.new` and a subdirectory of the same name with the directory package 7. `make.bat` without the _noneedtrd_ parameter (and we have it) creates and completes in test.trd 8. `mkevo.bat` renames `nedoos.$c to `sd_boot.$c` (so that you can start from Evo Reset Service with option "5. SDCard Boot") 9. `mkevo.bat` launches chkimg.bat with the sd parameter (if there was hdd, it would work with the hard disk image hdd_nedo.vhd) 10.chkimg.bat creates an image if necessary (using images.exe) 11.chkimg.bat writes everything in release / to the image (using dmimg.exe) 12.mkevo.bat launches the configured emulator 6. make.bat в цикле собирает все пакеты путём вызова build.bat у каждого из них, а после сборки пакета копирует в release/ полученные *.com, *.ext, *.txt, *.new и поддиректорию, одноимённую с директорией пакета 7. make.bat при отсутствии параметра noneedtrd (а он у нас присутствует) создаёт и заполняет test.trd 8. mkevo.bat переименовывает nedoos.$c в sd_boot.$c (чтобы можно было запускать из Evo Reset Service одной кнопкой 5) 9. mkevo.bat запускает chkimg.bat с параметром sd (если бы был hdd, то работали бы с образом жёсткого диска hdd_nedo.vhd) 10. chkimg.bat при необходимости создает образ (с помощью images.exe) 11. chkimg.bat записывает на образ всё, что есть в release/ (с помощью dmimg.exe) 12. mkevo.bat запускает настроенный эмулятор Аналогично работают сборщики для остальных конфигураций: mkevsd-g.bat - ZX Evo без SD-карты на NeoGS mkatm3.bat - ATM3 на дискете mkatm3hd.bat - ATM3 на HDD (Nemo IDE, можно исправить в батнике) mkatm3sd.bat - ATM3 на SD-карте mkpe26.bat - Pentagon 2.666LE на дискете mkpe26sd.bat - Pentagon 2.666LE на SD-карте mkatm2.bat - ATM2 на дискете mkatm2hd.bat - ATM2 на HDD (ATM IDE) Они все однотипные, так что каждый может создать свой сборщик или исправить существующий. makeall.bat вызывает все mk*.bat. Его надо запускать перед коммитом в репозиторий. Батник сборки Из всего этого (особенно из пункта про build.bat) видно, что программу пользователя лучше писать по образцу имеющихся программ. Проще всего взять директорию emptyapp (или, если ваша программа похожа на какую-то существующую - то её директорию), скопировать её целиком и исправлять. Как выглядит простейший build.bat: 1. Настройка путей: if "%settedpath%"=="" call ../_sdk/setpath.bat 2. Компиляция (тут может быть много строк, с вызовом нужных утилит конверсии ресурсов и т.п.): sjasmplus --nologo --msg=war emptyapp.asm Вот это место вы можете исправить для вашей программы, но если её главный модуль называется emptyapp.asm - не исправляйте :) А остальное исправлять, скорее всего, и так не придётся. 3. Если build.bat вызван отдельно (у меня это кнопка F9 в Notepad++), то копировать в release/ и на образ SD-карты: if "%currentdir%"=="" ( FOR %%j IN (*.com) DO ( copy /Y %%j "../../release/bin/" > nul "../../tools/dmimg.exe" ../../us/sd_nedo.vhd put %%j /bin/%%j ) pause if "%makeall%"=="" ..\..\us\emul.exe ) Вариант для игр с кучей файлов в одноимённой поддиректории: SET releasedir2=../../../release/ if "%currentdir%"=="" ( FOR %%j IN (*.com) DO ( "../../../tools/dmimg.exe" ../../../us/sd_nedo.vhd put %%j /nedogame/%%j move "*.com" "%releasedir2%nedogame" > nul IF EXIST %%~nj xcopy /Y "%%~nj" "%releasedir2%nedogame\%%~nj\" > nul ) cd ../../../src/ call ..\tools\chkimg.bat sd pause if "%makeall%"=="" ..\us\emul.exe ) Может быть, кто-то сможет сделать везде одинаково? Но пока живём так. Есть ещё вариант батника runcurl.bat, который после сборки пытается передать собранный *.com по HTTP на уже запущенный эмулятор, а потом его удалённо запустить. Для этого в эмуляторе должна быть запущена NedoOS, а в ней веб-сервер 3ws. Главный модуль программы Предположим, что вы пишете игру. Её исходники лежат в src/games/somegame/. Её главный модуль называется main.asm. Её com-файл называется somegame.com, а дополнительные файлы генерируются в src/games/somegame/somegame/ (при сборке попадут в release/nedogame/somegame/somegame/). Как выглядит main.asm: DEVICE ZXSPECTRUM128 include "../../_sdk/sys_h.asm" ;там все системные константы и макросы STACK=0x4000 ;так удобнее всего для игр org PROGSTART ;все программы начинаются с этого адреса begin ld sp,STACK ;иначе sp=0 ;наша программа работает не через stdio, а сама ковыряется в экране, поэтому: OS_HIDEFROMPARENT ;разбудить родителя программы (обычно это навигатор) ld e,0+128 ;e=0:EGA, e=2:MC, e=3:6912, e=6:text ;+8=noturbo ;+128=keep screen ;e=-1: disable gfx OS_SETGFX ;включаем графический режим и получаем фокус (out: e=old gfxmode) ld de,path OS_CHDIR ;включаем поддиректорию с файлами ;...грузим файлы... ; mainloop ;...играем... jr mainloop ld hl,0 ;результат QUIT ;выход path db "somegame",0 ;...какие-нибудь процедурки, таблицы, инклюды... ; end savebin "somegame.com",begin,end-begin LABELSLIST "../../../us/user.l" ;сохраняем метки, чтобы смотреть в отладчике Unreal Speccy Если код вашей программы компилируется в несколько страниц, то используйте директиву PAGE и несколько savebin (можно подсмотреть в проекте br, как это делается). Отладка Итак, если вы написали вышеприведённый пример main.asm и запустили build.bat (не забудьте указать там main.asm), то как раз попадёте в Unreal Speccy. По кнопке "5" вы окажетесь в NedoOS, оттуда запустите свою игру из nedogame/ (можно это автоматизировать с помощью autoexec.bat). Нажмите Shift+F1, чтобы попасть в отладчик. Нажмите Ctrl+L, чтобы увидеть метки (а чтобы развидеть - ещё раз Ctrl+L). Убедитесь, что текущая задача - именно ваша игра: Tab (в окно дампа), Ctrl+G, 0080: вы должны увидеть somegame.com. Если это не так, то вернитесь в окно дизассемблера (Shift+Tab), Ctrl+G, 0057 (конец обработчика прерываний), а дальше жмите F4 (выполнение до курсора), пока не увидите somegame.com в дампе. Справа внизу видны текущие включенные страницы во всех четырёх четвертинках. Шагать по программе - F7. Шагать с пропуском вызовов - F8. Шагать до выхода по стеку - F11. Можно переставлять текущий адрес исполнения - Z. В ходе исполнения программы можно вручную исправлять регистры (переход к ним - Shift+Tab, мышка тоже работает). Можно даже исправлять программу. Если другие задачи (особенно term.com) мешают трассировать вашу программу, их можно закрыть: попав в эту задачу, переставьте адрес исполнения на 0. Посмотреть экран - F9. Сохранить кусок памяти - Alt+W (сохраняется в src/). Загрузить - Alt+R. Выход из отладчика - Shift+F1. В текущей версии эмулятора это почему-то приводит к залипанию Shift, поэтому надо ткнуть ещё раз Shift. При выходе из отладчика также отключается Kempston mouse, его надо захватить кликом по окну эмулятора. Загрузка и сохранение файлов из программы Если вам нужно прочитать только какую-нибудь мелочь, типа отгрузки, то можно просто набросать: ld de,filename ;"name.ext",0 OS_OPENHANDLE ;открыли файл or a jr nz,ошибка ;не смогли открыть push bc ;b=хэндл открытого файла ld de,SAVEDATA ;адрес, куда грузить ld hl,SAVEDATAsz ;сколько байт OS_READHANDLE ;загрузили pop bc ;b=хэндл открытого файла OS_CLOSEHANDLE ;закрыли Сохранение - аналогично, только вместо OPENHANDLE пишем CREATEHANDLE, а вместо READHANDLE - WRITEHANDLE. Образец можно посмотреть в untangle. Но обычно данные надо грузить в страницы, а эти страницы надо выделить! Изначально ОС выделяет каждой программе 4 страницы, номера которых можно узнать через OS_GETMAINPAGES (получаем dehl=pages in 0000,4000,8000,c000). Лучше всего узнать их в самом начале и запомнить, например, так: OS_GETMAINPAGES ;dehl=pages in 0000,4000,8000,c000 ld a,e ld (pgmain4000),a ld a,h ld (pgmain8000),a ld a,l ld (pgmainc000),a ;а дальше уже будет загрузка А в конце программы написать удобные процедуры включения этих страниц: setpgmain4000 pgmain4000=$+1 ld a,0 SETPG4000 ret setpgmain8000 pgmain8000=$+1 ld a,0 SETPG8000 ret setpgmainc000 pgmainc000=$+1 ld a,0 SETPGС000 ret Если ваша программа всегда включает экран в 4000 и 8000, то имеет смысл объединить setpgmain4000 и setpgmain8000 в одну процедуру setpgsmain40008000. А текущий экран включать через процедуру setpgsscr40008000. Можно не писать это с нуля, а заимствовать целиком модуль src/games/sprexamp/mem.asm. Итак, нам надо загрузить сколько-то файлов в страницы. Возьмём универсальную процедуру загрузки одной страницы из того же sprexamp: loadpage ;заказывает страничку и грузит туда файл (имя файла в hl) ;out: hl=после имени файла, a=pg push hl OS_NEWPAGE pop hl ld a,e push af ;pg SETPGС000 push hl ex de,hl OS_OPENHANDLE push bc ld de,0xc000 ;addr ld hl,0x4000 ;size OS_READHANDLE pop bc OS_CLOSEHANDLE pop hl ld b,1 xor a cpir ;after 0 pop af ;pg ret И будем её вызывать так: ld hl,texfilename call loadpage ld (pg0),a call loadpage ld (pg1),a call loadpage ld (pgsfx),a call loadpage ld (pgmusic),a и т.п. В br это вообще делается в цикле, а номера страниц кладутся в таблицу. texfilename db "pg0.bin",0 db "pg1.bin",0 db "sfx.bin",0 db "music.bin",0 Легко переделать loadpage на загрузку с произвольного адреса, который тоже можно взять из texfilename (допустим, перед именем файла). Рисование на экране Есть два способа рисования - в видимом экране или с двойном буферизацией (один экран обновляем, другой виден). Для динамичных игр лучше всего использовать второй вариант, иначе возможны проблемы с мерцанием и исчезанием спрайтов. Главный цикл игры, работающей на двух экранах, выглядит примерно так (пример из loadscr): ld a,(timer) ld (oldtimer),a ;главный цикл mainloop ;вывод фона или восстановление фона под спрайтами ;... рекомендую взять из sprexamp! ;вывод спрайтов call setpgsscr40008000 ;включили страницы экрана ld iy,spaceship0 ld e,50 ;e=x = -(sprmaxwid-1)..159 (кодируется как x+(sprmaxwid-1)) ld c,50 ;c=y = -(sprmaxhgt-1)..199 (кодируется как есть) call prspr call setpgsmain40008000 ;включили страницы программы в 4000,8000, как было ;закончили рисовать ld a,(timer) push af call changescrpg ;с этого момента (точнее, с прерывания) можем видеть, что нарисовали ;логика ;... её вызывать столько раз, сколько прошло прерываний! mainloop_uvwaittimer0 ld a,(timer) oldtimer=$+1 ld b,0 ld (oldtimer),a sub b ld b,a jr z,mainloop_uvwaittimer0 ;если ни одного прерывания не прошло, крутимся тут ;b=сколько прошло прерываний mainloop_uvlogic0 push bc call logic ;<----------------- свою логику пишите сюда pop bc djnz mainloop_uvlogic0 ;ждём физического переключения экрана! ;можем начать новую отрисовку, только если с момента changescrpg прошло хотя бы одно прерывание (возможно, внутри logic) pop bc ;b=timer на момент changescrpg waitchangescr0 ld a,(timer) cp b jr z,waitchangescr0 ld a,(curkey) cp key_esc jp nz,mainloop ;выход по esc (break) Чтобы работал timer, подключите модуль sprexamp/int.asm и используйте swapimer (включение/выключение пользовательского обработчика прерываний) перед и после вашего главного цикла. Обратите внимание, музыка не играется в этом обработчике - для этого есть системный вызов OS_SETMUSIC, который гарантирует ровное проигрывание, даже если ваша задача вытеснена другими задачами! Процедуру prspr возьмите из sprexamp/prspr.asm. Если у вас спрайты скомпилированы не в страницы, а в отдельные файлы, то можно в них определить prsprqwid=0x100, а перед первым вызовом prspr записать в 0x100 команду jp prsprqwid. Вообще можно начинать написание игры с исправления проекта sprexamp. Там есть даже два готовых движка - со скроллом фона по двум осям (без скролла фон не перепечатывается) и со скроллом по вертикали (фон перепечатывается всегда). Причём к первому движку уже прикручено управление и логика в стиле платформера. Рекомендую прочитать nedoos.txt, api_base.txt и sys_h.asm, чтобы получить представление о других функциях системы!