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 explanations of 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 path. 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 card `sd_nedo.vhd` is created, and everything of the current `release/` directory is written to it. The image is located in the `us/` directory together with the emulator. How does it work inside: 1. `mkevo.bat` writes the configuration defines 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 need a 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 copies to release/ the obtained `*.com`, `*.ext`, `*.txt`, `*.new` and a subdirectory of the same name with the package directory 7. `make.bat` without the _noneedtrd_ parameter (and we have it) creates and fills the test.trd with the release files 8. `mkevo.bat` renames `nedoos.$c to `sd_boot.$c` (so that you can start it from Evo Reset Service with the 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 SD/HDD 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 Build scripts for other target configurations works in the similar way: * `mkevsd-g.bat` - ZX Evo without SD card on NeoGS * `mkatm3.bat` - ATM3, on floppy disk * `mkatm3hd.bat` - ATM3, on HDD (Nemo IDE, you can fix it in the batch file) * `mkatm3sd.bat` - ATM3, on the SD card * `mkpe26.bat` - Pentagon 2.666LE, on floppy disk * `mkpe26sd.bat` - Pentagon 2.666LE, on SD card * `mkatm2.bat` - ATM2, on floppy disk * `mkatm2hd.bat` - ATM2, on HDD (ATM IDE) They all have the same structure, so everyone can create their build script or fix an existing one. Script `makeall.bat` calls all `mk\*.bat`. It must be run before committing to the repository. Build batch ----------- You can understand from all this (especially from the point about `build.bat`), that it is better to modify an existing program that to write from scratch. The easiest way is to take the `emptyapp` directory (or a directory of another program which you take as an example) and copy it in its entirety, then modify the copy. What the simplest `build.bat` looks like: 1. Setting up paths: ``` if "%settedpath%"=="" call ../_sdk/setpath.bat ``` 2. Compilation (may consist of many commands with calls of necessary utilities for converting resources, etc.): `sjasmplus --nologo --msg=war emptyapp.asm` You can modify it for your program, but if its main module is called `emptyapp.asm` - do not modify :) And the rest, most likely, will not have to be modified. 3. If `build.bat` is called separately (for me it is the F9 button in Notepad++), then copy it to release/ and to the SD card image: ``` 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 ) ``` Option for games with a bunch of files in the subdirectory of the same name: ``` 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 ) ``` Maybe someone can do identical batches everywhere? But so far we live like this. There is another version of the batch file - `runcurl.bat`, which when the build is done, will transfer the assembled `\*.com` over HTTP to an already running emulator, and then launch it remotely. To do this, the emulator must be running NedoOS, and in it the `3ws` web server. Program main module ------------------- Let's say you are writing a game. Its sources are in src/games/somegame/. Its main module is called `main.asm`. Its com-file is called `somegame.com`, and additional files are generated in `src/games/somegame/somegame/` (after building they will be saved in `release/nedogame/somegame/somegame/`). What `main.asm` looks like: ``` DEVICE ZXSPECTRUM128 include "../../_sdk/sys_h.asm" ;there are all system constants and macros STACK=0x4000 ; this is most convenient for games org PROGSTART ;all programs start from this address begin ld sp,STACK ;otherwise sp=0 ;our program does not output via stdio, but invokes the graphical screen: OS_HIDEFROMPARENT ; wake up the parent of the program (usually the navigator) 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 ; turn on graphics mode and get focus (out: e = old gfxmode) ld de,path OS_CHDIR ;change to the subdirectory with files ;...load the files... ; mainloop ;...play... jr mainloop ld hl,0 ;result QUIT ;exit to OS path db "somegame",0 ; ... some procedures, tables, inclusions ... ; end savebin "somegame.com",begin,end-begin LABELSLIST "../../../us/user.l" ;save labels to look in the Unreal Speccy debugger ``` If your program code is compiled into several pages (banks), then use the PAGE directive and several _savebins_ (you can see how this is done in the _br_ project). Debugging --------- So, if you save the example above as `main.asm` and run `build.bat` (do not forget to include `main.asm` there), then you will just get into Unreal Speccy. By pressing the "5" button you will boot into NedoOS, from there start your game from `nedogame`/ (you can automate this using `autoexec.bat`). Press Shift+F1 to get into the debugger. Press Ctrl+L to see the labels (and to see them out - Ctrl+L again). Make sure that the current task is exactly your game: Tab (to the dump window), Ctrl+G, 0080: you should see `somegame.com`. If this is not the case, then return to the disassembler window (Shift+Tab), Ctrl+G, 0057 (end of the interrupt handler), and then press F4 (execute to the cursor) until you see `somegame.com` in the dump. At the bottom right, the currently enabled pages in all four windows of address space are visible. Step the program - F7. Walk with skipping calls - F8. Step up to the exit on the stack - F11. You can rearrange the current execution address - Z. During the execution of the program, you can manually modify the registers (transition to them - Shift+Tab, also mouse). You can also modify the program. If other tasks (especially `term.com`) interfere with the tracing of your program, you can close them: once in this task, change the execution address to 0. View screen - F9. Save a chunk of memory - Alt+W (saved in src/). Load - Alt+R. Exit the debugger - Shift+F1. In the current version of the emulator, this for some reason leads to sticking Shift, so you need to poke Shift again. When exiting the debugger, the Kempston mouse is also disabled, it must be captured by clicking on the emulator window. Loading and saving files from the program ----------------------------------------- If you only need to read a little thing, such as a game save, then you can simply sketch: ``` ld de,filename ;"name.ext",0 OS_OPENHANDLE ; opened the file or a jr nz,error ;could not open push bc ;b=handle of the open file ld de,SAVEDATA ;address where to load ld hl,SAVEDATAsz ;number of bytes OS_READHANDLE ;loaded pop bc ;b=handle of the open file OS_CLOSEHANDLE ;close ``` Saving is similar, instead of OPENHANDLE use CREATEHANDLE, and instead of READHANDLE - WRITEHANDLE. The sample can be viewed in `untangle`. But usually, the data needs to be loaded into pages, and these pages need to be set in the windows! Initially, the OS allocates 4 pages to each program. Page numbers can be obtained via OS_GETMAINPAGES (we get dehl=pages in 0000, 4000, 8000, c000). It is best to recognize them at the very beginning and remember, for example, like this: ``` 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 ;and then there will be loading ``` At the end of the program, write convenient procedures for setting these pages: ``` 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 ``` If your program always sets screens at 4000 and 8000, then it makes sense to combine `setpgmain4000` and `setpgmain8000` into one `setpgsmain40008000` routine. And turn on the current screen through the `setpgsscr40008000` procedure. You don't have to write it from scratch, but borrow them from src/games/sprexamp/mem.asm module. Then we need to load some files into the pages. Let's take a generic procedure for loading one page from the same sprexamp: ``` loadpage ;books a page and loads a file there (file name in hl) ;out: hl=after filename, 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 ``` Call it like this: ``` ld hl,texfilename call loadpage ld (pg0),a call loadpage ld (pg1),a call loadpage ld (pgsfx),a call loadpage ld (pgmusic),a ``` Etc. In `br`, this is even done in a loop, and the page numbers are put into a table. ``` texfilename db "pg0.bin",0 db "pg1.bin",0 db "sfx.bin",0 db "music.bin",0 ``` It is easy to modify the `loadpage` to load from an arbitrary address, which can also be taken from texfilename (for example, before the file name). Graphical output ---------------- There are two ways to draw - on a visible screen or double-buffered (one screen is refreshed, the other is visible). For dynamic games, it is best to use the second option, otherwise, there may be problems with flickering and disappearing of sprites. The main loop for a game running with double-buffering looks something like this (example from `loadscr`): ``` ld a,(timer) ld (oldtimer),a ;main cycle mainloop ; display the background or restore the background under the sprites ; ... I recommend taking it from `sprexamp`! ;sprite output call setpgsscr40008000 ;enable screen pages ld iy,spaceship0 ld e,50 ;e=x = -(sprmaxwid-1)..159 (encoded as x+(sprmaxwid-1)) ld c,50 ;c=y = -(sprmaxhgt-1)..199 (encoded as is) call prspr call setpgsmain40008000 ;set program pages in 4000,8000 as before ;finished drawing ld a,(timer) push af call changescrpg ; from this moment (more precisely, from the interrupt) it can be seen what we have drawn ;logic ;... call it as many times as there were interrupts! mainloop_uvwaittimer0 ld a,(timer) oldtimer=$+1 ld b,0 ld (oldtimer),a sub b ld b,a jr z,mainloop_uvwaittimer0 ; if no interruptions passed, spin around here ;b=how many interrupts has passed mainloop_uvlogic0 push bc call logic ;<----------------- write your program logic here pop bc djnz mainloop_uvlogic0 ; waiting for the physical screen switching! ; we can start a new rendering only if at least one interrupt has passed since ; the moment of changescrpg (possibly inside logic) pop bc ;b=timer of changescrpg waitchangescr0 ld a,(timer) cp b jr z,waitchangescr0 ld a,(curkey) cp key_esc jp nz,mainloop ;exit by Esc (Break) ``` For timer usage, include sprexamp/int.asm module and use `swapimer` (enable/disable custom interrupt handler) before and after your main loop. Please note that music is not played in this handler - there is the `OS_SETMUSIC` system call for this, which guarantees smooth playback, even if your task is superseded by other tasks! Take the `prspr` procedure from sprexamp/prspr.asm. If your sprites are not compiled into pages, but into separate files, then you can define `prsprqwid=0x100` in them, and before the first call to `prspr`, poke the `jp prsprqwid` command to 0x100. In general, you can start writing a game by fixing the `sprexamp` project. There are even two ready-made engines - with a background scrolling along two axes (without a scroll, the background is not reprinted) and with a vertical scroll (the background is always reprinted). Moreover, the first engine already has control and logic in the style of a platform game.