Compare commits

..

179 Commits

Author SHA1 Message Date
Rangi
7ae23e6cdb Release 0.6.0-rc2 2022-09-08 17:07:47 -04:00
Rangi
98a6dffbca Implement opt Q for fixed-point precision, and q literals (e.g. 12.34q8) (#958)
Fixes #957

Co-authored-by: ISSOtm <eldredhabert0@gmail.com>
2022-09-05 00:47:32 +02:00
Rangi
889302a9e2 Document the -H and -l flags
Fixes #1042
2022-09-02 08:43:41 +02:00
Rangi
c01317e08d Only increment the unique \@ ID when it is first used per context (#1030)
This avoids changes to generated `\@` labels just by adding or
removing macros or loops which do not actually use `\@`.

Fixes #1019
2022-08-31 17:45:21 -04:00
Rangi
a52a00a9ca macro_UndefUniqueID uses 0 to mean \@ is undefined 2022-08-30 16:50:34 -04:00
Rangi
fa13611bbf Make comments more consistent
- Changes most `/* comments */` to `// comments`
- Changes `/**` block comments consistently to `/*`
- Adds consistent license comments to all files

Also renames `T_POP_SET` to `T_Z80_SET`
2022-08-30 07:51:32 +02:00
Rangi
dca24a6d50 Test that OPT r fails immediately if the recursion depth is already exceeded
Fixes #1034
2022-08-28 22:21:24 +02:00
Rangi
4363ffcad4 Clarify the JR documentation based on its usage (#1032)
Fixes #1020
2022-08-28 15:42:04 -04:00
Rangi
14e6a79adc Deprecate the old macro syntax (#1025)
Fixes #1011
2022-08-28 15:22:21 -04:00
Rangi
7a2ee26792 rgbasm -r sets the maximum recursion depth (#1026)
Previously it set the minimum failure depth (off by one)

Fixes #978
2022-08-28 15:21:29 -04:00
Rangi
425339ccf6 Implement FMOD function for fixed-point modulo
Fixes #1021
2022-08-28 21:21:10 +02:00
Rangi
1a1f1365e6 Clarify the FOR loop documentation (#1031)
Clarify the FOR loop documentation

Fixes #1003

Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>
2022-08-28 15:16:49 -04:00
Rangi
f97139461c Clarify the linkerscript example (#1028)
* Clarify the linkerscript example

* Explain that backslash escape sequences are supported

Fixes #1006
2022-08-28 15:13:43 -04:00
Rangi
8207dc57b7 Add a -Wunmapped-char warning for characters not in the charmap (#1023)
Fixes #1022
2022-08-28 15:12:43 -04:00
Rangi
d29057e747 Indent "SLACK:" to match the "SECTION" headers 2022-08-28 20:35:21 +02:00
Rangi
f1b74fa610 Report empty space between sections in map file 2022-08-28 20:35:21 +02:00
Rangi
c7a92d3104 rgblink -M omits symbol names from .map file 2022-08-28 20:35:21 +02:00
ISSOtm
0105779789 Fix incorrect Bash completions after --long-opt=
This is because `$COMP_CWORD` points to that `=` "word".
2022-08-10 08:44:41 +02:00
ISSOtm
9ef7954670 Fix some lint warnings in Bash completions 2022-08-10 00:16:05 +02:00
ISSOtm
d7d524294b Fix shebangs in Bash completion scripts
Not that you are really supposed to run them stand-alone?
2022-08-09 22:32:18 +02:00
ISSOtm
12fed4c68e Harden Bash completion scripts against invalid states
Printing an error message is better than locking the shell up, honestly.
2022-08-09 22:30:50 +02:00
ISSOtm
3db3421f07 Correct wrong state transitions in RGBGFX Bash completion script
Isn't strinly-typed programming just lovely?
Fixes #1018
2022-08-09 22:28:12 +02:00
ISSOtm
92eb0a133b Allow rgbgfx -r to read an infinite amount of tiles without tilemap
Previously, indices would be wrapped after 256 even without a tilemap;
since RGBGFX can generate arbitrarily large tile data if `-N` is not used,
it should be possible to read those in.

Of course, this won't work if a tilemap is provided, but such "big blobs"
can't generate a meaningful tilemap anyway.
2022-08-04 20:50:08 +02:00
ISSOtm
b02ccf8f4a Check before attempting to generate empty image
This causes a libpng warning then error, but print a better error message
Fixes #1016
2022-08-04 20:40:20 +02:00
ISSOtm
2e0991f32b Use proper 16-bit type for image reversing width
Fixes #1015
2022-08-04 20:40:20 +02:00
Eldred Habert
f3f2c2ca16 Improve object file format documentation (#1010)
Replacing the big pre-formatted text block with a list brings:
- Better accessibility, obviously
- Responsiveness
- Better formatting (bold, etc.)
- Sub-sections that can now be linked to
- Hyperlink cross-refs to other pages

The slight disadvantage is that `ENDC` etc. are now individual
list items, whereas they'd be better as part of the same item.
No big deal though, it was much worse before.

Some descriptions have been overhauled for clarity, and some
outright corrected (such as Assertions' "Offset" field).

Co-authored-by: Antonio Vivace <avivace4@gmail.com>
2022-07-29 22:48:55 +02:00
ISSOtm
9ec8186ac6 Switch linkdefs from scattered arrays to an array of structs
The info is better organized this way
2022-07-19 19:11:02 +02:00
ISSOtm
ab9945c1ee Avoid using fscanf to detect RGBDS object files
This function is made for text, e.g. accepts spaces, leading zeros, etc. before `%u`.
This way checks that the correct amount of bytes are read instead.
2022-07-19 19:10:58 +02:00
ISSOtm
18e4f132a8 Fix labels subtraction docs
Fixes #1009
2022-07-14 13:43:36 +02:00
ISSOtm
828b2adcdf Make RGBLINK able to link SDCC object files
This requires a LOT of tricky code, mostly due to the format itself being,
er, not the most straightforward.
Everything is converted to existing RGBLINK concepts (sections, patches,
etc.), so the core code is essentially unchanged.
(A couple of genuine RGBLINK bugs were uncovered along the way, so some of
the core code *is* changed, notably regarding `SECTION FRAGMENT`s.)

All of this code was clean-roomed, so SDCC's GPLv2 license does not apply.
2022-07-11 21:17:34 +02:00
ISSOtm
1c2965467d Process linker script before doing sanity checks 2022-07-11 21:17:34 +02:00
ISSOtm
d243e50390 Do not perform any sanity checks for bad section types
Otherwise, the arrays get overflowed
2022-07-11 21:17:34 +02:00
ISSOtm
acb33777c6 Sort RGB colors as specified in the docs 2022-07-10 12:36:10 +02:00
ISSOtm
d15916b1bd Clarify sorting order of RGB colors 2022-07-10 12:32:57 +02:00
ISSOtm
28fcef0ecd Improve some wording slightly 2022-07-10 12:30:18 +02:00
ISSOtm
b53c115ec2 Fix width and height being reported in wrong order 2022-07-10 12:14:24 +02:00
ISSOtm
6a51e39a5c Print error if reverse() fails to open a file 2022-07-10 11:55:56 +02:00
Antonio Vivace
e348f70866 Remove funding options, leaving only OpenCollective 2022-07-08 21:22:28 +02:00
ISSOtm
43a487f0bf Fix two inverted column widths 2022-07-02 17:48:28 +02:00
ISSOtm
6b2dc37f43 Release 0.6.0-rc1 2022-07-02 17:08:54 +02:00
ISSOtm
2b83a81ceb Update completion scripts
Also correct minor blunders in the man page
2022-07-02 17:04:17 +02:00
ISSOtm
ca8693690a Cancel -U option
As it turns out, it is really difficult to implement, and can be dealt with later.
2022-07-02 17:04:17 +02:00
ISSOtm
87092208bc Stop passing options.columnMajor explicitly everywhere 2022-07-02 17:04:17 +02:00
ISSOtm
0d32775a1f Add more sanity checks when reversing an image 2022-07-02 17:04:17 +02:00
ISSOtm
0df07d3688 Add note on supporting extra pal formats 2022-07-02 17:04:17 +02:00
ISSOtm
3f70372308 Implement slicing input image 2022-07-02 17:04:17 +02:00
ISSOtm
9646f15b59 Add error message for nigh-impossible proto-palette overflow 2022-07-02 17:04:17 +02:00
ISSOtm
973fbb91bc Improve error message when a tile has too many colors 2022-07-02 17:04:17 +02:00
ISSOtm
903862c451 Improve flag set generation of RGBGFX tests
Less duplication = good
2022-07-02 17:04:17 +02:00
ISSOtm
3f5983358c Add proper error message for bad manual palettes 2022-07-02 17:04:17 +02:00
ISSOtm
7a7126f3b8 Implement bit flipping with a lookup table
Should improve performance.
This version is cooler, and also does not suffer from iteration limits
2022-07-02 17:04:17 +02:00
ISSOtm
b4dadd35b6 Use an iterator zip
Simplifies iterating over tiles and attributes at the same time
2022-07-02 17:04:17 +02:00
ISSOtm
d9b1402ef8 Fix RGBGFX number parsing 2022-07-02 17:04:17 +02:00
ISSOtm
832e0ec972 Report position in color errors 2022-07-02 17:04:17 +02:00
ISSOtm
caaf7a8444 Test VRA0 size as well in randtilegen tests 2022-07-02 17:04:17 +02:00
ISSOtm
a5ed0292b1 Reject colors with ambiguous alpha channel 2022-07-02 17:04:17 +02:00
ISSOtm
05e36767b0 Implement "palette map" output 2022-07-02 17:04:17 +02:00
ISSOtm
531092f5bd Add corrupted PNGs to check error handling
This is not intended to test libpng as much as checking that
we behave correctly if libpng gives us an error
2022-07-02 17:04:17 +02:00
ISSOtm
4c51792f15 Allow testing RGBGFX with specific images as input
No such tests yet, but the infrastructure will be there.
2022-07-02 17:04:17 +02:00
ISSOtm
c4359c1058 Ignore -b and -N when reversing without tilemap
These offsets should only be applied to a tile ID read as input... but this ain't one!
2022-07-02 17:04:17 +02:00
ISSOtm
159efe1257 Transfer test programs as well for cross-testing 2022-07-02 17:04:17 +02:00
ISSOtm
3cfe7800c7 Make randtilegen and rgbgfx_test compile with MSVC 2022-07-02 17:04:17 +02:00
ISSOtm
01cf0c5f98 Print RGBGFX test names 2022-07-02 17:04:17 +02:00
ISSOtm
0dbcebfeb4 Have CMake build and install steps be verbose 2022-07-02 17:04:17 +02:00
ISSOtm
491b6746ab Clean RGBGFX test programs with make clean 2022-07-02 17:04:17 +02:00
ISSOtm
cbf6fadcdb Add RGBGFX tests 2022-07-02 17:04:17 +02:00
ISSOtm
a77b0b396a Fix grayscale sorting not setting palette size
A black+white palette would turn into white+transparent, removing black pixels
from the palette (the third slot would be empty, and the 4th slot be set directly)
2022-07-02 17:04:17 +02:00
ISSOtm
568fb5e4c8 Only report "fusing" different colors once per pair 2022-07-02 17:04:17 +02:00
ISSOtm
82012f698e Fix alpha channel checking in RGBGFX test driver
Compare CGB colors only, including ignoring RGB components if alpha says
the color is transparent
2022-07-02 17:04:17 +02:00
ISSOtm
57ac07b03e Correctly handle fully-transparent tiles when outputting unoptimized tilemap 2022-07-02 17:04:17 +02:00
ISSOtm
c521233499 Fix ProtoPalette::compare
Some disjoint sets were mistakenly reported not as such
For example, {0} was considered to include {1}.
2022-07-02 17:04:17 +02:00
ISSOtm
bf869f6961 Fix memory leak in test driver program
Teeny tiny
2022-07-02 17:04:17 +02:00
ISSOtm
0f8cbb1faf Generate all necessary files for RGBGFX tests 2022-07-02 17:04:17 +02:00
ISSOtm
fcce42d3d2 Avoid sorting proto-palettes breaking mappings
The sorting was performed without updating the mappings, which broke the world.
We can instead sort the IDs as they are inserted into the packing queue,
which should also be faster than moving the actual proto-pal objects around.
2022-07-02 17:04:17 +02:00
Eievui
ed104a9f70 Add rgbgfx test program
Co-Authored-By: ISSOtm <eldredhabert0@gmail.com>
2022-07-02 17:04:17 +02:00
ISSOtm
f5d4126303 Report when an input "tile" contains too many colors
Which otherwise trips a later assertion in debug mode (phew!)
and crashes in release mode (oops)
2022-07-02 17:04:17 +02:00
ISSOtm
78e751f022 Fix reading interlaced PNGs
Either we let libpng handle the transform, or we don't.
But make up your mind!
2022-07-02 17:04:17 +02:00
ISSOtm
d569c6392c Avoid initializing the PNG row
It gets overwritten right after, and uncovers an error in the interlaced read
2022-07-02 17:04:17 +02:00
ISSOtm
75b9d48990 Make randtilegen report the file names it fails to open
Which of the two is erroring out can be inferred from the file name,
anyway.
2022-07-02 17:04:17 +02:00
ISSOtm
3aabe9c799 Move randtilegen to test/gfx subdir
It's tool-specific, so categorize it where it belongs
2022-07-02 17:04:17 +02:00
ISSOtm
5be2b96b40 Remove incorrect check for reversed image width
That check was when the image width was in tiles, and another check right below
is the correct equivalent for the new pixel-unit argument
2022-07-02 17:04:17 +02:00
ISSOtm
7fdfbbbbba Fix inverted condition in tile dedup warning 2022-07-02 17:04:17 +02:00
ISSOtm
7fdc6cbced Fix erroneous tabs in rgbgfx usage text
Thanks @GreenAndEievui!
2022-07-02 17:04:17 +02:00
ISSOtm
56115653ef Include cross-ref link to at-files where mentioned 2022-07-02 17:04:17 +02:00
ISSOtm
7927dfd2e3 Remove negative values in parseNumber checking
Including removal of a dead & useless check.
By the way, fuck integer promotion.
2022-07-02 17:04:17 +02:00
ISSOtm
b1aec91912 Assert that row size is not null
Silences some static analysis warning
2022-07-02 17:04:17 +02:00
ISSOtm
7defaad9d2 Remove placeholder palette output format from -p
This is now described in its own section
2022-07-02 17:04:17 +02:00
ISSOtm
dc9185e50b Make output format descriptions more succinct and link to Pan Docs 2022-07-02 17:04:17 +02:00
ISSOtm
02d957278d Document output formats 2022-07-02 17:04:17 +02:00
ISSOtm
6feb1fb73a Retire -f 2022-07-02 17:04:17 +02:00
ISSOtm
dc67f152a9 Document accepted external palette formats 2022-07-02 17:04:17 +02:00
ISSOtm
913c3dd711 Accept both colons and semicolons in inline pal spec 2022-07-02 17:04:17 +02:00
ISSOtm
32242e0ff2 Switch from colon separators to semicolon
As documented, oops
2022-07-02 17:04:17 +02:00
ISSOtm
91071009a8 Implement some external palette specs
PSP, ACT, and ACO are complete
2022-07-02 17:04:17 +02:00
ISSOtm
1da884db15 Handle processing errors appropriately
Alter the return status accordingly, and print the error counts on exit
2022-07-02 17:04:17 +02:00
ISSOtm
ef473de75a Explicitly error out if no input image is given 2022-07-02 17:04:17 +02:00
ISSOtm
6b0cab32a6 Implement inline palette spec parsing 2022-07-02 17:04:17 +02:00
ISSOtm
cc27169ecd Implement preliminary version of "reverse" feature
Not hooked to all RGBGFX flags yet, but good enough for most use cases
(and as a base for future development, should I need to `reset --hard`.)

TODOs marked appropriately.
2022-07-02 17:04:17 +02:00
ISSOtm
843022772b Clean up randtilegen
Apply review comments
2022-07-02 17:04:17 +02:00
ISSOtm
75f8b16f33 Implement "at-files" for RGBGFX
Useful for persisting flags outside of the build system
2022-07-02 17:04:17 +02:00
ISSOtm
188027bccc Rename convert to process
More consistent with its "main" function's name
2022-07-02 17:04:17 +02:00
ISSOtm
79adcdb7ea Ignore tested sub-projects 2022-07-02 17:04:17 +02:00
ISSOtm
8ed65078da Import fixes to random image generator 2022-07-02 17:04:17 +02:00
ISSOtm
7311fc9ef8 Fix transparency handling
Ensure that the color count is properly used, and that
transparency is not counted as a color when packing palettes
2022-07-02 17:04:17 +02:00
ISSOtm
7d54145e56 Record "seed" when generating images as well
For reproducibility
2022-07-02 17:04:17 +02:00
ISSOtm
e753b62d1a Add program by @aaaaaa123456789 to generate RGBGFX-able images 2022-07-02 17:04:17 +02:00
ISSOtm
6ed220b4c1 Make some style corrections
Co-Authored-By: Rangi <remy.oukaour+rangi42@gmail.com>
2022-07-02 17:04:17 +02:00
ISSOtm
e49fb457ea Factor out row bitplanes extraction into a common function 2022-07-02 17:04:17 +02:00
ISSOtm
3c9d5b05d6 Implement transparency handling
Though none of this has been tested so far...
2022-07-02 17:04:17 +02:00
ISSOtm
e86eb9337a Check the range of the CLI-specified palette size 2022-07-02 17:04:17 +02:00
ISSOtm
b0f8e04fb7 Do not do anything if option parsing goes wrong 2022-07-02 17:04:17 +02:00
ISSOtm
493b94919f Fix -TAP being non-functional 2022-07-02 17:04:17 +02:00
ISSOtm
71e22f3bfe Correct "is this palette empty?" function
This notably caused decantation to delete non-empty palettes, which crashes
2022-07-02 17:04:17 +02:00
ISSOtm
ac02382632 Clean up palette packing a bit
Rename a poorly-named attribute, and add a bunch of debug logging
2022-07-02 17:04:17 +02:00
ISSOtm
943d631701 Implement max palette count 2022-07-02 17:04:17 +02:00
ISSOtm
d2f9cc7e8c Document accepted number formats 2022-07-02 17:04:17 +02:00
ISSOtm
76bb950be5 Parse bank capacities 2022-07-02 17:04:17 +02:00
ISSOtm
f29c5d81ec Provide a per-option sane default when number parsing fails 2022-07-02 17:04:17 +02:00
ISSOtm
2307981878 Parse base tile IDs 2022-07-02 17:04:17 +02:00
ISSOtm
6bab2ea5c8 Add different verbosity levels
And also some ASCII art, perhaps?
2022-07-02 17:04:17 +02:00
ISSOtm
35e57a55c9 Handle base tile IDs in "optimized" output 2022-07-02 17:04:17 +02:00
ISSOtm
21e9a65f0b Remove old C headers 2022-07-02 17:04:17 +02:00
ISSOtm
779c8c9368 Implement -m
Though none of the code involved has been tested in any capacity yet, lol
2022-07-02 17:04:17 +02:00
ISSOtm
e855b6f622 Include libstdc++-6.dll in 32-bit MinGW build 2022-07-02 17:04:17 +02:00
ISSOtm
3b1808cc8f Fix Windows-breaking use of struct vs class
MSVC's (broken) ABI breaks otherwise.
What the f@!$#ck, Microsoft?
(Thank you based Clang for warning, by the way.)
2022-07-02 17:04:17 +02:00
ISSOtm
71cb2854e8 Use cinttypes instead of inttypes.h
Fixes build on some compilers for some reason I fail to understand
2022-07-02 17:04:17 +02:00
ISSOtm
2099a25ee0 Avoid using transform_reduce
Not available in libstdc++ 7, apparently
2022-07-02 17:04:17 +02:00
ISSOtm
b9de65c9a2 Set C++ compiler in CI as well
Duh
2022-07-02 17:04:17 +02:00
ISSOtm
c82cce6d95 Explicitly specify queue template param
GCC 7 fails to deduce the contained type
2022-07-02 17:04:17 +02:00
ISSOtm
97965c9766 Revamp number parsing and remove <charconv>
Support all intended formats and allow partial parsing
<charconv> is not available in GCC 7
2022-07-02 17:04:17 +02:00
ISSOtm
5efc49cb12 Change UseTab to ForIndentation
This fixes many whitespace issues
2022-07-02 17:04:17 +02:00
ISSOtm
c4361b965c Remove <filesystem>
Should fix compilation with GCC before 9
2022-07-02 17:04:17 +02:00
ISSOtm
8d00a61602 Flesh out man page
Describe current options, and add some TODOs for functionality to be implemented
2022-07-02 17:04:17 +02:00
ISSOtm
20442c8a43 Add compatibility hack for unused colors in indexed PNGs 2022-07-02 17:04:17 +02:00
ISSOtm
b95c26c886 Fully implement decanting step 2022-07-02 17:04:17 +02:00
ISSOtm
a96aa1725f Optimize AssignedProtos::empty() to stop early
Also allow counting an `AssignedProtos`'s number of proto-palettes
2022-07-02 17:04:17 +02:00
ISSOtm
3d79f76e41 Avoid calling skipEmpty() for AssignedProtos::end() 2022-07-02 17:04:17 +02:00
ISSOtm
fdfedc45a6 Allow computing the combined weight of any proto-palette set 2022-07-02 17:04:17 +02:00
ISSOtm
c98d92a4c4 Implement -C 2022-07-02 17:04:17 +02:00
ISSOtm
d438838db4 Correctly handle "multiple" overlap between proto-palettes
As explained by the comment
2022-07-02 17:04:17 +02:00
ISSOtm
d675523e49 Skip initializing the PNG pixel array
We are about to set all of the pixels anyway
2022-07-02 17:04:17 +02:00
ISSOtm
ad07c9deb9 Implement (stub) handling for all options 2022-07-02 17:04:17 +02:00
ISSOtm
bf9f99ebf5 Reorder Options members by usage order 2022-07-02 17:04:17 +02:00
ISSOtm
f0eca86c52 Update checkdiff to check for gfx/main.cpp 2022-07-02 17:04:17 +02:00
ISSOtm
e8d8ae4c78 Print all configuration on verbose startup 2022-07-02 17:04:17 +02:00
ISSOtm
0cc62824b9 Use MinGW C++ compiler as well 2022-07-02 17:04:17 +02:00
ISSOtm
2fb1eb9136 Print file paths in a platform-independent way
Conversion may be unspecified, but Windows better do the right thing (or else.)
2022-07-02 17:04:17 +02:00
ISSOtm
bde380f38b Fix unchecked narrowing conversion of tile IDs
The conversion is OK because of the tile amount cap
2022-07-02 17:04:17 +02:00
ISSOtm
38e8024ffa Add missing return type for unreachable_() 2022-07-02 17:04:17 +02:00
ISSOtm
3bd6078537 Tag AssignedProtos iterator as forward
That's what it actually is, oops
(Required for `minmax_element`)
2022-07-02 17:04:17 +02:00
ISSOtm
5409d0d15a Add missing header for strcasecmp 2022-07-02 17:04:17 +02:00
ISSOtm
9262fefd07 Fix compiling with Clang
Force version.c to be compiled as C++ (bodge, will need a proper fix for `version.c`)
Remove user-defined `ProtoPalette` assignment operator (same as default, anyway)
2022-07-02 17:04:17 +02:00
ISSOtm
638d024040 Update subprojects and patch pokecrystal 2022-07-02 17:04:17 +02:00
ISSOtm
3fa1854332 Implement enough functionality to compile & match pokecrystal 2022-07-02 17:04:17 +02:00
ISSOtm
6e406b22bb Implement more features and fix bugs 2022-07-02 17:04:17 +02:00
ISSOtm
d30e507270 Fix duplicated CGB color calculation in pal sorting
And also the inverted alpha channel condition causing the wrong colors
to be treated as transparent
2022-07-02 17:04:17 +02:00
ISSOtm
373a22660b Switch clang-format to C++17
Be consistent with what we settled on in code
2022-07-02 17:04:17 +02:00
ISSOtm
8c62e80c18 Reimplement basic RGBGFX features in C++
Currently missing from the old version:
- `-f` ("fixing" the input image to be indexed)
- `-m` (the code for detecting mirrored tiles is missing, but all of the
        "plumbing" is otherwise there)
- `-C`
- `-d`
- `-x` (though I need to check the exact functionality the old one has)
- Also the man page is still a draft and needs to be fleshed out

More planned features are not implemented yet either:
- Explicit palette spec
- Better error messages, also error "images"
- Better 8x16 support, as well as other "dedup unit" sizes
- Support for arbitrary number of palettes & colors per palette
- Other output formats (for example, a "full" palette map for "streaming"
  use cases like gb-open-world)
- Quantization?

Some things may also be bugged:
- Transparency support
- Tile offsets (not exposed yet)
- Tile counts per bank (not exposed yet)

...and performance remains to be checked.
We need to set up some tests, honestly.
2022-07-02 17:04:17 +02:00
DaKnig
34bc650341 Fix shebangs (#992) 2022-07-01 22:59:41 +02:00
ISSOtm
43ba7d0efb Explain to static analyzer that initial symbol creation cannot fail 2022-06-12 16:49:06 +02:00
ISSOtm
685ea5feed Fix RGBLINK leaking partial string at EOF
Does not really do much, but silences some static analyzers
2022-06-12 16:39:55 +02:00
ISSOtm
f5ac268989 Fix unchecked realloc in makeUnknown 2022-06-12 16:36:35 +02:00
ISSOtm
d51ab35203 Trim macro arg whitespace after line continuations 2022-06-09 00:07:27 +02:00
aaaaaa123456789
3a71910312 Update link to TPP1 spec in RGBFIX man page (#1004)
To match the repo transfer from late 2021.
2022-06-05 23:02:19 +02:00
ISSOtm
ec25b4ac0e Disable -Winline
<@aaaaaa123456789> I mean, `-Winline -Werror` is the "make my builds fail at random for no reason" switch
2022-06-04 11:14:07 +02:00
ISSOtm
83222a8147 Fix "building from source" link in README
Fixes #1001
2022-05-27 11:56:52 +02:00
ISSOtm
97c326942f Warn about automatic instruction alterations
Step 1 of #986
2022-05-21 21:49:07 +02:00
ISSOtm
b037d54f64 Remove deprecated symbols
Fixes #896
2022-05-21 21:45:06 +02:00
ISSOtm
80df6640e3 Update checkdiff to check new man page locations 2022-05-21 11:01:32 +02:00
ISSOtm
68d3ef8e76 Only test a few random padding values each time
It's unlikely that a specific value matters, though in theory possible;
however, an exhaustive test every time is really slow, and testing a couple
random values should, over time, cover everything.

It is fine to test random values because those tested will be logged,
so the problem can be manually reproduced later.
It does make CI technically not deterministic... but that should be fine.
2022-05-19 22:47:48 +02:00
Eldred Habert
01777c96c8 Link to rgbds-www repo in README
Should make it easier to find..!
2022-05-18 19:27:55 +02:00
ISSOtm
28f9183d80 Fix formatting of rgbasm -D documentation 2022-05-17 20:11:00 +02:00
ISSOtm
74c31f7c0f Update docs CI workflows to sync with new rgbds-www repo 2022-05-17 18:36:31 +02:00
ISSOtm
7e94ecbfe6 Move all man pages to a separate directory
Simplifies processing all around, and makes more sense
2022-05-15 13:34:35 +02:00
ISSOtm
0195196425 Cache built deps in Windows CI runs 2022-05-01 16:40:11 +02:00
ISSOtm
19c85a7c2e Update uCity commit to fix deprecation warnings
Cleaning up the logs!
See #991 for context.
2022-04-30 20:42:55 +02:00
ISSOtm
59e73e64ca Unconditionally output ROM0 even without any sections
Required for example when "overlaying" over a ROM and only patching ROMX.
Fixes #993
2022-04-30 14:27:36 +02:00
ISSOtm
972d06bb41 Fix gbdiff script
Fix issues with spaces in input filenames,
as well as a bunch more lint warnings.
2022-04-30 14:27:36 +02:00
ISSOtm
e27da737c6 Print name of up to 10 floating sections on overlay error 2022-04-14 23:58:32 +02:00
256 changed files with 9573 additions and 4855 deletions

View File

@@ -93,7 +93,7 @@ SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++20
Standard: c++17
TabWidth: 4
UseCRLF: false
UseTab: AlignWithSpaces
UseTab: ForIndentation

2
.github/FUNDING.yml vendored
View File

@@ -1,3 +1 @@
github: avivace
patreon: gbdev01
open_collective: gbdev

View File

@@ -1,56 +0,0 @@
#!/usr/bin/awk -f
/^\s+<td><b class="Sy">.+<\/b><\/td>$/ {
# Assuming that all cells whose contents are bold are heading cells,
# use the HTML tag for those
sub(/td><b class="Sy"/, "th");
sub(/b><\/td/, "th");
}
# The whole page is being generated, so it's not meant to contain any Liquid
BEGIN {
print "{% raw %}"
}
END {
print "{% endraw %}"
}
BEGIN {
in_synopsis = 0
}
/<table class="Nm">/ {
in_synopsis = 1
}
/<\/table>/ {
# Resets synopsis state even when already reset, but whatever
in_synopsis = 0
}
/<code class="Fl">-[a-zA-Z]/ {
# Add links to arg descr in synopsis section
if (in_synopsis) {
while (match($0, /<code class="Fl">-[a-zA-Z]+/)) {
# 123456789012345678 -> 18 chars
optchars = substr($0, RSTART + 18, RLENGTH - 18)
i = length(optchars)
while (i) {
end = RSTART + 18 + i
i -= 1
len = i ? 1 : 2
$0 = sprintf("%s<a href=\"#%s\">%s</a>%s",
substr($0, 0, end - len - 1),
substr($0, end - 1, 1),
substr($0, end - len, len),
substr($0, end))
}
}
}
}
{
# Make long opts (defined using `Fl Fl`) into a single tag
gsub(/<code class="Fl">-<\/code>\s*<code class="Fl">/, "<code class=\"Fl\">-")
}
{
print
}

View File

@@ -1,113 +0,0 @@
#!/bin/bash
usage() {
cat <<EOF
Usage: $0 [-h] [-r] <rgbds-www> <version>
Copy renders from RGBDS repository to rgbds-www documentation
Execute from the root folder of the RGBDS repo, checked out at the desired tag
<rgbds-www> : Path to the rgbds-www repository
<version> : Version to be copied, such as 'v0.4.1' or 'master'
-h Display this help message
-r Update "latest stable" redirection pages and add a new entry to the index
(use for releases, not master)
EOF
}
is_release=0
bad_usage=0
while getopts ":hr" opt; do
case $opt in
r)
is_release=1
;;
h)
usage
exit 0
;;
\?)
echo "Unknown option '$OPTARG'"
bad_usage=1
;;
esac
done
if [ $bad_usage -ne 0 ]; then
usage
exit 1
fi
shift $(($OPTIND - 1))
declare -A PAGES
PAGES=(
[rgbasm.1.html]=src/asm/rgbasm.1
[rgbasm.5.html]=src/asm/rgbasm.5
[rgblink.1.html]=src/link/rgblink.1
[rgblink.5.html]=src/link/rgblink.5
[rgbfix.1.html]=src/fix/rgbfix.1
[rgbgfx.1.html]=src/gfx/rgbgfx.1
[rgbds.5.html]=src/rgbds.5
[rgbds.7.html]=src/rgbds.7
[gbz80.7.html]=src/gbz80.7
)
WWWPATH="/docs"
mkdir -p "$1/_documentation/$2"
# `mandoc` uses a different format for referring to man pages present in the **current** directory.
# We want that format for RGBDS man pages, and the other one for the rest;
# we thus need to copy all pages to a temporary directory, and process them there.
# Copy all pages to current dir
cp "${PAGES[@]}" .
for page in "${!PAGES[@]}"; do
stem="${page%.html}"
manpage="${stem%.?}(${stem#*.})"
descr="$(awk -v 'FS=.Nd ' '/.Nd/ { print $2; }' "${PAGES[$page]}")"
cat >"$1/_documentation/$2/$page" <<EOF
---
layout: doc
title: $manpage [$2]
description: RGBDS $2 — $descr
---
EOF
options=fragment,man='%N.%S;https://linux.die.net/man/%S/%N'
if [ $stem = rgbasm.5 ]; then
options+=,toc
fi
mandoc -Thtml -I os=Linux -O$options "${PAGES[$page]##*/}" | .github/actions/doc_postproc.awk >> "$1/_documentation/$2/$page"
groff -Tpdf -mdoc -wall "${PAGES[$page]##*/}" >"$1/_documentation/$2/$stem.pdf"
if [ $is_release -ne 0 ]; then
cat - >"$1/_documentation/$page" <<EOF
---
redirect_to: $WWWPATH/$2/${page%.html}
permalink: $WWWPATH/${page%.html}/
title: $manpage [latest stable]
description: RGBDS latest stable — $descr
---
EOF
fi
done
cat - >"$1/_documentation/$2/index.html" <<EOF
---
layout: doc_index
permalink: /docs/$2/
title: RGBDS online manual [$2]
description: RGBDS $2 - Online manual
---
EOF
# If making a release, add a new entry right after `master`
if [ $is_release -ne 0 ]; then
awk '{ print }
/"name": "master"/ { print "\t\t{\"name\": \"'$2'\", \"text\": \"'$2'\" }," }
' "$1/_data/doc.json" >"$1/_data/doc.json.tmp"
mv "$1/_data/doc.json"{.tmp,}
fi
# Clean up
rm "${PAGES[@]##*/}"

View File

@@ -23,16 +23,16 @@ jobs:
run: |
sudo apt-get -qq update
sudo apt-get install -yq groff zlib1g-dev
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
tar xf mandoc-1.14.5.tar.gz
cd mandoc-1.14.5
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
tar xf mandoc-1.14.6.tar.gz
cd mandoc-1.14.6
./configure
make
sudo make install
- name: Update pages
working-directory: rgbds
working-directory: rgbds/man
run: | # The ref appears to be in the format "refs/tags/<version>", so strip that
./.github/actions/get-pages.sh -r ../rgbds-www ${GITHUB_REF##*/}
../../rgbds-www/.github/actions/get-pages.sh ${GITHUB_REF##*/} *
- name: Push new pages
working-directory: rgbds-www
run: |

View File

@@ -16,6 +16,11 @@ jobs:
cc: gcc
- os: macos-11.0
cc: gcc
include:
- cc: gcc
cxx: g++
- cc: clang
cxx: clang++
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
@@ -28,25 +33,25 @@ jobs:
# Apple's base version is severely outdated, not even supporting -Wall,
# but it overrides Homebrew's version nonetheless...
- name: Build & install using Make
run: |
export PATH="/usr/local/opt/bison/bin:$PATH"
make develop -j Q= CC=${{ matrix.cc }}
sudo make install -j Q=
if: matrix.buildsys == 'make'
- name: Build & install using CMake
run: |
export PATH="/usr/local/opt/bison/bin:$PATH"
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${{ matrix.cc }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
cmake --build build -j
cp build/src/rgb{asm,link,fix,gfx} .
sudo cmake --install build
make develop -j Q= CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
sudo make install -j Q=
- name: Build & install using CMake
if: matrix.buildsys == 'cmake'
run: |
export PATH="/usr/local/opt/bison/bin:$PATH"
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
cmake --build build -j --verbose
cp build/src/rgb{asm,link,fix,gfx} .
sudo cmake --install build --verbose
- name: Package binaries
run: |
mkdir bins
cp rgb{asm,link,fix,gfx} bins
- name: Upload binaries
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v3
with:
name: rgbds-canary-${{ matrix.os }}-${{ matrix.cc }}-${{ matrix.buildsys }}
path: bins
@@ -71,7 +76,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Get zlib, libpng and bison
run: | # TODO: use an array
run: | # TODO: use an array; remember to update the versions being downloaded, *and* the paths being extracted! (`Move-Item`)
$wc = New-Object System.Net.WebClient
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
@@ -95,28 +100,41 @@ jobs:
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
Move-Item zlib-1.2.12 zlib
Move-Item lpng1637 libpng
- uses: actions/cache@v3
id: cache
with:
path: |
zbuild
pngbuild
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
- name: Build zlib
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
cmake --build zbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true'
- name: Install zlib
run: |
cmake --install zbuild
- name: Build libpng
run: |
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
cmake --build pngbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true'
- name: Install libpng
run: |
cmake --install pngbuild
- name: Build Windows binaries
run: |
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j
cmake --install build
cmake --build build --config Release -j --verbose
cmake --install build --verbose --prefix install_dir
- name: Package binaries
shell: bash
run: |
mkdir bins
cp install_dir/bin/{rgbasm.exe,rgblink.exe,rgbfix.exe,rgbgfx.exe,zlib1.dll,libpng16.dll} bins
- name: Upload Windows binaries
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v3
with:
name: rgbds-canary-win${{ matrix.bits }}
path: bins
@@ -124,6 +142,7 @@ jobs:
shell: bash
run: |
cp bins/* .
cp bins/*.dll test/gfx
test/run-tests.sh
windows-xbuild:
@@ -150,7 +169,7 @@ jobs:
./.github/actions/install_deps.sh ${{ matrix.os }}
- name: Install MinGW
run: |
sudo apt-get install gcc-mingw-w64-${{ matrix.arch }} mingw-w64-tools libz-mingw-w64-dev
sudo apt-get install {gcc,g++}-mingw-w64-${{ matrix.arch }} mingw-w64-tools libz-mingw-w64-dev
- name: Install libpng dev headers for MinGW
run: |
sudo ./.github/actions/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
@@ -166,12 +185,21 @@ jobs:
mv rgbgfx bins/rgbgfx.exe
cp /usr/${{ matrix.triplet }}/lib/zlib1.dll bins
cp /usr/${{ matrix.triplet }}/bin/libpng16-16.dll bins
if [ ${{ matrix.bits }} -eq 32 ]; then cp /usr/lib/gcc/${{ matrix.triplet }}/7.3-win32/libgcc_s_sjlj-1.dll bins; fi
if [ ${{ matrix.bits }} -eq 32 ]; then cp /usr/lib/gcc/${{ matrix.triplet }}/7.3-win32/lib{gcc_s_sjlj-1,stdc++-6}.dll bins; fi
mv test/gfx/randtilegen{,.exe}
mv test/gfx/rgbgfx_test{,.exe}
- name: Upload Windows binaries
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v3
with:
name: rgbds-canary-mingw-win${{ matrix.bits }}
path: bins
- name: Upload Windows test binaries
uses: actions/upload-artifact@v3
with:
name: testing-programs-mingw-win${{ matrix.bits }}
path: |
test/gfx/randtilegen.exe
test/gfx/rgbgfx_test.exe
windows-xtesting:
needs: windows-xbuild
@@ -183,14 +211,20 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Retrieve binaries
uses: actions/download-artifact@v1
uses: actions/download-artifact@v3
with:
name: rgbds-canary-mingw-win${{ matrix.bits }}
path: bins
- name: Retrieve test binaries
uses: actions/download-artifact@v3
with:
name: testing-programs-mingw-win${{ matrix.bits }}
path: test/gfx
- name: Extract binaries
shell: bash
run: |
cp bins/* .
cp bins/*.dll test/gfx
- name: Run tests
shell: bash
run: |

View File

@@ -4,16 +4,15 @@ on:
branches:
- master
paths:
- .github/actions/get-pages.sh
- src/gbz80.7
- src/rgbds.5
- src/rgbds.7
- src/asm/rgbasm.1
- src/asm/rgbasm.5
- src/link/rgblink.1
- src/link/rgblink.5
- src/fix/rgbfix.1
- src/gfx/rgbgfx.1
- man/gbz80.7
- man/rgbds.5
- man/rgbds.7
- man/rgbasm.1
- man/rgbasm.5
- man/rgblink.1
- man/rgblink.5
- man/rgbfix.1
- man/rgbgfx.1
jobs:
build:
@@ -36,16 +35,16 @@ jobs:
run: |
sudo apt-get -qq update
sudo apt-get install -yq groff zlib1g-dev
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
tar xf mandoc-1.14.5.tar.gz
cd mandoc-1.14.5
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
tar xf mandoc-1.14.6.tar.gz
cd mandoc-1.14.6
./configure
make
sudo make install
- name: Update pages
working-directory: rgbds
working-directory: rgbds/man
run: |
./.github/actions/get-pages.sh ../rgbds-www master
../../rgbds-www/maintainer/man_to_html.sh master *
- name: Push new pages
working-directory: rgbds-www
run: |
@@ -56,7 +55,7 @@ jobs:
ssh-add ~/.ssh/id_ed25519
git config --global user.name "GitHub Action"
git config --global user.email "community@gbdev.io"
git add .
git add -A
git commit -m "Update RGBDS master documentation"
if git remote | grep -q origin; then
git remote set-url origin git@github.com:gbdev/rgbds-www.git

View File

@@ -10,7 +10,7 @@
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
project(rgbds
LANGUAGES C)
LANGUAGES C CXX)
# get real path of source and binary directories
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
@@ -29,8 +29,18 @@ option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC
if(MSVC)
# MSVC's standard library triggers warning C5105,
# "macro expansion producing 'defined' has undefined behavior"
add_compile_options(/std:c11 /W1 /MP /wd5105)
add_compile_options(/MP /wd5105)
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
# Also, CMake appears not to pass the C11-enabling flag, so we must add it manually... but only for C!
if(NOT CMAKE_C_FLAGS MATCHES "std:c11") # The flag may already have been injected by an earlier CMake invocation, so don't add it twice
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std:c11" CACHE STRING "Flags used by the C compiler during all build types." FORCE)
endif()
if(SANITIZERS)
set(SAN_FLAGS /fsanitize=address)
add_compile_options(${SAN_FLAGS})
add_link_options(${SAN_FLAGS})
endif()
else()
add_compile_options(-Wall -pedantic)
add_definitions(-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE)
@@ -41,18 +51,18 @@ else()
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
-fsanitize=alignment -fsanitize=null -fsanitize=address)
add_compile_options(${SAN_FLAGS})
link_libraries(${SAN_FLAGS})
add_link_options(${SAN_FLAGS})
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
# TODO: this overrides anything previously set... that's a bit sloppy!
set(CMAKE_C_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
endif()
if(MORE_WARNINGS)
add_compile_options(-Werror -Wextra
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
-Wfloat-equal -Winline -Wlogical-op -Wnested-externs -Wnull-dereference
-Wold-style-definition -Wshift-overflow=2 -Wstrict-overflow=5
-Wstrict-prototypes -Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused
-Wshadow # TODO: -Wshadow=compatible-local ?
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
-Wno-format-nonliteral # We have a couple of "dynamic" prints
@@ -77,12 +87,23 @@ else(GIT)
message(STATUS "Cannot determine RGBDS version (Git not installed), falling back")
endif(GIT)
find_package(PkgConfig)
if(MSVC OR NOT PKG_CONFIG_FOUND)
# fallback to find_package
find_package(PNG REQUIRED)
else()
pkg_check_modules(LIBPNG REQUIRED libpng)
endif()
include_directories("${PROJECT_SOURCE_DIR}/include")
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED True)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
add_subdirectory(src)
add_subdirectory(test)
# By default, build in Release mode; Debug mode must be explicitly requested
# (You may want to augment it with the options above)
@@ -97,3 +118,19 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release")
message(CHECK_FAIL "no")
endif()
endif()
set(MANDIR "share/man")
set(man1 "man/rgbasm.1"
"man/rgbfix.1"
"man/rgbgfx.1"
"man/rgblink.1")
set(man5 "man/rgbasm.5"
"man/rgblink.5"
"man/rgbds.5")
set(man7 "man/gbz80.7"
"man/rgbds.7")
foreach(SECTION "man1" "man5" "man7")
set(DEST "${MANDIR}/${SECTION}")
install(FILES ${${SECTION}} DESTINATION ${DEST})
endforeach()

View File

@@ -7,7 +7,7 @@
#
.SUFFIXES:
.SUFFIXES: .h .y .c .o
.SUFFIXES: .h .y .c .cpp .o
# User-defined variables
@@ -34,10 +34,13 @@ WARNFLAGS := -Wall -pedantic
# Overridable CFLAGS
CFLAGS ?= -O3 -flto -DNDEBUG
CXXFLAGS ?= -O3 -flto -DNDEBUG
# Non-overridable CFLAGS
# _ISOC11_SOURCE is required on certain platforms to get C11 on top of the C99-based POSIX 2008
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -I include \
-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++17 -I include \
-D_POSIX_C_SOURCE=200809L -fno-exceptions -fno-rtti
# Overridable LDFLAGS
LDFLAGS ?=
# Non-overridable LDFLAGS
@@ -88,6 +91,7 @@ rgblink_obj := \
src/link/output.o \
src/link/patch.o \
src/link/script.o \
src/link/sdas_obj.o \
src/link/section.o \
src/link/symbol.o \
src/extern/getopt.o \
@@ -102,9 +106,14 @@ rgbfix_obj := \
src/error.o
rgbgfx_obj := \
src/gfx/gb.o \
src/gfx/main.o \
src/gfx/makepng.o \
src/gfx/pal_packing.o \
src/gfx/pal_sorting.o \
src/gfx/pal_spec.o \
src/gfx/process.o \
src/gfx/proto_palette.o \
src/gfx/reverse.o \
src/gfx/rgba.o \
src/extern/getopt.o \
src/error.o
@@ -118,7 +127,13 @@ rgbfix: ${rgbfix_obj}
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCFLAGS} src/version.c
rgbgfx: ${rgbgfx_obj}
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCFLAGS} src/version.c ${PNGLDLIBS}
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} -x c++ src/version.c ${PNGLDLIBS}
test/gfx/randtilegen: test/gfx/randtilegen.c
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
test/gfx/rgbgfx_test: test/gfx/rgbgfx_test.cpp
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGLDLIBS}
# Rules to process files
@@ -145,7 +160,10 @@ src/asm/parser.c: src/asm/parser.y
${BISON} $$DEFS -d ${YFLAGS} -o $@ $<
.c.o:
$Q${CC} ${REALCFLAGS} ${PNGCFLAGS} -c -o $@ $<
$Q${CC} ${REALCFLAGS} -c -o $@ $<
.cpp.o:
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
# Target used to remove all files generated by other Makefile targets
@@ -157,6 +175,7 @@ clean:
$Qfind src/ -name "*.o" -exec rm {} \;
$Q${RM} rgbshim.sh
$Q${RM} src/asm/parser.c src/asm/parser.h
$Q${RM} test/gfx/randtilegen test/gfx/rgbgfx_test
# Target used to install the binaries and man pages.
@@ -167,15 +186,15 @@ install: all
$Qinstall ${STRIP} -m ${BINMODE} rgblink ${DESTDIR}${bindir}/rgblink
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx
$Qmkdir -p ${DESTDIR}${mandir}/man1 ${DESTDIR}${mandir}/man5 ${DESTDIR}${mandir}/man7
$Qinstall -m ${MANMODE} src/rgbds.7 ${DESTDIR}${mandir}/man7/rgbds.7
$Qinstall -m ${MANMODE} src/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
$Qinstall -m ${MANMODE} src/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
$Qinstall -m ${MANMODE} src/asm/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
$Qinstall -m ${MANMODE} src/asm/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
$Qinstall -m ${MANMODE} src/fix/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
$Qinstall -m ${MANMODE} src/link/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
$Qinstall -m ${MANMODE} src/link/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
$Qinstall -m ${MANMODE} src/gfx/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
$Qinstall -m ${MANMODE} man/rgbds.7 ${DESTDIR}${mandir}/man7/rgbds.7
$Qinstall -m ${MANMODE} man/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
$Qinstall -m ${MANMODE} man/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
$Qinstall -m ${MANMODE} man/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
$Qinstall -m ${MANMODE} man/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
$Qinstall -m ${MANMODE} man/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
$Qinstall -m ${MANMODE} man/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
$Qinstall -m ${MANMODE} man/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
$Qinstall -m ${MANMODE} man/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
# Target used to check the coding style of the whole codebase.
# `extern/` is excluded, as it contains external code that should not be patched
@@ -214,11 +233,9 @@ checkdiff:
develop:
$Qenv ${MAKE} WARNFLAGS="-Werror -Wextra \
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
-Wfloat-equal -Winline -Wlogical-op -Wnested-externs -Wold-style-definition \
-Wshift-overflow=2 \
-Wstrict-overflow=5 -Wstrict-prototypes -Wundef -Wuninitialized -Wunused \
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2 \
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused \
-Wshadow \
-Wnull-dereference -Wstringop-overflow=4 \
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
-Wno-format-nonliteral \
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare \
@@ -229,7 +246,8 @@ develop:
-fsanitize=signed-integer-overflow -fsanitize=bounds \
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
# Targets for the project maintainer to easily create Windows exes.
# This is not for Windows users!
@@ -237,12 +255,14 @@ develop:
# install instructions instead.
mingw32:
$Q${MAKE} CC=i686-w64-mingw32-gcc BISON=bison \
PKG_CONFIG=i686-w64-mingw32-pkg-config -j
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ \
BISON=bison PKG_CONFIG=i686-w64-mingw32-pkg-config -j
mingw64:
$Q${MAKE} CC=x86_64-w64-mingw32-gcc BISON=bison \
PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ \
BISON=bison PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
wine-shim:
$Qecho '#!/bin/bash' > rgbshim.sh

View File

@@ -12,11 +12,10 @@ for the Game Boy and Game Boy Color. It consists of:
This is a fork of the original RGBDS which aims to make the programs more like
other UNIX tools.
This toolchain is maintained on `GitHub <https://github.com/rednex/rgbds>`__.
This toolchain is maintained `on GitHub <https://github.com/gbdev/rgbds>`__.
The documentation of this toolchain can be viewed online
`here <https://rgbds.gbdev.io/docs/>`__, it is generated from the man pages
found in this repository.
The documentation of this toolchain can be viewed online `here <https://rgbds.gbdev.io/docs/>`__, it is generated from the man pages found in this repository.
The source code of the website itself is on GitHub as well under the repo `rgbds-www <https://github.com/gbdev/rgbds-www>`__.
If you want to contribute or maintain RGBDS, and have questions regarding the code, its organisation, etc. you can find me `on GBDev <https://gbdev.io/chat>`__ or via mail at ``rgbds at eldred dot fr``.
@@ -24,7 +23,7 @@ If you want to contribute or maintain RGBDS, and have questions regarding the co
-------------------
The `installation procedure <https://rgbds.gbdev.io/install>`__ is available
online for various platforms. `Building from source <https://rgbds.gbdev.io/install/source>`__
online for various platforms. `Building from source <https://rgbds.gbdev.io/install/#building-from-source>`__
is possible using ``make`` or ``cmake``; follow the link for more detailed instructions.
.. code:: sh
@@ -57,6 +56,8 @@ The RGBDS source code file structure somewhat resembles the following:
│ └── ...
├── include/
│ └── ...
├── man/
│ └── ...
├── src/
│ ├── asm/
│ │ └── ...
@@ -96,7 +97,9 @@ The RGBDS source code file structure somewhat resembles the following:
- ``include/`` - header files for each respective C files in `src`.
- ``src/`` - source code and manual pages for RGBDS.
- ``man/`` - manual pages.
- ``src/`` - source code of RGBDS.
* Note that the code unique to each RGBDS tool is stored in its respective subdirectory
(rgbasm -> ``src/asm/``, for example). ``src/extern/`` contains code imported from external sources.
@@ -126,3 +129,11 @@ The RGBDS source code file structure somewhat resembles the following:
- 2018, codebase relicensed under the MIT license.
- 2020, repository is moved to the `gbdev <https://github.com/gbdev>`__ organisation. The `rgbds.gbdev.io <https://rgbds.gbdev.io>`__ website serving documentation and downloads is created.
4. Acknowledgements
-------------------
RGBGFX generates palettes using algorithms found in the paper
`"Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items" <http://arxiv.org/abs/1605.00558>`__
(`GitHub <https://github.com/pagination-problem/pagination>`__, MIT license),
by Aristide Grange, Imed Kacem, and Sébastien Martin.

View File

@@ -1,4 +1,4 @@
#/usr/bin/env bash
#!/usr/bin/env bash
# Known bugs:
# - Newlines in file/directory names break this script
@@ -11,7 +11,7 @@
# - Directories are not completed as such in "coalesced" short-opt arguments. For example,
# `rgbasm -M d<tab>` can autocomplete to `rgbasm -M dir/` (no space), but
# `rgbasm -Md<tab>` would autocomplete to `rgbasm -Mdir ` (trailing space) instead.
# This is because dircetory handling is performed by Readline, whom we can't tell about the short
# This is because directory handling is performed by Readline, whom we can't tell about the short
# opt kerfuffle. The user can work around by separating the argument, as shown above.
# (Also, there might be more possible bugs if `-Mdir` is actually a directory. Ugh.)
@@ -20,16 +20,16 @@
# Thus, we don't need to do much to handle that form of argument passing: skip '=' after long opts.
_rgbasm_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
declare -A opts=(
[V]="version:normal"
[E]="export-all:normal"
[H]="nop-after-halt:normal"
[h]="halt-without-nop:normal"
[L]="preserve-ld:normal"
[l]="auto-ldh:normal"
[v]="verbose:normal"
[w]=":normal"
[b]="binary-digits:unk"
@@ -39,6 +39,7 @@ _rgbasm_completions() {
[M]="dependfile:glob-*.mk *.d"
[o]="output:glob-*.o"
[p]="pad-value:unk"
[Q]="q-precision:unk"
[r]="recursion-depth:unk"
[W]="warning:warning"
)
@@ -58,6 +59,18 @@ _rgbasm_completions() {
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
# part of the argument.
parse_short_opt() {
# These options act like a long option (= takes up the entire word), but only use a single dash
# So, they need some special handling
if [[ "$1" = "-M"[GP] ]]; then
state=normal
optlen=${#1}
return;
elif [[ "$1" = "-M"[QT] ]]; then
state='glob-*.d *.mk *.o'
optlen=${#1}
return;
fi
for (( i = 1; i < "${#1}"; i++ )); do
# If the option is not known, assume it doesn't take an argument
local opt="${opts["${1:$i:1}"]:-":normal"}"
@@ -71,7 +84,7 @@ _rgbasm_completions() {
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
for (( i = 1; i < COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
@@ -87,7 +100,7 @@ _rgbasm_completions() {
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
if [[ "$word" = '--'* ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
@@ -103,15 +116,7 @@ _rgbasm_completions() {
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
# The `-M?` ones are a mix of short and long, augh
# They must match the *full* word, but only take a single dash
# So, handle them here
if [[ "$1" = "-M"[GP] ]]; then
state=normal
elif [[ "$1" = "-M"[TQ] ]]; then
state='glob-*.d *.mk *.o'
else
elif [[ "$word" = '-'* ]]; then
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
@@ -124,34 +129,33 @@ _rgbasm_completions() {
fi
fi
fi
fi
done
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
local cur_word="${COMP_WORDS[$i]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
if [[ "$cur_word" = '--'* ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
return 0
elif [[ "$cur_word" = '-M'[GPQT] ]]; then
# These options act like long opts with no arguments, so return them and exactly them
COMPREPLY=( "$cur_word" )
return 0
else
# Short options may be grouped, parse them to determine what to complete
# The `-M?` ones may not be followed by anything
if [[ "$1" != "-M"[GPTQ] ]]; then
parse_short_opt "$cur_word"
# We got some short options that behave like long ones
COMPREPLY+=( $(compgen -W '-MG -MP -MT -MQ' -- "$cur_word") )
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" ''; compgen -W '-MG -MP -MQ -MT' "$cur_word")
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
@@ -159,18 +163,18 @@ _rgbasm_completions() {
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
COMPREPLY=( "$cur_word" )
return 0
fi
fi
fi
fi
COMPREPLY=()
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
warning)
COMPREPLY+=( $(compgen -W "
mapfile -t COMPREPLY < <(compgen -W "
assert
backwards-for
builtin-args
@@ -188,11 +192,12 @@ _rgbasm_completions() {
shift
shift-amount
truncation
unmapped-char
user
all
extra
everything
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}")
;;
normal) # Acts like a glob...
state="glob-*.asm *.inc *.sm83"
@@ -209,6 +214,10 @@ _rgbasm_completions() {
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
*)
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
return 1
;;
esac
}

View File

@@ -1,10 +1,8 @@
#/usr/bin/env bash
#!/usr/bin/env bash
# Same notes as RGBASM
_rgbfix_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
@@ -54,7 +52,7 @@ _rgbfix_completions() {
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
for (( i = 1; i < COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
@@ -70,7 +68,7 @@ _rgbfix_completions() {
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
if [[ "$word" = '--'* ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
@@ -86,7 +84,7 @@ _rgbfix_completions() {
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
elif [[ "$word" = '-'* ]]; then
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
@@ -103,25 +101,25 @@ _rgbfix_completions() {
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
local cur_word="${COMP_WORDS[$i]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
if [[ "$cur_word" = '--'* ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
return 0
else
# Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
@@ -129,22 +127,25 @@ _rgbfix_completions() {
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
COMPREPLY=( "$cur_word" )
return 0
fi
fi
fi
COMPREPLY=()
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
fix-spec)
COMPREPLY+=( "${cur_word}"{l,h,g,L,H,G} )
COMPREPLY=( "${cur_word}"{l,h,g,L,H,G} )
;;
mbc)
local cur_arg="${cur_word:$optlen}"
cur_arg="${cur_arg@U}"
COMPREPLY=( $(compgen -W "
compopt -o nosort # Keep `help` first in the list, mainly
mapfile -t COMPREPLY < <(compgen -W "help" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}")
mapfile -t COMPREPLY -O ${#COMPREPLY} < <(compgen -W "
ROM_ONLY
MBC1{,+RAM,+RAM+BATTERY}
MBC2{,+BATTERY}
@@ -157,8 +158,7 @@ _rgbfix_completions() {
BANDAI_TAMA5
HUC3
HUC1+RAM+BATTERY
TPP1_1.0{,+BATTERY}{,+RTC}{,+RUMBLE,+MULTIRUMBLE}" -P "${cur_word:0:$optlen}" -- "`tr 'a-z ' 'A-Z_' <<<"${cur_word/ /_}"`") )
COMPREPLY+=( $(compgen -W "help" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
TPP1_1.0{,+BATTERY}{,+RTC}{,+RUMBLE,+MULTIRUMBLE}" -P "${cur_word:0:$optlen}" -- "${cur_word/ /_}")
;;
normal) # Acts like a glob...
state="glob-*.gb *.gbc *.sgb"
@@ -175,6 +175,10 @@ _rgbfix_completions() {
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
*)
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
return 1
;;
esac
}

View File

@@ -1,30 +1,33 @@
#/usr/bin/env bash
#!/usr/bin/env bash
# Same notes as RGBASM
_rgbgfx_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
declare -A opts=(
[V]="version:normal"
[C]="color-curve:normal"
[D]="debug:normal"
[h]="horizontal:normal"
[m]="mirror-tiles:normal"
[u]="unique-tiles:normal"
[v]="verbose:normal"
[f]="fix:normal"
[F]="fix-and-save:normal"
[a]="attr-map:*.attrmap"
[Z]="columns:normal"
[a]="attr-map:glob-*.attrmap"
[A]="output-attr-map:normal"
[b]="base-tiles:unk"
[d]="depth:unk"
[o]="output:glob *.2bpp"
[p]="palette:glob *.pal"
[L]="slice:unk"
[N]="nb-tiles:unk"
[n]="nb-palettes:unk"
[o]="output:glob-*.2bpp"
[p]="palette:glob-*.pal"
[P]="output-palette:normal"
[t]="tilemap:glob *.tilemap"
[q]="palette-map:glob-*.palmap"
[Q]="output-palette-map:normal"
[r]="reverse:unk"
[s]="palette-size:unk"
[t]="tilemap:glob-*.tilemap"
[T]="output-tilemap:normal"
[x]="trim-end:unk"
)
@@ -57,7 +60,7 @@ _rgbgfx_completions() {
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
for (( i = 1; i < COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
@@ -73,7 +76,7 @@ _rgbgfx_completions() {
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
if [[ "$word" = '--'* ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
@@ -89,7 +92,7 @@ _rgbgfx_completions() {
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
elif [[ "$word" = '-'* ]]; then
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
@@ -106,25 +109,25 @@ _rgbgfx_completions() {
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
local cur_word="${COMP_WORDS[$i]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
if [[ "$cur_word" = '--'* ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
return 0
else
# Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
@@ -132,12 +135,13 @@ _rgbgfx_completions() {
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
COMPREPLY=( "$cur_word" )
return 0
fi
fi
fi
COMPREPLY=()
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
@@ -156,6 +160,10 @@ _rgbgfx_completions() {
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
*)
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
return 1
;;
esac
}

View File

@@ -1,10 +1,8 @@
#/usr/bin/env bash
#!/usr/bin/env bash
# Same notes as RGBASM
_rgblink_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
@@ -16,6 +14,7 @@ _rgblink_completions() {
[w]="wramx:normal"
[x]="nopad:normal"
[l]="linkerscript:glob-*"
[M]="no-sym-in-map:normal"
[m]="map:glob-*.map"
[n]="sym:glob-*.sym"
[O]="overlay:glob-*.gb *.gbc *.sgb"
@@ -52,7 +51,7 @@ _rgblink_completions() {
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
for (( i = 1; i < COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
@@ -68,7 +67,7 @@ _rgblink_completions() {
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
if [[ "$word" = '--'* ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
@@ -84,7 +83,7 @@ _rgblink_completions() {
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
elif [[ "$word" = '-'* ]]; then
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
@@ -101,25 +100,25 @@ _rgblink_completions() {
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
local cur_word="${COMP_WORDS[$i]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
if [[ "$cur_word" = '--'* ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
return 0
else
# Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
@@ -127,12 +126,13 @@ _rgblink_completions() {
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
COMPREPLY=( "$cur_word" )
return 0
fi
fi
fi
COMPREPLY=()
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
@@ -151,6 +151,10 @@ _rgblink_completions() {
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
*)
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
return 1
;;
esac
}

View File

@@ -25,7 +25,7 @@
declare -A FILES
while read -r -d '' file; do
FILES["$file"]="true"
done < <(git diff --name-only -z $1 HEAD)
done < <(git diff --name-only -z "$1" HEAD)
edited () {
${FILES["$1"]:-"false"}
@@ -40,13 +40,13 @@ dependency () {
# Pull requests that edit the first file without the second may be correct,
# but are suspicious enough to require review.
dependency include/linkdefs.h src/rgbds.5 \
dependency include/linkdefs.h man/rgbds.5 \
"Was the object file format changed?"
dependency src/asm/parser.y src/asm/rgbasm.5 \
dependency src/asm/parser.y man/rgbasm.5 \
"Was the rgbasm grammar changed?"
dependency include/asm/warning.h src/asm/rgbasm.1 \
dependency include/asm/warning.h man/rgbasm.1 \
"Were the rgbasm warnings changed?"
dependency src/asm/object.c include/linkdefs.h \
@@ -59,27 +59,27 @@ dependency Makefile CMakeLists.txt \
dependency Makefile src/CMakeLists.txt \
"Did the build process change?"
dependency src/asm/main.c src/asm/rgbasm.1 \
dependency src/asm/main.c man/rgbasm.1 \
"Did the rgbasm CLI change?"
dependency src/asm/main.c contrib/zsh_compl/_rgbasm \
"Did the rgbasm CLI change?"
dependency src/asm/main.c contrib/bash_compl/_rgbasm.bash \
"Did the rgbasm CLI change?"
dependency src/link/main.c src/link/rgblink.1 \
dependency src/link/main.c man/rgblink.1 \
"Did the rgblink CLI change?"
dependency src/link/main.c contrib/zsh_compl/_rgblink \
"Did the rgblink CLI change?"
dependency src/link/main.c contrib/bash_compl/_rgblink.bash \
"Did the rgblink CLI change?"
dependency src/fix/main.c src/fix/rgbfix.1 \
dependency src/fix/main.c man/rgbfix.1 \
"Did the rgbfix CLI change?"
dependency src/fix/main.c contrib/zsh_compl/_rgbfix \
"Did the rgbfix CLI change?"
dependency src/fix/main.c contrib/bash_compl/_rgbfix.bash \
"Did the rgbfix CLI change?"
dependency src/gfx/main.c src/gfx/rgbgfx.1 \
dependency src/gfx/main.cpp man/rgbgfx.1 \
"Did the rgbgfx CLI change?"
dependency src/gfx/main.c contrib/zsh_compl/_rgbgfx \
dependency src/gfx/main.cpp contrib/zsh_compl/_rgbgfx \
"Did the rgbgfx CLI change?"
dependency src/gfx/main.c contrib/bash_compl/_rgbgfx.bash \
dependency src/gfx/main.cpp contrib/bash_compl/_rgbgfx.bash \
"Did the rgbgfx CLI change?"

View File

@@ -23,51 +23,51 @@
# SOFTWARE.
STATE=0
diff <(xxd $1) <(xxd $2) | while read -r LINE; do
if [ $STATE -eq 0 ]; then
diff <(xxd "$1") <(xxd "$2") | while read -r LINE; do
if [[ $STATE -eq 0 ]]; then
# Discard first line (line info)
STATE=1
elif [ "$LINE" = '---' ]; then
elif [[ "$LINE" = '---' ]]; then
# Separator between files switches states
echo $LINE
echo "$LINE"
STATE=3
elif grep -Eq '^[0-9]+(,[0-9]+)?[cd][0-9]+(,[0-9]+)?' <<< "$LINE"; then
# Line info resets the whole thing
STATE=1
elif [ $STATE -eq 1 -o $STATE -eq 3 ]; then
elif [[ $STATE -eq 1 || $STATE -eq 3 ]]; then
# Compute the GB address from the ROM offset
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
BANK=$((0x$OFS / 0x4000))
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
ADDR=$((0x$OFS % 0x4000 + (BANK != 0) * 0x4000))
# Try finding the preceding symbol closest to the diff
if [ $STATE -eq 1 ]; then
if [[ $STATE -eq 1 ]]; then
STATE=2
SYMFILE=${1%.*}.sym
else
STATE=4
SYMFILE=${2%.*}.sym
fi
EXTRA=$(if [ -f "$SYMFILE" ]; then
EXTRA=$(if [[ -f "$SYMFILE" ]]; then
# Read the sym file for such a symbol
# Ignore comment lines, only pick matching bank
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
grep -Ei $(printf "^%02x:" $BANK) "$SYMFILE" |
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
cut -d ';' -f 1 |
tr -d "\r" |
while read -r SYMADDR SYM; do
SYMADDR=$((0x${SYMADDR#*:}))
if [ $SYMADDR -le $ADDR ]; then
printf " (%s+%#x)\n" "$SYM" $(($ADDR - $SYMADDR))
if [[ $SYMADDR -le $ADDR ]]; then
printf " (%s+%#x)\n" "$SYM" $((ADDR - SYMADDR))
fi
# TODO: assumes sorted sym files
done | tail -n 1
fi)
printf "%02x:%04x %s\n" $BANK $ADDR $EXTRA
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
fi
if [ $STATE -eq 2 -o $STATE -eq 4 ]; then
if [[ $STATE -eq 2 || $STATE -eq 4 ]]; then
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
BANK=$((0x$OFS / 0x4000))
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
ADDR=$((0x$OFS % 0x4000 + (BANK != 0) * 0x4000))
printf "%s %02x:%04x: %s\n" "${LINE:0:1}" $BANK $ADDR "${LINE#*: }"
fi
done

View File

@@ -25,6 +25,7 @@ _rgbasm_warnings() {
'shift:Warn when shifting negative values'
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
'truncation:Warn when implicit truncation loses bits'
'unmapped-char:Warn on unmapped character'
'user:Warn when executing the WARN built-in'
)
# TODO: handle `no-` and `error=` somehow?
@@ -37,8 +38,10 @@ local args=(
'(- : * options)'{-V,--version}'[Print version number]'
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
'(-h --halt-without-nop)'{-h,--halt-without-nop}'[Avoid outputting a `nop` after `halt`]'
'(-L ---preserve-ld)'{-L,--preserve-ld}'[Prevent auto-optimizing `ld` into `ldh`]'
'(-H --nop-after-halt)'{-H,--nop-after-halt}'[Output a `nop` after `halt`]'
'(-h --halt-without-nop)'{-h,--halt-without-nop}'[Prevent outputting a `nop` after `halt`]'
'(-L --preserve-ld)'{-L,--preserve-ld}'[Prevent optimizing `ld` into `ldh`]'
'(-l --auto-ldh)'{-l,--auto-ldh}'[Optimize `ld` into `ldh`]'
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
-w'[Disable all warnings]'
@@ -53,6 +56,7 @@ local args=(
'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
'(-Q --q-precision)'{-Q,--q-precision}'+[Set fixed-point precision]:precision:'
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'

View File

@@ -15,20 +15,25 @@ local args=(
'(-a --attr-map -A --output-attr-map)'{-A,--output-attr-map}'[Shortcut for -a <file>.attrmap]'
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
'(-D --debug)'{-D,--debug}'[Enable debug features]'
'(-f --fix -F --fix-and-save)'{-f,--fix}'[Fix input PNG into an indexed image]'
'(-f --fix -F --fix-and-save)'{-F,--fix-and-save}'[Like -f but also save CLI params within the PNG]'
'(-h --horizontal)'{-h,--horizontal}'[Lay out tiles horizontally instead of vertically]'
'(-m --mirror-tiles)'{-m,--mirror-tiles}'[Eliminate mirrored tiles from output]'
'(-p --palette -P --output-palette)'{-P,--output-palette}'[Shortcut for -p <file>.pal]'
'(-q --palette-map -Q --output-palette-map)'{-Q,--output-palette-map}'[Shortcut for -p <file>.palmap]'
'(-t --tilemap -T --output-tilemap)'{-T,--output-tilemap}'[Shortcut for -t <file>.tilemap]'
'(-u --unique-tiles)'{-u,--unique-tiles}'[Eliminate redundant tiles]'
'(-v --verbose)'{-v,--verbose}'[Enable verbose output]'
{-v,--verbose}'[Enable verbose output]'
'(-h --horizontal -Z --columns)'{-Z,--columns}'[Read the image in column-major order]'
'(-a --attr-map -A --output-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
'(-L --slice)'{-L,--slice}'+[Only process a portion of the image]:input slice:'
'(-N --nb-tiles)'{-n,--nb-tiles}'+[Limit number of tiles]:tile count:'
'(-n --nb-palettes)'{-n,--nb-palettes}'+[Limit number of palettes]:palette count:'
'(-o --output)'{-o,--output}'+[Set output file]:output file:_files'
'(-p --palette -P --output-palette)'{-p,--palette}"+[Output the image's palette in little-endian native RGB555 format]:palette file:_files"
'(-q --palette-map -Q --output-palette-map)'{-p,--palette-map}"+[Output the image's palette map]:palette map file:_files"
'(-r --reverse)'{-r,--reverse}'+[Yield an image from binary data]:image width (in tiles):'
'(-s --palette-size)'{-s,--palette-size}'+[Limit palette size]:palette size:'
'(-t --tilemap -T --output-tilemap)'{-t,--tilemap}'+[Generate a map of tile indices]:tilemap file:_files'
'(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:'

View File

@@ -11,6 +11,7 @@ local args=(
'(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]'
'(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'"
'(-M --no-sym-in-map)'{-M,--no-sym-in-map}'[Do not output symbol names in map file]'
'(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'"
'(-n --sym)'(-n,--sym)"+[Produce a symbol file]:sym file:_files -g '*.sym'"
'(-O --overlay)'{-O,--overlay}'+[Overlay sections over on top of bin file]:base overlay:_files'

View File

@@ -20,4 +20,4 @@ void charmap_Add(char *mapping, uint8_t value);
size_t charmap_Convert(char const *input, uint8_t *output);
size_t charmap_ConvertNext(char const **input, uint8_t **output);
#endif /* RGBDS_ASM_CHARMAP_H */
#endif // RGBDS_ASM_CHARMAP_H

View File

@@ -11,7 +11,9 @@
#include <stdint.h>
int32_t fix_Callback_PI(void);
extern uint8_t fixPrecision;
double fix_PrecisionFactor(void);
void fix_Print(int32_t i);
int32_t fix_Sin(int32_t i);
int32_t fix_Cos(int32_t i);
@@ -21,6 +23,7 @@ int32_t fix_ACos(int32_t i);
int32_t fix_ATan(int32_t i);
int32_t fix_ATan2(int32_t i, int32_t j);
int32_t fix_Mul(int32_t i, int32_t j);
int32_t fix_Mod(int32_t i, int32_t j);
int32_t fix_Div(int32_t i, int32_t j);
int32_t fix_Pow(int32_t i, int32_t j);
int32_t fix_Log(int32_t i, int32_t j);
@@ -28,4 +31,4 @@ int32_t fix_Round(int32_t i);
int32_t fix_Ceil(int32_t i);
int32_t fix_Floor(int32_t i);
#endif /* RGBDS_ASM_FIXPOINT_H */
#endif // RGBDS_ASM_FIXPOINT_H

View File

@@ -60,4 +60,4 @@ void fmt_FinishCharacters(struct FormatSpec *fmt);
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value);
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value);
#endif /* RGBDS_FORMAT_SPEC_H */
#endif // RGBDS_FORMAT_SPEC_H

View File

@@ -6,9 +6,7 @@
* SPDX-License-Identifier: MIT
*/
/*
* Contains some assembler-wide defines and externs
*/
// Contains some assembler-wide defines and externs
#ifndef RGBDS_ASM_FSTACK_H
#define RGBDS_ASM_FSTACK_H
@@ -21,13 +19,13 @@
struct FileStackNode {
struct FileStackNode *parent; /* Pointer to parent node, for error reporting */
/* Line at which the parent context was exited; meaningless for the root level */
struct FileStackNode *parent; // Pointer to parent node, for error reporting
// Line at which the parent context was exited; meaningless for the root level
uint32_t lineNo;
struct FileStackNode *next; /* Next node in the output linked list */
bool referenced; /* If referenced, don't free! */
uint32_t ID; /* Set only if referenced: ID within the object file, -1 if not output yet */
struct FileStackNode *next; // Next node in the output linked list
bool referenced; // If referenced, don't free!
uint32_t ID; // Set only if referenced: ID within the object file, -1 if not output yet
enum {
NODE_REPT,
@@ -36,18 +34,19 @@ struct FileStackNode {
} type;
};
struct FileStackReptNode { /* NODE_REPT */
struct FileStackReptNode { // NODE_REPT
struct FileStackNode node;
uint32_t reptDepth;
/* WARNING: if changing this type, change overflow check in `fstk_Init` */
uint32_t iters[]; /* REPT iteration counts since last named node, in reverse depth order */
// WARNING: if changing this type, change overflow check in `fstk_Init`
uint32_t iters[]; // REPT iteration counts since last named node, in reverse depth order
};
struct FileStackNamedNode { /* NODE_FILE, NODE_MACRO */
struct FileStackNamedNode { // NODE_FILE, NODE_MACRO
struct FileStackNode node;
char name[]; /* File name for files, file::macro name for macros */
char name[]; // File name for files, file::macro name for macros
};
#define DEFAULT_MAX_DEPTH 64
extern size_t maxRecursionDepth;
struct MacroArgs;
@@ -55,11 +54,11 @@ struct MacroArgs;
void fstk_Dump(struct FileStackNode const *node, uint32_t lineNo);
void fstk_DumpCurrent(void);
struct FileStackNode *fstk_GetFileStack(void);
/* The lifetime of the returned chars is until reaching the end of that file */
// The lifetime of the returned chars is until reaching the end of that file
char const *fstk_GetFileName(void);
void fstk_AddIncludePath(char const *s);
/**
/*
* @param path The user-provided file name
* @param fullPath The address of a pointer, which will be made to point at the full path
* The pointer's value must be a valid argument to `realloc`, including NULL
@@ -80,4 +79,4 @@ bool fstk_Break(void);
void fstk_NewRecursionDepth(size_t newDepth);
void fstk_Init(char const *mainPath, size_t maxDepth);
#endif /* RGBDS_ASM_FSTACK_H */
#endif // RGBDS_ASM_FSTACK_H

View File

@@ -49,9 +49,7 @@ static inline void lexer_SetGfxDigits(char const digits[4])
gfxDigits[3] = digits[3];
}
/*
* `path` is referenced, but not held onto..!
*/
// `path` is referenced, but not held onto..!
struct LexerState *lexer_OpenFile(char const *path);
struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size, uint32_t lineNo);
void lexer_RestartRept(uint32_t lineNo);
@@ -99,4 +97,4 @@ struct DsArgList {
struct Expression *args;
};
#endif /* RGBDS_ASM_LEXER_H */
#endif // RGBDS_ASM_LEXER_H

View File

@@ -31,7 +31,8 @@ uint32_t macro_GetUniqueID(void);
char const *macro_GetUniqueIDStr(void);
void macro_SetUniqueID(uint32_t id);
uint32_t macro_UseNewUniqueID(void);
uint32_t macro_UndefUniqueID(void);
void macro_ShiftCurrentArgs(int32_t count);
uint32_t macro_NbArgs(void);
#endif
#endif // RGBDS_MACRO_H

View File

@@ -16,9 +16,11 @@
#include "helpers.h"
extern bool haltnop;
extern bool warnOnHaltNop;
extern bool optimizeLoads;
extern bool warnOnLdOpt;
extern bool verbose;
extern bool warnings; /* True to enable warnings, false to disable them. */
extern bool warnings; // True to enable warnings, false to disable them.
extern FILE *dependfile;
extern char *targetFileName;
@@ -26,4 +28,4 @@ extern bool generatedMissingIncludes;
extern bool failedOnMissingInclude;
extern bool generatePhonyDeps;
#endif /* RGBDS_MAIN_H */
#endif // RGBDS_MAIN_H

View File

@@ -14,7 +14,8 @@
void opt_B(char const chars[2]);
void opt_G(char const chars[4]);
void opt_P(uint8_t fill);
void opt_P(uint8_t padByte);
void opt_Q(uint8_t precision);
void opt_L(bool optimize);
void opt_W(char const *flag);
void opt_Parse(char const *option);
@@ -22,4 +23,4 @@ void opt_Parse(char const *option);
void opt_Push(void);
void opt_Pop(void);
#endif
#endif // RGBDS_OPT_H

View File

@@ -27,4 +27,4 @@ bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
char const *message, uint32_t ofs);
void out_WriteObject(void);
#endif /* RGBDS_ASM_OUTPUT_H */
#endif // RGBDS_ASM_OUTPUT_H

View File

@@ -27,17 +27,13 @@ struct Expression {
uint32_t rpnPatchSize; // Size the expression will take in the object file
};
/*
* Determines if an expression is known at assembly time
*/
// Determines if an expression is known at assembly time
static inline bool rpn_isKnown(struct Expression const *expr)
{
return expr->isKnown;
}
/*
* Determines if an expression is a symbol suitable for const diffing
*/
// Determines if an expression is a symbol suitable for const diffing
static inline bool rpn_isSymbol(const struct Expression *expr)
{
return expr->isSymbol;
@@ -67,4 +63,4 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src);
void rpn_CheckNBit(struct Expression const *expr, uint8_t n);
int32_t rpn_GetConstVal(struct Expression const *expr);
#endif /* RGBDS_ASM_RPN_H */
#endif // RGBDS_ASM_RPN_H

View File

@@ -23,8 +23,8 @@ struct Section {
char *name;
enum SectionType type;
enum SectionModifier modifier;
struct FileStackNode *src; /* Where the section was defined */
uint32_t fileLine; /* Line where the section was defined */
struct FileStackNode *src; // Where the section was defined
uint32_t fileLine; // Line where the section was defined
uint32_t size;
uint32_t org;
uint32_t bank;
@@ -79,4 +79,4 @@ void sect_PopSection(void);
bool sect_IsSizeKnown(struct Section const NONNULL(name));
#endif
#endif // RGBDS_SECTION_H

View File

@@ -32,28 +32,28 @@ enum SymbolType {
struct Symbol {
char name[MAXSYMLEN + 1];
enum SymbolType type;
bool isExported; /* Whether the symbol is to be exported */
bool isBuiltin; /* Whether the symbol is a built-in */
bool isExported; // Whether the symbol is to be exported
bool isBuiltin; // Whether the symbol is a built-in
struct Section *section;
struct FileStackNode *src; /* Where the symbol was defined */
uint32_t fileLine; /* Line where the symbol was defined */
struct FileStackNode *src; // Where the symbol was defined
uint32_t fileLine; // Line where the symbol was defined
bool hasCallback;
union {
/* If sym_IsNumeric */
// If sym_IsNumeric
int32_t value;
int32_t (*numCallback)(void);
/* For SYM_MACRO and SYM_EQUS; TODO: have separate fields */
// For SYM_MACRO and SYM_EQUS; TODO: have separate fields
struct {
size_t macroSize;
char *macro;
};
/* For SYM_EQUS */
// For SYM_EQUS
char const *(*strCallback)(void);
};
uint32_t ID; /* ID of the symbol in the object file (-1 if none) */
struct Symbol *next; /* Next object to output in the object file */
uint32_t ID; // ID of the symbol in the object file (-1 if none)
struct Symbol *next; // Next object to output in the object file
};
bool sym_IsPC(struct Symbol const *sym);
@@ -98,9 +98,7 @@ static inline bool sym_IsExported(struct Symbol const *sym)
return sym->isExported;
}
/*
* Get a string equate's value
*/
// Get a string equate's value
static inline char const *sym_GetStringValue(struct Symbol const *sym)
{
if (sym->hasCallback)
@@ -123,17 +121,11 @@ struct Symbol *sym_AddVar(char const *symName, int32_t value);
uint32_t sym_GetPCValue(void);
uint32_t sym_GetConstantSymValue(struct Symbol const *sym);
uint32_t sym_GetConstantValue(char const *symName);
/*
* Find a symbol by exact name, bypassing expansion checks
*/
// Find a symbol by exact name, bypassing expansion checks
struct Symbol *sym_FindExactSymbol(char const *symName);
/*
* Find a symbol by exact name; may not be scoped, produces an error if it is
*/
// Find a symbol by exact name; may not be scoped, produces an error if it is
struct Symbol *sym_FindUnscopedSymbol(char const *symName);
/*
* Find a symbol, possibly scoped, by name
*/
// Find a symbol, possibly scoped, by name
struct Symbol *sym_FindScopedSymbol(char const *symName);
struct Symbol const *sym_GetPC(void);
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
@@ -143,8 +135,8 @@ struct Symbol *sym_RedefString(char const *symName, char const *value);
void sym_Purge(char const *symName);
void sym_Init(time_t now);
/* Functions to save and restore the current symbol scope. */
// Functions to save and restore the current symbol scope.
char const *sym_GetCurrentSymbolScope(void);
void sym_SetCurrentSymbolScope(char const *newScope);
#endif /* RGBDS_SYMBOL_H */
#endif // RGBDS_SYMBOL_H

View File

@@ -18,4 +18,4 @@ char const *printChar(int c);
*/
size_t readUTF8Char(uint8_t *dest, char const *src);
#endif /* RGBDS_UTIL_H */
#endif // RGBDS_UTIL_H

View File

@@ -36,6 +36,7 @@ enum WarningID {
WARNING_OBSOLETE, // Obsolete things
WARNING_SHIFT, // Shifting undefined behavior
WARNING_SHIFT_AMOUNT, // Strange shift amount
WARNING_UNMAPPED_CHAR, // Character without charmap entry
WARNING_USER, // User warnings
NB_PLAIN_WARNINGS,
@@ -90,4 +91,4 @@ _Noreturn void fatalerror(char const *fmt, ...) format_(printf, 1, 2);
*/
void error(char const *fmt, ...) format_(printf, 1, 2);
#endif
#endif // WARNING_H

View File

@@ -0,0 +1,39 @@
/*
* Allocator adaptor that interposes construct() calls to convert value-initialization
* (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
* zero out non-class types).
* From
* https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
*/
#ifndef DEFAULT_INIT_ALLOC_H
#define DEFAULT_INIT_ALLOC_H
#include <memory>
#include <vector>
template<typename T, typename A = std::allocator<T>>
class default_init_allocator : public A {
using a_t = std::allocator_traits<A>;
public:
template<typename U>
struct rebind {
using other = default_init_allocator<U, typename a_t::template rebind_alloc<U>>;
};
using A::A; // Inherit the allocator's constructors
template<typename U>
void construct(U *ptr) noexcept(std::is_nothrow_default_constructible_v<U>) {
::new (static_cast<void *>(ptr)) U;
}
template<typename U, typename... Args>
void construct(U *ptr, Args &&...args) {
a_t::construct(static_cast<A &>(*this), ptr, std::forward<Args>(args)...);
}
};
template<typename T>
using DefaultInitVec = std::vector<T, default_init_allocator<T>>;
#endif // DEFAULT_INIT_ALLOC_H

View File

@@ -12,10 +12,18 @@
#include "helpers.h"
#include "platform.h"
#ifdef __cplusplus
extern "C" {
#endif
void warn(char const NONNULL(fmt), ...) format_(printf, 1, 2);
void warnx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
_Noreturn void err(char const NONNULL(fmt), ...) format_(printf, 1, 2);
_Noreturn void errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
#endif /* RGBDS_ERROR_H */
#ifdef __cplusplus
}
#endif
#endif // RGBDS_ERROR_H

View File

@@ -26,6 +26,10 @@
#ifndef RGBDS_EXTERN_GETOPT_H
#define RGBDS_EXTERN_GETOPT_H
#ifdef __cplusplus
extern "C" {
#endif
extern char *musl_optarg;
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
@@ -43,4 +47,8 @@ int musl_getopt_long_only(int argc, char **argv, char const *optstring,
#define required_argument 1
#define optional_argument 2
#ifdef __cplusplus
} // extern "C"
#endif
#endif

View File

@@ -11,4 +11,4 @@
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte);
#endif /* EXTERN_UTF8DECODER_H */
#endif // EXTERN_UTF8DECODER_H

View File

@@ -1,36 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_GFX_GB_H
#define RGBDS_GFX_GB_H
#include <stdint.h>
#include "gfx/main.h"
#define XFLIP 0x40
#define YFLIP 0x20
void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb);
void output_file(const struct Options *opts, const struct GBImage *gb);
int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
int tile_size);
uint8_t reverse_bits(uint8_t b);
void xflip(uint8_t *tile, uint8_t *tile_xflip, int tile_size);
void yflip(uint8_t *tile, uint8_t *tile_yflip, int tile_size);
int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
int tile_size, int *flags);
void create_mapfiles(const struct Options *opts, struct GBImage *gb,
struct Mapfile *tilemap, struct Mapfile *attrmap);
void output_tilemap_file(const struct Options *opts,
const struct Mapfile *tilemap);
void output_attrmap_file(const struct Options *opts,
const struct Mapfile *attrmap);
void output_palette_file(const struct Options *opts,
const struct RawIndexedImage *raw_image);
#endif

View File

@@ -1,91 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_GFX_MAIN_H
#define RGBDS_GFX_MAIN_H
#include <png.h>
#include <stdbool.h>
#include <stdint.h>
#include "error.h"
struct Options {
bool debug;
bool verbose;
bool hardfix;
bool fix;
bool horizontal;
bool mirror;
bool unique;
bool colorcurve;
unsigned int trim;
char *tilemapfile;
bool tilemapout;
char *attrmapfile;
bool attrmapout;
char *palfile;
bool palout;
char *outfile;
char *infile;
};
struct RGBColor {
uint8_t red;
uint8_t green;
uint8_t blue;
};
struct ImageOptions {
bool horizontal;
unsigned int trim;
char *tilemapfile;
bool tilemapout;
char *attrmapfile;
bool attrmapout;
char *palfile;
bool palout;
};
struct PNGImage {
png_struct *png;
png_info *info;
png_byte **data;
int width;
int height;
png_byte depth;
png_byte type;
};
struct RawIndexedImage {
uint8_t **data;
struct RGBColor *palette;
int num_colors;
unsigned int width;
unsigned int height;
};
struct GBImage {
uint8_t *data;
int size;
bool horizontal;
int trim;
};
struct Mapfile {
uint8_t *data;
int size;
};
extern int depth, colors;
#include "gfx/makepng.h"
#include "gfx/gb.h"
#endif /* RGBDS_GFX_MAIN_H */

123
include/gfx/main.hpp Normal file
View File

@@ -0,0 +1,123 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_GFX_MAIN_HPP
#define RGBDS_GFX_MAIN_HPP
#include <array>
#include <limits.h>
#include <stdint.h>
#include <string>
#include <utility>
#include <vector>
#include "helpers.h"
#include "gfx/rgba.hpp"
struct Options {
uint16_t reversedWidth = 0; // -r, in tiles
bool reverse() const { return reversedWidth != 0; }
bool useColorCurve = false; // -C
bool allowMirroring = false; // -m
bool allowDedup = false; // -u
bool columnMajor = false; // -Z, previously -h
uint8_t verbosity = 0; // -v
std::string attrmap{}; // -a, -A
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
enum {
NO_SPEC,
EXPLICIT,
EMBEDDED,
} palSpecType = NO_SPEC; // -c
std::vector<std::array<Rgba, 4>> palSpec{};
uint8_t bitDepth = 2; // -d
struct {
uint16_t left;
uint16_t top;
uint16_t width;
uint16_t height;
} inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS)
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
uint8_t nbPalettes = 8; // -n
std::string output{}; // -o
std::string palettes{}; // -p, -P
std::string palmap{}; // -q, -Q
uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth;
std::string tilemap{}; // -t, -T
uint64_t trim = 0; // -x
std::string input{}; // positional arg
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
format_(printf, 3, 4) void verbosePrint(uint8_t level, char const *fmt, ...) const;
mutable bool hasTransparentPixels = false;
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
};
extern Options options;
/*
* Prints the error count, and exits with failure
*/
[[noreturn]] void giveUp();
/*
* Prints a warning, and does not change the error count
*/
void warning(char const *fmt, ...);
/*
* Prints an error, and increments the error count
*/
void error(char const *fmt, ...);
/*
* Prints a fatal error, increments the error count, and gives up
*/
[[noreturn]] void fatal(char const *fmt, ...);
struct Palette {
// An array of 4 GBC-native (RGB555) colors
std::array<uint16_t, 4> colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
void addColor(uint16_t color);
uint8_t indexOf(uint16_t color) const;
uint16_t &operator[](size_t index) { return colors[index]; }
uint16_t const &operator[](size_t index) const { return colors[index]; }
decltype(colors)::iterator begin();
decltype(colors)::iterator end();
decltype(colors)::const_iterator begin() const;
decltype(colors)::const_iterator end() const;
uint8_t size() const;
};
namespace detail {
template<typename T, T... i>
static constexpr auto flipTable(std::integer_sequence<T, i...>) {
return std::array{[](uint8_t byte) {
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
return byte;
}(i)...};
}
}
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
static constexpr auto flipTable = detail::flipTable(std::make_integer_sequence<uint16_t, 256>());
#endif // RGBDS_GFX_MAIN_HPP

View File

@@ -1,21 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_GFX_PNG_H
#define RGBDS_GFX_PNG_H
#include "gfx/main.h"
struct RawIndexedImage *input_png_file(const struct Options *opts,
struct ImageOptions *png_options);
void output_png_file(const struct Options *opts,
const struct ImageOptions *png_options,
const struct RawIndexedImage *raw_image);
void destroy_raw_image(struct RawIndexedImage **raw_image_ptr_ptr);
#endif /* RGBDS_GFX_PNG_H */

View File

@@ -0,0 +1,32 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_GFX_PAL_PACKING_HPP
#define RGBDS_GFX_PAL_PACKING_HPP
#include <tuple>
#include <vector>
#include "defaultinitalloc.hpp"
#include "gfx/main.hpp"
struct Palette;
class ProtoPalette;
namespace packing {
/*
* Returns which palette each proto-palette maps to, and how many palettes are necessary
*/
std::tuple<DefaultInitVec<size_t>, size_t>
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
}
#endif // RGBDS_GFX_PAL_PACKING_HPP

View File

@@ -0,0 +1,32 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_GFX_PAL_SORTING_HPP
#define RGBDS_GFX_PAL_SORTING_HPP
#include <array>
#include <assert.h>
#include <optional>
#include <png.h>
#include <vector>
#include "gfx/rgba.hpp"
struct Palette;
namespace sorting {
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
png_byte *palAlpha);
void grayscale(std::vector<Palette> &palettes,
std::array<std::optional<Rgba>, 0x8001> const &colors);
void rgb(std::vector<Palette> &palettes);
}
#endif // RGBDS_GFX_PAL_SORTING_HPP

15
include/gfx/pal_spec.hpp Normal file
View File

@@ -0,0 +1,15 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_GFX_PAL_SPEC_HPP
#define RGBDS_GFX_PAL_SPEC_HPP
void parseInlinePalSpec(char const * const arg);
void parseExternalPalSpec(char const *arg);
#endif // RGBDS_GFX_PAL_SPEC_HPP

14
include/gfx/process.hpp Normal file
View File

@@ -0,0 +1,14 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_GFX_CONVERT_HPP
#define RGBDS_GFX_CONVERT_HPP
void process();
#endif // RGBDS_GFX_CONVERT_HPP

View File

@@ -0,0 +1,44 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
#define RGBDS_GFX_PROTO_PALETTE_HPP
#include <algorithm>
#include <array>
#include <stddef.h>
#include <stdint.h>
class ProtoPalette {
// Up to 4 colors, sorted, and where SIZE_MAX means the slot is empty
// (OK because it's not a valid color index)
// Sorting is done on the raw numerical values to lessen `compare`'s complexity
std::array<uint16_t, 4> _colorIndices{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
public:
/*
* Adds the specified color to the set
* Returns false if the set is full
*/
bool add(uint16_t color);
enum ComparisonResult {
NEITHER,
WE_BIGGER,
THEY_BIGGER = -1,
};
ComparisonResult compare(ProtoPalette const &other) const;
size_t size() const;
bool empty() const;
decltype(_colorIndices)::const_iterator begin() const;
decltype(_colorIndices)::const_iterator end() const;
};
#endif // RGBDS_GFX_PROTO_PALETTE_HPP

14
include/gfx/reverse.hpp Normal file
View File

@@ -0,0 +1,14 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_GFX_REVERSE_HPP
#define RGBDS_GFX_REVERSE_HPP
void reverse();
#endif // RGBDS_GFX_REVERSE_HPP

67
include/gfx/rgba.hpp Normal file
View File

@@ -0,0 +1,67 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_GFX_RGBA_HPP
#define RGBDS_GFX_RGBA_HPP
#include <cstdint>
#include <stdint.h>
struct Rgba {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
: red(r), green(g), blue(b), alpha(a) {}
/*
* Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
*/
explicit constexpr Rgba(uint32_t rgba = 0)
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
static constexpr Rgba fromCGBColor(uint16_t cgbColor) {
constexpr auto _5to8 = [](uint8_t fiveBpp) -> uint8_t {
fiveBpp &= 0b11111; // For caller's convenience
return fiveBpp << 3 | fiveBpp >> 2;
};
return {_5to8(cgbColor), _5to8(cgbColor >> 5), _5to8(cgbColor >> 10),
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF)};
}
/*
* Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
* representation
*/
uint32_t toCSS() const {
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
}
friend bool operator!=(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() != rhs.toCSS(); }
/*
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
* Since the rest of the bits don't matter then, we return 0x8000 exactly.
*/
static constexpr uint16_t transparent = 0b1'00000'00000'00000;
static constexpr uint8_t transparency_threshold = 0x10;
bool isTransparent() const { return alpha < transparency_threshold; }
static constexpr uint8_t opacity_threshold = 0xF0;
bool isOpaque() const { return alpha >= opacity_threshold; }
/*
* Computes the equivalent CGB color, respects the color curve depending on options
*/
uint16_t cgbColor() const;
bool isGray() const { return red == green && green == blue; }
uint8_t grayIndex() const;
};
#endif // RGBDS_GFX_RGBA_HPP

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT
*/
/* Generic hashmap implementation (C++ templates are calling...) */
// Generic hashmap implementation (C++ templates are calling...)
#ifndef RGBDS_LINK_HASHMAP_H
#define RGBDS_LINK_HASHMAP_H
@@ -18,10 +18,10 @@
static_assert(HALF_HASH_NB_BITS * 2 == HASH_NB_BITS, "");
#define HASHMAP_NB_BUCKETS (1 << HALF_HASH_NB_BITS)
/* HashMapEntry is internal, please do not attempt to use it */
// HashMapEntry is internal, please do not attempt to use it
typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
/**
/*
* Adds an element to a hashmap.
* @warning Adding a new element with an already-present key will not cause an
* error, this must be handled externally.
@@ -33,7 +33,7 @@ typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
*/
void **hash_AddElement(HashMap map, char const *key, void *element);
/**
/*
* Removes an element from a hashmap.
* @param map The HashMap to remove the element from
* @param key The key to search the element with
@@ -41,7 +41,7 @@ void **hash_AddElement(HashMap map, char const *key, void *element);
*/
bool hash_RemoveElement(HashMap map, char const *key);
/**
/*
* Finds an element in a hashmap, and returns a pointer to its value field.
* @param map The map to consider the elements of
* @param key The key to search an element for
@@ -49,7 +49,7 @@ bool hash_RemoveElement(HashMap map, char const *key);
*/
void **hash_GetNode(HashMap const map, char const *key);
/**
/*
* Finds an element in a hashmap.
* @param map The map to consider the elements of
* @param key The key to search an element for
@@ -58,7 +58,7 @@ void **hash_GetNode(HashMap const map, char const *key);
*/
void *hash_GetElement(HashMap const map, char const *key);
/**
/*
* Executes a function on each element in a hashmap.
* @param map The map to consider the elements of
* @param func The function to run. The first argument will be the element,
@@ -67,11 +67,11 @@ void *hash_GetElement(HashMap const map, char const *key);
*/
void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg);
/**
/*
* Cleanly empties a hashmap from its contents.
* This does not `free` the data structure itself!
* @param map The map to empty
*/
void hash_EmptyMap(HashMap map);
#endif /* RGBDS_LINK_HASHMAP_H */
#endif // RGBDS_LINK_HASHMAP_H

View File

@@ -31,7 +31,7 @@
#define attr_(...)
// This seems to generate similar code to __builtin_unreachable, despite different semantics
// Note that executing this is undefined behavior (declared _Noreturn, but does return)
static inline _Noreturn unreachable_(void) {}
static inline _Noreturn void unreachable_(void) {}
#endif
// Use builtins whenever possible, and shim them otherwise
@@ -93,4 +93,4 @@
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
#endif /* HELPERS_H */
#endif // HELPERS_H

88
include/itertools.hpp Normal file
View File

@@ -0,0 +1,88 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_ITERTOOLS_HPP
#define RGBDS_ITERTOOLS_HPP
#include <tuple>
#include <utility>
template<typename... Ts>
static inline void report() {
puts(__PRETTY_FUNCTION__);
}
// This is not a fully generic implementation; its current use cases only require for-loop behavior.
// We also assume that all iterators have the same length.
template<typename... Iters>
class Zip {
std::tuple<Iters...> _iters;
public:
explicit Zip(std::tuple<Iters...> &&iters) : _iters(iters) {}
Zip &operator++() {
std::apply([](auto &&...it) { (++it, ...); }, _iters);
return *this;
}
auto operator*() const {
return std::apply([](auto &&...it) { return std::tuple<decltype(*it)...>(*it...); },
_iters);
}
friend auto operator==(Zip const &lhs, Zip const &rhs) {
return std::get<0>(lhs._iters) == std::get<0>(rhs._iters);
}
friend auto operator!=(Zip const &lhs, Zip const &rhs) {
return std::get<0>(lhs._iters) != std::get<0>(rhs._iters);
}
};
namespace detail {
template<typename... Containers>
class ZipContainer {
std::tuple<Containers...> _containers;
public:
ZipContainer(Containers &&...containers)
: _containers(std::forward<Containers>(containers)...) {}
auto begin() {
return Zip(std::apply(
[](auto &&...containers) {
using std::begin;
return std::make_tuple(begin(containers)...);
},
_containers));
}
auto end() {
return Zip(std::apply(
[](auto &&...containers) {
using std::end;
return std::make_tuple(end(containers)...);
},
_containers));
}
};
// Take ownership of objects and rvalue refs passed to us, but not lvalue refs
template<typename T>
using Holder = std::conditional_t<std::is_lvalue_reference_v<T>, T,
std::remove_cv_t<std::remove_reference_t<T>>>;
}
// Does the same number of iterations as the first container's iterator!
template<typename... Containers>
static constexpr auto zip(Containers &&...cs) {
return detail::ZipContainer<detail::Holder<Containers>...>(std::forward<Containers>(cs)...);
}
#endif // RGBDS_ITERTOOLS_HPP

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT
*/
/* Assigning all sections a place */
// Assigning all sections a place
#ifndef RGBDS_LINK_ASSIGN_H
#define RGBDS_LINK_ASSIGN_H
@@ -14,14 +14,10 @@
extern uint64_t nbSectionsToAssign;
/**
* Assigns all sections a slice of the address space
*/
// Assigns all sections a slice of the address space
void assign_AssignSections(void);
/**
* `free`s all assignment memory that was allocated.
*/
// `free`s all assignment memory that was allocated
void assign_Cleanup(void);
#endif /* RGBDS_LINK_ASSIGN_H */
#endif // RGBDS_LINK_ASSIGN_H

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT
*/
/* Declarations that all modules use, as well as `main` and related */
// Declarations that all modules use, as well as `main` and related
#ifndef RGBDS_LINK_MAIN_H
#define RGBDS_LINK_MAIN_H
@@ -16,10 +16,11 @@
#include "helpers.h"
/* Variables related to CLI options */
// Variables related to CLI options
extern bool isDmgMode;
extern char *linkerScriptName;
extern char const *mapFileName;
extern bool noSymInMap;
extern char const *symFileName;
extern char const *overlayFileName;
extern char const *outputFileName;
@@ -34,7 +35,7 @@ extern bool disablePadding;
struct FileStackNode {
struct FileStackNode *parent;
/* Line at which the parent context was exited; meaningless for the root level */
// Line at which the parent context was exited; meaningless for the root level
uint32_t lineNo;
enum {
@@ -43,21 +44,21 @@ struct FileStackNode {
NODE_MACRO,
} type;
union {
char *name; /* NODE_FILE, NODE_MACRO */
struct { /* NODE_REPT */
char *name; // NODE_FILE, NODE_MACRO
struct { // NODE_REPT
uint32_t reptDepth;
uint32_t *iters;
};
};
};
/* Helper macro for printing verbose-mode messages */
// Helper macro for printing verbose-mode messages
#define verbosePrint(...) do { \
if (beVerbose) \
fprintf(stderr, __VA_ARGS__); \
} while (0)
/**
/*
* Dump a file stack to stderr
* @param node The leaf node to dump the context of
*/
@@ -72,7 +73,7 @@ void error(struct FileStackNode const *where, uint32_t lineNo,
_Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo,
char const *fmt, ...) format_(printf, 3, 4);
/**
/*
* Opens a file if specified, and aborts on error.
* @param fileName The name of the file to open; if NULL, no file will be opened
* @param mode The mode to open the file with
@@ -86,4 +87,4 @@ FILE *openFile(char const *fileName, char const *mode);
fclose(tmp); \
} while (0)
#endif /* RGBDS_LINK_MAIN_H */
#endif // RGBDS_LINK_MAIN_H

View File

@@ -6,37 +6,37 @@
* SPDX-License-Identifier: MIT
*/
/* Declarations related to processing of object (.o) files */
// Declarations related to processing of object (.o) files
#ifndef RGBDS_LINK_OBJECT_H
#define RGBDS_LINK_OBJECT_H
/**
/*
* Read an object (.o) file, and add its info to the data structures.
* @param fileName A path to the object file to be read
* @param i The ID of the file
*/
void obj_ReadFile(char const *fileName, unsigned int i);
/**
/*
* Perform validation on the object files' contents
*/
void obj_DoSanityChecks(void);
/**
/*
* Evaluate all assertions
*/
void obj_CheckAssertions(void);
/**
/*
* Sets up object file reading
* @param nbFiles The number of object files that will be read
*/
void obj_Setup(unsigned int nbFiles);
/**
/*
* `free`s all object memory that was allocated.
*/
void obj_Cleanup(void);
#endif /* RGBDS_LINK_OBJECT_H */
#endif // RGBDS_LINK_OBJECT_H

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT
*/
/* Outputting the result of linking */
// Outputting the result of linking
#ifndef RGBDS_LINK_OUTPUT_H
#define RGBDS_LINK_OUTPUT_H
@@ -14,22 +14,22 @@
#include "link/section.h"
/**
/*
* Registers a section for output.
* @param section The section to add
*/
void out_AddSection(struct Section const *section);
/**
/*
* Finds an assigned section overlapping another one.
* @param section The section that is being overlapped
* @return A section overlapping it
*/
struct Section const *out_OverlappingSection(struct Section const *section);
/**
/*
* Writes all output (bin, sym, map) files.
*/
void out_WriteFiles(void);
#endif /* RGBDS_LINK_OUTPUT_H */
#endif // RGBDS_LINK_OUTPUT_H

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT
*/
/* Applying patches to SECTIONs */
// Applying patches to SECTIONs
#ifndef RGBDS_LINK_PATCH_H
#define RGBDS_LINK_PATCH_H
@@ -27,15 +27,15 @@ struct Assertion {
struct Assertion *next;
};
/**
/*
* Checks all assertions
* @return true if assertion failed
*/
void patch_CheckAssertions(struct Assertion *assertion);
/**
/*
* Applies all SECTIONs' patches to them
*/
void patch_ApplyPatches(void);
#endif /* RGBDS_LINK_PATCH_H */
#endif // RGBDS_LINK_PATCH_H

View File

@@ -6,31 +6,33 @@
* SPDX-License-Identifier: MIT
*/
/* Parsing a linker script */
// Parsing a linker script
#ifndef RGBDS_LINK_SCRIPT_H
#define RGBDS_LINK_SCRIPT_H
#include <stdint.h>
#include "linkdefs.h"
extern FILE * linkerScript;
struct SectionPlacement {
struct Section *section;
enum SectionType type;
uint16_t org;
uint32_t bank;
};
extern uint64_t script_lineNo;
/**
/*
* Parses the linker script to return the next section constraint
* @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed
*/
struct SectionPlacement *script_NextSection(void);
/**
/*
* `free`s all assignment memory that was allocated.
*/
void script_Cleanup(void);
#endif /* RGBDS_LINK_SCRIPT_H */
#endif // RGBDS_LINK_SCRIPT_H

19
include/link/sdas_obj.h Normal file
View File

@@ -0,0 +1,19 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
// Assigning all sections a place
#ifndef RGBDS_LINK_SDAS_OBJ_H
#define RGBDS_LINK_SDAS_OBJ_H
#include <stdio.h>
struct FileStackNode;
void sdobj_ReadFile(struct FileStackNode const *fileName, FILE *file);
#endif // RGBDS_LINK_SDAS_OBJ_H

View File

@@ -6,11 +6,11 @@
* SPDX-License-Identifier: MIT
*/
/* Declarations manipulating symbols */
// Declarations manipulating symbols
#ifndef RGBDS_LINK_SECTION_H
#define RGBDS_LINK_SECTION_H
/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */
// GUIDELINE: external code MUST NOT BE AWARE of the data structure used!!
#include <stdint.h>
#include <stdbool.h>
@@ -41,27 +41,29 @@ struct Patch {
};
struct Section {
/* Info contained in the object files */
// Info contained in the object files
char *name;
uint16_t size;
uint16_t offset;
enum SectionType type;
enum SectionModifier modifier;
bool isAddressFixed;
// This `struct`'s address in ROM.
// Importantly for fragments, this does not include `offset`!
uint16_t org;
bool isBankFixed;
uint32_t bank;
bool isAlignFixed;
uint16_t alignMask;
uint16_t alignOfs;
uint8_t *data; /* Array of size `size`*/
uint8_t *data; // Array of size `size`
uint32_t nbPatches;
struct Patch *patches;
/* Extra info computed during linking */
// Extra info computed during linking
struct Symbol **fileSymbols;
uint32_t nbSymbols;
struct Symbol const **symbols;
struct Section *nextu; /* The next "component" of this unionized sect */
struct Symbol **symbols;
struct Section *nextu; // The next "component" of this unionized sect
};
/*
@@ -74,27 +76,27 @@ struct Section {
*/
void sect_ForEach(void (*callback)(struct Section *, void *), void *arg);
/**
/*
* Registers a section to be processed.
* @param section The section to register.
*/
void sect_AddSection(struct Section *section);
/**
/*
* Finds a section by its name.
* @param name The name of the section to look for
* @return A pointer to the section, or NULL if it wasn't found
*/
struct Section *sect_GetSection(char const *name);
/**
/*
* `free`s all section memory that was allocated.
*/
void sect_CleanupSections(void);
/**
/*
* Checks if all sections meet reasonable criteria, such as max size
*/
void sect_DoSanityChecks(void);
#endif /* RGBDS_LINK_SECTION_H */
#endif // RGBDS_LINK_SECTION_H

View File

@@ -6,11 +6,11 @@
* SPDX-License-Identifier: MIT
*/
/* Declarations manipulating symbols */
// Declarations manipulating symbols
#ifndef RGBDS_LINK_SYMBOL_H
#define RGBDS_LINK_SYMBOL_H
/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */
// GUIDELINE: external code MUST NOT BE AWARE of the data structure used!!
#include <stdint.h>
@@ -19,7 +19,7 @@
struct FileStackNode;
struct Symbol {
/* Info contained in the object files */
// Info contained in the object files
char *name;
enum ExportLevel type;
char const *objFileName;
@@ -27,10 +27,11 @@ struct Symbol {
int32_t lineNo;
int32_t sectionID;
union {
// Both types must be identical
int32_t offset;
int32_t value;
};
/* Extra info computed during linking */
// Extra info computed during linking
struct Section *section;
};
@@ -46,16 +47,16 @@ void sym_ForEach(void (*callback)(struct Symbol *, void *), void *arg);
void sym_AddSymbol(struct Symbol *symbol);
/**
/*
* Finds a symbol in all the defined symbols.
* @param name The name of the symbol to look for
* @return A pointer to the symbol, or NULL if not found.
*/
struct Symbol *sym_GetSymbol(char const *name);
/**
/*
* `free`s all symbol memory that was allocated.
*/
void sym_CleanupSymbols(void);
#endif /* RGBDS_LINK_SYMBOL_H */
#endif // RGBDS_LINK_SYMBOL_H

View File

@@ -9,11 +9,11 @@
#ifndef RGBDS_LINKDEFS_H
#define RGBDS_LINKDEFS_H
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
#define RGBDS_OBJECT_VERSION_NUMBER 9U
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
#define RGBDS_OBJECT_REV 9U
enum AssertionType {
@@ -74,9 +74,50 @@ enum SectionType {
SECTTYPE_SRAM,
SECTTYPE_OAM,
// In RGBLINK, this is used for "indeterminate" sections; this is primarily for SDCC
// areas, which do not carry any section type info and must be told from the linker script
SECTTYPE_INVALID
};
// Nont-`const` members may be patched in RGBLINK depending on CLI flags
extern struct SectionTypeInfo {
char const *const name;
uint16_t const startAddr;
uint16_t size;
uint32_t const firstBank;
uint32_t lastBank;
} sectionTypeInfo[SECTTYPE_INVALID];
/*
* Tells whether a section has data in its object file definition,
* depending on type.
* @param type The section's type
* @return `true` if the section's definition includes data
*/
static inline bool sect_HasData(enum SectionType type)
{
assert(type != SECTTYPE_INVALID);
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
}
/*
* Computes a memory region's end address (last byte), eg. 0x7FFF
* @return The address of the last byte in that memory region
*/
static inline uint16_t endaddr(enum SectionType type)
{
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
}
/*
* Computes a memory region's number of banks
* @return The number of banks, 1 for regions without banking
*/
static inline uint32_t nbbanks(enum SectionType type)
{
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
}
enum SectionModifier {
SECTION_NORMAL,
SECTION_UNION,
@@ -85,17 +126,6 @@ enum SectionModifier {
extern char const * const sectionModNames[];
/**
* Tells whether a section has data in its object file definition,
* depending on type.
* @param type The section's type
* @return `true` if the section's definition includes data
*/
static inline bool sect_HasData(enum SectionType type)
{
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
}
enum ExportLevel {
SYMTYPE_LOCAL,
SYMTYPE_IMPORT,
@@ -111,45 +141,4 @@ enum PatchType {
PATCHTYPE_INVALID
};
#define BANK_MIN_ROM0 0
#define BANK_MAX_ROM0 0
#define BANK_MIN_ROMX 1
#define BANK_MAX_ROMX 511
#define BANK_MIN_VRAM 0
#define BANK_MAX_VRAM 1
#define BANK_MIN_SRAM 0
#define BANK_MAX_SRAM 15
#define BANK_MIN_WRAM0 0
#define BANK_MAX_WRAM0 0
#define BANK_MIN_WRAMX 1
#define BANK_MAX_WRAMX 7
#define BANK_MIN_OAM 0
#define BANK_MAX_OAM 0
#define BANK_MIN_HRAM 0
#define BANK_MAX_HRAM 0
extern uint16_t startaddr[];
extern uint16_t maxsize[];
extern uint32_t bankranges[][2];
/**
* Computes a memory region's end address (last byte), eg. 0x7FFF
* @return The address of the last byte in that memory region
*/
static inline uint16_t endaddr(enum SectionType type)
{
return startaddr[type] + maxsize[type] - 1;
}
/**
* Computes a memory region's number of banks
* @return The number of banks, 1 for regions without banking
*/
static inline uint32_t nbbanks(enum SectionType type)
{
return bankranges[type][1] - bankranges[type][0] + 1;
}
extern char const * const typeNames[SECTTYPE_INVALID];
#endif /* RGBDS_LINKDEFS_H */
#endif // RGBDS_LINKDEFS_H

View File

@@ -18,4 +18,4 @@ int32_t op_shift_left(int32_t value, int32_t amount);
int32_t op_shift_right(int32_t value, int32_t amount);
int32_t op_shift_right_unsigned(int32_t value, int32_t amount);
#endif /* RGBDS_OP_MATH_H */
#endif // RGBDS_OP_MATH_H

View File

@@ -6,7 +6,7 @@
* SPDX-License-Identifier: MIT
*/
/* platform-specific hacks */
// platform-specific hacks
#ifndef RGBDS_PLATFORM_H
#define RGBDS_PLATFORM_H
@@ -20,20 +20,20 @@
# include <strings.h>
#endif
/* MSVC has deprecated strdup in favor of _strdup */
// MSVC has deprecated strdup in favor of _strdup
#ifdef _MSC_VER
# define strdup _strdup
#endif
/* MSVC prefixes the names of S_* macros with underscores,
and doesn't define any S_IS* macros. Define them ourselves */
// MSVC prefixes the names of S_* macros with underscores,
// and doesn't define any S_IS* macros; define them ourselves
#ifdef _MSC_VER
# define S_IFMT _S_IFMT
# define S_IFDIR _S_IFDIR
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
#endif
/* MSVC doesn't use POSIX types or defines for `read` */
// MSVC doesn't use POSIX types or defines for `read`
#ifdef _MSC_VER
# include <io.h>
# define STDIN_FILENO 0
@@ -46,12 +46,14 @@
# include <unistd.h>
#endif
/* MSVC doesn't support `[static N]` for array arguments from C99 */
// MSVC doesn't support `[static N]` for array arguments from C99 or C11
#ifdef _MSC_VER
# define MIN_NB_ELMS(N)
# define ARR_QUALS(...)
# define NONNULL(ptr) *ptr
#else
# define MIN_NB_ELMS(N) static (N)
# define ARR_QUALS(...) __VA_ARGS__
# define NONNULL(ptr) ptr[static 1]
#endif
@@ -73,4 +75,4 @@
# define setmode(fd, mode) ((void)0)
#endif
#endif /* RGBDS_PLATFORM_H */
#endif // RGBDS_PLATFORM_H

View File

@@ -9,10 +9,19 @@
#ifndef EXTERN_VERSION_H
#define EXTERN_VERSION_H
#ifdef __cplusplus
extern "C" {
#endif
#define PACKAGE_VERSION_MAJOR 0
#define PACKAGE_VERSION_MINOR 5
#define PACKAGE_VERSION_PATCH 2
#define PACKAGE_VERSION_MINOR 6
#define PACKAGE_VERSION_PATCH 0
#define PACKAGE_VERSION_RC 2
char const *get_package_version_string(void);
#endif /* EXTERN_VERSION_H */
#ifdef __cplusplus
}
#endif
#endif // EXTERN_VERSION_H

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@
.Nd Game Boy assembler
.Sh SYNOPSIS
.Nm
.Op Fl EhLVvw
.Op Fl EHhLlVvw
.Op Fl b Ar chars
.Op Fl D Ar name Ns Op = Ns Ar value
.Op Fl g Ar chars
@@ -25,6 +25,7 @@
.Op Fl MQ Ar target_file
.Op Fl o Ar out_file
.Op Fl p Ar pad_value
.Op Fl Q Ar fix_precision
.Op Fl r Ar recursion_depth
.Op Fl W Ar warning
.Ar
@@ -55,9 +56,9 @@ The defaults are 01.
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl Fl define Ar name Ns Oo = Ns Ar value Oc
Add a string symbol to the compiled source code.
This is equivalent to
.Ql Ar name Ic EQUS \(dq Ns Ar value Ns \(dq
.Ql Ar name Ic EQUS No \(dq Ns Ar value Ns \(dq
in code, or
.Ql Ar name Ic EQUS \(dq1\(dq
.Ql Ar name Ic EQUS No \(dq1\(dq
if
.Ar value
is not specified.
@@ -66,25 +67,43 @@ Export all labels, including unreferenced and local labels.
.It Fl g Ar chars , Fl Fl gfx-chars Ar chars
Change the four characters used for gfx constants.
The defaults are 0123.
.It Fl h , Fl Fl halt-without-nop
.It Fl H , Fl Fl nop-after-halt
By default,
.Nm
inserts a
.Ic nop
instruction immediately after any
.Ic halt
instruction.
instruction,
but this has been deprecated and prints a warning message the first time it occurs.
The
.Fl h
option disables this behavior.
.Fl H
option opts into this insertion,
so no warning will be printed.
.It Fl h , Fl Fl halt-without-nop
Disables inserting a
.Ic nop
instruction immediately after any
.Ic halt
instruction.
.It Fl i Ar path , Fl Fl include Ar path
Add an include path.
.It Fl L , Fl Fl preserve-ld
Disable the optimization that turns loads of the form
By default,
.Nm
optimizes loads of the form
.Ic LD [$FF00+n8],A
into the opcode
.Ic LDH [$FF00+n8],A
in order to have full control of the result in the final ROM.
.Ic LDH [$FF00+n8],A ,
but this has been deprecated and prints a warning message the first time it occurs.
The
.Fl L
option disables this optimization.
.It Fl l , Fl Fl auto-ldh
Optimize loads of the form
.Ic LD [$FF00+n8],A
into the opcode
.Ic LDH [$FF00+n8],A .
.It Fl M Ar depend_file , Fl Fl dependfile Ar depend_file
Print
.Xr make 1
@@ -130,8 +149,16 @@ Write an object file to the given filename.
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
When padding an image, pad with this value.
The default is 0x00.
.It Fl Q Ar fix_precision , Fl Fl q-precision Ar fix_precision
Use this as the precision of fixed-point numbers after the decimal point, unless they specify their own precision.
The default is 16, so fixed-point numbers are Q16.16 (since they are 32-bit integers).
The argument may start with a
.Ql \&.
to match the Q notation, for example,
.Ql Fl Q Ar .16 .
.It Fl r Ar recursion_depth , Fl Fl recursion-depth Ar recursion_depth
Specifies the recursion depth at which RGBASM will assume being in an infinite loop.
Specifies the recursion depth past which RGBASM will assume being in an infinite loop.
The default is 64.
.It Fl V , Fl Fl version
Print the version of the program and exit.
.It Fl v , Fl Fl verbose
@@ -274,6 +301,12 @@ warns when an N-bit value's absolute value is 2**N or greater.
or just
.Fl Wtruncation
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
.It Fl Wunmapped-char
Warn when a character goes through charmap conversion but has no defined mapping.
This warning is always disabled if the active charmap is empty, and/or is the default charmap
.Sq main .
This warning is enabled by
.Fl Wall .
.It Fl Wno-user
Warn when the
.Ic WARN

View File

@@ -208,13 +208,14 @@ section.
The instructions in the macro-language generally require constant expressions.
.Ss Numeric formats
There are a number of numeric formats.
.Bl -column -offset indent "Fixed point (Q16.16)" "Prefix"
.Bl -column -offset indent "Precise fixed-point" "Prefix"
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
.It Decimal Ta none Ta 0123456789
.It Octal Ta & Ta 01234567
.It Binary Ta % Ta 01
.It Fixed point (Q16.16) Ta none Ta 01234.56789
.It Fixed-point Ta none Ta 01234.56789
.It Precise fixed-point Ta none Ta 12.34q8
.It Character constant Ta none Ta \(dqABYZ\(dq
.It Gameboy graphics Ta \` Ta 0123
.El
@@ -301,9 +302,19 @@ and
.Ic \&!
returns 1 if the operand was 0, and 0 otherwise.
.Ss Fixed-point expressions
Fixed-point numbers are basically normal (32-bit) integers, which count 65536ths instead of entire units, offering better precision than integers but limiting the range of values.
The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
Since they are still akin to integers, you can use them in normal integer expressions, and some integer operators like
Fixed-point numbers are basically normal (32-bit) integers, which count fractions instead of whole numbers.
They offer better precision than integers but limit the range of values.
By default, the upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
The default number of fractional bits can be changed with the
.Fl Q
command-line option.
You can also specify a precise fixed-point value by appending a
.Dq q
to it followed by the number of fractional bits, such as
.Ql 12.34q8 .
.Pp
Since fixed-point values are still just integers, you can use them in normal integer expressions.
Some integer operators like
.Sq +
and
.Sq -
@@ -317,8 +328,9 @@ delim $$
.EN
.Bl -column -offset indent "ATAN2(x, y)"
.It Sy Name Ta Sy Operation
.It Fn DIV x y Ta $x \[di] y$
.It Fn MUL x y Ta $x \[mu] y$
.It Fn DIV x y Ta Fixed-point division $( x \[di] y ) \[mu] ( 2 ^ precision )$
.It Fn MUL x y Ta Fixed-point multiplication $( x \[mu] y ) \[di] ( 2 ^ precision )$
.It Fn FMOD x y Ta Fixed-point modulo $( x % y ) \[di] ( 2 ^ precision )$
.It Fn POW x y Ta $x$ to the $y$ power
.It Fn LOG x y Ta Logarithm of $x$ to the base $y$
.It Fn ROUND x Ta Round $x$ to the nearest integer
@@ -945,9 +957,9 @@ assuming the section ends up at
.Ad $80C0 :
.Bd -literal -offset indent
SECTION "Player tiles", VRAM
PlayerTiles:
vPlayerTiles:
ds 6 * 16
.end
\&.end
.Ed
.Pp
A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants.
@@ -1213,14 +1225,16 @@ The example above defines
.Ql MyMacro
as a new macro.
String constants are not expanded within the name of the macro.
You may use the older syntax
.Pp
(Using the
.Em deprecated
older syntax
.Ql MyMacro: MACRO
instead of
.Ql MACRO MyMacro ,
with a single colon
.Ql \&:
following the macro's name.
With the older syntax, string constants may be expanded for the name.
following the macro's name, string constants may be expanded for the name.)
.Pp
Macros can't be exported or imported.
.Pp
@@ -1324,7 +1338,7 @@ DEF AOLer EQUS "Me too"
String constants are not expanded within the symbol names.
.Ss Predeclared symbols
The following symbols are defined by the assembler:
.Bl -column -offset indent "EQUS" "__ISO_8601_LOCAL__"
.Bl -column -offset indent "__ISO_8601_LOCAL__" "EQUS"
.It Sy Name Ta Sy Type Ta Sy Contents
.It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
.It Dv _RS Ta Ic = Ta _RS Counter
@@ -1804,11 +1818,29 @@ The
value will be updated by
.Ar step
until it reaches or exceeds
.Ar stop .
.Ar stop ,
i.e. it covers the half-open range from
.Ar start
(inclusive) to
.Ar stop
(exclusive).
The variable
.Ar V
will be assigned this value at the beginning of each new iteration; any changes made to it within the
.Ic FOR
loop's body will be overwritten.
So the symbol
.Ar V
need not be already defined before any iterations of the
.Ic FOR
loop, but it must be a variable
.Pq Sx Variables
if so.
For example:
.Bd -literal -offset indent
FOR V, 4, 25, 5
PRINT "{d:V} "
DEF V *= 2
ENDR
PRINTLN "done {d:V}"
.Ed
@@ -2013,17 +2045,15 @@ POPO
The options that
.Ic OPT
can modify are currently:
.Cm b , g , p , r , h , L ,
.Cm b , g , p , Q , r , h , L ,
and
.Cm W .
The Boolean flag options
.Cm h
.Cm H , h , L ,
and
.Cm L
can be negated as
.Ql OPT !h
and
.Ql OPT !L
.Cm l
can be negated like
.Ql OPT !H
to act like omitting them from the command-line.
.Pp
.Ic POPO

405
man/rgbds.5 Normal file
View File

@@ -0,0 +1,405 @@
.\"
.\" This file is part of RGBDS.
.\"
.\" Copyright (c) 2017-2021, Antonio Nino Diaz and RGBDS contributors.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd March 28, 2021
.Dt RGBDS 5
.Os
.Sh NAME
.Nm rgbds
.Nd object file format documentation
.Sh DESCRIPTION
This is the description of the object files used by
.Xr rgbasm 1
and
.Xr rgblink 1 .
.Em Please note that the specification is not stable yet.
RGBDS is still in active development, and some new features require adding more information to the object file, or modifying some fields, both of which break compatibility with older versions.
.Sh FILE STRUCTURE
The following types are used:
.Pp
.Cm LONG
is a 32-bit integer stored in little-endian format.
.Cm BYTE
is an 8-bit integer.
.Cm STRING
is a 0-terminated string of
.Cm BYTE .
Brackets after a type
.Pq e.g. Cm LONG Ns Bq Ar n
indicate
.Ar n
consecutive elements
.Pq here, Cm LONG Ns s .
All items are contiguous, with no padding anywhere\(emthis also means that they may not be aligned in the file!
.Pp
.Cm REPT Ar n
indicates that the fields between the
.Cm REPT
and corresponding
.Cm ENDR
are repeated
.Ar n
times.
.Pp
All IDs refer to objects within the file; for example, symbol ID $0001 refers to the second symbol defined in
.Em this
object file's
.Sx Symbols
array.
The only exception is the
.Sx Source file info
nodes, whose IDs are backwards, i.e. source node ID $0000 refers to the
.Em last
node in the array, not the first one.
References to other object files are made by imports (symbols), by name (sections), etc.\(embut never by ID.
.Ss Header
.Bl -tag -width Ds -compact
.It Cm BYTE Ar Magic[4]
"RGB9"
.It Cm LONG Ar RevisionNumber
The format's revision number this file uses.
.Pq This is always in the same place in all revisions.
.It Cm LONG Ar NumberOfSymbols
How many symbols are defined in this object file.
.It Cm LONG Ar NumberOfSections
How many sections are defined in this object file.
.El
.Ss Source file info
.Bl -tag -width Ds -compact
.It Cm LONG Ar NumberOfNodes
The number of source context nodes contained in this file.
.It Cm REPT Ar NumberOfNodes
.Bl -tag -width Ds -compact
.It Cm LONG Ar ParentID
ID of the parent node, -1 meaning that this is the root node.
.Pp
.Sy Important :
the nodes are actually written in
.Sy reverse
order, meaning the node with ID 0 is the last one in the list!
.It Cm LONG Ar ParentLineNo
Line at which the parent node's context was exited; meaningless for the root node.
.It Cm BYTE Ar Type
.Bl -column "Value" -compact
.It Sy Value Ta Sy Meaning
.It 0 Ta REPT node
.It 1 Ta File node
.It 2 Ta Macro node
.El
.It Cm IF Ar Type No \(!= 0
If the node is not a REPT node...
.Pp
.Bl -tag -width Ds -compact
.It Cm STRING Ar Name
The node's name: either a file name, or the macro's name prefixes by its definition's file name
.Pq e.g. Ql src/includes/defines.asm::error .
.El
.It Cm ELSE
If the node is a REPT, it also contains the iteration counter of all parent REPTs.
.Pp
.Bl -tag -width Ds -compact
.It Cm LONG Ar Depth
.It Cm LONG Ar Iter Ns Bq Ar Depth
The number of REPT iterations, by increasing depth.
.El
.It Cm ENDC
.El
.It Cm ENDR
.El
.Ss Symbols
.Bl -tag -width Ds -compact
.It Cm REPT Ar NumberOfSymbols
.Bl -tag -width Ds -compact
.It Cm STRING Ar Name
This symbol's name.
Local symbols are stored as their full name
.Pq Ql Scope.symbol .
.It Cm BYTE Ar Type
.Bl -column "Value" -compact
.It Sy Value Ta Sy Meaning
.It 0 Ta Sy Local No symbol only used in this file.
.It 1 Ta Sy Import No of an exported symbol (by name) from another object file.
.It 2 Ta Sy Exported No symbol visible from other object files.
.El
.It Cm IF Ar Type No \(!= 1
If the symbol is defined in this object file...
.Pp
.Bl -tag -width Ds -compact
.It Cm LONG Ar NodeID
Context in which the symbol was defined.
.It Cm LONG Ar LineNo
Line number in the context at which the symbol was defined.
.It Cm LONG Ar SectionID
The ID of the section in which the symbol is defined.
If the symbol doesn't belong to any specific section (i.e. it's a constant), this field contains -1.
.It Cm LONG Ar Value
The symbol's value.
If the symbol belongs to a section, this is the offset within that symbol's section.
.El
.It Cm ENDC
.El
.It Cm ENDR
.El
.Ss Sections
.Bl -tag -width Ds -compact
.It Cm REPT Ar NumberOfSections
.Bl -tag -width Ds -compact
.It Cm STRING Ar Name
The section's name.
.It Cm LONG Ar Size
The section's size, in bytes.
.It Cm BYTE Ar Type
Bits 0\(en2 indicate the section's type:
.Bl -column "Value" -compact
.It Sy Value Ta Sy Meaning
.It 0 Ta WRAM0
.It 1 Ta VRAM
.It 2 Ta ROMX
.It 3 Ta ROM0
.It 4 Ta HRAM
.It 5 Ta WRAMX
.It 6 Ta SRAM
.It 7 Ta OAM
.El
.Pp
Bit\ 7 being set means that the section is a "union"
.Pq see Do Unionized sections Dc in Xr rgbasm 5 .
Bit\ 6 being set means that the section is a "fragment"
.Pq see Do Section fragments Dc in Xr rgbasm 5 .
These two bits are mutually exclusive.
.It Cm LONG Ar Address
Address this section must be placed at.
This must either be valid for the section's
.Ar Type
(as affected by flags like
.Fl t
or
.Fl d
in
.Xr rgblink 1 ) ,
or -1 to indicate that the linker should automatically decide
.Pq the section is Dq floating .
.It Cm LONG Ar Bank
ID of the bank this section must be placed in.
This must either be valid for the section's
.Ar Type
(with the same caveats as for the
.Ar Address ) ,
or -1 to indicate that the linker should automatically decide.
.It Cm BYTE Ar Alignment
How many bits of the section's address should be equal to
.Ar AlignOfs ,
starting from the least-significant bit.
.It Cm LONG Ar AlignOfs
Alignment offset.
Must be strictly less than
.Ql 1 << Ar Alignment .
.It Cm IF Ar Type No \(eq 2 || Ar Type No \(eq 3
If the section has ROM type, it contains data.
.Pp
.Bl -tag -width Ds -compact
.It Cm BYTE Ar Data Ns Bq Size
The section's raw data.
Bytes that will be patched over must be present, even though their contents will be overwritten.
.It Cm LONG Ar NumberOfPatches
How many patches must be applied to this section's
.Ar Data .
.It Cm REPT Ar NumberOfPatches
.Bl -tag -width Ds -compact
.It Cm LONG Ar NodeID
Context in which the patch was defined.
.It Cm LONG Ar LineNo
Line number in the context at which the patch was defined.
.It Cm LONG Ar Offset
Offset within the section's
.Ar Data
at which the patch should be applied.
Must not be greater than the section's
.Ar Size
minus the patch's size
.Pq see Ar Type No below .
.It Cm LONG Ar PCSectionID
ID of the section in which PC is located.
(This is usually the same section within which the patch is applied, except for e.g.\&
.Ql LOAD
blocks, see
.Do RAM code Dc in Xr rgbasm 5 . )
.It Cm LONG Ar PCOffset
Offset of the PC symbol within the section designated by
.Ar PCSectionID .
It is expected that PC points to the instruction's first byte for instruction operands (i.e.\&
.Ql jp @
must be an infinite loop), and to the patch's first byte otherwise
.Ql ( db ,
.Ql dw ,
.Ql dl ) .
.It Cm BYTE Ar Type
.Bl -column "Value" -compact
.It Sy Value Ta Sy Meaning
.It 0 Ta Single-byte patch
.It 1 Ta Little-endian two-byte patch
.It 2 Ta Little-endian four-byte patch
.It 3 Ta Single-byte Ql jr
patch; the patch's value will be subtracted to PC + 2 (i.e.\&
.Ql jr @
must be the infinite loop
.Ql 18 FE ) .
.El
.It Cm LONG Ar RPNSize
Size of the
.Ar RPNExpr
below.
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
The patch's value, encoded as a RPN expression
.Pq see Sx RPN EXPRESSIONS .
.El
.It Cm ENDR
.El
.It Cm ENDC
.El
.El
.Ss Assertions
.Bl -tag -width Ds -compact
.It Cm LONG Ar NumberOfAssertions
How many assertions this object file contains.
.It Cm REPT Ar NumberOfAssertions
Assertions are essentially patches with a message.
.Pp
.Bl -tag -width Ds -compact
.It Cm LONG Ar NodeID
Context in which the assertions was defined.
.It Cm LONG Ar LineNo
Line number in the context at which the assertion was defined.
.It Cm LONG Ar Offset
Unused leftover from the patch structure.
.It Cm LONG Ar PCSectionID
ID of the section in which PC is located.
.It Cm LONG Ar PCOffset
Offset of the PC symbol within the section designated by
.Ar PCSectionID .
.It Cm BYTE Ar Type
Describes what should happen if the expression evaluates to a non-zero value.
.Bl -column "Value" -compact
.It Sy Value Ta Sy Meaning
.It 0 Ta Print a warning message, and continue linking normally.
.It 1 Ta Print an error message, so linking will fail, but allow other assertions to be evaluated.
.It 2 Ta Print a fatal error message, and abort immediately.
.El
.It Cm LONG Ar RPNSize
Size of the
.Ar RPNExpr
below.
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
The patch's value, encoded as a RPN expression
.Pq see Sx RPN EXPRESSIONS .
.It Cm STRING Ar Message
The message displayed if the expression evaluates to a non-zero value.
If empty, a generic message is displayed instead.
.El
.It Cm ENDR
.El
.Ss RPN EXPRESSIONS
Expressions in the object file are stored as RPN, or
.Dq Reverse Polish Notation ,
which is a notation that allows computing arbitrary expressions with just a simple stack.
For example, the expression
.Ql 2 5 -
will first push the value
.Dq 2
to the stack, then
.Dq 5 .
The
.Ql -
operator pops two arguments from the stack, subtracts them, and then pushes back the result
.Pq Dq 3
on the stack.
A well-formed RPN expression never tries to pop from an empty stack, and leaves exactly one value in it at the end.
.Pp
RGBDS encodes RPN expressions as an array of
.Cm BYTE Ns s .
The first byte encodes either an operator, or a literal, which consumes more
.Cm BYTE Ns s
after it.
.Bl -column -offset Ds "Value"
.It Sy Value Ta Sy Meaning
.It Li $00 Ta Addition operator Pq Ql +
.It Li $01 Ta Subtraction operator Pq Ql -
.It Li $02 Ta Multiplication operator Pq Ql *
.It Li $03 Ta Division operator Pq Ql /
.It Li $04 Ta Modulo operator Pq Ql %
.It Li $05 Ta Negation Pq unary Ql -
.It Li $06 Ta Exponent operator Pq Ql **
.It Li $10 Ta Bitwise OR operator Pq Ql \&|
.It Li $11 Ta Bitwise AND operator Pq Ql &
.It Li $12 Ta Bitwise XOR operator Pq Ql ^
.It Li $13 Ta Bitwise complement operator Pq unary Ql ~
.It Li $21 Ta Logical AND operator Pq Ql &&
.It Li $22 Ta Logical OR operator Pq Ql ||
.It Li $23 Ta Logical complement operator Pq unary Ql \&!
.It Li $30 Ta Equality operator Pq Ql ==
.It Li $31 Ta Non-equality operator Pq Ql !=
.It Li $32 Ta Greater-than operator Pq Ql >
.It Li $33 Ta Less-than operator Pq Ql <
.It Li $34 Ta Greater-than-or-equal operator Pq Ql >=
.It Li $35 Ta Less-than-or-equal operator Pq Ql <=
.It Li $40 Ta Left shift operator Pq Ql <<
.It Li $41 Ta Arithmetic/signed right shift operator Pq Ql >>
.It Li $42 Ta Logical/unsigned right shift operator Pq Ql >>>
.It Li $50 Ta Fn BANK symbol ,
followed by the
.Ar symbol Ap s Cm LONG
ID.
.It Li $51 Ta Fn BANK section ,
followed by the
.Ar section Ap s Cm STRING
name.
.It Li $52 Ta PC's Fn BANK Pq i.e. Ql BANK(@) .
.It Li $53 Ta Fn SIZEOF section ,
followed by the
.Ar section Ap s Cm STRING
name.
.It Li $54 Ta Fn STARTOF section ,
followed by the
.Ar section Ap s Cm STRING
name.
.It Li $60 Ta Ql ldh
check.
Checks if the value is a valid
.Ql ldh
operand
.Pq see Do Load Instructions Dc in Xr gbz80 7 ,
i.e. that it is between either $00 and $FF, or $FF00 and $FFFF, both inclusive.
The value is then ANDed with $00FF
.Pq Ql & $FF .
.It Li $61 Ta Ql rst
check.
Checks if the value is a valid
.Ql rst
.Pq see Do RST vec Dc in Xr gbz80 7
vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38.
The value is then ORed with $C7
.Pq Ql \&| $C7 .
.It Li $80 Ta Integer literal.
Followed by the
.Cm LONG
integer.
.It Li $81 Ta A symbol's value.
Followed by the symbol's
.Cm LONG
ID.
.El
.Sh SEE ALSO
.Xr rgbasm 1 ,
.Xr rgblink 1 ,
.Xr rgbds 7 ,
.Xr gbz80 7
.Sh HISTORY
.Nm
was originally written by Carsten S\(/orensen as part of the ASMotor package,
and was later packaged in RGBDS by Justin Lloyd.
It is now maintained by a number of contributors at
.Lk https://github.com/gbdev/rgbds .

View File

@@ -192,7 +192,7 @@ SurvivalKids.gbc
.Sh TPP1
TPP1 is a homebrew mapper designed as a functional superset of the common traditional MBCs, allowing larger ROM and RAM sizes combined with other hardware features.
Its specification, as well as more resources, can be found online at
.Lk https://github.com/TwitchPlaysPokemon/tpp1 .
.Lk https://github.com/aaaaaa123456789/tpp1 .
.Ss MBC name
The MBC name for TPP1 is more complex than standard mappers.
It must be followed with the revision number, of the form

591
man/rgbgfx.1 Normal file
View File

@@ -0,0 +1,591 @@
'\" e
.\"
.\" This file is part of RGBDS.
.\"
.\" Copyright (c) 2013-2021, stag019 and RGBDS contributors.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd March 28, 2021
.Dt RGBGFX 1
.Os
.Sh NAME
.Nm rgbgfx
.Nd Game Boy graphics converter
.Sh SYNOPSIS
.Nm
.Op Fl r Ar stride
.Op Fl CmuVZ
.Op Fl v Op Fl v No ...
.Op Fl a Ar attrmap | Fl A
.Op Fl b Ar base_ids
.Op Fl c Ar color_spec
.Op Fl d Ar depth
.Op Fl L Ar slice
.Op Fl N Ar nb_tiles
.Op Fl n Ar nb_pals
.Op Fl o Ar out_file
.Op Fl p Ar pal_file | Fl P
.Op Fl q Ar pal_map | Fl Q
.Op Fl s Ar nb_colors
.Op Fl t Ar tilemap | Fl T
.Op Fl x Ar quantity
.Ar file
.Sh DESCRIPTION
The
.Nm
program converts PNG images into data suitable for display on the Game Boy and Game Boy Color, or vice-versa.
.Pp
The main function of
.Nm
is to divide the input PNG into 8\[tmu]8 pixel
.Em squares ,
convert each of those squares into 1bpp or 2bpp tile data, and save all of the tile data in a file.
It also has options to generate a tile map, attribute map, and/or palette set as well; more on that and how the conversion process can be tweaked below.
.Sh ARGUMENTS
Note that options can be abbreviated as long as the abbreviation is unambiguous:
.Fl Fl verb
is
.Fl Fl verbose ,
but
.Fl Fl ver
is invalid because it could also be
.Fl Fl version .
.Pp
.Nm
accepts decimal, binary, and hexadecimal numbers in option arguments.
Decimal numbers are written as usual; binary numbers must be prefixed with either
.Ql %
or
.Ql 0b ,
and hexadecimal numbers must be prefixed with either
.Ql $
(which will likely need escaping or quoting to avoid being interpreted by the shell), or
.Ql 0x .
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
All of these are equivalent:
.Ql 42 ,
.Ql 042 ,
.Ql 0b00101010 ,
.Ql 0B101010 ,
.Ql 0x2A ,
.Ql 0X2A ,
.Ql 0x2a .
.Pp
The following options are accepted:
.Bl -tag -width Ds
.It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap
Generate an attribute map, which is a file containing tile
.Dq attributes .
For each square of the input image, its corresponding attribute map byte contains the mirroring bits (if
.Fl m
was specified), the bank bit
.Pq see Fl N ,
and the palette index.
See
.Lk https://gbdev.io/pandocs/Tile_Maps#bg-map-attributes-cgb-mode-only Pan Docs
for the individual bytes' format.
The output is written just like the tile map (see
.Fl t ) ,
follows the same order
.Pq Fl Z ,
and has the same size.
.It Fl A , Fl Fl output-attr-map
Same as
.Fl a Ar path ,
where
.Ar path
is the input image's path with the extension set to
.Pa .attrmap .
.It Fl b Ar base_ids , Fl Fl base-tiles Ar base_ids
Set the base IDs for tile map output.
.Ar base_ids
should be one or two numbers between 0 and 255, separated by a comma; they are for bank 0 and bank 1 respectively.
Both default to 0.
.It Fl C , Fl Fl color-curve
When generating palettes, use a color curve mimicking the Game Boy Color's screen.
The resulting colors may look closer to the input image's
.Sy on hardware and accurate emulators .
.It Fl c Ar color_spec , Fl Fl colors Ar color_spec
Use the specified color palettes instead of having
.Nm
automatically determine some.
.Ar color_spec
can be one of the following:
.Bl -tag -width Ds
.It Sy inline palette spec
If
.Ar color_spec
begins with a hash character
.Ql # ,
it is treated as an inline palette specification.
It should contain a comma-separated list of hexadecimal colors, each beginning with a hash.
Colors in are accepted either as
.Ql #rgb
or
.Ql #rrggbb
format.
Palettes must be separated by a colon or semicolon (the latter may require quoting to avoid special handling by the shell), and spaces are allowed around colons, semicolons and commas; trailing commas and semicolons are allowed.
See
.Sx EXAMPLES
for an example of an inline palette specification.
.It Sy embedded palette spec
If
.Ar color_spec
is the case-insensitive word
.Cm embedded ,
then the first four colors of the input PNG's embedded palette are used.
It is an error if the PNG is not indexed, or if colors other than these 4 are used.
.Pq This is different from the default behavior of indexed PNGs, as then unused entries in the embedded palette are ignored, whereas they are not with Fl c Cm embedded .
.It Sy external palette spec
Otherwise,
.Ar color_spec
is assumed to be an external palette specification.
The expected format is
.Ql format:path ,
where
.Ar path
is a path to a file, which will be processed according to the
.Ar format .
See
.Sx PALETTE SPECIFICATION FORMATS
for a list of formats and their descriptions.
.El
.It Fl d Ar depth , Fl Fl depth Ar depth
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
.It Fl L Ar slice , Fl Fl slice Ar slice
Only process a given rectangle of the image.
This is useful for example if the input image is a sheet of some sort, and you want to convert each cel individually.
The default is to process the whole image as-is.
.Pp
.Ar slice
must be two number pairs, separated by a colon.
The numbers must be separated by commas; space is allowed around all punctuation.
The first number pair specifies the X and Y coordinates of the top-left pixel that will be processed (anything above it or to its left will be ignored).
The second number pair specifies how many tiles to process horizontally and vertically, respectively.
.Pp
.Sy Fl L Sy is ignored in reverse mode , No no padding is inserted .
.It Fl m , Fl Fl mirror-tiles
Deduplicate tiles that are mirrors of each other.
Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring.
Useful with a tile map and attribute map together to keep track of the duplicated tiles and the dimension(s) mirrored.
Implies
.Fl u .
.It Fl N Ar nb_tiles , Fl Fl nb-tiles Ar nb_tiles
Set a maximum number of tiles that can be placed in each VRAM bank.
.Ar nb_tiles
should be one or two numbers between 0 and 256, separated by a comma; if the latter is omitted, it defaults to 0.
Setting either number to 0 prevents any tiles from being output in that bank.
.Pp
If more tiles are generated than can fit in the two banks combined,
.Nm
will abort.
If
.Fl N
is not specified, no limit will be set on the amount of tiles placed in bank 0, and tiles will not be placed in bank 1.
.It Fl n Ar nb_pals , Fl Fl nb-palettes Ar nb_pals
Abort if more than
.Ar nb_pals
palettes are generated.
This may not be more than 256.
.Pp
Note that attribute map output only has 3 bits for the palette ID, so a limit higher than 8 may yield incomplete data unless relying on a palette map
.Pq see Fl q .
.It Fl o Ar out_file , Fl Fl output Ar out_file
Output the tile data in native 2bpp format or in 1bpp
.Pq depending on Fl d
to this file.
.It Fl p Ar pal_file , Fl Fl palette Ar pal_file
Output the image's palette set to this file.
.It Fl P , Fl Fl output-palette
Same as
.Fl p Ar path ,
where
.Ar path
is the input image's path with the extension set to
.Pa .pal .
.It Fl q Ar pal_file , Fl Fl palette-map Ar pal_file
Output the image's palette map to this file.
This is useful if the input image contains more than 8 palettes, as the attribute map only contains the lower 3 bits of the palette indices.
.It Fl Q , Fl Fl output-palette-map
Same as
.Fl q Ar path ,
where
.Ar path
is the input image's path with the extension set to
.Pa .palmap .
.It Fl r Ar width , Fl Fl reverse Ar width
Switches
.Nm
into
.Dq Sy reverse
mode.
In this mode, instead of converting a PNG image into Game Boy data,
.Nm
will attempt to reverse the process, and render Game Boy data into an image.
See
.Sx REVERSE MODE
below for details.
.Pp
.Ar width
is the image's width, in tiles
.Pq including any margins specified by Fl L .
.It Fl s Ar nb_colors , Fl Fl palette-size Ar nb_colors
Specify how many colors each palette contains, including the transparent one if any.
.Ar nb_colors
cannot be more than
.Ql 1 << Ar depth
.Pq see Fl d .
.It Fl t Ar tilemap , Fl Fl tilemap Ar tilemap
Generate a file of tile indices.
For each square of the input image, its corresponding tile map byte contains the index of the associated tile in the tile data file.
The IDs wrap around from 255 back to 0, and do not include the bank bit; use
.Fl a
for that.
Useful in combination with
.Fl u
and/or
.Fl m
to keep track of duplicate tiles.
.It Fl T , Fl Fl output-tilemap
Same as
.Fl t Ar path ,
where
.Ar path
is the input image's path with the extension set to
.Pa .tilemap .
.It Fl u , Fl Fl unique-tiles
Deduplicate identical tiles, and omit the duplicates from the tile data file.
Useful with a tile map
.Pq see Fl t
to keep track of the duplicated tiles.
.Pp
Note that if this option is enabled, no guarantee is made on the order in which tiles are output; while it
.Em should
be consistent across identical runs of a given
.Nm
release, the same is not true for different releases.
.It Fl V , Fl Fl version
Print the version of the program and exit.
.It Fl v , Fl Fl verbose
Be verbose.
The verbosity level is increased by one each time the flag is specified, with each level including the previous:
.Bl -enum -width 2n -compact
.It
.Nm
prints out its configuration before doing anything.
.It
A generic message is printed before doing most actions.
.It
Some of the actions' intermediate results are printed.
.It
Some internal debug printing is enabled.
.El
The verbosity level does not go past 6.
.Pp
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
.It Fl x Ar quantity , Fl Fl trim-end Ar quantity
Do not output the last
.Ar quantity
tiles to the tile data file; no other output is affected.
This is useful for trimming
.Dq filler
/ blank squares at the end of an image.
If fewer than
.Ar quantity
tiles would have been emitted, the file will be empty.
.Pp
Note that this is done
.Em after
deduplication if
.Fl u
was enabled, so you probably don't want to use this option in combination with
.Fl u .
Note also that the tiles that don't get output will not count towards
.Fl N Ap s
limit.
.It Fl Z , Fl Fl columns
Read squares from the PNG in column-major order (column by column), instead of the default row-major order (line by line).
This primarily affects tile map and attribute map output, although it may also change generated tile data and palettes.
.El
.Ss At-files
In a given project, many images are to be converted with different flags.
The traditional way of solving this problem has been to specify the different flags for each image in the Makefile / build script; this can be inconvenient, as it centralizes all those flags away from the images they concern.
.Pp
To avoid these drawbacks,
.Nm
supports
.Dq at-files :
any command-line argument that begins with an at sign
.Pq Ql @
is interpreted as one.
The rest of the argument (without the @, that is) is interpreted as the path to a file, whose contents are interpreted as if given on the command line.
At-files can be stored right next to the corresponding image, for example.
.Pp
Since the contents of at-files are interpreted by
.Nm ,
.Sy no shell processing is performed ;
for example, shell variables are not expanded
.Ql ( $PWD ,
.Ql %WINDIR% ,
etc.).
In at-files, lines that are empty or contain only whitespace are ignored; lines that begin with a hash sign
.Pq Ql # ,
optionally preceded by whitespace, are considered comments and also ignored.
Each line can contain any number of arguments, which are separated by whitespace.
.Pq \&No quoting feature to prevent this is provided.
.Pp
Note that this special meaning given to arguments has less precedence than option arguments, and that the standard
.Ql --
to stop option processing also disables at-file processing.
For example, the following command line processes
.Ql @tilesets/town.png ,
outputs tile data to
.Ql @tilesets/town.2bpp ,
and reads command-line options from
.Ql tilesets/town.flags
then
.Ql tilesets.flags :
.Pp
.Dl $ rgbgfx -o @tilesets/town.2bpp @tilesets/town.flags @tilesets.flags -- @tilesets/town.png
.Pp
At-files can also specify the input image directly, and call for more at-files, both using the regular syntax.
Note that while
.Ql --
can be used in an at-file (with identical semantics), it is only effective inside of it\(emnormal option processing continues in the parent scope.
.Sh PALETTE SPECIFICATION FORMATS
The following formats are supported:
.Bl -tag -width Ds
.It Sy act
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 Adobe Photoshop color table .
.It Sy aco
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819 Adobe Photoshop color swatch .
.It Sy psp
.Lk https://www.selapa.net/swatches/colors/fileformats.php#psp_pal Paint Shop Pro palette .
.El
.Pp
If you wish for another format to be supported, please open an issue (see
.Sx BUGS
below) or contact us, and supply a few sample files.
.Sh PALETTE GENERATION
.Nm
must generate palettes from the colors in the input image, unless
.Fl c
was used; in that case, the provided palettes will be used.
.Sy If the order of colors in the palettes is important to you ,
for example because you want to use palette swaps, please use
.Fl c
to specify the palette explicitly.
.Pp
First, if the image contains
.Em any
transparent pixel, color #0 of
.Em all
palettes will be allocated to it.
This is done
.Sy even if palettes were explicitly specified using Fl c ;
then the specification only covers color #1 onwards.
.Pq If you do not want this, ask your image editor to remove the alpha channel.
.Pp
After generating palettes,
.Nm
sorts colors within those palettes using the following rules:
.EQ
delim $$
.EN
.Bl -bullet -offset indent
.It
If the PNG file internally contains a palette (often dubbed an
.Dq indexed
PNG), then colors in each output palette will be sorted according to their order in the PNG's palette.
Any unused entries will be ignored, and only the first entry is considered if there are any duplicates.
.Po If you want a given color to appear more than once, or an unused color to appear at all, you should specify the palettes explicitly instead using Fl c ;
.Fl c Cm embedded
may be appropriate.
.Pc
.It
Otherwise, if the PNG only contains shades of gray, they will be categorized into as many
.Dq bins
as there are colors per palette, and the palette is set to these bins.
The darkest gray will end up in bin #0, and so on; note that this is the opposite of the RGB method below.
If two distinct grays end up in the same bin, the RGB method is used instead.
.Pp
Be careful that
.Nm
is picky about what it considers
.Dq grays :
the red, green, and blue components of each color must
.Em all
be
.Em exactly
the same.
.It
If none of the above apply, colors are sorted from lightest (first) to darkest (last).
The definition of luminance that
.Nm
uses is
.Do
$2126 times red + 7152 times green + 722 times blue$
.Dc .
.El
.EQ
delim off
.EN
.Pp
Note that the
.Dq indexed
behavior depends on an internal detail of how the PNG is saved, specifically its
.Ql PLTE
chunk.
Since few image editors (such as GIMP) expose that detail, this behavior is only kept for compatibility and should be considered deprecated.
.Sh OUTPUT FILES
All files output by
.Nm
are binary files, and designed to follow the Game Boy and Game Boy Color's native formats.
What follows is succinct descriptions of those formats, including
.Nm Ns -specific
details.
For more complete, beginner-friendly descriptions of the native formats with illustrations, please check out
.Lk https://gbdev.io/pandocs/Rendering Pan Docs .
.Ss Tile data
Tile data is output like a binary dump of VRAM, with no padding between tiles.
Each tile is 16 bytes, 2 per row of 8 pixels; the bits of color IDs are split into each byte
.Pq or Dq bitplane .
The leftmost pixel's color ID is stored in the two bytes' most significant bits, and the rightmost pixel's color ID in their least significant bits.
.Pp
When the bit depth
.Pq Fl d
is set to 1, the most significant bitplane (second byte) of each row, being all zeros, is simply not output.
.Ss Palette data
Palette data is output like a dump of palette memory.
Each color is written as GBC-native little-endian RGB555, with the unused bit 15 set to 0.
There is no padding between colors, nor between palettes; however, empty colors in the palettes are output as 0xFFFF.
.EQ
delim $$
.EN
For example, if 5 palettes are generated with
.Fl s Cm 4 ,
the palette data file will be $2 times 4 times 5 = 40$ bytes long, even if some palettes contain less than 3 colors.
.EQ
delim off
.EN
Note that
.Fl n
only caps how many palettes are generated (and thus this file's size), but fewer may be generated still.
.Ss Tile map data
A tile map is an array of tile IDs, with one byte per tile ID.
The first byte always corresponds to the ID of the tile in top-left corner of the input image; the second byte is either the ID of the tile to its right (by default), or below it
.Pq with Fl Z ;
and so on, continuing in the same direction.
Rows / columns (respectively) are stored consecutively, with no padding.
.Ss Attribute map data
Attribute maps mirror the format of tile maps, like on the GBC, especially the order in which bytes are output.
The contents of individual bytes follows the GBC's native format:
.Bl -column "Bit 2\(en0" "Background Palette number"
.It Bit 7 Ta BG-to-OAM Priority Ta Set to 0
.It Bit 6 Ta Vertical Flip Ta 0=Normal, 1=Mirror vertically
.It Bit 5 Ta Horizontal Flip Ta 0=Normal, 1=Mirror horizontally
.It Bit 4 Ta Not used Ta Set to 0
.It Bit 3 Ta Tile VRAM Bank number Ta 0=Bank 0, 1=Bank 1
.It Bit 2\(en0 Ta Background Palette number Ta BGP0-7
.El
.Pp
Note that if more than 8 palettes are used, only the lowest 3 bits of the palette ID are output.
.Sh REVERSE MODE
.Nm
can produce a PNG image from valid data.
This may be useful for ripping graphics, recovering lost source images, etc.
An important caveat on that last one, though: the conversion process is
.Sy lossy
both ways, so the
.Do reversed Dc image won't be perfectly identical to the original\(embut it should be close to a Game Boy's output .
.Pq Keep in mind that many of consoles output different colors, so there is no true reference rendering.
.Pp
When using reverse mode, make sure to pass the same flags that were given when generating the data, especially
.Fl C , d , N , s , x ,
and
.Fl Z .
.Do Sx At-files Dc may help with this .
.Nm
will warn about any inconsistencies it detects.
.Pp
Files that are normally outputs
.Pq Fl a , p , t
become inputs, and
.Ar file
will be written to instead of read from, and thus needs not exist beforehand.
Any of these inputs not passed is assumed to be some default:
.Bl -column "attribute map"
.It palettes Ta Unspecified palette data makes
.Nm
assume DMG (monochrome Game Boy) mode: a single palette of 4 grays.
It is possible to pass palettes using
.Fl c
instead of
.Fl p .
.It tile data Ta Tile data must be provided, as there is no reasonable assumption to fall back on.
.It tile map Ta A missing tile map makes
.Nm
assume that tiles were not deduplicated, and should be laid out in the order they are stored.
.It attribute map Ta Without an attribute map,
.Nm
assumes that no tiles were mirrored.
.El
.Sh NOTES
Some flags have had their functionality removed.
.Fl D , f ,
and
.Fl F
are now ignored, and
.Fl h
is an alias for the new (and less confusingly named)
.Fl Z .
These will be removed and/or repurposed in future versions of
.Nm ,
so relying on them is not recommended.
The same applies to the corresponding long options.
.Pp
If you are curious, you may find out that palette generation is an NP-complete problem, so
.Nm
does not attempt to find the optimal solution, but instead to find a good one in a reasonable amount of time.
It is possible to compute the optimal solution externally (using a solver, for example), and then provide it to
.Nm
via
.Fl c .
.Sh EXAMPLES
The following will only validate the PNG (check its size, that all tiles have a suitable amount of colors, etc.), but output nothing:
.Pp
.Dl $ rgbgfx src/res/maps/overworld/tileset.png
.Pp
The following will convert the image using the two given palettes (and only those), and store the generated 2bpp tile data in
.Ql tileset.2bpp ,
and the attribute map in
.Ql tileset.attrmap .
.Pp
.Dl $ rgbgfx -c '#ffffff,#8d05de, #dc7905,#000000 ; #fff,#8d05de, #7e0000 \&, #000' -A -o tileset.2bpp tileset.png
.Pp
TODO: more examples.
.Sh BUGS
Please report bugs and mistakes in this man page on
.Lk https://github.com/gbdev/rgbds/issues GitHub .
Bug reports and feature requests about RGBDS are also welcome!
.Sh SEE ALSO
.Xr rgbds 7 ,
.Xr rgbasm 1 ,
.Xr rgblink 1 ,
.Xr rgbfix 1 ,
.Xr gbz80 7
.Pp
The Game Boy hardware reference
.Lk https://gbdev.io/pandocs/Rendering.html Pan Docs ,
particularly the section about graphics.
.Sh HISTORY
.Nm
was originally created by
.An stag019
to be included in RGBDS.
It was later rewritten by
.An ISSOtm ,
and is now maintained by a number of contributors at
.Lk https://github.com/gbdev/rgbds .

View File

@@ -13,7 +13,7 @@
.Nd Game Boy linker
.Sh SYNOPSIS
.Nm
.Op Fl dtVvwx
.Op Fl dMtVvwx
.Op Fl l Ar linker_script
.Op Fl m Ar map_file
.Op Fl n Ar sym_file
@@ -73,6 +73,8 @@ The attributes assigned in the linker script must be consistent with any assigne
See
.Xr rgblink 5
for more information about the linker script format.
.It Fl M , Fl Fl no-sym-in-map
If specified, the map file will not list symbols, only sections.
.It Fl m Ar map_file , Fl Fl map Ar map_file
Write a map file to the given filename, listing how sections and symbols were assigned.
.It Fl n Ar sym_file , Fl Fl sym Ar sym_file

View File

@@ -14,18 +14,22 @@
.Sh DESCRIPTION
The linker script is an external file that allows the user to specify the order of sections at link time and in a centralized manner.
.Pp
A linker script consists on a series of banks followed by a list of sections and, optionally, commands.
They can be lowercase or uppercase, it is ignored.
A linker script consists of a series of bank declarations, each optionally followed by a list of section names (in double quotes) or commands.
All reserved keywords (bank types and command names) are case-insensitive; all section names are case-sensitive.
.Pp
Any line can contain a comment starting with
.Ql \&;
that ends at the end of the line:
that ends at the end of the line.
.Pp
.Bd -literal -offset indent
ROMX $F ; This is a comment
"Functions to read array"
ALIGN 8
"Array aligned to 256 bytes"
; This line is a comment
ROMX $F ; start a bank
"Some functions" ; a section name
ALIGN 8 ; a command
"Some array"
WRAMX 2
WRAMX 2 ; start another bank
org $d123 ; another command
"Some variables"
.Ed
.Pp
@@ -47,6 +51,19 @@ and
.Cm WRAMX ,
it is needed to specify a bank number after the type.
.Pp
Section names in double quotes support the same character escape sequences as strings in
.Xr rgbasm 5 ,
specifically
.Ql \[rs]\[rs] ,
.Ql \[rs]" ,
.Ql \[rs]n ,
.Ql \[rs]r ,
and
.Ql \[rs]t .
Other backslash escape sequences in
.Xr rgbasm 5
are only relevant to assembly code and do not apply in section names.
.Pp
When a new bank statement is found, sections found after it will be placed right from the beginning of that bank.
If the linker script switches to a different bank and then comes back to a previous one, it will continue from the last address that was used.
.Pp

View File

@@ -14,14 +14,6 @@ set(common_src
"_version.c"
)
find_package(PkgConfig)
if(MSVC OR NOT PKG_CONFIG_FOUND)
# fallback to find_package
find_package(PNG REQUIRED)
else()
pkg_check_modules(LIBPNG REQUIRED libpng)
endif()
find_package(BISON REQUIRED)
set(BISON_FLAGS "-Wall")
# Set sompe optimization flags on versions that support them
@@ -70,9 +62,16 @@ set(rgbfix_src
)
set(rgbgfx_src
"gfx/gb.c"
"gfx/main.c"
"gfx/makepng.c"
"gfx/main.cpp"
"gfx/pal_packing.cpp"
"gfx/pal_sorting.cpp"
"gfx/pal_spec.cpp"
"gfx/process.cpp"
"gfx/proto_palette.cpp"
"gfx/reverse.cpp"
"gfx/rgba.cpp"
"extern/getopt.c"
"error.c"
)
set(rgblink_src
@@ -82,6 +81,7 @@ set(rgblink_src
"link/output.c"
"link/patch.c"
"link/script.c"
"link/sdas_obj.c"
"link/section.c"
"link/symbol.c"
"hashmap.c"
@@ -97,22 +97,6 @@ foreach(PROG "asm" "fix" "gfx" "link")
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin)
endforeach()
set(MANDIR "share/man")
set(man1 "asm/rgbasm.1"
"fix/rgbfix.1"
"gfx/rgbgfx.1"
"link/rgblink.1")
set(man5 "asm/rgbasm.5"
"link/rgblink.5"
"rgbds.5")
set(man7 "gbz80.7"
"rgbds.7")
foreach(SECTION "man1" "man5" "man7")
set(DEST "${MANDIR}/${SECTION}")
install(FILES ${${SECTION}} DESTINATION ${DEST})
endforeach()
if(LIBPNG_FOUND) # pkg-config
target_include_directories(rgbgfx PRIVATE ${LIBPNG_INCLUDE_DIRS})
target_link_directories(rgbgfx PRIVATE ${LIBPNG_LIBRARY_DIRS})

View File

@@ -21,33 +21,29 @@
#include "hashmap.h"
/*
* Charmaps are stored using a structure known as "trie".
* Essentially a tree, where each nodes stores a single character's worth of info:
* whether there exists a mapping that ends at the current character,
*/
// Charmaps are stored using a structure known as "trie".
// Essentially a tree, where each nodes stores a single character's worth of info:
// whether there exists a mapping that ends at the current character,
struct Charnode {
bool isTerminal; /* Whether there exists a mapping that ends here */
uint8_t value; /* If the above is true, its corresponding value */
/* This MUST be indexes and not pointers, because pointers get invalidated by `realloc`!! */
size_t next[255]; /* Indexes of where to go next, 0 = nowhere */
bool isTerminal; // Whether there exists a mapping that ends here
uint8_t value; // If the above is true, its corresponding value
// This MUST be indexes and not pointers, because pointers get invalidated by `realloc`!!
size_t next[255]; // Indexes of where to go next, 0 = nowhere
};
#define INITIAL_CAPACITY 32
struct Charmap {
char *name;
size_t usedNodes; /* How many nodes are being used */
size_t capacity; /* How many nodes have been allocated */
struct Charnode nodes[]; /* first node is reserved for the root node */
size_t usedNodes; // How many nodes are being used
size_t capacity; // How many nodes have been allocated
struct Charnode nodes[]; // first node is reserved for the root node
};
static HashMap charmaps;
/*
* Store pointers to hashmap nodes, so that there is only one pointer to the memory block
* that gets reallocated.
*/
// Store pointers to hashmap nodes, so that there is only one pointer to the memory block
// that gets reallocated.
static struct Charmap **currentCharmap;
struct CharmapStackEntry {
@@ -96,7 +92,7 @@ struct Charmap *charmap_New(char const *name, char const *baseName)
return charmap;
}
/* Init the new charmap's fields */
// Init the new charmap's fields
if (base) {
resizeCharmap(&charmap, base->capacity);
charmap->usedNodes = base->usedNodes;
@@ -105,7 +101,7 @@ struct Charmap *charmap_New(char const *name, char const *baseName)
} else {
resizeCharmap(&charmap, INITIAL_CAPACITY);
charmap->usedNodes = 1;
initNode(&charmap->nodes[0]); /* Init the root node */
initNode(&charmap->nodes[0]); // Init the root node
}
charmap->name = strdup(name);
@@ -169,16 +165,16 @@ void charmap_Add(char *mapping, uint8_t value)
if (node->next[c]) {
node = &charmap->nodes[node->next[c]];
} else {
/* Register next available node */
// Register next available node
node->next[c] = charmap->usedNodes;
/* If no more nodes are available, get new ones */
// If no more nodes are available, get new ones
if (charmap->usedNodes == charmap->capacity) {
charmap->capacity *= 2;
resizeCharmap(currentCharmap, charmap->capacity);
charmap = *currentCharmap;
}
/* Switch to and init new node */
// Switch to and init new node
node = &charmap->nodes[charmap->usedNodes++];
initNode(node);
}
@@ -203,12 +199,10 @@ size_t charmap_Convert(char const *input, uint8_t *output)
size_t charmap_ConvertNext(char const **input, uint8_t **output)
{
/*
* The goal is to match the longest mapping possible.
* For that, advance through the trie with each character read.
* If that would lead to a dead end, rewind characters until the last match, and output.
* If no match, read a UTF-8 codepoint and output that.
*/
// The goal is to match the longest mapping possible.
// For that, advance through the trie with each character read.
// If that would lead to a dead end, rewind characters until the last match, and output.
// If no match, read a UTF-8 codepoint and output that.
struct Charmap const *charmap = *currentCharmap;
struct Charnode const *node = &charmap->nodes[0];
struct Charnode const *match = NULL;
@@ -242,6 +236,7 @@ size_t charmap_ConvertNext(char const **input, uint8_t **output)
return 1;
} else if (**input) { // No match found, but there is some input left
int firstChar = **input;
// This will write the codepoint's value to `output`, little-endian
size_t codepointLen = readUTF8Char(output ? *output : NULL,
*input);
@@ -254,6 +249,12 @@ size_t charmap_ConvertNext(char const **input, uint8_t **output)
if (output)
*output += codepointLen;
// Check if the character map is not the default "main" one, or if
// it has any mappings defined
if (strcmp(charmap->name, "main") || charmap->usedNodes > 1)
warning(WARNING_UNMAPPED_CHAR,
"Unmapped character %s\n", printChar(firstChar));
return codepointLen;
} else { // End of input

View File

@@ -6,9 +6,7 @@
* SPDX-License-Identifier: MIT
*/
/*
* Fixed-point math routines
*/
// Fixed-point math routines
#include <inttypes.h>
#include <math.h>
@@ -19,30 +17,24 @@
#include "asm/symbol.h"
#include "asm/warning.h"
#define fix2double(i) ((double)((i) / 65536.0))
#define double2fix(d) ((int32_t)round((d) * 65536.0))
// pi radians == 32768 fixed-point "degrees"
#define fdeg2rad(f) ((f) * (M_PI / 32768.0))
#define rad2fdeg(r) ((r) * (32768.0 / M_PI))
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
/*
* Return the _PI symbol value
*/
int32_t fix_Callback_PI(void)
{
warning(WARNING_OBSOLETE, "`_PI` is deprecated; use 3.14159\n");
#define fix2double(i) ((double)((i) / fix_PrecisionFactor()))
#define double2fix(d) ((int32_t)round((d) * fix_PrecisionFactor()))
return double2fix(M_PI);
// pi*2 radians == 2**fixPrecision fixed-point "degrees"
#define fdeg2rad(f) ((f) * (M_PI * 2) / fix_PrecisionFactor())
#define rad2fdeg(r) ((r) * fix_PrecisionFactor() / (M_PI * 2))
uint8_t fixPrecision;
double fix_PrecisionFactor(void)
{
return pow(2.0, fixPrecision);
}
/*
* Print a fixed point value
*/
void fix_Print(int32_t i)
{
uint32_t u = i;
@@ -53,117 +45,80 @@ void fix_Print(int32_t i)
sign = "-";
}
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> 16,
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> fixPrecision,
((uint32_t)(fix2double(u) * 100000 + 0.5)) % 100000);
}
/*
* Calculate sine
*/
int32_t fix_Sin(int32_t i)
{
return double2fix(sin(fdeg2rad(fix2double(i))));
}
/*
* Calculate cosine
*/
int32_t fix_Cos(int32_t i)
{
return double2fix(cos(fdeg2rad(fix2double(i))));
}
/*
* Calculate tangent
*/
int32_t fix_Tan(int32_t i)
{
return double2fix(tan(fdeg2rad(fix2double(i))));
}
/*
* Calculate arcsine
*/
int32_t fix_ASin(int32_t i)
{
return double2fix(rad2fdeg(asin(fix2double(i))));
}
/*
* Calculate arccosine
*/
int32_t fix_ACos(int32_t i)
{
return double2fix(rad2fdeg(acos(fix2double(i))));
}
/*
* Calculate arctangent
*/
int32_t fix_ATan(int32_t i)
{
return double2fix(rad2fdeg(atan(fix2double(i))));
}
/*
* Calculate atan2
*/
int32_t fix_ATan2(int32_t i, int32_t j)
{
return double2fix(rad2fdeg(atan2(fix2double(i), fix2double(j))));
}
/*
* Multiplication
*/
int32_t fix_Mul(int32_t i, int32_t j)
{
return double2fix(fix2double(i) * fix2double(j));
}
/*
* Division
*/
int32_t fix_Div(int32_t i, int32_t j)
{
return double2fix(fix2double(i) / fix2double(j));
}
/*
* Power
*/
int32_t fix_Mod(int32_t i, int32_t j)
{
return double2fix(fmod(fix2double(i), fix2double(j)));
}
int32_t fix_Pow(int32_t i, int32_t j)
{
return double2fix(pow(fix2double(i), fix2double(j)));
}
/*
* Logarithm
*/
int32_t fix_Log(int32_t i, int32_t j)
{
return double2fix(log(fix2double(i)) / log(fix2double(j)));
}
/*
* Round
*/
int32_t fix_Round(int32_t i)
{
return double2fix(round(fix2double(i)));
}
/*
* Ceil
*/
int32_t fix_Ceil(int32_t i)
{
return double2fix(ceil(fix2double(i)));
}
/*
* Floor
*/
int32_t fix_Floor(int32_t i)
{
return double2fix(floor(fix2double(i)));

View File

@@ -15,6 +15,7 @@
#include <stdlib.h>
#include <string.h>
#include "asm/fixpoint.h"
#include "asm/format.h"
#include "asm/warning.h"
@@ -46,7 +47,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
return;
switch (c) {
/* sign */
// sign
case ' ':
case '+':
if (fmt->state > FORMAT_SIGN)
@@ -55,7 +56,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
fmt->sign = c;
break;
/* prefix */
// prefix
case '#':
if (fmt->state > FORMAT_PREFIX)
goto invalid;
@@ -63,7 +64,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
fmt->prefix = true;
break;
/* align */
// align
case '-':
if (fmt->state > FORMAT_ALIGN)
goto invalid;
@@ -71,11 +72,11 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
fmt->alignLeft = true;
break;
/* pad and width */
// pad and width
case '0':
if (fmt->state < FORMAT_WIDTH)
fmt->padZero = true;
/* fallthrough */
// fallthrough
case '1':
case '2':
case '3':
@@ -104,7 +105,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
fmt->hasFrac = true;
break;
/* type */
// type
case 'd':
case 'u':
case 'X':
@@ -149,7 +150,7 @@ void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, cha
size_t len = strlen(value);
size_t totalLen = fmt->width > len ? fmt->width : len;
if (totalLen > bufLen - 1) { /* bufLen includes terminator */
if (totalLen > bufLen - 1) { // bufLen includes terminator
error("Formatted string value too long\n");
totalLen = bufLen - 1;
if (len > totalLen)
@@ -182,7 +183,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
if (fmt->type == 's')
error("Formatting number as type 's'\n");
char sign = fmt->sign; /* 0 or ' ' or '+' */
char sign = fmt->sign; // 0 or ' ' or '+'
if (fmt->type == 'd' || fmt->type == 'f') {
int32_t v = value;
@@ -200,10 +201,10 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
: fmt->type == 'o' ? '&'
: 0;
char valueBuf[262]; /* Max 5 digits + decimal + 255 fraction digits + terminator */
char valueBuf[262]; // Max 5 digits + decimal + 255 fraction digits + terminator
if (fmt->type == 'b') {
/* Special case for binary */
// Special case for binary
char *ptr = valueBuf;
do {
@@ -213,7 +214,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
*ptr = '\0';
/* Reverse the digits */
// Reverse the digits
size_t valueLen = ptr - valueBuf;
for (size_t i = 0, j = valueLen - 1; i < j; i++, j--) {
@@ -223,9 +224,9 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
valueBuf[j] = c;
}
} else if (fmt->type == 'f') {
/* Special case for fixed-point */
// Special case for fixed-point
/* Default fractional width (C's is 6 for "%f"; here 5 is enough) */
// Default fractional width (C's is 6 for "%f"; here 5 is enough for Q16.16)
size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
if (fracWidth > 255) {
@@ -234,7 +235,8 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
fracWidth = 255;
}
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth, value / 65536.0);
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth,
value / fix_PrecisionFactor());
} else {
char const *spec = fmt->type == 'd' ? "%" PRId32
: fmt->type == 'u' ? "%" PRIu32
@@ -250,7 +252,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
size_t numLen = !!sign + !!prefix + len;
size_t totalLen = fmt->width > numLen ? fmt->width : numLen;
if (totalLen > bufLen - 1) { /* bufLen includes terminator */
if (totalLen > bufLen - 1) { // bufLen includes terminator
error("Formatted numeric value too long\n");
totalLen = bufLen - 1;
if (numLen > totalLen) {
@@ -273,7 +275,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
buf[i] = ' ';
} else {
if (fmt->padZero) {
/* sign, then prefix, then zero padding */
// sign, then prefix, then zero padding
if (sign)
buf[pos++] = sign;
if (prefix)
@@ -281,7 +283,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
for (size_t i = 0; i < padLen; i++)
buf[pos++] = '0';
} else {
/* space padding, then sign, then prefix */
// space padding, then sign, then prefix
for (size_t i = 0; i < padLen; i++)
buf[pos++] = ' ';
if (sign)

View File

@@ -19,7 +19,7 @@
#include "asm/main.h"
#include "asm/symbol.h"
#include "asm/warning.h"
#include "platform.h" /* S_ISDIR (stat macro) */
#include "platform.h" // S_ISDIR (stat macro)
#define MAXINCPATHS 128
@@ -28,7 +28,7 @@ struct Context {
struct FileStackNode *fileInfo;
struct LexerState *lexerState;
uint32_t uniqueID;
struct MacroArgs *macroArgs; /* Macro args are *saved* here */
struct MacroArgs *macroArgs; // Macro args are *saved* here
uint32_t nbReptIters;
int32_t forValue;
int32_t forStep;
@@ -37,7 +37,6 @@ struct Context {
static struct Context *contextStack;
static size_t contextDepth = 0;
#define DEFAULT_MAX_DEPTH 64
size_t maxRecursionDepth;
static unsigned int nbIncPaths = 0;
@@ -48,7 +47,7 @@ static const char *dumpNodeAndParents(struct FileStackNode const *node)
char const *name;
if (node->type == NODE_REPT) {
assert(node->parent); /* REPT nodes should always have a parent */
assert(node->parent); // REPT nodes should always have a parent
struct FileStackReptNode const *reptInfo = (struct FileStackReptNode const *)node;
name = dumpNodeAndParents(node->parent);
@@ -89,7 +88,7 @@ struct FileStackNode *fstk_GetFileStack(void)
struct FileStackNode *node = contextStack->fileInfo;
/* Mark node and all of its parents as referenced if not already so they don't get freed */
// Mark node and all of its parents as referenced if not already so they don't get freed
while (node && !node->referenced) {
node->ID = -1;
node->referenced = true;
@@ -100,7 +99,7 @@ struct FileStackNode *fstk_GetFileStack(void)
char const *fstk_GetFileName(void)
{
/* Iterating via the nodes themselves skips nested REPTs */
// Iterating via the nodes themselves skips nested REPTs
struct FileStackNode const *node = contextStack->fileInfo;
while (node->type != NODE_FILE)
@@ -121,7 +120,7 @@ void fstk_AddIncludePath(char const *path)
char *str = malloc(allocSize);
if (!str) {
/* Attempt to continue without that path */
// Attempt to continue without that path
error("Failed to allocate new include path: %s\n", strerror(errno));
return;
}
@@ -150,14 +149,14 @@ static bool isPathValid(char const *path)
if (stat(path, &statbuf) != 0)
return false;
/* Reject directories */
// Reject directories
return !S_ISDIR(statbuf.st_mode);
}
bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
{
if (!*size) {
*size = 64; /* This is arbitrary, really */
*size = 64; // This is arbitrary, really
*fullPath = realloc(*fullPath, *size);
if (!*fullPath)
error("realloc error during include path search: %s\n",
@@ -175,8 +174,8 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
break;
}
/* Oh how I wish `asnprintf` was standard... */
if ((size_t)len >= *size) { /* `len` doesn't include the terminator, `size` does */
// Oh how I wish `asnprintf` was standard...
if ((size_t)len >= *size) { // `size` includes the terminator, `len` doesn't
*size = len + 1;
*fullPath = realloc(*fullPath, *size);
if (!*fullPath) {
@@ -213,17 +212,18 @@ bool yywrap(void)
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n",
ifDepth, ifDepth == 1 ? "" : "s");
if (contextStack->fileInfo->type == NODE_REPT) { /* The context is a REPT block, which may loop */
if (contextStack->fileInfo->type == NODE_REPT) {
// The context is a REPT or FOR block, which may loop
struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo;
/* If the node is referenced, we can't edit it; duplicate it */
// If the node is referenced, we can't edit it; duplicate it
if (contextStack->fileInfo->referenced) {
size_t size = sizeof(*fileInfo) + sizeof(fileInfo->iters[0]) * fileInfo->reptDepth;
struct FileStackReptNode *copy = malloc(size);
if (!copy)
fatalerror("Failed to duplicate REPT file node: %s\n", strerror(errno));
/* Copy all info but the referencing */
// Copy all info but the referencing
memcpy(copy, fileInfo, size);
copy->node.next = NULL;
copy->node.referenced = false;
@@ -232,19 +232,19 @@ bool yywrap(void)
contextStack->fileInfo = (struct FileStackNode *)fileInfo;
}
/* If this is a FOR, update the symbol value */
// If this is a FOR, update the symbol value
if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
contextStack->forValue += contextStack->forStep;
struct Symbol *sym = sym_AddVar(contextStack->forName,
contextStack->forValue);
/* This error message will refer to the current iteration */
// This error message will refer to the current iteration
if (sym->type != SYM_VAR)
fatalerror("Failed to update FOR symbol value\n");
}
/* Advance to the next iteration */
// Advance to the next iteration
fileInfo->iters[0]++;
/* If this wasn't the last iteration, wrap instead of popping */
// If this wasn't the last iteration, wrap instead of popping
if (fileInfo->iters[0] <= contextStack->nbReptIters) {
lexer_RestartRept(contextStack->fileInfo->lineNo);
contextStack->uniqueID = macro_UseNewUniqueID();
@@ -261,15 +261,15 @@ bool yywrap(void)
contextDepth--;
lexer_DeleteState(context->lexerState);
/* Restore args if a macro (not REPT) saved them */
// Restore args if a macro (not REPT) saved them
if (context->fileInfo->type == NODE_MACRO)
macro_UseNewArgs(contextStack->macroArgs);
/* Free the file stack node */
// Free the file stack node
if (!context->fileInfo->referenced)
free(context->fileInfo);
/* Free the FOR symbol name */
// Free the FOR symbol name
free(context->forName);
/* Free the entry and make its parent the current entry */
// Free the entry and make its parent the current entry
free(context);
lexer_SetState(contextStack->lexerState);
@@ -277,30 +277,30 @@ bool yywrap(void)
return false;
}
/*
* Make sure not to switch the lexer state before calling this, so the saved line no is correct
* BE CAREFUL!! This modifies the file stack directly, you should have set up the file info first
* Callers should set contextStack->lexerState after this so it is not NULL
*/
// Make sure not to switch the lexer state before calling this, so the saved line no is correct.
// BE CAREFUL!! This modifies the file stack directly, you should have set up the file info first.
// Callers should set contextStack->lexerState after this so it is not NULL.
static void newContext(struct FileStackNode *fileInfo)
{
++contextDepth;
fstk_NewRecursionDepth(maxRecursionDepth); // Only checks if the max depth was exceeded
// Save the current `\@` value, to be restored when this context ends
contextStack->uniqueID = macro_GetUniqueID();
struct Context *context = malloc(sizeof(*context));
if (!context)
fatalerror("Failed to allocate memory for new context: %s\n", strerror(errno));
fileInfo->parent = contextStack->fileInfo;
fileInfo->lineNo = 0; /* Init to a default value, see struct definition for info */
fileInfo->lineNo = 0; // Init to a default value, see struct definition for info
fileInfo->referenced = false;
fileInfo->lineNo = lexer_GetLineNo();
context->fileInfo = fileInfo;
context->forName = NULL;
/*
* Link new entry to its parent so it's reachable later
* ERRORS SHOULD NOT OCCUR AFTER THIS!!
*/
// Link new entry to its parent so it's reachable later
// ERRORS SHOULD NOT OCCUR AFTER THIS!!
context->parent = contextStack;
contextStack = context;
}
@@ -338,9 +338,8 @@ void fstk_RunInclude(char const *path)
if (!contextStack->lexerState)
fatalerror("Failed to set up lexer for file include\n");
lexer_SetStateAtEOL(contextStack->lexerState);
/* We're back at top-level, so most things are reset */
contextStack->uniqueID = 0;
macro_SetUniqueID(0);
// We're back at top-level, so most things are reset
contextStack->uniqueID = macro_UndefUniqueID();
}
void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
@@ -357,16 +356,16 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
}
contextStack->macroArgs = macro_GetCurrentArgs();
/* Compute total length of this node's name: <base name>::<macro> */
// Compute total length of this node's name: <base name>::<macro>
size_t reptNameLen = 0;
struct FileStackNode const *node = macro->src;
if (node->type == NODE_REPT) {
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
/* 4294967295 = 2^32 - 1, aka UINT32_MAX */
// 4294967295 = 2^32 - 1, aka UINT32_MAX
reptNameLen += reptNode->reptDepth * strlen("::REPT~4294967295");
/* Look for next named node */
// Look for next named node
do {
node = node->parent;
} while (node->type == NODE_REPT);
@@ -382,7 +381,7 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
return;
}
fileInfo->node.type = NODE_MACRO;
/* Print the name... */
// Print the name...
char *dest = fileInfo->name;
memcpy(dest, baseNode->name, baseLen);
@@ -429,13 +428,13 @@ static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
fileInfo->reptDepth = reptDepth + 1;
fileInfo->iters[0] = 1;
if (reptDepth)
/* Copy all parent iter counts */
// Copy all parent iter counts
memcpy(&fileInfo->iters[1],
((struct FileStackReptNode *)contextStack->fileInfo)->iters,
reptDepth * sizeof(fileInfo->iters[0]));
newContext((struct FileStackNode *)fileInfo);
/* Correct our line number, which currently points to the `ENDR` line */
// Correct our line number, which currently points to the `ENDR` line
contextStack->fileInfo->lineNo = reptLineNo;
contextStack->lexerState = lexer_OpenFileView("REPT", body, size, reptLineNo);
@@ -493,7 +492,7 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
void fstk_StopRept(void)
{
/* Prevent more iterations */
// Prevent more iterations
contextStack->nbReptIters = 0;
}
@@ -510,7 +509,7 @@ bool fstk_Break(void)
void fstk_NewRecursionDepth(size_t newDepth)
{
if (contextDepth >= newDepth)
if (contextDepth > newDepth)
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
maxRecursionDepth = newDepth;
}
@@ -533,7 +532,7 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
fatalerror("Failed to allocate memory for main file info: %s\n", strerror(errno));
context->fileInfo = (struct FileStackNode *)fileInfo;
/* lineNo and reptIter are unused on the top-level context */
// lineNo and reptIter are unused on the top-level context
context->fileInfo->parent = NULL;
context->fileInfo->lineNo = 0; // This still gets written to the object file, so init it
context->fileInfo->referenced = false;
@@ -542,20 +541,17 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
context->parent = NULL;
context->lexerState = state;
context->uniqueID = 0;
macro_SetUniqueID(0);
context->uniqueID = macro_UndefUniqueID();
context->nbReptIters = 0;
context->forValue = 0;
context->forStep = 0;
context->forName = NULL;
/* Now that it's set up properly, register the context */
// Now that it's set up properly, register the context
contextStack = context;
/*
* Check that max recursion depth won't allow overflowing node `malloc`s
* This assumes that the rept node is larger
*/
// Check that max recursion depth won't allow overflowing node `malloc`s
// This assumes that the rept node is larger
#define DEPTH_LIMIT ((SIZE_MAX - sizeof(struct FileStackReptNode)) / sizeof(uint32_t))
if (maxDepth > DEPTH_LIMIT) {
error("Recursion depth may not be higher than %zu, defaulting to "
@@ -564,7 +560,7 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
} else {
maxRecursionDepth = maxDepth;
}
/* Make sure that the default of 64 is OK, though */
// Make sure that the default of 64 is OK, though
assert(DEPTH_LIMIT >= DEFAULT_MAX_DEPTH);
#undef DEPTH_LIMIT
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,11 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
@@ -12,13 +18,11 @@
#define MAXMACROARGS 99999
/*
* Your average macro invocation does not go past the tens, but some go further
* This ensures that sane and slightly insane invocations suffer no penalties,
* and the rest is insane and thus will assume responsibility.
* Additionally, ~300 bytes (on x64) of memory per level of nesting has been
* deemed reasonable. (Halve that on x86.)
*/
// Your average macro invocation does not go past the tens, but some go further
// This ensures that sane and slightly insane invocations suffer no penalties,
// and the rest is insane and thus will assume responsibility.
// Additionally, ~300 bytes (on x64) of memory per level of nesting has been
// deemed reasonable. (Halve that on x86.)
#define INITIAL_ARG_SIZE 32
struct MacroArgs {
unsigned int nbArgs;
@@ -33,11 +37,9 @@ struct MacroArgs {
static struct MacroArgs *macroArgs = NULL;
static uint32_t uniqueID = 0;
static uint32_t maxUniqueID = 0;
/*
* The initialization is somewhat harmful, since it is never used, but it
* guarantees the size of the buffer will be correct. I was unable to find a
* better solution, but if you have one, please feel free!
*/
// The initialization is somewhat harmful, since it is never used, but it
// guarantees the size of the buffer will be correct. I was unable to find a
// better solution, but if you have one, please feel free!
static char uniqueIDBuf[] = "_u4294967295"; // UINT32_MAX
static char *uniqueIDPtr = NULL;
@@ -68,7 +70,7 @@ void macro_AppendArg(struct MacroArgs **argPtr, char *s)
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
if (macArgs->nbArgs >= macArgs->capacity) {
macArgs->capacity *= 2;
/* Check that overflow didn't roll us back */
// Check that overflow didn't roll us back
if (macArgs->capacity <= macArgs->nbArgs)
fatalerror("Failed to add new macro argument: capacity overflow\n");
macArgs = realloc(macArgs, SIZEOF_ARGS(macArgs->capacity));
@@ -112,9 +114,9 @@ char const *macro_GetAllArgs(void)
size_t len = 0;
for (uint32_t i = macroArgs->shift; i < macroArgs->nbArgs; i++)
len += strlen(macroArgs->args[i]) + 1; /* 1 for comma */
len += strlen(macroArgs->args[i]) + 1; // 1 for comma
char *str = malloc(len + 1); /* 1 for '\0' */
char *str = malloc(len + 1); // 1 for '\0'
char *ptr = str;
if (!str)
@@ -126,9 +128,9 @@ char const *macro_GetAllArgs(void)
memcpy(ptr, macroArgs->args[i], n);
ptr += n;
/* Commas go between args and after a last empty arg */
// Commas go between args and after a last empty arg
if (i < macroArgs->nbArgs - 1 || n == 0)
*ptr++ = ','; /* no space after comma */
*ptr++ = ','; // no space after comma
}
*ptr = '\0';
@@ -142,19 +144,21 @@ uint32_t macro_GetUniqueID(void)
char const *macro_GetUniqueIDStr(void)
{
// Generate a new unique ID on the first use of `\@`
if (uniqueID == 0)
macro_SetUniqueID(++maxUniqueID);
return uniqueIDPtr;
}
void macro_SetUniqueID(uint32_t id)
{
uniqueID = id;
if (id == 0) {
if (id == 0 || id == (uint32_t)-1) {
uniqueIDPtr = NULL;
} else {
if (uniqueID > maxUniqueID)
maxUniqueID = uniqueID;
/* The buffer is guaranteed to be the correct size */
/* This is a valid label fragment, but not a valid numeric */
// The buffer is guaranteed to be the correct size
// This is a valid label fragment, but not a valid numeric
sprintf(uniqueIDBuf, "_u%" PRIu32, id);
uniqueIDPtr = uniqueIDBuf;
}
@@ -162,8 +166,16 @@ void macro_SetUniqueID(uint32_t id)
uint32_t macro_UseNewUniqueID(void)
{
macro_SetUniqueID(++maxUniqueID);
return maxUniqueID;
// A new ID will be generated on the first use of `\@`
macro_SetUniqueID(0);
return uniqueID;
}
uint32_t macro_UndefUniqueID(void)
{
// No ID will be generated; use of `\@` is an error
macro_SetUniqueID((uint32_t)-1);
return uniqueID;
}
void macro_ShiftCurrentArgs(int32_t count)

View File

@@ -19,6 +19,7 @@
#include <time.h>
#include "asm/charmap.h"
#include "asm/fixpoint.h"
#include "asm/format.h"
#include "asm/fstack.h"
#include "asm/lexer.h"
@@ -39,8 +40,8 @@
#ifdef __clang__
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
#define __SANITIZE_ADDRESS__
#endif /* __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) */
#endif /* __clang__ */
#endif // __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
#endif // __clang__
#ifdef __SANITIZE_ADDRESS__
// There are known, non-trivial to fix leaks. We would still like to have `make develop'
@@ -59,11 +60,13 @@ bool generatePhonyDeps;
char *targetFileName;
bool haltnop;
bool warnOnHaltNop;
bool optimizeLoads;
bool warnOnLdOpt;
bool verbose;
bool warnings; /* True to enable warnings, false to disable them. */
bool warnings; // True to enable warnings, false to disable them.
/* Escapes Make-special chars from a string */
// Escapes Make-special chars from a string
static char *make_escape(char const *str)
{
char * const escaped_str = malloc(strlen(str) * 2 + 1);
@@ -73,7 +76,7 @@ static char *make_escape(char const *str)
err("%s: Failed to allocate memory", __func__);
while (*str) {
/* All dollars needs to be doubled */
// All dollars needs to be doubled
if (*str == '$')
*dest++ = '$';
*dest++ = *str++;
@@ -83,30 +86,30 @@ static char *make_escape(char const *str)
return escaped_str;
}
/* Short options */
static const char *optstring = "b:D:Eg:hi:LM:o:p:r:VvW:w";
// Short options
static const char *optstring = "b:D:Eg:Hhi:LlM:o:p:Q:r:VvW:w";
/* Variables for the long-only options */
static int depType; /* Variants of `-M` */
// Variables for the long-only options
static int depType; // Variants of `-M`
/*
* Equivalent long options
* Please keep in the same order as short opts
*
* Also, make sure long opts don't create ambiguity:
* A long opt's name should start with the same letter as its short opt,
* except if it doesn't create any ambiguity (`verbose` versus `version`).
* This is because long opt matching, even to a single char, is prioritized
* over short opt matching
*/
// Equivalent long options
// Please keep in the same order as short opts
//
// Also, make sure long opts don't create ambiguity:
// A long opt's name should start with the same letter as its short opt,
// except if it doesn't create any ambiguity (`verbose` versus `version`).
// This is because long opt matching, even to a single char, is prioritized
// over short opt matching
static struct option const longopts[] = {
{ "binary-digits", required_argument, NULL, 'b' },
{ "define", required_argument, NULL, 'D' },
{ "export-all", no_argument, NULL, 'E' },
{ "gfx-chars", required_argument, NULL, 'g' },
{ "nop-after-halt", no_argument, NULL, 'H' },
{ "halt-without-nop", no_argument, NULL, 'h' },
{ "include", required_argument, NULL, 'i' },
{ "preserve-ld", no_argument, NULL, 'L' },
{ "auto-ldh", no_argument, NULL, 'l' },
{ "dependfile", required_argument, NULL, 'M' },
{ "MG", no_argument, &depType, 'G' },
{ "MP", no_argument, &depType, 'P' },
@@ -114,6 +117,7 @@ static struct option const longopts[] = {
{ "MQ", required_argument, &depType, 'Q' },
{ "output", required_argument, NULL, 'o' },
{ "pad-value", required_argument, NULL, 'p' },
{ "q-precision", required_argument, NULL, 'Q' },
{ "recursion-depth", required_argument, NULL, 'r' },
{ "version", no_argument, NULL, 'V' },
{ "verbose", no_argument, NULL, 'v' },
@@ -124,9 +128,10 @@ static struct option const longopts[] = {
static void print_usage(void)
{
fputs(
"Usage: rgbasm [-EhLVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n"
"Usage: rgbasm [-EHhLlVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n"
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
" [-o out_file] [-p pad_value] [-r depth] [-W warning] <file>\n"
" [-o out_file] [-p pad_value] [-Q precision] [-r depth]\n"
" [-W warning] <file>\n"
"Useful options:\n"
" -E, --export-all export all labels\n"
" -M, --dependfile <path> set the output dependency file\n"
@@ -142,19 +147,14 @@ static void print_usage(void)
int main(int argc, char *argv[])
{
#if YYDEBUG
yydebug = 1;
#endif
int ch;
char *ep;
time_t now = time(NULL);
char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH");
/*
* Support SOURCE_DATE_EPOCH for reproducible builds
* https://reproducible-builds.org/docs/source-date-epoch/
*/
// Support SOURCE_DATE_EPOCH for reproducible builds
// https://reproducible-builds.org/docs/source-date-epoch/
if (sourceDateEpoch)
now = (time_t)strtoul(sourceDateEpoch, NULL, 0);
@@ -173,12 +173,15 @@ int main(int argc, char *argv[])
opt_B("01");
opt_G("0123");
opt_P(0);
optimizeLoads = true;
opt_Q(16);
haltnop = true;
warnOnHaltNop = true;
optimizeLoads = true;
warnOnLdOpt = true;
verbose = false;
warnings = true;
sym_SetExportAll(false);
uint32_t maxDepth = 64;
uint32_t maxDepth = DEFAULT_MAX_DEPTH;
size_t targetFileNameLen = 0;
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
@@ -212,7 +215,14 @@ int main(int argc, char *argv[])
errx("Must specify exactly 4 characters for option 'g'");
break;
case 'H':
if (!haltnop)
errx("`-H` and `-h` don't make sense together");
warnOnHaltNop = false;
break;
case 'h':
if (!warnOnHaltNop)
errx("`-H` and `-h` don't make sense together");
haltnop = false;
break;
@@ -221,8 +231,15 @@ int main(int argc, char *argv[])
break;
case 'L':
if (!warnOnLdOpt)
errx("`-L` and `-l` don't make sense together");
optimizeLoads = false;
break;
case 'l':
if (!optimizeLoads)
errx("`-L` and `-l` don't make sense together");
warnOnLdOpt = false;
break;
case 'M':
if (!strcmp("-", musl_optarg))
@@ -237,17 +254,34 @@ int main(int argc, char *argv[])
out_SetFileName(musl_optarg);
break;
unsigned long fill;
unsigned long padByte;
case 'p':
fill = strtoul(musl_optarg, &ep, 0);
padByte = strtoul(musl_optarg, &ep, 0);
if (musl_optarg[0] == '\0' || *ep != '\0')
errx("Invalid argument for option 'p'");
if (fill > 0xFF)
if (padByte > 0xFF)
errx("Argument for option 'p' must be between 0 and 0xFF");
opt_P(fill);
opt_P(padByte);
break;
unsigned long precision;
const char *precisionArg;
case 'Q':
precisionArg = musl_optarg;
if (precisionArg[0] == '.')
precisionArg++;
precision = strtoul(precisionArg, &ep, 0);
if (musl_optarg[0] == '\0' || *ep != '\0')
errx("Invalid argument for option 'Q'");
if (precision < 1 || precision > 31)
errx("Argument for option 'Q' must be between 1 and 31");
opt_Q(precision);
break;
case 'r':
@@ -260,6 +294,7 @@ int main(int argc, char *argv[])
case 'V':
printf("rgbasm %s\n", get_package_version_string());
exit(0);
case 'v':
verbose = true;
break;
@@ -272,7 +307,7 @@ int main(int argc, char *argv[])
warnings = false;
break;
/* Long-only options */
// Long-only options
case 0:
switch (depType) {
case 'G':
@@ -304,10 +339,10 @@ int main(int argc, char *argv[])
}
break;
/* Unrecognized options */
// Unrecognized options
default:
print_usage();
/* NOTREACHED */
// NOTREACHED
}
}
@@ -338,7 +373,7 @@ int main(int argc, char *argv[])
charmap_New("main", NULL);
// Init lexer and file stack, prodiving file info
// Init lexer and file stack, providing file info
lexer_Init();
fstk_Init(mainFileName, maxDepth);
@@ -359,7 +394,7 @@ int main(int argc, char *argv[])
if (failedOnMissingInclude)
return 0;
/* If no path specified, don't write file */
// If no path specified, don't write file
if (objectName != NULL)
out_WriteObject();
return 0;

View File

@@ -1,3 +1,11 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <ctype.h>
#include <errno.h>
#include <stdbool.h>
@@ -6,6 +14,7 @@
#include <stdlib.h>
#include <string.h>
#include "asm/fixpoint.h"
#include "asm/fstack.h"
#include "asm/lexer.h"
#include "asm/main.h"
@@ -15,9 +24,12 @@
struct OptStackEntry {
char binary[2];
char gbgfx[4];
int32_t fillByte;
uint8_t fixPrecision;
uint8_t fillByte;
bool haltnop;
bool warnOnHaltNop;
bool optimizeLoads;
bool warnOnLdOpt;
bool warningsAreErrors;
size_t maxRecursionDepth;
// Don't be confused: we use the size of the **global variable** `warningStates`!
@@ -37,9 +49,14 @@ void opt_G(char const chars[4])
lexer_SetGfxDigits(chars);
}
void opt_P(uint8_t fill)
void opt_P(uint8_t padByte)
{
fillByte = fill;
fillByte = padByte;
}
void opt_Q(uint8_t precision)
{
fixPrecision = precision;
}
void opt_R(size_t newDepth)
@@ -48,6 +65,11 @@ void opt_R(size_t newDepth)
lexer_CheckRecursionDepth();
}
void opt_H(bool warn)
{
warnOnHaltNop = warn;
}
void opt_h(bool halt)
{
haltnop = halt;
@@ -58,6 +80,11 @@ void opt_L(bool optimize)
optimizeLoads = optimize;
}
void opt_l(bool warn)
{
warnOnLdOpt = warn;
}
void opt_W(char *flag)
{
processWarningFlag(flag);
@@ -83,18 +110,41 @@ void opt_Parse(char *s)
case 'p':
if (strlen(&s[1]) <= 2) {
int result;
unsigned int fillchar;
unsigned int padByte;
result = sscanf(&s[1], "%x", &fillchar);
if (result != EOF && result != 1)
result = sscanf(&s[1], "%x", &padByte);
if (result != 1)
error("Invalid argument for option 'p'\n");
else if (padByte > 0xFF)
error("Argument for option 'p' must be between 0 and 0xFF\n");
else
opt_P(fillchar);
opt_P(padByte);
} else {
error("Invalid argument for option 'p'\n");
}
break;
const char *precisionArg;
case 'Q':
precisionArg = &s[1];
if (precisionArg[0] == '.')
precisionArg++;
if (strlen(precisionArg) <= 2) {
int result;
unsigned int precision;
result = sscanf(precisionArg, "%u", &precision);
if (result != 1)
error("Invalid argument for option 'Q'\n");
else if (precision < 1 || precision > 31)
error("Argument for option 'Q' must be between 1 and 31\n");
else
opt_Q(precision);
} else {
error("Invalid argument for option 'Q'\n");
}
break;
case 'r': {
++s; // Skip 'r'
while (isblank(*s))
@@ -118,6 +168,13 @@ void opt_Parse(char *s)
break;
}
case 'H':
if (s[1] == '\0')
opt_H(false);
else
error("Option 'H' does not take an argument\n");
break;
case 'h':
if (s[1] == '\0')
opt_h(false);
@@ -132,6 +189,13 @@ void opt_Parse(char *s)
error("Option 'L' does not take an argument\n");
break;
case 'l':
if (s[1] == '\0')
opt_l(false);
else
error("Option 'l' does not take an argument\n");
break;
case 'W':
if (strlen(&s[1]) > 0)
opt_W(&s[1]);
@@ -141,6 +205,13 @@ void opt_Parse(char *s)
case '!': // negates flag options that do not take an argument
switch (s[1]) {
case 'H':
if (s[2] == '\0')
opt_H(true);
else
error("Option '!H' does not take an argument\n");
break;
case 'h':
if (s[2] == '\0')
opt_h(true);
@@ -155,6 +226,13 @@ void opt_Parse(char *s)
error("Option '!L' does not take an argument\n");
break;
case 'l':
if (s[2] == '\0')
opt_l(true);
else
error("Option '!l' does not take an argument\n");
break;
default:
error("Unknown option '!%c'\n", s[1]);
break;
@@ -183,11 +261,15 @@ void opt_Push(void)
entry->gbgfx[2] = gfxDigits[2];
entry->gbgfx[3] = gfxDigits[3];
entry->fixPrecision = fixPrecision; // Pulled from fixpoint.h
entry->fillByte = fillByte; // Pulled from section.h
entry->haltnop = haltnop; // Pulled from main.h
entry->warnOnHaltNop = warnOnHaltNop;
entry->optimizeLoads = optimizeLoads; // Pulled from main.h
entry->warnOnLdOpt = warnOnLdOpt;
// Both of these pulled from warning.h
entry->warningsAreErrors = warningsAreErrors;
@@ -209,8 +291,11 @@ void opt_Pop(void)
opt_B(entry->binary);
opt_G(entry->gbgfx);
opt_P(entry->fillByte);
opt_Q(entry->fixPrecision);
opt_H(entry->warnOnHaltNop);
opt_h(entry->haltnop);
opt_L(entry->optimizeLoads);
opt_l(entry->warnOnLdOpt);
// opt_W does not apply a whole warning state; it processes one flag string
warningsAreErrors = entry->warningsAreErrors;

View File

@@ -6,9 +6,7 @@
* SPDX-License-Identifier: MIT
*/
/*
* Outputs an objectfile
*/
// Outputs an objectfile
#include <assert.h>
#include <errno.h>
@@ -54,18 +52,16 @@ char *objectName;
struct Section *sectionList;
/* Linked list of symbols to put in the object file */
// Linked list of symbols to put in the object file
static struct Symbol *objectSymbols = NULL;
static struct Symbol **objectSymbolsTail = &objectSymbols;
static uint32_t nbSymbols = 0; /* Length of the above list */
static uint32_t nbSymbols = 0; // Length of the above list
static struct Assertion *assertions = NULL;
static struct FileStackNode *fileStackNodes = NULL;
/*
* Count the number of sections used in this object
*/
// Count the number of sections used in this object
static uint32_t countSections(void)
{
uint32_t count = 0;
@@ -76,9 +72,7 @@ static uint32_t countSections(void)
return count;
}
/*
* Count the number of patches used in this object
*/
// Count the number of patches used in this object
static uint32_t countPatches(struct Section const *sect)
{
uint32_t r = 0;
@@ -90,9 +84,7 @@ static uint32_t countPatches(struct Section const *sect)
return r;
}
/**
* Count the number of assertions used in this object
*/
// Count the number of assertions used in this object
static uint32_t countAsserts(void)
{
struct Assertion *assert = assertions;
@@ -105,9 +97,7 @@ static uint32_t countAsserts(void)
return count;
}
/*
* Write a long to a file (little-endian)
*/
// Write a long to a file (little-endian)
static void putlong(uint32_t i, FILE *f)
{
putc(i, f);
@@ -116,9 +106,7 @@ static void putlong(uint32_t i, FILE *f)
putc(i >> 24, f);
}
/*
* Write a NULL-terminated string to a file
*/
// Write a NULL-terminated string to a file
static void putstring(char const *s, FILE *f)
{
while (*s)
@@ -133,7 +121,7 @@ static uint32_t getNbFileStackNodes(void)
void out_RegisterNode(struct FileStackNode *node)
{
/* If node is not already registered, register it (and parents), and give it a unique ID */
// If node is not already registered, register it (and parents), and give it a unique ID
while (node->ID == (uint32_t)-1) {
node->ID = getNbFileStackNodes();
if (node->ID == (uint32_t)-1)
@@ -141,7 +129,7 @@ void out_RegisterNode(struct FileStackNode *node)
node->next = fileStackNodes;
fileStackNodes = node;
/* Also register the node's parents */
// Also register the node's parents
node = node->parent;
if (!node)
break;
@@ -156,25 +144,21 @@ This is code intended to replace a node, which is pretty useless until ref count
struct FileStackNode **ptr = &fileStackNodes;
/*
* The linked list is supposed to have decrementing IDs, so iterate with less memory reads,
* to hopefully hit the cache less. A debug check is added after, in case a change is made
* that breaks this assumption.
*/
// The linked list is supposed to have decrementing IDs, so iterate with less memory reads,
// to hopefully hit the cache less. A debug check is added after, in case a change is made
// that breaks this assumption.
for (uint32_t i = fileStackNodes->ID; i != node->ID; i--)
ptr = &(*ptr)->next;
assert((*ptr)->ID == node->ID);
node->next = (*ptr)->next;
assert(!node->next || node->next->ID == node->ID - 1); /* Catch inconsistencies early */
/* TODO: unreference the node */
assert(!node->next || node->next->ID == node->ID - 1); // Catch inconsistencies early
// TODO: unreference the node
*ptr = node;
#endif
}
/*
* Return a section's ID
*/
// Return a section's ID
static uint32_t getsectid(struct Section const *sect)
{
struct Section const *sec = sectionList;
@@ -195,9 +179,7 @@ static uint32_t getSectIDIfAny(struct Section const *sect)
return sect ? getsectid(sect) : (uint32_t)-1;
}
/*
* Write a patch to a file
*/
// Write a patch to a file
static void writepatch(struct Patch const *patch, FILE *f)
{
assert(patch->src->ID != (uint32_t)-1);
@@ -211,9 +193,7 @@ static void writepatch(struct Patch const *patch, FILE *f)
fwrite(patch->rpn, 1, patch->rpnSize, f);
}
/*
* Write a section to a file
*/
// Write a section to a file
static void writesection(struct Section const *sect, FILE *f)
{
putstring(sect->name, f);
@@ -255,9 +235,7 @@ static void freesection(struct Section const *sect)
}
}
/*
* Write a symbol to a file
*/
// Write a symbol to a file
static void writesymbol(struct Symbol const *sym, FILE *f)
{
putstring(sym->name, f);
@@ -285,10 +263,8 @@ static void registerSymbol(struct Symbol *sym)
sym->ID = nbSymbols++;
}
/*
* Returns a symbol's ID within the object file
* If the symbol does not have one, one is assigned by registering the symbol
*/
// Returns a symbol's ID within the object file
// If the symbol does not have one, one is assigned by registering the symbol
static uint32_t getSymbolID(struct Symbol *sym)
{
if (sym->ID == (uint32_t)-1 && !sym_IsPC(sym))
@@ -392,10 +368,8 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
}
}
/*
* Allocate a new patch structure and link it into the list
* WARNING: all patches are assumed to eventually be written, so the file stack node is registered
*/
// Allocate a new patch structure and link it into the list
// WARNING: all patches are assumed to eventually be written, so the file stack node is registered
static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
{
struct Patch *patch = malloc(sizeof(struct Patch));
@@ -417,10 +391,10 @@ static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, ui
patch->pcSection = sect_GetSymbolSection();
patch->pcOffset = sect_GetSymbolOffset();
/* If the rpnSize's value is known, output a constant RPN rpnSize directly */
// If the rpnSize's value is known, output a constant RPN rpnSize directly
if (expr->isKnown) {
patch->rpnSize = rpnSize;
/* Make sure to update `rpnSize` above if modifying this! */
// Make sure to update `rpnSize` above if modifying this!
patch->rpn[0] = RPN_CONST;
patch->rpn[1] = (uint32_t)(expr->val) & 0xFF;
patch->rpn[2] = (uint32_t)(expr->val) >> 8;
@@ -435,9 +409,7 @@ static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, ui
return patch;
}
/*
* Create a new patch (includes the rpn expr)
*/
// Create a new patch (includes the rpn expr)
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift)
{
struct Patch *patch = allocpatch(type, expr, ofs);
@@ -451,9 +423,7 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs,
currentSection->patches = patch;
}
/**
* Creates an assert that will be written to the object file
*/
// Creates an assert that will be written to the object file
bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
char const *message, uint32_t ofs)
{
@@ -499,7 +469,7 @@ static void writeFileStackNode(struct FileStackNode const *node, FILE *f)
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
putlong(reptNode->reptDepth, f);
/* Iters are stored by decreasing depth, so reverse the order for output */
// Iters are stored by decreasing depth, so reverse the order for output
for (uint32_t i = reptNode->reptDepth; i--; )
putlong(reptNode->iters[i], f);
}
@@ -515,9 +485,7 @@ static void registerUnregisteredSymbol(struct Symbol *symbol, void *arg)
}
}
/*
* Write an objectfile
*/
// Write an objectfile
void out_WriteObject(void)
{
FILE *f;
@@ -529,10 +497,10 @@ void out_WriteObject(void)
if (!f)
err("Couldn't write file '%s'", objectName);
/* Also write symbols that weren't written above */
// Also write symbols that weren't written above
sym_ForEach(registerUnregisteredSymbol, NULL);
fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER);
fprintf(f, RGBDS_OBJECT_VERSION_STRING);
putlong(RGBDS_OBJECT_REV, f);
putlong(nbSymbols, f);
@@ -569,9 +537,7 @@ void out_WriteObject(void)
fclose(f);
}
/*
* Set the objectfilename
*/
// Set the objectfilename
void out_SetFileName(char *s)
{
objectName = s;

View File

@@ -36,7 +36,7 @@
#include "linkdefs.h"
#include "platform.h" // strncasecmp, strdup
static struct CaptureBody captureBody; /* Captures a REPT/FOR or MACRO */
static struct CaptureBody captureBody; // Captures a REPT/FOR or MACRO
static void upperstring(char *dest, char const *src)
{
@@ -104,14 +104,14 @@ static size_t strlenUTF8(char const *s)
case 1:
errorInvalidUTF8Byte(byte, "STRLEN");
state = 0;
/* fallthrough */
// fallthrough
case 0:
len++;
break;
}
}
/* Check for partial code point. */
// Check for partial code point.
if (state != 0)
error("STRLEN: Incomplete UTF-8 character\n");
@@ -127,13 +127,13 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
uint32_t curLen = 0;
uint32_t curPos = 1;
/* Advance to starting position in source string. */
// Advance to starting position in source string.
while (src[srcIndex] && curPos < pos) {
switch (decode(&state, &codep, src[srcIndex])) {
case 1:
errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
state = 0;
/* fallthrough */
// fallthrough
case 0:
curPos++;
break;
@@ -141,21 +141,19 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
srcIndex++;
}
/*
* A position 1 past the end of the string is allowed, but will trigger the
* "Length too big" warning below if the length is nonzero.
*/
// A position 1 past the end of the string is allowed, but will trigger the
// "Length too big" warning below if the length is nonzero.
if (!src[srcIndex] && pos > curPos)
warning(WARNING_BUILTIN_ARG,
"STRSUB: Position %" PRIu32 " is past the end of the string\n", pos);
/* Copy from source to destination. */
// Copy from source to destination.
while (src[srcIndex] && destIndex < destLen - 1 && curLen < len) {
switch (decode(&state, &codep, src[srcIndex])) {
case 1:
errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
state = 0;
/* fallthrough */
// fallthrough
case 0:
curLen++;
break;
@@ -166,7 +164,7 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
if (curLen < len)
warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %" PRIu32 "\n", len);
/* Check for partial code point. */
// Check for partial code point.
if (state != 0)
error("STRSUB: Incomplete UTF-8 character\n");
@@ -187,7 +185,7 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
{
size_t charLen = 1;
/* Advance to starting position in source string. */
// Advance to starting position in source string.
for (uint32_t curPos = 1; charLen && curPos < pos; curPos++)
charLen = charmap_ConvertNext(&src, NULL);
@@ -197,7 +195,7 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
warning(WARNING_BUILTIN_ARG,
"CHARSUB: Position %" PRIu32 " is past the end of the string\n", pos);
/* Copy from source to destination. */
// Copy from source to destination.
memcpy(dest, start, src - start);
dest[src - start] = '\0';
@@ -205,10 +203,8 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionName)
{
/*
* STRSUB and CHARSUB adjust negative `pos` arguments the same way,
* such that position -1 is the last character of a string.
*/
// STRSUB and CHARSUB adjust negative `pos` arguments the same way,
// such that position -1 is the last character of a string.
if (pos < 0)
pos += len + 1;
if (pos < 1) {
@@ -545,7 +541,7 @@ enum {
%left T_OP_SHL T_OP_SHR T_OP_USHR
%left T_OP_MUL T_OP_DIV T_OP_MOD
%precedence NEG /* negation -- unary minus */
%precedence NEG // negation -- unary minus
%token T_OP_EXP "**"
%left T_OP_EXP
@@ -558,6 +554,7 @@ enum {
%token T_OP_ASIN "ASIN" T_OP_ACOS "ACOS" T_OP_ATAN "ATAN" T_OP_ATAN2 "ATAN2"
%token T_OP_FDIV "FDIV"
%token T_OP_FMUL "FMUL"
%token T_OP_FMOD "FMOD"
%token T_OP_POW "POW"
%token T_OP_LOG "LOG"
%token T_OP_ROUND "ROUND"
@@ -587,7 +584,6 @@ enum {
%type <symName> scoped_id
%type <symName> scoped_anon_id
%token T_POP_EQU "EQU"
%token T_POP_SET "SET"
%token T_POP_EQUAL "="
%token T_POP_EQUS "EQUS"
@@ -599,7 +595,6 @@ enum {
%token T_POP_INCLUDE "INCLUDE"
%token T_POP_PRINT "PRINT" T_POP_PRINTLN "PRINTLN"
%token T_POP_PRINTF "PRINTF" T_POP_PRINTT "PRINTT" T_POP_PRINTV "PRINTV" T_POP_PRINTI "PRINTI"
%token T_POP_IF "IF" T_POP_ELIF "ELIF" T_POP_ELSE "ELSE" T_POP_ENDC "ENDC"
%token T_POP_EXPORT "EXPORT"
%token T_POP_DB "DB" T_POP_DS "DS" T_POP_DW "DW" T_POP_DL "DL"
@@ -642,36 +637,35 @@ enum {
%type <forArgs> for_args
%token T_Z80_ADC "adc" T_Z80_ADD "add" T_Z80_AND "and"
%token T_Z80_BIT "bit" // There is no T_Z80_SET, only T_POP_SET
%token T_Z80_BIT "bit"
%token T_Z80_CALL "call" T_Z80_CCF "ccf" T_Z80_CP "cp" T_Z80_CPL "cpl"
%token T_Z80_DAA "daa" T_Z80_DEC "dec" T_Z80_DI "di"
%token T_Z80_EI "ei"
%token T_Z80_HALT "halt"
%token T_Z80_HALT "halt"
%token T_Z80_INC "inc"
%token T_Z80_JP "jp" T_Z80_JR "jr"
%token T_Z80_LD "ld"
%token T_Z80_LDI "ldi"
%token T_Z80_LDD "ldd"
%token T_Z80_LDH "ldh"
%token T_Z80_NOP "nope"
%token T_Z80_NOP "nop"
%token T_Z80_OR "or"
%token T_OWO "owo"
%token T_Z80_POP "pop" T_Z80_PUSH "push"
%token T_Z80_RES "res" T_Z80_RET "ret" T_Z80_RETI "reti" T_Z80_RST "rst"
%token T_Z80_RL "rl" T_Z80_RLA "rla" T_Z80_RLC "rlc" T_Z80_RLCA "rlca"
%token T_Z80_RR "rr" T_Z80_RRA "rra" T_Z80_RRC "rrc" T_Z80_RRCA "rrca"
%token T_Z80_SBC "sbc" T_Z80_SCF "scf" T_Z80_STOP "stop!!🛑"
%token T_Z80_SBC "sbc" T_Z80_SCF "scf" T_Z80_SET "set" T_Z80_STOP "stop"
%token T_Z80_SLA "sla" T_Z80_SRA "sra" T_Z80_SRL "srl" T_Z80_SUB "sub"
%token T_Z80_SWAP "swap"
%token T_Z80_XOR "xor"
%token T_TOKEN_A "( •̀A•́)" T_TOKEN_F "𝓕𝓾𝓬𝓴"
%token T_TOKEN_B "=B" T_TOKEN_C "♥(˘⌣˘ C)"
%token T_TOKEN_D ";D" T_TOKEN_E "(´ε` )♡" T_TOKEN_E_HEART "(´ε` )♡"
%token T_TOKEN_H "н" T_TOKEN_L_ARM "∠( ᐛ 」∠)_" T_TOKEN_L_FACE "∠( ᐛ 」∠)_" T_TOKEN_L_BODY "∠( ᐛ 」∠)_" T_TOKEN_L_LEG "∠( ᐛ 」∠)_"
%token T_MODE_AF "af" /* T_MODE_BC "bc" T_MODE_DE "de" */ T_MODE_SP "sp"
%token T_MODE_HL_START "н∠( ᐛ 」∠)_" T_MODE_HL_DEC "hld/hl-" T_MODE_HL_INC "hli/hl+"
%token T_CC_NZ "nz" T_CC_Z "z" T_CC_NC "nc" T_CC_C "c"
%token T_TOKEN_A "a"
%token T_TOKEN_B "b" T_TOKEN_C "c"
%token T_TOKEN_D "d" T_TOKEN_E "e"
%token T_TOKEN_H "h" T_TOKEN_L "l"
%token T_MODE_AF "af" T_MODE_BC "bc" T_MODE_DE "de" T_MODE_SP "sp"
%token T_MODE_HL "hl" T_MODE_HL_DEC "hld/hl-" T_MODE_HL_INC "hli/hl+"
%token T_CC_NZ "nz" T_CC_Z "z" T_CC_NC "nc" // There is no T_CC_C, only T_TOKEN_C
%type <constValue> reg_r
%type <constValue> reg_ss
@@ -708,8 +702,8 @@ plain_directive : label
;
line : plain_directive endofline
| line_directive /* Directives that manage newlines themselves */
/* Continue parsing the next line on a syntax error */
| line_directive // Directives that manage newlines themselves
// Continue parsing the next line on a syntax error
| error {
lexer_SetMode(LEXER_NORMAL);
lexer_ToggleStringExpansion(true);
@@ -717,7 +711,7 @@ line : plain_directive endofline
fstk_StopRept();
yyerrok;
}
/* Hint about unindented macros parsed as labels */
// Hint about unindented macros parsed as labels
| T_LABEL error {
lexer_SetMode(LEXER_NORMAL);
lexer_ToggleStringExpansion(true);
@@ -732,19 +726,17 @@ line : plain_directive endofline
}
;
/*
* For "logistical" reasons, these directives must manage newlines themselves.
* This is because we need to switch the lexer's mode *after* the newline has been read,
* and to avoid causing some grammar conflicts (token reducing is finicky).
* This is DEFINITELY one of the more FRAGILE parts of the codebase, handle with care.
*/
// For "logistical" reasons, these directives must manage newlines themselves.
// This is because we need to switch the lexer's mode *after* the newline has been read,
// and to avoid causing some grammar conflicts (token reducing is finicky).
// This is DEFINITELY one of the more FRAGILE parts of the codebase, handle with care.
line_directive : macrodef
| rept
| for
| break
| include
| if
/* It's important that all of these require being at line start for `skipIfBlock` */
// It's important that all of these require being at line start for `skipIfBlock`
| elif
| else
;
@@ -855,9 +847,9 @@ macroargs : %empty {
}
;
/* These commands start with a T_LABEL. */
// These commands start with a T_LABEL.
assignment_directive : equ
| set
| assignment
| rb
| rw
| rl
@@ -867,10 +859,6 @@ assignment_directive : equ
directive : endc
| print
| println
| printf
| printt
| printv
| printi
| export
| db
| dw
@@ -928,12 +916,8 @@ compoundeq : T_POP_ADDEQ { $$ = RPN_ADD; }
equ : T_LABEL T_POP_EQU const { sym_AddEqu($1, $3); }
;
set : T_LABEL T_POP_EQUAL const { sym_AddVar($1, $3); }
assignment : T_LABEL T_POP_EQUAL const { sym_AddVar($1, $3); }
| T_LABEL compoundeq const { compoundAssignment($1, $2, $3); }
| T_LABEL T_POP_SET const {
warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
sym_AddVar($1, $3);
}
;
equs : T_LABEL T_POP_EQUS string { sym_AddString($1, $3); }
@@ -1109,6 +1093,7 @@ macrodef : T_POP_MACRO {
captureBody.size);
}
| T_LABEL T_COLON T_POP_MACRO T_NEWLINE {
warning(WARNING_OBSOLETE, "`%s: MACRO` is deprecated; use `MACRO %s`\n", $1, $1);
$<captureTerminated>$ = lexer_CaptureMacroBody(&captureBody);
} endofline {
if ($<captureTerminated>5)
@@ -1175,14 +1160,6 @@ def_set : def_id T_POP_EQUAL const { sym_AddVar($1, $3); }
| redef_id T_POP_EQUAL const { sym_AddVar($1, $3); }
| def_id compoundeq const { compoundAssignment($1, $2, $3); }
| redef_id compoundeq const { compoundAssignment($1, $2, $3); }
| def_id T_POP_SET const {
warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
sym_AddVar($1, $3);
}
| redef_id T_POP_SET const {
warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
sym_AddVar($1, $3);
}
;
def_rb : def_id T_POP_RB rs_uconst {
@@ -1296,30 +1273,6 @@ print_expr : const_no_str { printf("$%" PRIX32, $1); }
| string { printf("%s", $1); }
;
printt : T_POP_PRINTT string {
warning(WARNING_OBSOLETE, "`PRINTT` is deprecated; use `PRINT`\n");
printf("%s", $2);
}
;
printv : T_POP_PRINTV const {
warning(WARNING_OBSOLETE, "`PRINTV` is deprecated; use `PRINT`\n");
printf("$%" PRIX32, $2);
}
;
printi : T_POP_PRINTI const {
warning(WARNING_OBSOLETE, "`PRINTI` is deprecated; use `PRINT` with `STRFMT` \"%%d\"\n");
printf("%" PRId32, $2);
}
;
printf : T_POP_PRINTF const {
warning(WARNING_OBSOLETE, "`PRINTF` is deprecated; use `PRINT` with `STRFMT` \"%%f\"\n");
fix_Print($2);
}
;
const_3bit : const {
int32_t value = $1;
@@ -1340,7 +1293,7 @@ constlist_8bit_entry : reloc_8bit_no_str {
sect_RelByte(&$1, 0);
}
| string {
uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */
uint8_t *output = malloc(strlen($1)); // Cannot be larger than that
size_t length = charmap_Convert($1, output);
sect_AbsByteGroup(output, length);
@@ -1356,7 +1309,7 @@ constlist_16bit_entry : reloc_16bit_no_str {
sect_RelWord(&$1, 0);
}
| string {
uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */
uint8_t *output = malloc(strlen($1)); // Cannot be larger than that
size_t length = charmap_Convert($1, output);
sect_AbsWordGroup(output, length);
@@ -1530,6 +1483,9 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); }
| T_OP_FMUL T_LPAREN const T_COMMA const T_RPAREN {
rpn_Number(&$$, fix_Mul($3, $5));
}
| T_OP_FMOD T_LPAREN const T_COMMA const T_RPAREN {
rpn_Number(&$$, fix_Mod($3, $5));
}
| T_OP_POW T_LPAREN const T_COMMA const T_RPAREN {
rpn_Number(&$$, fix_Pow($3, $5));
}
@@ -1718,7 +1674,7 @@ sectattrs : %empty {
$$.alignOfs = $7;
}
| sectattrs T_COMMA T_OP_BANK T_LBRACK uconst T_RBRACK {
/* We cannot check the validity of this now */
// We cannot check the validity of this now
$$.bank = $5;
}
;
@@ -1770,7 +1726,6 @@ cpu_command : z80_adc
| z80_sub
| z80_swap
| z80_xor
| T_OWO { fatalerror("*BONK* go to horny jail\n"); }
;
z80_adc : T_Z80_ADC op_a_n {
@@ -1844,9 +1799,14 @@ z80_ei : T_Z80_EI { sect_AbsByte(0xFB); }
z80_halt : T_Z80_HALT {
sect_AbsByte(0x76);
if (haltnop)
if (haltnop) {
if (warnOnHaltNop) {
warnOnHaltNop = false;
warning(WARNING_OBSOLETE, "`nop` after `halt` will stop being the default; pass `-H` to opt into it\n");
}
sect_AbsByte(0x00);
}
}
;
z80_inc : T_Z80_INC reg_r { sect_AbsByte(0x04 | ($2 << 3)); }
@@ -1953,6 +1913,10 @@ z80_ld_mem : T_Z80_LD op_mem_ind T_COMMA T_MODE_SP {
| T_Z80_LD op_mem_ind T_COMMA T_MODE_A {
if (optimizeLoads && rpn_isKnown(&$2)
&& $2.val >= 0xFF00) {
if (warnOnLdOpt) {
warnOnLdOpt = false;
warning(WARNING_OBSOLETE, "ld optimization will stop being the default; pass `-l` to opt into it\n");
}
sect_AbsByte(0xE0);
sect_AbsByte($2.val & 0xFF);
rpn_Free(&$2);
@@ -2001,6 +1965,10 @@ z80_ld_a : T_Z80_LD reg_r T_COMMA c_ind {
if ($2 == REG_A) {
if (optimizeLoads && rpn_isKnown(&$4)
&& $4.val >= 0xFF00) {
if (warnOnLdOpt) {
warnOnLdOpt = false;
warning(WARNING_OBSOLETE, "ld optimization will stop being the default; pass `-l` to opt into it\n");
}
sect_AbsByte(0xF0);
sect_AbsByte($4.val & 0xFF);
rpn_Free(&$4);
@@ -2023,10 +1991,8 @@ z80_ld_ss : T_Z80_LD T_MODE_BC T_COMMA reloc_16bit {
sect_AbsByte(0x01 | (REG_DE << 4));
sect_RelWord(&$4, 1);
}
/*
* HL is taken care of in z80_ld_hl
* SP is taken care of in z80_ld_sp
*/
// HL is taken care of in z80_ld_hl
// SP is taken care of in z80_ld_sp
;
z80_nop : T_Z80_NOP { sect_AbsByte(0x00); }
@@ -2114,7 +2080,7 @@ z80_sbc : T_Z80_SBC op_a_n {
z80_scf : T_Z80_SCF { sect_AbsByte(0x37); }
;
z80_set : T_POP_SET const_3bit T_COMMA reg_r {
z80_set : T_Z80_SET const_3bit T_COMMA reg_r {
sect_AbsByte(0xCB);
sect_AbsByte(0xC0 | ($2 << 3) | $4);
}
@@ -2179,7 +2145,7 @@ op_a_n : reloc_8bit
| T_MODE_A T_COMMA reloc_8bit { $$ = $3; }
;
T_MODE_A : T_LPAREN T_TOKEN_A T_RPAREN
T_MODE_A : T_TOKEN_A
| T_OP_HIGH T_LPAREN T_MODE_AF T_RPAREN
;
@@ -2187,7 +2153,7 @@ T_MODE_B : T_TOKEN_B
| T_OP_HIGH T_LPAREN T_MODE_BC T_RPAREN
;
T_MODE_C : T_TOKEN_C T_CC_C T_RPAREN
T_MODE_C : T_TOKEN_C
| T_OP_LOW T_LPAREN T_MODE_BC T_RPAREN
;
@@ -2195,7 +2161,7 @@ T_MODE_D : T_TOKEN_D
| T_OP_HIGH T_LPAREN T_MODE_DE T_RPAREN
;
T_MODE_E : T_TOKEN_E T_RPAREN T_TOKEN_E_HEART
T_MODE_E : T_TOKEN_E
| T_OP_LOW T_LPAREN T_MODE_DE T_RPAREN
;
@@ -2203,19 +2169,10 @@ T_MODE_H : T_TOKEN_H
| T_OP_HIGH T_LPAREN T_MODE_HL T_RPAREN
;
T_MODE_L : T_TOKEN_L_ARM T_TOKEN_L_FACE T_TOKEN_L_BODY T_RPAREN T_TOKEN_L_LEG
T_MODE_L : T_TOKEN_L
| T_OP_LOW T_LPAREN T_MODE_HL T_RPAREN
;
T_MODE_BC : T_TOKEN_B T_TOKEN_C T_CC_C T_RPAREN
;
T_MODE_DE : T_TOKEN_D T_TOKEN_E T_RPAREN T_TOKEN_E_HEART
;
T_MODE_HL : T_MODE_HL_START T_TOKEN_L_FACE T_TOKEN_L_BODY T_RPAREN T_TOKEN_L_LEG
;
ccode_expr : ccode
| T_OP_LOGICNOT ccode_expr {
$$ = $2 ^ 1;
@@ -2225,7 +2182,7 @@ ccode_expr : ccode
ccode : T_CC_NZ { $$ = CC_NZ; }
| T_CC_Z { $$ = CC_Z; }
| T_CC_NC { $$ = CC_NC; }
| T_CC_C { $$ = CC_C; }
| T_TOKEN_C { $$ = CC_C; }
;
reg_r : T_MODE_B { $$ = REG_B; }
@@ -2241,7 +2198,7 @@ reg_r : T_MODE_B { $$ = REG_B; }
reg_tt : T_MODE_BC { $$ = REG_BC; }
| T_MODE_DE { $$ = REG_DE; }
| T_MODE_HL { $$ = REG_HL; }
| T_LPAREN T_TOKEN_A T_RPAREN T_TOKEN_F { $$ = REG_AF; }
| T_MODE_AF { $$ = REG_AF; }
;
reg_ss : T_MODE_BC { $$ = REG_BC; }

View File

@@ -6,9 +6,7 @@
* SPDX-License-Identifier: MIT
*/
/*
* Controls RPN expressions for objectfiles
*/
// Controls RPN expressions for objectfiles
#include <assert.h>
#include <errno.h>
@@ -28,7 +26,7 @@
#include "opmath.h"
/* Makes an expression "not known", also setting its error message */
// Makes an expression "not known", also setting its error message
#define makeUnknown(expr_, ...) do { \
struct Expression *_expr = expr_; \
_expr->isKnown = false; \
@@ -39,23 +37,23 @@
int size = snprintf(_expr->reason, 128, __VA_ARGS__); \
if (size >= 128) { /* If this wasn't enough, try again */ \
_expr->reason = realloc(_expr->reason, size + 1); \
if (!_expr->reason) \
fatalerror("Can't allocate err string: %s\n", strerror(errno)); \
sprintf(_expr->reason, __VA_ARGS__); \
} \
} while (0)
static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
{
/* This assumes the RPN length is always less than the capacity */
// This assumes the RPN length is always less than the capacity
if (expr->rpnCapacity - expr->rpnLength < size) {
/* If there isn't enough room to reserve the space, realloc */
// If there isn't enough room to reserve the space, realloc
if (!expr->rpn)
expr->rpnCapacity = 256; /* Initial size */
expr->rpnCapacity = 256; // Initial size
while (expr->rpnCapacity - expr->rpnLength < size) {
if (expr->rpnCapacity >= MAXRPNLEN)
/*
* To avoid generating humongous object files, cap the
* size of RPN expressions
*/
// To avoid generating humongous object files, cap the
// size of RPN expressions
fatalerror("RPN expression cannot grow larger than "
EXPAND_AND_STR(MAXRPNLEN) " bytes\n");
else if (expr->rpnCapacity > MAXRPNLEN / 2)
@@ -75,9 +73,7 @@ static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
return ptr;
}
/*
* Init a RPN expression
*/
// Init a RPN expression
static void rpn_Init(struct Expression *expr)
{
expr->reason = NULL;
@@ -89,9 +85,7 @@ static void rpn_Init(struct Expression *expr)
expr->rpnPatchSize = 0;
}
/*
* Free the RPN expression
*/
// Free the RPN expression
void rpn_Free(struct Expression *expr)
{
free(expr->rpn);
@@ -99,9 +93,7 @@ void rpn_Free(struct Expression *expr)
rpn_Init(expr);
}
/*
* Add symbols, constants and operators to expression
*/
// Add symbols, constants and operators to expression
void rpn_Number(struct Expression *expr, uint32_t i)
{
rpn_Init(expr);
@@ -122,9 +114,9 @@ void rpn_Symbol(struct Expression *expr, char const *symName)
makeUnknown(expr, sym_IsPC(sym) ? "PC is not constant at assembly time"
: "'%s' is not constant at assembly time", symName);
sym = sym_Ref(symName);
expr->rpnPatchSize += 5; /* 1-byte opcode + 4-byte symbol ID */
expr->rpnPatchSize += 5; // 1-byte opcode + 4-byte symbol ID
size_t nameLen = strlen(sym->name) + 1; /* Don't forget NUL! */
size_t nameLen = strlen(sym->name) + 1; // Don't forget NUL!
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
*ptr++ = RPN_SYM;
memcpy(ptr, sym->name, nameLen);
@@ -153,7 +145,7 @@ void rpn_BankSymbol(struct Expression *expr, char const *symName)
{
struct Symbol const *sym = sym_FindScopedSymbol(symName);
/* The @ symbol is treated differently. */
// The @ symbol is treated differently.
if (sym_IsPC(sym)) {
rpn_BankSelf(expr);
return;
@@ -167,13 +159,13 @@ void rpn_BankSymbol(struct Expression *expr, char const *symName)
assert(sym); // If the symbol didn't exist, it should have been created
if (sym_GetSection(sym) && sym_GetSection(sym)->bank != (uint32_t)-1) {
/* Symbol's section is known and bank is fixed */
// Symbol's section is known and bank is fixed
expr->val = sym_GetSection(sym)->bank;
} else {
makeUnknown(expr, "\"%s\"'s bank is not known", symName);
expr->rpnPatchSize += 5; /* opcode + 4-byte sect ID */
expr->rpnPatchSize += 5; // opcode + 4-byte sect ID
size_t nameLen = strlen(sym->name) + 1; /* Room for NUL! */
size_t nameLen = strlen(sym->name) + 1; // Room for NUL!
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
*ptr++ = RPN_BANK_SYM;
memcpy(ptr, sym->name, nameLen);
@@ -192,7 +184,7 @@ void rpn_BankSection(struct Expression *expr, char const *sectionName)
} else {
makeUnknown(expr, "Section \"%s\"'s bank is not known", sectionName);
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
expr->rpnPatchSize += nameLen + 1;
@@ -212,7 +204,7 @@ void rpn_SizeOfSection(struct Expression *expr, char const *sectionName)
} else {
makeUnknown(expr, "Section \"%s\"'s size is not known", sectionName);
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
expr->rpnPatchSize += nameLen + 1;
@@ -232,7 +224,7 @@ void rpn_StartOfSection(struct Expression *expr, char const *sectionName)
} else {
makeUnknown(expr, "Section \"%s\"'s start is not known", sectionName);
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
expr->rpnPatchSize += nameLen + 1;
@@ -250,7 +242,7 @@ void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src)
expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_HRAM;
} else if (expr->val >= 0xFF00 && expr->val <= 0xFFFF) {
/* That range is valid, but only keep the lower byte */
// That range is valid, but only keep the lower byte
expr->val &= 0xFF;
} else if (expr->val < 0 || expr->val > 0xFF) {
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->val);
@@ -262,10 +254,10 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src)
*expr = *src;
if (rpn_isKnown(expr)) {
/* A valid RST address must be masked with 0x38 */
// A valid RST address must be masked with 0x38
if (expr->val & ~0x38)
error("Invalid address $%" PRIx32 " for RST\n", expr->val);
/* The target is in the "0x38" bits, all other bits are set */
// The target is in the "0x38" bits, all other bits are set
expr->val |= 0xC7;
} else {
expr->rpnPatchSize++;
@@ -273,9 +265,7 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src)
}
}
/*
* Checks that an RPN expression's value fits within N bits (signed or unsigned)
*/
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
void rpn_CheckNBit(struct Expression const *expr, uint8_t n)
{
assert(n != 0); // That doesn't make sense
@@ -322,7 +312,7 @@ struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym)
{
/* Check if both expressions only refer to a single symbol */
// Check if both expressions only refer to a single symbol
struct Symbol const *sym1 = rpn_SymbolOf(src);
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
@@ -339,7 +329,7 @@ static bool isDiffConstant(struct Expression const *src1,
return rpn_IsDiffConstant(src1, rpn_SymbolOf(src2));
}
/**
/*
* Attempts to compute a constant binary AND from non-constant operands
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
* a constant that only keeps (some of) the lower N bits.
@@ -385,12 +375,12 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
expr->isSymbol = false;
int32_t constMaskVal;
/* First, check if the expression is known */
// First, check if the expression is known
expr->isKnown = src1->isKnown && src2->isKnown;
if (expr->isKnown) {
rpn_Init(expr); /* Init the expression to something sane */
rpn_Init(expr); // Init the expression to something sane
/* If both expressions are known, just compute the value */
// If both expressions are known, just compute the value
uint32_t uleft = src1->val, uright = src2->val;
switch (op) {
@@ -535,9 +525,9 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
expr->val = constMaskVal;
expr->isKnown = true;
} else {
/* If it's not known, start computing the RPN expression */
// If it's not known, start computing the RPN expression
/* Convert the left-hand expression if it's constant */
// Convert the left-hand expression if it's constant
if (src1->isKnown) {
uint32_t lval = src1->val;
uint8_t bytes[] = {RPN_CONST, lval, lval >> 8,
@@ -549,11 +539,11 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
memcpy(reserveSpace(expr, sizeof(bytes)), bytes,
sizeof(bytes));
/* Use the other expression's un-const reason */
// Use the other expression's un-const reason
expr->reason = src2->reason;
free(src1->reason);
} else {
/* Otherwise just reuse its RPN buffer */
// Otherwise just reuse its RPN buffer
expr->rpnPatchSize = src1->rpnPatchSize;
expr->rpn = src1->rpn;
expr->rpnCapacity = src1->rpnCapacity;
@@ -562,12 +552,12 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
free(src2->reason);
}
/* Now, merge the right expression into the left one */
uint8_t *ptr = src2->rpn; /* Pointer to the right RPN */
uint32_t len = src2->rpnLength; /* Size of the right RPN */
// Now, merge the right expression into the left one
uint8_t *ptr = src2->rpn; // Pointer to the right RPN
uint32_t len = src2->rpnLength; // Size of the right RPN
uint32_t patchSize = src2->rpnPatchSize;
/* If the right expression is constant, merge a shim instead */
// If the right expression is constant, merge a shim instead
uint32_t rval = src2->val;
uint8_t bytes[] = {RPN_CONST, rval, rval >> 8, rval >> 16,
rval >> 24};
@@ -576,13 +566,13 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
len = sizeof(bytes);
patchSize = sizeof(bytes);
}
/* Copy the right RPN and append the operator */
// Copy the right RPN and append the operator
uint8_t *buf = reserveSpace(expr, len + 1);
memcpy(buf, ptr, len);
buf[len] = op;
free(src2->rpn); /* If there was none, this is `free(NULL)` */
free(src2->rpn); // If there was none, this is `free(NULL)`
expr->rpnPatchSize += patchSize + 1;
}
}

View File

@@ -1,3 +1,10 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <assert.h>
#include <errno.h>
@@ -16,6 +23,7 @@
#include "asm/warning.h"
#include "error.h"
#include "linkdefs.h"
#include "platform.h" // strdup
uint8_t fillByte;
@@ -29,7 +37,7 @@ struct UnionStackEntry {
struct SectionStackEntry {
struct Section *section;
struct Section *loadSection;
char const *scope; /* Section's symbol scope */
char const *scope; // Section's symbol scope
uint32_t offset;
int32_t loadOffset;
struct UnionStackEntry *unionStack;
@@ -37,14 +45,12 @@ struct SectionStackEntry {
};
struct SectionStackEntry *sectionStack;
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */
uint32_t curOffset; // Offset into the current section (see sect_GetSymbolOffset)
struct Section *currentSection = NULL;
static struct Section *currentLoadSection = NULL;
int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
/*
* A quick check to see if we have an initialized section
*/
// A quick check to see if we have an initialized section
attr_(warn_unused_result) static bool checksection(void)
{
if (currentSection)
@@ -54,10 +60,8 @@ attr_(warn_unused_result) static bool checksection(void)
return false;
}
/*
* A quick check to see if we have an initialized section that can contain
* this much initialized data
*/
// A quick check to see if we have an initialized section that can contain
// this much initialized data
attr_(warn_unused_result) static bool checkcodesection(void)
{
if (!checksection())
@@ -73,7 +77,7 @@ attr_(warn_unused_result) static bool checkcodesection(void)
attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sect, uint32_t size)
{
uint32_t maxSize = maxsize[sect->type];
uint32_t maxSize = sectionTypeInfo[sect->type].size;
// If the new size is reasonable, keep going
if (size <= maxSize)
@@ -84,17 +88,13 @@ attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sec
return false;
}
/*
* Check if the section has grown too much.
*/
// Check if the section has grown too much.
attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
{
/*
* This check is here to trap broken code that generates sections that are too big and to
* prevent the assembler from generating huge object files or trying to allocate too much
* memory.
* A check at the linking stage is still necessary.
*/
// This check is here to trap broken code that generates sections that are too big and to
// prevent the assembler from generating huge object files or trying to allocate too much
// memory.
// A check at the linking stage is still necessary.
// If the section has already overflowed, skip the check to avoid erroring out ad nauseam
if (currentSection->size != UINT32_MAX
@@ -132,15 +132,13 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
assert(alignment < 16); // Should be ensured by the caller
unsigned int nbSectErrors = 0;
/*
* Unionized sections only need "compatible" constraints, and they end up with the strictest
* combination of both.
*/
// Unionized sections only need "compatible" constraints, and they end up with the strictest
// combination of both.
if (sect_HasData(type))
fail("Cannot declare ROM sections as UNION\n");
if (org != (uint32_t)-1) {
/* If both are fixed, they must be the same */
// If both are fixed, they must be the same
if (sect->org != (uint32_t)-1 && sect->org != org)
fail("Section already declared as fixed at different address $%04"
PRIx32 "\n", sect->org);
@@ -148,16 +146,16 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
fail("Section already declared as aligned to %u bytes (offset %"
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
else
/* Otherwise, just override */
// Otherwise, just override
sect->org = org;
} else if (alignment != 0) {
/* Make sure any fixed address given is compatible */
// Make sure any fixed address given is compatible
if (sect->org != (uint32_t)-1) {
if ((sect->org - alignOffset) & mask(alignment))
fail("Section already declared as fixed at incompatible address $%04"
PRIx32 "\n", sect->org);
/* Check if alignment offsets are compatible */
// Check if alignment offsets are compatible
} else if ((alignOffset & mask(sect->align))
!= (sect->alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %u"
@@ -180,15 +178,13 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
assert(alignment < 16); // Should be ensured by the caller
unsigned int nbSectErrors = 0;
/*
* Fragments only need "compatible" constraints, and they end up with the strictest
* combination of both.
* The merging is however performed at the *end* of the original section!
*/
// Fragments only need "compatible" constraints, and they end up with the strictest
// combination of both.
// The merging is however performed at the *end* of the original section!
if (org != (uint32_t)-1) {
uint16_t curOrg = org - sect->size;
/* If both are fixed, they must be the same */
// If both are fixed, they must be the same
if (sect->org != (uint32_t)-1 && sect->org != curOrg)
fail("Section already declared as fixed at incompatible address $%04"
PRIx32 " (cur addr = %04" PRIx32 ")\n",
@@ -197,7 +193,7 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
fail("Section already declared as aligned to %u bytes (offset %"
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
else
/* Otherwise, just override */
// Otherwise, just override
sect->org = curOrg;
} else if (alignment != 0) {
@@ -206,12 +202,12 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
if (curOfs < 0)
curOfs += 1U << alignment;
/* Make sure any fixed address given is compatible */
// Make sure any fixed address given is compatible
if (sect->org != (uint32_t)-1) {
if ((sect->org - curOfs) & mask(alignment))
fail("Section already declared as fixed at incompatible address $%04"
PRIx32 "\n", sect->org);
/* Check if alignment offsets are compatible */
// Check if alignment offsets are compatible
} else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %u"
"-byte alignment (offset %" PRIu16 ")\n",
@@ -232,7 +228,7 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
unsigned int nbSectErrors = 0;
if (type != sect->type)
fail("Section already exists but with type %s\n", typeNames[sect->type]);
fail("Section already exists but with type %s\n", sectionTypeInfo[sect->type].name);
if (sect->modifier != mod) {
fail("Section already declared as %s section\n", sectionModNames[sect->modifier]);
@@ -245,10 +241,10 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
// Common checks
/* If the section's bank is unspecified, override it */
// If the section's bank is unspecified, override it
if (sect->bank == (uint32_t)-1)
sect->bank = bank;
/* If both specify a bank, it must be the same one */
// If both specify a bank, it must be the same one
else if (bank != (uint32_t)-1 && sect->bank != bank)
fail("Section already declared with different bank %" PRIu32 "\n",
sect->bank);
@@ -269,9 +265,7 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
#undef fail
/*
* Create a new section, not yet in the list.
*/
// Create a new section, not yet in the list.
static struct Section *createSection(char const *name, enum SectionType type,
uint32_t org, uint32_t bank, uint8_t alignment,
uint16_t alignOffset, enum SectionModifier mod)
@@ -297,9 +291,9 @@ static struct Section *createSection(char const *name, enum SectionType type,
sect->next = NULL;
sect->patches = NULL;
/* It is only needed to allocate memory for ROM sections. */
// It is only needed to allocate memory for ROM sections.
if (sect_HasData(type)) {
sect->data = malloc(maxsize[type]);
sect->data = malloc(sectionTypeInfo[type].size);
if (sect->data == NULL)
fatalerror("Not enough memory for section: %s\n", strerror(errno));
} else {
@@ -309,9 +303,7 @@ static struct Section *createSection(char const *name, enum SectionType type,
return sect;
}
/*
* Find a section by name and type. If it doesn't exist, create it.
*/
// Find a section by name and type. If it doesn't exist, create it.
static struct Section *getSection(char const *name, enum SectionType type, uint32_t org,
struct SectionSpec const *attrs, enum SectionModifier mod)
{
@@ -325,14 +317,13 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
else if (bank < bankranges[type][0]
|| bank > bankranges[type][1])
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
PRIx32 ")\n", typeNames[type], bank,
bankranges[type][0], bankranges[type][1]);
PRIx32 ")\n", sectionTypeInfo[type].name, bank,
sectionTypeInfo[type].firstBank, sectionTypeInfo[type].lastBank);
} else if (nbbanks(type) == 1) {
// If the section type only has a single bank, implicitly force it
bank = bankranges[type][0];
bank = sectionTypeInfo[type].firstBank;
}
if (alignOffset >= 1 << alignment) {
@@ -342,10 +333,10 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
}
if (org != (uint32_t)-1) {
if (org < startaddr[type] || org > endaddr(type))
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
error("Section \"%s\"'s fixed address %#" PRIx32
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
name, org, startaddr[type], endaddr(type));
name, org, sectionTypeInfo[type].startAddr, endaddr(type));
}
if (alignment != 0) {
@@ -353,18 +344,18 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
error("Alignment must be between 0 and 16, not %u\n", alignment);
alignment = 16;
}
/* It doesn't make sense to have both alignment and org set */
// It doesn't make sense to have both alignment and org set
uint32_t mask = mask(alignment);
if (org != (uint32_t)-1) {
if ((org - alignOffset) & mask)
error("Section \"%s\"'s fixed address doesn't match its alignment\n",
name);
alignment = 0; /* Ignore it if it's satisfied */
} else if (startaddr[type] & mask) {
alignment = 0; // Ignore it if it's satisfied
} else if (sectionTypeInfo[type].startAddr & mask) {
error("Section \"%s\"'s alignment cannot be attained in %s\n",
name, typeNames[type]);
alignment = 0; /* Ignore it if it's unattainable */
name, sectionTypeInfo[type].name);
alignment = 0; // Ignore it if it's unattainable
org = 0;
} else if (alignment == 16) {
// Treat an alignment of 16 as being fixed at address 0
@@ -390,9 +381,7 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
return sect;
}
/*
* Set the current section
*/
// Set the current section
static void changeSection(void)
{
if (unionStack)
@@ -401,9 +390,7 @@ static void changeSection(void)
sym_SetCurrentSymbolScope(NULL);
}
/*
* Set the current section by name and type
*/
// Set the current section by name and type
void sect_NewSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs, enum SectionModifier mod)
{
@@ -423,9 +410,7 @@ void sect_NewSection(char const *name, uint32_t type, uint32_t org,
currentSection = sect;
}
/*
* Set the current section by name and type
*/
// Set the current section by name and type
void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs, enum SectionModifier mod)
{
@@ -478,9 +463,7 @@ struct Section *sect_GetSymbolSection(void)
return currentLoadSection ? currentLoadSection : currentSection;
}
/*
* The offset into the section above
*/
// The offset into the section above
uint32_t sect_GetSymbolOffset(void)
{
return curOffset;
@@ -615,9 +598,7 @@ void sect_CheckUnionClosed(void)
error("Unterminated UNION construct!\n");
}
/*
* Output an absolute byte
*/
// Output an absolute byte
void sect_AbsByte(uint8_t b)
{
if (!checkcodesection())
@@ -661,9 +642,7 @@ void sect_AbsLongGroup(uint8_t const *s, size_t length)
writelong(*s++);
}
/*
* Skip this many bytes
*/
// Skip this many bytes
void sect_Skip(uint32_t skip, bool ds)
{
if (!checksection())
@@ -683,9 +662,7 @@ void sect_Skip(uint32_t skip, bool ds)
}
}
/*
* Output a NULL terminated string (excluding the NULL-character)
*/
// Output a NULL terminated string (excluding the NULL-character)
void sect_String(char const *s)
{
if (!checkcodesection())
@@ -697,10 +674,8 @@ void sect_String(char const *s)
writebyte(*s++);
}
/*
* Output a relocatable byte. Checking will be done to see if it
* is an absolute value in disguise.
*/
// Output a relocatable byte. Checking will be done to see if it
// is an absolute value in disguise.
void sect_RelByte(struct Expression *expr, uint32_t pcShift)
{
if (!checkcodesection())
@@ -717,10 +692,8 @@ void sect_RelByte(struct Expression *expr, uint32_t pcShift)
rpn_Free(expr);
}
/*
* Output several copies of a relocatable byte. Checking will be done to see if
* it is an absolute value in disguise.
*/
// Output several copies of a relocatable byte. Checking will be done to see if
// it is an absolute value in disguise.
void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
{
if (!checkcodesection())
@@ -743,10 +716,8 @@ void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
rpn_Free(&exprs[i]);
}
/*
* Output a relocatable word. Checking will be done to see if
* it's an absolute value in disguise.
*/
// Output a relocatable word. Checking will be done to see if
// it's an absolute value in disguise.
void sect_RelWord(struct Expression *expr, uint32_t pcShift)
{
if (!checkcodesection())
@@ -763,10 +734,8 @@ void sect_RelWord(struct Expression *expr, uint32_t pcShift)
rpn_Free(expr);
}
/*
* Output a relocatable longword. Checking will be done to see if
* is an absolute value in disguise.
*/
// Output a relocatable longword. Checking will be done to see if
// is an absolute value in disguise.
void sect_RelLong(struct Expression *expr, uint32_t pcShift)
{
if (!checkcodesection())
@@ -783,10 +752,8 @@ void sect_RelLong(struct Expression *expr, uint32_t pcShift)
rpn_Free(expr);
}
/*
* Output a PC-relative relocatable byte. Checking will be done to see if it
* is an absolute value in disguise.
*/
// Output a PC-relative relocatable byte. Checking will be done to see if it
// is an absolute value in disguise.
void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
{
if (!checkcodesection())
@@ -800,12 +767,12 @@ void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
writebyte(0);
} else {
struct Symbol const *sym = rpn_SymbolOf(expr);
/* The offset wraps (jump from ROM to HRAM, for example) */
// The offset wraps (jump from ROM to HRAM, for example)
int16_t offset;
/* Offset is relative to the byte *after* the operand */
// Offset is relative to the byte *after* the operand
if (sym == pc)
offset = -2; /* PC as operand to `jr` is lower than reference PC by 2 */
offset = -2; // PC as operand to `jr` is lower than reference PC by 2
else
offset = sym_GetValue(sym) - (sym_GetValue(pc) + 1);
@@ -820,9 +787,7 @@ void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
rpn_Free(expr);
}
/*
* Output a binary file
*/
// Output a binary file
void sect_BinaryFile(char const *s, int32_t startPos)
{
if (startPos < 0) {
@@ -869,7 +834,7 @@ void sect_BinaryFile(char const *s, int32_t startPos)
if (errno != ESPIPE)
error("Error determining size of INCBIN file '%s': %s\n",
s, strerror(errno));
/* The file isn't seekable, so we'll just skip bytes */
// The file isn't seekable, so we'll just skip bytes
while (startPos--)
(void)fgetc(f);
}
@@ -901,7 +866,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
if (!checkcodesection())
return;
if (length == 0) /* Don't even bother with 0-byte slices */
if (length == 0) // Don't even bother with 0-byte slices
return;
if (!reserveSpace(length))
return;
@@ -946,7 +911,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
if (errno != ESPIPE)
error("Error determining size of INCBIN file '%s': %s\n",
s, strerror(errno));
/* The file isn't seekable, so we'll just skip bytes */
// The file isn't seekable, so we'll just skip bytes
while (start_pos--)
(void)fgetc(f);
}
@@ -968,9 +933,7 @@ cleanup:
fclose(f);
}
/*
* Section stack routines
*/
// Section stack routines
void sect_PushSection(void)
{
struct SectionStackEntry *entry = malloc(sizeof(*entry));

View File

@@ -6,9 +6,7 @@
* SPDX-License-Identifier: MIT
*/
/*
* Symboltable and macroargs stuff
*/
// Symboltable and macroargs stuff
#include <assert.h>
#include <errno.h>
@@ -36,7 +34,7 @@
HashMap symbols;
static char const *labelScope; /* Current section's label scope */
static const char *labelScope; // Current section's label scope
static struct Symbol *PCSymbol;
static char savedTIME[256];
static char savedDATE[256];
@@ -85,36 +83,34 @@ static int32_t Callback__LINE__(void)
static char const *Callback__FILE__(void)
{
/*
* FIXME: this is dangerous, and here's why this is CURRENTLY okay. It's still bad, fix it.
* There are only two call sites for this; one copies the contents directly, the other is
* EQUS expansions, which cannot straddle file boundaries. So this should be fine.
*/
// FIXME: this is dangerous, and here's why this is CURRENTLY okay. It's still bad, fix it.
// There are only two call sites for this; one copies the contents directly, the other is
// EQUS expansions, which cannot straddle file boundaries. So this should be fine.
static char *buf = NULL;
static size_t bufsize = 0;
char const *fileName = fstk_GetFileName();
size_t j = 1;
assert(fileName[0]);
/* The assertion above ensures the loop runs at least once */
// The assertion above ensures the loop runs at least once
for (size_t i = 0; fileName[i]; i++, j++) {
/* Account for the extra backslash inserted below */
// Account for the extra backslash inserted below
if (fileName[i] == '"')
j++;
/* Ensure there will be enough room; DO NOT PRINT ANYTHING ABOVE THIS!! */
if (j + 2 >= bufsize) { /* Always keep room for 2 tail chars */
// Ensure there will be enough room; DO NOT PRINT ANYTHING ABOVE THIS!!
if (j + 2 >= bufsize) { // Always keep room for 2 tail chars
bufsize = bufsize ? bufsize * 2 : 64;
buf = realloc(buf, bufsize);
if (!buf)
fatalerror("Failed to grow buffer for file name: %s\n",
strerror(errno));
}
/* Escape quotes, since we're returning a string */
// Escape quotes, since we're returning a string
if (fileName[i] == '"')
buf[j - 1] = '\\';
buf[j] = fileName[i];
}
/* Write everything after the loop, to ensure the buffer has been allocated */
// Write everything after the loop, to ensure the buffer has been allocated
buf[0] = '"';
buf[j++] = '"';
buf[j] = '\0';
@@ -128,16 +124,14 @@ static int32_t CallbackPC(void)
return section ? section->org + sect_GetSymbolOffset() : 0;
}
/*
* Get the value field of a symbol
*/
// Get the value field of a symbol
int32_t sym_GetValue(struct Symbol const *sym)
{
if (sym_IsNumeric(sym) && sym->hasCallback)
return sym->numCallback();
if (sym->type == SYM_LABEL)
/* TODO: do not use section's org directly */
// TODO: do not use section's org directly
return sym->value + sym_GetSection(sym)->org;
return sym->value;
@@ -153,32 +147,26 @@ static void dumpFilename(struct Symbol const *sym)
fputs("<builtin>", stderr);
}
/*
* Set a symbol's definition filename and line
*/
// Set a symbol's definition filename and line
static void setSymbolFilename(struct Symbol *sym)
{
sym->src = fstk_GetFileStack();
sym->fileLine = sym->src ? lexer_GetLineNo() : 0; // This is (NULL, 1) for built-ins
}
/*
* Update a symbol's definition filename and line
*/
// Update a symbol's definition filename and line
static void updateSymbolFilename(struct Symbol *sym)
{
struct FileStackNode *oldSrc = sym->src;
setSymbolFilename(sym);
/* If the old node was referenced, ensure the new one is */
// If the old node was referenced, ensure the new one is
if (oldSrc && oldSrc->referenced && oldSrc->ID != (uint32_t)-1)
out_RegisterNode(sym->src);
/* TODO: unref the old node, and use `out_ReplaceNode` instead of deleting it */
// TODO: unref the old node, and use `out_ReplaceNode` instead of deleting it
}
/*
* Create a new symbol by name
*/
// Create a new symbol by name
static struct Symbol *createsymbol(char const *symName)
{
struct Symbol *sym = malloc(sizeof(*sym));
@@ -201,10 +189,8 @@ static struct Symbol *createsymbol(char const *symName)
return sym;
}
/*
* Creates the full name of a local symbol in a given scope, by prepending
* the name with the parent symbol's name.
*/
// Creates the full name of a local symbol in a given scope, by prepending
// the name with the parent symbol's name.
static void fullSymbolName(char *output, size_t outputSize,
char const *localName, char const *scopeName)
{
@@ -250,8 +236,8 @@ struct Symbol *sym_FindScopedSymbol(char const *symName)
if (strchr(localName + 1, '.'))
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n",
symName);
/* If auto-scoped local label, expand the name */
if (localName == symName) { /* Meaning, the name begins with the dot */
// If auto-scoped local label, expand the name
if (localName == symName) { // Meaning, the name begins with the dot
char fullName[MAXSYMLEN + 1];
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
@@ -271,9 +257,7 @@ static bool isReferenced(struct Symbol const *sym)
return sym->ID != (uint32_t)-1;
}
/*
* Purge a symbol
*/
// Purge a symbol
void sym_Purge(char const *symName)
{
struct Symbol *sym = sym_FindScopedSymbol(symName);
@@ -285,16 +269,14 @@ void sym_Purge(char const *symName)
} else if (isReferenced(sym)) {
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName);
} else {
/* Do not keep a reference to the label's name after purging it */
// Do not keep a reference to the label's name after purging it
if (sym->name == labelScope)
sym_SetCurrentSymbolScope(NULL);
/*
* FIXME: this leaks sym->macro for SYM_EQUS and SYM_MACRO, but this can't
* free(sym->macro) because the expansion may be purging itself.
*/
// FIXME: this leaks sym->macro for SYM_EQUS and SYM_MACRO, but this can't
// free(sym->macro) because the expansion may be purging itself.
hash_RemoveElement(symbols, sym->name);
/* TODO: ideally, also unref the file stack nodes */
// TODO: ideally, also unref the file stack nodes
free(sym);
}
}
@@ -312,9 +294,7 @@ uint32_t sym_GetPCValue(void)
return 0;
}
/*
* Return a constant symbol's value, assuming it's defined
*/
// Return a constant symbol's value, assuming it's defined
uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
{
if (sym == PCSymbol)
@@ -327,9 +307,7 @@ uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
return 0;
}
/*
* Return a constant symbol's value
*/
// Return a constant symbol's value
uint32_t sym_GetConstantValue(char const *symName)
{
struct Symbol const *sym = sym_FindScopedSymbol(symName);
@@ -381,9 +359,7 @@ static struct Symbol *createNonrelocSymbol(char const *symName, bool numeric)
return sym;
}
/*
* Add an equated symbol
*/
// Add an equated symbol
struct Symbol *sym_AddEqu(char const *symName, int32_t value)
{
struct Symbol *sym = createNonrelocSymbol(symName, true);
@@ -465,18 +441,14 @@ struct Symbol *sym_RedefString(char const *symName, char const *value)
}
updateSymbolFilename(sym);
/*
* FIXME: this leaks the previous sym->macro value, but this can't
* free(sym->macro) because the expansion may be redefining itself.
*/
// FIXME: this leaks the previous sym->macro value, but this can't
// free(sym->macro) because the expansion may be redefining itself.
assignStringSymbol(sym, value);
return sym;
}
/*
* Alter a mutable symbol's value
*/
// Alter a mutable symbol's value
struct Symbol *sym_AddVar(char const *symName, int32_t value)
{
struct Symbol *sym = sym_FindExactSymbol(symName);
@@ -506,7 +478,7 @@ struct Symbol *sym_AddVar(char const *symName, int32_t value)
*/
static struct Symbol *addLabel(char const *symName)
{
assert(symName[0] != '.'); /* The symbol name must have been expanded prior */
assert(symName[0] != '.'); // The symbol name must have been expanded prior
struct Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) {
@@ -519,7 +491,7 @@ static struct Symbol *addLabel(char const *symName)
} else {
updateSymbolFilename(sym);
}
/* If the symbol already exists as a ref, just "take over" it */
// If the symbol already exists as a ref, just "take over" it
sym->type = SYM_LABEL;
sym->value = sect_GetSymbolOffset();
if (exportall)
@@ -531,43 +503,41 @@ static struct Symbol *addLabel(char const *symName)
return sym;
}
/*
* Add a local (`.name` or `Parent.name`) relocatable symbol
*/
// Add a local (`.name` or `Parent.name`) relocatable symbol
struct Symbol *sym_AddLocalLabel(char const *symName)
{
if (!labelScope) {
error("Local label '%s' in main scope\n", symName);
return NULL;
}
assert(!strchr(labelScope, '.')); /* Assuming no dots in `labelScope` */
assert(!strchr(labelScope, '.')); // Assuming no dots in `labelScope`
char fullName[MAXSYMLEN + 1];
char const *localName = strchr(symName, '.');
assert(localName); /* There should be at least one dot in `symName` */
/* Check for something after the dot in `localName` */
assert(localName); // There should be at least one dot in `symName`
// Check for something after the dot in `localName`
if (localName[1] == '\0') {
fatalerror("'%s' is a nonsensical reference to an empty local label\n",
symName);
}
/* Check for more than one dot in `localName` */
// Check for more than one dot in `localName`
if (strchr(localName + 1, '.'))
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
symName);
if (localName == symName) {
/* Expand `symName` to the full `labelScope.symName` name */
// Expand `symName` to the full `labelScope.symName` name
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
symName = fullName;
} else {
size_t i = 0;
/* Find where `labelScope` and `symName` first differ */
// Find where `labelScope` and `symName` first differ
while (labelScope[i] && symName[i] == labelScope[i])
i++;
/* Check that `symName` starts with `labelScope` and then a '.' */
// Check that `symName` starts with `labelScope` and then a '.'
if (labelScope[i] != '\0' || symName[i] != '.') {
size_t parentLen = localName - symName;
@@ -579,14 +549,12 @@ struct Symbol *sym_AddLocalLabel(char const *symName)
return addLabel(symName);
}
/*
* Add a relocatable symbol
*/
// Add a relocatable symbol
struct Symbol *sym_AddLabel(char const *symName)
{
struct Symbol *sym = addLabel(symName);
/* Set the symbol as the new scope */
// Set the symbol as the new scope
if (sym)
sym_SetCurrentSymbolScope(sym->name);
return sym;
@@ -594,9 +562,7 @@ struct Symbol *sym_AddLabel(char const *symName)
static uint32_t anonLabelID;
/*
* Add an anonymous label
*/
// Add an anonymous label
struct Symbol *sym_AddAnonLabel(void)
{
if (anonLabelID == UINT32_MAX) {
@@ -610,9 +576,7 @@ struct Symbol *sym_AddAnonLabel(void)
return addLabel(name);
}
/*
* Write an anonymous label's name to a buffer
*/
// Write an anonymous label's name to a buffer
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg)
{
uint32_t id = 0;
@@ -636,9 +600,7 @@ void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs,
sprintf(buf, "!%u", id);
}
/*
* Export a symbol
*/
// Export a symbol
void sym_Export(char const *symName)
{
if (symName[0] == '!') {
@@ -648,15 +610,13 @@ void sym_Export(char const *symName)
struct Symbol *sym = sym_FindScopedSymbol(symName);
/* If the symbol doesn't exist, create a ref that can be purged */
// If the symbol doesn't exist, create a ref that can be purged
if (!sym)
sym = sym_Ref(symName);
sym->isExported = true;
}
/*
* Add a macro definition
*/
// Add a macro definition
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size)
{
struct Symbol *sym = createNonrelocSymbol(symName, false);
@@ -667,20 +627,16 @@ struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body,
sym->type = SYM_MACRO;
sym->macroSize = size;
sym->macro = body;
setSymbolFilename(sym); /* TODO: is this really necessary? */
/*
* The symbol is created at the line after the `endm`,
* override this with the actual definition line
*/
setSymbolFilename(sym); // TODO: is this really necessary?
// The symbol is created at the line after the `endm`,
// override this with the actual definition line
sym->fileLine = defLineNo;
return sym;
}
/*
* Flag that a symbol is referenced in an RPN expression
* and create it if it doesn't exist yet
*/
// Flag that a symbol is referenced in an RPN expression
// and create it if it doesn't exist yet
struct Symbol *sym_Ref(char const *symName)
{
struct Symbol *sym = sym_FindScopedSymbol(symName);
@@ -702,9 +658,7 @@ struct Symbol *sym_Ref(char const *symName)
return sym;
}
/*
* Set whether to export all relocatable symbols by default
*/
// Set whether to export all relocatable symbols by default
void sym_SetExportAll(bool set)
{
exportall = set;
@@ -721,9 +675,7 @@ static struct Symbol *createBuiltinSymbol(char const *symName)
return sym;
}
/*
* Initialize the symboltable
*/
// Initialize the symboltable
void sym_Init(time_t now)
{
PCSymbol = createBuiltinSymbol("@");
@@ -743,8 +695,13 @@ void sym_Init(time_t now)
sym_AddVar("_RS", 0)->isBuiltin = true;
#define addNumber(name, val) sym_AddEqu(name, val)->isBuiltin = true
#define addString(name, val) sym_AddString(name, val)->isBuiltin = true
#define addSym(fn, name, val) do { \
struct Symbol *sym = fn(name, val); \
assert(sym); \
sym->isBuiltin = true; \
} while (0)
#define addNumber(name, val) addSym(sym_AddEqu, name, val)
#define addString(name, val) addSym(sym_AddString, name, val)
addString("__RGBDS_VERSION__", get_package_version_string());
addNumber("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR);
@@ -756,7 +713,7 @@ void sym_Init(time_t now)
if (now == (time_t)-1) {
warn("Couldn't determine current time");
/* Fall back by pretending we are at the Epoch */
// Fall back by pretending we are at the Epoch
now = 0;
}
@@ -788,16 +745,7 @@ void sym_Init(time_t now)
#undef addNumber
#undef addString
#undef addSym
sym_SetCurrentSymbolScope(NULL);
anonLabelID = 0;
/* _PI is deprecated */
struct Symbol *_PISymbol = createBuiltinSymbol("_PI");
_PISymbol->type = SYM_EQU;
_PISymbol->src = NULL;
_PISymbol->fileLine = 0;
_PISymbol->hasCallback = true;
_PISymbol->numCallback = fix_Callback_PI;
}
anonLabelID = 0;}

View File

@@ -44,7 +44,7 @@ char const *printChar(int c)
buf[2] = 't';
break;
default: /* Print as hex */
default: // Print as hex
buf[0] = '0';
buf[1] = 'x';
snprintf(&buf[2], 3, "%02hhX", (uint8_t)c); // includes the '\0'

View File

@@ -38,6 +38,7 @@ static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
[WARNING_OBSOLETE] = WARNING_ENABLED,
[WARNING_SHIFT] = WARNING_DISABLED,
[WARNING_SHIFT_AMOUNT] = WARNING_DISABLED,
[WARNING_UNMAPPED_CHAR] = WARNING_ENABLED,
[WARNING_USER] = WARNING_ENABLED,
[WARNING_NUMERIC_STRING_1] = WARNING_ENABLED,
@@ -48,19 +49,19 @@ static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
enum WarningState warningStates[ARRAY_SIZE(warningStates)];
bool warningsAreErrors; /* Set if `-Werror` was specified */
bool warningsAreErrors; // Set if `-Werror` was specified
static enum WarningState warningState(enum WarningID id)
{
/* Check if warnings are globally disabled */
// Check if warnings are globally disabled
if (!warnings)
return WARNING_DISABLED;
/* Get the actual state */
// Get the actual state
enum WarningState state = warningStates[id];
if (state == WARNING_DEFAULT)
/* The state isn't set, grab its default state */
// The state isn't set, grab its default state
state = defaultWarnings[id];
if (warningsAreErrors && state == WARNING_ENABLED)
@@ -85,6 +86,7 @@ static const char * const warningFlags[NB_WARNINGS] = {
"obsolete",
"shift",
"shift-amount",
"unmapped-char",
"user",
// Parametric warnings
@@ -93,10 +95,10 @@ static const char * const warningFlags[NB_WARNINGS] = {
"truncation",
"truncation",
/* Meta warnings */
// Meta warnings
"all",
"extra",
"everything", /* Especially useful for testing */
"everything", // Especially useful for testing
};
static const struct {
@@ -149,7 +151,7 @@ enum MetaWarningCommand {
META_WARNING_DONE = NB_WARNINGS
};
/* Warnings that probably indicate an error */
// Warnings that probably indicate an error
static uint8_t const _wallCommands[] = {
WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG,
@@ -160,11 +162,12 @@ static uint8_t const _wallCommands[] = {
WARNING_LONG_STR,
WARNING_NESTED_COMMENT,
WARNING_OBSOLETE,
WARNING_UNMAPPED_CHAR,
WARNING_NUMERIC_STRING_1,
META_WARNING_DONE
};
/* Warnings that are less likely to indicate an error */
// Warnings that are less likely to indicate an error
static uint8_t const _wextraCommands[] = {
WARNING_EMPTY_MACRO_ARG,
WARNING_MACRO_SHIFT,
@@ -176,7 +179,7 @@ static uint8_t const _wextraCommands[] = {
META_WARNING_DONE
};
/* Literally everything. Notably useful for testing */
// Literally everything. Notably useful for testing
static uint8_t const _weverythingCommands[] = {
WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG,
@@ -191,11 +194,12 @@ static uint8_t const _weverythingCommands[] = {
WARNING_OBSOLETE,
WARNING_SHIFT,
WARNING_SHIFT_AMOUNT,
WARNING_UNMAPPED_CHAR,
WARNING_NUMERIC_STRING_1,
WARNING_NUMERIC_STRING_2,
WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2,
/* WARNING_USER, */
// WARNING_USER,
META_WARNING_DONE
};
@@ -209,18 +213,18 @@ void processWarningFlag(char *flag)
{
static bool setError = false;
/* First, try to match against a "meta" warning */
// First, try to match against a "meta" warning
for (enum WarningID id = META_WARNINGS_START; id < NB_WARNINGS; id++) {
/* TODO: improve the matching performance? */
// TODO: improve the matching performance?
if (!strcmp(flag, warningFlags[id])) {
/* We got a match! */
// We got a match!
if (setError)
errx("Cannot make meta warning \"%s\" into an error",
flag);
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
*ptr != META_WARNING_DONE; ptr++) {
/* Warning flag, set without override */
// Warning flag, set without override
if (warningStates[*ptr] == WARNING_DEFAULT)
warningStates[*ptr] = WARNING_ENABLED;
}
@@ -229,31 +233,31 @@ void processWarningFlag(char *flag)
}
}
/* If it's not a meta warning, specially check against `-Werror` */
// If it's not a meta warning, specially check against `-Werror`
if (!strncmp(flag, "error", strlen("error"))) {
char *errorFlag = flag + strlen("error");
switch (*errorFlag) {
case '\0':
/* `-Werror` */
// `-Werror`
warningsAreErrors = true;
return;
case '=':
/* `-Werror=XXX` */
// `-Werror=XXX`
setError = true;
processWarningFlag(errorFlag + 1); /* Skip the `=` */
processWarningFlag(errorFlag + 1); // Skip the `=`
setError = false;
return;
/* Otherwise, allow parsing as another flag */
// Otherwise, allow parsing as another flag
}
}
/* Well, it's either a normal warning or a mistake */
// Well, it's either a normal warning or a mistake
enum WarningState state = setError ? WARNING_ERROR :
/* Not an error, then check if this is a negation */
// Not an error, then check if this is a negation
strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
: WARNING_DISABLED;
char const *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
@@ -304,10 +308,10 @@ void processWarningFlag(char *flag)
}
}
/* Try to match the flag against a "normal" flag */
// Try to match the flag against a "normal" flag
for (enum WarningID id = 0; id < NB_PLAIN_WARNINGS; id++) {
if (!strcmp(rootFlag, warningFlags[id])) {
/* We got a match! */
// We got a match!
warningStates[id] = state;
return;
}
@@ -370,7 +374,7 @@ void warning(enum WarningID id, char const *fmt, ...)
case WARNING_DEFAULT:
unreachable_();
/* Not reached */
// Not reached
case WARNING_ENABLED:
break;

View File

@@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env bash
bison -V | awk -v major="$1" -v minor="$2" '
/^bison.*[0-9]+(\.[0-9]+)(\.[0-9]+)?$/ {
match($0, /[0-9]+(\.[0-9]+)(\.[0-9]+)?$/);

View File

@@ -29,7 +29,7 @@
#define BANK_SIZE 0x4000
/* Short options */
// Short options
static const char *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv";
/*
@@ -191,7 +191,7 @@ static void printAcceptedMBCNames(void)
static uint8_t tpp1Rev[2];
/**
/*
* @return False on failure
*/
static bool readMBCSlice(char const **name, char const *expected)
@@ -837,7 +837,7 @@ static ssize_t writeBytes(int fd, void *buf, size_t len)
return total;
}
/**
/*
* @param rom0 A pointer to rom0
* @param addr What address to check
* @param fixedByte The fixed byte at the address
@@ -853,7 +853,7 @@ static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char
rom0[addr] = fixedByte;
}
/**
/*
* @param rom0 A pointer to rom0
* @param startAddr What address to begin checking from
* @param fixed The fixed bytes at the address
@@ -878,7 +878,7 @@ static void overwriteBytes(uint8_t *rom0, uint16_t startAddr, uint8_t const *fix
memcpy(&rom0[startAddr], fixed, size);
}
/**
/*
* @param input File descriptor to be used for reading
* @param output File descriptor to be used for writing, may be equal to `input`
* @param name The file's name, to be displayed for error output

View File

@@ -1,385 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "gfx/gb.h"
void transpose_tiles(struct GBImage *gb, int width)
{
uint8_t *newdata;
int i;
int newbyte;
newdata = calloc(gb->size, 1);
if (!newdata)
err("%s: Failed to allocate memory for new data", __func__);
for (i = 0; i < gb->size; i++) {
newbyte = i / (8 * depth) * width * 8 * depth;
newbyte = newbyte % gb->size
+ 8 * depth * (newbyte / gb->size)
+ i % (8 * depth);
newdata[newbyte] = gb->data[i];
}
free(gb->data);
gb->data = newdata;
}
void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb)
{
uint8_t index;
for (unsigned int y = 0; y < raw_image->height; y++) {
for (unsigned int x = 0; x < raw_image->width; x++) {
index = raw_image->data[y][x];
index &= (1 << depth) - 1;
unsigned int byte = y * depth
+ x / 8 * raw_image->height / 8 * 8 * depth;
gb->data[byte] |= (index & 1) << (7 - x % 8);
if (depth == 2) {
gb->data[byte + 1] |=
(index >> 1) << (7 - x % 8);
}
}
}
if (!gb->horizontal)
transpose_tiles(gb, raw_image->width / 8);
}
void output_file(const struct Options *opts, const struct GBImage *gb)
{
FILE *f;
f = fopen(opts->outfile, "wb");
if (!f)
err("%s: Opening output file '%s' failed", __func__,
opts->outfile);
fwrite(gb->data, 1, gb->size - gb->trim * 8 * depth, f);
fclose(f);
}
int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles, int tile_size)
{
int i, j;
for (i = 0; i < num_tiles; i++) {
for (j = 0; j < tile_size; j++) {
if (tile[j] != tiles[i][j])
break;
}
if (j >= tile_size)
return i;
}
return -1;
}
uint8_t reverse_bits(uint8_t b)
{
uint8_t rev = 0;
rev |= (b & 0x80) >> 7;
rev |= (b & 0x40) >> 5;
rev |= (b & 0x20) >> 3;
rev |= (b & 0x10) >> 1;
rev |= (b & 0x08) << 1;
rev |= (b & 0x04) << 3;
rev |= (b & 0x02) << 5;
rev |= (b & 0x01) << 7;
return rev;
}
void xflip(uint8_t *tile, uint8_t *tile_xflip, int tile_size)
{
int i;
for (i = 0; i < tile_size; i++)
tile_xflip[i] = reverse_bits(tile[i]);
}
void yflip(uint8_t *tile, uint8_t *tile_yflip, int tile_size)
{
int i;
for (i = 0; i < tile_size; i++)
tile_yflip[i] = tile[(tile_size - i - 1) ^ (depth - 1)];
}
/*
* get_mirrored_tile_index looks for `tile` in tile array `tiles`, also
* checking x-, y-, and xy-mirrored versions of `tile`. If one is found,
* `*flags` is set according to the type of mirroring and the index of the
* matched tile is returned. If no match is found, -1 is returned.
*/
int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
int tile_size, int *flags)
{
int index;
uint8_t *tile_xflip;
uint8_t *tile_yflip;
index = get_tile_index(tile, tiles, num_tiles, tile_size);
if (index >= 0) {
*flags = 0;
return index;
}
tile_yflip = malloc(tile_size);
if (!tile_yflip)
err("%s: Failed to allocate memory for Y flip of tile",
__func__);
yflip(tile, tile_yflip, tile_size);
index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
if (index >= 0) {
*flags = YFLIP;
free(tile_yflip);
return index;
}
tile_xflip = malloc(tile_size);
if (!tile_xflip)
err("%s: Failed to allocate memory for X flip of tile",
__func__);
xflip(tile, tile_xflip, tile_size);
index = get_tile_index(tile_xflip, tiles, num_tiles, tile_size);
if (index >= 0) {
*flags = XFLIP;
free(tile_yflip);
free(tile_xflip);
return index;
}
yflip(tile_xflip, tile_yflip, tile_size);
index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
if (index >= 0)
*flags = XFLIP | YFLIP;
free(tile_yflip);
free(tile_xflip);
return index;
}
void create_mapfiles(const struct Options *opts, struct GBImage *gb,
struct Mapfile *tilemap, struct Mapfile *attrmap)
{
int i, j;
int gb_i;
int tile_size;
int max_tiles;
int num_tiles;
int index;
int flags;
int gb_size;
uint8_t *tile;
uint8_t **tiles;
tile_size = sizeof(*tile) * depth * 8;
gb_size = gb->size - (gb->trim * tile_size);
max_tiles = gb_size / tile_size;
/* If the input image doesn't fill the last tile, increase the count. */
if (gb_size > max_tiles * tile_size)
max_tiles++;
tiles = calloc(max_tiles, sizeof(*tiles));
if (!tiles)
err("%s: Failed to allocate memory for tiles", __func__);
num_tiles = 0;
if (*opts->tilemapfile) {
tilemap->data = calloc(max_tiles, sizeof(*tilemap->data));
if (!tilemap->data)
err("%s: Failed to allocate memory for tilemap data",
__func__);
tilemap->size = 0;
}
if (*opts->attrmapfile) {
attrmap->data = calloc(max_tiles, sizeof(*attrmap->data));
if (!attrmap->data)
err("%s: Failed to allocate memory for attrmap data",
__func__);
attrmap->size = 0;
}
gb_i = 0;
while (gb_i < gb_size) {
flags = 0;
tile = malloc(tile_size);
if (!tile)
err("%s: Failed to allocate memory for tile",
__func__);
/*
* If the input image doesn't fill the last tile,
* `gb_i` will reach `gb_size`.
*/
for (i = 0; i < tile_size && gb_i < gb_size; i++) {
tile[i] = gb->data[gb_i];
gb_i++;
}
if (opts->unique) {
if (opts->mirror) {
index = get_mirrored_tile_index(tile, tiles,
num_tiles,
tile_size,
&flags);
} else {
index = get_tile_index(tile, tiles, num_tiles,
tile_size);
}
if (index < 0) {
index = num_tiles;
tiles[num_tiles] = tile;
num_tiles++;
} else {
free(tile);
}
} else {
index = num_tiles;
tiles[num_tiles] = tile;
num_tiles++;
}
if (*opts->tilemapfile) {
tilemap->data[tilemap->size] = index;
tilemap->size++;
}
if (*opts->attrmapfile) {
attrmap->data[attrmap->size] = flags;
attrmap->size++;
}
}
if (opts->unique) {
free(gb->data);
gb->data = malloc(tile_size * num_tiles);
if (!gb->data)
err("%s: Failed to allocate memory for tile data",
__func__);
for (i = 0; i < num_tiles; i++) {
tile = tiles[i];
for (j = 0; j < tile_size; j++)
gb->data[i * tile_size + j] = tile[j];
}
gb->size = i * tile_size;
}
for (i = 0; i < num_tiles; i++)
free(tiles[i]);
free(tiles);
}
void output_tilemap_file(const struct Options *opts,
const struct Mapfile *tilemap)
{
FILE *f;
f = fopen(opts->tilemapfile, "wb");
if (!f)
err("%s: Opening tilemap file '%s' failed", __func__,
opts->tilemapfile);
fwrite(tilemap->data, 1, tilemap->size, f);
fclose(f);
if (opts->tilemapout)
free(opts->tilemapfile);
}
void output_attrmap_file(const struct Options *opts,
const struct Mapfile *attrmap)
{
FILE *f;
f = fopen(opts->attrmapfile, "wb");
if (!f)
err("%s: Opening attrmap file '%s' failed", __func__,
opts->attrmapfile);
fwrite(attrmap->data, 1, attrmap->size, f);
fclose(f);
if (opts->attrmapout)
free(opts->attrmapfile);
}
/*
* based on the Gaussian-like curve used by SameBoy since commit
* 65dd02cc52f531dbbd3a7e6014e99d5b24e71a4c (Oct 2017)
* with ties resolved by comparing the difference of the squares.
*/
static int reverse_curve[] = {
0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7,
7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10,
10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13,
13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24,
24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26,
26, 27, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 30, 30, 31,
};
void output_palette_file(const struct Options *opts,
const struct RawIndexedImage *raw_image)
{
FILE *f;
int i, color;
uint8_t cur_bytes[2];
f = fopen(opts->palfile, "wb");
if (!f)
err("%s: Opening palette file '%s' failed", __func__,
opts->palfile);
for (i = 0; i < raw_image->num_colors; i++) {
int r = raw_image->palette[i].red;
int g = raw_image->palette[i].green;
int b = raw_image->palette[i].blue;
if (opts->colorcurve) {
g = (g * 4 - b) / 3;
if (g < 0)
g = 0;
r = reverse_curve[r];
g = reverse_curve[g];
b = reverse_curve[b];
} else {
r >>= 3;
g >>= 3;
b >>= 3;
}
color = b << 10 | g << 5 | r;
cur_bytes[0] = color & 0xFF;
cur_bytes[1] = color >> 8;
fwrite(cur_bytes, 2, 1, f);
}
fclose(f);
if (opts->palout)
free(opts->palfile);
}

View File

@@ -1,358 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <png.h>
#include <stdlib.h>
#include <string.h>
#include "gfx/main.h"
#include "extern/getopt.h"
#include "version.h"
int depth, colors;
/* Short options */
static char const *optstring = "Aa:CDd:Ffhmo:Pp:Tt:uVvx:";
/*
* Equivalent long options
* Please keep in the same order as short opts
*
* Also, make sure long opts don't create ambiguity:
* A long opt's name should start with the same letter as its short opt,
* except if it doesn't create any ambiguity (`verbose` versus `version`).
* This is because long opt matching, even to a single char, is prioritized
* over short opt matching
*/
static struct option const longopts[] = {
{ "output-attr-map", no_argument, NULL, 'A' },
{ "attr-map", required_argument, NULL, 'a' },
{ "color-curve", no_argument, NULL, 'C' },
{ "debug", no_argument, NULL, 'D' },
{ "depth", required_argument, NULL, 'd' },
{ "fix", no_argument, NULL, 'f' },
{ "fix-and-save", no_argument, NULL, 'F' },
{ "horizontal", no_argument, NULL, 'h' },
{ "mirror-tiles", no_argument, NULL, 'm' },
{ "output", required_argument, NULL, 'o' },
{ "output-palette", no_argument, NULL, 'P' },
{ "palette", required_argument, NULL, 'p' },
{ "output-tilemap", no_argument, NULL, 'T' },
{ "tilemap", required_argument, NULL, 't' },
{ "unique-tiles", no_argument, NULL, 'u' },
{ "version", no_argument, NULL, 'V' },
{ "verbose", no_argument, NULL, 'v' },
{ "trim-end", required_argument, NULL, 'x' },
{ NULL, no_argument, NULL, 0 }
};
static void print_usage(void)
{
fputs(
"Usage: rgbgfx [-CDhmuVv] [-f | -F] [-a <attr_map> | -A] [-d <depth>]\n"
" [-o <out_file>] [-p <pal_file> | -P] [-t <tile_map> | -T]\n"
" [-x <tiles>] <file>\n"
"Useful options:\n"
" -f, --fix make the input image an indexed PNG\n"
" -m, --mirror-tiles optimize out mirrored tiles\n"
" -o, --output <path> set the output binary file\n"
" -t, --tilemap <path> set the output tilemap file\n"
" -u, --unique-tiles optimize out identical tiles\n"
" -V, --version print RGBGFX version and exit\n"
"\n"
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
stderr);
exit(1);
}
int main(int argc, char *argv[])
{
int ch, size;
struct Options opts = {0};
struct ImageOptions png_options = {0};
struct RawIndexedImage *raw_image;
struct GBImage gb = {0};
struct Mapfile tilemap = {0};
struct Mapfile attrmap = {0};
char *ext;
opts.tilemapfile = "";
opts.attrmapfile = "";
opts.palfile = "";
opts.outfile = "";
depth = 2;
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts,
NULL)) != -1) {
switch (ch) {
case 'A':
opts.attrmapout = true;
break;
case 'a':
opts.attrmapfile = musl_optarg;
break;
case 'C':
opts.colorcurve = true;
break;
case 'D':
opts.debug = true;
break;
case 'd':
depth = strtoul(musl_optarg, NULL, 0);
break;
case 'F':
opts.hardfix = true;
/* fallthrough */
case 'f':
opts.fix = true;
break;
case 'h':
opts.horizontal = true;
break;
case 'm':
opts.mirror = true;
opts.unique = true;
break;
case 'o':
opts.outfile = musl_optarg;
break;
case 'P':
opts.palout = true;
break;
case 'p':
opts.palfile = musl_optarg;
break;
case 'T':
opts.tilemapout = true;
break;
case 't':
opts.tilemapfile = musl_optarg;
break;
case 'u':
opts.unique = true;
break;
case 'V':
printf("rgbgfx %s\n", get_package_version_string());
exit(0);
case 'v':
opts.verbose = true;
break;
case 'x':
opts.trim = strtoul(musl_optarg, NULL, 0);
break;
default:
print_usage();
/* NOTREACHED */
}
}
argc -= musl_optind;
argv += musl_optind;
if (argc == 0) {
fputs("FATAL: no input files\n", stderr);
print_usage();
}
#define WARN_MISMATCH(property) \
warnx("The PNG's " property \
" setting doesn't match the one defined on the command line")
opts.infile = argv[argc - 1];
if (depth != 1 && depth != 2)
errx("Depth option must be either 1 or 2.");
colors = 1 << depth;
raw_image = input_png_file(&opts, &png_options);
png_options.tilemapfile = "";
png_options.attrmapfile = "";
png_options.palfile = "";
if (png_options.horizontal != opts.horizontal) {
if (opts.verbose)
WARN_MISMATCH("horizontal");
if (opts.hardfix)
png_options.horizontal = opts.horizontal;
}
if (png_options.horizontal)
opts.horizontal = png_options.horizontal;
if (png_options.trim != opts.trim) {
if (opts.verbose)
WARN_MISMATCH("trim");
if (opts.hardfix)
png_options.trim = opts.trim;
}
if (png_options.trim)
opts.trim = png_options.trim;
if (raw_image->width % 8) {
errx("Input PNG file %s not sized correctly. The image's width must be a multiple of 8.",
opts.infile);
}
if (raw_image->width / 8 > 1 && raw_image->height % 8) {
errx("Input PNG file %s not sized correctly. If the image is more than 1 tile wide, its height must be a multiple of 8.",
opts.infile);
}
if (opts.trim &&
opts.trim > (raw_image->width / 8) * (raw_image->height / 8) - 1) {
errx("Trim (%d) for input raw_image file '%s' too large (max: %u)",
opts.trim, opts.infile,
(raw_image->width / 8) * (raw_image->height / 8) - 1);
}
if (strcmp(png_options.tilemapfile, opts.tilemapfile) != 0) {
if (opts.verbose)
WARN_MISMATCH("tilemap file");
if (opts.hardfix)
png_options.tilemapfile = opts.tilemapfile;
}
if (!*opts.tilemapfile)
opts.tilemapfile = png_options.tilemapfile;
if (png_options.tilemapout != opts.tilemapout) {
if (opts.verbose)
WARN_MISMATCH("tilemap file");
if (opts.hardfix)
png_options.tilemapout = opts.tilemapout;
}
if (png_options.tilemapout)
opts.tilemapout = png_options.tilemapout;
if (strcmp(png_options.attrmapfile, opts.attrmapfile) != 0) {
if (opts.verbose)
WARN_MISMATCH("attrmap file");
if (opts.hardfix)
png_options.attrmapfile = opts.attrmapfile;
}
if (!*opts.attrmapfile)
opts.attrmapfile = png_options.attrmapfile;
if (png_options.attrmapout != opts.attrmapout) {
if (opts.verbose)
WARN_MISMATCH("attrmap file");
if (opts.hardfix)
png_options.attrmapout = opts.attrmapout;
}
if (png_options.attrmapout)
opts.attrmapout = png_options.attrmapout;
if (strcmp(png_options.palfile, opts.palfile) != 0) {
if (opts.verbose)
WARN_MISMATCH("palette file");
if (opts.hardfix)
png_options.palfile = opts.palfile;
}
if (!*opts.palfile)
opts.palfile = png_options.palfile;
if (png_options.palout != opts.palout) {
if (opts.verbose)
WARN_MISMATCH("palette file");
if (opts.hardfix)
png_options.palout = opts.palout;
}
#undef WARN_MISMATCH
if (png_options.palout)
opts.palout = png_options.palout;
if (!*opts.tilemapfile && opts.tilemapout) {
ext = strrchr(opts.infile, '.');
if (ext != NULL) {
size = ext - opts.infile + 9;
opts.tilemapfile = malloc(size);
strncpy(opts.tilemapfile, opts.infile, size);
*strrchr(opts.tilemapfile, '.') = '\0';
strcat(opts.tilemapfile, ".tilemap");
} else {
opts.tilemapfile = malloc(strlen(opts.infile) + 9);
strcpy(opts.tilemapfile, opts.infile);
strcat(opts.tilemapfile, ".tilemap");
}
}
if (!*opts.attrmapfile && opts.attrmapout) {
ext = strrchr(opts.infile, '.');
if (ext != NULL) {
size = ext - opts.infile + 9;
opts.attrmapfile = malloc(size);
strncpy(opts.attrmapfile, opts.infile, size);
*strrchr(opts.attrmapfile, '.') = '\0';
strcat(opts.attrmapfile, ".attrmap");
} else {
opts.attrmapfile = malloc(strlen(opts.infile) + 9);
strcpy(opts.attrmapfile, opts.infile);
strcat(opts.attrmapfile, ".attrmap");
}
}
if (!*opts.palfile && opts.palout) {
ext = strrchr(opts.infile, '.');
if (ext != NULL) {
size = ext - opts.infile + 5;
opts.palfile = malloc(size);
strncpy(opts.palfile, opts.infile, size);
*strrchr(opts.palfile, '.') = '\0';
strcat(opts.palfile, ".pal");
} else {
opts.palfile = malloc(strlen(opts.infile) + 5);
strcpy(opts.palfile, opts.infile);
strcat(opts.palfile, ".pal");
}
}
gb.size = raw_image->width * raw_image->height * depth / 8;
gb.data = calloc(gb.size, 1);
gb.trim = opts.trim;
gb.horizontal = opts.horizontal;
if (*opts.outfile || *opts.tilemapfile || *opts.attrmapfile) {
raw_to_gb(raw_image, &gb);
create_mapfiles(&opts, &gb, &tilemap, &attrmap);
}
if (*opts.outfile)
output_file(&opts, &gb);
if (*opts.tilemapfile)
output_tilemap_file(&opts, &tilemap);
if (*opts.attrmapfile)
output_attrmap_file(&opts, &attrmap);
if (*opts.palfile)
output_palette_file(&opts, raw_image);
if (opts.fix || opts.debug)
output_png_file(&opts, &png_options, raw_image);
destroy_raw_image(&raw_image);
free(gb.data);
return 0;
}

813
src/gfx/main.cpp Normal file
View File

@@ -0,0 +1,813 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include "gfx/main.hpp"
#include <algorithm>
#include <assert.h>
#include <cinttypes>
#include <cstdint>
#include <ctype.h>
#include <fstream>
#include <ios>
#include <limits>
#include <numeric>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string_view>
#include "extern/getopt.h"
#include "platform.h"
#include "version.h"
#include "gfx/pal_spec.hpp"
#include "gfx/process.hpp"
#include "gfx/reverse.hpp"
using namespace std::literals::string_view_literals;
Options options;
char const *externalPalSpec = nullptr;
static uintmax_t nbErrors;
[[noreturn]] void giveUp() {
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
exit(1);
}
void warning(char const *fmt, ...) {
va_list ap;
fputs("warning: ", stderr);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
putc('\n', stderr);
}
void error(char const *fmt, ...) {
va_list ap;
fputs("error: ", stderr);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
putc('\n', stderr);
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
nbErrors++;
}
[[noreturn]] void fatal(char const *fmt, ...) {
va_list ap;
fputs("FATAL: ", stderr);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
putc('\n', stderr);
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
nbErrors++;
giveUp();
}
void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
if (verbosity >= level) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
}
// Short options
static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:o:Pp:Qq:r:s:Tt:U:uVvx:Z";
/*
* Equivalent long options
* Please keep in the same order as short opts
*
* Also, make sure long opts don't create ambiguity:
* A long opt's name should start with the same letter as its short opt,
* except if it doesn't create any ambiguity (`verbose` versus `version`).
* This is because long opt matching, even to a single char, is prioritized
* over short opt matching
*/
static struct option const longopts[] = {
{"output-attr-map", no_argument, NULL, 'A'},
{"attr-map", required_argument, NULL, 'a'},
{"base-tiles", required_argument, NULL, 'b'},
{"color-curve", no_argument, NULL, 'C'},
{"colors", required_argument, NULL, 'c'},
{"debug", no_argument, NULL, 'D'}, // Ignored
{"depth", required_argument, NULL, 'd'},
{"fix", no_argument, NULL, 'f'},
{"fix-and-save", no_argument, NULL, 'F'}, // Deprecated
{"horizontal", no_argument, NULL, 'h'}, // Deprecated
{"slice", required_argument, NULL, 'L'},
{"mirror-tiles", no_argument, NULL, 'm'},
{"nb-tiles", required_argument, NULL, 'N'},
{"nb-palettes", required_argument, NULL, 'n'},
{"output", required_argument, NULL, 'o'},
{"output-palette", no_argument, NULL, 'P'},
{"palette", required_argument, NULL, 'p'},
{"output-palette-map", no_argument, NULL, 'Q'},
{"palette-map", required_argument, NULL, 'q'},
{"reverse", required_argument, NULL, 'r'},
{"output-tilemap", no_argument, NULL, 'T'},
{"tilemap", required_argument, NULL, 't'},
{"unit-size", required_argument, NULL, 'U'},
{"unique-tiles", no_argument, NULL, 'u'},
{"version", no_argument, NULL, 'V'},
{"verbose", no_argument, NULL, 'v'},
{"trim-end", required_argument, NULL, 'x'},
{"columns", no_argument, NULL, 'Z'},
{NULL, no_argument, NULL, 0 }
};
static void printUsage(void) {
fputs("Usage: rgbgfx [-r stride] [-CmuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
"Useful options:\n"
" -m, --mirror-tiles optimize out mirrored tiles\n"
" -o, --output <path> output the tile data to this path\n"
" -t, --tilemap <path> output the tile map to this path\n"
" -u, --unique-tiles optimize out identical tiles\n"
" -V, --version print RGBGFX version and exit\n"
"\n"
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
stderr);
exit(1);
}
/*
* Parses a number at the beginning of a string, moving the pointer to skip the parsed characters
* Returns the provided errVal on error
*/
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
uint8_t base = 10;
if (*string == '\0') {
error("%s: expected number, but found nothing", errPrefix);
return errVal;
} else if (*string == '$') {
base = 16;
++string;
} else if (*string == '%') {
base = 2;
++string;
} else if (*string == '0' && string[1] != '\0') {
// Check if we have a "0x" or "0b" here
if (string[1] == 'x' || string[1] == 'X') {
base = 16;
string += 2;
} else if (string[1] == 'b' || string[1] == 'B') {
base = 2;
string += 2;
}
}
/*
* Turns a digit into its numeric value in the current base, if it has one.
* Maximum is inclusive. The string_view is modified to "consume" all digits.
* Returns 255 on parse failure (including wrong char for base), in which case
* the string_view may be pointing on garbage.
*/
auto charIndex = [&base](unsigned char c) -> uint8_t {
unsigned char index = c - '0'; // Use wrapping semantics
if (base == 2 && index >= 2) {
return 255;
} else if (index < 10) {
return index;
} else if (base != 16) {
return 255; // Letters are only valid in hex
}
index = tolower(c) - 'a'; // OK because we pass an `unsigned char`
if (index < 6) {
return index + 10;
}
return 255;
};
if (charIndex(*string) == 255) {
error("%s: expected digit%s, but found nothing", errPrefix,
base != 10 ? " after base" : "");
return errVal;
}
uint16_t number = 0;
do {
// Read a character, and check if it's valid in the given base
uint8_t index = charIndex(*string);
if (index == 255) {
break; // Found an invalid character, end
}
++string;
number *= base;
number += index;
// The lax check covers the addition on top of the multiplication
if (number >= UINT16_MAX / base) {
error("%s: the number is too large!", errPrefix);
return errVal;
}
} while (*string != '\0'); // No more characters?
return number;
}
static void skipWhitespace(char *&arg) {
arg += strspn(arg, " \t");
}
static void registerInput(char const *arg) {
if (!options.input.empty()) {
fprintf(stderr,
"FATAL: input image specified more than once! (first \"%s\", then "
"\"%s\")\n",
options.input.c_str(), arg);
printUsage();
exit(1);
} else if (arg[0] == '\0') { // Empty input path
fprintf(stderr, "FATAL: input image path cannot be empty!\n");
printUsage();
exit(1);
} else {
options.input = arg;
}
}
/*
* Turn an "at-file"'s contents into an argv that `getopt` can handle
* @param argPool Argument characters will be appended to this vector, for storage purposes.
*/
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
std::filebuf file;
file.open(path, std::ios_base::in);
static_assert(decltype(file)::traits_type::eof() == EOF,
"isblank(char_traits<...>::eof()) is UB!");
std::vector<size_t> argvOfs;
for (;;) {
int c;
// First, discard any leading whitespace
do {
c = file.sbumpc();
if (c == EOF) {
return argvOfs;
}
} while (isblank(c));
switch (c) {
case '#': // If it's a comment, discard everything until EOL
while ((c = file.sbumpc()) != '\n') {
if (c == EOF) {
return argvOfs;
}
}
continue; // Start processing the next line
// If it's an empty line, ignore it
case '\r': // Assuming CRLF here
file.sbumpc(); // Discard the upcoming '\n'
[[fallthrough]];
case '\n':
continue; // Start processing the next line
}
// Alright, now we can parse the line
do {
// Read one argument (until the next whitespace char).
// We know there is one because we already have its first character in `c`.
argvOfs.push_back(argPool.size());
// Reading and appending characters one at a time may be inefficient, but I'm counting
// on `vector` and `sbumpc` to do the right thing here.
argPool.push_back(c); // Push the character we've already read
for (;;) {
c = file.sbumpc();
if (isblank(c) || c == '\n' || c == EOF) {
break;
} else if (c == '\r') {
file.sbumpc(); // Discard the '\n'
break;
}
argPool.push_back(c);
}
argPool.push_back('\0');
// Discard whitespace until the next argument (candidate)
while (isblank(c)) {
c = file.sbumpc();
}
if (c == '\r') {
c = file.sbumpc(); // Skip the '\n'
}
} while (c != '\n' && c != EOF); // End if we reached EOL
}
}
/*
* Parses an arg vector, modifying `options` as options are read.
* The three booleans are for the "auto path" flags, since their processing must be deferred to the
* end of option parsing.
*
* Returns NULL if the vector was fully parsed, or a pointer (which is part of the arg vector) to an
* "at-file" path if one is encountered.
*/
static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilemap,
bool &autoPalettes, bool &autoPalmap) {
int opt;
while ((opt = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1) {
char *arg = musl_optarg; // Make a copy for scanning
switch (opt) {
case 'A':
autoAttrmap = true;
break;
case 'a':
autoAttrmap = false;
options.attrmap = musl_optarg;
break;
case 'b':
options.baseTileIDs[0] = parseNumber(arg, "Bank 0 base tile ID", 0);
if (options.baseTileIDs[0] >= 256) {
error("Bank 0 base tile ID must be below 256");
}
if (*arg == '\0') {
options.baseTileIDs[1] = 0;
break;
}
skipWhitespace(arg);
if (*arg != ',') {
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
musl_optarg);
break;
}
++arg; // Skip comma
skipWhitespace(arg);
options.baseTileIDs[1] = parseNumber(arg, "Bank 1 base tile ID", 0);
if (options.baseTileIDs[1] >= 256) {
error("Bank 1 base tile ID must be below 256");
}
if (*arg != '\0') {
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
musl_optarg);
break;
}
break;
case 'C':
options.useColorCurve = true;
break;
case 'c':
if (musl_optarg[0] == '#') {
options.palSpecType = Options::EXPLICIT;
parseInlinePalSpec(musl_optarg);
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
// Use PLTE, error out if missing
options.palSpecType = Options::EMBEDDED;
} else {
options.palSpecType = Options::EXPLICIT;
// Can't parse the file yet, as "flat" color collections need to know the palette
// size to be split; thus, we defer that
// TODO: this does not validate the `fmt` part of any external spec but the last
// one, but I guess that's okay
externalPalSpec = musl_optarg;
}
break;
case 'd':
options.bitDepth = parseNumber(arg, "Bit depth", 2);
if (*arg != '\0') {
error("Bit depth (-b) argument must be a valid number, not \"%s\"", musl_optarg);
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
error("Bit depth must be 1 or 2, not %" PRIu8);
options.bitDepth = 2;
}
break;
case 'L':
options.inputSlice.left = parseNumber(arg, "Input slice left coordinate");
if (options.inputSlice.left > INT16_MAX) {
error("Input slice left coordinate is out of range!");
break;
}
skipWhitespace(arg);
if (*arg != ',') {
error("Missing comma after left coordinate in \"%s\"", musl_optarg);
break;
}
++arg;
skipWhitespace(arg);
options.inputSlice.top = parseNumber(arg, "Input slice upper coordinate");
skipWhitespace(arg);
if (*arg != ':') {
error("Missing colon after upper coordinate in \"%s\"", musl_optarg);
break;
}
++arg;
skipWhitespace(arg);
options.inputSlice.width = parseNumber(arg, "Input slice width");
skipWhitespace(arg);
if (options.inputSlice.width == 0) {
error("Input slice width may not be 0!");
}
if (*arg != ',') {
error("Missing comma after width in \"%s\"", musl_optarg);
break;
}
++arg;
skipWhitespace(arg);
options.inputSlice.height = parseNumber(arg, "Input slice height");
if (options.inputSlice.height == 0) {
error("Input slice height may not be 0!");
}
if (*arg != '\0') {
error("Unexpected extra characters after slice spec in \"%s\"", musl_optarg);
}
break;
case 'm':
options.allowMirroring = true;
[[fallthrough]]; // Imply `-u`
case 'u':
options.allowDedup = true;
break;
case 'N':
options.maxNbTiles[0] = parseNumber(arg, "Number of tiles in bank 0", 256);
if (options.maxNbTiles[0] > 256) {
error("Bank 0 cannot contain more than 256 tiles");
}
if (*arg == '\0') {
options.maxNbTiles[1] = 0;
break;
}
skipWhitespace(arg);
if (*arg != ',') {
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
musl_optarg);
break;
}
++arg; // Skip comma
skipWhitespace(arg);
options.maxNbTiles[1] = parseNumber(arg, "Number of tiles in bank 1", 256);
if (options.maxNbTiles[1] > 256) {
error("Bank 1 cannot contain more than 256 tiles");
}
if (*arg != '\0') {
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
musl_optarg);
break;
}
break;
case 'n':
options.nbPalettes = parseNumber(arg, "Number of palettes", 256);
if (*arg != '\0') {
error("Number of palettes (-n) must be a valid number, not \"%s\"", musl_optarg);
}
if (options.nbPalettes > 256) {
error("Number of palettes (-n) must not exceed 256!");
} else if (options.nbPalettes == 0) {
error("Number of palettes (-n) may not be 0!");
}
break;
case 'o':
options.output = musl_optarg;
break;
case 'P':
autoPalettes = true;
break;
case 'p':
autoPalettes = false;
options.palettes = musl_optarg;
break;
case 'Q':
autoPalmap = true;
break;
case 'q':
autoPalmap = false;
options.palmap = musl_optarg;
break;
case 'r':
options.reversedWidth = parseNumber(arg, "Reversed image stride");
if (*arg != '\0') {
error("Reversed image stride (-r) must be a valid number, not \"%s\"", musl_optarg);
}
if (options.reversedWidth == 0) {
error("Reversed image stride (-r) may not be 0!");
}
break;
case 's':
options.nbColorsPerPal = parseNumber(arg, "Number of colors per palette", 4);
if (*arg != '\0') {
error("Palette size (-s) must be a valid number, not \"%s\"", musl_optarg);
}
if (options.nbColorsPerPal > 4) {
error("Palette size (-s) must not exceed 4!");
} else if (options.nbColorsPerPal == 0) {
error("Palette size (-s) may not be 0!");
}
break;
case 'T':
autoTilemap = true;
break;
case 't':
autoTilemap = false;
options.tilemap = musl_optarg;
break;
case 'V':
printf("rgbgfx %s\n", get_package_version_string());
exit(0);
case 'v':
if (options.verbosity < Options::VERB_VVVVVV) {
++options.verbosity;
}
break;
case 'x':
options.trim = parseNumber(arg, "Number of tiles to trim", 0);
if (*arg != '\0') {
error("Tile trim (-x) argument must be a valid number, not \"%s\"", musl_optarg);
}
break;
case 'h':
warning("`-h` is deprecated, use `-Z` instead");
[[fallthrough]];
case 'Z':
options.columnMajor = true;
break;
case 1: // Positional argument, requested by leading `-` in opt string
if (musl_optarg[0] == '@') {
// Instruct the caller to process that at-file
return &musl_optarg[1];
} else {
registerInput(musl_optarg);
}
break;
case 'D':
case 'F':
case 'f':
warning("Ignoring retired option `-%c`", opt);
break;
default:
printUsage();
exit(1);
}
}
return nullptr; // Done processing this argv
}
int main(int argc, char *argv[]) {
bool autoAttrmap = false, autoTilemap = false, autoPalettes = false, autoPalmap = false;
struct AtFileStackEntry {
int parentInd; // Saved offset into parent argv
std::vector<char *> argv; // This context's arg pointer vec
std::vector<char> argPool;
AtFileStackEntry(int parentInd_, std::vector<char *> argv_)
: parentInd(parentInd_), argv(argv_) {}
};
std::vector<AtFileStackEntry> atFileStack;
int curArgc = argc;
char **curArgv = argv;
for (;;) {
char *atFileName =
parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes, autoPalmap);
if (atFileName) {
// Copy `argv[0]` for error reporting, and because option parsing skips it
AtFileStackEntry &stackEntry =
atFileStack.emplace_back(musl_optind, std::vector{atFileName});
// It would be nice to compute the char pointers on the fly, but reallocs don't allow
// that; so we must compute the offsets after the pool is fixed
auto offsets = readAtFile(&musl_optarg[1], stackEntry.argPool);
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
for (size_t ofs : offsets) {
stackEntry.argv.push_back(&stackEntry.argPool.data()[ofs]);
}
stackEntry.argv.push_back(nullptr); // Don't forget the arg vector terminator!
curArgc = stackEntry.argv.size() - 1;
curArgv = stackEntry.argv.data();
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
continue; // Begin scanning that arg vector
}
if (musl_optind != curArgc) {
// This happens if `--` is passed, process the remaining arg(s) as positional
assert(musl_optind < curArgc);
for (int i = musl_optind; i < curArgc; ++i) {
registerInput(argv[i]);
}
}
// Pop off the top stack entry, or end parsing if none
if (atFileStack.empty()) {
break;
}
// OK to restore `optind` directly, because `optpos` must be 0 right now.
// (Providing 0 would be a "proper" reset, but we want to resume parsing)
musl_optind = atFileStack.back().parentInd;
atFileStack.pop_back();
if (atFileStack.empty()) {
curArgc = argc;
curArgv = argv;
} else {
auto &vec = atFileStack.back().argv;
curArgc = vec.size();
curArgv = vec.data();
}
}
if (options.nbColorsPerPal == 0) {
options.nbColorsPerPal = 1u << options.bitDepth;
} else if (options.nbColorsPerPal > 1u << options.bitDepth) {
error("%" PRIu8 "bpp palettes can only contain %u colors, not %" PRIu8, options.bitDepth,
1u << options.bitDepth, options.nbColorsPerPal);
}
auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) {
if (autoOptEnabled) {
constexpr std::string_view chars =
// Both must start with a dot!
#if defined(_MSC_VER) || defined(__MINGW32__)
"./\\"sv;
#else
"./"sv;
#endif
size_t len = options.input.npos;
size_t i = options.input.find_last_of(chars);
if (i != options.input.npos && options.input[i] == '.') {
// We found the last dot, but check if it's part of a stem
// (There must be a non-path separator character before it)
if (i != 0 && chars.find(options.input[i - 1], 1) == chars.npos) {
// We can replace the extension
len = i;
}
}
path.assign(options.input, 0, len);
path.append(extension);
}
};
autoOutPath(autoAttrmap, options.attrmap, ".attrmap");
autoOutPath(autoTilemap, options.tilemap, ".tilemap");
autoOutPath(autoPalettes, options.palettes, ".pal");
autoOutPath(autoPalmap, options.palmap, ".palmap");
// Execute deferred external pal spec parsing, now that all other params are known
if (externalPalSpec) {
parseExternalPalSpec(externalPalSpec);
}
if (options.verbosity >= Options::VERB_CFG) {
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
if (options.verbosity >= Options::VERB_VVVVVV) {
fputc('\n', stderr);
static std::array<uint16_t, 21> gfx{
0x1FE, 0x3FF, 0x399, 0x399, 0x3FF, 0x3FF, 0x381, 0x3C3, 0x1FE, 0x078, 0x1FE,
0x3FF, 0x3FF, 0x3FF, 0x37B, 0x37B, 0x0FC, 0x0CC, 0x1CE, 0x1CE, 0x1CE,
};
static std::array<char const *, 3> textbox{
" ,----------------------------------------.",
" | Augh, dimensional interference again?! |",
" `----------------------------------------'"};
for (size_t i = 0; i < gfx.size(); ++i) {
uint16_t row = gfx[i];
for (uint8_t _ = 0; _ < 10; ++_) {
unsigned char c = row & 1 ? '0' : ' ';
fputc(c, stderr);
// Double the pixel horizontally, otherwise the aspect ratio looks wrong
fputc(c, stderr);
row >>= 1;
}
if (i < textbox.size()) {
fputs(textbox[i], stderr);
}
fputc('\n', stderr);
}
fputc('\n', stderr);
}
fputs("Options:\n", stderr);
if (options.columnMajor)
fputs("\tVisit image in column-major order\n", stderr);
if (options.allowMirroring)
fputs("\tAllow mirroring tiles\n", stderr);
if (options.allowDedup)
fputs("\tAllow deduplicating tiles\n", stderr);
if (options.useColorCurve)
fputs("\tUse color curve\n", stderr);
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
if (options.trim != 0)
fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
fprintf(stderr, "\tMaximum %" PRIu8 " palettes\n", options.nbPalettes);
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
fprintf(stderr, "\t%s palette spec\n", []() {
switch (options.palSpecType) {
case Options::NO_SPEC:
return "No";
case Options::EXPLICIT:
return "Explicit";
case Options::EMBEDDED:
return "Embedded";
}
return "???";
}());
if (options.palSpecType == Options::EXPLICIT) {
fputs("\t[\n", stderr);
for (std::array<Rgba, 4> const &pal : options.palSpec) {
fprintf(stderr, "\t\t#%06x, #%06x, #%06x, #%06x,\n", pal[0].toCSS() >> 8,
pal[1].toCSS() >> 8, pal[2].toCSS() >> 8, pal[3].toCSS() >> 8);
}
fputs("\t]\n", stderr);
}
fprintf(stderr,
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32
", %" PRIi32 ")\n",
options.inputSlice.width, options.inputSlice.height, options.inputSlice.left,
options.inputSlice.top);
fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0],
options.baseTileIDs[1]);
fprintf(stderr, "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
options.maxNbTiles[0], options.maxNbTiles[1]);
auto printPath = [](char const *name, std::string const &path) {
if (!path.empty()) {
fprintf(stderr, "\t%s: %s\n", name, path.c_str());
}
};
printPath("Input image", options.input);
printPath("Output tile data", options.output);
printPath("Output tilemap", options.tilemap);
printPath("Output attrmap", options.attrmap);
printPath("Output palettes", options.palettes);
fputs("Ready.\n", stderr);
}
if (options.input.empty()) {
fatal("No input image specified");
}
// Do not do anything if option parsing went wrong
if (nbErrors) {
giveUp();
}
if (options.reverse()) {
reverse();
} else {
process();
}
if (nbErrors) {
giveUp();
}
return 0;
}
void Palette::addColor(uint16_t color) {
for (size_t i = 0; true; ++i) {
assert(i < colors.size()); // The packing should guarantee this
if (colors[i] == color) { // The color is already present
break;
} else if (colors[i] == UINT16_MAX) { // Empty slot
colors[i] = color;
break;
}
}
}
/*
* Returns the ID of the color in the palette, or `size()` if the color is not in
*/
uint8_t Palette::indexOf(uint16_t color) const {
return std::find(colors.begin(), colors.end(), color) - colors.begin();
}
auto Palette::begin() -> decltype(colors)::iterator {
// Skip the first slot if reserved for transparency
return colors.begin() + options.hasTransparentPixels;
}
auto Palette::end() -> decltype(colors)::iterator {
return std::find(begin(), colors.end(), UINT16_MAX);
}
auto Palette::begin() const -> decltype(colors)::const_iterator {
// Skip the first slot if reserved for transparency
return colors.begin() + options.hasTransparentPixels;
}
auto Palette::end() const -> decltype(colors)::const_iterator {
return std::find(begin(), colors.end(), UINT16_MAX);
}
uint8_t Palette::size() const {
return indexOf(UINT16_MAX);
}

View File

@@ -1,806 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gfx/makepng.h"
static void initialize_png(struct PNGImage *img, FILE * f);
static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img);
static struct RawIndexedImage *grayscale_png_to_raw(struct PNGImage *img);
static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img);
static void get_text(const struct PNGImage *img,
struct ImageOptions *png_options);
static void set_text(const struct PNGImage *img,
const struct ImageOptions *png_options);
static void free_png_data(const struct PNGImage *png);
struct RawIndexedImage *input_png_file(const struct Options *opts,
struct ImageOptions *png_options)
{
struct PNGImage img;
struct RawIndexedImage *raw_image;
FILE *f;
f = fopen(opts->infile, "rb");
if (!f)
err("Opening input png file '%s' failed", opts->infile);
initialize_png(&img, f);
if (img.depth != depth) {
if (opts->verbose) {
warnx("Image bit depth is not %d (is %d).",
depth, img.depth);
}
}
switch (img.type) {
case PNG_COLOR_TYPE_PALETTE:
raw_image = indexed_png_to_raw(&img); break;
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_GRAY_ALPHA:
raw_image = grayscale_png_to_raw(&img); break;
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_RGB_ALPHA:
raw_image = truecolor_png_to_raw(&img); break;
default:
/* Shouldn't happen, but might as well handle just in case. */
errx("Input PNG file is of invalid color type.");
}
get_text(&img, png_options);
png_destroy_read_struct(&img.png, &img.info, NULL);
fclose(f);
free_png_data(&img);
return raw_image;
}
void output_png_file(const struct Options *opts,
const struct ImageOptions *png_options,
const struct RawIndexedImage *raw_image)
{
FILE *f;
char *outfile;
struct PNGImage img;
png_color *png_palette;
int i;
/*
* TODO: Variable outfile is for debugging purposes. Eventually,
* opts.infile will be used directly.
*/
if (opts->debug) {
outfile = malloc(strlen(opts->infile) + 5);
if (!outfile)
err("%s: Failed to allocate memory for outfile",
__func__);
strcpy(outfile, opts->infile);
strcat(outfile, ".out");
} else {
outfile = opts->infile;
}
f = fopen(outfile, "wb");
if (!f)
err("Opening output png file '%s' failed", outfile);
if (opts->debug)
free(outfile);
img.png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (!img.png)
errx("Creating png structure failed");
img.info = png_create_info_struct(img.png);
if (!img.info)
errx("Creating png info structure failed");
if (setjmp(png_jmpbuf(img.png)))
exit(1);
png_init_io(img.png, f);
png_set_IHDR(img.png, img.info, raw_image->width, raw_image->height,
8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_palette = malloc(sizeof(*png_palette) * raw_image->num_colors);
if (!png_palette)
err("%s: Failed to allocate memory for PNG palette",
__func__);
for (i = 0; i < raw_image->num_colors; i++) {
png_palette[i].red = raw_image->palette[i].red;
png_palette[i].green = raw_image->palette[i].green;
png_palette[i].blue = raw_image->palette[i].blue;
}
png_set_PLTE(img.png, img.info, png_palette, raw_image->num_colors);
free(png_palette);
if (opts->fix)
set_text(&img, png_options);
png_write_info(img.png, img.info);
png_write_image(img.png, (png_byte **) raw_image->data);
png_write_end(img.png, NULL);
png_destroy_write_struct(&img.png, &img.info);
fclose(f);
}
void destroy_raw_image(struct RawIndexedImage **raw_image_ptr_ptr)
{
struct RawIndexedImage *raw_image = *raw_image_ptr_ptr;
for (unsigned int y = 0; y < raw_image->height; y++)
free(raw_image->data[y]);
free(raw_image->data);
free(raw_image->palette);
free(raw_image);
*raw_image_ptr_ptr = NULL;
}
static void initialize_png(struct PNGImage *img, FILE *f)
{
img->png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (!img->png)
errx("Creating png structure failed");
img->info = png_create_info_struct(img->png);
if (!img->info)
errx("Creating png info structure failed");
if (setjmp(png_jmpbuf(img->png)))
exit(1);
png_init_io(img->png, f);
png_read_info(img->png, img->info);
img->width = png_get_image_width(img->png, img->info);
img->height = png_get_image_height(img->png, img->info);
img->depth = png_get_bit_depth(img->png, img->info);
img->type = png_get_color_type(img->png, img->info);
}
static void read_png(struct PNGImage *img);
static struct RawIndexedImage *create_raw_image(int width, int height,
int num_colors);
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
png_color const *palette, int num_colors);
static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img)
{
struct RawIndexedImage *raw_image;
png_color *palette;
int colors_in_PLTE;
int colors_in_new_palette;
png_byte *trans_alpha;
int num_trans;
png_color_16 *trans_color;
png_color *original_palette;
uint8_t *old_to_new_palette;
int i, x, y;
if (img->depth < 8)
png_set_packing(img->png);
png_get_PLTE(img->png, img->info, &palette, &colors_in_PLTE);
raw_image = create_raw_image(img->width, img->height, colors);
/*
* Transparent palette entries are removed, and the palette is
* collapsed. Transparent pixels are then replaced with palette index 0.
* This way, an indexed PNG can contain transparent pixels in *addition*
* to 4 normal colors.
*/
if (png_get_tRNS(img->png, img->info, &trans_alpha, &num_trans,
&trans_color)) {
original_palette = palette;
palette = malloc(sizeof(*palette) * colors_in_PLTE);
if (!palette)
err("%s: Failed to allocate memory for palette",
__func__);
colors_in_new_palette = 0;
old_to_new_palette = malloc(sizeof(*old_to_new_palette)
* colors_in_PLTE);
if (!old_to_new_palette)
err("%s: Failed to allocate memory for new palette",
__func__);
for (i = 0; i < num_trans; i++) {
if (trans_alpha[i] == 0) {
old_to_new_palette[i] = 0;
} else {
old_to_new_palette[i] = colors_in_new_palette;
palette[colors_in_new_palette++] =
original_palette[i];
}
}
for (i = num_trans; i < colors_in_PLTE; i++) {
old_to_new_palette[i] = colors_in_new_palette;
palette[colors_in_new_palette++] = original_palette[i];
}
if (colors_in_new_palette != colors_in_PLTE) {
palette = realloc(palette,
sizeof(*palette) *
colors_in_new_palette);
if (!palette)
err("%s: Failed to allocate memory for palette",
__func__);
}
/*
* Setting and validating palette before reading
* allows us to error out *before* doing the data
* transformation if the palette is too long.
*/
set_raw_image_palette(raw_image, palette,
colors_in_new_palette);
read_png(img);
for (y = 0; y < img->height; y++) {
for (x = 0; x < img->width; x++) {
raw_image->data[y][x] =
old_to_new_palette[img->data[y][x]];
}
}
free(palette);
free(old_to_new_palette);
} else {
set_raw_image_palette(raw_image, palette, colors_in_PLTE);
read_png(img);
for (y = 0; y < img->height; y++) {
for (x = 0; x < img->width; x++)
raw_image->data[y][x] = img->data[y][x];
}
}
return raw_image;
}
static struct RawIndexedImage *grayscale_png_to_raw(struct PNGImage *img)
{
if (img->depth < 8)
png_set_expand_gray_1_2_4_to_8(img->png);
png_set_gray_to_rgb(img->png);
return truecolor_png_to_raw(img);
}
static void rgba_png_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors);
static struct RawIndexedImage
*processed_rgba_png_to_raw(const struct PNGImage *img,
png_color const *palette,
int colors_in_palette);
static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img)
{
struct RawIndexedImage *raw_image;
png_color *palette;
int colors_in_palette;
if (img->depth == 16) {
#if PNG_LIBPNG_VER >= 10504
png_set_scale_16(img->png);
#else
png_set_strip_16(img->png);
#endif
}
if (!(img->type & PNG_COLOR_MASK_ALPHA)) {
if (png_get_valid(img->png, img->info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(img->png);
else
png_set_add_alpha(img->png, 0xFF, PNG_FILLER_AFTER);
}
read_png(img);
rgba_png_palette(img, &palette, &colors_in_palette);
raw_image = processed_rgba_png_to_raw(img, palette, colors_in_palette);
free(palette);
return raw_image;
}
static void rgba_PLTE_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors);
static void rgba_build_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors);
static void rgba_png_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors)
{
if (png_get_valid(img->png, img->info, PNG_INFO_PLTE))
rgba_PLTE_palette(img, palette_ptr_ptr, num_colors);
else
rgba_build_palette(img, palette_ptr_ptr, num_colors);
}
static void rgba_PLTE_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors)
{
png_get_PLTE(img->png, img->info, palette_ptr_ptr, num_colors);
/*
* Lets us free the palette manually instead of leaving it to libpng,
* which lets us handle a PLTE and a built palette the same way.
*/
png_data_freer(img->png, img->info,
PNG_USER_WILL_FREE_DATA, PNG_FREE_PLTE);
}
static void update_built_palette(png_color *palette,
png_color const *pixel_color, png_byte alpha,
int *num_colors, bool *only_grayscale);
static int fit_grayscale_palette(png_color *palette, int *num_colors);
static void order_color_palette(png_color *palette, int num_colors);
static void rgba_build_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors)
{
png_color *palette;
int y, value_index;
png_color cur_pixel_color;
png_byte cur_alpha;
bool only_grayscale = true;
/*
* By filling the palette up with black by default, if the image
* doesn't have enough colors, the palette gets padded with black.
*/
*palette_ptr_ptr = calloc(colors, sizeof(**palette_ptr_ptr));
if (!*palette_ptr_ptr)
err("%s: Failed to allocate memory for palette", __func__);
palette = *palette_ptr_ptr;
*num_colors = 0;
for (y = 0; y < img->height; y++) {
value_index = 0;
while (value_index < img->width * 4) {
cur_pixel_color.red = img->data[y][value_index++];
cur_pixel_color.green = img->data[y][value_index++];
cur_pixel_color.blue = img->data[y][value_index++];
cur_alpha = img->data[y][value_index++];
update_built_palette(palette, &cur_pixel_color,
cur_alpha,
num_colors, &only_grayscale);
}
}
/* In order not to count 100% transparent images as grayscale. */
only_grayscale = *num_colors ? only_grayscale : false;
if (!only_grayscale || !fit_grayscale_palette(palette, num_colors))
order_color_palette(palette, *num_colors);
}
static void update_built_palette(png_color *palette,
png_color const *pixel_color, png_byte alpha,
int *num_colors, bool *only_grayscale)
{
bool color_exists;
png_color cur_palette_color;
int i;
/*
* Transparent pixels don't count toward the palette,
* as they'll be replaced with color #0 later.
*/
if (alpha == 0)
return;
if (*only_grayscale && !(pixel_color->red == pixel_color->green &&
pixel_color->red == pixel_color->blue)) {
*only_grayscale = false;
}
color_exists = false;
for (i = 0; i < *num_colors; i++) {
cur_palette_color = palette[i];
if (pixel_color->red == cur_palette_color.red &&
pixel_color->green == cur_palette_color.green &&
pixel_color->blue == cur_palette_color.blue) {
color_exists = true;
break;
}
}
if (!color_exists) {
if (*num_colors == colors) {
errx("Too many colors in input PNG file to fit into a %d-bit palette (max %d).",
depth, colors);
}
palette[*num_colors] = *pixel_color;
(*num_colors)++;
}
}
static int fit_grayscale_palette(png_color *palette, int *num_colors)
{
int interval = 256 / colors;
png_color *fitted_palette = malloc(sizeof(*fitted_palette) * colors);
bool *set_indices = calloc(colors, sizeof(*set_indices));
int i, shade_index;
if (!fitted_palette)
err("%s: Failed to allocate memory for palette", __func__);
if (!set_indices)
err("%s: Failed to allocate memory for indices", __func__);
fitted_palette[0].red = 0xFF;
fitted_palette[0].green = 0xFF;
fitted_palette[0].blue = 0xFF;
fitted_palette[colors - 1].red = 0;
fitted_palette[colors - 1].green = 0;
fitted_palette[colors - 1].blue = 0;
if (colors == 4) {
fitted_palette[1].red = 0xA9;
fitted_palette[1].green = 0xA9;
fitted_palette[1].blue = 0xA9;
fitted_palette[2].red = 0x55;
fitted_palette[2].green = 0x55;
fitted_palette[2].blue = 0x55;
}
for (i = 0; i < *num_colors; i++) {
shade_index = colors - 1 - palette[i].red / interval;
if (set_indices[shade_index]) {
free(fitted_palette);
free(set_indices);
return false;
}
fitted_palette[shade_index] = palette[i];
set_indices[shade_index] = true;
}
for (i = 0; i < colors; i++)
palette[i] = fitted_palette[i];
*num_colors = colors;
free(fitted_palette);
free(set_indices);
return true;
}
/* A combined struct is needed to sort csolors in order of luminance. */
struct ColorWithLuminance {
png_color color;
int luminance;
};
static int compare_luminance(void const *a, void const *b)
{
const struct ColorWithLuminance *x, *y;
x = (const struct ColorWithLuminance *)a;
y = (const struct ColorWithLuminance *)b;
return y->luminance - x->luminance;
}
static void order_color_palette(png_color *palette, int num_colors)
{
int i;
struct ColorWithLuminance *palette_with_luminance =
malloc(sizeof(*palette_with_luminance) * num_colors);
if (!palette_with_luminance)
err("%s: Failed to allocate memory for palette", __func__);
for (i = 0; i < num_colors; i++) {
/*
* Normally this would be done with floats, but since it's only
* used for comparison, we might as well use integer math.
*/
palette_with_luminance[i].color = palette[i];
palette_with_luminance[i].luminance = 2126 * palette[i].red +
7152 * palette[i].green +
722 * palette[i].blue;
}
qsort(palette_with_luminance, num_colors,
sizeof(*palette_with_luminance), compare_luminance);
for (i = 0; i < num_colors; i++)
palette[i] = palette_with_luminance[i].color;
free(palette_with_luminance);
}
static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
const struct PNGImage *img,
int *value_index, int x, int y,
png_color const *palette,
int colors_in_palette);
static struct RawIndexedImage
*processed_rgba_png_to_raw(const struct PNGImage *img,
png_color const *palette,
int colors_in_palette)
{
struct RawIndexedImage *raw_image;
int x, y, value_index;
raw_image = create_raw_image(img->width, img->height, colors);
set_raw_image_palette(raw_image, palette, colors_in_palette);
for (y = 0; y < img->height; y++) {
x = raw_image->width - 1;
value_index = img->width * 4 - 1;
while (x >= 0) {
put_raw_image_pixel(raw_image, img,
&value_index, x, y,
palette, colors_in_palette);
x--;
}
}
return raw_image;
}
static uint8_t palette_index_of(png_color const *palette,
int num_colors, png_color const *color);
static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
const struct PNGImage *img,
int *value_index, int x, int y,
png_color const *palette,
int colors_in_palette)
{
png_color pixel_color;
png_byte alpha;
alpha = img->data[y][*value_index];
if (alpha == 0) {
raw_image->data[y][x] = 0;
*value_index -= 4;
} else {
(*value_index)--;
pixel_color.blue = img->data[y][(*value_index)--];
pixel_color.green = img->data[y][(*value_index)--];
pixel_color.red = img->data[y][(*value_index)--];
raw_image->data[y][x] = palette_index_of(palette,
colors_in_palette,
&pixel_color);
}
}
static uint8_t palette_index_of(png_color const *palette,
int num_colors, png_color const *color)
{
uint8_t i;
for (i = 0; i < num_colors; i++) {
if (palette[i].red == color->red &&
palette[i].green == color->green &&
palette[i].blue == color->blue) {
return i;
}
}
errx("The input PNG file contains colors that don't appear in its embedded palette.");
}
static void read_png(struct PNGImage *img)
{
int y;
png_read_update_info(img->png, img->info);
img->data = malloc(sizeof(*img->data) * img->height);
if (!img->data)
err("%s: Failed to allocate memory for image data",
__func__);
for (y = 0; y < img->height; y++) {
img->data[y] = malloc(png_get_rowbytes(img->png, img->info));
if (!img->data[y])
err("%s: Failed to allocate memory for image data",
__func__);
}
png_read_image(img->png, img->data);
png_read_end(img->png, img->info);
}
static struct RawIndexedImage *create_raw_image(int width, int height,
int num_colors)
{
struct RawIndexedImage *raw_image;
int y;
raw_image = malloc(sizeof(*raw_image));
if (!raw_image)
err("%s: Failed to allocate memory for raw image",
__func__);
raw_image->width = width;
raw_image->height = height;
raw_image->num_colors = num_colors;
raw_image->palette = malloc(sizeof(*raw_image->palette) * num_colors);
if (!raw_image->palette)
err("%s: Failed to allocate memory for raw image palette",
__func__);
raw_image->data = malloc(sizeof(*raw_image->data) * height);
if (!raw_image->data)
err("%s: Failed to allocate memory for raw image data",
__func__);
for (y = 0; y < height; y++) {
raw_image->data[y] = malloc(sizeof(*raw_image->data[y])
* width);
if (!raw_image->data[y])
err("%s: Failed to allocate memory for raw image data",
__func__);
}
return raw_image;
}
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
png_color const *palette, int num_colors)
{
int i;
if (num_colors > raw_image->num_colors) {
errx("Too many colors in input PNG file's palette to fit into a %d-bit palette (%d in input palette, max %d).",
raw_image->num_colors >> 1,
num_colors, raw_image->num_colors);
}
for (i = 0; i < num_colors; i++) {
raw_image->palette[i].red = palette[i].red;
raw_image->palette[i].green = palette[i].green;
raw_image->palette[i].blue = palette[i].blue;
}
for (i = num_colors; i < raw_image->num_colors; i++) {
raw_image->palette[i].red = 0;
raw_image->palette[i].green = 0;
raw_image->palette[i].blue = 0;
}
}
static void get_text(const struct PNGImage *img,
struct ImageOptions *png_options)
{
png_text *text;
int i, numtxts, numremoved;
png_get_text(img->png, img->info, &text, &numtxts);
for (i = 0; i < numtxts; i++) {
if (strcmp(text[i].key, "h") == 0 && !*text[i].text) {
png_options->horizontal = true;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "x") == 0) {
png_options->trim = strtoul(text[i].text, NULL, 0);
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "t") == 0) {
png_options->tilemapfile = text[i].text;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "T") == 0 && !*text[i].text) {
png_options->tilemapout = true;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "a") == 0) {
png_options->attrmapfile = text[i].text;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "A") == 0 && !*text[i].text) {
png_options->attrmapout = true;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "p") == 0) {
png_options->palfile = text[i].text;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "P") == 0 && !*text[i].text) {
png_options->palout = true;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
}
}
/*
* TODO: Remove this and simply change the warning function not to warn
* instead.
*/
for (i = 0, numremoved = 0; i < numtxts; i++) {
if (text[i].key == NULL)
numremoved++;
text[i].key = text[i + numremoved].key;
text[i].text = text[i + numremoved].text;
text[i].compression = text[i + numremoved].compression;
}
png_set_text(img->png, img->info, text, numtxts - numremoved);
}
static void set_text(const struct PNGImage *img,
const struct ImageOptions *png_options)
{
png_text *text;
char buffer[3];
text = malloc(sizeof(*text));
if (!text)
err("%s: Failed to allocate memory for PNG text",
__func__);
if (png_options->horizontal) {
text[0].key = "h";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(img->png, img->info, text, 1);
}
if (png_options->trim) {
text[0].key = "x";
snprintf(buffer, 3, "%d", png_options->trim);
text[0].text = buffer;
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(img->png, img->info, text, 1);
}
if (*png_options->tilemapfile) {
text[0].key = "t";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(img->png, img->info, text, 1);
}
if (png_options->tilemapout) {
text[0].key = "T";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(img->png, img->info, text, 1);
}
if (*png_options->attrmapfile) {
text[0].key = "a";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(img->png, img->info, text, 1);
}
if (png_options->attrmapout) {
text[0].key = "A";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(img->png, img->info, text, 1);
}
if (*png_options->palfile) {
text[0].key = "p";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(img->png, img->info, text, 1);
}
if (png_options->palout) {
text[0].key = "P";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(img->png, img->info, text, 1);
}
free(text);
}
static void free_png_data(const struct PNGImage *img)
{
int y;
for (y = 0; y < img->height; y++)
free(img->data[y]);
free(img->data);
}

512
src/gfx/pal_packing.cpp Normal file
View File

@@ -0,0 +1,512 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include "gfx/pal_packing.hpp"
#include <algorithm>
#include <assert.h>
#include <bitset>
#include <cinttypes>
#include <deque>
#include <numeric>
#include <optional>
#include <queue>
#include <tuple>
#include <type_traits>
#include <unordered_set>
#include <vector>
#include "defaultinitalloc.hpp"
#include "gfx/main.hpp"
#include "gfx/proto_palette.hpp"
using std::swap;
namespace packing {
// The solvers here are picked from the paper at http://arxiv.org/abs/1605.00558:
// "Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items"
// Their formulation of the problem consists in packing "tiles" into "pages"; here is a
// correspondence table for our application of it:
// Paper | RGBGFX
// ------+-------
// Tile | Proto-palette
// Page | Palette
/*
* A reference to a proto-palette, and attached attributes for sorting purposes
*/
struct ProtoPalAttrs {
size_t const protoPalIndex;
/*
* Pages from which we are banned (to prevent infinite loops)
* This is dynamic because we wish not to hard-cap the amount of palettes
*/
std::vector<bool> bannedPages;
ProtoPalAttrs(size_t index) : protoPalIndex(index) {}
bool isBannedFrom(size_t index) const {
return index < bannedPages.size() && bannedPages[index];
}
void banFrom(size_t index) {
if (bannedPages.size() <= index) {
bannedPages.resize(index + 1);
}
bannedPages[index] = true;
}
};
/*
* A collection of proto-palettes assigned to a palette
* Does not contain the actual color indices because we need to be able to remove elements
*/
class AssignedProtos {
// We leave room for emptied slots to avoid copying the structs around on removal
std::vector<std::optional<ProtoPalAttrs>> _assigned;
// For resolving proto-palette indices
std::vector<ProtoPalette> const *_protoPals;
public:
template<typename... Ts>
AssignedProtos(std::vector<ProtoPalette> const &protoPals, Ts &&...elems)
: _assigned{std::forward<Ts>(elems)...}, _protoPals{&protoPals} {}
private:
template<typename Inner, template<typename> typename Constness>
class Iter {
public:
friend class AssignedProtos;
// For `iterator_traits`
using difference_type = typename std::iterator_traits<Inner>::difference_type;
using value_type = ProtoPalAttrs;
using pointer = Constness<value_type> *;
using reference = Constness<value_type> &;
using iterator_category = std::forward_iterator_tag;
private:
Constness<decltype(_assigned)> *_array = nullptr;
Inner _iter{};
Iter(decltype(_array) array, decltype(_iter) &&iter) : _array(array), _iter(iter) {}
Iter &skipEmpty() {
while (_iter != _array->end() && !_iter->has_value()) {
++_iter;
}
return *this;
}
public:
Iter() = default;
bool operator==(Iter const &other) const { return _iter == other._iter; }
bool operator!=(Iter const &other) const { return !(*this == other); }
Iter &operator++() {
++_iter;
skipEmpty();
return *this;
}
Iter operator++(int) {
Iter it = *this;
++(*this);
return it;
}
reference operator*() const {
assert((*_iter).has_value());
return **_iter;
}
pointer operator->() const {
return &(**this); // Invokes the operator above, not quite a no-op!
}
friend void swap(Iter &lhs, Iter &rhs) {
swap(lhs._array, rhs._array);
swap(lhs._iter, rhs._iter);
}
};
public:
using iterator = Iter<decltype(_assigned)::iterator, std::remove_const_t>;
iterator begin() { return iterator{&_assigned, _assigned.begin()}.skipEmpty(); }
iterator end() { return iterator{&_assigned, _assigned.end()}; }
using const_iterator = Iter<decltype(_assigned)::const_iterator, std::add_const_t>;
const_iterator begin() const {
return const_iterator{&_assigned, _assigned.begin()}.skipEmpty();
}
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
/*
* Assigns a new ProtoPalAttrs in a free slot, assuming there is one
* Args are passed to the `ProtoPalAttrs`'s constructor
*/
template<typename... Ts>
void assign(Ts &&...args) {
auto freeSlot = std::find_if_not(
_assigned.begin(), _assigned.end(),
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); });
if (freeSlot == _assigned.end()) { // We are full, use a new slot
_assigned.emplace_back(std::forward<Ts>(args)...);
} else { // Reuse a free slot
freeSlot->emplace(std::forward<Ts>(args)...);
}
}
void remove(iterator const &iter) {
iter._iter->reset(); // This time, we want to access the `optional` itself
}
void clear() { _assigned.clear(); }
bool empty() const {
return std::find_if(
_assigned.begin(), _assigned.end(),
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); })
== _assigned.end();
}
size_t nbProtoPals() const { return std::distance(begin(), end()); }
private:
template<typename Iter>
static void addUniqueColors(std::unordered_set<uint16_t> &colors, Iter iter, Iter const &end,
std::vector<ProtoPalette> const &protoPals) {
for (; iter != end; ++iter) {
ProtoPalette const &protoPal = protoPals[iter->protoPalIndex];
colors.insert(protoPal.begin(), protoPal.end());
}
}
// This function should stay private because it returns a reference to a unique object
std::unordered_set<uint16_t> &uniqueColors() const {
// We check for *distinct* colors by stuffing them into a `set`; this should be
// faster than "back-checking" on every element (O(n²))
//
// TODO: calc84maniac suggested another approach; try implementing it, see if it
// performs better:
// > So basically you make a priority queue that takes iterators into each of your sets
// > (paired with end iterators so you'll know where to stop), and the comparator tests the
// > values pointed to by each iterator
// > Then each iteration you pop from the queue,
// > optionally add one to your count, increment the iterator and push it back into the
// > queue if it didn't reach the end
// > And you do this until the priority queue is empty
static std::unordered_set<uint16_t> colors;
colors.clear();
addUniqueColors(colors, begin(), end(), *_protoPals);
return colors;
}
public:
/*
* Returns the number of distinct colors
*/
size_t volume() const { return uniqueColors().size(); }
bool canFit(ProtoPalette const &protoPal) const {
auto &colors = uniqueColors();
colors.insert(protoPal.begin(), protoPal.end());
return colors.size() <= options.maxOpaqueColors();
}
/*
* Computes the "relative size" of a proto-palette on this palette
*/
double relSizeOf(ProtoPalette const &protoPal) const {
// NOTE: this function must not call `uniqueColors`, or one of its callers will break!
double relSize = 0.;
for (uint16_t color : protoPal) {
auto n = std::count_if(begin(), end(), [this, &color](ProtoPalAttrs const &attrs) {
ProtoPalette const &pal = (*_protoPals)[attrs.protoPalIndex];
return std::find(pal.begin(), pal.end(), color) != pal.end();
});
// NOTE: The paper and the associated code disagree on this: the code has
// this `1 +`, whereas the paper does not; its lack causes a division by 0
// if the symbol is not found anywhere, so I'm assuming the paper is wrong.
relSize += 1. / (1 + n);
}
return relSize;
}
/*
* Computes the "relative size" of a set of proto-palettes on this palette
*/
template<typename Iter>
auto combinedVolume(Iter &&begin, Iter const &end,
std::vector<ProtoPalette> const &protoPals) const {
auto &colors = uniqueColors();
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
return colors.size();
}
/*
* Computes the "relative size" of a set of colors on this palette
*/
template<typename Iter>
auto combinedVolume(Iter &&begin, Iter &&end) const {
auto &colors = uniqueColors();
colors.insert(std::forward<Iter>(begin), std::forward<Iter>(end));
return colors.size();
}
};
static void decant(std::vector<AssignedProtos> &assignments,
std::vector<ProtoPalette> const &protoPalettes) {
// "Decanting" is the process of moving all *things* that can fit in a lower index there
auto decantOn = [&assignments](auto const &tryDecanting) {
// No need to attempt decanting on palette #0, as there are no palettes to decant to
for (size_t from = assignments.size(); --from;) {
// Scan all palettes before this one
for (size_t to = 0; to < from; ++to) {
tryDecanting(assignments[to], assignments[from]);
}
// If the proto-palette is now empty, remove it
// Doing this now reduces the number of iterations performed by later steps
// NB: order is intentionally preserved so as not to alter the "decantation"'s
// properties
// NB: this does mean that the first step might get empty palettes as its input!
// NB: this is safe to do because we go towards the beginning of the vector, thereby not
// invalidating our iteration (thus, iterators should not be used to drivethe outer
// loop)
if (assignments[from].empty()) {
assignments.erase(assignments.begin() + from);
}
}
};
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes before decanting\n",
assignments.size());
// Decant on palettes
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
// If the entire palettes can be merged, move all of `from`'s proto-palettes
if (to.combinedVolume(from.begin(), from.end(), protoPalettes)
<= options.maxOpaqueColors()) {
for (ProtoPalAttrs &attrs : from) {
to.assign(attrs.protoPalIndex);
}
from.clear();
}
});
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n",
assignments.size());
// Decant on "components" (= proto-pals sharing colors)
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
// We need to iterate on all the "components", which are groups of proto-palettes sharing at
// least one color with another proto-palettes in the group.
// We do this by adding the first available proto-palette, and then looking for palettes
// with common colors. (As an optimization, we know we can skip palettes already scanned.)
std::vector<bool> processed(from.nbProtoPals(), false);
std::unordered_set<uint16_t> colors;
std::vector<size_t> members;
while (true) {
auto iter = std::find(processed.begin(), processed.end(), true);
if (iter == processed.end()) { // Processed everything!
break;
}
auto attrs = from.begin();
std::advance(attrs, (iter - processed.begin()));
// Build up the "component"...
colors.clear();
members.clear();
assert(members.empty()); // Compiler optimization hint
do {
ProtoPalette const &protoPal = protoPalettes[attrs->protoPalIndex];
// If this is the first proto-pal, or if at least one color matches, add it
if (members.empty()
|| std::find_first_of(colors.begin(), colors.end(), protoPal.begin(),
protoPal.end())
!= colors.end()) {
colors.insert(protoPal.begin(), protoPal.end());
members.push_back(iter - processed.begin());
*iter = true; // Mark that proto-pal as processed
}
++iter;
++attrs;
} while (iter != processed.end());
if (to.combinedVolume(colors.begin(), colors.end()) <= options.maxOpaqueColors()) {
// Iterate through the component's proto-palettes, and transfer them
auto member = from.begin();
size_t curIndex = 0;
for (size_t index : members) {
std::advance(member, index - curIndex);
curIndex = index;
to.assign(std::move(*member));
from.remove(member); // Removing does not shift elements, so it's cheap
}
}
}
});
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n",
assignments.size());
// Decant on individual proto-palettes
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
for (auto iter = from.begin(); iter != from.end(); ++iter) {
if (to.canFit(protoPalettes[iter->protoPalIndex])) {
to.assign(std::move(*iter));
from.remove(iter);
}
}
});
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n",
assignments.size());
}
std::tuple<DefaultInitVec<size_t>, size_t>
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes) {
options.verbosePrint(Options::VERB_LOG_ACT,
"Paginating palettes using \"overload-and-remove\" strategy...\n");
// Sort the proto-palettes by size, which improves the packing algorithm's efficiency
DefaultInitVec<size_t> sortedProtoPalIDs(protoPalettes.size());
sortedProtoPalIDs.clear();
for (size_t i = 0; i < protoPalettes.size(); ++i) {
sortedProtoPalIDs.insert(
std::lower_bound(sortedProtoPalIDs.begin(), sortedProtoPalIDs.end(), i), i);
}
// Begin with all proto-palettes queued up for insertion
std::queue<ProtoPalAttrs> queue(
std::deque<ProtoPalAttrs>(sortedProtoPalIDs.begin(), sortedProtoPalIDs.end()));
// Begin with no pages
std::vector<AssignedProtos> assignments{};
for (; !queue.empty(); queue.pop()) {
ProtoPalAttrs const &attrs = queue.front(); // Valid until the `queue.pop()`
options.verbosePrint(Options::VERB_DEBUG, "Handling proto-pal %zu\n", attrs.protoPalIndex);
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
size_t bestPalIndex = assignments.size();
// We're looking for a palette where the proto-palette's relative size is less than
// its actual size; so only overwrite the "not found" index on meeting that criterion
double bestRelSize = protoPal.size();
for (size_t i = 0; i < assignments.size(); ++i) {
// Skip the page if this one is banned from it
if (attrs.isBannedFrom(i)) {
continue;
}
options.verbosePrint(Options::VERB_DEBUG, "%zu/%zu: Rel size: %f (size = %zu)\n", i + 1,
assignments.size(), assignments[i].relSizeOf(protoPal),
protoPal.size());
if (assignments[i].relSizeOf(protoPal) < bestRelSize) {
bestPalIndex = i;
}
}
if (bestPalIndex == assignments.size()) {
// Found nowhere to put it, create a new page containing just that one
assignments.emplace_back(protoPalettes, std::move(attrs));
} else {
auto &bestPal = assignments[bestPalIndex];
// Add the color to that palette
bestPal.assign(std::move(attrs));
// If this overloads the palette, get it back to normal (if possible)
while (bestPal.volume() > options.maxOpaqueColors()) {
options.verbosePrint(Options::VERB_DEBUG,
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
bestPalIndex, bestPal.volume(), options.maxOpaqueColors());
// Look for a proto-pal minimizing "efficiency" (size / rel_size)
auto efficiency = [&bestPal](ProtoPalette const &pal) {
return pal.size() / bestPal.relSizeOf(pal);
};
auto [minEfficiencyIter, maxEfficiencyIter] =
std::minmax_element(bestPal.begin(), bestPal.end(),
[&efficiency, &protoPalettes](ProtoPalAttrs const &lhs,
ProtoPalAttrs const &rhs) {
return efficiency(protoPalettes[lhs.protoPalIndex])
< efficiency(protoPalettes[rhs.protoPalIndex]);
});
// All efficiencies are identical iff min equals max
// TODO: maybe not ideal to re-compute these two?
// TODO: yikes for float comparison! I *think* this threshold is OK?
if (efficiency(protoPalettes[maxEfficiencyIter->protoPalIndex])
- efficiency(protoPalettes[minEfficiencyIter->protoPalIndex])
< .001) {
break;
}
// Remove the proto-pal with minimal efficiency
queue.emplace(std::move(*minEfficiencyIter));
queue.back().banFrom(bestPalIndex); // Ban it from this palette
bestPal.remove(minEfficiencyIter);
}
}
}
// Deal with palettes still overloaded, by emptying them
for (AssignedProtos &pal : assignments) {
if (pal.volume() > options.maxOpaqueColors()) {
for (ProtoPalAttrs &attrs : pal) {
queue.emplace(std::move(attrs));
}
pal.clear();
}
}
// Place back any proto-palettes now in the queue via first-fit
while (!queue.empty()) {
ProtoPalAttrs const &attrs = queue.front();
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
auto iter =
std::find_if(assignments.begin(), assignments.end(),
[&protoPal](AssignedProtos const &pal) { return pal.canFit(protoPal); });
if (iter == assignments.end()) { // No such page, create a new one
options.verbosePrint(Options::VERB_DEBUG,
"Adding new palette (%zu) for overflowing proto-pal %zu\n",
assignments.size(), attrs.protoPalIndex);
assignments.emplace_back(protoPalettes, std::move(attrs));
} else {
options.verbosePrint(Options::VERB_DEBUG,
"Assigning overflowing proto-pal %zu to palette %zu\n",
attrs.protoPalIndex, iter - assignments.begin());
iter->assign(std::move(attrs));
}
queue.pop();
}
if (options.verbosity >= Options::VERB_INTERM) {
for (auto &&assignment : assignments) {
fprintf(stderr, "{ ");
for (auto &&attrs : assignment) {
fprintf(stderr, "[%zu] ", attrs.protoPalIndex);
for (auto &&colorIndex : protoPalettes[attrs.protoPalIndex]) {
fprintf(stderr, "%04" PRIx16 ", ", colorIndex);
}
}
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
}
}
// "Decant" the result
decant(assignments, protoPalettes);
// Note that the result does not contain any empty palettes
if (options.verbosity >= Options::VERB_INTERM) {
for (auto &&assignment : assignments) {
fprintf(stderr, "{ ");
for (auto &&attrs : assignment) {
fprintf(stderr, "[%zu] ", attrs.protoPalIndex);
for (auto &&colorIndex : protoPalettes[attrs.protoPalIndex]) {
fprintf(stderr, "%04" PRIx16 ", ", colorIndex);
}
}
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
}
}
DefaultInitVec<size_t> mappings(protoPalettes.size());
for (size_t i = 0; i < assignments.size(); ++i) {
for (ProtoPalAttrs const &attrs : assignments[i]) {
mappings[attrs.protoPalIndex] = i;
}
}
return {mappings, assignments.size()};
}
} // namespace packing

112
src/gfx/pal_sorting.cpp Normal file
View File

@@ -0,0 +1,112 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include "gfx/pal_sorting.hpp"
#include <algorithm>
#include <png.h>
#include <vector>
#include "helpers.h"
#include "gfx/main.hpp"
#include "gfx/process.hpp"
namespace sorting {
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
png_byte *palAlpha) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes using embedded palette...\n");
auto pngToRgb = [&palRGB, &palAlpha](int index) {
auto const &c = palRGB[index];
return Rgba(c.red, c.green, c.blue, palAlpha ? palAlpha[index] : 0xFF);
};
// HACK: for compatibility with old versions, add unused colors if:
// - there is only one palette, and
// - only some of the first N colors are being used
if (palettes.size() == 1) {
Palette &palette = palettes[0];
// Build our candidate array of colors
decltype(palette.colors) colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
for (int i = 0; i < options.maxOpaqueColors(); ++i) {
colors[i + options.hasTransparentPixels] = pngToRgb(i).cgbColor();
}
// Check that the palette only uses those colors
if (std::all_of(palette.begin(), palette.end(), [&colors](uint16_t color) {
return std::find(colors.begin(), colors.end(), color) != colors.end();
})) {
if (palette.size() != options.maxOpaqueColors()) {
warning("Unused color in PNG embedded palette was re-added; please use `-c "
"embedded` to get this in future versions");
}
// Overwrite the palette, and return with that (it's already sorted)
palette.colors = colors;
return;
}
}
for (Palette &pal : palettes) {
std::sort(pal.begin(), pal.end(), [&](uint16_t lhs, uint16_t rhs) {
// Iterate through the PNG's palette, looking for either of the two
for (int i = 0; i < palSize; ++i) {
uint16_t color = pngToRgb(i).cgbColor();
if (color == Rgba::transparent) {
continue;
}
// Return whether lhs < rhs
if (color == rhs) {
return false;
}
if (color == lhs) {
return true;
}
}
unreachable_(); // This should not be possible
});
}
}
void grayscale(std::vector<Palette> &palettes,
std::array<std::optional<Rgba>, 0x8001> const &colors) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n");
// This method is only applicable if there are at most as many colors as colors per palette, so
// we should only have a single palette.
assert(palettes.size() == 1);
Palette &palette = palettes[0];
std::fill(palette.colors.begin(), palette.colors.end(), Rgba::transparent);
for (auto const &slot : colors) {
if (!slot.has_value() || slot->isTransparent()) {
continue;
}
palette[slot->grayIndex()] = slot->cgbColor();
}
}
static unsigned int legacyLuminance(uint16_t color) {
uint8_t red = color & 0b11111;
uint8_t green = color >> 5 & 0b11111;
uint8_t blue = color >> 10;
return 2126 * red + 7152 * green + 722 * blue;
}
void rgb(std::vector<Palette> &palettes) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by \"\"\"luminance\"\"\"...\n");
for (Palette &pal : palettes) {
std::sort(pal.begin(), pal.end(), [](uint16_t lhs, uint16_t rhs) {
return legacyLuminance(lhs) > legacyLuminance(rhs);
});
}
}
} // namespace sorting

451
src/gfx/pal_spec.cpp Normal file
View File

@@ -0,0 +1,451 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include "gfx/pal_spec.hpp"
#include <algorithm>
#include <cassert>
#include <cinttypes>
#include <climits>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <ostream>
#include <streambuf>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include "platform.h"
#include "gfx/main.hpp"
using namespace std::string_view_literals;
constexpr uint8_t nibble(char c) {
if (c >= 'a') {
assert(c <= 'f');
return c - 'a' + 10;
} else if (c >= 'A') {
assert(c <= 'F');
return c - 'A' + 10;
} else {
assert(c >= '0' && c <= '9');
return c - '0';
}
}
constexpr uint8_t toHex(char c1, char c2) {
return nibble(c1) * 16 + nibble(c2);
}
constexpr uint8_t singleToHex(char c) {
return toHex(c, c);
}
template<typename Str> // Should be std::string or std::string_view
static void skipWhitespace(Str const &str, typename Str::size_type &pos) {
pos = std::min(str.find_first_not_of(" \t", pos), str.length());
}
void parseInlinePalSpec(char const * const rawArg) {
// List of #rrggbb/#rgb colors, comma-separated, palettes are separated by colons
std::string_view arg(rawArg);
using size_type = decltype(arg)::size_type;
auto parseError = [&rawArg, &arg](size_type ofs, size_type len, char const *fmt,
auto &&...args) {
(void)arg; // With NDEBUG, `arg` is otherwise not used
assert(ofs <= arg.length());
assert(len <= arg.length());
error(fmt, args...);
fprintf(stderr,
"In inline palette spec: %s\n"
" ",
rawArg);
for (auto i = ofs; i; --i) {
putc(' ', stderr);
}
for (auto i = len; i; --i) {
putc('^', stderr);
}
putc('\n', stderr);
};
options.palSpec.clear();
options.palSpec.emplace_back(); // Value-initialized, not default-init'd, so we get zeros
size_type n = 0; // Index into the argument
// TODO: store max `nbColors` ever reached, and compare against palette size later
size_t nbColors = 0; // Number of colors in the current palette
for (;;) {
++n; // Ignore the '#' (checked either by caller or previous loop iteration)
Rgba &color = options.palSpec.back()[nbColors];
auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
switch (pos - n) {
case 3:
color = Rgba(singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]),
0xFF);
break;
case 6:
color = Rgba(toHex(arg[n + 0], arg[n + 1]), toHex(arg[n + 2], arg[n + 3]),
toHex(arg[n + 4], arg[n + 5]), 0xFF);
break;
case 0:
parseError(n - 1, 1, "Missing color after '#'");
return;
default:
parseError(n, pos - n, "Unknown color specification");
return;
}
n = pos;
// Skip whitespace, if any
skipWhitespace(arg, n);
// Skip comma/semicolon, or end
if (n == arg.length()) {
break;
}
switch (arg[n]) {
case ',':
++n; // Skip it
++nbColors;
// A trailing comma may be followed by a semicolon
skipWhitespace(arg, n);
if (n == arg.length()) {
break;
} else if (arg[n] != ';' && arg[n] != ':') {
if (nbColors == 4) {
parseError(n, 1, "Each palette can only contain up to 4 colors");
return;
}
break;
}
[[fallthrough]];
case ':':
case ';':
++n;
skipWhitespace(arg, n);
nbColors = 0; // Start a new palette
// Avoid creating a spurious empty palette
if (n != arg.length()) {
options.palSpec.emplace_back();
}
break;
default:
parseError(n, 1, "Unexpected character, expected ',', ';', or end of argument");
return;
}
// Check again to allow trailing a comma/semicolon
if (n == arg.length()) {
break;
}
if (arg[n] != '#') {
parseError(n, 1, "Unexpected character, expected '#'");
return;
}
}
}
/*
* Tries to read some magic bytes from the provided `file`.
* Returns whether the magic was correctly read.
*/
template<size_t n>
static bool readMagic(std::filebuf &file, char const *magic) {
assert(strlen(magic) == n);
char magicBuf[n];
return file.sgetn(magicBuf, n) == n && memcmp(magicBuf, magic, n);
}
// Like `readMagic`, but automatically determines the size from the string literal's length.
// Don't worry if you make a mistake, an `assert`'s got your back!
#define READ_MAGIC(file, magic) \
readMagic<sizeof(magic) - 1>(file, magic) // Don't count the terminator
template<typename T, typename U>
static T readBE(U const *bytes) {
T val = 0;
for (size_t i = 0; i < sizeof(val); ++i) {
val = val << 8 | static_cast<uint8_t>(bytes[i]);
}
return val;
}
/*
* **Appends** the first line read from `file` to the end of the provided `buffer`.
*/
static void readLine(std::filebuf &file, std::string &buffer) {
// TODO: maybe this can be optimized to bulk reads?
for (;;) {
auto c = file.sbumpc();
if (c == std::filebuf::traits_type::eof()) {
return;
}
if (c == '\n') {
// Discard a trailing CRLF
if (!buffer.empty() && buffer.back() == '\r') {
buffer.pop_back();
}
return;
}
buffer.push_back(c);
}
}
// FIXME: Normally we'd use `std::from_chars`, but that's not available with GCC 7
/*
* Parses the initial part of a string_view, advancing the "read index" as it does
*/
static uint16_t parseDec(std::string const &str, std::string::size_type &n) {
uint32_t value = 0; // Use a larger type to handle overflow more easily
for (auto end = std::min(str.length(), str.find_first_not_of("0123456789", n)); n < end; ++n) {
value = std::min<uint32_t>(value * 10 + (str[n] - '0'), UINT16_MAX);
}
return value;
}
static void parsePSPFile(std::filebuf &file) {
// https://www.selapa.net/swatches/colors/fileformats.php#psp_pal
std::string line;
readLine(file, line);
if (line != "JASC-PAL") {
error("Palette file does not appear to be a PSP palette file");
return;
}
line.clear();
readLine(file, line);
if (line != "0100") {
error("Unsupported PSP palette file version \"%s\"", line.c_str());
return;
}
line.clear();
readLine(file, line);
std::string::size_type n = 0;
uint16_t nbColors = parseDec(line, n);
if (n != line.length()) {
error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str());
return;
}
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors, options.nbColorsPerPal * options.nbPalettes);
nbColors = options.nbColorsPerPal * options.nbPalettes;
}
options.palSpec.clear();
for (uint16_t i = 0; i < nbColors; ++i) {
line.clear();
readLine(file, line);
n = 0;
uint8_t r = parseDec(line, n);
skipWhitespace(line, n);
if (n == line.length()) {
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
line.c_str());
return;
}
uint8_t g = parseDec(line, n);
if (n == line.length()) {
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
line.c_str());
return;
}
skipWhitespace(line, n);
uint8_t b = parseDec(line, n);
if (n != line.length()) {
error("Failed to parse color #%" PRIu16
" (\"%s\"): trailing characters after blue component",
i + 1, line.c_str());
return;
}
if (i % options.nbColorsPerPal == 0) {
options.palSpec.emplace_back();
}
options.palSpec.back()[i % options.nbColorsPerPal] = Rgba(r, g, b, 0xFF);
}
}
void parseACTFile(std::filebuf &file) {
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626
std::array<char, 772> buf;
auto len = file.sgetn(buf.data(), buf.size());
uint16_t nbColors = 256;
if (len == 772) {
nbColors = readBE<uint16_t>(&buf[768]);
// TODO: apparently there is a "transparent color index"? What?
if (nbColors > 256 || nbColors == 0) {
error("Invalid number of colors in ACT file (%" PRIu16 ")", nbColors);
return;
}
} else if (len != 768) {
error("Invalid file size for ACT file (expected 768 or 772 bytes, got %zu", len);
return;
}
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
warning("ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors, options.nbColorsPerPal * options.nbPalettes);
nbColors = options.nbColorsPerPal * options.nbPalettes;
}
options.palSpec.clear();
options.palSpec.emplace_back();
char const *ptr = buf.data();
size_t colorIdx = 0;
for (uint16_t i = 0; i < nbColors; ++i) {
Rgba &color = options.palSpec.back()[colorIdx];
color = Rgba(ptr[0], ptr[1], ptr[2], 0xFF);
ptr += 3;
++colorIdx;
if (colorIdx == options.nbColorsPerPal) {
options.palSpec.emplace_back();
colorIdx = 0;
}
}
// Remove the spurious empty palette if there is one
if (colorIdx == 0) {
options.palSpec.pop_back();
}
}
void parseACOFile(std::filebuf &file) {
// rhttps://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819
// http://www.nomodes.com/aco.html
char buf[10];
if (file.sgetn(buf, 2) != 2) {
error("Couldn't read ACO file version");
return;
}
if (readBE<uint16_t>(buf) != 1) {
error("Palette file does not appear to be an ACO file");
return;
}
if (file.sgetn(buf, 2) != 2) {
error("Couldn't read number of colors in palette file");
return;
}
uint16_t nbColors = readBE<uint16_t>(buf);
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
warning("ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16
"; ignoring extra",
nbColors, options.nbColorsPerPal * options.nbPalettes);
nbColors = options.nbColorsPerPal * options.nbPalettes;
}
options.palSpec.clear();
for (uint16_t i = 0; i < nbColors; ++i) {
if (file.sgetn(buf, 10) != 10) {
error("Failed to read color #%" PRIu16 " from palette file", i + 1);
return;
}
if (i % options.nbColorsPerPal == 0) {
options.palSpec.emplace_back();
}
Rgba &color = options.palSpec.back()[i % options.nbColorsPerPal];
uint16_t colorType = readBE<uint16_t>(buf);
switch (colorType) {
case 0: // RGB
color = Rgba(buf[0], buf[2], buf[4], 0xFF);
break;
case 1: // HSB
error("Unsupported color type (HSB) for ACO file");
return;
case 2: // CMYK
error("Unsupported color type (CMYK) for ACO file");
return;
case 7: // Lab
error("Unsupported color type (lab) for ACO file");
return;
case 8: // Grayscale
error("Unsupported color type (grayscale) for ACO file");
return;
default:
error("Unknown color type (%" PRIu16 ") for ACO file", colorType);
return;
}
}
// TODO: maybe scan the v2 data instead (if present)
// `codecvt` can be used to convert from UTF-16 to UTF-8
}
void parseExternalPalSpec(char const *arg) {
// `fmt:path`, parse the file according to the given format
// Split both parts, error out if malformed
char const *ptr = strchr(arg, ':');
if (ptr == nullptr) {
error("External palette spec must have format `fmt:path` (missing colon)");
return;
}
char const *path = ptr + 1;
static std::array parsers{
std::tuple{"PSP", &parsePSPFile, std::ios::in },
std::tuple{"ACT", &parseACTFile, std::ios::binary},
std::tuple{"ACO", &parseACOFile, std::ios::binary},
};
auto iter = std::find_if(parsers.begin(), parsers.end(),
[&arg, &ptr](decltype(parsers)::value_type const &parser) {
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
});
if (iter == parsers.end()) {
error("Unknown external palette format \"%.*s\"",
static_cast<int>(std::min(ptr - arg, static_cast<decltype(ptr - arg)>(INT_MAX))),
arg);
return;
}
std::filebuf file;
// Some parsers read the file in text mode, others in binary mode
if (!file.open(path, std::ios::in | std::get<2>(*iter))) {
error("Failed to open palette file \"%s\"", path);
return;
}
std::get<1> (*iter)(file);
}

1115
src/gfx/process.cpp Normal file

File diff suppressed because it is too large Load Diff

83
src/gfx/proto_palette.cpp Normal file
View File

@@ -0,0 +1,83 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include "gfx/proto_palette.hpp"
#include <algorithm>
#include <array>
#include <cassert>
#include <stddef.h>
#include <stdint.h>
bool ProtoPalette::add(uint16_t color) {
size_t i = 0;
// Seek the first slot greater than our color
// (A linear search is better because we don't store the array size,
// and there are very few slots anyway)
while (_colorIndices[i] < color) {
++i;
if (i == _colorIndices.size())
return false;
}
// If we found ourselves, great!
if (_colorIndices[i] == color)
return true;
// Swap entries until the end
while (_colorIndices[i] != UINT16_MAX) {
std::swap(_colorIndices[i], color);
++i;
if (i == _colorIndices.size())
return false; // Oh well
}
// Write that last one into the new slot
_colorIndices[i] = color;
return true;
}
ProtoPalette::ComparisonResult ProtoPalette::compare(ProtoPalette const &other) const {
// This works because the sets are sorted numerically
assert(std::is_sorted(_colorIndices.begin(), _colorIndices.end()));
assert(std::is_sorted(other._colorIndices.begin(), other._colorIndices.end()));
auto ours = _colorIndices.begin(), theirs = other._colorIndices.begin();
bool weBigger = true, theyBigger = true;
while (ours != _colorIndices.end() && theirs != other._colorIndices.end()) {
if (*ours == *theirs) {
++ours;
++theirs;
} else if (*ours < *theirs) {
++ours;
theyBigger = false;
} else { // *ours > *theirs
++theirs;
weBigger = false;
}
}
weBigger &= theirs == other._colorIndices.end();
theyBigger &= ours == _colorIndices.end();
return theyBigger ? THEY_BIGGER : (weBigger ? WE_BIGGER : NEITHER);
}
size_t ProtoPalette::size() const {
return std::distance(begin(), end());
}
bool ProtoPalette::empty() const {
return _colorIndices[0] == UINT16_MAX;
}
auto ProtoPalette::begin() const -> decltype(_colorIndices)::const_iterator {
return _colorIndices.begin();
}
auto ProtoPalette::end() const -> decltype(_colorIndices)::const_iterator {
return std::find(_colorIndices.begin(), _colorIndices.end(), UINT16_MAX);
}

Some files were not shown because too many files have changed in this diff Show More