├── .gitattributes ├── .github └── workflows │ └── testing.yml ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── .gitignore ├── correct.asm ├── pitfalls.asm └── tests.asm └── structs.inc /.gitattributes: -------------------------------------------------------------------------------- 1 | *.inc linguist-language=Assembly 2 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Regression testing 2 | on: 3 | push: 4 | pull_request: 5 | schedule: 6 | - cron: '37 13 20 * *' 7 | 8 | jobs: 9 | testing: 10 | strategy: 11 | matrix: 12 | rgbds: ['', '--HEAD'] 13 | runs-on: macos-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Install RGBDS 17 | run: | 18 | brew install ${{ matrix.rgbds }} rgbds 19 | - name: Check that examples assemble correctly 20 | run: | 21 | cd examples 22 | for f in *.asm; do 23 | if [[ $f = pitfalls.asm ]]; then continue; fi 24 | echo "Assembling: $f" 25 | rgbasm "$f" -o "${f%.asm}.o" 26 | echo "Linking: $f" 27 | rgblink "${f%.asm}.o" 28 | done 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gb 2 | *.gbc 3 | *.sym 4 | *.map 5 | 6 | *.sav 7 | *.srm 8 | 9 | *.sn* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2024 Eldred Habert and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RGBDS structs 2 | 3 | A [RGBDS](https://rgbds.gbdev.io) macro pack that provides `struct`-like functionality. 4 | 5 | ## Download 6 | 7 | Please select a version from [the releases](https://github.com/ISSOtm/rgbds-structs/releases), and download either of the "source code" links. 8 | (If you do not know what a `.tar.gz` file is, download the `.zip` one.) 9 | 10 | The [latest rgbds-structs version](https://github.com/ISSOtm/rgbds-structs/releases/latest) is **4.0.2**. 11 | It will only work with RGBDS 0.6.0 and newer. 12 | A previous version, [1.3.0](https://github.com/ISSOtm/rgbds-structs/releases/tag/v1.3.0), is confirmed to work with RGBDS 0.3.7, but should also work with versions 0.3.3 and newer. 13 | If you find a compatibility issue, [please file it here](https://github.com/ISSOtm/rgbds-structs/issues/new). 14 | 15 | ## Installing 16 | 17 | This doesn't actually require any installing, only to `INCLUDE` the file `structs.inc` in your project. 18 | This project is licensed under [the MIT license](https://github.com/ISSOtm/rgbds-structs/blob/master/LICENSE), which allows including a copy of `structs.inc` in your project. 19 | I only ask that you credit me, please :) 20 | 21 | Examples can be found in [the `examples` folder](https://github.com/ISSOtm/rgbds-structs/tree/master/examples); both of what to do, and what not to do with explanations of the error messages you should get. 22 | 23 | ## Usage 24 | 25 | Please do not rely on any macro or symbol not documented below, as they are not considered part of the API, and may unexpectedly break between releases. 26 | RGBDS does not allow any scoping, so macros are "leaky"; apologies if you get name clashes. 27 | 28 | ### Ensuring version compatibility 29 | 30 | rgbds-structs follows [semantic versioning](https://semver.org), so you can know if there are breaking changes between releases. 31 | You can also easily enforce this in your code, using the `rgbds_structs_version` macro: simply call it with the version of rgbds-structs you require (`rgbds_structs_version 2.0.0`), and it will error out if a (potentially) incompatible version is used. 32 | 33 | ### Declaring a struct 34 | 35 | 1. Begin the declaration with `struct StructName`. 36 | This need not be in a `SECTION`, and it is rather recommended to declare structs in header files, as with C. 37 | 2. Then, declare each member, using the macros `bytes`, `words`, and `longs`. 38 | The declaration style is inspired by [RGBASM's `_RS` command family](https://rgbds.gbdev.io/docs/v0.5.2/rgbasm.5/#Offset_constants): the macro name gives the unit type, the first argument specifies how many units the member uses, and the second argument gives the member name. 39 | 3. Finally, you must close the declaration with `end_struct`. 40 | This is required to properly define all of the struct's constants, and to be able to declare another struct (which will otherwise fail with a descriptive error message). 41 | Please note that forgetting to add an `end_struct` does not always yield any error messages, so please be careful. 42 | 43 | Please do not use anything other than the prescribed macros between `struct` and `end_struct` (especially the `_RS` family of directives), as this may break the macros' operation. 44 | 45 | Example of correct usage: 46 | 47 | ```asm 48 | ; RGBASM requires whitespace before the macro name 49 | struct NPC 50 | words 1, YPos ; 2 bytes 51 | words 1, XPos ; 2 bytes 52 | bytes 1, YBox ; 1 byte 53 | bytes 1, XBox ; 1 byte 54 | bytes 6, Name ; 6 bytes 55 | longs 4, MovementData ; 16 bytes 56 | end_struct 57 | ``` 58 | 59 | Note that no padding is inserted between members by rgbds-structs itself; insert "throwaway" members for that. 60 | 61 | ```asm 62 | struct Example 63 | bytes 3, First 64 | bytes 1, Padding ; like this 65 | words 2, Second 66 | end_struct 67 | ``` 68 | 69 | (Some like to insert an extra level of indentation for member definitions. This is not required, but may help with readability.) 70 | 71 | Note also that whitespace is **not** allowed in a struct or member's name. 72 | 73 | Sometimes it's useful to give multiple names to the same area of memory, which can be accomplished with `alias`. 74 | 75 | ```asm 76 | struct Actor 77 | words 1, YPos 78 | words 1, XPos 79 | ; Since `alias` is used, the following field will have 2 names 80 | alias Money ; If this actor is the player, store how much money they have. 81 | words 1, Target ; If this actor is an enemy, store their target actor. 82 | end_struct 83 | ``` 84 | 85 | Passing a size of 0 to any of `bytes`, `words`, or `longs` works the same. 86 | 87 | `extends` can be used to nest a structure within another. 88 | 89 | ```asm 90 | struct Item 91 | words 1, Name 92 | words 1, Graphics 93 | bytes 1, Type 94 | end_struct 95 | 96 | struct HealingItem 97 | extends Item 98 | bytes Strength 99 | end_struct 100 | ``` 101 | 102 | This effectively copies the members of the source struct, meaning that you can now use `HealingItem_Name` as well as `HealingItem_Strength`. 103 | 104 | If a second argument is provided, the copied members will be prefixed with this string. 105 | 106 | ```asm 107 | struct SaveFile 108 | longs 1, Checksum 109 | extends NPC, Player 110 | end_struct 111 | ``` 112 | 113 | This creates constants like `SaveFile_Player_Name`. 114 | 115 | `extends` can be used as many times as you want, anywhere within the struct. 116 | 117 | #### Defined constants 118 | 119 | A struct's definition has one constant defined per member, which indicates its offset (in bytes) from the beginning of the struct. 120 | For the `NPC` example above, `NPC_YPos` would be 0, `NPC_XPos` would be 2, `NPC_YBox` would be 4, and so on. 121 | 122 | Two additional constants are defined: `sizeof_NPC` contains the struct's total size (here, 16), and `NPC_nb_fields` contains the number of members (here, 6). 123 | 124 | Be careful that `dstruct` relies on all of these constants and a couple more; `dstruct` may break in unexpected ways if tampering with them, which includes `PURGE`. 125 | 126 | None of these constants are exported by default, but you can [`export` them manually](https://rgbds.gbdev.io/docs/v0.5.2/rgbasm.5/#Exporting_and_importing_symbols), or `INCLUDE` the struct definition(s) everywhere the constants are to be used. 127 | Since `dstruct` and family require the constants to be defined at assembling time, those macros require the former solution. 128 | However, the latter solution may decrease build times if you have a lot of source files. 129 | 130 | If you want the constants to be exported by default, define symbol `STRUCTS_EXPORT_CONSTANTS` before calling `struct`. 131 | 132 | ### Using a struct 133 | 134 | **The following functionality requires the struct to have been defined earlier in the same RGBASM invocation (aka "translation unit").** 135 | 136 | To allocate a struct in memory, use the `dstruct StructName, VarName` macro. For example: 137 | 138 | ```asm 139 | ; Again, remember to put whitespace before the macro name 140 | dstruct NPC, Player 141 | ``` 142 | 143 | This will define the following labels: `Player` (pointing to the struct's first byte), `Player_YPos`, `Player_XPos`, `Player_YBox`, etc. (all pointing to the struct's corresponding attribute). 144 | These are all declared as **exported** labels, and will thus be available at link time. 145 | (You can `PURGE` them if you do not want this.) 146 | 147 | You can customize the label naming by defining the string equate `STRUCT_SEPARATOR`; it will replace the underscore in the above. 148 | Of particular interest is `DEF STRUCT_SEPARATOR equs "."`, which causes members to be defined as local labels, but prevents the "root" label from itself being a local label. 149 | (This is because `Player.YPos` is a valid RGBDS symbol name, but `.player.YPos` is not.) 150 | `STRUCT_SEPARATOR` can be changed and even `PURGE`d between invocations of `dstruct` and family. 151 | 152 | It is unnecessary to put a label right before `dstruct`, since a label is declared at the struct's root. 153 | 154 | Unless the struct's name contains a dot `.`, two extra constants are declared, that mirror the struct's: `sizeof_Player` would be equal to `sizeof_NPC`, and `Player_nb_fields` would equal `NPC_nb_fields` (sse below). 155 | These constants will keep their values even if the originals, such as `sizeof_NPC`, are `PURGE`'d. 156 | Like structs' constants, these are not exported unless `STRUCTS_EXPORT_CONSTANTS` is defined. 157 | 158 | #### Defining data from a struct 159 | 160 | The use of `dstruct` described above makes it act like [`ds`](https://rgbds.gbdev.io/docs/v0.5.2/rgbasm.5/#Statically_allocating_space_in_RAM) (meaning, it can be used in RAM, and will be filled with padding bytes if used in ROM). 161 | However, it is possible to use `dstruct` to define data without having to resort to piles of `db`s and `dw`s. 162 | 163 | ```asm 164 | dstruct NPC, Toad, 42, 69, 3, 2, "TOAD", $DEAD\, $BEEF 165 | ``` 166 | 167 | The syntax is the same, but add one argument ("initializer") per struct member. 168 | `bytes` will be provided to `db`, `words` to `dw`, and `longs` to `dl`. 169 | (Of course, this can only be used in ROM `SECTION`s.) 170 | If you have more than one "unit" per member, you will likely want a list as a single initializer; to achieve this, you can escape commas like shown above. 171 | 172 | If an initializer provides less units than specified (such as `"TOAD"` above being 4 bytes instead of 6), trailing padding bytes will be inserted. 173 | 174 | Having to remember the order of arguments is tedious and nondescript, though, so rgbds-structs took a hint from C99/C++20 and supports "designated initializers": 175 | 176 | ```asm 177 | dstruct NPC, Toad, .YPos=42, .XPos=69, .YBox=3, .XBox=2, .Name="TOAD", .MovementData=$DEAD\, $BEEF 178 | ; or, equivalent: 179 | dstruct NPC, Toad, .Name="TOAD", .YPos=42, .XPos=69, .MovementData=$DEAD\, $BEEF, .YBox=3, .XBox=2 180 | ``` 181 | 182 | When using designated initializers, their order does not matter, but they must all be defined once and exactly once. 183 | 184 | #### Defining an array of structs 185 | 186 | It's possible to copy-paste a few calls to `dstruct` to create an array, but `dstructs` automates the task. 187 | Its first argument is the number of structs to define, and the next two are passed as-is to `dstruct`, except that a decimal index is appended to the struct name. 188 | `dstructs` does not support data arguments; make manual calls to `dstruct` for that—you would have to pass all the data arguments individually anyway. 189 | 190 | ## Credits 191 | 192 | Written by [ISSOtm](https://github.com/ISSOtm) and [contributors](https://github.com/ISSOtm/rgbds-structs/graphs/contributors). 193 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | -------------------------------------------------------------------------------- /examples/correct.asm: -------------------------------------------------------------------------------- 1 | INCLUDE "../structs.inc" 2 | 3 | ; Check for the expected RGBDS-structs version 4 | rgbds_structs_version 4.0.2 5 | 6 | 7 | ; Struct declarations (ideally in a separate file, but grouped here for simplicity) 8 | ; Note that everything is happening outside of a `SECTION` 9 | 10 | 11 | ; Defines a sprite as it is in OAM 12 | struct Sprite 13 | bytes 1, YPos ; Indenting is optional, but recommended 14 | bytes 1, XPos 15 | bytes 1, Tile 16 | bytes 1, Attr 17 | end_struct 18 | 19 | ; Defines an NPC, as I used in Aevilia (https://github.com/ISSOtm/Aevilia-GB/blob/master/macros/memory.asm#L10-L25) 20 | struct NPC 21 | words 1, YPos 22 | words 1, XPos 23 | bytes 1, YBox 24 | bytes 1, XBox 25 | bytes 1, InteractID 26 | 27 | ; Empty lines are fine as well 28 | 29 | bytes 1, Sprite 30 | bytes 1, Palettes 31 | 32 | bytes 1, Steps 33 | bytes 1, MovtFlags 34 | bytes 1, Speed 35 | bytes 1, YDispl 36 | bytes 1, Unused 37 | bytes 1, XDispl 38 | end_struct 39 | 40 | ; Defines a 3-byte CGB palette 41 | struct RawPalette 42 | bytes 3, Color0 43 | bytes 3, Color1 44 | bytes 3, Color2 45 | bytes 3, Color3 46 | end_struct 47 | 48 | 49 | SECTION "Code", ROM0 50 | 51 | Routine:: 52 | 53 | ; Using struct offsets 54 | ld de, wPlayer 55 | ld hl, NPC_InteractID 56 | add hl, de 57 | xor a 58 | ld [hl], a 59 | 60 | ld de, NPC_Steps 61 | add hl, de 62 | ld [hli], a 63 | ld [hl], a 64 | 65 | 66 | ; Using variable members 67 | ld hl, wPlayer_YPos 68 | ld c, wPlayer_InteractID - wPlayer_YPos 69 | ; xor a 70 | .clearPosition 71 | ld [hli], a 72 | dec c 73 | jr nz, .clearPosition 74 | 75 | 76 | ; Using sizeof 77 | ld hl, wBGPalette0 78 | ld de, DefaultPalette 79 | ld c, sizeof_RawPalette ; Using the struct's size 80 | call memcpy_small 81 | 82 | ld hl, wOBJPalette0 83 | ld de, DefaultPalette 84 | ld c, sizeof_wOBJPalette0 ; Using the variable's size 85 | call memcpy_small 86 | 87 | ; ... 88 | 89 | ; Ordered instantiation of a struct passes each field in order 90 | ; Multi-byte fields repeat the byte to fill their size 91 | dstruct RawPalette, DefaultPalette, $00, $0A, $15, $1F 92 | 93 | ; Named instantiation can be out of order 94 | dstruct RawPalette, CustomPalette, \ 95 | .Color1=$1E\,$0A\,$06, \ ; Multi-byte fields can take a 96 | .Color2=$1F\,$13\,$16, \ ; sequence of bytes to repeat 97 | .Color3=$1F, .Color0=$00 98 | 99 | 100 | memcpy_small: 101 | ld a, [de] 102 | ld [hli], a 103 | inc de 104 | dec c 105 | jr nz, memcpy_small 106 | ret 107 | 108 | 109 | SECTION "Structs", WRAM0 ; But it can be HRAM, WRAMX, or SRAM, too! 110 | 111 | dstruct RawPalette, wBGPalette0 112 | dstruct RawPalette, wOBJPalette0 113 | 114 | DEF STRUCT_SEPARATOR equs "." 115 | dstruct NPC, wPlayer ; Defines `wPlayer.YPos`, etc. 116 | 117 | dstructs 16, NPC, wActors ; Defines `wNPC0`, `wNPC1`, and so on up to `wNPC15`; also all associated members 118 | -------------------------------------------------------------------------------- /examples/pitfalls.asm: -------------------------------------------------------------------------------- 1 | 2 | ; Common pitfalls when using rgbds-structs 3 | ; Resulting error messages included below the code snippet 4 | ; The same causes may cause error messages not included there, especially forgetting `end_struct`. 5 | 6 | 7 | 8 | ; Not including `structs.inc` (it happens!) 9 | INCLUDE "../structs.inc" 10 | ; "Error: Unable to open included file '../structs.inc'" 11 | ; "Macro 'struct' not defined" 12 | 13 | ; Including `structs.inc` twice 14 | INCLUDE "../structs.inc" 15 | ; A slew of "'xxx' already defined in structs.inc(yyy)" messages 16 | 17 | 18 | ; No spacing before macro names 19 | struct Trimmed 20 | ; "Macro 'Universe' not defined" 21 | 22 | 23 | ; Not closing a struct declaration with `end_struct` 24 | struct Infinite 25 | bytes 1, Forever 26 | ; "Please close struct definitions using `end_struct`", when declaring another macro afterwards 27 | ; "'sizeof_Infinite' not defined", when trying to use sizeof_XXX or XXX_nb_fields 28 | 29 | 30 | ; Using illegal chars in struct names 31 | struct $Money$ 32 | longs 1, MONEYYYY 33 | end_struct 34 | ; "syntax error" (VERY DESCRIPTIVE), will be located in macro `new_field`. 35 | 36 | ; Forgetting that the member declaration macros take plural 37 | struct Singular 38 | byte 1, TheChosenOne 39 | word 1, TheChosenOther 40 | end_struct 41 | ; "Macro 'byte' not defined", or 'word', or 'long', etc. 42 | 43 | ; Using dashes (-) instead of underscores (_) 44 | struct Dashing 45 | bytes 1, Foo 46 | end-struct 47 | ; "Macro 'end' not defined" 48 | 49 | ; Initalizing an alias 50 | struct Actor 51 | longs 0, Position 52 | words 1, YPos 53 | words 1, XPos 54 | alias Money 55 | words 1, Target 56 | end_struct 57 | 58 | dstruct Actor, MyActorWithMoney, .Money=100 59 | ; "Cannot initialize an alias" 60 | -------------------------------------------------------------------------------- /examples/tests.asm: -------------------------------------------------------------------------------- 1 | 2 | ; This file isn't particularly meant to contain examples, 3 | ; but everything here is tested to work fine. 4 | ; Use it as inspiration if you want. 5 | 6 | INCLUDE "../structs.inc" 7 | 8 | 9 | SECTION "Stats", ROM0 10 | 11 | struct Stats 12 | bytes 8, FirstName 13 | bytes 8, LastName 14 | bytes 1, HP 15 | bytes 1, MaxHP 16 | end_struct 17 | 18 | dstruct Stats, _PartyMember, "foo", "bar", 100, 100 19 | dstruct Stats, _PartyMember2, .FirstName="foo", .LastName="bar", .HP=100, .MaxHP=100 20 | dstruct Stats, _PartyMember3, .HP=100, .LastName="bar", .MaxHP=100, .FirstName="foo" 21 | 22 | 23 | struct Multi 24 | bytes 2, test 25 | end_struct 26 | 27 | dstruct Multi, Anon, 1\, 2 28 | dstruct Multi, Named, .test=1\, 2 29 | 30 | struct Extended 31 | bytes 27, FirstField 32 | extends Stats 33 | extends Stats, Player 34 | end_struct 35 | 36 | dstruct Extended, _MyExtendedStruct 37 | dstruct Extended, _MyExtendedStruct2, "debug test", "foo", "bar", 100, 100, "foo", "bar", 100, 100 38 | dstruct Extended, _MyExtendedStruct3, .FirstField="debug test", .HP=100, .LastName="bar", .MaxHP=100, .FirstName="foo", .Player_FirstName="foo", .Player_LastName="bar", .Player_HP=100, .Player_MaxHP=100 39 | 40 | ASSERT Extended_FirstName == 27 41 | ASSERT Extended_Player_FirstName == 27 + sizeof_Stats 42 | 43 | struct Actor 44 | longs 0, Position 45 | words 1, YPos 46 | words 1, XPos 47 | alias Money 48 | words 1, Target 49 | end_struct 50 | 51 | ASSERT Actor_Position == Actor_YPos 52 | ASSERT Actor_Money == Actor_Target 53 | ASSERT sizeof_Actor == 6 54 | 55 | dstruct Actor, _Test 56 | dstruct Actor, _Test2, 1, 2, 3 57 | dstruct Actor, _Test3, .Target=3, .XPos=2, .YPos=1 58 | 59 | ASSERT _Test == _Test_Position 60 | ASSERT _Test_Position == _Test_YPos 61 | -------------------------------------------------------------------------------- /structs.inc: -------------------------------------------------------------------------------- 1 | ; MIT License 2 | ; 3 | ; Copyright (c) 2018-2024 Eldred Habert and contributors 4 | ; Originally hosted at https://github.com/ISSOtm/rgbds-structs 5 | ; 6 | ; Permission is hereby granted, free of charge, to any person obtaining a copy 7 | ; of this software and associated documentation files (the "Software"), to deal 8 | ; in the Software without restriction, including without limitation the rights 9 | ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | ; copies of the Software, and to permit persons to whom the Software is 11 | ; furnished to do so, subject to the following conditions: 12 | ; 13 | ; The above copyright notice and this permission notice shall be included in all 14 | ; copies or substantial portions of the Software. 15 | ; 16 | ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | ; SOFTWARE. 23 | 24 | 25 | 26 | DEF STRUCTS_VERSION equs "4.0.2" 27 | MACRO structs_assert 28 | assert (\1), "rgbds-structs {STRUCTS_VERSION} bug. Please report at https://github.com/ISSOtm/rgbds-structs, and share the above stack trace *and* your code there!" 29 | ENDM 30 | 31 | 32 | ; Call with the expected RGBDS-structs version string to ensure your code 33 | ; is compatible with the INCLUDEd version of RGBDS-structs. 34 | ; Example: `rgbds_structs_version 2.0.0` 35 | MACRO rgbds_structs_version ; version_string 36 | DEF CURRENT_VERSION EQUS STRRPL("{STRUCTS_VERSION}", ".", ",") 37 | 38 | ; Undefine `EXPECTED_VERSION` if it does not match `CURRENT_VERSION` 39 | DEF EXPECTED_VERSION EQUS STRRPL("\1", ".", ",") 40 | check_ver {EXPECTED_VERSION}, {CURRENT_VERSION} 41 | 42 | IF !DEF(EXPECTED_VERSION) 43 | FAIL "rgbds-structs version \1 is required, which is incompatible with current version {STRUCTS_VERSION}" 44 | ENDC 45 | 46 | PURGE CURRENT_VERSION, EXPECTED_VERSION 47 | ENDM 48 | 49 | ; Checks whether trios of version components match. 50 | ; Used internally by `rgbds_structs_version`. 51 | MACRO check_ver ; expected major, minor, patch, current major, minor, patch 52 | IF (\1) != (\4) || (\2) > (\5) || (\3) > (\6) 53 | PURGE EXPECTED_VERSION 54 | ENDC 55 | ENDM 56 | 57 | 58 | ; Begins a struct declaration. 59 | MACRO struct ; struct_name 60 | IF DEF(STRUCT_NAME) || DEF(NB_FIELDS) 61 | FAIL "Please close struct definitions using `end_struct`" 62 | ENDC 63 | 64 | ; Define two internal variables for field definitions 65 | DEF STRUCT_NAME EQUS "\1" 66 | DEF NB_FIELDS = 0 67 | DEF NB_NONALIASES = 0 68 | 69 | ; Initialize _RS to 0 for defining offset constants 70 | RSRESET 71 | ENDM 72 | 73 | ; Ends a struct declaration. 74 | MACRO end_struct 75 | ; Define the number of fields and size in bytes 76 | DEF {STRUCT_NAME}_nb_fields EQU NB_FIELDS 77 | DEF {STRUCT_NAME}_nb_nonaliases EQU NB_NONALIASES 78 | DEF sizeof_{STRUCT_NAME} EQU _RS 79 | 80 | IF DEF(STRUCTS_EXPORT_CONSTANTS) 81 | EXPORT {STRUCT_NAME}_nb_fields, sizeof_{STRUCT_NAME} 82 | ENDC 83 | 84 | ; Purge the internal variables defined by `struct` 85 | PURGE STRUCT_NAME, NB_FIELDS, NB_NONALIASES 86 | ENDM 87 | 88 | 89 | ; Defines a field of N bytes. 90 | DEF bytes equs "new_field rb," 91 | DEF words equs "new_field rw," 92 | DEF longs equs "new_field rl," 93 | DEF alias equs "new_field rb, 0," 94 | 95 | ; Extends a new struct by an existing struct, effectively cloning its fields. 96 | MACRO extends ; struct_type[, sub_struct_name] 97 | IF !DEF(\1_nb_fields) 98 | FAIL "Struct \1 isn't defined!" 99 | ENDC 100 | IF _NARG != 1 && _NARG != 2 101 | FAIL "Invalid number of arguments, expected 1 or 2" 102 | ENDC 103 | FOR FIELD_ID, \1_nb_fields 104 | DEF EXTENDS_FIELD EQUS "\1_field{d:FIELD_ID}" 105 | get_nth_field_info {STRUCT_NAME}, NB_FIELDS 106 | 107 | IF _NARG == 1 108 | DEF {STRUCT_FIELD_NAME} EQUS "{{EXTENDS_FIELD}_name}" 109 | ELSE 110 | DEF {STRUCT_FIELD_NAME} EQUS "\2_{{EXTENDS_FIELD}_name}" 111 | ENDC 112 | DEF {STRUCT_FIELD} RB {EXTENDS_FIELD}_size 113 | IF DEF(STRUCTS_EXPORT_CONSTANTS) 114 | EXPORT {STRUCT_FIELD} 115 | ENDC 116 | DEF {STRUCT_NAME}_{{STRUCT_FIELD_NAME}} EQU {STRUCT_FIELD} 117 | DEF {STRUCT_FIELD_SIZE} EQU {EXTENDS_FIELD}_size 118 | DEF {STRUCT_FIELD_TYPE} EQUS "{{EXTENDS_FIELD}_type}" 119 | 120 | purge_nth_field_info 121 | 122 | DEF NB_FIELDS += 1 123 | IF {EXTENDS_FIELD}_size != 0 124 | DEF NB_NONALIASES += 1 125 | ENDC 126 | PURGE EXTENDS_FIELD 127 | ENDR 128 | ENDM 129 | 130 | 131 | ; Defines EQUS strings pertaining to a struct's Nth field. 132 | ; Used internally by `new_field` and `dstruct`. 133 | MACRO get_nth_field_info ; struct_name, field_id 134 | DEF STRUCT_FIELD EQUS "\1_field{d:\2}" ; prefix for other EQUS 135 | DEF STRUCT_FIELD_NAME EQUS "{STRUCT_FIELD}_name" ; field's name 136 | DEF STRUCT_FIELD_TYPE EQUS "{STRUCT_FIELD}_type" ; type ("b", "l", or "l") 137 | DEF STRUCT_FIELD_SIZE EQUS "{STRUCT_FIELD}_size" ; sizeof(type) * nb_el 138 | ENDM 139 | 140 | ; Purges the variables defined by `get_nth_field_info`. 141 | ; Used internally by `new_field` and `dstruct`. 142 | DEF purge_nth_field_info equs "PURGE STRUCT_FIELD, STRUCT_FIELD_NAME, STRUCT_FIELD_TYPE, STRUCT_FIELD_SIZE" 143 | 144 | ; Defines a field with a given RS type (`rb`, `rw`, or `rl`). 145 | ; Used internally by `bytes`, `words`, and `longs`. 146 | MACRO new_field ; rs_type, nb_elems, field_name 147 | IF !DEF(STRUCT_NAME) || !DEF(NB_FIELDS) 148 | FAIL "Please start defining a struct, using `struct`" 149 | ENDC 150 | 151 | get_nth_field_info {STRUCT_NAME}, NB_FIELDS 152 | 153 | ; Set field name 154 | DEF {STRUCT_FIELD_NAME} EQUS "\3" 155 | ; Set field offset 156 | DEF {STRUCT_FIELD} \1 (\2) 157 | IF DEF(STRUCTS_EXPORT_CONSTANTS) 158 | EXPORT {STRUCT_FIELD} 159 | ENDC 160 | ; Alias this in a human-comprehensible manner 161 | DEF {STRUCT_NAME}_\3 EQU {STRUCT_FIELD} 162 | ; Compute field size 163 | DEF {STRUCT_FIELD_SIZE} EQU _RS - {STRUCT_FIELD} 164 | ; Set properties 165 | DEF {STRUCT_FIELD_TYPE} EQUS STRSUB("\1", 2, 1) 166 | 167 | purge_nth_field_info 168 | 169 | DEF NB_FIELDS += 1 170 | IF \2 != 0 171 | DEF NB_NONALIASES += 1 172 | ENDC 173 | ENDM 174 | 175 | 176 | ; Strips whitespace from the left of a string. 177 | ; Used internally by `dstruct`. 178 | MACRO lstrip ; string_variable 179 | FOR START_POS, 1, STRLEN("{\1}") + 1 180 | IF !STRIN(" \t", STRSUB("{\1}", START_POS, 1)) 181 | BREAK 182 | ENDC 183 | ENDR 184 | REDEF \1 EQUS STRSUB("{\1}", START_POS) 185 | PURGE START_POS 186 | ENDM 187 | 188 | ; Allocates space for a struct in memory. 189 | ; If no further arguments are supplied, the space is allocated using `ds`. 190 | ; Otherwise, the data is written to memory using the appropriate types. 191 | ; For example, a struct defined with `bytes 1, Field1` and `words 3, Field2` 192 | ; could take four extra arguments, one byte then three words. 193 | ; Each such argument would have an equal sign between the name and value. 194 | MACRO dstruct ; struct_type, instance_name[, ...] 195 | IF !DEF(\1_nb_fields) 196 | FAIL "Struct \1 isn't defined!" 197 | ELIF _NARG != 2 && _NARG != 2 + \1_nb_nonaliases 198 | ; We must have either a RAM declaration (no data args) 199 | ; or a ROM one (RAM args + data args) 200 | FAIL STRFMT("Expected 2 or %u args to `dstruct`, but got {d:_NARG}", 2 + \1_nb_nonaliases) 201 | ENDC 202 | 203 | ; RGBASM always expands macro args, so `IF _NARG > 2 && STRIN("\3", "=")` 204 | ; would error out when there are only two args. 205 | ; Therefore, the condition is checked here (we can't nest the `IF`s over 206 | ; there because that would require a duplicated `ELSE`). 207 | DEF IS_NAMED_INSTANTIATION = 0 208 | IF _NARG > 2 209 | DEF IS_NAMED_INSTANTIATION = !STRCMP(STRSUB("\3", 1, 1), ".") && STRIN("\3", "=") 210 | ENDC 211 | 212 | IF IS_NAMED_INSTANTIATION 213 | ; This is a named instantiation; translate that to an ordered one. 214 | ; This is needed because data has to be laid out in order, so some translation is needed anyway. 215 | ; And finally, I believe it's better to re-use the existing code at the cost of a single nested macro. 216 | 217 | FOR ARG_NUM, 3, _NARG + 1 218 | ; Remove leading whitespace to obtain something like ".name=value" 219 | ; (this enables a simple check for starting with a period) 220 | REDEF CUR_ARG EQUS "\" 221 | lstrip CUR_ARG 222 | 223 | ; Ensure that the argument has a name and a value, 224 | ; separated by an equal sign 225 | DEF EQUAL_POS = STRIN("{CUR_ARG}", "=") 226 | IF !EQUAL_POS 227 | FAIL "\"{CUR_ARG}\" is not a named initializer!" 228 | ELIF STRCMP(STRSUB("{CUR_ARG}", 1, 1), ".") 229 | FAIL "\"{CUR_ARG}\" does not start with a period!" 230 | ENDC 231 | 232 | ; Find out which field the current argument is 233 | FOR FIELD_ID, \1_nb_fields 234 | IF !STRCMP(STRSUB("{CUR_ARG}", 2, EQUAL_POS - 2), "{\1_field{d:FIELD_ID}_name}") 235 | IF \1_field{d:FIELD_ID}_size == 0 236 | FAIL "Cannot initialize an alias" 237 | ENDC 238 | BREAK ; Match found! 239 | ENDC 240 | ENDR 241 | 242 | IF FIELD_ID == \1_nb_fields 243 | FAIL "\"{CUR_ARG}\" does not match any member of \1" 244 | ELIF DEF(FIELD_{d:FIELD_ID}_INITIALIZER) 245 | FAIL "\"{CUR_ARG}\" conflicts with \"{FIELD_{d:FIELD_ID}_ARG}\"" 246 | ENDC 247 | 248 | ; Save the argument to report in case a later argument conflicts with it 249 | DEF FIELD_{d:FIELD_ID}_ARG EQUS "{CUR_ARG}" 250 | 251 | ; Enclose argument initializers in parentheses so any commas inside 252 | ; it will not start a new argument 253 | DEF FIELD_{d:FIELD_ID}_INITIALIZER EQUS STRCAT("(", STRSUB("{CUR_ARG}", EQUAL_POS + 1), ")") 254 | ENDR 255 | PURGE ARG_NUM, CUR_ARG 256 | 257 | ; Now that we matched each named initializer to their order, 258 | ; invoke the macro again but without names 259 | DEF ORDERED_ARGS EQUS "\1, \2" 260 | FOR FIELD_ID, \1_nb_fields 261 | IF \1_field{d:FIELD_ID}_size != 0 262 | REDEF ORDERED_ARGS EQUS "{ORDERED_ARGS}, {FIELD_{d:FIELD_ID}_INITIALIZER}" 263 | PURGE FIELD_{d:FIELD_ID}_ARG, FIELD_{d:FIELD_ID}_INITIALIZER 264 | ENDC 265 | ENDR 266 | PURGE FIELD_ID 267 | 268 | ; Do the nested ordered instantiation 269 | DEF WAS_NAMED_INSTANTIATION = 1 270 | dstruct {ORDERED_ARGS} ; purges IS_NAMED_INSTANTIATION 271 | PURGE ORDERED_ARGS, WAS_NAMED_INSTANTIATION 272 | 273 | ELSE 274 | ; This is an ordered instantiation, not a named one. 275 | 276 | ; Define the struct's root label 277 | \2:: 278 | 279 | IF DEF(STRUCT_SEPARATOR) 280 | DEF DSTRUCT_SEPARATOR equs "{STRUCT_SEPARATOR}" 281 | ELSE 282 | DEF DSTRUCT_SEPARATOR equs "_" 283 | ENDC 284 | ; Define each field 285 | DEF ARG_NUM = 3 286 | FOR FIELD_ID, \1_nb_fields 287 | get_nth_field_info \1, FIELD_ID 288 | 289 | ; Define the label for the field 290 | \2_{{STRUCT_FIELD_NAME}}:: 291 | 292 | IF STRUCT_FIELD_SIZE != 0 ; Skip aliases 293 | ; Declare the space for the field 294 | IF ARG_NUM <= _NARG 295 | ; ROM declaration; use `db`, `dw`, or `dl` 296 | DEF FIELD_INITIALIZER EQUS "\" 297 | IF DEF(WAS_NAMED_INSTANTIATION) 298 | ; Remove the parentheses that named instantiation placed around the field initializer 299 | REDEF FIELD_INITIALIZER EQUS STRSUB("{FIELD_INITIALIZER}", 2, STRLEN("{FIELD_INITIALIZER}") - 2) 300 | ENDC 301 | d{{STRUCT_FIELD_TYPE}} {FIELD_INITIALIZER} 302 | PURGE FIELD_INITIALIZER 303 | DEF ARG_NUM += 1 304 | ENDC 305 | ; Add padding as necessary after the provided initializer 306 | ; (possibly all of it, especially for RAM use) 307 | IF {STRUCT_FIELD_SIZE} < @ - \2_{{STRUCT_FIELD_NAME}} 308 | FAIL STRFMT("Initializer for %s is %u bytes, expected %u at most", "\2_{{STRUCT_FIELD_NAME}}", @ - \2_{{STRUCT_FIELD_NAME}}, {STRUCT_FIELD_SIZE}) 309 | ENDC 310 | ds {STRUCT_FIELD_SIZE} - (@ - \2_{{STRUCT_FIELD_NAME}}) 311 | ENDC 312 | 313 | purge_nth_field_info 314 | ENDR 315 | PURGE FIELD_ID, ARG_NUM, DSTRUCT_SEPARATOR 316 | 317 | ; Define instance's properties from struct's 318 | IF !STRIN("\2", ".") 319 | DEF \2_nb_fields EQU \1_nb_fields 320 | DEF sizeof_\2 EQU @ - (\2) 321 | structs_assert sizeof_\1 == sizeof_\2 322 | 323 | IF DEF(STRUCTS_EXPORT_CONSTANTS) 324 | EXPORT \2_nb_fields, sizeof_\2 325 | ENDC 326 | ENDC 327 | 328 | PURGE IS_NAMED_INSTANTIATION 329 | ENDC 330 | ENDM 331 | 332 | 333 | ; Allocates space for an array of structs in memory. 334 | ; Each struct will have the index appended to its name **as decimal**. 335 | ; For example: `dstructs 32, NPC, wNPC` will define `wNPC0`, `wNPC1`, and so on until `wNPC31`. 336 | ; This is a change from the previous version of rgbds-structs, where the index was uppercase hexadecimal. 337 | ; Does not support data declarations because I think each struct should be defined individually for that purpose. 338 | MACRO dstructs ; nb_structs, struct_type, instance_name 339 | IF _NARG != 3 340 | FAIL "`dstructs` only takes 3 arguments!" 341 | ENDC 342 | 343 | FOR STRUCT_ID, \1 344 | dstruct \2, \3{d:STRUCT_ID} 345 | ENDR 346 | PURGE STRUCT_ID 347 | ENDM 348 | --------------------------------------------------------------------------------