Compare commits

...

59 Commits

Author SHA1 Message Date
Antonio Niño Díaz
748943f6fc Increase version number to 0.3.7
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-05-02 20:04:00 +01:00
Antonio Niño Díaz
d945c5811c rgbasm: Check the values of operands in bit shifts
The tests are not exhaustive, there are some conditions that aren't
checked. The tests are based in the C standard rules about undefined
behaviour.

This is a compatibility break but, hopefully, all projects are using
sane values. If not, there is no guarantee that the projects will build
in any platform where RGBDS can be compiled, so it would be better to
fix them.

Even though, technically, the left shift of a negative value is always
undefined, some projects rely on its current behaviour. This is the
reason why this doesn't cause a fatal error.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-28 01:59:37 +01:00
Antonio Niño Díaz
6fe2741f2d Enable GCC options to detect undefined behaviour
GCC has an Undefined Behavior Sanitizer (ubsan), which enables run-time
checks of undefined behaviour. It has been enabled for the `develop`
build target.

A small bug detected with it has been fixed.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-28 00:57:25 +01:00
Antonio Niño Díaz
24d7cfe0f9 checkpatch: Ignore warnings about SPDX
The Linux kernel expects the SPDX license tag to be in the first line of
all source code files. The reason is that they have many different
license headers, and this simplifies the work for any tool that has to
determine the license of each file.

In this project, the license headers follow the same pattern, so it
isn't useful.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-27 23:00:29 +01:00
Antonio Niño Díaz
630933b148 rgbfix: Fix checkpatch issues
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-22 21:02:08 +01:00
Antonio Niño Díaz
e8a16c6f53 Don't generate output file if overlay isn't found
Previously, the output file was opened before trying to open the
overlay. Because of this, rgblink generated an empty output file if the
overlay couldn't be opened.

Now the overlay is opened (and checked) before the output file, so the
output file is only generated if the overlay is found.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-22 20:54:48 +01:00
Antonio Niño Díaz
2cb50730a1 Document Section ID == -1 in object files
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-22 20:21:45 +01:00
Antonio Niño Díaz
efae6c7fd2 Merge pull request #264 from inmemrgbfix
Rewrite rgbfix to perform most actions in memory.

Limit file operations to a small number that can be reasonably checked,
and do the majority of the actual work on a buffered header in memory
where operations won't fail.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-22 20:03:32 +01:00
Anthony J. Bentley
8a559beeb8 Rewrite rgbfix to perform most actions in memory.
Limit file operations to a small number that can be reasonably checked,
and do the majority of the actual work on a buffered header in memory
where operations won't fail.
2018-04-05 00:41:09 -06:00
Antonio Niño Díaz
7149fc1e39 tests: Update references
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-03 23:04:29 +01:00
Antonio Niño Díaz
2e695334c1 rgbfix: Add check to malloc()
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-03 22:41:51 +01:00
Antonio Niño Díaz
e2b4554a5c Replace tabs by spaces in fprintf()
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-03 22:41:44 +01:00
Antonio Niño Díaz
ef87dd5a6e rgbasm: Fix declaration of fatalerror()
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-03 22:41:37 +01:00
Antonio Niño Díaz
4f126b37d0 Merge pull request #261 from warnings
Enable a lot of GCC warnings and cleanup Makefile and compiler macros

Most of the warnings in the GCC documentation have been enabled. As the
build system was using `-Werror`, this made the new builds very
susceptible to any minor change in the compiler. To fix that, this
option has been removed from the default Makefile target and it is only
used when the code is compiled through `make develop`.

The file `stdnoreturn.h` has been modified to remove support for
compilers that aren't actually tested. It now has defines for
`__attribute__((unused))` and it has been renamed to `helpers.h`.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-03 19:57:04 +01:00
Antonio Niño Díaz
1b4187e51f Cleanup GCC compiler attributes
Added define 'unused_' for '__attribute__((unused))'. The oldest version
of GCC with online docs (GCC 2.95.3, released in March 16, 2001 [1])
already has support for this attribute, so it doesn't make sense to
check the version.

Renamed 'noreturn' to 'noreturn_' for consistency.

[1] https://gcc.gnu.org/onlinedocs/

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-03 19:52:50 +01:00
Antonio Niño Díaz
0daec91683 Check code style as part of the CI tests
If checkpatch.pl detects any ERROR in the new patches, the Travis CI
build will fail.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-03 00:02:17 +01:00
Antonio Niño Díaz
cbaaec98ca Simplify helpers.h
`__attribute__((noreturn))` has been supported since GCC 2.5, that was
released October 22, 1993. It doesn't make sense to check if the version
is at least that one, we are compiling for C99, that is more modern. [1]

Also, remove the MSVC check. This code is never compiled with it so
there may be problems that need to be solved to make it compile. All
releases cross-compiled from linux. If there is an actual need to
support MSVC, the compiler definitions can be added again.

Also, if the compiler is not supported, the compiler helpers default to
nothing, so the code can still compile.

[1] https://gcc.gnu.org/onlinedocs/

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-02 22:53:48 +01:00
Antonio Niño Díaz
895d1d5813 Remove check for C11 in helpers.h
The mandatory CFLAGS in the Makefile make the compiler use C99.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-02 22:53:48 +01:00
Antonio Niño Díaz
e99a651165 Create makefile target to check all warnings
Remove '-Werror' from the default make target to make it easy for
regular users of RGBDS to compile the source code. Only a few basic
warnings are left in that target.

All the warnings have been moved to a new target called 'develop'. This
target is now the one used in Travis CI to check for problems during
compilation.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-02 22:53:48 +01:00
Antonio Niño Díaz
0ae69b3114 Rename stdnoreturn.h to helpers.h
This file will contain more compiler-specific helpers.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-02 22:53:48 +01:00
Antonio Niño Díaz
95ccc48d0c Enable -Wundef
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-02 22:53:48 +01:00
Antonio Niño Díaz
29253046d5 Remove C++ check in stdnoreturn.h
This isn't a C++ project, only keep checks for C compilers.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-02 22:53:48 +01:00
Antonio Niño Díaz
340362d984 Enable a few warning flags
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-02 22:53:48 +01:00
Antonio Niño Díaz
85ece88268 Add default clauses to switch statements
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-02 22:53:43 +01:00
Antonio Niño Díaz
516e4578ea Enable more optional warnings
-Wformat-truncation is set to 1 instead of 2 because in some cases the
return value of snprintf is used to warn about truncations but GCC still
creates a warning for it, preventing the compilation from continuing.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-01 01:42:55 +01:00
Antonio Niño Díaz
9829be1045 Enable -Wextra
-Wsign-compare has been disabled because flex generates a comparison
that triggers a warning and cannot be fixed in the code.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-01 01:42:55 +01:00
Antonio Niño Díaz
b28a16c0da Enable -Wpedantic
Fix a few warnings related needed to build the source with this option.

Add new exception to .checkpatch.conf.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-04-01 00:56:00 +01:00
Antonio Niño Díaz
4d13d57491 Fix behaviour of '@'
When '@' is used as argument of any instruction (LD, JR, JP, etc), the
address it refers to is the address of the first byte of the
instruction. When '@' is used with DB/DW/DL, it refers to the same
address it is being placed at. This means that instructions need an
offset of 1 byte and others need an offset of 0 bytes.

The assembler doesn't evaluate anything related to '@' because it would
only work in sections with a fixed base address. It is left to the
linker. This means that the offset needs to be added to the RPN
expression of the patch that is saved in the linker. It isn't enough by
adding an offset to the final expresion. The following value wouldn't be
calculated correctly (even if it doesn't make sense):

    JP @ * @

The correct patch is `(@ - 1) * (@ - 1)`, not `(@ * @) - 1`.

This patch introduces an offset on 1 byte by default in every line, and
sets it to 0 only if a DB/DW/DL is detected.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-03-31 00:43:25 +01:00
Antonio Niño Díaz
be6bc7460b Don't save '@' in map and sym files
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-03-31 00:43:25 +01:00
Antonio Niño Díaz
efdd42c6a8 Simplify parsing of variable-length lists
This change removes 2 reduce/reduce conflicts in the parser while
preserving the behaviour of the rules.

Note that now each list with empty elements will only print a warning
per line.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-03-31 00:43:02 +01:00
Antonio Niño Díaz
c1a97f6541 Allow to JR to numeric constants
Previously, JR was only allowed to labels (in the same section, or
different sections). When trying to JR to an address specified as a
numeric value, rgbasm would fail to calculate the JR offset (as it
doesn't know the final address of the JR so it can't calculate the
difference).

This patch makes rgblink calculate the offset whenever there is a JR.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-03-29 23:38:54 +01:00
Antonio Niño Díaz
ea4276c7ac Increase version number to 0.3.6
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-03-20 20:11:38 +00:00
Antonio Niño Díaz
7bce97f817 Fix crash in rgbgfx with height not multiple of 8
Images are allowed to have any arbitrary height if the width is 8. If
the height is not a multiple of 8, the number of tiles calculated won't
be an exact number and it will be rounded down. This patch increases the
number of tiles allocated in this case to prevent rgbgfx from accessing
memory that hasn't been allocated.

The buffers are now initialized to 0 with calloc instead of being
created with malloc.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-03-19 21:47:19 +00:00
Antonio Niño Díaz
483a63156b Document character maps
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-03-15 21:04:43 +00:00
Anthony J. Bentley
5a4bbe4985 Add a new flag, -f, which allows independently fixing or trashing checksums. 2018-03-10 21:48:23 -07:00
Antonio Niño Díaz
f86dbafad0 Fix format in manpage
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-03-07 18:23:44 +00:00
Antonio Niño Díaz
8744d360a3 Fix format of manpage
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-27 19:42:29 +00:00
Antonio Niño Díaz
b6bd57a764 Merge pull request #237 from continue-lines
Allow to continue lines

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-26 21:55:19 +00:00
Antonio Niño Díaz
f2b55527d5 Update html manpages
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-26 21:49:19 +00:00
Antonio Niño Díaz
84a6899c6c Document line continuation syntax
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-26 21:48:28 +00:00
Antonio Niño Díaz
8cffe22295 Fix style of code sections in manpages
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-26 21:48:26 +00:00
Antonio Niño Díaz
0c85240b97 Allow line continuations in list of macro args
For example:

    PrintMacro : MACRO
        PRINTT \1
    ENDM

        PrintMacro STRCAT(\"Hello\"\,  \
                          \" world\\n\")

It is possible to have spaces after the '\' and before the newline
character. This is needed because Windows line endings "\r\n" are
converted to " \n" before the lexer has a chance to handle them.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-26 21:47:52 +00:00
Antonio Niño Díaz
58ab88da82 Allow to scape " in lists of macro args
For example:

    PrintMacro : MACRO
        PRINTT \1
    ENDM

        PrintMacro STRCAT(\"Hello\"\,  \" world\\n\")

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-26 21:47:52 +00:00
Antonio Niño Díaz
3e219dee36 Allow to continuate lines except inside macros
Lines can be continuated after a newline character ('\n'):

    DB 1, 2, 3, 4 \
       5, 6, 7, 8

This doesn't work for now in lists of arguments of macros.

It is possible to have spaces after the '\' and before the newline
character. This is needed because Windows line endings "\r\n" are
converted to " \n" before the lexer has a chance to handle them.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-26 21:47:52 +00:00
Antonio Niño Díaz
6ad5bd2325 Add flag to rgbasm to disable LD->LDH optimization
rgbasm tries to optimize any loads from/to $FF00-$FFFF and generate
LDH 2-byte opcodes instead of regular LD 3-byte opcodes. This is a bit
inconsistent as it only works for constant values. If a load is trying
to access a label in a HRAM floating section, or a section found in a
different object file, this optimization doesn't work.

This means that a simple refactor or code could allow rgbasm to perform
the optimzation or prevent it from doing so. For certain projects, like
disassemblies, this is a problem.

This patch adds flag -L to rgbasm to disable the optimization, and
doesn't change the behaviour of any other existing code.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-26 21:44:00 +00:00
Antonio Niño Díaz
2a97535e75 Add safeguards against string overflows
Use snprintf instead of other unsafe functions. That way it is possible
to limit the size of the buffer and to ensure that it never overflows.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-25 22:58:29 +00:00
Antonio Niño Díaz
0e0e12a769 Add CSS file for the html documentation
It has been obtained from here:

http://mdocml.bsd.lv/cgi-bin/cvsweb/mandoc.css

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-24 16:40:55 +00:00
Antonio Niño Díaz
3bebedf1f8 Handle newlines and comments correctly
Newlines have to be handled before comments or comments won't be able to
handle line endings that don't include at least one LF character.

Also, document an obscure comment syntax: Anything that follows a '*'
placed at the start of a line is also a comment until the end of the
line.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-23 19:24:18 +00:00
Antonio Niño Díaz
2ed937db2c Allow JR between sections
Previously, JR was only allowed if the destination label was in the same
section as the JR. This patch removes this restriction. The check to see
if the relative value overflows is now done when linking the ROM.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-23 19:21:44 +00:00
Antonio Niño Díaz
d243bd04ef Introduce command PRINTI to print integers
PRINTV prints integers in hexadecimal, PRINTI prints them in signed
decimal. For example:

    PRINTT "Error at line "
    PRINTI __LINE__
    PRINTT "\n"

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-23 19:20:52 +00:00
Antonio Niño Díaz
3623638be7 Fix HIGH() and LOW() for constants
HIGH() and LOW() only worked with labels and register pairs.

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-22 21:23:25 +00:00
Antonio Niño Díaz
8ea3669a64 Merge pull request #235 from obskyr/rgbgfx-color
Add color support to rgbgfx (again)!

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-20 19:56:39 +00:00
obskyr
8fe5293077 Allow superfluous height for 1-tile-wide images
Currently used here and there for small, icon-like tiles, it seems.

Signed-off-by: obskyr <powpowd@gmail.com>
2018-02-20 10:06:33 +01:00
obskyr
825fa915ee Fix rgbgfx's code style
Signed-off-by: obskyr <powpowd@gmail.com>
2018-02-20 10:06:00 +01:00
obskyr
885e8ea24a Add to contributor list
Signed-off-by: obskyr <powpowd@gmail.com>
2018-02-20 09:35:07 +01:00
obskyr
898f75ce57 Clarify and update rgbgfx documentation
Signed-off-by: obskyr <powpowd@gmail.com>
2018-02-20 09:35:00 +01:00
obskyr
b8af100c63 Handle grayscale images as expected
Signed-off-by: obskyr <powpowd@gmail.com>
2018-02-20 09:34:48 +01:00
obskyr
3075945367 Add color and transparency support to rgbgfx
In addition, fix various bugs.
Among them are minor memory issues and edge cases with certain inputs.

Signed-off-by: obskyr <powpowd@gmail.com>
2018-02-20 09:33:53 +01:00
Antonio Niño Díaz
d602ebfde5 Fix typo in rgblink manpage
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
2018-02-18 19:57:02 +00:00
63 changed files with 2183 additions and 790 deletions

View File

@@ -16,6 +16,9 @@
# Show file line, not input line # Show file line, not input line
--showfile --showfile
# Don't expect SPDX tag in the first line of a file
--ignore SPDX_LICENSE_TAG
# List of ignored rules # List of ignored rules
# --------------------- # ---------------------
@@ -53,6 +56,9 @@
# Prefer stdint.h types over kernel types # Prefer stdint.h types over kernel types
--ignore PREFER_KERNEL_TYPES --ignore PREFER_KERNEL_TYPES
# Don't ask to replace sscanf by kstrto
--ignore SSCANF_TO_KSTRTO
# Parentheses can make the code clearer # Parentheses can make the code clearer
--ignore UNNECESSARY_PARENTHESES --ignore UNNECESSARY_PARENTHESES

48
.travis-checkpatch.sh Executable file
View File

@@ -0,0 +1,48 @@
#!/bin/bash
echo "Checking code style..."
# Return failure as soon as a command fails to execute
set -e
# Download checkpatch.pl and related files
echo "Getting checkpatch.pl..."
mkdir checkpatchdir
wget https://raw.githubusercontent.com/torvalds/linux/master/scripts/checkpatch.pl
mv checkpatch.pl checkpatchdir/checkpatch.pl
chmod +x checkpatchdir/checkpatch.pl
touch checkpatchdir/const_structs.checkpatch
touch checkpatchdir/spelling.txt
# Run checkpatch.pl on the new commits
echo "Running checkpatch.pl..."
fname=$(mktemp)
rc=0
git remote set-branches --add origin develop
git fetch
make CHECKPATCH=checkpatchdir/checkpatch.pl checkpatch > $fname
cat $fname
if grep "ERROR" $fname; then
# At least one error found
echo "Code style errors have been found!"
rc=1
else
echo "No code style errors found, your patches are ready!"
fi
# Cleanup
rm -rf checkpatchdir
exit $rc

View File

@@ -10,5 +10,6 @@ compiler:
- clang - clang
- gcc - gcc
script: script:
- ./.travis-checkpatch.sh
- cd test - cd test
- ./run-tests.sh - ./run-tests.sh

View File

@@ -70,24 +70,29 @@ copyright and the reference to the MIT License.
3. Create a new branch to work on. You could still work on ``develop``, but it's 3. Create a new branch to work on. You could still work on ``develop``, but it's
easier that way. easier that way.
4. Sign off your commits: ``git commit -s`` 4. Compile your changes with ``make develop`` instead of just ``make``. This
target checks for additional warnings. Your patches shouldn't introduce any
new warning (but it may be possible to remove some warning checks if it makes
the code much easier).
5. Follow the Linux kernel coding style, which can be found in the file 5. Sign off your commits: ``git commit -s``
6. Follow the Linux kernel coding style, which can be found in the file
``Documentation/process/coding-style.rst`` in the Linux kernel repository. ``Documentation/process/coding-style.rst`` in the Linux kernel repository.
Note that the coding style isn't writen on stone, if there is a good reason Note that the coding style isn't writen on stone, if there is a good reason
to deviate from it, it should be fine. to deviate from it, it should be fine.
6. Download the files ``checkpatch.pl``, ``const_structs.checkpatch`` and 7. Download the files ``checkpatch.pl``, ``const_structs.checkpatch`` and
``spelling.txt`` from the folder ``scripts`` in the Linux kernel repository. ``spelling.txt`` from the folder ``scripts`` in the Linux kernel repository.
7. To use ``checkpatch.pl`` you can use ``make checkpatch``, which will check 8. To use ``checkpatch.pl`` you can use ``make checkpatch``, which will check
the coding style of all patches between the current one and the upstream the coding style of all patches between the current one and the upstream
code. By default, the Makefile expects the script (and associate files) to be code. By default, the Makefile expects the script (and associate files) to be
located in ``../linux/scripts/``, but you can place them anywhere you like as located in ``../linux/scripts/``, but you can place them anywhere you like as
long as you specify it when executing the command: long as you specify it when executing the command:
``CHECKPATCH=../path/to/folder make checkpatch``. ``CHECKPATCH=../path/to/folder make checkpatch``.
8. Create a pull request against the branch ``develop``. 9. Create a pull request against the branch ``develop``.
9. Be prepared to get some comments about your code and to modify it. Tip: Use 10. Be prepared to get some comments about your code and to modify it. Tip: Use
``git rebase -i origin/develop`` to modify chains of commits. ``git rebase -i origin/develop`` to modify chains of commits.

View File

@@ -30,6 +30,8 @@ Other contributors
- The Musl C library <http://www.musl-libc.org> - The Musl C library <http://www.musl-libc.org>
- obskyr <powpowd@gmail.com>
- The OpenBSD Project <http://www.openbsd.org> - The OpenBSD Project <http://www.openbsd.org>
- Sanqui <gsanky@gmail.com> - Sanqui <gsanky@gmail.com>

View File

@@ -26,7 +26,7 @@ PNGLDLIBS := `${PKG_CONFIG} --static --libs-only-l libpng`
VERSION_STRING := `git describe --tags --dirty --always 2>/dev/null` VERSION_STRING := `git describe --tags --dirty --always 2>/dev/null`
WARNFLAGS := -Wall -Werror WARNFLAGS := -Wall
# Overridable CFLAGS # Overridable CFLAGS
CFLAGS := -g CFLAGS := -g
@@ -160,6 +160,7 @@ install: all
# Target used to check the coding style of the whole codebase. '.y' and '.l' # Target used to check the coding style of the whole codebase. '.y' and '.l'
# files aren't checked, unfortunately... # files aren't checked, unfortunately...
checkcodebase: checkcodebase:
$Qfor file in `git ls-files | grep -E '\.c|\.h' | grep -v '\.html'`; do \ $Qfor file in `git ls-files | grep -E '\.c|\.h' | grep -v '\.html'`; do \
${CHECKPATCH} -f "$$file"; \ ${CHECKPATCH} -f "$$file"; \
@@ -169,6 +170,7 @@ checkcodebase:
# to the HEAD. Runs checkpatch once for each commit between the current HEAD and # to the HEAD. Runs checkpatch once for each commit between the current HEAD and
# the first common commit between the HEAD and origin/develop. '.y' and '.l' # the first common commit between the HEAD and origin/develop. '.y' and '.l'
# files aren't checked, unfortunately... # files aren't checked, unfortunately...
checkpatch: checkpatch:
$Qeval COMMON_COMMIT=$$(git merge-base HEAD origin/develop); \ $Qeval COMMON_COMMIT=$$(git merge-base HEAD origin/develop); \
for commit in `git rev-list $$COMMON_COMMIT..HEAD`; do \ for commit in `git rev-list $$COMMON_COMMIT..HEAD`; do \
@@ -180,7 +182,7 @@ checkpatch:
# Target for the project maintainer to easily create web manuals. # Target for the project maintainer to easily create web manuals.
# It relies on mandoc: http://mdocml.bsd.lv # It relies on mandoc: http://mdocml.bsd.lv
MANDOC := -Thtml -Ios=General -Oman=%N.%S.html -Ostyle=manual.css MANDOC := -Thtml -Ios=General -Oman=%N.%S.html -Ostyle=mandoc.css
wwwman: wwwman:
$Qmandoc ${MANDOC} src/rgbds.7 > docs/rgbds.7.html $Qmandoc ${MANDOC} src/rgbds.7 > docs/rgbds.7.html
@@ -193,6 +195,25 @@ wwwman:
$Qmandoc ${MANDOC} src/link/rgblink.5 > docs/rgblink.5.html $Qmandoc ${MANDOC} src/link/rgblink.5 > docs/rgblink.5.html
$Qmandoc ${MANDOC} src/gfx/rgbgfx.1 > docs/rgbgfx.1.html $Qmandoc ${MANDOC} src/gfx/rgbgfx.1 > docs/rgbgfx.1.html
# This target is used during development in order to prevent adding new issues
# to the source code. All warnings are treated as errors in order to block the
# compilation and make the continous integration infrastructure return failure.
develop:
$Qenv make -j WARNFLAGS="-Werror -Wall -Wextra -Wpedantic \
-Wno-sign-compare -Wchkp -Wformat=2 -Wformat-overflow=2 \
-Wformat-truncation=1 -Wformat-y2k -Wswitch-enum -Wunused \
-Wuninitialized -Wunknown-pragmas -Wstrict-overflow=5 \
-Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond \
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op \
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline \
-Wundef -Wstrict-prototypes -Wold-style-definition \
-fsanitize=shift -fsanitize=integer-divide-by-zero \
-fsanitize=unreachable -fsanitize=vla-bound \
-fsanitize=signed-integer-overflow -fsanitize=bounds \
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
-fsanitize=alignment -fsanitize=null"
# Targets for the project maintainer to easily create Windows exes. # Targets for the project maintainer to easily create Windows exes.
# This is not for Windows users! # This is not for Windows users!
# If you're building on Windows with Cygwin or Mingw, just follow the Unix # If you're building on Windows with Cygwin or Mingw, just follow the Unix
@@ -200,7 +221,7 @@ wwwman:
mingw32: mingw32:
$Qenv PKG_CONFIG_PATH=/usr/i686-w64-mingw32/sys-root/mingw/lib/pkgconfig/ \ $Qenv PKG_CONFIG_PATH=/usr/i686-w64-mingw32/sys-root/mingw/lib/pkgconfig/ \
make CC=i686-w64-mingw32-gcc YACC=bison WARNFLAGS= -j make CC=i686-w64-mingw32-gcc YACC=bison -j
$Qmv rgbasm rgbasm.exe $Qmv rgbasm rgbasm.exe
$Qmv rgblink rgblink.exe $Qmv rgblink rgblink.exe
$Qmv rgbfix rgbfix.exe $Qmv rgbfix rgbfix.exe
@@ -208,7 +229,7 @@ mingw32:
mingw64: mingw64:
$Qenv PKG_CONFIG_PATH=/usr/x86_64-w64-mingw32/sys-root/mingw/lib/pkgconfig/ \ $Qenv PKG_CONFIG_PATH=/usr/x86_64-w64-mingw32/sys-root/mingw/lib/pkgconfig/ \
make CC=x86_64-w64-mingw32-gcc YACC=bison WARNFLAGS= -j make CC=x86_64-w64-mingw32-gcc YACC=bison -j
$Qmv rgbasm rgbasm.exe $Qmv rgbasm rgbasm.exe
$Qmv rgblink rgblink.exe $Qmv rgblink rgblink.exe
$Qmv rgbfix rgbfix.exe $Qmv rgbfix rgbfix.exe

View File

@@ -8,7 +8,7 @@
td.head-vol { text-align: center; } td.head-vol { text-align: center; }
div.Pp { margin: 1ex 0ex; } div.Pp { margin: 1ex 0ex; }
</style> </style>
<link rel="stylesheet" href="manual.css" type="text/css" media="all"/> <link rel="stylesheet" href="mandoc.css" type="text/css" media="all"/>
<title>GBZ80(7)</title> <title>GBZ80(7)</title>
</head> </head>
<body> <body>
@@ -34,8 +34,12 @@ Note: All arithmetic/logic operations that use register
is assumed it's register <b class="Sy" title="Sy">A</b>. The following two is assumed it's register <b class="Sy" title="Sy">A</b>. The following two
lines have the same effect: lines have the same effect:
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">OR A,B</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<div class="D1"><code class="Li">OR B</code></div> <pre class="Li">
OR A,B
OR B
</pre>
</div>
<h1 class="Sh" title="Sh" id="LEGEND"><a class="selflink" href="#LEGEND">LEGEND</a></h1> <h1 class="Sh" title="Sh" id="LEGEND"><a class="selflink" href="#LEGEND">LEGEND</a></h1>
List of abbreviations used in this document. List of abbreviations used in this document.
<dl class="Bl-tag"> <dl class="Bl-tag">
@@ -1689,7 +1693,7 @@ Flags: See <a class="Sx" title="Sx" href="#XOR_A,r8">XOR A,r8</a>
<a class="Lk" title="Lk" href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a>.</div> <a class="Lk" title="Lk" href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a>.</div>
<table class="foot"> <table class="foot">
<tr> <tr>
<td class="foot-date">January 26, 2018</td> <td class="foot-date">February 23, 2018</td>
<td class="foot-os">RGBDS Manual</td> <td class="foot-os">RGBDS Manual</td>
</tr> </tr>
</table> </table>

View File

@@ -1,32 +1,36 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <meta charset="utf-8"/>
<title>General Information</title> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link rel="stylesheet" type="text/css" href="./style.css"> <title>General Information</title>
<link rel="stylesheet" type="text/css" href="mandoc.css">
</head> </head>
<body> <body>
<h1>RGBDS — Rednex Game Boy Development System</h1> <h1>RGBDS — Rednex Game Boy Development System</h1>
<h2>Table of Contents</h2> <h2>Table of Contents</h2>
<ol>
<li>General information
<ul>
<li><a href="rgbds.7.html">RGBDS general information</a></li>
<li><a href="rgbds.5.html">RGBDS object file format</a></li>
</ul>
<li>Language description
<ul>
<li><a href="rgbasm.5.html">RGBASM language description</a></li>
<li><a href="rgblink.5.html">RGBLINK linkerscript language description</a></li>
<li><a href="gbz80.7.html">GBZ80 CPU instruction set description</a></li>
</ul>
<li>Command line usage
<ul>
<li><a href="rgbasm.1.html">RGBASM command-line usage</a></li>
<li><a href="rgblink.1.html">RGBLINK command-line usage</a></li>
<li><a href="rgbfix.1.html">RGBFIX command-line usage</a></li>
<li><a href="rgbgfx.1.html">RGBGFX command-line usage</a></li>
</ul>
</ol>
<h2 id="GitHub Repository">GitHub Repository:</h2>
<ul> <ul>
<li><a href="rgbds.7.html">RGBDS general information</a> <li><a href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a></li>
</ul>
<ul>
<li><a href="rgbasm.5.html">RGBASM language description</a>
<li><a href="rgblink.5.html">RGBLINK linkerscript language description</a>
<li><a href="gbz80.7.html">GBZ80 CPU instruction set description</a>
</ul>
<ul>
<li><a href="rgbasm.1.html">RGBASM command-line usage</a>
<li><a href="rgblink.1.html">RGBLINK command-line usage</a>
<li><a href="rgbfix.1.html">RGBFIX command-line usage</a>
<li><a href="rgbgfx.1.html">RGBGFX command-line usage</a>
</ul>
<ul>
<li><a href="rgbds.5.html">RGBDS object file format</a>
</ul>
<h3 id="GitHub Repository">GitHub Repository:</h3>
<ul>
<li><a href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a>
</ul> </ul>
</body> </body>

203
docs/mandoc.css Executable file
View File

@@ -0,0 +1,203 @@
/* $Id: mandoc.css,v 1.22 2017/07/16 18:45:00 schwarze Exp $ */
/*
* Standard style sheet for mandoc(1) -Thtml and man.cgi(8).
*/
/* Global defaults. */
html { max-width: 100ex; }
body { font-family: Helvetica,Arial,sans-serif; }
table { margin-top: 0em;
margin-bottom: 0em; }
td { vertical-align: top; }
ul, ol, dl { margin-top: 0em;
margin-bottom: 0em; }
li, dt { margin-top: 1em; }
a.selflink { border-bottom: thin dotted;
color: inherit;
font: inherit;
text-decoration: inherit; }
* { clear: both }
/* Search form and search results. */
fieldset { border: thin solid silver;
border-radius: 1em;
text-align: center; }
input[name=expr] {
width: 25%; }
table.results { margin-top: 1em;
margin-left: 2em;
font-size: smaller; }
/* Header and footer lines. */
table.head { width: 100%;
border-bottom: 1px dotted #808080;
margin-bottom: 1em;
font-size: smaller; }
td.head-vol { text-align: center; }
td.head-rtitle {
text-align: right; }
span.Nd { }
table.foot { width: 100%;
border-top: 1px dotted #808080;
margin-top: 1em;
font-size: smaller; }
td.foot-os { text-align: right; }
/* Sections and paragraphs. */
div.manual-text {
margin-left: 5ex; }
h1.Sh { margin-top: 2ex;
margin-bottom: 1ex;
margin-left: -4ex;
font-size: 110%; }
h2.Ss { margin-top: 2ex;
margin-bottom: 1ex;
margin-left: -2ex;
font-size: 105%; }
div.Pp { margin: 1ex 0ex; }
a.Sx { }
a.Xr { }
/* Displays and lists. */
div.Bd { }
div.D1 { margin-left: 5ex; }
ul.Bl-bullet { list-style-type: disc;
padding-left: 1em; }
li.It-bullet { }
ul.Bl-dash { list-style-type: none;
padding-left: 0em; }
li.It-dash:before {
content: "\2014 "; }
ul.Bl-item { list-style-type: none;
padding-left: 0em; }
li.It-item { }
ul.Bl-compact > li {
margin-top: 0ex; }
ol.Bl-enum { padding-left: 2em; }
li.It-enum { }
ol.Bl-compact > li {
margin-top: 0ex; }
dl.Bl-diag { }
dt.It-diag { }
dd.It-diag { margin-left: 0ex; }
b.It-diag { font-style: normal; }
dl.Bl-hang { }
dt.It-hang { }
dd.It-hang { margin-left: 10.2ex; }
dl.Bl-inset { }
dt.It-inset { }
dd.It-inset { margin-left: 0ex; }
dl.Bl-ohang { }
dt.It-ohang { }
dd.It-ohang { margin-left: 0ex; }
dl.Bl-tag { margin-left: 10.2ex; }
dt.It-tag { float: left;
margin-top: 0ex;
margin-left: -10.2ex;
padding-right: 2ex;
vertical-align: top; }
dd.It-tag { clear: right;
width: 100%;
margin-top: 0ex;
margin-left: 0ex;
vertical-align: top;
overflow: auto; }
dl.Bl-compact > dt {
margin-top: 0ex; }
table.Bl-column { }
tr.It-column { }
td.It-column { margin-top: 1em; }
table.Bl-compact > tbody > tr > td {
margin-top: 0ex; }
cite.Rs { font-style: normal;
font-weight: normal; }
span.RsA { }
i.RsB { font-weight: normal; }
span.RsC { }
span.RsD { }
i.RsI { font-weight: normal; }
i.RsJ { font-weight: normal; }
span.RsN { }
span.RsO { }
span.RsP { }
span.RsQ { }
span.RsR { }
span.RsT { text-decoration: underline; }
a.RsU { }
span.RsV { }
span.eqn { }
table.tbl { }
/* Semantic markup for command line utilities. */
table.Nm { }
b.Nm { font-style: normal; }
b.Fl { font-style: normal; }
b.Cm { font-style: normal; }
var.Ar { font-style: italic;
font-weight: normal; }
span.Op { }
b.Ic { font-style: normal; }
code.Ev { font-style: normal;
font-weight: normal;
font-family: monospace; }
i.Pa { font-weight: normal; }
/* Semantic markup for function libraries. */
span.Lb { }
b.In { font-style: normal; }
a.In { }
b.Fd { font-style: normal; }
var.Ft { font-style: italic;
font-weight: normal; }
b.Fn { font-style: normal; }
var.Fa { font-style: italic;
font-weight: normal; }
var.Vt { font-style: italic;
font-weight: normal; }
var.Va { font-style: italic;
font-weight: normal; }
code.Dv { font-style: normal;
font-weight: normal;
font-family: monospace; }
code.Er { font-style: normal;
font-weight: normal;
font-family: monospace; }
/* Various semantic markup. */
span.An { }
a.Lk { }
a.Mt { }
b.Cd { font-style: normal; }
i.Ad { font-weight: normal; }
b.Ms { font-style: normal; }
span.St { }
a.Ux { }
/* Physical markup. */
.No { font-style: normal;
font-weight: normal; }
.Em { font-style: italic;
font-weight: normal; }
.Sy { font-style: normal;
font-weight: bold; }
.Li { font-style: normal;
font-weight: normal;
font-family: monospace; }

View File

@@ -8,7 +8,7 @@
td.head-vol { text-align: center; } td.head-vol { text-align: center; }
div.Pp { margin: 1ex 0ex; } div.Pp { margin: 1ex 0ex; }
</style> </style>
<link rel="stylesheet" href="manual.css" type="text/css" media="all"/> <link rel="stylesheet" href="mandoc.css" type="text/css" media="all"/>
<title>RGBASM(1)</title> <title>RGBASM(1)</title>
</head> </head>
<body> <body>
@@ -27,7 +27,7 @@
<table class="Nm"> <table class="Nm">
<tr> <tr>
<td><b class="Nm" title="Nm">rgbasm</b></td> <td><b class="Nm" title="Nm">rgbasm</b></td>
<td>[<span class="Op"><b class="Fl" title="Fl">-EhVvw</b></span>] <td>[<span class="Op"><b class="Fl" title="Fl">-EhLVvw</b></span>]
[<span class="Op"><b class="Fl" title="Fl">-b</b> [<span class="Op"><b class="Fl" title="Fl">-b</b>
<var class="Ar" title="Ar">chars</var></span>] <var class="Ar" title="Ar">chars</var></span>]
[<span class="Op"><b class="Fl" title="Fl">-D</b> [<span class="Op"><b class="Fl" title="Fl">-D</b>
@@ -89,6 +89,13 @@ The <b class="Nm" title="Nm">rgbasm</b> program creates an object file from an
<dd class="It-tag">Add an include path.</dd> <dd class="It-tag">Add an include path.</dd>
<dt class="It-tag">&#x00A0;</dt> <dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd> <dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#L"><b class="Fl" title="Fl" id="L">-L</b></a></dt>
<dd class="It-tag">Disable the optimization that turns loads of the form
<b class="Sy" title="Sy">LD [$FF00+n8],A</b> into the opcode
<b class="Sy" title="Sy">LDH [$FF00+n8],A</b> in order to have full
control of the result in the final ROM.</dd>
<dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#M"><b class="Fl" title="Fl" id="M">-M</b></a> <dt class="It-tag"><a class="selflink" href="#M"><b class="Fl" title="Fl" id="M">-M</b></a>
<var class="Ar" title="Ar">dependfile</var></dt> <var class="Ar" title="Ar">dependfile</var></dt>
<dd class="It-tag">Print <a class="Xr" title="Xr">make(1)</a> dependencies to <dd class="It-tag">Print <a class="Xr" title="Xr">make(1)</a> dependencies to
@@ -120,7 +127,11 @@ The <b class="Nm" title="Nm">rgbasm</b> program creates an object file from an
<h1 class="Sh" title="Sh" id="EXAMPLES"><a class="selflink" href="#EXAMPLES">EXAMPLES</a></h1> <h1 class="Sh" title="Sh" id="EXAMPLES"><a class="selflink" href="#EXAMPLES">EXAMPLES</a></h1>
Assembling a basic source file is simple: Assembling a basic source file is simple:
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1">$ rgbasm -o bar.o foo.asm</div> <div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
$ rgbasm -o bar.o foo.asm
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
The resulting object file is not yet a usable ROM image &#x2014; it must first The resulting object file is not yet a usable ROM image &#x2014; it must first
be run through <a class="Xr" title="Xr">rgblink(1)</a> and be run through <a class="Xr" title="Xr">rgblink(1)</a> and
@@ -138,7 +149,7 @@ The resulting object file is not yet a usable ROM image &#x2014; it must first
<a class="Lk" title="Lk" href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a>.</div> <a class="Lk" title="Lk" href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a>.</div>
<table class="foot"> <table class="foot">
<tr> <tr>
<td class="foot-date">January 26, 2018</td> <td class="foot-date">February 24, 2018</td>
<td class="foot-os">RGBDS Manual</td> <td class="foot-os">RGBDS Manual</td>
</tr> </tr>
</table> </table>

View File

@@ -8,7 +8,7 @@
td.head-vol { text-align: center; } td.head-vol { text-align: center; }
div.Pp { margin: 1ex 0ex; } div.Pp { margin: 1ex 0ex; }
</style> </style>
<link rel="stylesheet" href="manual.css" type="text/css" media="all"/> <link rel="stylesheet" href="mandoc.css" type="text/css" media="all"/>
<title>RGBASM(5)</title> <title>RGBASM(5)</title>
</head> </head>
<body> <body>
@@ -38,16 +38,51 @@ The syntax is line&#x2010;based, just as in any other assembler, meaning that
<div class="Pp"></div> <div class="Pp"></div>
Example: Example:
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">John: ld a,87 ;Weee</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
John: ld a,87 ;Weee
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
All pseudo&#x2010;ops, mnemonics and registers (reserved keywords) are All pseudo&#x2010;ops, mnemonics and registers (reserved keywords) are
case&#x2010;insensitive and all labels are case&#x2010;sensitive. case&#x2010;insensitive and all labels are case&#x2010;sensitive.
<div class="Pp"></div>
There are two syntaxes for comments. In both cases, a comment ends at the end of
the line. The most common one is: anything that follows a semicolon
&quot;;&quot; (that isn't inside a string) is a comment. There is another
format: anything that follows a &quot;*&quot; that is placed right at the
start of a line is a comment. The assembler removes all comments from the code
before doing anything else.
<div class="Pp"></div>
Sometimes lines can be too long and it may be necessary to split them. The
syntax to do so is the following one:
<div class="Pp"></div>
<div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
DB 1, 2, 3, 4 \
5, 6, 7, 8
</pre>
</div>
<div class="Pp"></div>
This works anywhere in the code except inside of strings. To split strings it is
needed to use <b class="Sy" title="Sy">STRCAT</b> like this:
<div class="Pp"></div>
<div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
DB STRCAT(&quot;Hello &quot;, \
&quot;world!&quot;)
</pre>
</div>
<h2 class="Ss" title="Ss" id="Sections"><a class="selflink" href="#Sections">Sections</a></h2> <h2 class="Ss" title="Ss" id="Sections"><a class="selflink" href="#Sections">Sections</a></h2>
Before you can start writing code, you must define a section. This tells the Before you can start writing code, you must define a section. This tells the
assembler what kind of information follows and, if it is code, where to put assembler what kind of information follows and, if it is code, where to put
it. it.
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">SECTION &quot;CoolStuff&quot;,ROMX</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
SECTION &quot;CoolStuff&quot;,ROMX
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
This switches to the section called &quot;CoolStuff&quot; (or creates it if it This switches to the section called &quot;CoolStuff&quot; (or creates it if it
doesn't already exist) and it defines it as a code section. All sections doesn't already exist) and it defines it as a code section. All sections
@@ -119,7 +154,10 @@ Possible section types are as follows:
<b class="Sy" title="Sy">LDH [$FF00+n8],A</b> and <b class="Sy" title="Sy">LDH [$FF00+n8],A</b> and
<b class="Sy" title="Sy">LDH A,[$FF00+n8]</b> syntax instead. This forces <b class="Sy" title="Sy">LDH A,[$FF00+n8]</b> syntax instead. This forces
the assembler to emit the correct instruction and the linker to check if the assembler to emit the correct instruction and the linker to check if
the value is in the correct range.</dd> the value is in the correct range. This optimization can be disabled by
passing the <b class="Fl" title="Fl">-L</b> flag to
<b class="Sy" title="Sy">rgbasm</b> as explained in
<a class="Xr" title="Xr">rgbasm(1)</a>.</dd>
</dl> </dl>
<div class="Pp"></div> <div class="Pp"></div>
A section is usually defined as a floating one, but the code can restrict where A section is usually defined as a floating one, but the code can restrict where
@@ -130,25 +168,38 @@ If a section is defined with no indications, it is a floating section. The
obligation to follow any specific rules. The following example defines a obligation to follow any specific rules. The following example defines a
section that can be placed anywhere in any ROMX bank: section that can be placed anywhere in any ROMX bank:
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">SECTION &quot;CoolStuff&quot;,ROMX</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
SECTION &quot;CoolStuff&quot;,ROMX
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
If it is needed, the following syntax can be used to fix the base address of the If it is needed, the following syntax can be used to fix the base address of the
section: section:
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">SECTION <div class="Bd" style="margin-left: 5.00ex;">
&quot;CoolStuff&quot;,ROMX[$4567]</code></div> <pre class="Li">
SECTION &quot;CoolStuff&quot;,ROMX[$4567]
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
It won't, however, fix the bank number, which is left to the linker. If you also It won't, however, fix the bank number, which is left to the linker. If you also
want to specify the bank you can do: want to specify the bank you can do:
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">SECTION <div class="Bd" style="margin-left: 5.00ex;">
&quot;CoolStuff&quot;,ROMX[$4567],BANK[3]</code></div> <pre class="Li">
SECTION &quot;CoolStuff&quot;,ROMX[$4567],BANK[3]
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
And if you only want to force the section into a certain bank, and not it's And if you only want to force the section into a certain bank, and not it's
position within the bank, that's also possible: position within the bank, that's also possible:
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">SECTION <div class="Bd" style="margin-left: 5.00ex;">
&quot;CoolStuff&quot;,ROMX,BANK[7]</code></div> <pre class="Li">
SECTION &quot;CoolStuff&quot;,ROMX,BANK[7]
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
In addition, you can specify byte alignment for a section. This ensures that the In addition, you can specify byte alignment for a section. This ensures that the
section starts at a memory address where the given number of least-significant section starts at a memory address where the given number of least-significant
@@ -158,11 +209,13 @@ In addition, you can specify byte alignment for a section. This ensures that the
needed to align the start of an array to 256 bytes to optimize the code that needed to align the start of an array to 256 bytes to optimize the code that
accesses it. accesses it.
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">SECTION &quot;OAM Data&quot;,WRAM0,ALIGN[8]; <div class="Bd" style="margin-left: 5.00ex;">
align to 256 bytes</code></div> <pre class="Li">
<div class="Pp"></div> SECTION &quot;OAM Data&quot;,WRAM0,ALIGN[8] ; align to 256 bytes
<div class="D1"><code class="Li">SECTION &quot;VRAM
Data&quot;,ROMX,BANK[2],ALIGN[4]; align to 16 bytes</code></div> SECTION &quot;VRAM Data&quot;,ROMX,BANK[2],ALIGN[4] ; align to 16 bytes
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
HINT: If you think this is a lot of typing for doing a simple HINT: If you think this is a lot of typing for doing a simple
<b class="Ic" title="Ic">ORG</b> type thing you can quite easily write an <b class="Ic" title="Ic">ORG</b> type thing you can quite easily write an
@@ -251,8 +304,12 @@ ThisWillBeExported.too::
EQUates are constant symbols. They can, for example, be used for things such EQUates are constant symbols. They can, for example, be used for things such
as bit-definitions of hardware registers. as bit-definitions of hardware registers.
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">EXIT_OK EQU $00</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<div class="D1"><code class="Li">EXIT_FAILURE EQU $01</code></div> <pre class="Li">
EXIT_OK EQU $00
EXIT_FAILURE EQU $01
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
Note that a colon (:) following the label-name is not allowed. EQUates Note that a colon (:) following the label-name is not allowed. EQUates
cannot be exported and imported. They don't change their value during the cannot be exported and imported. They don't change their value during the
@@ -276,7 +333,11 @@ COUNT SET ARRAY_SIZE+COUNT
be exported and imported. Alternatively you can use = as a synonym for be exported and imported. Alternatively you can use = as a synonym for
SET. SET.
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">COUNT = 2</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
COUNT = 2
</pre>
</div>
</dd> </dd>
<dt class="It-hang"><b class="Sy" title="Sy">RSSET</b>, <dt class="It-hang"><b class="Sy" title="Sy">RSSET</b>,
<b class="Sy" title="Sy">RSRESET</b>, <b class="Sy" title="Sy">RB</b>, <b class="Sy" title="Sy">RSRESET</b>, <b class="Sy" title="Sy">RB</b>,
@@ -369,10 +430,10 @@ str_SIZEOF = 259
<div class="Bd" style="margin-left: 5.00ex;"> <div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li"> <pre class="Li">
COUNTREG EQUS &quot;[hl+]&quot; COUNTREG EQUS &quot;[hl+]&quot;
ld a,COUNTREG ld a,COUNTREG
PLAYER_NAME EQUS &quot;\&quot;John\&quot;&quot; PLAYER_NAME EQUS &quot;\&quot;John\&quot;&quot;
db PLAYER_NAME db PLAYER_NAME
</pre> </pre>
</div> </div>
<div class="Pp"></div> <div class="Pp"></div>
@@ -381,13 +442,20 @@ db PLAYER_NAME
<div class="Pp"></div> <div class="Pp"></div>
This will be interpreted as: This will be interpreted as:
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">ld a,[hl+]</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<div class="D1"><code class="Li">db &quot;John&quot;</code></div> <pre class="Li">
ld a,[hl+]
db &quot;John&quot;
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
String-symbols can also be used to define small one-line macros: String-symbols can also be used to define small one-line macros:
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">PUSHA EQUS &quot;push af\npush bc\npush <div class="Bd" style="margin-left: 5.00ex;">
de\npush hl\n&quot;</code></div> <pre class="Li">
PUSHA EQUS &quot;push af\npush bc\npush de\npush hl\n&quot;
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
Note that a colon (:) following the label-name is not allowed. String Note that a colon (:) following the label-name is not allowed. String
equates can't be exported or imported. equates can't be exported or imported.
@@ -501,7 +569,11 @@ LoopyMacro: MACRO
address and the second being a bytecount. The macro will then reset all address and the second being a bytecount. The macro will then reset all
bytes in this range. bytes in this range.
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">LoopyMacro MyVars,54</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
LoopyMacro MyVars,54
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
Arguments are passed as string equates. There's no need to enclose them in Arguments are passed as string equates. There's no need to enclose them in
quotes. An expression will not be evaluated first but passed directly. quotes. An expression will not be evaluated first but passed directly.
@@ -516,6 +588,21 @@ LoopyMacro: MACRO
use the first 9 like this. If you want to use the rest, you need to use use the first 9 like this. If you want to use the rest, you need to use
the keyword <b class="Ic" title="Ic">SHIFT</b>. the keyword <b class="Ic" title="Ic">SHIFT</b>.
<div class="Pp"></div> <div class="Pp"></div>
Line continuations work as usual inside macros or lists of arguments of
macros. Strings, however, are a bit trickier. The following example shows
how to use strings as arguments for a macro:
<div class="Pp"></div>
<div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
PrintMacro : MACRO
PRINTT \1
ENDM
PrintMacro STRCAT(\&quot;Hello\&quot;\, \
\&quot; world\\n\&quot;)
</pre>
</div>
<div class="Pp"></div>
<b class="Ic" title="Ic">SHIFT</b> is a special command only available in <b class="Ic" title="Ic">SHIFT</b> is a special command only available in
macros. Very useful in REPT-blocks. It will &quot;shift&quot; the macros. Very useful in REPT-blocks. It will &quot;shift&quot; the
arguments by one &quot;to the left&quot;. <b class="Ic" title="Ic">\1</b> arguments by one &quot;to the left&quot;. <b class="Ic" title="Ic">\1</b>
@@ -701,8 +788,11 @@ The following symbols are defined by the assembler:
<b class="Ic" title="Ic">DB</b> defines a list of bytes that will be stored in <b class="Ic" title="Ic">DB</b> defines a list of bytes that will be stored in
the final image. Ideal for tables and text (which is not zero-terminated). the final image. Ideal for tables and text (which is not zero-terminated).
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">DB 1,2,3,4,&quot;This is a <div class="Bd" style="margin-left: 5.00ex;">
string&quot;</code></div> <pre class="Li">
DB 1,2,3,4,&quot;This is a string&quot;
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
Alternatively, you can use <b class="Ic" title="Ic">DW</b> to store a list of Alternatively, you can use <b class="Ic" title="Ic">DW</b> to store a list of
words (16-bits) or <b class="Ic" title="Ic">DL</b> to store a list of words (16-bits) or <b class="Ic" title="Ic">DL</b> to store a list of
@@ -729,8 +819,11 @@ You can also use <b class="Ic" title="Ic">DB</b>,
<b class="Ic" title="Ic">DW</b> and <b class="Ic" title="Ic">DL</b> without <b class="Ic" title="Ic">DW</b> and <b class="Ic" title="Ic">DL</b> without
any arguments instead. any arguments instead.
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">DS str_SIZEOF ;allocate str_SIZEOF <div class="Bd" style="margin-left: 5.00ex;">
bytes</code></div> <pre class="Li">
DS str_SIZEOF ;allocate str_SIZEOF bytes
</pre>
</div>
<h2 class="Ss" title="Ss" id="Including_binary_files"><a class="selflink" href="#Including_binary_files">Including <h2 class="Ss" title="Ss" id="Including_binary_files"><a class="selflink" href="#Including_binary_files">Including
binary files</a></h2> binary files</a></h2>
You probably have some graphics you'd like to include. Use You probably have some graphics you'd like to include. Use
@@ -738,17 +831,23 @@ You probably have some graphics you'd like to include. Use
the file isn't found in the current directory, the include-path list passed to the file isn't found in the current directory, the include-path list passed to
the linker on the command line will be searched. the linker on the command line will be searched.
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">INCBIN &quot;titlepic.bin&quot;</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<div class="D1"><code class="Li">INCBIN &quot;sprites/hero.bin&quot;&#x00A0;; <pre class="Li">
UNIX</code></div> INCBIN &quot;titlepic.bin&quot;
<div class="D1"><code class="Li">INCBIN &quot;sprites\\hero.bin&quot;&#x00A0;; INCBIN &quot;sprites/hero.bin&quot;&#x00A0;; UNIX
Windows</code></div> INCBIN &quot;sprites\\hero.bin&quot;&#x00A0;; Windows
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
You can also include only part of a file with You can also include only part of a file with
<b class="Ic" title="Ic">INCBIN</b>. The example below includes 256 bytes from <b class="Ic" title="Ic">INCBIN</b>. The example below includes 256 bytes from
data.bin starting from byte 78. data.bin starting from byte 78.
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">INCBIN &quot;data.bin&quot;,78,256</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
INCBIN &quot;data.bin&quot;,78,256
</pre>
</div>
<h2 class="Ss" title="Ss" id="Unions"><a class="selflink" href="#Unions">Unions</a></h2> <h2 class="Ss" title="Ss" id="Unions"><a class="selflink" href="#Unions">Unions</a></h2>
Unions allow multiple memory allocations to share the same space in memory, like Unions allow multiple memory allocations to share the same space in memory, like
unions in C. This allows you to easily reuse memory for different purposes, unions in C. This allows you to easily reuse memory for different purposes,
@@ -791,17 +890,20 @@ These three instructions type text and values to stdout. Useful for debugging
<div class="Bd" style="margin-left: 5.00ex;"> <div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li"> <pre class="Li">
PRINTT &quot;I'm the greatest programmer in the whole wide world\n&quot; PRINTT &quot;I'm the greatest programmer in the whole wide world\n&quot;
PRINTV (2+3)/5 PRINTI (2 + 3) / 5
PRINTF MUL(3.14,3987.0) PRINTV $FF00 + $F0
PRINTF MUL(3.14, 3987.0)
</pre> </pre>
</div> </div>
<dl class="Bl-inset"> <dl class="Bl-inset">
<dt class="It-inset"><a class="selflink" href="#PRINTT"><b class="Ic" title="Ic" id="PRINTT">PRINTT</b></a></dt> <dt class="It-inset"><a class="selflink" href="#PRINTT"><b class="Ic" title="Ic" id="PRINTT">PRINTT</b></a></dt>
<dd class="It-inset">prints out a string.</dd> <dd class="It-inset">prints out a string.</dd>
<dt class="It-inset"><a class="selflink" href="#PRINTV"><b class="Ic" title="Ic" id="PRINTV">PRINTV</b></a></dt> <dt class="It-inset"><a class="selflink" href="#PRINTV"><b class="Ic" title="Ic" id="PRINTV">PRINTV</b></a></dt>
<dd class="It-inset">prints out an integer value or, as in the example, the <dd class="It-inset">prints out an integer value in hexadecimal or, as in the
result of a calculation. Unsurprisingly, you can also print out a constant example, the result of a calculation. Unsurprisingly, you can also print
symbols value.</dd> out a constant symbols value.</dd>
<dt class="It-inset"><a class="selflink" href="#PRINTI"><b class="Ic" title="Ic" id="PRINTI">PRINTI</b></a></dt>
<dd class="It-inset">prints out a signed integer value.</dd>
<dt class="It-inset"><a class="selflink" href="#PRINTF"><b class="Ic" title="Ic" id="PRINTF">PRINTF</b></a></dt> <dt class="It-inset"><a class="selflink" href="#PRINTF"><b class="Ic" title="Ic" id="PRINTF">PRINTF</b></a></dt>
<dd class="It-inset">prints out a fixed point value.</dd> <dd class="It-inset">prints out a fixed point value.</dd>
</dl> </dl>
@@ -860,7 +962,11 @@ Use <b class="Ic" title="Ic">INCLUDE</b> to process another assembler-file and
<b class="Ic" title="Ic">INCLUDE</b> calls infinitely (or until you run out of <b class="Ic" title="Ic">INCLUDE</b> calls infinitely (or until you run out of
memory, whichever comes first). memory, whichever comes first).
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">INCLUDE &quot;irq.inc&quot;</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
INCLUDE &quot;irq.inc&quot;
</pre>
</div>
<h2 class="Ss" title="Ss" id="Conditional_assembling"><a class="selflink" href="#Conditional_assembling">Conditional <h2 class="Ss" title="Ss" id="Conditional_assembling"><a class="selflink" href="#Conditional_assembling">Conditional
assembling</a></h2> assembling</a></h2>
The four commands <b class="Ic" title="Ic">IF</b>, The four commands <b class="Ic" title="Ic">IF</b>,
@@ -917,7 +1023,11 @@ The last one, Gameboy graphics, is quite interesting and useful. The values are
actually pixel values and it converts the &#x201C;chunky&#x201D; data to actually pixel values and it converts the &#x201C;chunky&#x201D; data to
&#x201C;planar&#x201D; data as used in the Gameboy. &#x201C;planar&#x201D; data as used in the Gameboy.
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1"><code class="Li">DW `01012323</code></div> <div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
DW `01012323
</pre>
</div>
<div class="Pp"></div> <div class="Pp"></div>
Admittedly, an expression with just a single number is quite boring. To spice Admittedly, an expression with just a single number is quite boring. To spice
things up a bit there are a few operators you can use to perform calculations things up a bit there are a few operators you can use to perform calculations
@@ -1199,6 +1309,32 @@ Whenever the macro-language expects a string you can actually use a string
returns the new string.</td> returns the new string.</td>
</tr> </tr>
</table> </table>
<h2 class="Ss" title="Ss" id="Character_maps"><a class="selflink" href="#Character_maps">Character
maps</a></h2>
When writing text that is meant to be displayed in the Game Boy, the ASCII
characters used in the source code may not be the same ones used in the
tileset used in the ROM. For example, the tiles used for uppercase letters may
be placed starting at tile index 128, which makes it difficult to add text
strings to the ROM.
<div class="Pp"></div>
Character maps allow the code to map strings up to 16 characters long to an
abitrary 8-bit value:
<div class="Pp"></div>
<div class="Bd" style="margin-left: 5.00ex;">
<pre class="Li">
CHARMAP &quot;&lt;LF&gt;&quot;, 10
CHARMAP &quot;&amp;iacute&quot;, 20
CHARMAP &quot;A&quot;, 128
</pre>
</div>
<div class="Pp"></div>
<b class="Sy" title="Sy">Note:</b> Character maps affect all strings in the file
from the point in which they are defined. This means that any string that the
code may want to print as debug information will also be affected by it.
<div class="Pp"></div>
<b class="Sy" title="Sy">Note:</b> The output value of a mapping can be 0. If
this happens, the assembler will treat this as the end of the string and the
rest of it will be trimmed.
<h2 class="Ss" title="Ss" id="Other_functions"><a class="selflink" href="#Other_functions">Other <h2 class="Ss" title="Ss" id="Other_functions"><a class="selflink" href="#Other_functions">Other
functions</a></h2> functions</a></h2>
There are a few other functions that do various useful things: There are a few other functions that do various useful things:
@@ -1319,6 +1455,8 @@ The options that OPT can modify are currently: <b class="Sy" title="Sy">b</b>,
<dd class="It-inset"></dd> <dd class="It-inset"></dd>
<dt class="It-inset"><a class="Sx" title="Sx" href="#BANK">BANK</a></dt> <dt class="It-inset"><a class="Sx" title="Sx" href="#BANK">BANK</a></dt>
<dd class="It-inset"></dd> <dd class="It-inset"></dd>
<dt class="It-inset"><a class="Sx" title="Sx" href="#CHARMAP">CHARMAP</a></dt>
<dd class="It-inset"></dd>
<dt class="It-inset"><a class="Sx" title="Sx" href="#COS">COS</a></dt> <dt class="It-inset"><a class="Sx" title="Sx" href="#COS">COS</a></dt>
<dd class="It-inset"></dd> <dd class="It-inset"></dd>
<dt class="It-inset"><a class="Sx" title="Sx" href="#DB">DB</a></dt> <dt class="It-inset"><a class="Sx" title="Sx" href="#DB">DB</a></dt>
@@ -1377,6 +1515,8 @@ The options that OPT can modify are currently: <b class="Sy" title="Sy">b</b>,
<dd class="It-inset"></dd> <dd class="It-inset"></dd>
<dt class="It-inset"><a class="Sx" title="Sx" href="#PRINTF">PRINTF</a></dt> <dt class="It-inset"><a class="Sx" title="Sx" href="#PRINTF">PRINTF</a></dt>
<dd class="It-inset"></dd> <dd class="It-inset"></dd>
<dt class="It-inset"><a class="Sx" title="Sx" href="#PRINTI">PRINTI</a></dt>
<dd class="It-inset"></dd>
<dt class="It-inset"><a class="Sx" title="Sx" href="#PRINTT">PRINTT</a></dt> <dt class="It-inset"><a class="Sx" title="Sx" href="#PRINTT">PRINTT</a></dt>
<dd class="It-inset"></dd> <dd class="It-inset"></dd>
<dt class="It-inset"><a class="Sx" title="Sx" href="#PRINTV">PRINTV</a></dt> <dt class="It-inset"><a class="Sx" title="Sx" href="#PRINTV">PRINTV</a></dt>
@@ -1451,7 +1591,7 @@ The options that OPT can modify are currently: <b class="Sy" title="Sy">b</b>,
<a class="Lk" title="Lk" href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a>.</div> <a class="Lk" title="Lk" href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a>.</div>
<table class="foot"> <table class="foot">
<tr> <tr>
<td class="foot-date">January 27, 2018</td> <td class="foot-date">March 13, 2018</td>
<td class="foot-os">RGBDS Manual</td> <td class="foot-os">RGBDS Manual</td>
</tr> </tr>
</table> </table>

View File

@@ -8,7 +8,7 @@
td.head-vol { text-align: center; } td.head-vol { text-align: center; }
div.Pp { margin: 1ex 0ex; } div.Pp { margin: 1ex 0ex; }
</style> </style>
<link rel="stylesheet" href="manual.css" type="text/css" media="all"/> <link rel="stylesheet" href="mandoc.css" type="text/css" media="all"/>
<title>RGBDS(5)</title> <title>RGBDS(5)</title>
</head> </head>
<body> <body>
@@ -65,7 +65,9 @@ REPT NumberOfSymbols ; Number of symbols defined in this object file.
LONG LineNum ; Line number in the file where the symbol is defined. LONG LineNum ; Line number in the file where the symbol is defined.
LONG SectionID ; The section number (of this object file) in which LONG SectionID ; The section number (of this object file) in which
; this symbol is defined. ; this symbol is defined. If it doesn't belong to any
; specific section (like a constant), this field has
; the value -1.
LONG Value ; The symbols value. It's the offset into that LONG Value ; The symbols value. It's the offset into that
; symbol's section. ; symbol's section.
@@ -121,6 +123,7 @@ REPT NumberOfSections
BYTE Type ; 0 = BYTE patch. BYTE Type ; 0 = BYTE patch.
; 1 = little endian WORD patch. ; 1 = little endian WORD patch.
; 2 = little endian LONG patch. ; 2 = little endian LONG patch.
; 3 = JR offset value BYTE patch.
LONG RPNSize ; Size of the buffer with the RPN. LONG RPNSize ; Size of the buffer with the RPN.
; expression. ; expression.

View File

@@ -8,7 +8,7 @@
td.head-vol { text-align: center; } td.head-vol { text-align: center; }
div.Pp { margin: 1ex 0ex; } div.Pp { margin: 1ex 0ex; }
</style> </style>
<link rel="stylesheet" href="manual.css" type="text/css" media="all"/> <link rel="stylesheet" href="mandoc.css" type="text/css" media="all"/>
<title>RGBDS(7)</title> <title>RGBDS(7)</title>
</head> </head>
<body> <body>
@@ -26,9 +26,13 @@
<h1 class="Sh" title="Sh" id="EXAMPLES"><a class="selflink" href="#EXAMPLES">EXAMPLES</a></h1> <h1 class="Sh" title="Sh" id="EXAMPLES"><a class="selflink" href="#EXAMPLES">EXAMPLES</a></h1>
To get a working ROM image from a single assembly source file: To get a working ROM image from a single assembly source file:
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1">$ rgbasm -o bar.o foo.asm</div> <div class="Bd" style="margin-left: 5.00ex;">
<div class="D1">$ rgblink -o baz.gb bar.o</div> <pre class="Li">
<div class="D1">$ rgbfix -v -p 0 baz.gb</div> $ rgbasm -o bar.o foo.asm
$ rgblink -o baz.gb bar.o
$ rgbfix -v -p 0 baz.gb
</pre>
</div>
<h1 class="Sh" title="Sh" id="SEE_ALSO"><a class="selflink" href="#SEE_ALSO">SEE <h1 class="Sh" title="Sh" id="SEE_ALSO"><a class="selflink" href="#SEE_ALSO">SEE
ALSO</a></h1> ALSO</a></h1>
<a class="Xr" title="Xr">rgbasm(1)</a>, <a class="Xr" title="Xr">rgbfix(1)</a>, <a class="Xr" title="Xr">rgbasm(1)</a>, <a class="Xr" title="Xr">rgbfix(1)</a>,
@@ -52,13 +56,14 @@ To get a working ROM image from a single assembly source file:
<dt class="It-ohang"></dt> <dt class="It-ohang"></dt>
<dd class="It-ohang">2017, Bentley's repository is moved to a neutral name. It <dd class="It-ohang">2017, Bentley's repository is moved to a neutral name. It
is now maintained by a number of contributors at is now maintained by a number of contributors at
<a class="Lk" title="Lk" href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a>. <a class="Lk" title="Lk" href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a>.</dd>
2018, codebase relicensed under the MIT license.</dd> <dt class="It-ohang"></dt>
<dd class="It-ohang">2018, codebase relicensed under the MIT license.</dd>
</dl> </dl>
</div> </div>
<table class="foot"> <table class="foot">
<tr> <tr>
<td class="foot-date">January 26, 2018</td> <td class="foot-date">March 7, 2018</td>
<td class="foot-os">RGBDS Manual</td> <td class="foot-os">RGBDS Manual</td>
</tr> </tr>
</table> </table>

View File

@@ -8,7 +8,7 @@
td.head-vol { text-align: center; } td.head-vol { text-align: center; }
div.Pp { margin: 1ex 0ex; } div.Pp { margin: 1ex 0ex; }
</style> </style>
<link rel="stylesheet" href="manual.css" type="text/css" media="all"/> <link rel="stylesheet" href="mandoc.css" type="text/css" media="all"/>
<title>RGBFIX(1)</title> <title>RGBFIX(1)</title>
</head> </head>
<body> <body>
@@ -28,6 +28,8 @@
<tr> <tr>
<td><b class="Nm" title="Nm">rgbfix</b></td> <td><b class="Nm" title="Nm">rgbfix</b></td>
<td>[<span class="Op"><b class="Fl" title="Fl">-CcjsVv</b></span>] <td>[<span class="Op"><b class="Fl" title="Fl">-CcjsVv</b></span>]
[<span class="Op"><b class="Fl" title="Fl">-f</b>
<var class="Ar" title="Ar">fix_spec</var></span>]
[<span class="Op"><b class="Fl" title="Fl">-i</b> [<span class="Op"><b class="Fl" title="Fl">-i</b>
<var class="Ar" title="Ar">game_id</var></span>] <var class="Ar" title="Ar">game_id</var></span>]
[<span class="Op"><b class="Fl" title="Fl">-k</b> [<span class="Op"><b class="Fl" title="Fl">-k</b>
@@ -67,6 +69,32 @@ The <b class="Nm" title="Nm">rgbfix</b> program changes headers of Game Boy ROM
<b class="Fl" title="Fl">-C</b> takes precedence.</dd> <b class="Fl" title="Fl">-C</b> takes precedence.</dd>
<dt class="It-tag">&#x00A0;</dt> <dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd> <dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#f"><b class="Fl" title="Fl" id="f">-f</b></a>
<var class="Ar" title="Ar">fix_spec</var></dt>
<dd class="It-tag">Fix certain header values that the Game Boy checks for
correctness. Alternatively, intentionally trash these values by writing
their binary inverse instead. <var class="Ar" title="Ar">fix_spec</var> is
a string containing any combination of the following characters:
<div class="Pp"></div>
<dl class="Bl-tag Bl-compact" style="margin-left: 5.40ex;">
<dt class="It-tag" style="margin-left: -5.40ex;"><a class="selflink" href="#l"><b class="Cm" title="Cm" id="l">l</b></a></dt>
<dd class="It-tag">Fix the Nintendo logo
(<i class="Ad">0x104</i>&#x2013;<i class="Ad">0x133</i>).</dd>
<dt class="It-tag" style="margin-left: -5.40ex;"><a class="selflink" href="#L"><b class="Cm" title="Cm" id="L">L</b></a></dt>
<dd class="It-tag">Trash the Nintendo logo.</dd>
<dt class="It-tag" style="margin-left: -5.40ex;"><a class="selflink" href="#h"><b class="Cm" title="Cm" id="h">h</b></a></dt>
<dd class="It-tag">Fix the header checksum (<i class="Ad">0x14D</i>).</dd>
<dt class="It-tag" style="margin-left: -5.40ex;"><a class="selflink" href="#H"><b class="Cm" title="Cm" id="H">H</b></a></dt>
<dd class="It-tag">Trash the header checksum.</dd>
<dt class="It-tag" style="margin-left: -5.40ex;"><a class="selflink" href="#g"><b class="Cm" title="Cm" id="g">g</b></a></dt>
<dd class="It-tag">Fix the global checksum
(<i class="Ad">0x14E</i>&#x2013;<i class="Ad">0x14F</i>).</dd>
<dt class="It-tag" style="margin-left: -5.40ex;"><a class="selflink" href="#G"><b class="Cm" title="Cm" id="G">G</b></a></dt>
<dd class="It-tag">Trash the global checksum.</dd>
</dl>
</dd>
<dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#i"><b class="Fl" title="Fl" id="i">-i</b></a> <dt class="It-tag"><a class="selflink" href="#i"><b class="Fl" title="Fl" id="i">-i</b></a>
<var class="Ar" title="Ar">game_id</var></dt> <var class="Ar" title="Ar">game_id</var></dt>
<dd class="It-tag">Set the game ID string <dd class="It-tag">Set the game ID string
@@ -141,10 +169,8 @@ The <b class="Nm" title="Nm">rgbfix</b> program changes headers of Game Boy ROM
<dt class="It-tag">&#x00A0;</dt> <dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd> <dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#v"><b class="Fl" title="Fl" id="v">-v</b></a></dt> <dt class="It-tag"><a class="selflink" href="#v"><b class="Fl" title="Fl" id="v">-v</b></a></dt>
<dd class="It-tag">Validate the header and fix checksums: the Nintendo <dd class="It-tag">Equivalent to <b class="Fl" title="Fl">-f</b>
character area (<i class="Ad">0x104</i>&#x2013;<i class="Ad">0x133</i>), <b class="Cm" title="Cm">lhg</b>.</dd>
the header checksum (<i class="Ad">0x14D</i>), and the global checksum
(<i class="Ad">0x14E</i>&#x2013;<i class="Ad">0x14F</i>).</dd>
</dl> </dl>
<h1 class="Sh" title="Sh" id="EXAMPLES"><a class="selflink" href="#EXAMPLES">EXAMPLES</a></h1> <h1 class="Sh" title="Sh" id="EXAMPLES"><a class="selflink" href="#EXAMPLES">EXAMPLES</a></h1>
Most values in the ROM header are only cosmetic. The bare minimum requirements Most values in the ROM header are only cosmetic. The bare minimum requirements
@@ -179,7 +205,7 @@ The following will duplicate the header (sans global checksum) of the game
<a class="Lk" title="Lk" href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a>.</div> <a class="Lk" title="Lk" href="https://github.com/rednex/rgbds">https://github.com/rednex/rgbds</a>.</div>
<table class="foot"> <table class="foot">
<tr> <tr>
<td class="foot-date">January 26, 2018</td> <td class="foot-date">March 11, 2018</td>
<td class="foot-os">RGBDS Manual</td> <td class="foot-os">RGBDS Manual</td>
</tr> </tr>
</table> </table>

View File

@@ -8,7 +8,7 @@
td.head-vol { text-align: center; } td.head-vol { text-align: center; }
div.Pp { margin: 1ex 0ex; } div.Pp { margin: 1ex 0ex; }
</style> </style>
<link rel="stylesheet" href="manual.css" type="text/css" media="all"/> <link rel="stylesheet" href="mandoc.css" type="text/css" media="all"/>
<title>RGBGFX(1)</title> <title>RGBGFX(1)</title>
</head> </head>
<body> <body>
@@ -43,7 +43,28 @@
</table> </table>
<h1 class="Sh" title="Sh" id="DESCRIPTION"><a class="selflink" href="#DESCRIPTION">DESCRIPTION</a></h1> <h1 class="Sh" title="Sh" id="DESCRIPTION"><a class="selflink" href="#DESCRIPTION">DESCRIPTION</a></h1>
The <b class="Nm" title="Nm">rgbgfx</b> program converts PNG images into the The <b class="Nm" title="Nm">rgbgfx</b> program converts PNG images into the
Nintendo Game Boy's planar tile format. The arguments are as follows: Nintendo Game Boy's planar tile format.
<div style="height: 1.00em;">&#x00A0;</div>
The resulting colors and their palette indices are determined differently
depending on the input PNG file:
<ul class="Bl-dash">
<li class="It-dash">If the file has an embedded palette, that palette's color
and order are used.</li>
<li class="It-dash">If not, and the image only contains shades of gray, rgbgfx
maps them to the indices appropriate for each shade. Any undetermined
indices are set to respective default shades of gray. For example: if the
bit depth is 2 and the image contains light gray and black, they become
the second and fourth colors - and the first and third colors get set to
default white and dark gray. If the image has multiple shades that map to
the same index, the palette is instead determined as if the image had
color.</li>
<li class="It-dash">If the image has color (or the grayscale method failed),
the colors are sorted from lightest to darkest.</li>
</ul>
<div style="height: 1.00em;">&#x00A0;</div>
The input image may not contain more colors than the selected bit depth allows.
Transparent pixels are set to palette index 0.
<h1 class="Sh" title="Sh" id="ARGUMENTS"><a class="selflink" href="#ARGUMENTS">ARGUMENTS</a></h1>
<dl class="Bl-tag"> <dl class="Bl-tag">
<dt class="It-tag">&#x00A0;</dt> <dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd> <dd class="It-tag">&#x00A0;</dd>
@@ -58,14 +79,14 @@ The <b class="Nm" title="Nm">rgbgfx</b> program converts PNG images into the
<dd class="It-tag">&#x00A0;</dd> <dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#F"><b class="Fl" title="Fl" id="F">-F</b></a></dt> <dt class="It-tag"><a class="selflink" href="#F"><b class="Fl" title="Fl" id="F">-F</b></a></dt>
<dd class="It-tag">Same as <b class="Fl" title="Fl">-f</b>, but additionally, <dd class="It-tag">Same as <b class="Fl" title="Fl">-f</b>, but additionally,
the input PNG file is fixed to have its parameters match the command the supplied command line parameters are saved within the PNG and will be
line's parameters.</dd> loaded and automatically used next time.</dd>
<dt class="It-tag">&#x00A0;</dt> <dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd> <dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#d"><b class="Fl" title="Fl" id="d">-d</b></a> <dt class="It-tag"><a class="selflink" href="#d"><b class="Fl" title="Fl" id="d">-d</b></a>
<var class="Ar" title="Ar">depth</var></dt> <var class="Ar" title="Ar">depth</var></dt>
<dd class="It-tag">The bitdepth of the output image (either 1 or 2). By <dd class="It-tag">The bit depth of the output image (either 1 or 2). By
default, the bitdepth is 2 (two bits per pixel).</dd> default, the bit depth is 2 (two bits per pixel).</dd>
<dt class="It-tag">&#x00A0;</dt> <dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd> <dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#h"><b class="Fl" title="Fl" id="h">-h</b></a></dt> <dt class="It-tag"><a class="selflink" href="#h"><b class="Fl" title="Fl" id="h">-h</b></a></dt>
@@ -79,15 +100,16 @@ The <b class="Nm" title="Nm">rgbgfx</b> program converts PNG images into the
<dd class="It-tag">&#x00A0;</dd> <dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#p"><b class="Fl" title="Fl" id="p">-p</b></a> <dt class="It-tag"><a class="selflink" href="#p"><b class="Fl" title="Fl" id="p">-p</b></a>
<var class="Ar" title="Ar">palfile</var></dt> <var class="Ar" title="Ar">palfile</var></dt>
<dd class="It-tag">Raw bytes (8 bytes for two bits per pixel, 4 bytes for one <dd class="It-tag">Output the image's palette in standard GBC palette format -
bit per pixel) containing the RGB15 values in the little-endian byte order bytes (8 bytes for two bits per pixel, 4 bytes for one bit per pixel)
and then ordered from lightest to darkest.</dd> containing the RGB15 values in little-endian byte order. If the palette
contains too few colors, the remaining entries are set to black.</dd>
<dt class="It-tag">&#x00A0;</dt> <dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd> <dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#P"><b class="Fl" title="Fl" id="P">-P</b></a></dt> <dt class="It-tag"><a class="selflink" href="#P"><b class="Fl" title="Fl" id="P">-P</b></a></dt>
<dd class="It-tag">Same as <b class="Fl" title="Fl">-p</b>, but the pallete <dd class="It-tag">Same as <b class="Fl" title="Fl">-p</b>, but the palette
file output name is made by taking the input filename, removing the file file output name is made by taking the input PNG file's filename, removing
extension, and appending <i class="Pa" title="Pa">.pal</i>.</dd> the file extension, and appending <i class="Pa" title="Pa">.pal</i>.</dd>
<dt class="It-tag">&#x00A0;</dt> <dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd> <dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#t"><b class="Fl" title="Fl" id="t">-t</b></a> <dt class="It-tag"><a class="selflink" href="#t"><b class="Fl" title="Fl" id="t">-t</b></a>
@@ -120,7 +142,7 @@ The <b class="Nm" title="Nm">rgbgfx</b> program converts PNG images into the
<dd class="It-tag">Trim the end of the output file by this many tiles.</dd> <dd class="It-tag">Trim the end of the output file by this many tiles.</dd>
</dl> </dl>
<h1 class="Sh" title="Sh" id="EXAMPLES"><a class="selflink" href="#EXAMPLES">EXAMPLES</a></h1> <h1 class="Sh" title="Sh" id="EXAMPLES"><a class="selflink" href="#EXAMPLES">EXAMPLES</a></h1>
The following will take a PNG file with a bitdepth of 1, 2, or 8, and output The following will take a PNG file with a bit depth of 1, 2, or 8, and output
planar 2bpp data: planar 2bpp data:
<div class="Pp"></div> <div class="Pp"></div>
<div class="D1">$ rgbgfx -o out.2bpp in.png</div> <div class="D1">$ rgbgfx -o out.2bpp in.png</div>

View File

@@ -8,7 +8,7 @@
td.head-vol { text-align: center; } td.head-vol { text-align: center; }
div.Pp { margin: 1ex 0ex; } div.Pp { margin: 1ex 0ex; }
</style> </style>
<link rel="stylesheet" href="manual.css" type="text/css" media="all"/> <link rel="stylesheet" href="mandoc.css" type="text/css" media="all"/>
<title>RGBLINK(1)</title> <title>RGBLINK(1)</title>
</head> </head>
<body> <body>
@@ -82,7 +82,7 @@ The arguments are as follows:
<var class="Ar" title="Ar">overlayfile</var></dt> <var class="Ar" title="Ar">overlayfile</var></dt>
<dd class="It-tag">The ROM image to overlay sections over. When an overlay ROM <dd class="It-tag">The ROM image to overlay sections over. When an overlay ROM
is provided, all sections must be fixed. This may be used to patch an is provided, all sections must be fixed. This may be used to patch an
existing binray.</dd> existing binary.</dd>
<dt class="It-tag">&#x00A0;</dt> <dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd> <dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#o"><b class="Fl" title="Fl" id="o">-o</b></a> <dt class="It-tag"><a class="selflink" href="#o"><b class="Fl" title="Fl" id="o">-o</b></a>

View File

@@ -8,7 +8,7 @@
td.head-vol { text-align: center; } td.head-vol { text-align: center; }
div.Pp { margin: 1ex 0ex; } div.Pp { margin: 1ex 0ex; }
</style> </style>
<link rel="stylesheet" href="manual.css" type="text/css" media="all"/> <link rel="stylesheet" href="mandoc.css" type="text/css" media="all"/>
<title>RGBLINK(5)</title> <title>RGBLINK(5)</title>
</head> </head>
<body> <body>

View File

@@ -12,15 +12,16 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "extern/stdnoreturn.h" #include "helpers.h"
struct sOptions { struct sOptions {
char gbgfx[4];
char binary[2]; char binary[2];
int32_t fillchar; char gbgfx[4];
bool verbose;
bool haltnop;
bool exportall; bool exportall;
int32_t fillchar;
bool haltnop;
bool optimizeloads;
bool verbose;
bool warnings; /* True to enable warnings, false to disable them. */ bool warnings; /* True to enable warnings, false to disable them. */
}; };
@@ -45,7 +46,7 @@ void opt_Parse(char *s);
* It is also used when the assembler goes into an invalid state (for example, * It is also used when the assembler goes into an invalid state (for example,
* when it fails to allocate memory). * when it fails to allocate memory).
*/ */
noreturn void fatalerror(const char *fmt, ...); noreturn_ void fatalerror(const char *fmt, ...);
/* /*
* Used for errors that make it impossible to assemble correctly, but don't * Used for errors that make it impossible to assemble correctly, but don't

10
include/extern/err.h vendored
View File

@@ -17,7 +17,7 @@
#include <stdarg.h> #include <stdarg.h>
#include "extern/stdnoreturn.h" #include "helpers.h"
#define warn rgbds_warn #define warn rgbds_warn
#define vwarn rgbds_vwarn #define vwarn rgbds_vwarn
@@ -34,10 +34,10 @@ void vwarn(const char *fmt, va_list ap);
void warnx(const char *fmt, ...); void warnx(const char *fmt, ...);
void vwarnx(const char *fmt, va_list ap); void vwarnx(const char *fmt, va_list ap);
noreturn void err(int status, const char *fmt, ...); noreturn_ void err(int status, const char *fmt, ...);
noreturn void verr(int status, const char *fmt, va_list ap); noreturn_ void verr(int status, const char *fmt, va_list ap);
noreturn void errx(int status, const char *fmt, ...); noreturn_ void errx(int status, const char *fmt, ...);
noreturn void verrx(int status, const char *fmt, va_list ap); noreturn_ void verrx(int status, const char *fmt, va_list ap);
#endif /* ERR_IN_LIBC */ #endif /* ERR_IN_LIBC */

View File

@@ -1,29 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2014-2018, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef EXTERN_STDNORETURN_H
#define EXTERN_STDNORETURN_H
#if __STDC_VERSION__ >= 201112L
/* C11 or newer */
#define noreturn _Noreturn
#elif __cplusplus >= 201103L
/* C++11 or newer */
#define noreturn [[noreturn]]
#elif __GNUC__ > 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ >= 5))
/* GCC 2.5 or newer */
#define noreturn __attribute__ ((noreturn))
#elif _MSC_VER >= 1310
/* MS Visual Studio 2003/.NET Framework 1.1 or newer */
#define noreturn _declspec(noreturn)
#else
/* Unsupported, but no need to throw a fit */
#define noreturn
#endif
#endif /* EXTERN_STDNORETURN_H */

View File

@@ -12,14 +12,15 @@
#include <stdint.h> #include <stdint.h>
#include "gfx/main.h" #include "gfx/main.h"
void png_to_gb(const struct PNGImage png, struct GBImage *gb); void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb);
void output_file(const struct Options opts, const 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 get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
int tile_size); int tile_size);
void create_tilemap(const struct Options opts, struct GBImage *gb, void create_tilemap(const struct Options *opts, struct GBImage *gb,
struct Tilemap *tilemap); struct Tilemap *tilemap);
void output_tilemap_file(const struct Options opts, void output_tilemap_file(const struct Options *opts,
const struct Tilemap tilemap); const struct Tilemap *tilemap);
void output_palette_file(const struct Options opts, const struct PNGImage png); void output_palette_file(const struct Options *opts,
const struct RawIndexedImage *raw_image);
#endif #endif

View File

@@ -31,6 +31,21 @@ struct Options {
char *infile; char *infile;
}; };
struct RGBColor {
uint8_t red;
uint8_t green;
uint8_t blue;
};
struct ImageOptions {
bool horizontal;
int trim;
char *mapfile;
bool mapout;
char *palfile;
bool palout;
};
struct PNGImage { struct PNGImage {
png_struct *png; png_struct *png;
png_info *info; png_info *info;
@@ -39,12 +54,14 @@ struct PNGImage {
int height; int height;
png_byte depth; png_byte depth;
png_byte type; png_byte type;
bool horizontal; };
int trim;
char *mapfile; struct RawIndexedImage {
bool mapout; uint8_t **data;
char *palfile; struct RGBColor *palette;
bool palout; int num_colors;
int width;
int height;
}; };
struct GBImage { struct GBImage {

View File

@@ -11,10 +11,11 @@
#include "gfx/main.h" #include "gfx/main.h"
void input_png_file(const struct Options opts, struct PNGImage *img); struct RawIndexedImage *input_png_file(const struct Options *opts,
void get_text(struct PNGImage *png); struct ImageOptions *png_options);
void set_text(const struct PNGImage *png); void output_png_file(const struct Options *opts,
void output_png_file(const struct Options opts, const struct PNGImage *png); const struct ImageOptions *png_options,
void free_png_data(const struct PNGImage *png); const struct RawIndexedImage *raw_image);
void destroy_raw_image(struct RawIndexedImage **raw_image_ptr_ptr);
#endif /* RGBDS_GFX_PNG_H */ #endif /* RGBDS_GFX_PNG_H */

22
include/helpers.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2014-2018, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef HELPERS_H
#define HELPERS_H
#ifdef __GNUC__
/* GCC or compatible */
#define noreturn_ __attribute__ ((noreturn))
#define unused_ __attribute__ ((unused))
#else
/* Unsupported, but no need to throw a fit */
#define noreturn_
#define unused_
#endif
#endif /* HELPERS_H */

View File

@@ -11,9 +11,9 @@
#include <stdint.h> #include <stdint.h>
#include "extern/stdnoreturn.h" #include "helpers.h"
noreturn void script_fatalerror(const char *fmt, ...); noreturn_ void script_fatalerror(const char *fmt, ...);
void script_Parse(const char *path); void script_Parse(const char *path);

View File

@@ -66,7 +66,8 @@ enum eSymbolType {
enum ePatchType { enum ePatchType {
PATCH_BYTE = 0x00, PATCH_BYTE = 0x00,
PATCH_WORD_L = 0x01, PATCH_WORD_L = 0x01,
PATCH_LONG_L = 0x02 PATCH_LONG_L = 0x02,
PATCH_BYTE_JR = 0x03
}; };
#endif /* RGBDS_LINKDEFS_H */ #endif /* RGBDS_LINKDEFS_H */

View File

@@ -11,7 +11,7 @@
#define PACKAGE_VERSION_MAJOR (0) #define PACKAGE_VERSION_MAJOR (0)
#define PACKAGE_VERSION_MINOR (3) #define PACKAGE_VERSION_MINOR (3)
#define PACKAGE_VERSION_PATCH (5) #define PACKAGE_VERSION_PATCH (7)
const char *get_package_version_string(void); const char *get_package_version_string(void);

View File

@@ -28,8 +28,10 @@
#include "common.h" #include "common.h"
#include "linkdefs.h" #include "linkdefs.h"
uint32_t nListCountEmpty;
char *tzNewMacro; char *tzNewMacro;
uint32_t ulNewMacroSize; uint32_t ulNewMacroSize;
int32_t nPCOffset;
static void bankrangecheck(char *name, uint32_t secttype, int32_t org, static void bankrangecheck(char *name, uint32_t secttype, int32_t org,
int32_t bank) int32_t bank)
@@ -339,8 +341,9 @@ static void if_skip_to_else(void)
break; break;
case '\"': case '\"':
src++; src += 2;
inString = false; inString = false;
break;
default: default:
src++; src++;
@@ -455,7 +458,6 @@ static void updateUnion(void)
%type <nConstValue> const_3bit %type <nConstValue> const_3bit
%type <sVal> const_8bit %type <sVal> const_8bit
%type <sVal> const_16bit %type <sVal> const_16bit
%type <sVal> const_PCrel
%type <nConstValue> sectiontype %type <nConstValue> sectiontype
%type <tzString> string %type <tzString> string
@@ -504,7 +506,7 @@ static void updateUnion(void)
%token <tzSym> T_POP_SET %token <tzSym> T_POP_SET
%token <tzSym> T_POP_EQUS %token <tzSym> T_POP_EQUS
%token T_POP_INCLUDE T_POP_PRINTF T_POP_PRINTT T_POP_PRINTV %token T_POP_INCLUDE T_POP_PRINTF T_POP_PRINTT T_POP_PRINTV T_POP_PRINTI
%token T_POP_IF T_POP_ELIF T_POP_ELSE T_POP_ENDC %token T_POP_IF T_POP_ELIF T_POP_ELSE T_POP_ENDC
%token T_POP_IMPORT T_POP_EXPORT T_POP_GLOBAL %token T_POP_IMPORT T_POP_EXPORT T_POP_GLOBAL
%token T_POP_DB T_POP_DS T_POP_DW T_POP_DL %token T_POP_DB T_POP_DS T_POP_DW T_POP_DL
@@ -580,10 +582,13 @@ asmfile : lines;
/* Note: The lexer adds '\n' at the end of the input */ /* Note: The lexer adds '\n' at the end of the input */
lines : /* empty */ lines : /* empty */
| lines line '\n' { | lines {
nLineNo += 1; nListCountEmpty = 0;
nTotalLines += 1; nPCOffset = 1;
} } line '\n' {
nLineNo += 1;
nTotalLines += 1;
}
; ;
line : label line : label
@@ -648,6 +653,7 @@ simple_pseudoop : include
| printf | printf
| printt | printt
| printv | printv
| printi
| if | if
| elif | elif
| else | else
@@ -655,9 +661,9 @@ simple_pseudoop : include
| import | import
| export | export
| global | global
| db | { nPCOffset = 0; } db
| dw | { nPCOffset = 0; } dw
| dl | { nPCOffset = 0; } dl
| ds | ds
| section | section
| rsreset | rsreset
@@ -802,16 +808,28 @@ ds : T_POP_DS uconst
} }
; ;
db : T_POP_DB constlist_8bit_entry comma constlist_8bit db : T_POP_DB constlist_8bit_entry comma constlist_8bit {
| T_POP_DB constlist_8bit_entry_single if ((nPass == 1) && (nListCountEmpty > 0)) {
warning("Empty entry in list of 8-bit elements (treated as 0).");
}
}
| T_POP_DB constlist_8bit_entry
; ;
dw : T_POP_DW constlist_16bit_entry comma constlist_16bit dw : T_POP_DW constlist_16bit_entry comma constlist_16bit {
| T_POP_DW constlist_16bit_entry_single if ((nPass == 1) && (nListCountEmpty > 0)) {
warning("Empty entry in list of 16-bit elements (treated as 0).");
}
}
| T_POP_DW constlist_16bit_entry
; ;
dl : T_POP_DL constlist_32bit_entry comma constlist_32bit dl : T_POP_DL constlist_32bit_entry comma constlist_32bit {
| T_POP_DL constlist_32bit_entry_single if ((nPass == 1) && (nListCountEmpty > 0)) {
warning("Empty entry in list of 32-bit elements (treated as 0).");
}
}
| T_POP_DL constlist_32bit_entry
; ;
purge : T_POP_PURGE { purge : T_POP_PURGE {
@@ -933,6 +951,13 @@ printv : T_POP_PRINTV const
} }
; ;
printi : T_POP_PRINTI const
{
if (nPass == 1)
printf("%d", $2);
}
;
printf : T_POP_PRINTF const printf : T_POP_PRINTF const
{ {
if (nPass == 1) if (nPass == 1)
@@ -1021,26 +1046,7 @@ constlist_8bit : constlist_8bit_entry
constlist_8bit_entry : /* empty */ constlist_8bit_entry : /* empty */
{ {
out_Skip(1); out_Skip(1);
if (nPass == 1) nListCountEmpty++;
warning("Empty entry in list of 8-bit elements (treated as 0).");
}
| const_8bit
{
out_RelByte(&$1);
}
| string
{
char *s = $1;
int32_t length = charmap_Convert(&s);
out_AbsByteGroup(s, length);
free(s);
}
;
constlist_8bit_entry_single : /* empty */
{
out_Skip(1);
} }
| const_8bit | const_8bit
{ {
@@ -1063,18 +1069,7 @@ constlist_16bit : constlist_16bit_entry
constlist_16bit_entry : /* empty */ constlist_16bit_entry : /* empty */
{ {
out_Skip(2); out_Skip(2);
if (nPass == 1) nListCountEmpty++;
warning("Empty entry in list of 16-bit elements (treated as 0).");
}
| const_16bit
{
out_RelWord(&$1);
}
;
constlist_16bit_entry_single : /* empty */
{
out_Skip(2);
} }
| const_16bit | const_16bit
{ {
@@ -1089,8 +1084,7 @@ constlist_32bit : constlist_32bit_entry
constlist_32bit_entry : /* empty */ constlist_32bit_entry : /* empty */
{ {
out_Skip(4); out_Skip(4);
if (nPass == 1) nListCountEmpty++;
warning("Empty entry in list of 32-bit elements (treated as 0).");
} }
| relocconst | relocconst
{ {
@@ -1098,24 +1092,6 @@ constlist_32bit_entry : /* empty */
} }
; ;
constlist_32bit_entry_single : /* empty */
{
out_Skip(4);
}
| relocconst
{
out_RelLong(&$1);
}
;
const_PCrel : relocconst
{
if (!rpn_isPCRelative(&$1))
yyerror("Expression must be PC-relative");
$$ = $1;
}
;
const_8bit : relocconst const_8bit : relocconst
{ {
if( (!rpn_isReloc(&$1)) && (($1.nVal < -128) || ($1.nVal > 255)) ) if( (!rpn_isReloc(&$1)) && (($1.nVal < -128) || ($1.nVal > 255)) )
@@ -1135,8 +1111,39 @@ const_16bit : relocconst
relocconst : T_ID relocconst : T_ID
{ {
rpn_Symbol(&$$, $1); /*
$$.nVal = sym_GetValue($1); * The value of @ needs to be evaluated by the linker,
* it can only be calculated by the assembler in very
* few cases (when the base address of a section is
* known).
*
* '@' is a bit special in that it means different
* things depending on when it is used:
*
* - JR/LD/ADD/etc: It refers to the first byte of the
* instruction (1 byte offset relative to the value
* stored in the ROM).
* - DB/DW/DL: It refers to the address of the value
* that is being saved (0 byte offset relative to the
* value stored in the ROM.
*
* This offset must be added whenever '@' is added to a
* RPN expression so that the linker can calculate the
* correct result of any expression that uses '@'.
*/
if ((strcmp($1, "@") == 0) && (nPCOffset != 0)) {
struct Expression sTemp, sOffset;
rpn_Symbol(&sTemp, $1);
sTemp.nVal = sym_GetValue($1);
rpn_Number(&sOffset, nPCOffset);
rpn_SUB(&$$, &sTemp, &sOffset);
} else {
rpn_Symbol(&$$, $1);
$$.nVal = sym_GetValue($1);
}
} }
| T_NUMBER | T_NUMBER
{ {
@@ -1234,6 +1241,8 @@ uconst : const
const : T_ID { $$ = sym_GetConstantValue($1); } const : T_ID { $$ = sym_GetConstantValue($1); }
| T_NUMBER { $$ = $1; } | T_NUMBER { $$ = $1; }
| T_OP_HIGH '(' const ')' { $$ = ($3 >> 8) & 0xFF; }
| T_OP_LOW '(' const ')' { $$ = $3 & 0xFF; }
| string { $$ = str2int($1); } | string { $$ = str2int($1); }
| T_OP_LOGICNOT const %prec NEG { $$ = !$2; } | T_OP_LOGICNOT const %prec NEG { $$ = !$2; }
| const T_OP_LOGICOR const { $$ = $1 || $3; } | const T_OP_LOGICOR const { $$ = $1 || $3; }
@@ -1255,19 +1264,38 @@ const : T_ID { $$ = sym_GetConstantValue($1); }
| const T_OP_XOR const { $$ = $1 ^ $3; } | const T_OP_XOR const { $$ = $1 ^ $3; }
| const T_OP_OR const { $$ = $1 | $3; } | const T_OP_OR const { $$ = $1 | $3; }
| const T_OP_AND const { $$ = $1 & $3; } | const T_OP_AND const { $$ = $1 & $3; }
| const T_OP_SHL const { $$ = $1 << $3; } | const T_OP_SHL const
| const T_OP_SHR const { $$ = $1 >> $3; } {
if ($1 < 0)
warning("Left shift of negative value: %d", $1);
if ($3 < 0)
fatalerror("Shift by negative value: %d", $3);
else if ($3 >= 32)
fatalerror("Shift by too big value: %d", $3);
$$ = $1 << $3;
}
| const T_OP_SHR const
{
if ($3 < 0)
fatalerror("Shift by negative value: %d", $3);
else if ($3 >= 32)
fatalerror("Shift by too big value: %d", $3);
$$ = $1 >> $3;
}
| const T_OP_MUL const { $$ = $1 * $3; } | const T_OP_MUL const { $$ = $1 * $3; }
| const T_OP_DIV const | const T_OP_DIV const
{ {
if ($3 == 0) if ($3 == 0)
fatalerror("division by zero"); fatalerror("Division by zero");
$$ = $1 / $3; $$ = $1 / $3;
} }
| const T_OP_MOD const | const T_OP_MOD const
{ {
if ($3 == 0) if ($3 == 0)
fatalerror("division by zero"); fatalerror("Division by zero");
$$ = $1 % $3; $$ = $1 % $3;
} }
| T_OP_ADD const %prec NEG { $$ = +$2; } | T_OP_ADD const %prec NEG { $$ = +$2; }
@@ -1311,26 +1339,37 @@ const : T_ID { $$ = sym_GetConstantValue($1); }
string : T_STRING string : T_STRING
{ {
strcpy($$, $1); if (snprintf($$, MAXSTRLEN + 1, "%s", $1) > MAXSTRLEN)
warning("String is too long '%s'", $1);
} }
| T_OP_STRSUB '(' string comma uconst comma uconst ')' | T_OP_STRSUB '(' string comma uconst comma uconst ')'
{ {
strncpy($$, $3 + $5 - 1, $7); uint32_t len = $7;
$$[$7] = 0; if (len > MAXSTRLEN) {
warning("STRSUB: Length too big: %u", len);
len = MAXSTRLEN;
}
if (snprintf($$, len + 1, "%s", $3 + $5 - 1) > MAXSTRLEN)
warning("STRSUB: String too long '%s'", $$);
} }
| T_OP_STRCAT '(' string comma string ')' | T_OP_STRCAT '(' string comma string ')'
{ {
strcpy($$, $3); if (snprintf($$, MAXSTRLEN + 1, "%s%s", $3, $5) > MAXSTRLEN)
strcat($$, $5); warning("STRCAT: String too long '%s%s'", $3, $5);
} }
| T_OP_STRUPR '(' string ')' | T_OP_STRUPR '(' string ')'
{ {
strcpy($$, $3); if (snprintf($$, MAXSTRLEN + 1, "%s", $3) > MAXSTRLEN)
warning("STRUPR: String too long '%s'", $3);
upperstring($$); upperstring($$);
} }
| T_OP_STRLWR '(' string ')' | T_OP_STRLWR '(' string ')'
{ {
strcpy($$, $3); if (snprintf($$, MAXSTRLEN + 1, "%s", $3) > MAXSTRLEN)
warning("STRUPR: String too long '%s'", $3);
lowerstring($$); lowerstring($$);
} }
; ;
@@ -1602,12 +1641,12 @@ z80_jp : T_Z80_JP const_16bit
} }
; ;
z80_jr : T_Z80_JR const_PCrel z80_jr : T_Z80_JR const_16bit
{ {
out_AbsByte(0x18); out_AbsByte(0x18);
out_PCRelByte(&$2); out_PCRelByte(&$2);
} }
| T_Z80_JR ccode comma const_PCrel | T_Z80_JR ccode comma const_16bit
{ {
out_AbsByte(0x20 | ($2 << 3)); out_AbsByte(0x20 | ($2 << 3));
out_PCRelByte(&$4); out_PCRelByte(&$4);
@@ -1716,7 +1755,8 @@ z80_ld_mem : T_Z80_LD op_mem_ind comma T_MODE_SP
} }
| T_Z80_LD op_mem_ind comma T_MODE_A | T_Z80_LD op_mem_ind comma T_MODE_A
{ {
if ((!rpn_isReloc(&$2)) && ($2.nVal >= 0xFF00)) { if (CurrentOptions.optimizeloads &&
(!rpn_isReloc(&$2)) && ($2.nVal >= 0xFF00)) {
out_AbsByte(0xE0); out_AbsByte(0xE0);
out_AbsByte($2.nVal & 0xFF); out_AbsByte($2.nVal & 0xFF);
} else { } else {
@@ -1769,7 +1809,8 @@ z80_ld_a : T_Z80_LD reg_r comma T_MODE_C_IND
| T_Z80_LD reg_r comma op_mem_ind | T_Z80_LD reg_r comma op_mem_ind
{ {
if ($2 == REG_A) { if ($2 == REG_A) {
if ((!rpn_isReloc(&$4)) && ($4.nVal >= 0xFF00)) { if (CurrentOptions.optimizeloads &&
(!rpn_isReloc(&$4)) && ($4.nVal >= 0xFF00)) {
out_AbsByte(0xF0); out_AbsByte(0xF0);
out_AbsByte($4.nVal & 0xFF); out_AbsByte($4.nVal & 0xFF);
} else { } else {

View File

@@ -87,6 +87,8 @@ static void pushcontext(void)
(*ppFileStack)->nREPTBlockSize = nCurrentREPTBlockSize; (*ppFileStack)->nREPTBlockSize = nCurrentREPTBlockSize;
(*ppFileStack)->nREPTBlockCount = nCurrentREPTBlockCount; (*ppFileStack)->nREPTBlockCount = nCurrentREPTBlockCount;
break; break;
default:
fatalerror("%s: Internal error.", __func__);
} }
nLineNo = 0; nLineNo = 0;
@@ -152,6 +154,8 @@ static int32_t popcontext(void)
nCurrentREPTBlockSize = pLastFile->nREPTBlockSize; nCurrentREPTBlockSize = pLastFile->nREPTBlockSize;
nCurrentREPTBlockCount = pLastFile->nREPTBlockCount; nCurrentREPTBlockCount = pLastFile->nREPTBlockCount;
break; break;
default:
fatalerror("%s: Internal error.", __func__);
} }
free(*ppLastFile); free(*ppLastFile);
@@ -174,6 +178,8 @@ int32_t fstk_GetLine(void)
return nLineNo; /* ??? */ return nLineNo; /* ??? */
case STAT_isREPTBlock: case STAT_isREPTBlock:
break; /* Peek top file of the stack */ break; /* Peek top file of the stack */
default:
fatalerror("%s: Internal error.", __func__);
} }
pLastFile = pFileStack; pLastFile = pFileStack;
@@ -257,8 +263,10 @@ FILE *fstk_FindFile(char *fname)
* space had been available. Thus, a return value of `size` or * space had been available. Thus, a return value of `size` or
* more means that the output was truncated. * more means that the output was truncated.
*/ */
if (snprintf(path, sizeof(path), "%s%s", IncludePaths[i], fname) int fullpathlen = snprintf(path, sizeof(path), "%s%s",
>= sizeof(path)) IncludePaths[i], fname);
if (fullpathlen >= (int)sizeof(path))
continue; continue;
f = fopen(path, "rb"); f = fopen(path, "rb");
@@ -347,7 +355,7 @@ void fstk_RunMacroArg(int32_t s)
pushcontext(); pushcontext();
nCurrentStatus = STAT_isMacroArg; nCurrentStatus = STAT_isMacroArg;
sprintf(tzCurrentFileName, "%c", (uint8_t)s); snprintf(tzCurrentFileName, _MAX_PATH + 1, "%c", (uint8_t)s);
CurrentFlexHandle = yy_scan_bytes(sym, strlen(sym)); CurrentFlexHandle = yy_scan_bytes(sym, strlen(sym));
yy_switch_to_buffer(CurrentFlexHandle); yy_switch_to_buffer(CurrentFlexHandle);
} }
@@ -410,7 +418,7 @@ void fstk_Init(char *s)
nMacroCount = 0; nMacroCount = 0;
nCurrentStatus = STAT_isInclude; nCurrentStatus = STAT_isInclude;
strcpy(tzCurrentFileName, tzFileName); snprintf(tzCurrentFileName, _MAX_PATH + 1, "%s", tzFileName);
CurrentFlexHandle = yy_create_buffer(pCurrentFile); CurrentFlexHandle = yy_create_buffer(pCurrentFile);
yy_switch_to_buffer(CurrentFlexHandle); yy_switch_to_buffer(CurrentFlexHandle);
nLineNo = 1; nLineNo = 1;

View File

@@ -20,6 +20,8 @@
#include "asm/symbol.h" #include "asm/symbol.h"
#include "asm/symbol.h" #include "asm/symbol.h"
#include "helpers.h"
#include "asmy.h" #include "asmy.h"
bool oDontExpandStrings; bool oDontExpandStrings;
@@ -93,6 +95,9 @@ static int32_t ascii2bin(char *s)
s += 1; s += 1;
convertfunc = binary2bin; convertfunc = binary2bin;
break; break;
default:
/* Handle below */
break;
} }
if (radix == 4) { if (radix == 4) {
@@ -216,7 +221,7 @@ uint32_t PutMacroArg(char *src, uint32_t size)
return 0; return 0;
} }
uint32_t PutUniqueArg(char *src, uint32_t size) uint32_t PutUniqueArg(unused_ char *src, uint32_t size)
{ {
char *s; char *s;
@@ -368,6 +373,7 @@ const struct sLexInitString lexer_strings[] = {
{"include", T_POP_INCLUDE}, {"include", T_POP_INCLUDE},
{"printt", T_POP_PRINTT}, {"printt", T_POP_PRINTT},
{"printi", T_POP_PRINTI},
{"printv", T_POP_PRINTV}, {"printv", T_POP_PRINTV},
{"printf", T_POP_PRINTF}, {"printf", T_POP_PRINTF},
{"export", T_POP_EXPORT}, {"export", T_POP_EXPORT},
@@ -578,7 +584,7 @@ void setup_lexer(void)
lex_FloatAddRange(id, '@', '@'); lex_FloatAddRange(id, '@', '@');
lex_FloatAddRange(id, '#', '#'); lex_FloatAddRange(id, '#', '#');
//@ID // "@"
id = lex_FloatAlloc(&tIDToken); id = lex_FloatAlloc(&tIDToken);
lex_FloatAddFirstRange(id, '@', '@'); lex_FloatAddFirstRange(id, '@', '@');

View File

@@ -159,6 +159,8 @@ YY_BUFFER_STATE yy_create_buffer(FILE *f)
pBuffer->pBuffer[size + 1] = 0; pBuffer->pBuffer[size + 1] = 0;
pBuffer->nBufferSize = size + 1; pBuffer->nBufferSize = size + 1;
/* Convert all line endings to LF and spaces */
char *mem = pBuffer->pBuffer; char *mem = pBuffer->pBuffer;
uint32_t instring = 0; uint32_t instring = 0;
@@ -171,20 +173,44 @@ YY_BUFFER_STATE yy_create_buffer(FILE *f)
} else if (instring) { } else if (instring) {
mem += 1; mem += 1;
} else { } else {
if ((mem[0] == 10 && mem[1] == 13) /* LF CR and CR LF */
|| (mem[0] == 13 && mem[1] == 10)) { if (((mem[0] == 10) && (mem[1] == 13))
|| ((mem[0] == 13) && (mem[1] == 10))) {
mem[0] = ' '; mem[0] = ' ';
mem[1] = '\n'; mem[1] = '\n';
mem += 2; mem += 2;
} else if (mem[0] == 10 || mem[0] == 13) { /* LF and CR */
} else if ((mem[0] == 10) || (mem[0] == 13)) {
mem[0] = '\n'; mem[0] = '\n';
mem += 1; mem += 1;
} else if (mem[0] == '\n' && mem[1] == '*') { } else {
mem += 1; mem += 1;
while (!(*mem == '\n' || *mem == '\0')) }
}
}
/* Remove comments */
mem = pBuffer->pBuffer;
instring = 0;
while (*mem) {
if (*mem == '\"')
instring = 1 - instring;
if ((mem[0] == '\\') && (mem[1] == '\"' || mem[1] == '\\')) {
mem += 2;
} else if (instring) {
mem += 1;
} else {
/* Comments that start with ; anywhere in a line */
if (*mem == ';') {
while (!((*mem == '\n') || (*mem == '\0')))
*mem++ = ' '; *mem++ = ' ';
} else if (*mem == ';') { /* Comments that start with * at the start of a line */
while (!(*mem == '\n' || *mem == '\0')) } else if ((mem[0] == '\n') && (mem[1] == '*')) {
mem += 1;
while (!((*mem == '\n') || (*mem == '\0')))
*mem++ = ' '; *mem++ = ' ';
} else { } else {
mem += 1; mem += 1;
@@ -388,7 +414,7 @@ void yylex_GetFloatMaskAndFloatLen(uint32_t *pnFloatMask, uint32_t *pnFloatLen)
/* /*
* Gets the longest keyword/operator from the current position in the buffer. * Gets the longest keyword/operator from the current position in the buffer.
*/ */
struct sLexString *yylex_GetLongestFixed() struct sLexString *yylex_GetLongestFixed(void)
{ {
struct sLexString *pLongestFixed = NULL; struct sLexString *pLongestFixed = NULL;
char *s = pLexBuffer; char *s = pLexBuffer;
@@ -612,6 +638,43 @@ scanagain:
} }
} }
/* Check for line continuation character */
if (*pLexBuffer == '\\') {
/*
* Look for line continuation character after a series of
* spaces. This is also useful for files that use Windows line
* endings: "\r\n" is replaced by " \n" before the lexer has the
* opportunity to see it.
*/
if (pLexBuffer[1] == ' ') {
pLexBuffer += 2;
while (1) {
if (*pLexBuffer == ' ') {
pLexBuffer++;
} else if (*pLexBuffer == '\n') {
pLexBuffer++;
nLineNo += 1;
goto scanagain;
} else {
errx(1, "Expected a new line after the continuation character.");
}
}
}
/* Line continuation character */
if (pLexBuffer[1] == '\n') {
pLexBuffer += 2;
nLineNo += 1;
goto scanagain;
}
/*
* If there isn't a newline character or a space, ignore the
* character '\'. It will eventually be handled by other
* functions like PutMacroArg().
*/
}
/* /*
* Try to match an identifier, macro argument (e.g. \1), * Try to match an identifier, macro argument (e.g. \1),
* or numeric literal. * or numeric literal.
@@ -699,6 +762,9 @@ static uint32_t yylex_MACROARGS(void)
case '\\': case '\\':
ch = '\\'; ch = '\\';
break; break;
case '"':
ch = '\"';
break;
case ',': case ',':
ch = ','; ch = ',';
break; break;
@@ -708,6 +774,32 @@ static uint32_t yylex_MACROARGS(void)
case '}': case '}':
ch = '}'; ch = '}';
break; break;
case ' ':
/*
* Look for line continuation character after a
* series of spaces. This is also useful for
* files that use Windows line endings: "\r\n"
* is replaced by " \n" before the lexer has the
* opportunity to see it.
*/
while (1) {
if (*pLexBuffer == ' ') {
pLexBuffer++;
} else if (*pLexBuffer == '\n') {
pLexBuffer++;
nLineNo += 1;
ch = 0;
break;
} else {
errx(1, "Expected a new line after the continuation character.");
}
}
break;
case '\n':
/* Line continuation character */
nLineNo += 1;
ch = 0;
break;
default: default:
maxLength = MAXSTRLEN - index; maxLength = MAXSTRLEN - index;
length = CopyMacroArg(&yylval.tzString[index], length = CopyMacroArg(&yylval.tzString[index],
@@ -758,7 +850,7 @@ uint32_t yylex(void)
return yylex_NORMAL(); return yylex_NORMAL();
case LEX_STATE_MACROARGS: case LEX_STATE_MACROARGS:
return yylex_MACROARGS(); return yylex_MACROARGS();
default:
fatalerror("%s: Internal error.", __func__);
} }
fatalerror("Internal error in %s", __func__);
} }

View File

@@ -6,6 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include <float.h>
#include <math.h> #include <math.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
@@ -23,6 +24,7 @@
#include "extern/err.h" #include "extern/err.h"
#include "helpers.h"
#include "version.h" #include "version.h"
extern int yyparse(void); extern int yyparse(void);
@@ -147,10 +149,13 @@ void opt_Parse(char *s)
case 'z': case 'z':
if (strlen(&s[1]) <= 2) { if (strlen(&s[1]) <= 2) {
int32_t result; int32_t result;
unsigned int fillchar;
result = sscanf(&s[1], "%x", &newopt.fillchar); result = sscanf(&s[1], "%x", &fillchar);
if (!((result == EOF) || (result == 1))) if (!((result == EOF) || (result == 1)))
errx(1, "Invalid argument for option 'z'"); errx(1, "Invalid argument for option 'z'");
newopt.fillchar = fillchar;
} else { } else {
errx(1, "Invalid argument for option 'z'"); errx(1, "Invalid argument for option 'z'");
} }
@@ -222,7 +227,7 @@ void opt_AddDefine(char *s)
static void opt_ParseDefines(void) static void opt_ParseDefines(void)
{ {
int32_t i; uint32_t i;
for (i = 0; i < cldefines_index; i += 2) for (i = 0; i < cldefines_index; i += 2)
sym_AddString(cldefines[i], cldefines[i + 1]); sym_AddString(cldefines[i], cldefines[i + 1]);
@@ -235,7 +240,7 @@ void verror(const char *fmt, va_list args)
{ {
fprintf(stderr, "ERROR: "); fprintf(stderr, "ERROR: ");
fstk_Dump(); fstk_Dump();
fprintf(stderr, ":\n\t"); fprintf(stderr, ":\n ");
vfprintf(stderr, fmt, args); vfprintf(stderr, fmt, args);
fprintf(stderr, "\n"); fprintf(stderr, "\n");
nErrors += 1; nErrors += 1;
@@ -250,7 +255,7 @@ void yyerror(const char *fmt, ...)
va_end(args); va_end(args);
} }
void fatalerror(const char *fmt, ...) noreturn_ void fatalerror(const char *fmt, ...)
{ {
va_list args; va_list args;
@@ -272,7 +277,7 @@ void warning(const char *fmt, ...)
fprintf(stderr, "warning: "); fprintf(stderr, "warning: ");
fstk_Dump(); fstk_Dump();
fprintf(stderr, ":\n\t"); fprintf(stderr, ":\n ");
vfprintf(stderr, fmt, args); vfprintf(stderr, fmt, args);
fprintf(stderr, "\n"); fprintf(stderr, "\n");
@@ -282,7 +287,7 @@ void warning(const char *fmt, ...)
static void print_usage(void) static void print_usage(void)
{ {
printf( printf(
"usage: rgbasm [-EhVvw] [-b chars] [-Dname[=value]] [-g chars] [-i path]\n" "usage: rgbasm [-EhLVvw] [-b chars] [-Dname[=value]] [-g chars] [-i path]\n"
" [-M dependfile] [-o outfile] [-p pad_value] file.asm\n"); " [-M dependfile] [-o outfile] [-p pad_value] file.asm\n");
exit(1); exit(1);
} }
@@ -316,17 +321,18 @@ int main(int argc, char *argv[])
DefaultOptions.gbgfx[3] = '3'; DefaultOptions.gbgfx[3] = '3';
DefaultOptions.binary[0] = '0'; DefaultOptions.binary[0] = '0';
DefaultOptions.binary[1] = '1'; DefaultOptions.binary[1] = '1';
DefaultOptions.fillchar = 0;
DefaultOptions.verbose = false;
DefaultOptions.haltnop = true;
DefaultOptions.exportall = false; DefaultOptions.exportall = false;
DefaultOptions.fillchar = 0;
DefaultOptions.optimizeloads = true;
DefaultOptions.haltnop = true;
DefaultOptions.verbose = false;
DefaultOptions.warnings = true; DefaultOptions.warnings = true;
opt_SetCurrentOptions(&DefaultOptions); opt_SetCurrentOptions(&DefaultOptions);
newopt = CurrentOptions; newopt = CurrentOptions;
while ((ch = getopt(argc, argv, "b:D:g:hi:M:o:p:EVvw")) != -1) { while ((ch = getopt(argc, argv, "b:D:Eg:hi:LM:o:p:Vvw")) != -1) {
switch (ch) { switch (ch) {
case 'b': case 'b':
if (strlen(optarg) == 2) { if (strlen(optarg) == 2) {
@@ -358,6 +364,9 @@ int main(int argc, char *argv[])
case 'i': case 'i':
fstk_AddIncludePath(optarg); fstk_AddIncludePath(optarg);
break; break;
case 'L':
newopt.optimizeloads = false;
break;
case 'M': case 'M':
dependfile = fopen(optarg, "w"); dependfile = fopen(optarg, "w");
if (dependfile == NULL) if (dependfile == NULL)
@@ -476,7 +485,7 @@ int main(int argc, char *argv[])
if (CurrentOptions.verbose) { if (CurrentOptions.verbose) {
printf("Success! %u lines in %d.%02d seconds ", nTotalLines, printf("Success! %u lines in %d.%02d seconds ", nTotalLines,
(int)timespent, ((int)(timespent * 100.0)) % 100); (int)timespent, ((int)(timespent * 100.0)) % 100);
if (timespent == 0) if (timespent < FLT_MIN_EXP)
printf("(INFINITY lines/minute)\n"); printf("(INFINITY lines/minute)\n");
else else
printf("(%d lines/minute)\n", printf("(%d lines/minute)\n",

View File

@@ -593,7 +593,7 @@ void out_SetFileName(char *s)
} }
/* /*
* 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
*/ */
struct Section *out_FindSection(char *pzName, uint32_t secttype, int32_t org, struct Section *out_FindSection(char *pzName, uint32_t secttype, int32_t org,
int32_t bank, int32_t alignment) int32_t bank, int32_t alignment)
@@ -764,7 +764,7 @@ void out_String(char *s)
} }
/* /*
* Output a relocatable byte. Checking will be done to see if it * Output a relocatable byte. Checking will be done to see if it
* is an absolute value in disguise. * is an absolute value in disguise.
*/ */
void out_RelByte(struct Expression *expr) void out_RelByte(struct Expression *expr)
@@ -803,7 +803,7 @@ void out_AbsWord(int32_t b)
} }
/* /*
* Output a relocatable word. Checking will be done to see if * Output a relocatable word. Checking will be done to see if
* it's an absolute value in disguise. * it's an absolute value in disguise.
*/ */
void out_RelWord(struct Expression *expr) void out_RelWord(struct Expression *expr)
@@ -847,7 +847,7 @@ void out_AbsLong(int32_t b)
} }
/* /*
* Output a relocatable longword. Checking will be done to see if * Output a relocatable longword. Checking will be done to see if
* is an absolute value in disguise. * is an absolute value in disguise.
*/ */
void out_RelLong(struct Expression *expr) void out_RelLong(struct Expression *expr)
@@ -875,19 +875,23 @@ void out_RelLong(struct Expression *expr)
} }
/* /*
* Output a PC-relative byte * Output a PC-relative relocatable byte. Checking will be done to see if it
* is an absolute value in disguise.
*/ */
void out_PCRelByte(struct Expression *expr) void out_PCRelByte(struct Expression *expr)
{ {
int32_t b = expr->nVal;
checkcodesection(); checkcodesection();
checksectionoverflow(1); checksectionoverflow(1);
b = (b & 0xFFFF) - (nPC + 1);
if (nPass == 2 && (b < -128 || b > 127))
yyerror("PC-relative value must be 8-bit");
out_AbsByte(b); /* Always let the linker calculate the offset. */
if (nPass == 2) {
pCurrentSection->tData[nPC] = 0;
createpatch(PATCH_BYTE_JR, expr);
}
pCurrentSection->nPC += 1;
nPC += 1;
pPCSymbol->nValue += 1;
rpn_Reset(expr); rpn_Reset(expr);
} }

View File

@@ -5,7 +5,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd January 26, 2018 .Dd February 24, 2018
.Dt RGBASM 1 .Dt RGBASM 1
.Os RGBDS Manual .Os RGBDS Manual
.Sh NAME .Sh NAME
@@ -13,7 +13,7 @@
.Nd Game Boy assembler .Nd Game Boy assembler
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm rgbasm .Nm rgbasm
.Op Fl EhVvw .Op Fl EhLVvw
.Op Fl b Ar chars .Op Fl b Ar chars
.Op Fl D Ar name Ns Op = Ns Ar value .Op Fl D Ar name Ns Op = Ns Ar value
.Op Fl g Ar chars .Op Fl g Ar chars
@@ -55,6 +55,12 @@ The
option disables this behavior. option disables this behavior.
.It Fl i Ar path .It Fl i Ar path
Add an include path. Add an include path.
.It Fl L
Disable the optimization that turns loads of the form
.Sy LD [$FF00+n8],A
into the opcode
.Sy LDH [$FF00+n8],A
in order to have full control of the result in the final ROM.
.It Fl M Ar dependfile .It Fl M Ar dependfile
Print Print
.Xr make 1 .Xr make 1
@@ -75,7 +81,9 @@ Disable warning output.
.Sh EXAMPLES .Sh EXAMPLES
Assembling a basic source file is simple: Assembling a basic source file is simple:
.Pp .Pp
.D1 $ rgbasm -o bar.o foo.asm .Bd -literal -offset indent
$ rgbasm -o bar.o foo.asm
.Ed
.Pp .Pp
The resulting object file is not yet a usable ROM image \(em it must first be The resulting object file is not yet a usable ROM image \(em it must first be
run through run through

View File

@@ -5,7 +5,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd January 27, 2018 .Dd March 13, 2018
.Dt RGBASM 5 .Dt RGBASM 5
.Os RGBDS Manual .Os RGBDS Manual
.Sh NAME .Sh NAME
@@ -26,16 +26,46 @@ one instruction or pseudoop per line:
.Pp .Pp
Example: Example:
.Pp .Pp
.Dl John: ld a,87 ;Weee .Bd -literal -offset indent
John: ld a,87 ;Weee
.Ed
.Pp .Pp
All pseudoops, mnemonics and registers (reserved keywords) are caseinsensitive All pseudoops, mnemonics and registers (reserved keywords) are caseinsensitive
and all labels are casesensitive. and all labels are casesensitive.
.Pp
There are two syntaxes for comments. In both cases, a comment ends at the end of
the line. The most common one is: anything that follows a semicolon
\[dq]\&;\[dq] (that isn't inside a string) is a comment. There is another
format: anything that follows a \[dq]*\[dq] that is placed right at the start of
a line is a comment. The assembler removes all comments from the code before
doing anything else.
.Pp
Sometimes lines can be too long and it may be necessary to split them. The
syntax to do so is the following one:
.Pp
.Bd -literal -offset indent
DB 1, 2, 3, 4 \[rs]
5, 6, 7, 8
.Ed
.Pp
This works anywhere in the code except inside of strings. To split strings it is
needed to use
.Sy STRCAT
like this:
.Pp
.Bd -literal -offset indent
DB STRCAT("Hello ", \[rs]
"world!")
.Ed
.Pp
.Ss Sections .Ss Sections
Before you can start writing code, you must define a section. Before you can start writing code, you must define a section.
This tells the assembler what kind of information follows and, if it is code, This tells the assembler what kind of information follows and, if it is code,
where to put it. where to put it.
.Pp .Pp
.Dl SECTION \[dq]CoolStuff\[dq],ROMX .Bd -literal -offset indent
SECTION \[dq]CoolStuff\[dq],ROMX
.Ed
.Pp .Pp
This switches to the section called "CoolStuff" (or creates it if it doesn't This switches to the section called "CoolStuff" (or creates it if it doesn't
already exist) and it defines it as a code section. already exist) and it defines it as a code section.
@@ -104,7 +134,13 @@ and
.Sy LDH A,[$FF00+n8] .Sy LDH A,[$FF00+n8]
syntax instead. syntax instead.
This forces the assembler to emit the correct instruction and the linker to This forces the assembler to emit the correct instruction and the linker to
check if the value is in the correct range. check if the value is in the correct range. This optimization can be disabled
by passing the
.Fl L
flag to
.Sy rgbasm
as explained in
.Xr rgbasm 1 .
.El .El
.Pp .Pp
A section is usually defined as a floating one, but the code can restrict where A section is usually defined as a floating one, but the code can restrict where
@@ -116,22 +152,30 @@ obligation to follow any specific rules.
The following example defines a section that can be placed anywhere in any ROMX The following example defines a section that can be placed anywhere in any ROMX
bank: bank:
.Pp .Pp
.Dl SECTION \[dq]CoolStuff\[dq],ROMX .Bd -literal -offset indent
SECTION \[dq]CoolStuff\[dq],ROMX
.Ed
.Pp .Pp
If it is needed, the following syntax can be used to fix the base address of the If it is needed, the following syntax can be used to fix the base address of the
section: section:
.Pp .Pp
.Dl SECTION \[dq]CoolStuff\[dq],ROMX[$4567] .Bd -literal -offset indent
SECTION \[dq]CoolStuff\[dq],ROMX[$4567]
.Ed
.Pp .Pp
It won't, however, fix the bank number, which is left to the linker. It won't, however, fix the bank number, which is left to the linker.
If you also want to specify the bank you can do: If you also want to specify the bank you can do:
.Pp .Pp
.Dl SECTION \[dq]CoolStuff\[dq],ROMX[$4567],BANK[3] .Bd -literal -offset indent
SECTION \[dq]CoolStuff\[dq],ROMX[$4567],BANK[3]
.Ed
.Pp .Pp
And if you only want to force the section into a certain bank, and not it's And if you only want to force the section into a certain bank, and not it's
position within the bank, that's also possible: position within the bank, that's also possible:
.Pp .Pp
.Dl SECTION \[dq]CoolStuff\[dq],ROMX,BANK[7] .Bd -literal -offset indent
SECTION \[dq]CoolStuff\[dq],ROMX,BANK[7]
.Ed
.Pp .Pp
In addition, you can specify byte alignment for a section. In addition, you can specify byte alignment for a section.
This ensures that the section starts at a memory address where the given number This ensures that the section starts at a memory address where the given number
@@ -143,9 +187,11 @@ However, if an alignment is specified, the base address must be left unassigned.
This can be useful when using DMA to copy data or when it is needed to align the This can be useful when using DMA to copy data or when it is needed to align the
start of an array to 256 bytes to optimize the code that accesses it. start of an array to 256 bytes to optimize the code that accesses it.
.Pp .Pp
.Dl SECTION \[dq]OAM Data\[dq],WRAM0,ALIGN[8] ; align to 256 bytes .Bd -literal -offset indent
.Pp SECTION \[dq]OAM Data\[dq],WRAM0,ALIGN[8] ; align to 256 bytes
.Dl SECTION \[dq]VRAM Data\[dq],ROMX,BANK[2],ALIGN[4] ; align to 16 bytes
SECTION \[dq]VRAM Data\[dq],ROMX,BANK[2],ALIGN[4] ; align to 16 bytes
.Ed
.Pp .Pp
HINT: If you think this is a lot of typing for doing a simple HINT: If you think this is a lot of typing for doing a simple
.Ic ORG .Ic ORG
@@ -243,8 +289,10 @@ EQUates are constant symbols.
They can, for example, be used for things such as bit-definitions of hardware They can, for example, be used for things such as bit-definitions of hardware
registers. registers.
.Pp .Pp
.Dl EXIT_OK EQU $00 .Bd -literal -offset indent
.Dl EXIT_FAILURE EQU $01 EXIT_OK EQU $00
EXIT_FAILURE EQU $01
.Ed
.Pp .Pp
Note that a colon (:) following the label-name is not allowed. Note that a colon (:) following the label-name is not allowed.
EQUates cannot be exported and imported. EQUates cannot be exported and imported.
@@ -266,7 +314,9 @@ Note that a colon (:) following the label-name is not allowed.
SETs cannot be exported and imported. SETs cannot be exported and imported.
Alternatively you can use = as a synonym for SET. Alternatively you can use = as a synonym for SET.
.Pp .Pp
.Dl COUNT = 2 .Bd -literal -offset indent
COUNT = 2
.Ed
.Pp .Pp
.It Sy RSSET , RSRESET , RB , RW .It Sy RSSET , RSRESET , RB , RW
.Pp .Pp
@@ -318,10 +368,10 @@ If you are familiar with C you can think of it as the same as #define.
.Pp .Pp
.Bd -literal -offset indent .Bd -literal -offset indent
COUNTREG EQUS "[hl+]" COUNTREG EQUS "[hl+]"
ld a,COUNTREG ld a,COUNTREG
PLAYER_NAME EQUS \[dq]\[rs]\[dq]John\[rs]\[dq]\[dq] PLAYER_NAME EQUS \[dq]\[rs]\[dq]John\[rs]\[dq]\[dq]
db PLAYER_NAME db PLAYER_NAME
.Ed .Ed
.Pp .Pp
Note that : following the label-name is not allowed, and that strings must be Note that : following the label-name is not allowed, and that strings must be
@@ -329,12 +379,16 @@ quoted to be useful.
.Pp .Pp
This will be interpreted as: This will be interpreted as:
.Pp .Pp
.Dl ld a,[hl+] .Bd -literal -offset indent
.Dl db \[dq]John\[dq] ld a,[hl+]
db \[dq]John\[dq]
.Ed
.Pp .Pp
String-symbols can also be used to define small one-line macros: String-symbols can also be used to define small one-line macros:
.Pp .Pp
.Dl PUSHA EQUS \[dq]push af\[rs]npush bc\[rs]npush de\[rs]npush hl\[rs]n\[dq] .Bd -literal -offset indent
PUSHA EQUS \[dq]push af\[rs]npush bc\[rs]npush de\[rs]npush hl\[rs]n\[dq]
.Ed
.Pp .Pp
Note that a colon (:) following the label-name is not allowed. Note that a colon (:) following the label-name is not allowed.
String equates can't be exported or imported. String equates can't be exported or imported.
@@ -447,7 +501,9 @@ Now I can call the macro specifying two arguments.
The first being the address and the second being a bytecount. The first being the address and the second being a bytecount.
The macro will then reset all bytes in this range. The macro will then reset all bytes in this range.
.Pp .Pp
.Dl LoopyMacro MyVars,54 .Bd -literal -offset indent
LoopyMacro MyVars,54
.Ed
.Pp .Pp
Arguments are passed as string equates. Arguments are passed as string equates.
There's no need to enclose them in quotes. There's no need to enclose them in quotes.
@@ -467,6 +523,19 @@ In reality, up to 256 arguments can be passed to a macro, but you can only use
the first 9 like this. If you want to use the rest, you need to use the keyword the first 9 like this. If you want to use the rest, you need to use the keyword
.Ic SHIFT . .Ic SHIFT .
.Pp .Pp
Line continuations work as usual inside macros or lists of arguments of macros.
Strings, however, are a bit trickier. The following example shows how to use
strings as arguments for a macro:
.Pp
.Bd -literal -offset indent
PrintMacro : MACRO
PRINTT \[rs]1
ENDM
PrintMacro STRCAT(\[rs]\[dq]Hello\[rs]\[dq]\[rs], \[rs]
\[rs]\[dq] world\[rs]\[rs]n\[rs]\[dq])
.Ed
.Pp
.Ic SHIFT .Ic SHIFT
is a special command only available in macros. is a special command only available in macros.
Very useful in REPT-blocks. Very useful in REPT-blocks.
@@ -553,7 +622,9 @@ The following symbols are defined by the assembler:
defines a list of bytes that will be stored in the final image. defines a list of bytes that will be stored in the final image.
Ideal for tables and text (which is not zero-terminated). Ideal for tables and text (which is not zero-terminated).
.Pp .Pp
.Dl DB 1,2,3,4,\[dq]This is a string\[dq] .Bd -literal -offset indent
DB 1,2,3,4,\[dq]This is a string\[dq]
.Ed
.Pp .Pp
Alternatively, you can use Alternatively, you can use
.Ic DW .Ic DW
@@ -597,7 +668,9 @@ and
.Ic DL .Ic DL
without any arguments instead. without any arguments instead.
.Pp .Pp
.Dl DS str_SIZEOF ;allocate str_SIZEOF bytes .Bd -literal -offset indent
DS str_SIZEOF ;allocate str_SIZEOF bytes
.Ed
.Pp .Pp
.Ss Including binary files .Ss Including binary files
You probably have some graphics you'd like to include. You probably have some graphics you'd like to include.
@@ -607,15 +680,19 @@ to include a raw binary file as it is.
If the file isn't found in the current directory, the include-path list passed If the file isn't found in the current directory, the include-path list passed
to the linker on the command line will be searched. to the linker on the command line will be searched.
.Pp .Pp
.Dl INCBIN \[dq]titlepic.bin\[dq] .Bd -literal -offset indent
.Dl INCBIN \[dq]sprites/hero.bin\[dq]\ ; UNIX INCBIN \[dq]titlepic.bin\[dq]
.Dl INCBIN \[dq]sprites\[rs]\[rs]hero.bin\[dq]\ ; Windows INCBIN \[dq]sprites/hero.bin\[dq]\ ; UNIX
INCBIN \[dq]sprites\[rs]\[rs]hero.bin\[dq]\ ; Windows
.Ed
.Pp .Pp
You can also include only part of a file with You can also include only part of a file with
.Ic INCBIN . .Ic INCBIN .
The example below includes 256 bytes from data.bin starting from byte 78. The example below includes 256 bytes from data.bin starting from byte 78.
.Pp .Pp
.Dl INCBIN \[dq]data.bin\[dq],78,256 .Bd -literal -offset indent
INCBIN \[dq]data.bin\[dq],78,256
.Ed
.Ss Unions .Ss Unions
Unions allow multiple memory allocations to share the same space in memory, Unions allow multiple memory allocations to share the same space in memory,
like unions in C. like unions in C.
@@ -660,16 +737,19 @@ some important information.
.Pp .Pp
.Bd -literal -offset indent .Bd -literal -offset indent
PRINTT \[dq]I'm the greatest programmer in the whole wide world\[rs]n\[dq] PRINTT \[dq]I'm the greatest programmer in the whole wide world\[rs]n\[dq]
PRINTV (2+3)/5 PRINTI (2 + 3) / 5
PRINTF MUL(3.14,3987.0) PRINTV $FF00 + $F0
PRINTF MUL(3.14, 3987.0)
.Ed .Ed
.Pp .Pp
.Bl -inset .Bl -inset
.It Ic PRINTT .It Ic PRINTT
prints out a string. prints out a string.
.It Ic PRINTV .It Ic PRINTV
prints out an integer value or, as in the example, the result of a calculation. prints out an integer value in hexadecimal or, as in the example, the result of
Unsurprisingly, you can also print out a constant symbols value. a calculation. Unsurprisingly, you can also print out a constant symbols value.
.It Ic PRINTI
prints out a signed integer value.
.It Ic PRINTF .It Ic PRINTF
prints out a fixed point value. prints out a fixed point value.
.El .El
@@ -740,7 +820,9 @@ You may nest
.Ic INCLUDE .Ic INCLUDE
calls infinitely (or until you run out of memory, whichever comes first). calls infinitely (or until you run out of memory, whichever comes first).
.Pp .Pp
.Dl INCLUDE \[dq]irq.inc\[dq] .Bd -literal -offset indent
INCLUDE \[dq]irq.inc\[dq]
.Ed
.Pp .Pp
.Ss Conditional assembling .Ss Conditional assembling
The four commands The four commands
@@ -816,7 +898,9 @@ The last one, Gameboy graphics, is quite interesting and useful.
The values are actually pixel values and it converts the The values are actually pixel values and it converts the
.Do chunky Dc data to Do planar Dc data as used in the Gameboy. .Do chunky Dc data to Do planar Dc data as used in the Gameboy.
.Pp .Pp
.Dl DW \`01012323 .Bd -literal -offset indent
DW \`01012323
.Ed
.Pp .Pp
Admittedly, an expression with just a single number is quite boring. Admittedly, an expression with just a single number is quite boring.
To spice things up a bit there are a few operators you can use to perform To spice things up a bit there are a few operators you can use to perform
@@ -957,6 +1041,34 @@ new string.
the new string. the new string.
.El .El
.Pp .Pp
.Ss Character maps
.Pp
When writing text that is meant to be displayed in the Game Boy, the ASCII
characters used in the source code may not be the same ones used in the tileset
used in the ROM.
For example, the tiles used for uppercase letters may be placed starting at tile
index 128, which makes it difficult to add text strings to the ROM.
.Pp
Character maps allow the code to map strings up to 16 characters long to an
abitrary 8-bit value:
.Pp
.Bd -literal -offset indent
CHARMAP "<LF>", 10
CHARMAP "&iacute", 20
CHARMAP "A", 128
.Ed
.Pp
.Sy Note:
Character maps affect all strings in the file from the point in which they are
defined.
This means that any string that the code may want to print as debug information
will also be affected by it.
.Pp
.Sy Note:
The output value of a mapping can be 0.
If this happens, the assembler will treat this as the end of the string and the
rest of it will be trimmed.
.Pp
.Ss Other functions .Ss Other functions
There are a few other functions that do various useful things: There are a few other functions that do various useful things:
.Pp .Pp
@@ -1039,6 +1151,7 @@ machine.
.It Sx ATAN .It Sx ATAN
.It Sx ATAN2 .It Sx ATAN2
.It Sx BANK .It Sx BANK
.It Sx CHARMAP
.It Sx COS .It Sx COS
.It Sx DB .It Sx DB
.It Sx DEF .It Sx DEF
@@ -1068,6 +1181,7 @@ machine.
.It Sx POPO .It Sx POPO
.It Sx POPS .It Sx POPS
.It Sx PRINTF .It Sx PRINTF
.It Sx PRINTI
.It Sx PRINTT .It Sx PRINTT
.It Sx PRINTV .It Sx PRINTV
.It Sx PURGE .It Sx PURGE

View File

@@ -330,6 +330,15 @@ void rpn_SHL(struct Expression *expr, const struct Expression *src1,
const struct Expression *src2) const struct Expression *src2)
{ {
joinexpr(); joinexpr();
if (src1->nVal < 0)
warning("Left shift of negative value: %d", src1->nVal);
if (src2->nVal < 0)
fatalerror("Shift by negative value: %d", src2->nVal);
else if (src2->nVal >= 32)
fatalerror("Shift by too big value: %d", src2->nVal);
expr->nVal = (expr->nVal << src2->nVal); expr->nVal = (expr->nVal << src2->nVal);
pushbyte(expr, RPN_SHL); pushbyte(expr, RPN_SHL);
} }
@@ -338,6 +347,11 @@ void rpn_SHR(struct Expression *expr, const struct Expression *src1,
const struct Expression *src2) const struct Expression *src2)
{ {
joinexpr(); joinexpr();
if (src2->nVal < 0)
fatalerror("Shift by negative value: %d", src2->nVal);
else if (src2->nVal >= 32)
fatalerror("Shift by too big value: %d", src2->nVal);
expr->nVal = (expr->nVal >> src2->nVal); expr->nVal = (expr->nVal >> src2->nVal);
pushbyte(expr, RPN_SHR); pushbyte(expr, RPN_SHR);
} }
@@ -355,7 +369,7 @@ void rpn_DIV(struct Expression *expr, const struct Expression *src1,
{ {
joinexpr(); joinexpr();
if (src2->nVal == 0) if (src2->nVal == 0)
fatalerror("division by zero"); fatalerror("Division by zero");
expr->nVal = (expr->nVal / src2->nVal); expr->nVal = (expr->nVal / src2->nVal);
pushbyte(expr, RPN_DIV); pushbyte(expr, RPN_DIV);
@@ -366,7 +380,7 @@ void rpn_MOD(struct Expression *expr, const struct Expression *src1,
{ {
joinexpr(); joinexpr();
if (src2->nVal == 0) if (src2->nVal == 0)
fatalerror("division by zero"); fatalerror("Division by zero");
expr->nVal = (expr->nVal % src2->nVal); expr->nVal = (expr->nVal % src2->nVal);
pushbyte(expr, RPN_MOD); pushbyte(expr, RPN_MOD);

View File

@@ -25,6 +25,7 @@
#include "extern/err.h" #include "extern/err.h"
#include "helpers.h"
#include "version.h" #include "version.h"
struct sSymbol *tHashedSymbols[HASHSIZE]; struct sSymbol *tHashedSymbols[HASHSIZE];
@@ -40,7 +41,7 @@ static char SavedTIMESTAMP_ISO8601_LOCAL[256];
static char SavedTIMESTAMP_ISO8601_UTC[256]; static char SavedTIMESTAMP_ISO8601_UTC[256];
static char SavedDAY[3]; static char SavedDAY[3];
static char SavedMONTH[3]; static char SavedMONTH[3];
static char SavedYEAR[5]; static char SavedYEAR[20];
static char SavedHOUR[3]; static char SavedHOUR[3];
static char SavedMINUTE[3]; static char SavedMINUTE[3];
static char SavedSECOND[3]; static char SavedSECOND[3];
@@ -62,7 +63,7 @@ void helper_RemoveLeadingZeros(char *string)
memmove(string, new_beginning, strlen(new_beginning) + 1); memmove(string, new_beginning, strlen(new_beginning) + 1);
} }
int32_t Callback_NARG(struct sSymbol *sym) int32_t Callback_NARG(unused_ struct sSymbol *sym)
{ {
uint32_t i = 0; uint32_t i = 0;
@@ -72,7 +73,7 @@ int32_t Callback_NARG(struct sSymbol *sym)
return i; return i;
} }
int32_t Callback__LINE__(struct sSymbol __attribute__((unused)) *sym) int32_t Callback__LINE__(unused_ struct sSymbol *sym)
{ {
return nLineNo; return nLineNo;
} }
@@ -122,7 +123,9 @@ struct sSymbol *createsymbol(char *s)
return NULL; return NULL;
} }
strcpy((*ppsym)->tzName, s); if (snprintf((*ppsym)->tzName, MAXSYMLEN + 1, "%s", s) > MAXSYMLEN)
warning("Symbol name is too long: '%s'", s);
(*ppsym)->nValue = 0; (*ppsym)->nValue = 0;
(*ppsym)->nType = 0; (*ppsym)->nType = 0;
(*ppsym)->pScope = NULL; (*ppsym)->pScope = NULL;
@@ -130,7 +133,13 @@ struct sSymbol *createsymbol(char *s)
(*ppsym)->pMacro = NULL; (*ppsym)->pMacro = NULL;
(*ppsym)->pSection = NULL; (*ppsym)->pSection = NULL;
(*ppsym)->Callback = NULL; (*ppsym)->Callback = NULL;
strcpy((*ppsym)->tzFileName, tzCurrentFileName);
if (snprintf((*ppsym)->tzFileName, _MAX_PATH + 1, "%s",
tzCurrentFileName) > _MAX_PATH) {
fatalerror("%s: File name is too long: '%s'", __func__,
tzCurrentFileName);
}
(*ppsym)->nFileLine = fstk_GetLine(); (*ppsym)->nFileLine = fstk_GetLine();
return *ppsym; return *ppsym;
} }
@@ -509,7 +518,7 @@ void sym_SetMacroArgID(uint32_t nMacroCount)
{ {
char s[256]; char s[256];
sprintf(s, "_%u", nMacroCount); snprintf(s, sizeof(s), "_%u", nMacroCount);
newmacroargs[MAXMACROARGS] = strdup(s); newmacroargs[MAXMACROARGS] = strdup(s);
} }
@@ -560,7 +569,7 @@ void sym_AddEqu(char *tzSym, int32_t value)
* *
* If the desired symbol is a string it needs to be passed to this function with * If the desired symbol is a string it needs to be passed to this function with
* quotes inside the string, like sym_AddString("name", "\"test\"), or the * quotes inside the string, like sym_AddString("name", "\"test\"), or the
* assembler won't be able to use it with DB and similar. This is equivalent as * assembler won't be able to use it with DB and similar. This is equivalent to
* ``` name EQUS "\"test\"" ``` * ``` name EQUS "\"test\"" ```
* *
* If the desired symbol is a register or a number, just the terminator quotes * If the desired symbol is a register or a number, just the terminator quotes
@@ -665,7 +674,7 @@ void sym_AddReloc(char *tzSym)
struct sSymbol *parent = pScope->pScope ? struct sSymbol *parent = pScope->pScope ?
pScope->pScope : pScope; pScope->pScope : pScope;
int32_t parentLen = localPtr - tzSym; uint32_t parentLen = localPtr - tzSym;
if (strchr(localPtr + 1, '.') != NULL) { if (strchr(localPtr + 1, '.') != NULL) {
fatalerror("'%s' is a nonsensical reference to a nested local symbol", fatalerror("'%s' is a nonsensical reference to a nested local symbol",
@@ -965,16 +974,22 @@ void sym_Init(void)
* The '?' have to be escaped or they will be treated as * The '?' have to be escaped or they will be treated as
* trigraphs... * trigraphs...
*/ */
strcpy(SavedTIME, "\"\?\?:\?\?:\?\?\""); snprintf(SavedTIME, sizeof(SavedTIME),
strcpy(SavedDATE, "\"\?\? \?\?\? \?\?\?\?\""); "\"\?\?:\?\?:\?\?\"");
strcpy(SavedTIMESTAMP_ISO8601_LOCAL, "\"\?\?\?\?-\?\?-\?\?T\?\?:\?\?:\?\?+\?\?\?\?\""); snprintf(SavedDATE, sizeof(SavedDATE),
strcpy(SavedTIMESTAMP_ISO8601_UTC, "\"\?\?\?\?-\?\?-\?\?T\?\?:\?\?:\?\?Z\""); "\"\?\? \?\?\? \?\?\?\?\"");
strcpy(SavedDAY, "1"); snprintf(SavedTIMESTAMP_ISO8601_LOCAL,
strcpy(SavedMONTH, "1"); sizeof(SavedTIMESTAMP_ISO8601_LOCAL),
strcpy(SavedYEAR, "1900"); "\"\?\?\?\?-\?\?-\?\?T\?\?:\?\?:\?\?+\?\?\?\?\"");
strcpy(SavedHOUR, "0"); snprintf(SavedTIMESTAMP_ISO8601_UTC,
strcpy(SavedMINUTE, "0"); sizeof(SavedTIMESTAMP_ISO8601_UTC),
strcpy(SavedSECOND, "0"); "\"\?\?\?\?-\?\?-\?\?T\?\?:\?\?:\?\?Z\"");
snprintf(SavedDAY, sizeof(SavedDAY), "1");
snprintf(SavedMONTH, sizeof(SavedMONTH), "1");
snprintf(SavedYEAR, sizeof(SavedYEAR), "1900");
snprintf(SavedHOUR, sizeof(SavedHOUR), "0");
snprintf(SavedMINUTE, sizeof(SavedMINUTE), "0");
snprintf(SavedSECOND, sizeof(SavedSECOND), "0");
} }
sym_AddString("__TIME__", SavedTIME); sym_AddString("__TIME__", SavedTIME);

8
src/extern/err.c vendored
View File

@@ -33,7 +33,7 @@ void rgbds_vwarnx(const char *fmt, va_list ap)
putc('\n', stderr); putc('\n', stderr);
} }
noreturn void rgbds_verr(int status, const char *fmt, va_list ap) noreturn_ void rgbds_verr(int status, const char *fmt, va_list ap)
{ {
fprintf(stderr, "error"); fprintf(stderr, "error");
if (fmt) { if (fmt) {
@@ -44,7 +44,7 @@ noreturn void rgbds_verr(int status, const char *fmt, va_list ap)
exit(status); exit(status);
} }
noreturn void rgbds_verrx(int status, const char *fmt, va_list ap) noreturn_ void rgbds_verrx(int status, const char *fmt, va_list ap)
{ {
fprintf(stderr, "error"); fprintf(stderr, "error");
if (fmt) { if (fmt) {
@@ -73,7 +73,7 @@ void rgbds_warnx(const char *fmt, ...)
va_end(ap); va_end(ap);
} }
noreturn void rgbds_err(int status, const char *fmt, ...) noreturn_ void rgbds_err(int status, const char *fmt, ...)
{ {
va_list ap; va_list ap;
@@ -82,7 +82,7 @@ noreturn void rgbds_err(int status, const char *fmt, ...)
va_end(ap); va_end(ap);
} }
noreturn void rgbds_errx(int status, const char *fmt, ...) noreturn_ void rgbds_errx(int status, const char *fmt, ...)
{ {
va_list ap; va_list ap;

View File

@@ -20,9 +20,9 @@
static void print_usage(void) static void print_usage(void)
{ {
printf( printf(
"usage: rgbfix [-CcjsVv] [-i game_id] [-k licensee_str] [-l licensee_id]\n" "usage: rgbfix [-CcjsVv] [-f fix_spec] [-i game_id] [-k licensee_str]\n"
" [-m mbc_type] [-n rom_version] [-p pad_value] [-r ram_size]\n" " [-l licensee_id] [-m mbc_type] [-n rom_version] [-p pad_value]\n"
" [-t title_str] file\n"); " [-r ram_size] [-t title_str] file\n");
exit(1); exit(1);
} }
@@ -37,7 +37,12 @@ int main(int argc, char *argv[])
*/ */
/* all flags default to false unless options specify otherwise */ /* all flags default to false unless options specify otherwise */
bool validate = false; bool fixlogo = false;
bool fixheadsum = false;
bool fixglobalsum = false;
bool trashlogo = false;
bool trashheadsum = false;
bool trashglobalsum = false;
bool settitle = false; bool settitle = false;
bool setid = false; bool setid = false;
bool colorcompatible = false; bool colorcompatible = false;
@@ -61,7 +66,7 @@ int main(int argc, char *argv[])
int version = 0; /* mask ROM version number */ int version = 0; /* mask ROM version number */
int padvalue = 0; /* to pad the rom with if it changes size */ int padvalue = 0; /* to pad the rom with if it changes size */
while ((ch = getopt(argc, argv, "Cci:jk:l:m:n:p:sr:t:Vv")) != -1) { while ((ch = getopt(argc, argv, "Ccf:i:jk:l:m:n:p:sr:t:Vv")) != -1) {
switch (ch) { switch (ch) {
case 'C': case 'C':
coloronly = true; coloronly = true;
@@ -69,6 +74,14 @@ int main(int argc, char *argv[])
case 'c': case 'c':
colorcompatible = true; colorcompatible = true;
break; break;
case 'f':
fixlogo = strchr(optarg, 'l');
fixheadsum = strchr(optarg, 'h');
fixglobalsum = strchr(optarg, 'g');
trashlogo = strchr(optarg, 'L');
trashheadsum = strchr(optarg, 'H');
trashglobalsum = strchr(optarg, 'G');
break;
case 'i': case 'i':
setid = true; setid = true;
@@ -168,7 +181,9 @@ int main(int argc, char *argv[])
printf("rgbfix %s\n", get_package_version_string()); printf("rgbfix %s\n", get_package_version_string());
exit(0); exit(0);
case 'v': case 'v':
validate = true; fixlogo = true;
fixheadsum = true;
fixglobalsum = true;
break; break;
default: default:
print_usage(); print_usage();
@@ -192,10 +207,20 @@ int main(int argc, char *argv[])
err(1, "Error opening file %s", argv[argc - 1]); err(1, "Error opening file %s", argv[argc - 1]);
/* /*
* Write changes to ROM * Read ROM header
*
* Offsets in the buffer are 0x100 less than the equivalent in ROM.
*/ */
if (validate) { uint8_t header[0x50];
if (fseek(rom, 0x100, SEEK_SET) != 0)
err(1, "Could not locate ROM header");
if (fread(header, sizeof(uint8_t), sizeof(header), rom)
!= sizeof(header))
err(1, "Could not read ROM header");
if (fixlogo || trashlogo) {
/* /*
* Offset 0x1040x133: Nintendo Logo * Offset 0x1040x133: Nintendo Logo
* This is a bitmap image that displays when the Game Boy is * This is a bitmap image that displays when the Game Boy is
@@ -205,7 +230,7 @@ int main(int argc, char *argv[])
/* /*
* See also: global checksums at 0x14D0x14F, They must * See also: global checksums at 0x14D0x14F, They must
* also be correct for the game to boot, so we fix them * also be correct for the game to boot, so we fix them
* as well when the -v flag is set. * as well when requested with the -f flag.
*/ */
uint8_t ninlogo[48] = { uint8_t ninlogo[48] = {
@@ -217,8 +242,12 @@ int main(int argc, char *argv[])
0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E
}; };
fseek(rom, 0x104, SEEK_SET); if (trashlogo) {
fwrite(ninlogo, 1, 48, rom); for (int i = 0; i < sizeof(ninlogo); i++)
ninlogo[i] = ~ninlogo[i];
}
memcpy(header + 0x04, ninlogo, sizeof(ninlogo));
} }
if (settitle) { if (settitle) {
@@ -239,11 +268,10 @@ int main(int argc, char *argv[])
* characters may conflict with the title. * characters may conflict with the title.
*/ */
fseek(rom, 0x134, SEEK_SET); int n = snprintf((char *)header + 0x34, 16, "%s", title);
fwrite(title, 1, strlen(title) + 1, rom);
while (ftell(rom) < 0x143) for (int i = 16; i > n; i--)
fputc(0, rom); header[0x34 + i] = '\0';
} }
if (setid) { if (setid) {
@@ -253,8 +281,7 @@ int main(int argc, char *argv[])
* characters). * characters).
*/ */
fseek(rom, 0x13F, SEEK_SET); memcpy(header + 0x3F, id, 4);
fwrite(id, 1, 4, rom);
} }
if (colorcompatible) { if (colorcompatible) {
@@ -272,20 +299,12 @@ int main(int argc, char *argv[])
* may conflict. * may conflict.
*/ */
uint8_t byte; header[0x43] |= 1 << 7;
fseek(rom, 0x143, SEEK_SET);
byte = fgetc(rom);
byte |= 1 << 7;
if (coloronly) if (coloronly)
byte |= 1 << 6; header[0x43] |= 1 << 6;
if (byte & 0x3F) if (header[0x43] & 0x3F)
warnx("Color flag conflicts with game title"); warnx("Color flag conflicts with game title");
fseek(rom, 0x143, SEEK_SET);
fputc(byte, rom);
} }
if (setnewlicensee) { if (setnewlicensee) {
@@ -301,8 +320,8 @@ int main(int argc, char *argv[])
* as a Super Game Boy flag. * as a Super Game Boy flag.
*/ */
fseek(rom, 0x144, SEEK_SET); header[0x44] = newlicensee[0];
fwrite(newlicensee, 1, 2, rom); header[0x45] = newlicensee[1];
} }
if (super) { if (super) {
@@ -321,8 +340,7 @@ int main(int argc, char *argv[])
if (!setlicensee) if (!setlicensee)
warnx("You should probably set both '-s' and '-l 0x33'"); warnx("You should probably set both '-s' and '-l 0x33'");
fseek(rom, 0x146, SEEK_SET); header[0x46] = 3;
fputc(3, rom);
} }
if (setcartridge) { if (setcartridge) {
@@ -332,8 +350,7 @@ int main(int argc, char *argv[])
* external RAM, timer, rumble, or battery. * external RAM, timer, rumble, or battery.
*/ */
fseek(rom, 0x147, SEEK_SET); header[0x47] = cartridge;
fputc(cartridge, rom);
} }
if (resize) { if (resize) {
@@ -347,8 +364,13 @@ int main(int argc, char *argv[])
int headbyte; int headbyte;
uint8_t *buf; uint8_t *buf;
fseek(rom, 0, SEEK_END); if (fseek(rom, 0, SEEK_END) != 0)
err(1, "Could not pad ROM file");
romsize = ftell(rom); romsize = ftell(rom);
if (romsize == -1)
err(1, "Could not pad ROM file");
newsize = 0x8000; newsize = 0x8000;
headbyte = 0; headbyte = 0;
@@ -361,11 +383,14 @@ int main(int argc, char *argv[])
warnx("ROM size is bigger than 8MiB"); warnx("ROM size is bigger than 8MiB");
buf = malloc(newsize - romsize); buf = malloc(newsize - romsize);
memset(buf, padvalue, newsize - romsize); if (buf == NULL)
fwrite(buf, 1, newsize - romsize, rom); errx(1, "Couldn't allocate memory for padded ROM.");
fseek(rom, 0x148, SEEK_SET); memset(buf, padvalue, newsize - romsize);
fputc(headbyte, rom); if (fwrite(buf, 1, newsize - romsize, rom) != newsize - romsize)
err(1, "Could not pad ROM file");
header[0x48] = headbyte;
free(buf); free(buf);
} }
@@ -375,8 +400,7 @@ int main(int argc, char *argv[])
* Offset 0x149: RAM Size * Offset 0x149: RAM Size
*/ */
fseek(rom, 0x149, SEEK_SET); header[0x49] = ramsize;
fputc(ramsize, rom);
} }
if (nonjapan) { if (nonjapan) {
@@ -384,8 +408,7 @@ int main(int argc, char *argv[])
* Offset 0x14A: Non-Japanese Region Flag * Offset 0x14A: Non-Japanese Region Flag
*/ */
fseek(rom, 0x14A, SEEK_SET); header[0x4A] = 1;
fputc(1, rom);
} }
if (setlicensee) { if (setlicensee) {
@@ -401,8 +424,7 @@ int main(int argc, char *argv[])
* See also: the New Licensee ID at 0x1440x145. * See also: the New Licensee ID at 0x1440x145.
*/ */
fseek(rom, 0x14B, SEEK_SET); header[0x4B] = licensee;
fputc(licensee, rom);
} }
if (setversion) { if (setversion) {
@@ -411,46 +433,71 @@ int main(int argc, char *argv[])
* Which version of the ROM this is. * Which version of the ROM this is.
*/ */
fseek(rom, 0x14C, SEEK_SET); header[0x4C] = version;
fputc(version, rom);
} }
if (validate) { if (fixheadsum || trashheadsum) {
/* /*
* Offset 0x14D: Header Checksum * Offset 0x14D: Header Checksum
*/ */
uint8_t headcksum = 0; uint8_t headcksum = 0;
fseek(rom, 0x134, SEEK_SET); for (int i = 0x34; i < 0x4D; ++i)
for (int i = 0; i < (0x14D - 0x134); ++i) headcksum = headcksum - header[i] - 1;
headcksum = headcksum - fgetc(rom) - 1;
fseek(rom, 0x14D, SEEK_SET); if (trashheadsum)
fputc(headcksum, rom); headcksum = ~headcksum;
header[0x4D] = headcksum;
}
/*
* Before calculating the global checksum, we must write the modified
* header to the ROM.
*/
if (fseek(rom, 0x100, SEEK_SET) != 0)
err(1, "Could not locate header for writing");
if (fwrite(header, sizeof(uint8_t), sizeof(header), rom)
!= sizeof(header))
err(1, "Could not write modified ROM header");
if (fixglobalsum || trashglobalsum) {
/* /*
* Offset 0x14E0x14F: Global Checksum * Offset 0x14E0x14F: Global Checksum
*/ */
uint16_t globalcksum = 0; uint16_t globalcksum = 0;
rewind(rom); if (fseek(rom, 0, SEEK_SET) != 0)
for (int i = 0; i < 0x14E; ++i) err(1, "Could not start calculating global checksum");
globalcksum += fgetc(rom);
int i = 0;
int byte; int byte;
fseek(rom, 0x150, SEEK_SET); while ((byte = fgetc(rom)) != EOF) {
while ((byte = fgetc(rom)) != EOF) i++;
globalcksum += byte; if (i != 0x150)
globalcksum += byte;
}
if (ferror(rom))
err(1, "Could not calculate global checksum");
if (trashglobalsum)
globalcksum = ~globalcksum;
fseek(rom, 0x14E, SEEK_SET); fseek(rom, 0x14E, SEEK_SET);
fputc(globalcksum >> 8, rom); fputc(globalcksum >> 8, rom);
fputc(globalcksum & 0xFF, rom); fputc(globalcksum & 0xFF, rom);
if (ferror(rom))
err(1, "Could not write global checksum");
} }
fclose(rom); if (fclose(rom) != 0)
err(1, "Could not complete ROM write");
return 0; return 0;
} }

View File

@@ -5,7 +5,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd January 26, 2018 .Dd March 11, 2018
.Dt RGBFIX 1 .Dt RGBFIX 1
.Os RGBDS Manual .Os RGBDS Manual
.Sh NAME .Sh NAME
@@ -14,6 +14,7 @@
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm rgbfix .Nm rgbfix
.Op Fl CcjsVv .Op Fl CcjsVv
.Op Fl f Ar fix_spec
.Op Fl i Ar game_id .Op Fl i Ar game_id
.Op Fl k Ar licensee_str .Op Fl k Ar licensee_str
.Op Fl l Ar licensee_id .Op Fl l Ar licensee_id
@@ -46,6 +47,30 @@ If both this and the
flag are set, flag are set,
.Fl C .Fl C
takes precedence. takes precedence.
.It Fl f Ar fix_spec
Fix certain header values that the Game Boy checks for correctness.
Alternatively, intentionally trash these values by writing their binary inverse
instead.
.Ar fix_spec
is a string containing any combination of the following characters:
.Pp
.Bl -tag -compact -width xx
.It Cm l
Fix the Nintendo logo
.Pq Ad 0x104 Ns \(en Ns Ad 0x133 .
.It Cm L
Trash the Nintendo logo.
.It Cm h
Fix the header checksum
.Pq Ad 0x14D .
.It Cm H
Trash the header checksum.
.It Cm g
Fix the global checksum
.Pq Ad 0x14E Ns \(en Ns Ad 0x14F .
.It Cm G
Trash the global checksum.
.El
.It Fl i Ar game_id .It Fl i Ar game_id
Set the game ID string Set the game ID string
.Pq Ad 0x13F Ns \(en Ns Ad 0x142 .Pq Ad 0x13F Ns \(en Ns Ad 0x142
@@ -104,12 +129,8 @@ overlapping portion of the title.
.It Fl V .It Fl V
Print the version of the program and exit. Print the version of the program and exit.
.It Fl v .It Fl v
Validate the header and fix checksums: the Nintendo character area Equivalent to
.Pq Ad 0x104 Ns \(en Ns Ad 0x133 , .Fl f Cm lhg .
the header checksum
.Pq Ad 0x14D ,
and the global checksum
.Pq Ad 0x14E Ns \(en Ns Ad 0x14F .
.El .El
.Sh EXAMPLES .Sh EXAMPLES
Most values in the ROM header are only cosmetic. Most values in the ROM header are only cosmetic.

View File

@@ -5,7 +5,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd January 26, 2018 .Dd February 23, 2018
.Dt GBZ80 7 .Dt GBZ80 7
.Os RGBDS Manual .Os RGBDS Manual
.Sh NAME .Sh NAME
@@ -24,8 +24,10 @@ as destination can omit the destination as it is assumed it's register
.Sy A . .Sy A .
The following two lines have the same effect: The following two lines have the same effect:
.Pp .Pp
.Dl OR A,B .Bd -literal -offset indent
.Dl OR B OR A,B
OR B
.Ed
.Pp .Pp
.Sh LEGEND .Sh LEGEND
List of abbreviations used in this document. List of abbreviations used in this document.

View File

@@ -6,6 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -31,23 +32,18 @@ void transpose_tiles(struct GBImage *gb, int width)
gb->data = newdata; gb->data = newdata;
} }
void png_to_gb(const struct PNGImage png, struct GBImage *gb) void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb)
{ {
int x, y, byte; int x, y, byte;
png_byte index; uint8_t index;
for (y = 0; y < png.height; y++) { for (y = 0; y < raw_image->height; y++) {
for (x = 0; x < png.width; x++) { for (x = 0; x < raw_image->width; x++) {
index = png.data[y][x]; index = raw_image->data[y][x];
index &= (1 << depth) - 1; index &= (1 << depth) - 1;
if (!gb->horizontal) { byte = y * depth
byte = y * depth + x / 8 * raw_image->height / 8 * 8 * depth;
+ x / 8 * png.height / 8 * 8 * depth;
} else {
byte = y * depth
+ x / 8 * png.height / 8 * 8 * depth;
}
gb->data[byte] |= (index & 1) << (7 - x % 8); gb->data[byte] |= (index & 1) << (7 - x % 8);
if (depth == 2) { if (depth == 2) {
gb->data[byte + 1] |= gb->data[byte + 1] |=
@@ -57,18 +53,18 @@ void png_to_gb(const struct PNGImage png, struct GBImage *gb)
} }
if (!gb->horizontal) if (!gb->horizontal)
transpose_tiles(gb, png.width / 8); transpose_tiles(gb, raw_image->width / 8);
} }
void output_file(const struct Options opts, const struct GBImage gb) void output_file(const struct Options *opts, const struct GBImage *gb)
{ {
FILE *f; FILE *f;
f = fopen(opts.outfile, "wb"); f = fopen(opts->outfile, "wb");
if (!f) if (!f)
err(1, "Opening output file '%s' failed", opts.outfile); err(1, "Opening output file '%s' failed", opts->outfile);
fwrite(gb.data, 1, gb.size - gb.trim * 8 * depth, f); fwrite(gb->data, 1, gb->size - gb->trim * 8 * depth, f);
fclose(f); fclose(f);
} }
@@ -89,7 +85,7 @@ int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles, int tile_size)
return -1; return -1;
} }
void create_tilemap(const struct Options opts, struct GBImage *gb, void create_tilemap(const struct Options *opts, struct GBImage *gb,
struct Tilemap *tilemap) struct Tilemap *tilemap)
{ {
int i, j; int i, j;
@@ -105,10 +101,15 @@ void create_tilemap(const struct Options opts, struct GBImage *gb,
tile_size = sizeof(uint8_t) * depth * 8; tile_size = sizeof(uint8_t) * depth * 8;
gb_size = gb->size - (gb->trim * tile_size); gb_size = gb->size - (gb->trim * tile_size);
max_tiles = gb_size / tile_size; max_tiles = gb_size / tile_size;
tiles = malloc(sizeof(uint8_t *) * max_tiles);
/* 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(uint8_t *));
num_tiles = 0; num_tiles = 0;
tilemap->data = malloc(sizeof(uint8_t) * max_tiles); tilemap->data = calloc(max_tiles, sizeof(uint8_t));
tilemap->size = 0; tilemap->size = 0;
gb_i = 0; gb_i = 0;
@@ -118,7 +119,7 @@ void create_tilemap(const struct Options opts, struct GBImage *gb,
tile[i] = gb->data[gb_i]; tile[i] = gb->data[gb_i];
gb_i++; gb_i++;
} }
if (opts.unique) { if (opts->unique) {
index = get_tile_index(tile, tiles, num_tiles, index = get_tile_index(tile, tiles, num_tiles,
tile_size); tile_size);
if (index < 0) { if (index < 0) {
@@ -135,7 +136,7 @@ void create_tilemap(const struct Options opts, struct GBImage *gb,
tilemap->size++; tilemap->size++;
} }
if (opts.unique) { if (opts->unique) {
free(gb->data); free(gb->data);
gb->data = malloc(tile_size * num_tiles); gb->data = malloc(tile_size * num_tiles);
for (i = 0; i < num_tiles; i++) { for (i = 0; i < num_tiles; i++) {
@@ -152,43 +153,44 @@ void create_tilemap(const struct Options opts, struct GBImage *gb,
free(tiles); free(tiles);
} }
void output_tilemap_file(const struct Options opts, void output_tilemap_file(const struct Options *opts,
const struct Tilemap tilemap) const struct Tilemap *tilemap)
{ {
FILE *f; FILE *f;
f = fopen(opts.mapfile, "wb"); f = fopen(opts->mapfile, "wb");
if (!f) if (!f)
err(1, "Opening tilemap file '%s' failed", opts.mapfile); err(1, "Opening tilemap file '%s' failed", opts->mapfile);
fwrite(tilemap.data, 1, tilemap.size, f); fwrite(tilemap->data, 1, tilemap->size, f);
fclose(f); fclose(f);
if (opts.mapout) if (opts->mapout)
free(opts.mapfile); free(opts->mapfile);
} }
void output_palette_file(const struct Options opts, const struct PNGImage png) void output_palette_file(const struct Options *opts,
const struct RawIndexedImage *raw_image)
{ {
FILE *f; FILE *f;
int i, colors, color; int i, color;
png_color *palette; uint8_t cur_bytes[2];
if (png_get_PLTE(png.png, png.info, &palette, &colors)) { f = fopen(opts->palfile, "wb");
f = fopen(opts.palfile, "wb"); if (!f)
if (!f) { err(1, "Opening palette file '%s' failed", opts->palfile);
err(1, "Opening palette file '%s' failed",
opts.palfile); for (i = 0; i < raw_image->num_colors; i++) {
} color =
for (i = 0; i < colors; i++) { raw_image->palette[i].blue >> 3 << 10 |
color = palette[i].blue >> 3 << 10 raw_image->palette[i].green >> 3 << 5 |
| palette[i].green >> 3 << 5 raw_image->palette[i].red >> 3;
| palette[i].red >> 3; cur_bytes[0] = color & 0xFF;
fwrite(&color, 2, 1, f); cur_bytes[1] = color >> 8;
} fwrite(cur_bytes, 2, 1, f);
fclose(f);
} }
fclose(f);
if (opts.palout) if (opts->palout)
free(opts.palfile); free(opts->palfile);
} }

View File

@@ -6,6 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include <png.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
@@ -26,7 +27,8 @@ int main(int argc, char *argv[])
{ {
int ch, size; int ch, size;
struct Options opts = {0}; struct Options opts = {0};
struct PNGImage png = {0}; struct ImageOptions png_options = {0};
struct RawIndexedImage *raw_image;
struct GBImage gb = {0}; struct GBImage gb = {0};
struct Tilemap tilemap = {0}; struct Tilemap tilemap = {0};
char *ext; char *ext;
@@ -51,6 +53,7 @@ int main(int argc, char *argv[])
break; break;
case 'F': case 'F':
opts.hardfix = true; opts.hardfix = true;
/* fallthrough */
case 'f': case 'f':
opts.fix = true; opts.fix = true;
break; break;
@@ -102,80 +105,89 @@ int main(int argc, char *argv[])
colors = 1 << depth; colors = 1 << depth;
input_png_file(opts, &png); raw_image = input_png_file(&opts, &png_options);
png.mapfile = ""; png_options.mapfile = "";
png.palfile = ""; png_options.palfile = "";
get_text(&png); if (png_options.horizontal != opts.horizontal) {
if (png.horizontal != opts.horizontal) {
if (opts.verbose) if (opts.verbose)
warnx(errmsg, "horizontal"); warnx(errmsg, "horizontal");
if (opts.hardfix) if (opts.hardfix)
png.horizontal = opts.horizontal; png_options.horizontal = opts.horizontal;
} }
if (png.horizontal) if (png_options.horizontal)
opts.horizontal = png.horizontal; opts.horizontal = png_options.horizontal;
if (png.trim != opts.trim) { if (png_options.trim != opts.trim) {
if (opts.verbose) if (opts.verbose)
warnx(errmsg, "trim"); warnx(errmsg, "trim");
if (opts.hardfix) if (opts.hardfix)
png.trim = opts.trim; png_options.trim = opts.trim;
} }
if (png.trim) if (png_options.trim)
opts.trim = png.trim; opts.trim = png_options.trim;
if (opts.trim > png.width / 8 - 1) { if (raw_image->width % 8) {
errx(1, "Trim (%i) for input png file '%s' too large (max: %i)", errx(1, "Input PNG file %s not sized correctly. The image's width must be a multiple of 8.",
opts.trim, opts.infile, png.width / 8 - 1); opts.infile);
}
if (raw_image->width / 8 > 1 && raw_image->height % 8) {
errx(1, "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 (strcmp(png.mapfile, opts.mapfile) != 0) { if (opts.trim &&
opts.trim > (raw_image->width / 8) * (raw_image->height / 8) - 1) {
errx(1, "Trim (%i) for input raw_image file '%s' too large (max: %i)",
opts.trim, opts.infile,
(raw_image->width / 8) * (raw_image->height / 8) - 1);
}
if (strcmp(png_options.mapfile, opts.mapfile) != 0) {
if (opts.verbose) if (opts.verbose)
warnx(errmsg, "tilemap file"); warnx(errmsg, "tilemap file");
if (opts.hardfix) if (opts.hardfix)
png.mapfile = opts.mapfile; png_options.mapfile = opts.mapfile;
} }
if (!*opts.mapfile) if (!*opts.mapfile)
opts.mapfile = png.mapfile; opts.mapfile = png_options.mapfile;
if (png.mapout != opts.mapout) { if (png_options.mapout != opts.mapout) {
if (opts.verbose) if (opts.verbose)
warnx(errmsg, "tilemap file"); warnx(errmsg, "tilemap file");
if (opts.hardfix) if (opts.hardfix)
png.mapout = opts.mapout; png_options.mapout = opts.mapout;
} }
if (png.mapout) if (png_options.mapout)
opts.mapout = png.mapout; opts.mapout = png_options.mapout;
if (strcmp(png.palfile, opts.palfile) != 0) { if (strcmp(png_options.palfile, opts.palfile) != 0) {
if (opts.verbose) if (opts.verbose)
warnx(errmsg, "palette file"); warnx(errmsg, "palette file");
if (opts.hardfix) if (opts.hardfix)
png.palfile = opts.palfile; png_options.palfile = opts.palfile;
} }
if (!*opts.palfile) if (!*opts.palfile)
opts.palfile = png.palfile; opts.palfile = png_options.palfile;
if (png.palout != opts.palout) { if (png_options.palout != opts.palout) {
if (opts.verbose) if (opts.verbose)
warnx(errmsg, "palette file"); warnx(errmsg, "palette file");
if (opts.hardfix) if (opts.hardfix)
png.palout = opts.palout; png_options.palout = opts.palout;
} }
if (png.palout) if (png_options.palout)
opts.palout = png.palout; opts.palout = png_options.palout;
if (!*opts.mapfile && opts.mapout) { if (!*opts.mapfile && opts.mapout) {
ext = strrchr(opts.infile, '.'); ext = strrchr(opts.infile, '.');
@@ -209,31 +221,29 @@ int main(int argc, char *argv[])
} }
} }
gb.size = png.width * png.height * depth / 8; gb.size = raw_image->width * raw_image->height * depth / 8;
gb.data = calloc(gb.size, 1); gb.data = calloc(gb.size, 1);
gb.trim = opts.trim; gb.trim = opts.trim;
gb.horizontal = opts.horizontal; gb.horizontal = opts.horizontal;
if (*opts.outfile || *opts.mapfile) { if (*opts.outfile || *opts.mapfile) {
png_to_gb(png, &gb); raw_to_gb(raw_image, &gb);
create_tilemap(opts, &gb, &tilemap); create_tilemap(&opts, &gb, &tilemap);
} }
if (*opts.outfile) if (*opts.outfile)
output_file(opts, gb); output_file(&opts, &gb);
if (*opts.mapfile) if (*opts.mapfile)
output_tilemap_file(opts, tilemap); output_tilemap_file(&opts, &tilemap);
if (*opts.palfile) if (*opts.palfile)
output_palette_file(opts, png); output_palette_file(&opts, raw_image);
if (opts.fix || opts.debug) { if (opts.fix || opts.debug)
set_text(&png); output_png_file(&opts, &png_options, raw_image);
output_png_file(opts, &png);
}
free_png_data(&png); destroy_raw_image(&raw_image);
free(gb.data); free(gb.data);
return 0; return 0;

View File

@@ -6,25 +6,150 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include <png.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "gfx/main.h" #include "gfx/main.h"
void input_png_file(const struct Options opts, struct PNGImage *img) 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(1, "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 %i (is %i).",
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(1, "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; FILE *f;
int i, y, num_trans; char *outfile;
bool has_palette = false; struct PNGImage img;
png_byte *trans_alpha; png_color *png_palette;
png_color_16 *trans_values; int i;
bool *full_alpha;
png_color *palette;
f = fopen(opts.infile, "rb"); /*
* TODO: Variable outfile is for debugging purposes. Eventually,
* opts.infile will be used directly.
*/
if (opts->debug) {
outfile = malloc(strlen(opts->infile) + 5);
strcpy(outfile, opts->infile);
strcat(outfile, ".out");
} else {
outfile = opts->infile;
}
f = fopen(outfile, "wb");
if (!f) if (!f)
err(1, "Opening input png file '%s' failed", opts.infile); err(1, "Opening output png file '%s' failed", outfile);
img.png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (!img.png)
errx(1, "Creating png structure failed");
img.info = png_create_info_struct(img.png);
if (!img.info)
errx(1, "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_color *) * raw_image->num_colors);
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);
if (opts->debug)
free(outfile);
}
void destroy_raw_image(struct RawIndexedImage **raw_image_ptr_ptr)
{
int y;
struct RawIndexedImage *raw_image = *raw_image_ptr_ptr;
for (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, img->png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL); NULL, NULL, NULL);
if (!img->png) if (!img->png)
@@ -34,7 +159,6 @@ void input_png_file(const struct Options opts, struct PNGImage *img)
if (!img->info) if (!img->info)
errx(1, "Creating png info structure failed"); errx(1, "Creating png info structure failed");
/* TODO: Better error handling here? */
if (setjmp(png_jmpbuf(img->png))) if (setjmp(png_jmpbuf(img->png)))
exit(1); exit(1);
@@ -46,125 +170,415 @@ void input_png_file(const struct Options opts, struct PNGImage *img)
img->height = png_get_image_height(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->depth = png_get_bit_depth(img->png, img->info);
img->type = png_get_color_type(img->png, img->info); img->type = png_get_color_type(img->png, img->info);
}
if (img->type & PNG_COLOR_MASK_ALPHA) static void read_png(struct PNGImage *img);
png_set_strip_alpha(img->png); static struct RawIndexedImage *create_raw_image(int width, int height,
int num_colors);
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
const png_color *palette, int num_colors);
if (img->depth != depth) { static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img)
if (opts.verbose) { {
warnx("Image bit depth is not %i (is %i).", depth, struct RawIndexedImage *raw_image;
img->depth); 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->type == PNG_COLOR_TYPE_GRAY) { if (img->depth < 8)
if (img->depth < 8) png_set_packing(img->png);
png_set_expand_gray_1_2_4_to_8(img->png);
png_set_gray_to_rgb(img->png); png_get_PLTE(img->png, img->info, &palette, &colors_in_PLTE);
} else {
if (img->depth < 8)
png_set_expand_gray_1_2_4_to_8(img->png);
has_palette = png_get_PLTE(img->png, img->info, &palette, raw_image = create_raw_image(img->width, img->height, colors);
&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, if (png_get_tRNS(img->png, img->info, &trans_alpha, &num_trans,
&trans_values)) { &trans_color)) {
if (img->type == PNG_COLOR_TYPE_PALETTE) { original_palette = palette;
full_alpha = malloc(sizeof(bool) * num_trans); palette = malloc(sizeof(png_color) * colors_in_PLTE);
colors_in_new_palette = 0;
old_to_new_palette = malloc(sizeof(uint8_t) * colors_in_PLTE);
for (i = 0; i < num_trans; i++) { for (i = 0; i < num_trans; i++) {
if (trans_alpha[i] > 0) if (trans_alpha[i] == 0) {
full_alpha[i] = false; old_to_new_palette[i] = 0;
else } else {
full_alpha[i] = true; old_to_new_palette[i] = colors_in_new_palette;
palette[colors_in_new_palette++] =
original_palette[i];
} }
}
for (i = 0; i < num_trans; i++) { for (i = num_trans; i < colors_in_PLTE; i++) {
if (full_alpha[i]) { old_to_new_palette[i] = colors_in_new_palette;
palette[i].red = 0xFF; palette[colors_in_new_palette++] = original_palette[i];
palette[i].green = 0x00;
palette[i].blue = 0xFF;
/*
* Set to the lightest color in the
* palette.
*/
}
}
free(full_alpha);
} else {
/* Set to the lightest color in the image. */
} }
png_free_data(img->png, img->info, PNG_FREE_TRNS, -1); if (colors_in_new_palette != colors_in_PLTE) {
} palette = realloc(palette,
sizeof(png_color) *
colors_in_new_palette);
}
if (has_palette) {
/* Make sure palette only has the amount of colors you want. */
} else {
/* /*
* Eventually when this copies colors from the image itself, * Setting and validating palette before reading
* make sure order is lightest to darkest. * allows us to error out *before* doing the data
* transformation if the palette is too long.
*/ */
palette = malloc(sizeof(png_color) * colors); set_raw_image_palette(raw_image, palette,
colors_in_new_palette);
read_png(img);
if (strcmp(opts.infile, "rgb.png") == 0) { for (y = 0; y < img->height; y++) {
palette[0].red = 0xFF; for (x = 0; x < img->width; x++) {
palette[0].green = 0xEF; raw_image->data[y][x] =
palette[0].blue = 0xFF; old_to_new_palette[img->data[y][x]];
}
}
palette[1].red = 0xF7; free(old_to_new_palette);
palette[1].green = 0xF7; } else {
palette[1].blue = 0x8C; set_raw_image_palette(raw_image, palette, colors_in_PLTE);
read_png(img);
palette[2].red = 0x94; for (y = 0; y < img->height; y++) {
palette[2].green = 0x94; for (x = 0; x < img->width; x++)
palette[2].blue = 0xC6; raw_image->data[y][x] = img->data[y][x];
palette[3].red = 0x39;
palette[3].green = 0x39;
palette[3].blue = 0x84;
} else {
palette[0].red = 0xFF;
palette[0].green = 0xFF;
palette[0].blue = 0xFF;
palette[1].red = 0xA9;
palette[1].green = 0xA9;
palette[1].blue = 0xA9;
palette[2].red = 0x55;
palette[2].green = 0x55;
palette[2].blue = 0x55;
palette[3].red = 0x00;
palette[3].green = 0x00;
palette[3].blue = 0x00;
} }
} }
/* return raw_image;
* Also unfortunately, this sets it at 8 bit, and I can't find any }
* option to reduce to 2 or 1 bit.
*/
#if PNG_LIBPNG_VER < 10402
png_set_dither(img->png, palette, colors, colors, NULL, 1);
#else
png_set_quantize(img->png, palette, colors, colors, NULL, 1);
#endif
if (!has_palette) { static struct RawIndexedImage *grayscale_png_to_raw(struct PNGImage *img)
png_set_PLTE(img->png, img->info, palette, colors); {
free(palette); 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,
const png_color *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);
/* /*
* If other useless chunks exist (sRGB, bKGD, pHYs, gAMA, cHRM, iCCP, * Lets us free the palette manually instead of leaving it to libpng,
* etc.) offer to remove? * 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,
const png_color *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(png_color));
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,
const png_color *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) {
err(1, "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(png_color) * colors);
bool *set_indices = calloc(colors, sizeof(bool));
int i, shade_index;
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(const void *a, const void *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(struct ColorWithLuminance) * num_colors);
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(struct ColorWithLuminance), 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,
const png_color *palette,
int colors_in_palette);
static struct RawIndexedImage
*processed_rgba_png_to_raw(const struct PNGImage *img,
const png_color *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(const png_color *palette,
int num_colors, const png_color *color);
static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
const struct PNGImage *img,
int *value_index, int x, int y,
const png_color *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(const png_color *palette,
int num_colors, const png_color *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(1, "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); png_read_update_info(img->png, img->info);
@@ -174,35 +588,78 @@ void input_png_file(const struct Options opts, struct PNGImage *img)
png_read_image(img->png, img->data); png_read_image(img->png, img->data);
png_read_end(img->png, img->info); png_read_end(img->png, img->info);
fclose(f);
} }
void get_text(struct PNGImage *png) static struct RawIndexedImage *create_raw_image(int width, int height,
int num_colors)
{
struct RawIndexedImage *raw_image;
int y;
raw_image = malloc(sizeof(struct RawIndexedImage));
raw_image->width = width;
raw_image->height = height;
raw_image->num_colors = num_colors;
raw_image->palette = malloc(sizeof(struct RGBColor) * num_colors);
raw_image->data = malloc(sizeof(uint8_t *) * height);
for (y = 0; y < height; y++)
raw_image->data[y] = malloc(sizeof(uint8_t) * width);
return raw_image;
}
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
const png_color *palette, int num_colors)
{
int i;
if (num_colors > raw_image->num_colors) {
errx(1, "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; png_text *text;
int i, numtxts, numremoved; int i, numtxts, numremoved;
png_get_text(png->png, png->info, &text, &numtxts); png_get_text(img->png, img->info, &text, &numtxts);
for (i = 0; i < numtxts; i++) { for (i = 0; i < numtxts; i++) {
if (strcmp(text[i].key, "h") == 0 && !*text[i].text) { if (strcmp(text[i].key, "h") == 0 && !*text[i].text) {
png->horizontal = true; png_options->horizontal = true;
png_free_data(png->png, png->info, PNG_FREE_TEXT, i); png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "x") == 0) { } else if (strcmp(text[i].key, "x") == 0) {
png->trim = strtoul(text[i].text, NULL, 0); png_options->trim = strtoul(text[i].text, NULL, 0);
png_free_data(png->png, png->info, PNG_FREE_TEXT, i); png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "t") == 0) { } else if (strcmp(text[i].key, "t") == 0) {
png->mapfile = text[i].text; png_options->mapfile = text[i].text;
png_free_data(png->png, png->info, PNG_FREE_TEXT, i); png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "T") == 0 && !*text[i].text) { } else if (strcmp(text[i].key, "T") == 0 && !*text[i].text) {
png->mapout = true; png_options->mapout = true;
png_free_data(png->png, png->info, PNG_FREE_TEXT, i); png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "p") == 0) { } else if (strcmp(text[i].key, "p") == 0) {
png->palfile = text[i].text; png_options->palfile = text[i].text;
png_free_data(png->png, png->info, PNG_FREE_TEXT, i); png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "P") == 0 && !*text[i].text) { } else if (strcmp(text[i].key, "P") == 0 && !*text[i].text) {
png->palout = true; png_options->palout = true;
png_free_data(png->png, png->info, PNG_FREE_TEXT, i); png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} }
} }
@@ -218,106 +675,64 @@ void get_text(struct PNGImage *png)
text[i].text = text[i + numremoved].text; text[i].text = text[i + numremoved].text;
text[i].compression = text[i + numremoved].compression; text[i].compression = text[i + numremoved].compression;
} }
png_set_text(png->png, png->info, text, numtxts - numremoved); png_set_text(img->png, img->info, text, numtxts - numremoved);
} }
void set_text(const struct PNGImage *png) static void set_text(const struct PNGImage *img,
const struct ImageOptions *png_options)
{ {
png_text *text; png_text *text;
char buffer[3]; char buffer[3];
text = malloc(sizeof(png_text)); text = malloc(sizeof(png_text));
if (png->horizontal) { if (png_options->horizontal) {
text[0].key = "h"; text[0].key = "h";
text[0].text = ""; text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE; text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1); png_set_text(img->png, img->info, text, 1);
} }
if (png->trim) { if (png_options->trim) {
text[0].key = "x"; text[0].key = "x";
snprintf(buffer, 3, "%d", png->trim); snprintf(buffer, 3, "%d", png_options->trim);
text[0].text = buffer; text[0].text = buffer;
text[0].compression = PNG_TEXT_COMPRESSION_NONE; text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1); png_set_text(img->png, img->info, text, 1);
} }
if (*png->mapfile) { if (*png_options->mapfile) {
text[0].key = "t"; text[0].key = "t";
text[0].text = ""; text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE; text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1); png_set_text(img->png, img->info, text, 1);
} }
if (png->mapout) { if (png_options->mapout) {
text[0].key = "T"; text[0].key = "T";
text[0].text = ""; text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE; text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1); png_set_text(img->png, img->info, text, 1);
} }
if (*png->palfile) { if (*png_options->palfile) {
text[0].key = "p"; text[0].key = "p";
text[0].text = ""; text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE; text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1); png_set_text(img->png, img->info, text, 1);
} }
if (png->palout) { if (png_options->palout) {
text[0].key = "P"; text[0].key = "P";
text[0].text = ""; text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE; text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1); png_set_text(img->png, img->info, text, 1);
} }
free(text); free(text);
} }
void output_png_file(const struct Options opts, const struct PNGImage *png) static void free_png_data(const struct PNGImage *img)
{
FILE *f;
char *outfile;
png_struct *img;
/*
* TODO: Variable outfile is for debugging purposes. Eventually,
* opts.infile will be used directly.
*/
if (opts.debug) {
outfile = malloc(strlen(opts.infile) + 5);
strcpy(outfile, opts.infile);
strcat(outfile, ".out");
} else {
outfile = opts.infile;
}
f = fopen(outfile, "wb");
if (!f)
err(1, "Opening output png file '%s' failed", outfile);
img = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!img)
errx(1, "Creating png structure failed");
/* TODO: Better error handling here? */
if (setjmp(png_jmpbuf(img)))
exit(1);
png_init_io(img, f);
png_write_info(img, png->info);
png_write_image(img, png->data);
png_write_end(img, NULL);
fclose(f);
if (opts.debug)
free(outfile);
}
void free_png_data(const struct PNGImage *png)
{ {
int y; int y;
for (y = 0; y < png->height; y++) for (y = 0; y < img->height; y++)
free(png->data[y]); free(img->data[y]);
free(png->data); free(img->data);
} }

View File

@@ -24,7 +24,28 @@
The The
.Nm .Nm
program converts PNG images into the Nintendo Game Boy's planar tile format. program converts PNG images into the Nintendo Game Boy's planar tile format.
The arguments are as follows:
The resulting colors and their palette indices are determined differently
depending on the input PNG file:
.Bl -dash -width Ds
.It
If the file has an embedded palette, that palette's color and order are used.
.It
If not, and the image only contains shades of gray, rgbgfx maps them to the
indices appropriate for each shade. Any undetermined indices are set to
respective default shades of gray. For example: if the bit depth is 2 and the
image contains light gray and black, they become the second and fourth colors -
and the first and third colors get set to default white and dark gray. If the
image has multiple shades that map to the same index, the palette is instead
determined as if the image had color.
.It
If the image has color (or the grayscale method failed), the colors are sorted
from lightest to darkest.
.El
The input image may not contain more colors than the selected bit depth
allows. Transparent pixels are set to palette index 0.
.Sh ARGUMENTS
.Bl -tag -width Ds .Bl -tag -width Ds
.It Fl D .It Fl D
Debug features are enabled. Debug features are enabled.
@@ -33,24 +54,25 @@ Fix the input PNG file to be a correctly indexed image.
.It Fl F .It Fl F
Same as Same as
.Fl f , .Fl f ,
but additionally, the input PNG file is fixed to have its parameters match the but additionally, the supplied command line parameters are saved within the PNG
command line's parameters. and will be loaded and automatically used next time.
.It Fl d Ar depth .It Fl d Ar depth
The bitdepth of the output image (either 1 or 2). The bit depth of the output image (either 1 or 2).
By default, the bitdepth is 2 (two bits per pixel). By default, the bit depth is 2 (two bits per pixel).
.It Fl h .It Fl h
Lay out tiles horizontally rather than vertically. Lay out tiles horizontally rather than vertically.
.It Fl o Ar outfile .It Fl o Ar outfile
The name of the output file. The name of the output file.
.It Fl p Ar palfile .It Fl p Ar palfile
Raw bytes (8 bytes for two bits per pixel, 4 bytes for one bit per pixel) Output the image's palette in standard GBC palette format - bytes (8 bytes for
containing the RGB15 values in the little-endian byte order and then ordered two bits per pixel, 4 bytes for one bit per pixel) containing the RGB15 values
from lightest to darkest. in little-endian byte order. If the palette contains too few colors, the
remaining entries are set to black.
.It Fl P .It Fl P
Same as Same as
.Fl p , .Fl p ,
but the pallete file output name is made by taking the input filename, but the palette file output name is made by taking the input PNG file's
removing the file extension, and appending filename, removing the file extension, and appending
.Pa .pal . .Pa .pal .
.It Fl t Ar mapfile .It Fl t Ar mapfile
If any tiles are the same, don't place the repeat tiles in the output file, and If any tiles are the same, don't place the repeat tiles in the output file, and
@@ -73,7 +95,7 @@ the PNG file don't match.
Trim the end of the output file by this many tiles. Trim the end of the output file by this many tiles.
.El .El
.Sh EXAMPLES .Sh EXAMPLES
The following will take a PNG file with a bitdepth of 1, 2, or 8, and output The following will take a PNG file with a bit depth of 1, 2, or 8, and output
planar 2bpp data: planar 2bpp data:
.Pp .Pp
.D1 $ rgbgfx -o out.2bpp in.png .D1 $ rgbgfx -o out.2bpp in.png

View File

@@ -74,6 +74,10 @@ static void do_max_bank(enum eSectionType Type, int32_t nBank)
if (nBank > MaxVBankUsed) if (nBank > MaxVBankUsed)
MaxVBankUsed = nBank; MaxVBankUsed = nBank;
break; break;
case SECT_ROM0:
case SECT_WRAM0:
case SECT_OAM:
case SECT_HRAM:
default: default:
break; break;
} }
@@ -494,7 +498,6 @@ void SetLinkerscriptName(char *tzLinkerscriptFile)
void AssignSections(void) void AssignSections(void)
{ {
int32_t i;
struct sSection *pSection; struct sSection *pSection;
MaxBankUsed = 0; MaxBankUsed = 0;
@@ -503,7 +506,7 @@ void AssignSections(void)
* Initialize the memory areas * Initialize the memory areas
*/ */
for (i = 0; i < BANK_INDEX_MAX; i += 1) { for (int32_t i = 0; i < BANK_INDEX_MAX; i += 1) {
BankFree[i] = malloc(sizeof(*BankFree[i])); BankFree[i] = malloc(sizeof(*BankFree[i]));
if (!BankFree[i]) { if (!BankFree[i]) {
@@ -617,6 +620,9 @@ void AssignSections(void)
pSection->nOrg, pSection->nBank); pSection->nOrg, pSection->nBank);
} }
break; break;
default:
errx(1, "%s: Internal error: Type %d", __func__,
pSection->Type);
} }
} }
@@ -660,6 +666,10 @@ void AssignSections(void)
do_max_bank(pSection->Type, pSection->nBank); do_max_bank(pSection->Type, pSection->nBank);
break; break;
case SECT_ROM0:
case SECT_WRAM0:
case SECT_OAM:
case SECT_HRAM:
default: /* Handle other sections later */ default: /* Handle other sections later */
break; break;
} }

View File

@@ -23,7 +23,7 @@
#include "types.h" #include "types.h"
extern int yyparse(); extern int yyparse(void);
/* File include stack. */ /* File include stack. */
@@ -179,13 +179,13 @@ void script_PrintFileStack(void)
fprintf(stderr, "%s(%d)", linkerscript_path, include_line[i]); fprintf(stderr, "%s(%d)", linkerscript_path, include_line[i]);
} }
noreturn void script_fatalerror(const char *fmt, ...) noreturn_ void script_fatalerror(const char *fmt, ...)
{ {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
fprintf(stderr, "error: "); fprintf(stderr, "error: ");
script_PrintFileStack(); script_PrintFileStack();
fprintf(stderr, ":\n\t"); fprintf(stderr, ":\n ");
vfprintf(stderr, fmt, args); vfprintf(stderr, fmt, args);
fprintf(stderr, "\n"); fprintf(stderr, "\n");
va_end(args); va_end(args);

View File

@@ -120,6 +120,10 @@ void MapfileWriteSection(const struct sSection *pSect)
for (i = 0; i < pSect->nNumberOfSymbols; i += 1) { for (i = 0; i < pSect->nNumberOfSymbols; i += 1) {
const struct sSymbol *pSym = pSect->tSymbols[i]; const struct sSymbol *pSym = pSect->tSymbols[i];
/* Don't print '@' */
if (strcmp(pSym->pzName, "@") == 0)
continue;
if ((pSym->pSection == pSect) && (pSym->Type != SYM_IMPORT)) { if ((pSym->pSection == pSect) && (pSym->Type != SYM_IMPORT)) {
if (mf) { if (mf) {
fprintf(mf, " $%04X = %s\n", fprintf(mf, " $%04X = %s\n",

View File

@@ -36,14 +36,14 @@ uint8_t oReadLib;
*/ */
static int32_t readlong(FILE *f) static int32_t readlong(FILE *f)
{ {
int32_t r; uint32_t r;
r = fgetc(f); r = ((uint32_t)(uint8_t)fgetc(f));
r |= fgetc(f) << 8; r |= ((uint32_t)(uint8_t)fgetc(f)) << 8;
r |= fgetc(f) << 16; r |= ((uint32_t)(uint8_t)fgetc(f)) << 16;
r |= fgetc(f) << 24; r |= ((uint32_t)(uint8_t)fgetc(f)) << 24;
return r; return (int32_t)r;
} }
/* /*

View File

@@ -110,46 +110,53 @@ void Output(void)
FILE *f_overlay = NULL; FILE *f_overlay = NULL;
/* /*
* Apply overlay * Load overlay
*/
if (tzOverlayname) {
f_overlay = fopen(tzOverlayname, "rb");
if (!f_overlay) {
errx(1, "Failed to open overlay file %s\n",
tzOverlayname);
}
fseek(f_overlay, 0, SEEK_END);
if (ftell(f_overlay) % 0x4000 != 0)
errx(1, "Overlay file must be aligned to 0x4000 bytes.");
MaxOverlayBank = (ftell(f_overlay) / 0x4000) - 1;
if (MaxOverlayBank < 1)
errx(1, "Overlay file must be at least 0x8000 bytes.");
if (MaxOverlayBank > MaxBankUsed)
MaxBankUsed = MaxOverlayBank;
}
/*
* Write ROM.
*/ */
f = fopen(tzOutname, "wb"); f = fopen(tzOutname, "wb");
if (f != NULL) { if (f != NULL) {
if (tzOverlayname) {
f_overlay = fopen(tzOverlayname, "rb");
if (!f_overlay) {
errx(1, "Failed to open overlay file %s\n",
tzOverlayname);
}
fseek(f_overlay, 0, SEEK_END);
if (ftell(f_overlay) % 0x4000 != 0)
errx(1, "Overlay file must be aligned to 0x4000 bytes.");
MaxOverlayBank = (ftell(f_overlay) / 0x4000) - 1;
if (MaxOverlayBank < 1)
errx(1, "Overlay file must be at least 0x8000 bytes.");
if (MaxOverlayBank > MaxBankUsed)
MaxBankUsed = MaxOverlayBank;
}
writehome(f, f_overlay); writehome(f, f_overlay);
for (i = 1; i <= MaxBankUsed; i += 1) for (i = 1; i <= MaxBankUsed; i += 1)
writebank(f, f_overlay, i); writebank(f, f_overlay, i);
fclose(f); fclose(f);
if (tzOverlayname)
fclose(f_overlay);
} }
/* /*
* Add regular sections * Close overlay
*/
if (tzOverlayname)
fclose(f_overlay);
/*
* Add regular sections to map and sym files.
*/ */
for (i = BANK_INDEX_WRAM0; i < BANK_INDEX_MAX; i++) { for (i = BANK_INDEX_WRAM0; i < BANK_INDEX_MAX; i++) {

View File

@@ -14,7 +14,7 @@
#include "link/script.h" #include "link/script.h"
int yylex(); int yylex(void);
void yyerror(char *); void yyerror(char *);
extern int yylineno; extern int yylineno;
@@ -107,8 +107,8 @@ statement:
%% %%
extern int yylex(); extern int yylex(void);
extern int yyparse(); extern int yyparse(void);
int yywrap(void) int yywrap(void)
{ {

View File

@@ -267,6 +267,7 @@ void Patch(void)
pPatch = pSect->pPatches; pPatch = pSect->pPatches;
while (pPatch) { while (pPatch) {
int32_t t; int32_t t;
int32_t nPatchOrg;
nPC = pSect->nOrg + pPatch->nOffset; nPC = pSect->nOrg + pPatch->nOffset;
t = calcrpn(pPatch); t = calcrpn(pPatch);
@@ -306,6 +307,26 @@ void Patch(void)
pSect->pData[pPatch->nOffset + 3] = pSect->pData[pPatch->nOffset + 3] =
(t >> 24) & 0xFF; (t >> 24) & 0xFF;
break; break;
case PATCH_BYTE_JR:
/* Calculate absolute address of the patch */
nPatchOrg = pSect->nOrg + pPatch->nOffset;
/* t contains the destination of the jump */
t = (int16_t)((t & 0xFFFF) - (nPatchOrg + 1));
if (t >= -128 && t <= 127) {
t &= 0xFF;
pSect->pData[pPatch->nOffset] =
(uint8_t)t;
} else {
errx(1,
"%s(%ld) : Value must be 8-bit",
pPatch->pzFilename,
pPatch->nLineNo);
}
break;
default:
errx(1, "%s: Internal error.", __func__);
} }
pPatch = pPatch->pNext; pPatch = pPatch->pNext;

View File

@@ -57,7 +57,7 @@ Write a symbol file to the given filename.
.It Fl O Ar overlayfile .It Fl O Ar overlayfile
The ROM image to overlay sections over. The ROM image to overlay sections over.
When an overlay ROM is provided, all sections must be fixed. When an overlay ROM is provided, all sections must be fixed.
This may be used to patch an existing binray. This may be used to patch an existing binary.
.It Fl o Ar outfile .It Fl o Ar outfile
Write ROM image to the given filename. Write ROM image to the given filename.
.It Fl p Ar pad_value .It Fl p Ar pad_value

View File

@@ -85,59 +85,59 @@ void script_InitSections(void)
} }
} }
void script_SetCurrentSectionType(const char *type, uint32_t bank) void script_SetCurrentSectionType(const char *type, uint32_t bank_num)
{ {
if (strcmp(type, "ROM0") == 0) { if (strcmp(type, "ROM0") == 0) {
if (bank != 0) if (bank_num != 0)
errx(1, "Trying to assign a bank number to ROM0.\n"); errx(1, "Trying to assign a bank number to ROM0.\n");
current_bank = BANK_INDEX_ROM0; current_bank = BANK_INDEX_ROM0;
current_real_bank = 0; current_real_bank = 0;
return; return;
} else if (strcmp(type, "ROMX") == 0) { } else if (strcmp(type, "ROMX") == 0) {
if (bank == 0) if (bank_num == 0)
errx(1, "ROMX index can't be 0.\n"); errx(1, "ROMX index can't be 0.\n");
if (bank > BANK_COUNT_ROMX) { if (bank_num > BANK_COUNT_ROMX) {
errx(1, "ROMX index too big (%d > %d).\n", bank, errx(1, "ROMX index too big (%d > %d).\n", bank_num,
BANK_COUNT_ROMX); BANK_COUNT_ROMX);
} }
current_bank = BANK_INDEX_ROMX + bank - 1; current_bank = BANK_INDEX_ROMX + bank_num - 1;
current_real_bank = bank; current_real_bank = bank_num;
return; return;
} else if (strcmp(type, "VRAM") == 0) { } else if (strcmp(type, "VRAM") == 0) {
if (bank >= BANK_COUNT_VRAM) { if (bank_num >= BANK_COUNT_VRAM) {
errx(1, "VRAM index too big (%d >= %d).\n", bank, errx(1, "VRAM index too big (%d >= %d).\n", bank_num,
BANK_COUNT_VRAM); BANK_COUNT_VRAM);
} }
current_bank = BANK_INDEX_VRAM + bank; current_bank = BANK_INDEX_VRAM + bank_num;
current_real_bank = bank; current_real_bank = bank_num;
return; return;
} else if (strcmp(type, "WRAM0") == 0) { } else if (strcmp(type, "WRAM0") == 0) {
if (bank != 0) if (bank_num != 0)
errx(1, "Trying to assign a bank number to WRAM0.\n"); errx(1, "Trying to assign a bank number to WRAM0.\n");
current_bank = BANK_INDEX_WRAM0; current_bank = BANK_INDEX_WRAM0;
current_real_bank = 0; current_real_bank = 0;
return; return;
} else if (strcmp(type, "WRAMX") == 0) { } else if (strcmp(type, "WRAMX") == 0) {
if (bank == 0) if (bank_num == 0)
errx(1, "WRAMX index can't be 0.\n"); errx(1, "WRAMX index can't be 0.\n");
if (bank > BANK_COUNT_WRAMX) { if (bank_num > BANK_COUNT_WRAMX) {
errx(1, "WRAMX index too big (%d > %d).\n", bank, errx(1, "WRAMX index too big (%d > %d).\n", bank_num,
BANK_COUNT_WRAMX); BANK_COUNT_WRAMX);
} }
current_bank = BANK_INDEX_WRAMX + bank - 1; current_bank = BANK_INDEX_WRAMX + bank_num - 1;
current_real_bank = bank; current_real_bank = bank_num;
return; return;
} else if (strcmp(type, "SRAM") == 0) { } else if (strcmp(type, "SRAM") == 0) {
if (bank >= BANK_COUNT_SRAM) { if (bank_num >= BANK_COUNT_SRAM) {
errx(1, "SRAM index too big (%d >= %d).\n", bank, errx(1, "SRAM index too big (%d >= %d).\n", bank_num,
BANK_COUNT_SRAM); BANK_COUNT_SRAM);
} }
current_bank = BANK_INDEX_SRAM + bank; current_bank = BANK_INDEX_SRAM + bank_num;
current_real_bank = bank; current_real_bank = bank_num;
return; return;
} else if (strcmp(type, "OAM") == 0) { } else if (strcmp(type, "OAM") == 0) {
if (bank != 0) { if (bank_num != 0) {
errx(1, "%s: Trying to assign a bank number to OAM.\n", errx(1, "%s: Trying to assign a bank number to OAM.\n",
__func__); __func__);
} }
@@ -145,7 +145,7 @@ void script_SetCurrentSectionType(const char *type, uint32_t bank)
current_real_bank = 0; current_real_bank = 0;
return; return;
} else if (strcmp(type, "HRAM") == 0) { } else if (strcmp(type, "HRAM") == 0) {
if (bank != 0) { if (bank_num != 0) {
errx(1, "%s: Trying to assign a bank number to HRAM.\n", errx(1, "%s: Trying to assign a bank number to HRAM.\n",
__func__); __func__);
} }

View File

@@ -57,7 +57,9 @@ REPT NumberOfSymbols ; Number of symbols defined in this object file.
LONG LineNum ; Line number in the file where the symbol is defined. LONG LineNum ; Line number in the file where the symbol is defined.
LONG SectionID ; The section number (of this object file) in which LONG SectionID ; The section number (of this object file) in which
; this symbol is defined. ; this symbol is defined. If it doesn't belong to any
; specific section (like a constant), this field has
; the value -1.
LONG Value ; The symbols value. It's the offset into that LONG Value ; The symbols value. It's the offset into that
; symbol's section. ; symbol's section.
@@ -113,6 +115,7 @@ REPT NumberOfSections
BYTE Type ; 0 = BYTE patch. BYTE Type ; 0 = BYTE patch.
; 1 = little endian WORD patch. ; 1 = little endian WORD patch.
; 2 = little endian LONG patch. ; 2 = little endian LONG patch.
; 3 = JR offset value BYTE patch.
LONG RPNSize ; Size of the buffer with the RPN. LONG RPNSize ; Size of the buffer with the RPN.
; expression. ; expression.

View File

@@ -5,7 +5,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd January 26, 2018 .Dd March 7, 2018
.Dt RGBDS 7 .Dt RGBDS 7
.Os RGBDS Manual .Os RGBDS Manual
.Sh NAME .Sh NAME
@@ -14,9 +14,11 @@
.Sh EXAMPLES .Sh EXAMPLES
To get a working ROM image from a single assembly source file: To get a working ROM image from a single assembly source file:
.Pp .Pp
.D1 $ rgbasm \-o bar.o foo.asm .Bd -literal -offset indent
.D1 $ rgblink \-o baz.gb bar.o $ rgbasm \-o bar.o foo.asm
.D1 $ rgbfix \-v \-p 0 baz.gb $ rgblink \-o baz.gb bar.o
$ rgbfix \-v \-p 0 baz.gb
.Ed
.Sh SEE ALSO .Sh SEE ALSO
.Xr rgbasm 1 , .Xr rgbasm 1 ,
.Xr rgbfix 1 , .Xr rgbfix 1 ,
@@ -41,5 +43,6 @@ implementation of rgbds.
2017, Bentley's repository is moved to a neutral name. 2017, Bentley's repository is moved to a neutral name.
It is now maintained by a number of contributors at It is now maintained by a number of contributors at
.Lk https://github.com/rednex/rgbds . .Lk https://github.com/rednex/rgbds .
.It
2018, codebase relicensed under the MIT license. 2018, codebase relicensed under the MIT license.
.El .El

View File

@@ -1,2 +1,2 @@
ERROR: divzero-instr.asm(2): ERROR: divzero-instr.asm(2):
division by zero Division by zero

View File

@@ -1,2 +1,2 @@
ERROR: divzero-section-bank.asm(1): ERROR: divzero-section-bank.asm(1):
division by zero Division by zero

View File

@@ -1,3 +1,3 @@
ERROR: local-wrong-parent.asm(5): ERROR: local-wrong-parent.asm(5):
Not currently in the scope of 'WrongParent' Not currently in the scope of 'WrongParent'
error: Assembly aborted in pass 1 (1 errors)! error: Assembly aborted in pass 1 (1 errors)!

View File

@@ -1,2 +1,2 @@
ERROR: macro-@.asm(1) -> @(-1): ERROR: macro-@.asm(1) -> @(-1):
Macro '@' not defined Macro '@' not defined

View File

@@ -1,2 +1,2 @@
ERROR: null-in-macro.asm(1): ERROR: null-in-macro.asm(1):
Unterminated MACRO definition. Unterminated MACRO definition.

View File

@@ -1,2 +1,2 @@
ERROR: remote-local-noexist.asm(7): ERROR: remote-local-noexist.asm(7):
'Parent.child.fail' is a nonsensical reference to a nested local symbol 'Parent.child.fail' is a nonsensical reference to a nested local symbol

View File

@@ -1,3 +1,3 @@
ERROR: undefined-dot.asm(3): ERROR: undefined-dot.asm(3):
'.' not defined '.' not defined
error: Assembly aborted in pass 2 (1 errors)! error: Assembly aborted in pass 2 (1 errors)!