# AMOS Professional Extension Development [[amiga:development_environments:basic:amos_professional|AMOS Professional]] extensions are written in [[m68k:development:m68k_assembly_language|Motorola 68000 Assembly Language]]. ## Original Notes [+Music.s](https://github.com/AOZ-Studio/AMOS-Professional-Official/blob/master/extensions/%2BMusic.s) contains instructions for how to make an extension. I've added my own notes below and clarified some of the existing information. ## Defining Tokens ### Return value and parameter lists Don't forget the terminating `-1`! If you built it and forgot it, trying to start AMOS Pro will do nothing. Replace with you known good copy and reboot, then try again. #### Return value * `I` - none * `0` - long * `2` - string* #### Parameters * `0` - long * `2` - string* ##### String parameters Strings are stored in AMOS with a word for string length (so strings can be up to 65535 characters in length), and then the string itself. I'm not sure if they're null terminated. * https://www.ultimateamiga.co.uk/HostedProjects/AMOSFactory/AMOSProManual/14/1407.html ## Your own routines You can `Rbsr` and other branch related jumps to your own stuff, but you **can't `Rjsr`** to your routines! Only to built-in AMOS routines. Use `Rbsr` and its equivalent! The Compiler will let you know of this mistake **very quickly**. ## Built-in Routines ### `L_Ram*` These call [exec/AllocMem](https://d0.se/autodocs/exec.library/AllocMem) for you through AMOS. * `L_RamChip` == `PUBLIC|CLEAR|CHIP` * `L_RamChip2` == `PUBLIC|CHIP` * `L_RamFast` == `PUBLIC|CLEAR` * `L_RamFast2` == `PUBLIC` ```asm MOVE.L #400,D0 Rjsr L_RamFast BNE RamAllocated ; couldn't get RAM, D0 is 0 RamAllocated: ; D0 contains address of RAM ``` Free it with `L_RamFree`: ```asm MOVE.L D0,A1 MOVE.L #length,D0 Rjsr L_RamFree ``` ### L_Demande and HiChaine When you want to demand string space, you apply for it with `L_Demande`. The requested length goes into D3. It has to be even. You get the base address in both A0 and A1. This is an AMOS string, so the first two bytes are the size of the string as a word. Then the string comes after. Then, you have to eventually hand `HiChaine(A5)` (be sure to preserve A5) the ending address of the string. Make sure you don't try to allocate a zero width string with this! It will fail hard. Writing out a routine in [+CompExt.s](https://github.com/AOZ-Studio/AMOS-Professional-Official/blob/533b4b1f5acd604814513f78e02c7b4796cb0531/compiler/%2BCompExt.s#L294-L319) with comments to understand this: ```asm ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ; =COMP ERR$ ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Lib_Par CompErr ; - - - - - - - - - - - - - Dload a2 ; get the data area moveq #0,d3 ; clean out d3 move.w LErreur-CD(a2),d3 ; this is the length of the error string as a word beq.s .Vide ; if it's zero there's nothing to do Rjsr L_Demande ; get string space, put the address in a0 and a1 move.w d3,d0 ; preserve existing length in d0 move.l a0,d3 ; move the address into d3 move.w d0,(a1)+ ; put the length into the start of the string subq.w #1,d0 ; reduce the length by 1 lea Erreur-CD(a2),a0 ; load the error string address into a0 .Loop1 move.b (a0)+,(a1)+ ; copy a bit from a0 to a1 dbra d0,.Loop1 ; decrement d0. if d0 is greater than 0, go to loop move.w a1,d1; put the current target address into a1 and.w #$0001,d1 ; keep the lowest bit add.w d1,a1 ; add the current address to a1 move.l a1,HiChaine(a5) ; update the high end of string memory moveq #2,d2 rts .Vide move.l ChVide(a5),d3 ; return empty string moveq #2,d2 rts ``` Here's a better version: ```asm ; D4 contains your string length MOVE.L D4,D3 AND.W #$FFFE,D3 ADDQ #2,D3 ; round up to the nearest 2 Rjsr L_Demande ; string is in A0/A1 LEA 2(A0,D3.W),A1 MOVE.L A1,HiChaine(A5) MOVE.L A0,A1 ; put A1 back MOVE.W D4,(A0)+ ; put length of string at start of memory .stringstuff MOVE.B (A2)+,(A0)+ BNE .stringstuff ; copy until null terminator ``` You know your string code is working if you can do this: ```basic A$=My String Function$ ' returns "wow" Print "cat" + A$ + "dog" ' "catwowdog" ``` #### Returning an empty string Send `ChVide(A5)` to `D3`. "vide" in French is "empty" so that makes sense. ## Compiler AMOS Professional will let you be a little sloppy with the differences between `Lib_Def`, `Lib_Par`, `Rbcc`, and `Rjsr`. The compiler will not. Ensure you have your compiler set up properly: * Show default screen * Don't put AMOS to back * Include the AMOS library Save those settings and you can use `apcmp file.amos inclib` in your build script to build something for testing. If your code is crashing, check for the following: * You are **only** using `Rbcc` for routines in your library. * You are ideally **only** `Rbcc` to `Lib_Def` routines, not `Lib_Par` routines. If you do this, the compiler will be happy. I suggest having a test AMOS file you can compile and run that will exercise all the code that `Rbcc`s within your own code and running it often, to keep you safe. ## Tips and Tricks * Memory blocks and `Peek`/`Deek`/`Leek` are one of the few ways you have of debugging things. Don't worry about allocating RAM and not freeing it at this point. You'll likely be rebooting very soon anyway... * Bisecting your changes is a must! You're going to be rebooting your machine a lot. * Don't forget to put the address registers back on the stack! * Ensure you test closing AMOS after you do stuff. Some bad things only happen once AMOS itself is unloaded. * Look for A4-A6 and D6-D7 not being restored, or A3 not being completely unwound for functions. * When adding new tokens, double-check that you are putting the label in the right spot! ```asm ; functions dc.w L_Nul,L_ThisWillReturnAValue ; instructions dc.w L_ThisReturnsGarbage,L_Nul ``` * Keep a known good copy of your extension around! If your startup routine is suddenly failing, you'll need to reboot and restore the known good copy. * This is because the `Library_Digest` executable seems to load AMOS config and will OOM if the extension is broken? Or maybe this just needs a reboot. Or a rewrite in C. * Set up AMOS assigns in your build script. * Did you `Dload A3` into code that takes function parameters? You need to preserve and restore A3, otherwise AMOS will crash. * Checking a memory area to see if something needs to be freed? Be sure to clear that memory to avoid double frees. * Extension titles are one line only. They stop rendering at any lower ASCII character. * Did I mention you're going to be rebooting a lot? Setting up an `ASSIGN` to your code, Leaving your code file Out on the desktop, and whatever else you can do to get back into coding is necessary. * Of course, if you're cross-compiling, this is less of an issue. * You don't seem to be able to `INCLUDE` a file that contains `Lib_Def` code. * If you discovered you had been unwinding stacks incorrectly or anything else that could screw AMOS up, just reboot. * **Don't use A5 for stuff!** Unless you put it back right away. That's where extension memory is located. --- ## How-Tos ### Add a new command to be used in the Editor These use a bunch of macros I built for the BSD Socket extension: * Add a new function definition to the token table: ```asm C_Tk dc.w 1,0 ;... ; if something that accepts parameters and returns a value AddTokenFunction MyNewFunction dc.b "my new functio","n"+$80,"00,0",-1 ; if something that returns nothing and just has side effects AddTokenInstruction MyNewInstruction dc.b "my new instructio"."n"+$80,"I",-1 ``` * Add a new `Lib_Par` section to the bottom of the file ```asm Lib_Par MyNewFunction PreserveStackFunction RestoreStackFunction Ret_Int ``` * For functions with more than one parameter, peel off the parameters from `A3` in reverse order: ```asm ; takes four parameters Lib_Par MyNewFunction PreserveStackFunction MOVE.L D3,D4 ; last parameter MOVE.L (A3)+,D3 ; second-to-last MOVE.L (A3)+,D2 ; second-to-first MOVE.L (A3)+,D1 ; first RestoreStackFunction Ret_Int ``` * If returning an integer, put it in `D3`: ```asm Lib_Par MyNewFunction PreserveStackFunction MOVE.L (A0),D3 ; return value RestoreStackFunction Ret_Int ```