mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b85875b67 | ||
|
|
7054d81650 | ||
|
|
5942117ac3 | ||
|
|
e7a3b9d90e | ||
|
|
b13d623ad4 | ||
|
|
37bf9fae01 | ||
|
|
612cf3b7dd | ||
|
|
089e366ddc | ||
|
|
fa9e29e4ce | ||
|
|
fa3d83a3d1 | ||
|
|
804db4e073 | ||
|
|
5d998ef483 | ||
|
|
126b1e5726 | ||
|
|
4f2400c15b | ||
|
|
063d284cbf | ||
|
|
205bf5a11d | ||
|
|
41c94aa448 | ||
|
|
d413870e6d | ||
|
|
e95ac6fb06 | ||
|
|
e1ae92709c | ||
|
|
1715f85d50 | ||
|
|
c2de0a991a | ||
|
|
2e6e1ccf06 | ||
|
|
081f48404c | ||
|
|
bdac0ce053 | ||
|
|
122d91509f | ||
|
|
7c6f778ae7 | ||
|
|
8cf6c5423a | ||
|
|
56f7222230 | ||
|
|
e45b9625ca | ||
|
|
e0a8eb8aff | ||
|
|
2a5b9b5f98 | ||
|
|
a72843748f | ||
|
|
762e2311d2 | ||
|
|
0b7cda9e0c | ||
|
|
df83bc31d2 | ||
|
|
bc8d99d915 | ||
|
|
c841672059 | ||
|
|
75b605797d | ||
|
|
00d0ae840d | ||
|
|
2cdbb145da | ||
|
|
d8192560b0 | ||
|
|
9b395f3bf1 | ||
|
|
0150eb4bf3 | ||
|
|
632342b254 | ||
|
|
c9060c7f2d | ||
|
|
993879a2ed | ||
|
|
62309d5c87 | ||
|
|
b2e865ee2a | ||
|
|
3feb75f84f | ||
|
|
ad4d9da4cf | ||
|
|
1489854932 | ||
|
|
2aef09c8d9 | ||
|
|
48412e9c56 | ||
|
|
177a3abfac | ||
|
|
4c916b8da8 | ||
|
|
d9d381cb62 | ||
|
|
fbde24ee17 | ||
|
|
91310c9eb6 | ||
|
|
81ea4ee920 | ||
|
|
29ece2940d | ||
|
|
03452c6d4f | ||
|
|
b35e9d86fb | ||
|
|
e20347e38c | ||
|
|
f61019dd68 | ||
|
|
c19ddc80f0 | ||
|
|
a59867cd78 | ||
|
|
375adc6804 | ||
|
|
44caffe04a | ||
|
|
d54619a453 | ||
|
|
e49291b7cf | ||
|
|
34a9c8e083 | ||
|
|
c4b456b166 | ||
|
|
79401cce8b | ||
|
|
4e44958d26 | ||
|
|
cae31005f8 | ||
|
|
25c9f8f383 | ||
|
|
01c9106b59 | ||
|
|
192fc808c8 | ||
|
|
9c8e327ae2 | ||
|
|
9ebd2a7e8e | ||
|
|
b8b60207f5 | ||
|
|
c5e59f40fd | ||
|
|
a354af3d08 | ||
|
|
20c18256ed | ||
|
|
890528812e | ||
|
|
84f59e14ed | ||
|
|
91d7ce5e09 | ||
|
|
d9654b752f | ||
|
|
157826bf82 | ||
|
|
a5e36f924f | ||
|
|
82f7bdb480 | ||
|
|
056190413e | ||
|
|
c2db23aef0 | ||
|
|
2426068409 | ||
|
|
147a5c9bf3 | ||
|
|
6ae3f040b8 | ||
|
|
e561f63db3 | ||
|
|
af9de812ec | ||
|
|
edc9e07a2d | ||
|
|
382ad17969 | ||
|
|
fac5e35d24 | ||
|
|
a85d6b3b57 | ||
|
|
f23a14afc7 | ||
|
|
f63167dd0f | ||
|
|
0ee4ba95b3 | ||
|
|
727c1f5b50 | ||
|
|
d829fd2ffe | ||
|
|
b13c0f2f8e | ||
|
|
d9773424e4 | ||
|
|
4e2464a69d | ||
|
|
a5f12f66bb | ||
|
|
73ad431b8d | ||
|
|
d88feee1c0 | ||
|
|
5963dc9e0e | ||
|
|
8363f25d47 | ||
|
|
72b2a4d7c0 | ||
|
|
06daf2a9b5 | ||
|
|
ad95d2e06f | ||
|
|
5197e6b79f | ||
|
|
b99ce3845e |
@@ -24,6 +24,7 @@ AttributeMacros:
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BreakAfterAttributes: Always
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeConceptDeclarations: true
|
||||
@@ -54,12 +55,14 @@ IncludeCategories:
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: NoIndent
|
||||
IndentExternBlock: Indent
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: BeforeHash
|
||||
IndentRequires: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
InsertBraces: true
|
||||
InsertNewlineAtEOF: true
|
||||
# Only support for Javascript as of clang-format 14...
|
||||
# InsertTrailingCommas: Wrapped
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
@@ -71,6 +74,8 @@ PPIndentWidth: -1
|
||||
PointerAlignment: Right
|
||||
QualifierAlignment: Right
|
||||
ReflowComments: true
|
||||
RemoveParentheses: ReturnStatement
|
||||
RemoveSemicolon: true
|
||||
SortIncludes: CaseSensitive
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
|
||||
4
.github/scripts/build_libpng.sh
vendored
4
.github/scripts/build_libpng.sh
vendored
@@ -1,13 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.43
|
||||
pngver=1.6.45
|
||||
|
||||
## Grab sources and check them
|
||||
|
||||
curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download"
|
||||
# Brew doesn't provide any sha256sum, so we're making do with `sha2` instead.
|
||||
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c ]; then
|
||||
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 ]; then
|
||||
sha2 -256 libpng-$pngver.tar.xz
|
||||
echo Checksum mismatch! Aborting. >&2
|
||||
exit 1
|
||||
|
||||
4
.github/scripts/get_win_deps.ps1
vendored
4
.github/scripts/get_win_deps.ps1
vendored
@@ -16,8 +16,8 @@ function getlibrary ([string] $URI, [string] $filename, [string] $hash, [string]
|
||||
}
|
||||
|
||||
getlibrary 'https://www.zlib.net/zlib131.zip' 'zlib.zip' '72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17' .
|
||||
getlibrary 'https://github.com/glennrp/libpng/archive/refs/tags/v1.6.43.zip' 'libpng.zip' '5e18474a26814ae479e02ca6432da32d19dc6e615551d140c954a68d63b3f192' .
|
||||
getlibrary 'https://github.com/glennrp/libpng/archive/refs/tags/v1.6.45.zip' 'libpng.zip' '1b3d94b2f1d137db1bf1842cb9f03df179772a517f7b86e26351742190632785' .
|
||||
getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip' 'winflexbison.zip' '8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135' install_dir
|
||||
|
||||
Move-Item zlib-1.3.1 zlib
|
||||
Move-Item libpng-1.6.43 libpng
|
||||
Move-Item libpng-1.6.45 libpng
|
||||
|
||||
2
.github/scripts/mingw-w64-libpng-dev.sh
vendored
2
.github/scripts/mingw-w64-libpng-dev.sh
vendored
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.43
|
||||
pngver=1.6.45
|
||||
arch="$1"
|
||||
|
||||
## Grab sources and check them
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
d6bd2a3f43f17020918a4c1bd81c1a78111b6f759af9c1d3c754f704a1bf0429 libpng-1.6.43-apng.patch.gz
|
||||
6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c libpng-1.6.43.tar.xz
|
||||
f7caa3b55f003ce23d6a087b1c2a643262647bfd1a1b31afdc9b18eabf1bbc7e libpng-1.6.45-apng.patch.gz
|
||||
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-1.6.45.tar.xz
|
||||
|
||||
18
.github/workflows/analysis.yml
vendored
Normal file
18
.github/workflows/analysis.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Static analysis
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ubuntu-latest
|
||||
- name: Static analysis
|
||||
run: | # Silence warnings with too many false positives (https://stackoverflow.com/a/73913076)
|
||||
make -kj CXX=g++-14 CXXFLAGS="-fanalyzer -fanalyzer-verbosity=0 -Wno-analyzer-use-of-uninitialized-value -DNDEBUG" Q=
|
||||
1
.github/workflows/build-container.yml
vendored
1
.github/workflows/build-container.yml
vendored
@@ -38,6 +38,7 @@ jobs:
|
||||
sed -i "2i LABEL org.opencontainers.image.description=\"RGBDS container image for the release version $TAG_NAME\"" Dockerfile
|
||||
docker build . --tag ghcr.io/gbdev/rgbds:$TAG_NAME
|
||||
docker push ghcr.io/gbdev/rgbds:$TAG_NAME
|
||||
docker push ghcr.io/gbdev/rgbds:latest
|
||||
|
||||
- name: Delete untagged container images
|
||||
if: github.repository_owner == 'gbdev'
|
||||
|
||||
2
.github/workflows/checkdiff.yml
vendored
2
.github/workflows/checkdiff.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
cd rgbds
|
||||
git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}"
|
||||
git fetch upstream
|
||||
- name: Checkdiff
|
||||
- name: Check diff
|
||||
working-directory: rgbds
|
||||
run: |
|
||||
make checkdiff "BASE_REF=${{ github.event.pull_request.base.sha }}" Q= | tee log
|
||||
|
||||
12
.github/workflows/checkformat.yml
vendored
Normal file
12
.github/workflows/checkformat.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Code format checking
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
checkformat:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Check format
|
||||
run: |
|
||||
./contrib/checkformat.bash
|
||||
26
.github/workflows/create-release-artifacts.yml
vendored
26
.github/workflows/create-release-artifacts.yml
vendored
@@ -61,12 +61,12 @@ jobs:
|
||||
cmake --install build --verbose --prefix install_dir --strip
|
||||
- name: Package binaries
|
||||
run: |
|
||||
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-${{ env.version }}-win${{ matrix.bits }}.zip"
|
||||
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-win${{ matrix.bits }}.zip"
|
||||
- name: Upload Windows binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: win${{ matrix.bits }}
|
||||
path: rgbds-${{ env.version }}-win${{ matrix.bits }}.zip
|
||||
path: rgbds-win${{ matrix.bits }}.zip
|
||||
|
||||
macos:
|
||||
runs-on: macos-14
|
||||
@@ -92,15 +92,15 @@ jobs:
|
||||
strip rgb{asm,link,fix,gfx}
|
||||
- name: Package binaries
|
||||
run: |
|
||||
zip --junk-paths rgbds-${{ env.version }}-macos.zip rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||
zip --junk-paths rgbds-macos.zip rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||
- name: Upload macOS binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: rgbds-${{ env.version }}-macos.zip
|
||||
path: rgbds-macos.zip
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-20.04 # Oldest supported, for best glibc compatibility.
|
||||
runs-on: ubuntu-22.04 # Oldest supported, for best glibc compatibility.
|
||||
steps:
|
||||
- name: Get version from tag
|
||||
shell: bash
|
||||
@@ -112,19 +112,19 @@ jobs:
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ubuntu-20.04
|
||||
./.github/scripts/install_deps.sh ubuntu-22.04
|
||||
- name: Build binaries
|
||||
run: |
|
||||
make -kj WARNFLAGS="-Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q=
|
||||
strip rgb{asm,link,fix,gfx}
|
||||
- name: Package binaries
|
||||
run: |
|
||||
tar caf rgbds-${{ env.version }}-linux-x86_64.tar.xz --transform='s#.*/##' rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||
tar caf rgbds-linux-x86_64.tar.xz --transform='s#.*/##' rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||
- name: Upload Linux binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux
|
||||
path: rgbds-${{ env.version }}-linux-x86_64.tar.xz
|
||||
path: rgbds-linux-x86_64.tar.xz
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -155,11 +155,11 @@ jobs:
|
||||
draft: true # Don't publish the release quite yet...
|
||||
prerelease: ${{ contains(github.ref, '-rc') }}
|
||||
files: |
|
||||
win32/rgbds-${{ env.version }}-win32.zip
|
||||
win64/rgbds-${{ env.version }}-win64.zip
|
||||
macos/rgbds-${{ env.version }}-macos.zip
|
||||
linux/rgbds-${{ env.version }}-linux-x86_64.tar.xz
|
||||
rgbds-${{ env.version }}.tar.gz
|
||||
win32/rgbds-win32.zip
|
||||
win64/rgbds-win64.zip
|
||||
macos/rgbds-macos.zip
|
||||
linux/rgbds-linux-x86_64.tar.xz
|
||||
rgbds-source.tar.gz
|
||||
fail_on_unmatched_files: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
68
.github/workflows/testing.yml
vendored
68
.github/workflows/testing.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
unix:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-22.04, macos-14]
|
||||
os: [ubuntu-22.04, macos-14]
|
||||
cxx: [g++, clang++]
|
||||
buildsys: [make, cmake]
|
||||
exclude:
|
||||
@@ -320,3 +320,69 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
test/run-tests.sh
|
||||
|
||||
cygwin:
|
||||
strategy:
|
||||
matrix:
|
||||
bits: [32, 64]
|
||||
include:
|
||||
- bits: 32
|
||||
arch: x86
|
||||
- bits: 64
|
||||
arch: x86_64
|
||||
fail-fast: false
|
||||
runs-on: windows-2019
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Setup Cygwin
|
||||
uses: cygwin/cygwin-install-action@v4
|
||||
with:
|
||||
platform: ${{ matrix.arch }}
|
||||
packages: >-
|
||||
bison
|
||||
gcc-g++
|
||||
git
|
||||
libpng-devel
|
||||
make
|
||||
pkg-config
|
||||
- name: Build & install using Make
|
||||
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
|
||||
run: | # Cygwin does not support `make develop` sanitizers ASan or UBSan
|
||||
make -kj Q=
|
||||
make install -j Q=
|
||||
- name: Run tests
|
||||
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
|
||||
run: |
|
||||
test/run-tests.sh --only-internal
|
||||
|
||||
freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Build & test using CMake on FreeBSD
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "15.0"
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
bash \
|
||||
bison \
|
||||
cmake \
|
||||
git \
|
||||
png
|
||||
run: | # FreeBSD `c++` compiler does not support `make develop` sanitizers ASan or UBSan
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=c++ -DUSE_EXTERNAL_TESTS=OFF
|
||||
cmake --build build -j4 --verbose
|
||||
cmake --install build --verbose
|
||||
cmake --build build --target test
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,4 +14,5 @@ CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
build/
|
||||
*.dSYM/
|
||||
callgrind.out.*
|
||||
|
||||
@@ -45,25 +45,21 @@ else()
|
||||
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
|
||||
endif()
|
||||
if(SANITIZERS)
|
||||
set(SAN_FLAGS -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 -fsanitize=address)
|
||||
set(SAN_FLAGS -fsanitize=address -fsanitize=undefined
|
||||
-fsanitize=float-divide-by-zero)
|
||||
add_compile_options(${SAN_FLAGS})
|
||||
add_link_options(${SAN_FLAGS})
|
||||
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
||||
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
||||
# TODO: this overrides anything previously set... that's a bit sloppy!
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls ${CMAKE_CXX_FLAGS_DEBUG}"
|
||||
CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
if(MORE_WARNINGS)
|
||||
add_compile_options(-Werror -Wextra
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
||||
-Wshadow # TODO: -Wshadow=compatible-local?
|
||||
-Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||
-Wno-format-nonliteral -Wno-strict-overflow
|
||||
-Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused
|
||||
@@ -78,7 +74,7 @@ endif()
|
||||
|
||||
find_program(GIT git)
|
||||
if(GIT)
|
||||
execute_process(COMMAND ${GIT} --git-dir=.git describe --tags --dirty --always
|
||||
execute_process(COMMAND ${GIT} --git-dir=.git -c safe.directory='*' describe --tags --dirty --always
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET)
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@@ -1,6 +1,6 @@
|
||||
FROM debian:11-slim
|
||||
FROM debian:12-slim
|
||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||
ARG version=0.9.0
|
||||
ARG version=0.9.3
|
||||
WORKDIR /rgbds
|
||||
|
||||
COPY . .
|
||||
@@ -8,7 +8,14 @@ COPY . .
|
||||
RUN apt-get update && \
|
||||
apt-get install sudo make cmake gcc build-essential -y
|
||||
|
||||
RUN ./.github/scripts/install_deps.sh ubuntu-20.04
|
||||
# Install dependencies and compile RGBDS
|
||||
RUN ./.github/scripts/install_deps.sh ubuntu-22.04
|
||||
RUN make -j CXXFLAGS="-O3 -flto -DNDEBUG -static" PKG_CONFIG="pkg-config --static" Q=
|
||||
|
||||
RUN tar caf rgbds-${version}-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh
|
||||
# Create an archive with the compiled executables and all the necessary to install it,
|
||||
# so it can be copied outside of the container and installed/used in another system
|
||||
RUN tar caf rgbds-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh
|
||||
|
||||
# Install RGBDS on the container so all the executables will be available in the PATH
|
||||
RUN cp man/* .
|
||||
RUN ./.github/scripts/install.sh
|
||||
|
||||
54
Makefile
54
Makefile
@@ -7,43 +7,43 @@
|
||||
|
||||
# User-defined variables
|
||||
|
||||
Q := @
|
||||
PREFIX := /usr/local
|
||||
bindir := ${PREFIX}/bin
|
||||
mandir := ${PREFIX}/share/man
|
||||
SUFFIX :=
|
||||
STRIP := -s
|
||||
BINMODE := 755
|
||||
MANMODE := 644
|
||||
Q := @
|
||||
PREFIX := /usr/local
|
||||
bindir := ${PREFIX}/bin
|
||||
mandir := ${PREFIX}/share/man
|
||||
SUFFIX :=
|
||||
STRIP := -s
|
||||
BINMODE := 755
|
||||
MANMODE := 644
|
||||
|
||||
# Other variables
|
||||
|
||||
PKG_CONFIG := pkg-config
|
||||
PNGCFLAGS := `${PKG_CONFIG} --cflags libpng`
|
||||
PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
|
||||
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
||||
PKG_CONFIG := pkg-config
|
||||
PNGCFLAGS := `${PKG_CONFIG} --cflags libpng`
|
||||
PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
|
||||
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
||||
|
||||
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
|
||||
VERSION_STRING := `git --git-dir=.git describe --tags --dirty --always 2>/dev/null`
|
||||
VERSION_STRING := `git --git-dir=.git -c safe.directory='*' describe --tags --dirty --always 2>/dev/null`
|
||||
|
||||
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
|
||||
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
|
||||
|
||||
# Overridable CXXFLAGS
|
||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||
# Non-overridable CXXFLAGS
|
||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
|
||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
|
||||
# Overridable LDFLAGS
|
||||
LDFLAGS ?=
|
||||
LDFLAGS ?=
|
||||
# Non-overridable LDFLAGS
|
||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||
|
||||
# Wrapper around bison that passes flags depending on what the version supports
|
||||
BISON := src/bison.sh
|
||||
BISON := src/bison.sh
|
||||
|
||||
RM := rm -rf
|
||||
RM := rm -rf
|
||||
|
||||
# Used for checking pull requests
|
||||
BASE_REF := origin/master
|
||||
BASE_REF := origin/master
|
||||
|
||||
# Rules to build the RGBDS binaries
|
||||
|
||||
@@ -202,16 +202,12 @@ develop:
|
||||
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 \
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||
-Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
||||
-D_GLIBCXX_ASSERTIONS \
|
||||
-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 -fsanitize=address" \
|
||||
-D_GLIBCXX_ASSERTIONS -fsanitize=address -fsanitize=undefined \
|
||||
-fsanitize=float-divide-by-zero" \
|
||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||
|
||||
# This target is used during development in order to more easily debug with gdb.
|
||||
@@ -269,4 +265,4 @@ wine-shim:
|
||||
|
||||
dist:
|
||||
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
|
||||
| tar -czf rgbds-`git describe --tags | cut -c 2-`.tar.gz -C .. -T -
|
||||
| tar -czf rgbds-source.tar.gz -C .. -T -
|
||||
|
||||
@@ -137,9 +137,12 @@ The RGBDS source code file structure is as follows:
|
||||
this version as [rgbds-linux](https://github.com/vegard/rgbds-linux).
|
||||
- 2010-01-12: Anthony J. Bentley [forks](https://github.com/bentley) Nossum's
|
||||
repository. The fork becomes the reference implementation of RGBDS.
|
||||
- 2010-09-25: Sørensen continues development of
|
||||
[ASMotor](https://github.com/asmotor/asmotor) to this day.
|
||||
- 2015-01-18: stag019 begins implementing [RGBGFX](https://github.com/stag019/rgbgfx),
|
||||
a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
- 2016-09-05: RGBGFX is [integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||
- 2016-09-05: RGBGFX is
|
||||
[integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||
into Bentley's repository.
|
||||
- 2017-02-23: Bentley's repository is moved to the [rednex](https://github.com/rednex)
|
||||
organization.
|
||||
|
||||
17
RELEASE.md
17
RELEASE.md
@@ -72,9 +72,14 @@ GitHub.
|
||||
|
||||
8. Update the following related projects.
|
||||
|
||||
- [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
||||
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
||||
If the object file revision has been updated, rgbobj will need a corresponding release.
|
||||
- [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
||||
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
||||
to list the new release.
|
||||
1. [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
||||
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
||||
to list the new release.
|
||||
2. [rgbds-live](https://github.com/gbdev/rgbds-live): update the `rgbds` submodule (and
|
||||
[patches/rgbds.patch](https://github.com/gbdev/rgbds-live/blob/master/patches/rgbds.patch)
|
||||
if necessary) to use the new release, and
|
||||
[index.html](https://github.com/gbdev/rgbds-live/blob/master/index.html)
|
||||
to link to the new manual version.
|
||||
3. [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
||||
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
||||
If the object file revision has been updated, `rgbobj` will need a corresponding release.
|
||||
|
||||
@@ -25,6 +25,7 @@ _rgbasm_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[E]="export-all:normal"
|
||||
[v]="verbose:normal"
|
||||
[w]=":normal"
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgbfix_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[j]="non-japanese:normal"
|
||||
[s]="sgb-compatible:normal"
|
||||
[v]="validate:normal"
|
||||
@@ -20,6 +21,7 @@ _rgbfix_completions() {
|
||||
[l]="old-licensee:unk"
|
||||
[m]="mbc-type:mbc"
|
||||
[n]="rom-version:unk"
|
||||
[o]="output:glob-*.gb *.gbc *.sgb"
|
||||
[p]="pad-value:unk"
|
||||
[r]="ram-size:unk"
|
||||
[t]="title:unk"
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgbgfx_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[C]="color-curve:normal"
|
||||
[m]="mirror-tiles:normal"
|
||||
[O]="group-outputs:normal"
|
||||
@@ -18,6 +19,7 @@ _rgbgfx_completions() {
|
||||
[Z]="columns:normal"
|
||||
[a]="attr-map:glob-*.attrmap"
|
||||
[A]="auto-attr-map:normal"
|
||||
[B]="background-color:unk"
|
||||
[b]="base-tiles:unk"
|
||||
[c]="colors:unk"
|
||||
[d]="depth:unk"
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgblink_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[d]="dmg:normal"
|
||||
[t]="tiny:normal"
|
||||
[v]="verbose:normal"
|
||||
|
||||
15
contrib/checkformat.bash
Executable file
15
contrib/checkformat.bash
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
clang-format --version
|
||||
|
||||
find . -type f \( -iname '*.hpp' -o -iname '*.cpp' \) -exec clang-format -i {} +
|
||||
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
echo 'Unformatted files:'
|
||||
git diff-index --name-only HEAD --
|
||||
echo
|
||||
git diff HEAD --
|
||||
return 1
|
||||
fi
|
||||
@@ -32,14 +32,15 @@ diff <(xxd "$1") <(xxd "$2") | while read -r LINE; do
|
||||
# Ignore comment lines, only pick matching bank
|
||||
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
||||
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
|
||||
sed "s/$(printf "^%02x:" $BANK)/0x/g" |
|
||||
cut -d ';' -f 1 |
|
||||
tr -d "\r" |
|
||||
sort -g |
|
||||
while read -r SYMADDR SYM; do
|
||||
SYMADDR=$((0x${SYMADDR#*:}))
|
||||
SYMADDR=$(($SYMADDR))
|
||||
if [[ $SYMADDR -le $ADDR ]]; then
|
||||
printf " (%s+0x%x)\n" "$SYM" $((ADDR - SYMADDR))
|
||||
fi
|
||||
# TODO: assumes sorted sym files
|
||||
done | tail -n 1
|
||||
fi)
|
||||
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
||||
|
||||
@@ -36,8 +36,9 @@ _rgbasm_warnings() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
|
||||
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
||||
|
||||
@@ -35,8 +35,9 @@ _mbc_names() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-C --color-only -c --color-compatible)'{-C,--color-only}'[Mark ROM as GBC-only]'
|
||||
'(-C --color-only -c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'
|
||||
@@ -52,6 +53,7 @@ local args=(
|
||||
'(-L --logo)'{-L,--logo}'+[Set custom logo]:1bpp image:'
|
||||
'(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names"
|
||||
'(-n --rom-version)'{-n,--rom-version}'+[Set ROM version]:rom version byte:'
|
||||
'(-o --output)'{-o,--output}"+[Output file]:output file:_files -g '*.{gb,sgb,gbc}'"
|
||||
'(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'
|
||||
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'
|
||||
'(-t --title)'{-t,--title}'+[Set title string]:11-char title string:'
|
||||
|
||||
@@ -10,8 +10,9 @@ _depths() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-a --attr-map -A --auto-attr-map)'{-A,--auto-attr-map}'[Shortcut for -a <file>.attrmap]'
|
||||
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
|
||||
@@ -27,6 +28,7 @@ local args=(
|
||||
'(-Z --columns)'{-Z,--columns}'[Read the image in column-major order]'
|
||||
|
||||
'(-a --attr-map -A --auto-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
|
||||
'(-B --background-color)'{-B,--background-color}'+[Ignore tiles containing only specified color]:color:'
|
||||
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
|
||||
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
|
||||
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#compdef rgblink
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-d --dmg)'{-d,--dmg}'[Enable DMG mode (-w + no VRAM banking)]'
|
||||
'(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_CHARMAP_HPP
|
||||
#define RGBDS_ASM_CHARMAP_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -20,8 +21,11 @@ void charmap_Push();
|
||||
void charmap_Pop();
|
||||
void charmap_CheckStack();
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
||||
bool charmap_HasChar(std::string const &input);
|
||||
bool charmap_HasChar(std::string const &mapping);
|
||||
size_t charmap_CharSize(std::string const &mapping);
|
||||
std::optional<int32_t> charmap_CharValue(std::string const &mapping, size_t idx);
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input);
|
||||
size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output);
|
||||
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique);
|
||||
|
||||
#endif // RGBDS_ASM_CHARMAP_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_FIXPOINT_HPP
|
||||
#define RGBDS_ASM_FIXPOINT_HPP
|
||||
@@ -8,7 +8,6 @@
|
||||
extern uint8_t fixPrecision;
|
||||
|
||||
uint8_t fix_Precision();
|
||||
double fix_PrecisionFactor();
|
||||
int32_t fix_Sin(int32_t i, int32_t q);
|
||||
int32_t fix_Cos(int32_t i, int32_t q);
|
||||
int32_t fix_Tan(int32_t i, int32_t q);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_FORMAT_HPP
|
||||
#define RGBDS_ASM_FORMAT_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Contains some assembler-wide defines and externs
|
||||
|
||||
@@ -41,15 +41,11 @@ struct FileStackNode {
|
||||
std::string const &name() const { return data.get<std::string>(); }
|
||||
|
||||
FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_)
|
||||
: type(type_), data(data_){};
|
||||
: type(type_), data(data_) {}
|
||||
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
|
||||
// If true, entering this context generates a new unique ID.
|
||||
bool generatesUniqueID() const { return type == NODE_REPT || type == NODE_MACRO; }
|
||||
};
|
||||
|
||||
#define DEFAULT_MAX_DEPTH 64
|
||||
extern size_t maxRecursionDepth;
|
||||
|
||||
struct MacroArgs;
|
||||
@@ -75,7 +71,6 @@ void fstk_RunFor(
|
||||
int32_t reptLineNo,
|
||||
ContentSpan const &span
|
||||
);
|
||||
void fstk_StopRept();
|
||||
bool fstk_Break();
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_LEXER_HPP
|
||||
#define RGBDS_ASM_LEXER_HPP
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
// This value is a compromise between `LexerState` allocation performance when `mmap` works, and
|
||||
// buffering performance when it doesn't/can't (e.g. when piping a file into RGBASM).
|
||||
#define LEXER_BUF_SIZE 64
|
||||
static constexpr size_t LEXER_BUF_SIZE = 64;
|
||||
// The buffer needs to be large enough for the maximum `lexerState->peek()` lookahead distance
|
||||
static_assert(LEXER_BUF_SIZE > 1, "Lexer buffer size is too small");
|
||||
// This caps the size of buffer reads, and according to POSIX, passing more than SSIZE_MAX is UB
|
||||
@@ -83,7 +83,6 @@ struct LexerState {
|
||||
LexerMode mode;
|
||||
bool atLineStart;
|
||||
uint32_t lineNo;
|
||||
uint32_t colNo;
|
||||
int lastToken;
|
||||
|
||||
std::deque<IfStackEntry> ifStack;
|
||||
@@ -119,17 +118,8 @@ struct LexerState {
|
||||
extern char binDigits[2];
|
||||
extern char gfxDigits[4];
|
||||
|
||||
static inline void lexer_SetBinDigits(char const digits[2]) {
|
||||
binDigits[0] = digits[0];
|
||||
binDigits[1] = digits[1];
|
||||
}
|
||||
|
||||
static inline void lexer_SetGfxDigits(char const digits[4]) {
|
||||
gfxDigits[0] = digits[0];
|
||||
gfxDigits[1] = digits[1];
|
||||
gfxDigits[2] = digits[2];
|
||||
gfxDigits[3] = digits[3];
|
||||
}
|
||||
void lexer_SetBinDigits(char const digits[2]);
|
||||
void lexer_SetGfxDigits(char const digits[4]);
|
||||
|
||||
bool lexer_AtTopLevel();
|
||||
void lexer_RestartRept(uint32_t lineNo);
|
||||
@@ -147,7 +137,6 @@ void lexer_ReachELSEBlock();
|
||||
|
||||
void lexer_CheckRecursionDepth();
|
||||
uint32_t lexer_GetLineNo();
|
||||
uint32_t lexer_GetColNo();
|
||||
void lexer_DumpStringExpansions();
|
||||
|
||||
struct Capture {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_MACRO_HPP
|
||||
#define RGBDS_ASM_MACRO_HPP
|
||||
@@ -9,11 +9,11 @@
|
||||
#include <vector>
|
||||
|
||||
struct MacroArgs {
|
||||
unsigned int shift;
|
||||
uint32_t shift;
|
||||
std::vector<std::shared_ptr<std::string>> args;
|
||||
|
||||
uint32_t nbArgs() const { return args.size() - shift; }
|
||||
std::shared_ptr<std::string> getArg(uint32_t i) const;
|
||||
std::shared_ptr<std::string> getArg(int32_t i) const;
|
||||
std::shared_ptr<std::string> getAllArgs() const;
|
||||
|
||||
void appendArg(std::shared_ptr<std::string> arg);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_MAIN_HPP
|
||||
#define RGBDS_ASM_MAIN_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_OPT_HPP
|
||||
#define RGBDS_ASM_OPT_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_OUTPUT_HPP
|
||||
#define RGBDS_ASM_OUTPUT_HPP
|
||||
@@ -14,14 +14,7 @@
|
||||
struct Expression;
|
||||
struct FileStackNode;
|
||||
|
||||
enum StateFeature {
|
||||
STATE_EQU,
|
||||
STATE_VAR,
|
||||
STATE_EQUS,
|
||||
STATE_CHAR,
|
||||
STATE_MACRO,
|
||||
NB_STATE_FEATURES
|
||||
};
|
||||
enum StateFeature { STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO, NB_STATE_FEATURES };
|
||||
|
||||
extern std::string objectFileName;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_RPN_HPP
|
||||
#define RGBDS_ASM_RPN_HPP
|
||||
@@ -22,21 +22,8 @@ struct Expression {
|
||||
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
||||
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
||||
|
||||
Expression() = default;
|
||||
Expression(Expression &&) = default;
|
||||
#ifdef _MSC_VER
|
||||
// MSVC and WinFlexBison won't build without this...
|
||||
Expression(Expression const &) = default;
|
||||
#endif
|
||||
|
||||
Expression &operator=(Expression &&) = default;
|
||||
|
||||
bool isKnown() const {
|
||||
return data.holds<int32_t>();
|
||||
}
|
||||
int32_t value() const {
|
||||
return data.get<int32_t>();
|
||||
}
|
||||
bool isKnown() const { return data.holds<int32_t>(); }
|
||||
int32_t value() const { return data.get<int32_t>(); }
|
||||
|
||||
int32_t getConstVal() const;
|
||||
Symbol const *symbolOf() const;
|
||||
@@ -55,6 +42,7 @@ struct Expression {
|
||||
|
||||
bool makeCheckHRAM();
|
||||
void makeCheckRST();
|
||||
void makeCheckBitIndex(uint8_t mask);
|
||||
|
||||
void checkNBit(uint8_t n) const;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_SECTION_HPP
|
||||
#define RGBDS_ASM_SECTION_HPP
|
||||
@@ -91,11 +91,11 @@ void sect_ByteString(std::vector<int32_t> const &string);
|
||||
void sect_WordString(std::vector<int32_t> const &string);
|
||||
void sect_LongString(std::vector<int32_t> const &string);
|
||||
void sect_Skip(uint32_t skip, bool ds);
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs);
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift);
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift);
|
||||
void sect_RelByte(Expression const &expr, uint32_t pcShift);
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs);
|
||||
void sect_RelWord(Expression const &expr, uint32_t pcShift);
|
||||
void sect_RelLong(Expression const &expr, uint32_t pcShift);
|
||||
void sect_PCRelByte(Expression const &expr, uint32_t pcShift);
|
||||
void sect_BinaryFile(std::string const &name, int32_t startPos);
|
||||
void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_SYMBOL_HPP
|
||||
#define RGBDS_ASM_SYMBOL_HPP
|
||||
@@ -82,7 +82,6 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value);
|
||||
Symbol *sym_AddVar(std::string const &symName, int32_t value);
|
||||
int32_t sym_GetRSValue();
|
||||
void sym_SetRSValue(int32_t value);
|
||||
uint32_t sym_GetConstantValue(std::string const &symName);
|
||||
// Find a symbol by exact name, bypassing expansion checks
|
||||
Symbol *sym_FindExactSymbol(std::string const &symName);
|
||||
// Find a symbol, possibly scoped, by name
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_WARNING_HPP
|
||||
#define RGBDS_ASM_WARNING_HPP
|
||||
@@ -62,27 +62,24 @@ extern bool warningsAreErrors;
|
||||
|
||||
void processWarningFlag(char const *flag);
|
||||
|
||||
/*
|
||||
* Used to warn the user about problems that don't prevent the generation of
|
||||
* valid code.
|
||||
*/
|
||||
[[gnu::format(printf, 2, 3)]] void warning(WarningID id, char const *fmt, ...);
|
||||
// Used to warn the user about problems that don't prevent the generation of
|
||||
// valid code.
|
||||
[[gnu::format(printf, 2, 3)]]
|
||||
void warning(WarningID id, char const *fmt, ...);
|
||||
|
||||
/*
|
||||
* Used for errors that compromise the whole assembly process by affecting the
|
||||
* following code, potencially making the assembler generate errors caused by
|
||||
* the first one and unrelated to the code that the assembler complains about.
|
||||
* It is also used when the assembler goes into an invalid state (for example,
|
||||
* when it fails to allocate memory).
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void fatalerror(char const *fmt, ...);
|
||||
// Used for errors that compromise the whole assembly process by affecting the
|
||||
// following code, potencially making the assembler generate errors caused by
|
||||
// the first one and unrelated to the code that the assembler complains about.
|
||||
// It is also used when the assembler goes into an invalid state (for example,
|
||||
// when it fails to allocate memory).
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatalerror(char const *fmt, ...);
|
||||
|
||||
/*
|
||||
* Used for errors that make it impossible to assemble correctly, but don't
|
||||
* affect the following code. The code will fail to assemble but the user will
|
||||
* get a list of all errors at the end, making it easier to fix all of them at
|
||||
* once.
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
|
||||
// Used for errors that make it impossible to assemble correctly, but don't
|
||||
// affect the following code. The code will fail to assemble but the user will
|
||||
// get a list of all errors at the end, making it easier to fix all of them at
|
||||
// once.
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
|
||||
#endif // RGBDS_ASM_WARNING_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||
#define RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||
@@ -6,14 +6,11 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
* Allocator adaptor that interposes construct() calls to convert value-initialization
|
||||
* (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
|
||||
* zero out non-class types).
|
||||
* From
|
||||
* https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
|
||||
*/
|
||||
|
||||
// Allocator adaptor that interposes construct() calls to convert value-initialization
|
||||
// (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
|
||||
// zero out non-class types).
|
||||
// From
|
||||
// https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
|
||||
template<typename T, typename A = std::allocator<T>>
|
||||
class default_init_allocator : public A {
|
||||
using a_t = std::allocator_traits<A>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_EITHER_HPP
|
||||
#define RGBDS_EITHER_HPP
|
||||
@@ -103,7 +103,7 @@ public:
|
||||
} else if (other._tag == other._t2.tag_value) {
|
||||
*this = other._t2.value;
|
||||
} else {
|
||||
_tag = nulltag;
|
||||
_tag = nulltag; // LCOV_EXCL_LINE
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ERROR_HPP
|
||||
#define RGBDS_ERROR_HPP
|
||||
|
||||
extern "C" {
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warn(char const *fmt...);
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warnx(char const *fmt, ...);
|
||||
|
||||
[[gnu::format(printf, 1, 2)]] void warn(char const *fmt...);
|
||||
[[gnu::format(printf, 1, 2)]] void warnx(char const *fmt, ...);
|
||||
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void err(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void errx(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void err(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void errx(char const *fmt, ...);
|
||||
}
|
||||
|
||||
#endif // RGBDS_ERROR_HPP
|
||||
|
||||
16
include/extern/getopt.hpp
vendored
16
include/extern/getopt.hpp
vendored
@@ -1,11 +1,15 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* This implementation was taken from musl and modified for RGBDS */
|
||||
// This implementation was taken from musl and modified for RGBDS
|
||||
|
||||
#ifndef RGBDS_EXTERN_GETOPT_HPP
|
||||
#define RGBDS_EXTERN_GETOPT_HPP
|
||||
|
||||
extern "C" {
|
||||
// clang-format off: vertically align values
|
||||
static constexpr int no_argument = 0;
|
||||
static constexpr int required_argument = 1;
|
||||
static constexpr int optional_argument = 2;
|
||||
// clang-format on
|
||||
|
||||
extern char *musl_optarg;
|
||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
||||
@@ -21,10 +25,4 @@ int musl_getopt_long_only(
|
||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx
|
||||
);
|
||||
|
||||
#define no_argument 0
|
||||
#define required_argument 1
|
||||
#define optional_argument 2
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // RGBDS_EXTERN_GETOPT_HPP
|
||||
|
||||
2
include/extern/utf8decoder.hpp
vendored
2
include/extern/utf8decoder.hpp
vendored
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_EXTERN_UTF8DECODER_HPP
|
||||
#define RGBDS_EXTERN_UTF8DECODER_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_FILE_HPP
|
||||
#define RGBDS_FILE_HPP
|
||||
@@ -18,17 +18,13 @@
|
||||
#include "gfx/main.hpp"
|
||||
|
||||
class File {
|
||||
// Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`.
|
||||
Either<std::streambuf *, std::filebuf> _file;
|
||||
|
||||
public:
|
||||
File() {}
|
||||
~File() { close(); }
|
||||
File() : _file(nullptr) {}
|
||||
|
||||
/**
|
||||
* This should only be called once, and before doing any `->` operations.
|
||||
* Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||
*/
|
||||
// This should only be called once, and before doing any `->` operations.
|
||||
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||
File *open(std::string const &path, std::ios_base::openmode mode) {
|
||||
if (path != "-") {
|
||||
_file.emplace<std::filebuf>();
|
||||
@@ -63,20 +59,6 @@ public:
|
||||
return const_cast<File *>(this)->operator->();
|
||||
}
|
||||
|
||||
File *close() {
|
||||
if (_file.holds<std::filebuf>()) {
|
||||
// This is called by the destructor, and an explicit `close` shouldn't close twice.
|
||||
std::filebuf fileBuf = std::move(_file.get<std::filebuf>());
|
||||
_file.emplace<std::streambuf *>(nullptr);
|
||||
if (fileBuf.close() != nullptr) {
|
||||
return this;
|
||||
}
|
||||
} else if (_file.get<std::streambuf *>() != nullptr) {
|
||||
return this;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char const *c_str(std::string const &path) const {
|
||||
return _file.holds<std::filebuf>() ? path.c_str()
|
||||
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_MAIN_HPP
|
||||
#define RGBDS_GFX_MAIN_HPP
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
struct Options {
|
||||
@@ -21,13 +23,16 @@ struct Options {
|
||||
uint8_t verbosity = 0; // -v
|
||||
|
||||
std::string attrmap{}; // -a, -A
|
||||
std::optional<Rgba> bgColor{}; // -B
|
||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
||||
enum {
|
||||
NO_SPEC,
|
||||
EXPLICIT,
|
||||
EMBEDDED,
|
||||
DMG,
|
||||
} palSpecType = NO_SPEC; // -c
|
||||
std::vector<std::array<std::optional<Rgba>, 4>> palSpec{};
|
||||
uint8_t palSpecDmg = 0;
|
||||
uint8_t bitDepth = 2; // -d
|
||||
std::string inputTileset{}; // -i
|
||||
struct {
|
||||
@@ -48,6 +53,7 @@ struct Options {
|
||||
|
||||
std::string input{}; // positional arg
|
||||
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||
@@ -55,40 +61,40 @@ struct Options {
|
||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
||||
static constexpr uint8_t VERB_TRACE = 5; // Step-by-step algorithm details
|
||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
||||
[[gnu::format(printf, 3, 4)]] void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||
// clang-format on
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||
|
||||
mutable bool hasTransparentPixels = false;
|
||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
||||
|
||||
uint8_t dmgColors[4] = {};
|
||||
uint8_t dmgValue(uint8_t i) const {
|
||||
assume(i < 4);
|
||||
return (palSpecDmg >> (2 * i)) & 0b11;
|
||||
}
|
||||
};
|
||||
|
||||
extern Options options;
|
||||
|
||||
/*
|
||||
* Prints the error count, and exits with failure
|
||||
*/
|
||||
[[noreturn]] void giveUp();
|
||||
/*
|
||||
* If any error has been emitted thus far, calls `giveUp()`.
|
||||
*/
|
||||
// Prints the error count, and exits with failure
|
||||
[[noreturn]]
|
||||
void giveUp();
|
||||
// If any error has been emitted thus far, calls `giveUp()`.
|
||||
void requireZeroErrors();
|
||||
/*
|
||||
* Prints a warning, and does not change the error count
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2)]] void warning(char const *fmt, ...);
|
||||
/*
|
||||
* Prints an error, and increments the error count
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
|
||||
/*
|
||||
* Prints an error, and increments the error count
|
||||
* Does not take format arguments so `format_` and `-Wformat-security` won't complain about
|
||||
* calling `errorMessage(msg)`.
|
||||
*/
|
||||
// Prints a warning, and does not change the error count
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warning(char const *fmt, ...);
|
||||
// Prints an error, and increments the error count
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
// Prints an error, and increments the error count
|
||||
// Does not take format arguments so `format_` and `-Wformat-security` won't complain about
|
||||
// calling `errorMessage(msg)`.
|
||||
void errorMessage(char const *msg);
|
||||
/*
|
||||
* Prints a fatal error, increments the error count, and gives up
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void fatal(char const *fmt, ...);
|
||||
// Prints a fatal error, increments the error count, and gives up
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatal(char const *fmt, ...);
|
||||
|
||||
struct Palette {
|
||||
// An array of 4 GBC-native (RGB555) colors
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
||||
#define RGBDS_GFX_PAL_PACKING_HPP
|
||||
@@ -11,9 +11,7 @@
|
||||
struct Palette;
|
||||
class ProtoPalette;
|
||||
|
||||
/*
|
||||
* Returns which palette each proto-palette maps to, and how many palettes are necessary
|
||||
*/
|
||||
// Returns which palette each proto-palette maps to, and how many palettes are necessary
|
||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
||||
#define RGBDS_GFX_PAL_SORTING_HPP
|
||||
@@ -10,6 +10,10 @@
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
// Allow a slot for every possible CGB color, plus one for transparency
|
||||
// 32 (1 << 5) per channel, times 3 RGB channels = 32768 CGB colors
|
||||
static constexpr size_t NB_COLOR_SLOTS = (1 << (5 * 3)) + 1;
|
||||
|
||||
struct Palette;
|
||||
|
||||
void sortIndexed(
|
||||
@@ -20,7 +24,7 @@ void sortIndexed(
|
||||
png_byte *palAlpha
|
||||
);
|
||||
void sortGrayscale(
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, NB_COLOR_SLOTS> const &colors
|
||||
);
|
||||
void sortRgb(std::vector<Palette> &palettes);
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
||||
#define RGBDS_GFX_PAL_SPEC_HPP
|
||||
|
||||
void parseInlinePalSpec(char const * const arg);
|
||||
void parseInlinePalSpec(char const * const rawArg);
|
||||
void parseExternalPalSpec(char const *arg);
|
||||
void parseDmgPalSpec(char const * const rawArg);
|
||||
|
||||
void parseBackgroundPalSpec(char const *arg);
|
||||
|
||||
#endif // RGBDS_GFX_PAL_SPEC_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PROCESS_HPP
|
||||
#define RGBDS_GFX_PROCESS_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
|
||||
#define RGBDS_GFX_PROTO_PALETTE_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_REVERSE_HPP
|
||||
#define RGBDS_GFX_REVERSE_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_RGBA_HPP
|
||||
#define RGBDS_GFX_RGBA_HPP
|
||||
@@ -13,9 +13,7 @@ struct Rgba {
|
||||
|
||||
constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
: red(r), green(g), blue(b), alpha(a) {}
|
||||
/*
|
||||
* Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
|
||||
*/
|
||||
// Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
|
||||
explicit constexpr Rgba(uint32_t rgba = 0)
|
||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||
|
||||
@@ -32,30 +30,24 @@ struct Rgba {
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
||||
* representation
|
||||
*/
|
||||
// Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
||||
// representation
|
||||
uint32_t toCSS() const {
|
||||
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
|
||||
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
||||
}
|
||||
bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); }
|
||||
bool operator!=(Rgba const &rhs) const { return toCSS() != rhs.toCSS(); }
|
||||
|
||||
/*
|
||||
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||
* Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||
*/
|
||||
bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); }
|
||||
|
||||
// CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||
// Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||
static constexpr uint16_t transparent = 0b1'00000'00000'00000;
|
||||
|
||||
static constexpr uint8_t transparency_threshold = 0x10;
|
||||
bool isTransparent() const { return alpha < transparency_threshold; }
|
||||
static constexpr uint8_t opacity_threshold = 0xF0;
|
||||
bool isOpaque() const { return alpha >= opacity_threshold; }
|
||||
/*
|
||||
* Computes the equivalent CGB color, respects the color curve depending on options
|
||||
*/
|
||||
// Computes the equivalent CGB color, respects the color curve depending on options
|
||||
uint16_t cgbColor() const;
|
||||
|
||||
bool isGray() const { return red == green && green == blue; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_HELPERS_HPP
|
||||
#define RGBDS_HELPERS_HPP
|
||||
@@ -14,7 +14,8 @@
|
||||
#else
|
||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||
// Note that executing this is undefined behavior (declared [[noreturn]], but does return)
|
||||
[[noreturn]] static inline void unreachable_() {
|
||||
[[noreturn]]
|
||||
static inline void unreachable_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -26,8 +27,9 @@
|
||||
// `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
|
||||
#define assume(x) \
|
||||
do { \
|
||||
if (!(x)) \
|
||||
if (!(x)) { \
|
||||
unreachable_(); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
#else
|
||||
@@ -93,15 +95,14 @@ static inline int clz(unsigned int x) {
|
||||
#define CAT(x, y) x##y
|
||||
#define EXPAND_AND_CAT(x, y) CAT(x, y)
|
||||
|
||||
// Obtaining the size of an array; `arr` must be an expression, not a type!
|
||||
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
|
||||
|
||||
// For lack of <ranges>, this adds some more brevity
|
||||
#define RANGE(s) std::begin(s), std::end(s)
|
||||
|
||||
// MSVC does not inline `strlen()` or `.length()` of a constant string, so we use `sizeof`
|
||||
#define QUOTEDSTRLEN(s) (sizeof(s) - 1)
|
||||
// MSVC does not inline `strlen()` or `.length()` of a constant string
|
||||
template<int N>
|
||||
static constexpr int literal_strlen(char const (&)[N]) {
|
||||
return N - 1;
|
||||
}
|
||||
|
||||
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
|
||||
template<typename T>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ITERTOOLS_HPP
|
||||
#define RGBDS_ITERTOOLS_HPP
|
||||
@@ -25,7 +25,6 @@ class EnumSeq {
|
||||
auto operator*() const { return _value; }
|
||||
|
||||
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
||||
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -59,10 +58,6 @@ public:
|
||||
bool operator==(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) == std::get<0>(rhs._iters);
|
||||
}
|
||||
|
||||
bool operator!=(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) != std::get<0>(rhs._iters);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_ASSIGN_HPP
|
||||
#define RGBDS_LINK_ASSIGN_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_MAIN_HPP
|
||||
#define RGBDS_LINK_MAIN_HPP
|
||||
@@ -32,8 +32,9 @@ extern bool disablePadding;
|
||||
// Helper macro for printing verbose-mode messages
|
||||
#define verbosePrint(...) \
|
||||
do { \
|
||||
if (beVerbose) \
|
||||
if (beVerbose) { \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
struct FileStackNode {
|
||||
@@ -58,11 +59,11 @@ struct FileStackNode {
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
};
|
||||
|
||||
[[gnu::format(printf, 3, 4)]] void
|
||||
warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4)]] void
|
||||
error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4), noreturn]] void
|
||||
fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4), noreturn]]
|
||||
void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
|
||||
#endif // RGBDS_LINK_MAIN_HPP
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_OBJECT_HPP
|
||||
#define RGBDS_LINK_OBJECT_HPP
|
||||
|
||||
/*
|
||||
* Read an object (.o) file, and add its info to the data structures.
|
||||
* @param fileName A path to the object file to be read
|
||||
* @param i The ID of the file
|
||||
*/
|
||||
void obj_ReadFile(char const *fileName, unsigned int i);
|
||||
// Read an object (.o) file, and add its info to the data structures.
|
||||
void obj_ReadFile(char const *fileName, unsigned int fileID);
|
||||
|
||||
/*
|
||||
* Sets up object file reading
|
||||
* @param nbFiles The number of object files that will be read
|
||||
*/
|
||||
// Sets up object file reading
|
||||
void obj_Setup(unsigned int nbFiles);
|
||||
|
||||
#endif // RGBDS_LINK_OBJECT_HPP
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_OUTPUT_HPP
|
||||
#define RGBDS_LINK_OUTPUT_HPP
|
||||
|
||||
struct Section;
|
||||
|
||||
/*
|
||||
* Registers a section for output.
|
||||
* @param section The section to add
|
||||
*/
|
||||
// Registers a section for output.
|
||||
void out_AddSection(Section const §ion);
|
||||
|
||||
/*
|
||||
* Finds an assigned section overlapping another one.
|
||||
* @param section The section that is being overlapped
|
||||
* @return A section overlapping it
|
||||
*/
|
||||
// Finds an assigned section overlapping another one.
|
||||
Section const *out_OverlappingSection(Section const §ion);
|
||||
|
||||
/*
|
||||
* Writes all output (bin, sym, map) files.
|
||||
*/
|
||||
// Writes all output (bin, sym, map) files.
|
||||
void out_WriteFiles();
|
||||
|
||||
#endif // RGBDS_LINK_OUTPUT_HPP
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_PATCH_HPP
|
||||
#define RGBDS_LINK_PATCH_HPP
|
||||
|
||||
/*
|
||||
* Checks all assertions
|
||||
* @return true if assertion failed
|
||||
*/
|
||||
// Checks all assertions
|
||||
void patch_CheckAssertions();
|
||||
|
||||
/*
|
||||
* Applies all SECTIONs' patches to them
|
||||
*/
|
||||
// Applies all SECTIONs' patches to them
|
||||
void patch_ApplyPatches();
|
||||
|
||||
#endif // RGBDS_LINK_PATCH_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SDAS_OBJ_HPP
|
||||
#define RGBDS_LINK_SDAS_OBJ_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SECTION_HPP
|
||||
#define RGBDS_LINK_SECTION_HPP
|
||||
@@ -65,29 +65,17 @@ struct Assertion {
|
||||
|
||||
extern std::deque<Assertion> assertions;
|
||||
|
||||
/*
|
||||
* Execute a callback for each section currently registered.
|
||||
* This is to avoid exposing the data structure in which sections are stored.
|
||||
* @param callback The function to call for each structure.
|
||||
*/
|
||||
// Execute a callback for each section currently registered.
|
||||
// This is to avoid exposing the data structure in which sections are stored.
|
||||
void sect_ForEach(void (*callback)(Section &));
|
||||
|
||||
/*
|
||||
* Registers a section to be processed.
|
||||
* @param section The section to register.
|
||||
*/
|
||||
// Registers a section to be processed.
|
||||
void sect_AddSection(std::unique_ptr<Section> &§ion);
|
||||
|
||||
/*
|
||||
* Finds a section by its name.
|
||||
* @param name The name of the section to look for
|
||||
* @return A pointer to the section, or `nullptr` if it wasn't found
|
||||
*/
|
||||
// Finds a section by its name.
|
||||
Section *sect_GetSection(std::string const &name);
|
||||
|
||||
/*
|
||||
* Checks if all sections meet reasonable criteria, such as max size
|
||||
*/
|
||||
// Checks if all sections meet reasonable criteria, such as max size
|
||||
void sect_DoSanityChecks();
|
||||
|
||||
#endif // RGBDS_LINK_SECTION_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SYMBOL_HPP
|
||||
#define RGBDS_LINK_SYMBOL_HPP
|
||||
@@ -41,11 +41,7 @@ void sym_ForEach(void (*callback)(Symbol &));
|
||||
|
||||
void sym_AddSymbol(Symbol &symbol);
|
||||
|
||||
/*
|
||||
* Finds a symbol in all the defined symbols.
|
||||
* @param name The name of the symbol to look for
|
||||
* @return A pointer to the symbol, or `nullptr` if not found.
|
||||
*/
|
||||
// Finds a symbol in all the defined symbols.
|
||||
Symbol *sym_GetSymbol(std::string const &name);
|
||||
|
||||
void sym_DumpLocalAliasedSymbols(std::string const &name);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINKDEFS_HPP
|
||||
#define RGBDS_LINKDEFS_HPP
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
||||
#define RGBDS_OBJECT_REV 11U
|
||||
#define RGBDS_OBJECT_REV 12U
|
||||
|
||||
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
|
||||
|
||||
@@ -52,6 +52,7 @@ enum RPNCommand {
|
||||
|
||||
RPN_HRAM = 0x60,
|
||||
RPN_RST = 0x61,
|
||||
RPN_BIT_INDEX = 0x62,
|
||||
|
||||
RPN_HIGH = 0x70,
|
||||
RPN_LOW = 0x71,
|
||||
@@ -92,29 +93,19 @@ extern struct SectionTypeInfo {
|
||||
uint32_t lastBank;
|
||||
} sectionTypeInfo[SECTTYPE_INVALID];
|
||||
|
||||
/*
|
||||
* Tells whether a section has data in its object file definition,
|
||||
* depending on type.
|
||||
* @param type The section's type
|
||||
* @return `true` if the section's definition includes data
|
||||
*/
|
||||
// Tells whether a section has data in its object file definition,
|
||||
// depending on type.
|
||||
static inline bool sect_HasData(SectionType type) {
|
||||
assume(type != SECTTYPE_INVALID);
|
||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes a memory region's end address (last byte), eg. 0x7FFF
|
||||
* @return The address of the last byte in that memory region
|
||||
*/
|
||||
// Returns a memory region's end address (last byte), e.g. 0x7FFF
|
||||
static inline uint16_t endaddr(SectionType type) {
|
||||
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes a memory region's number of banks
|
||||
* @return The number of banks, 1 for regions without banking
|
||||
*/
|
||||
// Returns a memory region's number of banks, or 1 for regions without banking
|
||||
static inline uint32_t nbbanks(SectionType type) {
|
||||
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_OP_MATH_HPP
|
||||
#define RGBDS_OP_MATH_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// platform-specific hacks
|
||||
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_UTIL_HPP
|
||||
#define RGBDS_UTIL_HPP
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
bool startsIdentifier(int c);
|
||||
bool continuesIdentifier(int c);
|
||||
|
||||
char const *printChar(int c);
|
||||
|
||||
/*
|
||||
* @return The number of bytes read, or 0 if invalid data was found
|
||||
*/
|
||||
size_t readUTF8Char(std::vector<int32_t> *dest, char const *src);
|
||||
|
||||
#endif // RGBDS_UTIL_HPP
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_VERSION_HPP
|
||||
#define RGBDS_VERSION_HPP
|
||||
|
||||
extern "C" {
|
||||
|
||||
#define PACKAGE_VERSION_MAJOR 0
|
||||
#define PACKAGE_VERSION_MINOR 9
|
||||
#define PACKAGE_VERSION_PATCH 0
|
||||
#define PACKAGE_VERSION_PATCH 3
|
||||
|
||||
char const *get_package_version_string();
|
||||
}
|
||||
|
||||
#endif // RGBDS_VERSION_H
|
||||
|
||||
11
man/gbz80.7
11
man/gbz80.7
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd June 30, 2025
|
||||
.Dt GBZ80 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -615,9 +615,6 @@ to the adjustment.
|
||||
.It
|
||||
Subtract the adjustment from
|
||||
.Sy A .
|
||||
.It
|
||||
Set the carry flag if borrow (i.e. if adjustment >
|
||||
.Sy A ) .
|
||||
.El
|
||||
.It If the subtract flag Sy N No is not set:
|
||||
.Bl -enum -compact
|
||||
@@ -639,15 +636,13 @@ to the adjustment.
|
||||
If the carry flag is set or
|
||||
.Sy A
|
||||
>
|
||||
.Ad $9F ,
|
||||
.Ad $99 ,
|
||||
then add
|
||||
.Ad $60
|
||||
to the adjustment.
|
||||
to the adjustment and set the carry flag.
|
||||
.It
|
||||
Add the adjustment to
|
||||
.Sy A .
|
||||
.It
|
||||
Set the carry flag if overflow from bit 7.
|
||||
.El
|
||||
.El
|
||||
.Pp
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd June 30, 2025
|
||||
.Dt RGBASM-OLD 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -15,7 +15,7 @@ assembly language over its decades of evolution, along with their modern alterna
|
||||
Its goal is to be a reference for backwards incompatibility, when upgrading an old assembly codebase to work with the latest RGBDS release.
|
||||
It does
|
||||
.Em not
|
||||
attempt to list syntax bugs that were fixed, nor new reserved keywords that may conflict with old identifiers.
|
||||
attempt to list every syntax bug that was ever fixed (with some notable exceptions), nor new reserved keywords that may conflict with old identifiers.
|
||||
.Sh REMOVED
|
||||
These are features which have been completely removed, without any direct alternatives.
|
||||
Usually these features were limiting the addition of other features, or had awkward limits on their own intended effects.
|
||||
@@ -266,6 +266,14 @@ Instead, use
|
||||
.Ql LDH [C], A
|
||||
and
|
||||
.Ql LDH A, [C] .
|
||||
.Pp
|
||||
Note that
|
||||
.Ql LD [$FF00+C], A
|
||||
and
|
||||
.Ql LD A, [$FF00+C]
|
||||
were also deprecated in 0.9.0, but were
|
||||
.Em undeprecated
|
||||
in 0.9.1.
|
||||
.Ss LDH [n8], A and LDH A, [n8]
|
||||
Deprecated in 0.9.0.
|
||||
.Pp
|
||||
@@ -288,7 +296,7 @@ Deprecated in 0.3.0, removed in 0.4.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LD HL, SP + e8 .
|
||||
.Ss LDHL, SP, e8
|
||||
.Ss LDHL SP, e8
|
||||
Supported in ASMotor, removed in RGBDS.
|
||||
.Pp
|
||||
Instead, use
|
||||
@@ -360,6 +368,68 @@ Previously we had
|
||||
.Pp
|
||||
Instead, now we have
|
||||
.Ql p ** q ** r == p ** (q ** r) .
|
||||
.Sh BUGS
|
||||
These are misfeatures that may have been possible by mistake.
|
||||
They do not get deprecated, just fixed.
|
||||
.Ss Space between exported labels' colons
|
||||
Fixed in 0.7.0.
|
||||
.Pp
|
||||
Labels with two colons used to ignore a space between them; for example,
|
||||
.Ql Label:\ : .
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql Label:: .
|
||||
.Ss Space between label and colon
|
||||
Fixed in 0.9.0.
|
||||
.Pp
|
||||
Space between a label and its colon(s) used to be ignored; for example,
|
||||
.Ql Label\ :
|
||||
and
|
||||
.Ql Label\ :: .
|
||||
Now they are treated as invocations of the
|
||||
.Ql Label
|
||||
macro with
|
||||
.Ql \&:
|
||||
and
|
||||
.Ql ::
|
||||
as arguments.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql Label:
|
||||
and
|
||||
.Ql Label:: .
|
||||
.Ss ADD r16 with implicit first HL operand
|
||||
Fixed in 0.5.0.
|
||||
.Pp
|
||||
For example,
|
||||
.Ql ADD BC
|
||||
used to be treated as
|
||||
.Ql ADD HL, BC ,
|
||||
and likewise for
|
||||
.Ql DE ,
|
||||
.Ql HL ,
|
||||
and
|
||||
.Ql SP .
|
||||
.Pp
|
||||
Instead, use an explicit first
|
||||
.Ql HL
|
||||
operand.
|
||||
.Ss = instead of SET
|
||||
Fixed in 0.4.0.
|
||||
.Pp
|
||||
The
|
||||
.Ic =
|
||||
operator used to be an alias for the
|
||||
.Ic SET
|
||||
keyword, which included using
|
||||
.Ic =
|
||||
for the
|
||||
.Ic SET
|
||||
.Em instruction .
|
||||
.Pp
|
||||
Instead, just use
|
||||
.Ic SET
|
||||
for the instruction.
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 1 ,
|
||||
.Xr gbz80 7 ,
|
||||
|
||||
39
man/rgbasm.1
39
man/rgbasm.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd June 30, 2025
|
||||
.Dt RGBASM 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy assembler
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl EVvw
|
||||
.Op Fl EhVvw
|
||||
.Op Fl b Ar chars
|
||||
.Op Fl D Ar name Ns Op = Ns Ar value
|
||||
.Op Fl g Ar chars
|
||||
@@ -51,8 +51,19 @@ is invalid because it could also be
|
||||
The arguments are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl b Ar chars , Fl \-binary-digits Ar chars
|
||||
Change the two characters used for binary constants.
|
||||
The defaults are 01.
|
||||
Allow two characters to be used for binary constants in addition to the default
|
||||
.Sq 0
|
||||
and
|
||||
.Sq 1 .
|
||||
Valid characters are numbers other than
|
||||
.Sq 0
|
||||
and
|
||||
.Sq 1 ,
|
||||
letters,
|
||||
.Sq \&. ,
|
||||
.Sq # ,
|
||||
or
|
||||
.Sq @ .
|
||||
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl \-define Ar name Ns Oo = Ns Ar value Oc
|
||||
Add a string symbol to the compiled source code.
|
||||
This is equivalent to
|
||||
@@ -65,8 +76,24 @@ is not specified.
|
||||
.It Fl E , Fl \-export-all
|
||||
Export all labels, including unreferenced and local labels.
|
||||
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
|
||||
Change the four characters used for gfx constants.
|
||||
Allow four characters to be used for graphics constants in addition to the default
|
||||
.Sq 0 ,
|
||||
.Sq 1 ,
|
||||
.Sq 2 ,
|
||||
and
|
||||
.Sq 3 .
|
||||
Valid characters are numbers other than
|
||||
.Sq 0
|
||||
to
|
||||
.Sq 3 ,
|
||||
letters,
|
||||
.Sq \&. ,
|
||||
.Sq # ,
|
||||
or
|
||||
.Sq @ .
|
||||
The defaults are 0123.
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl I Ar path , Fl \-include Ar path
|
||||
Add a new
|
||||
.Dq include path ;
|
||||
@@ -273,7 +300,7 @@ This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wbuiltin-args
|
||||
Warn about incorrect arguments to built-in functions, such as
|
||||
.Fn STRSUB
|
||||
.Fn STRSLICE
|
||||
with indexes outside of the string's bounds.
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
|
||||
79
man/rgbasm.5
79
man/rgbasm.5
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd June 30, 2025
|
||||
.Dt RGBASM 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -548,7 +548,7 @@ There are a number of escape sequences you can use within a string:
|
||||
.El
|
||||
.Pp
|
||||
Multi-line strings are contained in triple quotes
|
||||
.Pq Ql \&"\&"\&"for instance\&"\&"\&" .
|
||||
.Pq Ql \&"\&"\&"for instance""" .
|
||||
Escape sequences work the same way in multi-line strings; however, literal newline characters will be included as-is, without needing to escape them with
|
||||
.Ql \er
|
||||
or
|
||||
@@ -560,26 +560,30 @@ Inside them, backslashes and braces are treated like regular characters, so they
|
||||
For example, the raw string
|
||||
.Ql #"\et\e1{s}\e"
|
||||
is equivalent to the regular string
|
||||
.Ql "\e\et\e\e1\e{s}\e\e" .
|
||||
.Ql \&"\e\et\e\e1\e{s}\e\e" .
|
||||
(Note that this prevents raw strings from including the double quote character.)
|
||||
Raw strings also may be contained in triple quotes for them to be multi-line, so they can include literal newline or quote characters (although still not three quotes in a row).
|
||||
.Pp
|
||||
The following functions operate on string expressions.
|
||||
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
|
||||
.Bl -column "STRSUB(str, pos, len)"
|
||||
You can use the
|
||||
.Sq ++
|
||||
operator to concatenate two strings.
|
||||
.Ql \&"str" ++ \&"ing"
|
||||
is equivalent to
|
||||
.Ql \&"string" ,
|
||||
or to
|
||||
.Ql STRCAT("str", \&"ing") .
|
||||
.Pp
|
||||
The following functions operate on string expressions, and return strings themselves.
|
||||
.Bl -column "STRSLICE(str, start, stop)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||
.It Fn STRCAT strs... Ta Concatenates Ar strs .
|
||||
.It Fn STRCMP str1 str2 Ta Returns -1 if Ar str1 No is alphabetically lower than Ar str2 No , zero if they match, 1 if Ar str1 No is greater than Ar str2 .
|
||||
.It Fn STRIN str1 str2 Ta Returns the first position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
||||
.It Fn STRRIN str1 str2 Ta Returns the last position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
||||
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos No (first character is position 1, last is position -1) and Ar len No characters long. If Ar len No is not specified the substring continues to the end of Ar str .
|
||||
.It Fn STRUPR str Ta Returns Ar str No with all ASCII letters
|
||||
.Pq Ql a-z
|
||||
in uppercase.
|
||||
.It Fn STRLWR str Ta Returns Ar str No with all ASCII letters
|
||||
.Pq Ql A-Z
|
||||
in lowercase.
|
||||
.It Fn STRSLICE str start stop Ta Returns a substring of Ar str No starting at Ar start No and ending at Ar stop No (exclusive). If Ar stop No is not specified, the substring continues to the end of Ar str .
|
||||
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
|
||||
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
||||
.Ql %spec
|
||||
@@ -589,9 +593,42 @@ pattern replaced by interpolating the format
|
||||
with its corresponding argument in
|
||||
.Ar args
|
||||
.Pq So %% Sc is replaced by the So % Sc character .
|
||||
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, and 0 otherwise .
|
||||
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap .
|
||||
.It Fn CHARSUB str pos Ta Returns the substring for the charmap entry at Ar pos No in Ar str No (first character is position 1, last is position -1) with the current charmap .
|
||||
.It Fn STRCHAR str idx Ta Returns the substring of Ar str No for the charmap entry at Ar idx No with the current charmap . Pq Ar idx No counts charmap entries, not characters.
|
||||
.It Fn REVCHAR vals... Ta Returns the string that is mapped to Ar vals No with the current charmap. If there is no unique charmap entry for Ar vals Ns , an error occurs.
|
||||
.El
|
||||
.Pp
|
||||
The following functions operate on string expressions, but return integers.
|
||||
.Bl -column "STRRFIND(str, sub)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||
.It Fn STRCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to ASCII ordering of their characters. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
|
||||
.It Fn STRFIND str sub Ta Returns the first index of Ar sub No in Ar str Ns , or -1 if it's not present.
|
||||
.It Fn STRRFIND str sub Ta Returns the last index of Ar sub No in Ar str Ns , or -1 if it's not present.
|
||||
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, or 0 otherwise.
|
||||
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap.
|
||||
.It Fn CHARCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to their charmap entry values with the current charmap. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
|
||||
.It Fn CHARSIZE char Ta Returns how many values are in the charmap entry for Ar char No with the current charmap.
|
||||
.It Fn CHARVAL char idx Ta Returns the value at Ar idx No of the charmap entry for Ar char .
|
||||
.El
|
||||
.Pp
|
||||
Note that indexes count starting from 0 at the beginning, or from -1 at the end.
|
||||
The characters of a string are counted by
|
||||
.Ql STRLEN ;
|
||||
the charmap entries of a string are counted by
|
||||
.Ql CHARLEN ;
|
||||
and the values of a charmap entry are counted by
|
||||
.Ql CHARSIZE .
|
||||
.Pp
|
||||
The following legacy functions are similar to other functions that operate on string expressions, but for historical reasons, they count starting from
|
||||
.Em position 1 ,
|
||||
not from index 0!
|
||||
(Position -1 still counts from the end.)
|
||||
.Bl -column "STRSUB(str, pos, len)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRSUB str pos len Ta Returns a substring of Ar str No starting at Ar pos No and Ar len No characters long. If Ar len No is not specified, the substring continues to the end of Ar str No .
|
||||
.It Fn STRIN str sub Ta Returns the first position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||
.It Fn STRRIN str sub Ta Returns the last position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||
.It Fn CHARSUB str pos Ta Returns the substring of Ar str No for the charmap entry at Ar pos No with the current charmap . Pq Ar pos No counts charmap entries, not characters.
|
||||
.El
|
||||
.Ss Character maps
|
||||
When writing text strings that are meant to be displayed on the Game Boy, the character encoding in the ROM may need to be different than the source file encoding.
|
||||
@@ -1052,7 +1089,7 @@ and
|
||||
.Ic WRAMX
|
||||
types are still considered different.
|
||||
.It
|
||||
Different constraints (alignment, bank, etc.) can be specified for each unionized section declaration, but they must all be compatible.
|
||||
Different constraints (alignment, bank, etc.) can be specified for each section fragment declaration, but they must all be compatible.
|
||||
For example, alignment must be compatible with any fixed address, all specified banks must be the same, etc.
|
||||
.It
|
||||
A section fragment may not be unionized; after all, that wouldn't make much sense.
|
||||
@@ -1104,7 +1141,9 @@ Additionally, label names can contain up to a single dot
|
||||
.Ql \&. ,
|
||||
which may not be the first character.
|
||||
.Pp
|
||||
A symbol cannot have the same name as a reserved keyword, unless it is prefixed by a hash
|
||||
A symbol cannot have the same name as a reserved keyword, unless its name is a
|
||||
.Dq raw identifier
|
||||
prefixed by a hash
|
||||
.Sq # .
|
||||
For example,
|
||||
.Ql #load
|
||||
@@ -1279,7 +1318,7 @@ it at the same time.
|
||||
below).
|
||||
.Ss Numeric constants
|
||||
.Ic EQU
|
||||
is used to define immutable numeric symbols.
|
||||
is used to define numeric constant symbols.
|
||||
Unlike
|
||||
.Sq =
|
||||
above, constants defined this way cannot be redefined.
|
||||
@@ -1387,6 +1426,8 @@ This expansion is disabled in a few contexts:
|
||||
and
|
||||
.Ql MACRO name
|
||||
will not expand string constants in their names.
|
||||
Expansion is also disabled if the string constant's name is a raw identifier prefixed by a hash
|
||||
.Sq # .
|
||||
.Bd -literal -offset indent
|
||||
DEF COUNTREG EQUS "[hl+]"
|
||||
ld a, COUNTREG
|
||||
@@ -1852,9 +1893,11 @@ being the second, and so on. Since there are only nine digits, you can only use
|
||||
To use the rest, you put the argument number in angle brackets, like
|
||||
.Ic \e<10> .
|
||||
.Pp
|
||||
This bracketed syntax supports decimal numbers and numeric constant symbols.
|
||||
This bracketed syntax supports decimal numbers and numeric symbols, where negative values count from the last argument.
|
||||
For example,
|
||||
.Ql \e<_NARG>
|
||||
or
|
||||
.Ql \e<-1>
|
||||
will get the last argument.
|
||||
.Pp
|
||||
Other macro arguments and symbol interpolations will also be expanded inside the angle brackets.
|
||||
|
||||
15
man/rgbds.5
15
man/rgbds.5
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd June 30, 2025
|
||||
.Dt RGBDS 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -388,10 +388,19 @@ The value is then ANDed with $00FF
|
||||
check.
|
||||
Checks if the value is a valid
|
||||
.Ql rst
|
||||
.Pq see Do RST vec Dc in Xr gbz80 7
|
||||
vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
||||
vector
|
||||
.Pq see Do RST vec Dc in Xr gbz80 7 ,
|
||||
that is, one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
||||
The value is then ORed with $C7
|
||||
.Pq Ql \&| $C7 .
|
||||
.It Li $62 Ta Ql bit/res/set
|
||||
check; followed by the instruction's
|
||||
.Cm BYTE
|
||||
mask.
|
||||
Checks if the value is a valid bit index
|
||||
.Pq see e.g. Do BIT u3, r8 Dc in Xr gbz80 7 ,
|
||||
that is, from 0 to 7.
|
||||
The value is then ORed with the instruction's mask.
|
||||
.It Li $80 Ta Integer literal; followed by the
|
||||
.Cm LONG
|
||||
integer.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd June 30, 2025
|
||||
.Dt RGBDS 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -51,6 +51,10 @@ adapts the code to be more UNIX-like and releases this version as rgbds-linux.
|
||||
forks Nossum's repository.
|
||||
The fork becomes the reference implementation of RGBDS.
|
||||
.It
|
||||
2010-09-25: S\(/orensen continues development of
|
||||
.Lk https://github.com/asmotor/asmotor ASMotor
|
||||
to this day.
|
||||
.It
|
||||
2015-01-18:
|
||||
.An stag019
|
||||
begins implementing RGBGFX, a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
|
||||
10
man/rgbfix.1
10
man/rgbfix.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd June 30, 2025
|
||||
.Dt RGBFIX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy header utility and checksum fixer
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl jOsVv
|
||||
.Op Fl hjOsVv
|
||||
.Op Fl C | c
|
||||
.Op Fl f Ar fix_spec
|
||||
.Op Fl i Ar game_id
|
||||
@@ -17,6 +17,7 @@
|
||||
.Op Fl l Ar licensee_id
|
||||
.Op Fl m Ar mbc_type
|
||||
.Op Fl n Ar rom_version
|
||||
.Op Fl o Ar out_file
|
||||
.Op Fl p Ar pad_value
|
||||
.Op Fl r Ar ram_size
|
||||
.Op Fl t Ar title_str
|
||||
@@ -91,6 +92,8 @@ Fix the global checksum
|
||||
.It Cm G
|
||||
Trash the global checksum.
|
||||
.El
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl i Ar game_id , Fl \-game-id Ar game_id
|
||||
Set the game ID string
|
||||
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
||||
@@ -132,6 +135,9 @@ Set the ROM version
|
||||
to a given value from 0 to 0xFF.
|
||||
.It Fl O , Fl \-overwrite
|
||||
Allow overwriting different non-zero bytes in the header without a warning being emitted.
|
||||
.It Fl o Ar out_file , Fl \-output Ar out_file
|
||||
Write the modified ROM image to the given file, or '-' to write to standard output.
|
||||
If not specified, the input files are modified in-place, or written to standard output if read from standard input.
|
||||
.It Fl p Ar pad_value , Fl \-pad-value Ar pad_value
|
||||
Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
|
||||
.Nm
|
||||
|
||||
41
man/rgbgfx.1
41
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd June 30, 2025
|
||||
.Dt RGBGFX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -10,7 +10,7 @@
|
||||
.Nd Game Boy graphics converter
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl CmOuVXYZ
|
||||
.Op Fl CmhOuVXYZ
|
||||
.Op Fl v Op Fl v No ...
|
||||
.Op Fl a Ar attrmap | Fl A
|
||||
.Op Fl b Ar base_ids
|
||||
@@ -103,6 +103,19 @@ and has the same size.
|
||||
Same as
|
||||
.Fl a Ar base_path Ns .attrmap
|
||||
.Pq see Sx Automatic output paths .
|
||||
.It Fl B Ar color , Fl \-background-color Ar color
|
||||
Set a background color to be omitted from output.
|
||||
Colors are accepted in
|
||||
.Ql #rgb
|
||||
or
|
||||
.Ql #rrggbb
|
||||
format, or as
|
||||
.Ql transparent .
|
||||
Input tiles which are entirely the specified background color are ignored and will not be output in tile data file.
|
||||
The tilemap, atrribute map, or palette map files
|
||||
.Em will
|
||||
use placeholder values where background tiles were.
|
||||
If a background color is specified, it cannot be used within tiles which are not ignored.
|
||||
.It Fl b Ar base_ids , Fl \-base-tiles Ar base_ids
|
||||
Set the base IDs for tile map output.
|
||||
.Ar base_ids
|
||||
@@ -126,7 +139,7 @@ begins with a hash character
|
||||
.Ql # ,
|
||||
it is treated as an inline palette specification.
|
||||
It should contain a comma-separated list of hexadecimal colors, each beginning with a hash.
|
||||
Colors are accepted either as
|
||||
Colors are accepted in
|
||||
.Ql #rgb
|
||||
or
|
||||
.Ql #rrggbb
|
||||
@@ -146,6 +159,24 @@ is the case-insensitive word
|
||||
then the first four colors of the input PNG's embedded palette are used.
|
||||
It is an error if the PNG is not indexed, or if colors other than these 4 are used.
|
||||
.Pq This is different from the default behavior of indexed PNGs, as then unused entries in the embedded palette are ignored, whereas they are not with Fl c Cm embedded .
|
||||
.It Sy DMG palette spec
|
||||
If
|
||||
.Ar pal_spec
|
||||
starts with case-insensitive
|
||||
.Cm dmg= ,
|
||||
then the following two-digit hexadecimal number specifies four grayscale DMG color indexes.
|
||||
The number functions like the DMG's $FF47
|
||||
.Sy BGP
|
||||
register
|
||||
(see
|
||||
.Lk https://gbdev.io/pandocs/Palettes.html Pan Docs
|
||||
for more information):
|
||||
the low two bits 0-1 specify which gray shade goes in color index 0,
|
||||
the next two bits 2-3 specify which gray shade goes in color index 1,
|
||||
and so on.
|
||||
Gray shade 0 is the lightest (white), 3 is the darkest (black).
|
||||
The same gray shade cannot go in two color indexes.
|
||||
To specify a DMG palette, the input PNG must have all its colors in shades of gray, without any transparent colors.
|
||||
.It Sy external palette spec
|
||||
Otherwise,
|
||||
.Ar pal_spec
|
||||
@@ -165,6 +196,8 @@ for a list of formats and their descriptions.
|
||||
.It Fl d Ar depth , Fl \-depth Ar depth
|
||||
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
|
||||
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles
|
||||
Use the specified input tiles in addition to having
|
||||
.Nm
|
||||
@@ -513,6 +546,8 @@ Otherwise, if the PNG only contains shades of gray, they will be categorized int
|
||||
.Dq bins
|
||||
as there are colors per palette, and the palette is set to these bins.
|
||||
The darkest gray will end up in bin #0, and so on; note that this is the opposite of the RGB method below.
|
||||
This is equivalent to having specified a DMG palette of
|
||||
.Fl c Cm dmg=E4 .
|
||||
If two distinct grays end up in the same bin, the RGB method is used instead.
|
||||
.Pp
|
||||
Be careful that
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd June 30, 2025
|
||||
.Dt RGBLINK 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy linker
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl dMtVvwx
|
||||
.Op Fl dhMtVvwx
|
||||
.Op Fl l Ar linker_script
|
||||
.Op Fl m Ar map_file
|
||||
.Op Fl n Ar sym_file
|
||||
@@ -67,6 +67,8 @@ Enable DMG mode.
|
||||
Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1.
|
||||
This option automatically enables
|
||||
.Fl w .
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl l Ar linker_script , Fl \-linkerscript Ar linker_script
|
||||
Specify a linker script file that tells the linker how sections must be placed in the ROM.
|
||||
The attributes assigned in the linker script must be consistent with any assigned in the code.
|
||||
@@ -119,8 +121,8 @@ WRAMX sections that are fixed to a bank other than 1 become errors, other WRAMX
|
||||
Disables padding the end of the final file.
|
||||
This option automatically enables
|
||||
.Fl t .
|
||||
You can use this when not not making a ROM.
|
||||
When making a ROM, be careful that not using this is not a replacement for
|
||||
You can use this to make binary files that are not a ROM.
|
||||
When making a ROM, note that not using this is not a replacement for
|
||||
.Xr rgbfix 1 Ap s Fl p
|
||||
option!
|
||||
.El
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd June 30, 2025
|
||||
.Dt RGBLINK 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -120,7 +120,7 @@ causes all sections between it and the next
|
||||
.Ic ORG
|
||||
or bank specification to be placed at addresses automatically determined by
|
||||
.Nm .
|
||||
.Pq It is, however, compatible with Ic ALIGN No below.
|
||||
.Pq \&It is, however, compatible with Ic ALIGN No below.
|
||||
.Pp
|
||||
.Ql Ic ALIGN Ar addr , Ar offset
|
||||
increases the
|
||||
|
||||
@@ -9,10 +9,10 @@ set(common_src
|
||||
)
|
||||
|
||||
find_package(BISON 3.0.0 REQUIRED)
|
||||
set(BISON_FLAGS "-Wall -Dparse.lac=full -Dlr.type=ielr")
|
||||
set(BISON_FLAGS "-Wall -Dlr.type=ielr")
|
||||
# Set some optimization flags on versions that support them
|
||||
if(BISON_VERSION VERSION_GREATER_EQUAL "3.5")
|
||||
set(BISON_FLAGS "${BISON_FLAGS} -Dapi.token.raw=true")
|
||||
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full -Dapi.token.raw=true")
|
||||
endif()
|
||||
if(BISON_VERSION VERSION_GREATER_EQUAL "3.6")
|
||||
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
@@ -30,6 +31,29 @@ struct CharmapNode {
|
||||
struct Charmap {
|
||||
std::string name;
|
||||
std::vector<CharmapNode> nodes; // first node is reserved for the root node
|
||||
|
||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||
template<typename F>
|
||||
bool forEachChar(F callback) const {
|
||||
// clang-format off: nested initializers
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||
// clang-format on
|
||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||
prefixes.pop();
|
||||
CharmapNode const &node = nodes[nodeIdx];
|
||||
if (node.isTerminal()) {
|
||||
if (!callback(nodeIdx, mapping)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (unsigned c = 0; c < std::size(node.next); c++) {
|
||||
if (size_t nextIdx = node.next[c]; nextIdx) {
|
||||
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static std::deque<Charmap> charmapList;
|
||||
@@ -43,22 +67,16 @@ bool charmap_ForEach(
|
||||
void (*charFunc)(std::string const &, std::vector<int32_t>)
|
||||
) {
|
||||
for (Charmap const &charmap : charmapList) {
|
||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||
std::map<size_t, std::string> mappings;
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||
prefixes.pop();
|
||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||
if (node.isTerminal())
|
||||
mappings[nodeIdx] = mapping;
|
||||
for (unsigned c = 0; c < 256; c++) {
|
||||
if (size_t nextIdx = node.next[c]; nextIdx)
|
||||
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
||||
}
|
||||
}
|
||||
charmap.forEachChar([&mappings](size_t nodeIdx, std::string const &mapping) {
|
||||
mappings[nodeIdx] = mapping;
|
||||
return true;
|
||||
});
|
||||
|
||||
mapFunc(charmap.name);
|
||||
for (auto [nodeIdx, mapping] : mappings)
|
||||
for (auto [nodeIdx, mapping] : mappings) {
|
||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||
}
|
||||
}
|
||||
return !charmapList.empty();
|
||||
}
|
||||
@@ -67,10 +85,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
size_t baseIdx = SIZE_MAX;
|
||||
|
||||
if (baseName != nullptr) {
|
||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end())
|
||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end()) {
|
||||
error("Base charmap '%s' doesn't exist\n", baseName->c_str());
|
||||
else
|
||||
} else {
|
||||
baseIdx = search->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (charmapMap.find(name) != charmapMap.end()) {
|
||||
@@ -82,10 +101,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
charmapMap[name] = charmapList.size();
|
||||
Charmap &charmap = charmapList.emplace_back();
|
||||
|
||||
if (baseIdx != SIZE_MAX)
|
||||
if (baseIdx != SIZE_MAX) {
|
||||
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
|
||||
else
|
||||
} else {
|
||||
charmap.nodes.emplace_back(); // Zero-init the root node
|
||||
}
|
||||
|
||||
charmap.name = name;
|
||||
|
||||
@@ -93,10 +113,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
}
|
||||
|
||||
void charmap_Set(std::string const &name) {
|
||||
if (auto search = charmapMap.find(name); search == charmapMap.end())
|
||||
if (auto search = charmapMap.find(name); search == charmapMap.end()) {
|
||||
error("Charmap '%s' doesn't exist\n", name.c_str());
|
||||
else
|
||||
} else {
|
||||
currentCharmap = &charmapList[search->second];
|
||||
}
|
||||
}
|
||||
|
||||
void charmap_Push() {
|
||||
@@ -146,30 +167,59 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
|
||||
CharmapNode &node = charmap.nodes[nodeIdx];
|
||||
|
||||
if (node.isTerminal())
|
||||
if (node.isTerminal()) {
|
||||
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
||||
}
|
||||
|
||||
std::swap(node.value, value);
|
||||
}
|
||||
|
||||
bool charmap_HasChar(std::string const &input) {
|
||||
bool charmap_HasChar(std::string const &mapping) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : input) {
|
||||
for (char c : mapping) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
|
||||
if (!nodeIdx)
|
||||
if (!nodeIdx) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return charmap.nodes[nodeIdx].isTerminal();
|
||||
}
|
||||
|
||||
static CharmapNode const *charmapEntry(std::string const &mapping) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : mapping) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
|
||||
if (!nodeIdx) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return &charmap.nodes[nodeIdx];
|
||||
}
|
||||
|
||||
size_t charmap_CharSize(std::string const &mapping) {
|
||||
CharmapNode const *node = charmapEntry(mapping);
|
||||
return node && node->isTerminal() ? node->value.size() : 0;
|
||||
}
|
||||
|
||||
std::optional<int32_t> charmap_CharValue(std::string const &mapping, size_t idx) {
|
||||
if (CharmapNode const *node = charmapEntry(mapping);
|
||||
node && node->isTerminal() && idx < node->value.size()) {
|
||||
return node->value[idx];
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
||||
std::vector<int32_t> output;
|
||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);)
|
||||
;
|
||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);) {}
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -186,8 +236,9 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
for (size_t nodeIdx = 0; inputIdx < input.length();) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(input[inputIdx])];
|
||||
|
||||
if (!nodeIdx)
|
||||
if (!nodeIdx) {
|
||||
break;
|
||||
}
|
||||
|
||||
inputIdx++; // Consume that char
|
||||
|
||||
@@ -207,27 +258,42 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
if (matchIdx) { // A match was found, use it
|
||||
std::vector<int32_t> const &value = charmap.nodes[matchIdx].value;
|
||||
|
||||
if (output)
|
||||
if (output) {
|
||||
output->insert(output->end(), RANGE(value));
|
||||
}
|
||||
|
||||
matchLen = value.size();
|
||||
} else if (inputIdx < input.length()) { // No match found, but there is some input left
|
||||
int firstChar = input[inputIdx];
|
||||
size_t codepointLen = 0;
|
||||
// This will write the codepoint's value to `output`, little-endian
|
||||
size_t codepointLen = readUTF8Char(output, input.data() + inputIdx);
|
||||
for (uint32_t state = 0, codepoint = 0; inputIdx + codepointLen < input.length();) {
|
||||
if (decode(&state, &codepoint, input[inputIdx + codepointLen]) == 1) {
|
||||
error("Input string is not valid UTF-8\n");
|
||||
codepointLen = 1;
|
||||
break;
|
||||
}
|
||||
codepointLen++;
|
||||
if (state == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (codepointLen == 0)
|
||||
error("Input string is not valid UTF-8\n");
|
||||
if (output) {
|
||||
output->insert(
|
||||
output->end(), input.data() + inputIdx, input.data() + inputIdx + codepointLen
|
||||
);
|
||||
}
|
||||
|
||||
// Warn if this character is not mapped but any others are
|
||||
if (charmap.nodes.size() > 1)
|
||||
if (int firstChar = input[inputIdx]; charmap.nodes.size() > 1) {
|
||||
warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n", printChar(firstChar));
|
||||
else if (charmap.name != DEFAULT_CHARMAP_NAME)
|
||||
} else if (charmap.name != DEFAULT_CHARMAP_NAME) {
|
||||
warning(
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
|
||||
printChar(firstChar)
|
||||
);
|
||||
}
|
||||
|
||||
inputIdx += codepointLen;
|
||||
matchLen = codepointLen;
|
||||
@@ -236,3 +302,20 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
input = input.substr(inputIdx);
|
||||
return matchLen;
|
||||
}
|
||||
|
||||
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
std::string revMapping;
|
||||
unique = charmap.forEachChar([&](size_t nodeIdx, std::string const &mapping) {
|
||||
if (charmap.nodes[nodeIdx].value == value) {
|
||||
if (revMapping.empty()) {
|
||||
revMapping = mapping;
|
||||
} else {
|
||||
revMapping.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return revMapping;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Fixed-point math routines
|
||||
|
||||
@@ -16,19 +16,17 @@ uint8_t fix_Precision() {
|
||||
return fixPrecision;
|
||||
}
|
||||
|
||||
double fix_PrecisionFactor() {
|
||||
return pow(2.0, fixPrecision);
|
||||
}
|
||||
|
||||
static double fix2double(int32_t i, int32_t q) {
|
||||
return i / pow(2.0, q);
|
||||
}
|
||||
|
||||
static int32_t double2fix(double d, int32_t q) {
|
||||
if (isnan(d))
|
||||
if (isnan(d)) {
|
||||
return 0;
|
||||
if (isinf(d))
|
||||
}
|
||||
if (isinf(d)) {
|
||||
return d < 0 ? INT32_MIN : INT32_MAX;
|
||||
}
|
||||
return static_cast<int32_t>(round(d * pow(2.0, q)));
|
||||
}
|
||||
|
||||
@@ -73,7 +71,12 @@ int32_t fix_Mul(int32_t i, int32_t j, int32_t q) {
|
||||
}
|
||||
|
||||
int32_t fix_Div(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(fix2double(i, q) / fix2double(j, q), q);
|
||||
double dividend = fix2double(i, q);
|
||||
double divisor = fix2double(j, q);
|
||||
if (fpclassify(divisor) == FP_ZERO) {
|
||||
return dividend < 0 ? INT32_MIN : dividend > 0 ? INT32_MAX : 0;
|
||||
}
|
||||
return double2fix(dividend / divisor, q);
|
||||
}
|
||||
|
||||
int32_t fix_Mod(int32_t i, int32_t j, int32_t q) {
|
||||
@@ -85,7 +88,11 @@ int32_t fix_Pow(int32_t i, int32_t j, int32_t q) {
|
||||
}
|
||||
|
||||
int32_t fix_Log(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(log(fix2double(i, q)) / log(fix2double(j, q)), q);
|
||||
double divisor = log(fix2double(j, q));
|
||||
if (fpclassify(divisor) == FP_ZERO) {
|
||||
return INT32_MAX;
|
||||
}
|
||||
return double2fix(log(fix2double(i, q)) / divisor, q);
|
||||
}
|
||||
|
||||
int32_t fix_Round(int32_t i, int32_t q) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/format.hpp"
|
||||
|
||||
@@ -13,39 +13,44 @@
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
void FormatSpec::useCharacter(int c) {
|
||||
if (state == FORMAT_INVALID)
|
||||
if (state == FORMAT_INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
// sign
|
||||
case ' ':
|
||||
case '+':
|
||||
if (state > FORMAT_SIGN)
|
||||
goto invalid;
|
||||
if (state > FORMAT_SIGN) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_EXACT;
|
||||
sign = c;
|
||||
break;
|
||||
return;
|
||||
|
||||
// exact
|
||||
case '#':
|
||||
if (state > FORMAT_EXACT)
|
||||
goto invalid;
|
||||
if (state > FORMAT_EXACT) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_ALIGN;
|
||||
exact = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// align
|
||||
case '-':
|
||||
if (state > FORMAT_ALIGN)
|
||||
goto invalid;
|
||||
if (state > FORMAT_ALIGN) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_WIDTH;
|
||||
alignLeft = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// pad, width, and prec values
|
||||
case '0':
|
||||
if (state < FORMAT_WIDTH)
|
||||
if (state < FORMAT_WIDTH) {
|
||||
padZero = true;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case '1':
|
||||
case '2':
|
||||
@@ -66,25 +71,27 @@ void FormatSpec::useCharacter(int c) {
|
||||
} else if (state == FORMAT_PREC) {
|
||||
precision = precision * 10 + (c - '0');
|
||||
} else {
|
||||
goto invalid;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
return;
|
||||
|
||||
// width
|
||||
case '.':
|
||||
if (state > FORMAT_WIDTH)
|
||||
goto invalid;
|
||||
if (state > FORMAT_WIDTH) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_FRAC;
|
||||
hasFrac = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// prec
|
||||
case 'q':
|
||||
if (state > FORMAT_PREC)
|
||||
goto invalid;
|
||||
if (state > FORMAT_PREC) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_PREC;
|
||||
hasPrec = true;
|
||||
break;
|
||||
return;
|
||||
|
||||
// type
|
||||
case 'd':
|
||||
@@ -95,23 +102,26 @@ void FormatSpec::useCharacter(int c) {
|
||||
case 'o':
|
||||
case 'f':
|
||||
case 's':
|
||||
if (state >= FORMAT_DONE)
|
||||
goto invalid;
|
||||
if (state >= FORMAT_DONE) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_DONE;
|
||||
valid = true;
|
||||
type = c;
|
||||
break;
|
||||
return;
|
||||
|
||||
default:
|
||||
invalid:
|
||||
state = FORMAT_INVALID;
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
state = FORMAT_INVALID;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
void FormatSpec::finishCharacters() {
|
||||
if (!isValid())
|
||||
if (!isValid()) {
|
||||
state = FORMAT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string escapeString(std::string const &str) {
|
||||
@@ -151,16 +161,21 @@ void FormatSpec::appendString(std::string &str, std::string const &value) const
|
||||
useType = 's';
|
||||
}
|
||||
|
||||
if (sign)
|
||||
if (sign) {
|
||||
error("Formatting string with sign flag '%c'\n", sign);
|
||||
if (padZero)
|
||||
}
|
||||
if (padZero) {
|
||||
error("Formatting string with padding flag '0'\n");
|
||||
if (hasFrac)
|
||||
}
|
||||
if (hasFrac) {
|
||||
error("Formatting string with fractional width\n");
|
||||
if (hasPrec)
|
||||
}
|
||||
if (hasPrec) {
|
||||
error("Formatting string with fractional precision\n");
|
||||
if (useType != 's')
|
||||
}
|
||||
if (useType != 's') {
|
||||
error("Formatting string as type '%c'\n", useType);
|
||||
}
|
||||
|
||||
std::string useValue = exact ? escapeString(value) : value;
|
||||
size_t valueLen = useValue.length();
|
||||
@@ -187,22 +202,27 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
}
|
||||
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
|
||||
&& useExact)
|
||||
&& useExact) {
|
||||
error("Formatting type '%c' with exact flag '#'\n", useType);
|
||||
if (useType != 'f' && hasFrac)
|
||||
}
|
||||
if (useType != 'f' && hasFrac) {
|
||||
error("Formatting type '%c' with fractional width\n", useType);
|
||||
if (useType != 'f' && hasPrec)
|
||||
}
|
||||
if (useType != 'f' && hasPrec) {
|
||||
error("Formatting type '%c' with fractional precision\n", useType);
|
||||
if (useType == 's')
|
||||
}
|
||||
if (useType == 's') {
|
||||
error("Formatting number as type 's'\n");
|
||||
}
|
||||
|
||||
char signChar = sign; // 0 or ' ' or '+'
|
||||
|
||||
if (useType == 'd' || useType == 'f') {
|
||||
if (int32_t v = value; v < 0) {
|
||||
signChar = '-';
|
||||
if (v != INT32_MIN)
|
||||
if (v != INT32_MIN) {
|
||||
value = -v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,10 +270,11 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
}
|
||||
|
||||
double fval = fabs(value / pow(2.0, usePrec));
|
||||
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact)
|
||||
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
||||
else
|
||||
} else {
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
||||
}
|
||||
} else if (useType == 'd') {
|
||||
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
|
||||
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
|
||||
@@ -278,27 +299,33 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
|
||||
str.reserve(str.length() + totalLen);
|
||||
if (alignLeft) {
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
str.append(valueBuf);
|
||||
str.append(padLen, ' ');
|
||||
} else {
|
||||
if (padZero) {
|
||||
// sign, then prefix, then zero padding
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
str.append(padLen, '0');
|
||||
} else {
|
||||
// space padding, then sign, then prefix
|
||||
str.append(padLen, ' ');
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
}
|
||||
str.append(valueBuf);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
#include <sys/stat.h>
|
||||
@@ -90,8 +90,9 @@ std::shared_ptr<std::string> fstk_GetUniqueIDStr() {
|
||||
std::shared_ptr<std::string> &str = contextStack.top().uniqueIDStr;
|
||||
|
||||
// If a unique ID is allowed but has not been generated yet, generate one now.
|
||||
if (str && str->empty())
|
||||
if (str && str->empty()) {
|
||||
*str = "_u"s + std::to_string(nextUniqueID++);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
@@ -103,28 +104,26 @@ MacroArgs *fstk_GetCurrentMacroArgs() {
|
||||
}
|
||||
|
||||
void fstk_AddIncludePath(std::string const &path) {
|
||||
if (path.empty())
|
||||
if (path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string &includePath = includePaths.emplace_back(path);
|
||||
if (includePath.back() != '/')
|
||||
if (includePath.back() != '/') {
|
||||
includePath += '/';
|
||||
}
|
||||
}
|
||||
|
||||
void fstk_SetPreIncludeFile(std::string const &path) {
|
||||
if (!preIncludeName.empty())
|
||||
if (!preIncludeName.empty()) {
|
||||
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
||||
preIncludeName = path;
|
||||
if (verbose)
|
||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
||||
}
|
||||
|
||||
static void printDep(std::string const &path) {
|
||||
if (dependFile) {
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
||||
if (generatePhonyDeps)
|
||||
fprintf(dependFile, "%s:\n", path.c_str());
|
||||
}
|
||||
preIncludeName = path;
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
static bool isValidFilePath(std::string const &path) {
|
||||
@@ -132,6 +131,15 @@ static bool isValidFilePath(std::string const &path) {
|
||||
return stat(path.c_str(), &statBuf) == 0 && !S_ISDIR(statBuf.st_mode); // Reject directories
|
||||
}
|
||||
|
||||
static void printDep(std::string const &path) {
|
||||
if (dependFile) {
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
||||
if (generatePhonyDeps && isValidFilePath(path)) {
|
||||
fprintf(dependFile, "%s:\n", path.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> fstk_FindFile(std::string const &path) {
|
||||
for (std::string &incPath : includePaths) {
|
||||
if (std::string fullPath = incPath + path; isValidFilePath(fullPath)) {
|
||||
@@ -141,20 +149,22 @@ std::optional<std::string> fstk_FindFile(std::string const &path) {
|
||||
}
|
||||
|
||||
errno = ENOENT;
|
||||
if (generatedMissingIncludes)
|
||||
if (generatedMissingIncludes) {
|
||||
printDep(path);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool yywrap() {
|
||||
uint32_t ifDepth = lexer_GetIFDepth();
|
||||
|
||||
if (ifDepth != 0)
|
||||
if (ifDepth != 0) {
|
||||
fatalerror(
|
||||
"Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||
ifDepth,
|
||||
ifDepth == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
|
||||
if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) {
|
||||
// The context is a REPT or FOR block, which may loop
|
||||
@@ -177,8 +187,9 @@ bool yywrap() {
|
||||
Symbol *sym = sym_AddVar(context.forName, context.forValue);
|
||||
|
||||
// This error message will refer to the current iteration
|
||||
if (sym->type != SYM_VAR)
|
||||
if (sym->type != SYM_VAR) {
|
||||
fatalerror("Failed to update FOR symbol value\n");
|
||||
}
|
||||
}
|
||||
// Advance to the next iteration
|
||||
fileInfoIters.front()++;
|
||||
@@ -199,8 +210,9 @@ bool yywrap() {
|
||||
}
|
||||
|
||||
static void checkRecursionDepth() {
|
||||
if (contextStack.size() > maxRecursionDepth)
|
||||
if (contextStack.size() > maxRecursionDepth) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||
}
|
||||
}
|
||||
|
||||
static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
@@ -210,7 +222,7 @@ static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
std::shared_ptr<MacroArgs> macroArgs = nullptr;
|
||||
|
||||
auto fileInfo =
|
||||
std::make_shared<FileStackNode>(NODE_MACRO, filePath == "-" ? "<stdin>" : filePath);
|
||||
std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath);
|
||||
if (!contextStack.empty()) {
|
||||
Context &oldContext = contextStack.top();
|
||||
fileInfo->parent = oldContext.fileInfo;
|
||||
@@ -298,8 +310,11 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
||||
|
||||
if (!fullPath) {
|
||||
if (generatedMissingIncludes && !preInclude) {
|
||||
if (verbose)
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
|
||||
@@ -307,18 +322,20 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newFileContext(*fullPath, false))
|
||||
fatalerror("Failed to set up lexer for file include\n");
|
||||
if (!newFileContext(*fullPath, false)) {
|
||||
fatalerror("Failed to set up lexer for file include\n"); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs) {
|
||||
Symbol *macro = sym_FindExactSymbol(macroName);
|
||||
|
||||
if (!macro) {
|
||||
if (sym_IsPurgedExact(macroName))
|
||||
if (sym_IsPurgedExact(macroName)) {
|
||||
error("Macro \"%s\" not defined; it was purged\n", macroName.c_str());
|
||||
else
|
||||
} else {
|
||||
error("Macro \"%s\" not defined\n", macroName.c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (macro->type != SYM_MACRO) {
|
||||
@@ -330,8 +347,9 @@ void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macr
|
||||
}
|
||||
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span) {
|
||||
if (count == 0)
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
newReptContext(reptLineNo, span, count);
|
||||
}
|
||||
@@ -344,24 +362,28 @@ void fstk_RunFor(
|
||||
int32_t reptLineNo,
|
||||
ContentSpan const &span
|
||||
) {
|
||||
if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR)
|
||||
if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t count = 0;
|
||||
if (step > 0 && start < stop)
|
||||
if (step > 0 && start < stop) {
|
||||
count = (static_cast<int64_t>(stop) - start - 1) / step + 1;
|
||||
else if (step < 0 && stop < start)
|
||||
} else if (step < 0 && stop < start) {
|
||||
count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1;
|
||||
else if (step == 0)
|
||||
} else if (step == 0) {
|
||||
error("FOR cannot have a step value of 0\n");
|
||||
}
|
||||
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop))
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop)) {
|
||||
warning(
|
||||
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
|
||||
);
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context &context = newReptContext(reptLineNo, span, count);
|
||||
context.isForLoop = true;
|
||||
@@ -370,32 +392,31 @@ void fstk_RunFor(
|
||||
context.forName = symName;
|
||||
}
|
||||
|
||||
void fstk_StopRept() {
|
||||
contextStack.top().nbReptIters = 0; // Prevent more iterations
|
||||
}
|
||||
|
||||
bool fstk_Break() {
|
||||
if (contextStack.top().fileInfo->type != NODE_REPT) {
|
||||
error("BREAK can only be used inside a REPT/FOR block\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
fstk_StopRept();
|
||||
contextStack.top().nbReptIters = 0; // Prevent more iterations
|
||||
return true;
|
||||
}
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth) {
|
||||
if (contextStack.size() > newDepth + 1)
|
||||
if (contextStack.size() > newDepth + 1) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||
}
|
||||
maxRecursionDepth = newDepth;
|
||||
}
|
||||
|
||||
void fstk_Init(std::string const &mainPath, size_t maxDepth) {
|
||||
if (!newFileContext(mainPath, true))
|
||||
if (!newFileContext(mainPath, true)) {
|
||||
fatalerror("Failed to open main file\n");
|
||||
}
|
||||
|
||||
maxRecursionDepth = maxDepth;
|
||||
|
||||
if (!preIncludeName.empty())
|
||||
if (!preIncludeName.empty()) {
|
||||
fstk_RunInclude(preIncludeName, true);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/macro.hpp"
|
||||
|
||||
@@ -6,28 +6,32 @@
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#define MAXMACROARGS 99999
|
||||
std::shared_ptr<std::string> MacroArgs::getArg(int32_t i) const {
|
||||
// Bracketed macro arguments adjust negative indexes such that -1 is the last argument.
|
||||
if (i < 0) {
|
||||
i += args.size() + 1;
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
||||
uint32_t realIndex = i + shift - 1;
|
||||
int32_t realIndex = i + shift - 1;
|
||||
|
||||
return realIndex >= args.size() ? nullptr : args[realIndex];
|
||||
return realIndex < 0 || static_cast<uint32_t>(realIndex) >= args.size() ? nullptr
|
||||
: args[realIndex];
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||
size_t nbArgs = args.size();
|
||||
|
||||
if (shift >= nbArgs)
|
||||
if (shift >= nbArgs) {
|
||||
return std::make_shared<std::string>("");
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
|
||||
for (uint32_t i = shift; i < nbArgs; i++)
|
||||
for (uint32_t i = shift; i < nbArgs; i++) {
|
||||
len += args[i]->length() + 1; // 1 for comma
|
||||
}
|
||||
|
||||
auto str = std::make_shared<std::string>();
|
||||
str->reserve(len + 1); // 1 for comma
|
||||
@@ -38,18 +42,18 @@ std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||
str->append(*arg);
|
||||
|
||||
// Commas go between args and after a last empty arg
|
||||
if (i < nbArgs - 1 || arg->empty())
|
||||
if (i < nbArgs - 1 || arg->empty()) {
|
||||
str->push_back(','); // no space after comma
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
||||
if (arg->empty())
|
||||
if (arg->empty()) {
|
||||
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
|
||||
if (args.size() == MAXMACROARGS)
|
||||
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
|
||||
}
|
||||
args.push_back(arg);
|
||||
}
|
||||
|
||||
|
||||
150
src/asm/main.cpp
150
src/asm/main.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/main.hpp"
|
||||
|
||||
@@ -36,31 +36,31 @@ static std::string make_escape(std::string &str) {
|
||||
size_t pos = 0;
|
||||
for (;;) {
|
||||
// All dollars needs to be doubled
|
||||
size_t nextPos = str.find("$", pos);
|
||||
if (nextPos == std::string::npos)
|
||||
size_t nextPos = str.find('$', pos);
|
||||
if (nextPos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
escaped.append(str, pos, nextPos - pos);
|
||||
escaped.append("$$");
|
||||
pos = nextPos + QUOTEDSTRLEN("$");
|
||||
pos = nextPos + literal_strlen("$");
|
||||
}
|
||||
escaped.append(str, pos, str.length() - pos);
|
||||
return escaped;
|
||||
}
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "b:D:Eg:I:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
static char const *optstring = "b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int depType; // Variants of `-M`
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts
|
||||
//
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"binary-digits", required_argument, nullptr, 'b'},
|
||||
{"define", required_argument, nullptr, 'D'},
|
||||
@@ -69,6 +69,7 @@ static option const longopts[] = {
|
||||
{"include", required_argument, nullptr, 'I'},
|
||||
{"dependfile", required_argument, nullptr, 'M'},
|
||||
{"MG", no_argument, &depType, 'G'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"MP", no_argument, &depType, 'P'},
|
||||
{"MT", required_argument, &depType, 'T'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
@@ -86,9 +87,10 @@ static option const longopts[] = {
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
// LCOV_EXCL_START
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbasm [-EVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
||||
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
|
||||
@@ -106,17 +108,20 @@ static void printUsage() {
|
||||
stderr
|
||||
);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
time_t now = time(nullptr);
|
||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch)
|
||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
||||
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
||||
}
|
||||
|
||||
Defer closeDependFile{[&] {
|
||||
if (dependFile)
|
||||
if (dependFile) {
|
||||
fclose(dependFile);
|
||||
}
|
||||
}};
|
||||
|
||||
// Perform some init for below
|
||||
@@ -128,23 +133,25 @@ int main(int argc, char *argv[]) {
|
||||
opt_P(0);
|
||||
opt_Q(16);
|
||||
sym_SetExportAll(false);
|
||||
uint32_t maxDepth = DEFAULT_MAX_DEPTH;
|
||||
uint32_t maxDepth = 64;
|
||||
char const *dependFileName = nullptr;
|
||||
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs;
|
||||
std::string newTarget;
|
||||
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal.
|
||||
if (isatty(STDERR_FILENO))
|
||||
if (isatty(STDERR_FILENO)) {
|
||||
maxErrors = 100;
|
||||
}
|
||||
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
switch (ch) {
|
||||
char *endptr;
|
||||
|
||||
case 'b':
|
||||
if (strlen(musl_optarg) == 2)
|
||||
if (strlen(musl_optarg) == 2) {
|
||||
opt_B(musl_optarg);
|
||||
else
|
||||
} else {
|
||||
errx("Must specify exactly 2 characters for option 'b'");
|
||||
}
|
||||
break;
|
||||
|
||||
char *equals;
|
||||
@@ -163,19 +170,27 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
if (strlen(musl_optarg) == 4)
|
||||
if (strlen(musl_optarg) == 4) {
|
||||
opt_G(musl_optarg);
|
||||
else
|
||||
} else {
|
||||
errx("Must specify exactly 4 characters for option 'g'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'I':
|
||||
fstk_AddIncludePath(musl_optarg);
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
if (dependFile)
|
||||
if (dependFile) {
|
||||
warnx("Overriding dependfile %s", dependFileName);
|
||||
}
|
||||
if (strcmp("-", musl_optarg)) {
|
||||
dependFile = fopen(musl_optarg, "w");
|
||||
dependFileName = musl_optarg;
|
||||
@@ -183,8 +198,9 @@ int main(int argc, char *argv[]) {
|
||||
dependFile = stdout;
|
||||
dependFileName = "<stdout>";
|
||||
}
|
||||
if (dependFile == nullptr)
|
||||
err("Failed to open dependfile \"%s\"", dependFileName);
|
||||
if (dependFile == nullptr) {
|
||||
err("Failed to open dependfile \"%s\"", dependFileName); // LCOV_EXCL_LINE
|
||||
}
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
@@ -199,11 +215,13 @@ int main(int argc, char *argv[]) {
|
||||
case 'p':
|
||||
padByte = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'p'");
|
||||
}
|
||||
|
||||
if (padByte > 0xFF)
|
||||
if (padByte > 0xFF) {
|
||||
errx("Argument for option 'p' must be between 0 and 0xFF");
|
||||
}
|
||||
|
||||
opt_P(padByte);
|
||||
break;
|
||||
@@ -212,15 +230,18 @@ int main(int argc, char *argv[]) {
|
||||
char const *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = musl_optarg;
|
||||
if (precisionArg[0] == '.')
|
||||
if (precisionArg[0] == '.') {
|
||||
precisionArg++;
|
||||
}
|
||||
precision = strtoul(precisionArg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'Q'");
|
||||
}
|
||||
|
||||
if (precision < 1 || precision > 31)
|
||||
if (precision < 1 || precision > 31) {
|
||||
errx("Argument for option 'Q' must be between 1 and 31");
|
||||
}
|
||||
|
||||
opt_Q(precision);
|
||||
break;
|
||||
@@ -228,35 +249,41 @@ int main(int argc, char *argv[]) {
|
||||
case 'r':
|
||||
maxDepth = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'r'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 's': {
|
||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
||||
char *name = strchr(musl_optarg, ':');
|
||||
if (!name)
|
||||
if (!name) {
|
||||
errx("Invalid argument for option 's'");
|
||||
}
|
||||
*name++ = '\0';
|
||||
|
||||
std::vector<StateFeature> features;
|
||||
for (char *feature = musl_optarg; feature;) {
|
||||
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
|
||||
char *next = strchr(feature, ',');
|
||||
if (next)
|
||||
if (next) {
|
||||
*next++ = '\0';
|
||||
}
|
||||
// Trim whitespace from the beginning of `feature`...
|
||||
feature += strspn(feature, " \t");
|
||||
// ...and from the end
|
||||
if (char *end = strpbrk(feature, " \t"); end)
|
||||
if (char *end = strpbrk(feature, " \t"); end) {
|
||||
*end = '\0';
|
||||
}
|
||||
// A feature must be specified
|
||||
if (*feature == '\0')
|
||||
if (*feature == '\0') {
|
||||
errx("Empty feature for option 's'");
|
||||
}
|
||||
// Parse the `feature` and update the `features` list
|
||||
if (!strcasecmp(feature, "all")) {
|
||||
if (!features.empty())
|
||||
if (!features.empty()) {
|
||||
warnx("Redundant feature before \"%s\" for option 's'", feature);
|
||||
}
|
||||
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
|
||||
} else {
|
||||
StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU
|
||||
@@ -276,10 +303,14 @@ int main(int argc, char *argv[]) {
|
||||
feature = next;
|
||||
}
|
||||
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end())
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
||||
warnx("Overriding state filename %s", name);
|
||||
if (verbose)
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("State filename %s\n", name);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
stateFileSpecs.emplace(name, std::move(features));
|
||||
break;
|
||||
}
|
||||
@@ -289,8 +320,10 @@ int main(int argc, char *argv[]) {
|
||||
exit(0);
|
||||
|
||||
case 'v':
|
||||
// LCOV_EXCL_START
|
||||
verbose = true;
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'W':
|
||||
opt_W(musl_optarg);
|
||||
@@ -304,11 +337,13 @@ int main(int argc, char *argv[]) {
|
||||
case 'X':
|
||||
maxValue = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'X'");
|
||||
}
|
||||
|
||||
if (maxValue > UINT_MAX)
|
||||
if (maxValue > UINT_MAX) {
|
||||
errx("Argument for option 'X' must be between 0 and %u", UINT_MAX);
|
||||
}
|
||||
|
||||
maxErrors = maxValue;
|
||||
break;
|
||||
@@ -327,10 +362,12 @@ int main(int argc, char *argv[]) {
|
||||
case 'Q':
|
||||
case 'T':
|
||||
newTarget = musl_optarg;
|
||||
if (depType == 'Q')
|
||||
if (depType == 'Q') {
|
||||
newTarget = make_escape(newTarget);
|
||||
if (!targetFileName.empty())
|
||||
}
|
||||
if (!targetFileName.empty()) {
|
||||
targetFileName += ' ';
|
||||
}
|
||||
targetFileName += newTarget;
|
||||
break;
|
||||
}
|
||||
@@ -338,13 +375,16 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// Unrecognized options
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(1);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
if (targetFileName.empty() && !objectFileName.empty())
|
||||
if (targetFileName.empty() && !objectFileName.empty()) {
|
||||
targetFileName = objectFileName;
|
||||
}
|
||||
|
||||
if (argc == musl_optind) {
|
||||
fputs(
|
||||
@@ -360,13 +400,15 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
std::string mainFileName = argv[musl_optind];
|
||||
|
||||
if (verbose)
|
||||
printf("Assembling %s\n", mainFileName.c_str());
|
||||
if (verbose) {
|
||||
printf("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (dependFile) {
|
||||
if (targetFileName.empty())
|
||||
if (targetFileName.empty()) {
|
||||
errx("Dependency files can only be created if a target file is specified with either "
|
||||
"-o, -MQ or -MT");
|
||||
}
|
||||
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), mainFileName.c_str());
|
||||
}
|
||||
@@ -377,28 +419,34 @@ int main(int argc, char *argv[]) {
|
||||
fstk_Init(mainFileName, maxDepth);
|
||||
|
||||
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
||||
if (yy::parser parser; parser.parse() != 0 && nbErrors == 0)
|
||||
if (yy::parser parser; parser.parse() != 0 && nbErrors == 0) {
|
||||
nbErrors = 1;
|
||||
}
|
||||
|
||||
sect_CheckUnionClosed();
|
||||
sect_CheckLoadClosed();
|
||||
sect_CheckSizes();
|
||||
if (!failedOnMissingInclude) {
|
||||
sect_CheckUnionClosed();
|
||||
sect_CheckLoadClosed();
|
||||
sect_CheckSizes();
|
||||
|
||||
charmap_CheckStack();
|
||||
opt_CheckStack();
|
||||
sect_CheckStack();
|
||||
charmap_CheckStack();
|
||||
opt_CheckStack();
|
||||
sect_CheckStack();
|
||||
}
|
||||
|
||||
if (nbErrors != 0)
|
||||
if (nbErrors != 0) {
|
||||
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
||||
if (failedOnMissingInclude)
|
||||
if (failedOnMissingInclude) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
out_WriteObject();
|
||||
|
||||
for (auto [name, features] : stateFileSpecs)
|
||||
for (auto [name, features] : stateFileSpecs) {
|
||||
out_WriteState(name, features);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
@@ -14,8 +14,8 @@
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
struct OptStackEntry {
|
||||
char binary[2];
|
||||
char gbgfx[4];
|
||||
char binDigits[2];
|
||||
char gfxDigits[4];
|
||||
uint8_t fixPrecision;
|
||||
uint8_t fillByte;
|
||||
bool warningsAreErrors;
|
||||
@@ -53,17 +53,19 @@ void opt_W(char const *flag) {
|
||||
void opt_Parse(char const *s) {
|
||||
switch (s[0]) {
|
||||
case 'b':
|
||||
if (strlen(&s[1]) == 2)
|
||||
if (strlen(&s[1]) == 2) {
|
||||
opt_B(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify exactly 2 characters for option 'b'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
if (strlen(&s[1]) == 4)
|
||||
if (strlen(&s[1]) == 4) {
|
||||
opt_G(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify exactly 4 characters for option 'g'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
@@ -72,12 +74,13 @@ void opt_Parse(char const *s) {
|
||||
unsigned int padByte;
|
||||
|
||||
result = sscanf(&s[1], "%x", &padByte);
|
||||
if (result != 1)
|
||||
if (result != 1) {
|
||||
error("Invalid argument for option 'p'\n");
|
||||
else if (padByte > 0xFF)
|
||||
} else if (padByte > 0xFF) {
|
||||
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
||||
else
|
||||
} else {
|
||||
opt_P(padByte);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'p'\n");
|
||||
}
|
||||
@@ -86,19 +89,21 @@ void opt_Parse(char const *s) {
|
||||
char const *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = &s[1];
|
||||
if (precisionArg[0] == '.')
|
||||
if (precisionArg[0] == '.') {
|
||||
precisionArg++;
|
||||
}
|
||||
if (strlen(precisionArg) <= 2) {
|
||||
int result;
|
||||
unsigned int precision;
|
||||
|
||||
result = sscanf(precisionArg, "%u", &precision);
|
||||
if (result != 1)
|
||||
if (result != 1) {
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
else if (precision < 1 || precision > 31)
|
||||
} else if (precision < 1 || precision > 31) {
|
||||
error("Argument for option 'Q' must be between 1 and 31\n");
|
||||
else
|
||||
} else {
|
||||
opt_Q(precision);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
}
|
||||
@@ -106,8 +111,9 @@ void opt_Parse(char const *s) {
|
||||
|
||||
case 'r': {
|
||||
++s; // Skip 'r'
|
||||
while (isblank(*s))
|
||||
while (isblank(*s)) {
|
||||
++s; // Skip leading whitespace
|
||||
}
|
||||
|
||||
if (s[0] == '\0') {
|
||||
error("Missing argument to option 'r'\n");
|
||||
@@ -128,10 +134,11 @@ void opt_Parse(char const *s) {
|
||||
}
|
||||
|
||||
case 'W':
|
||||
if (strlen(&s[1]) > 0)
|
||||
if (strlen(&s[1]) > 0) {
|
||||
opt_W(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify an argument for option 'W'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -144,13 +151,8 @@ void opt_Push() {
|
||||
OptStackEntry entry;
|
||||
|
||||
// Both of these are pulled from lexer.hpp
|
||||
entry.binary[0] = binDigits[0];
|
||||
entry.binary[1] = binDigits[1];
|
||||
|
||||
entry.gbgfx[0] = gfxDigits[0];
|
||||
entry.gbgfx[1] = gfxDigits[1];
|
||||
entry.gbgfx[2] = gfxDigits[2];
|
||||
entry.gbgfx[3] = gfxDigits[3];
|
||||
memcpy(entry.binDigits, binDigits, std::size(binDigits));
|
||||
memcpy(entry.gfxDigits, gfxDigits, std::size(gfxDigits));
|
||||
|
||||
entry.fixPrecision = fixPrecision; // Pulled from fixpoint.hpp
|
||||
|
||||
@@ -174,8 +176,8 @@ void opt_Pop() {
|
||||
OptStackEntry entry = stack.top();
|
||||
stack.pop();
|
||||
|
||||
opt_B(entry.binary);
|
||||
opt_G(entry.gbgfx);
|
||||
opt_B(entry.binDigits);
|
||||
opt_G(entry.gfxDigits);
|
||||
opt_P(entry.fillByte);
|
||||
opt_Q(entry.fixPrecision);
|
||||
opt_R(entry.maxRecursionDepth);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/output.hpp"
|
||||
|
||||
@@ -61,15 +61,18 @@ void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
|
||||
}
|
||||
}
|
||||
|
||||
// Return a section's ID, or UINT32_MAX if the section is not in the list
|
||||
// Return a section's ID, or UINT32_MAX if the section does not exist
|
||||
static uint32_t getSectIDIfAny(Section *sect) {
|
||||
if (!sect)
|
||||
if (!sect) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end())
|
||||
return static_cast<uint32_t>(sectionMap.size() - search->second - 1);
|
||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) {
|
||||
return static_cast<uint32_t>(search->second);
|
||||
}
|
||||
|
||||
fatalerror("Unknown section '%s'\n", sect->name.c_str());
|
||||
// Every section that exists should be in `sectionMap`
|
||||
fatalerror("Unknown section '%s'\n", sect->name.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
static void writePatch(Patch const &patch, FILE *file) {
|
||||
@@ -109,8 +112,9 @@ static void writeSection(Section const §, FILE *file) {
|
||||
fwrite(sect.data.data(), 1, sect.size, file);
|
||||
putLong(sect.patches.size(), file);
|
||||
|
||||
for (Patch const &patch : sect.patches)
|
||||
for (Patch const &patch : sect.patches) {
|
||||
writePatch(patch, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,8 +166,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
symName.clear();
|
||||
for (;;) {
|
||||
uint8_t c = rpn[offset++];
|
||||
if (c == 0)
|
||||
if (c == 0) {
|
||||
break;
|
||||
}
|
||||
symName += c;
|
||||
}
|
||||
|
||||
@@ -171,7 +176,7 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
sym = sym_FindExactSymbol(symName);
|
||||
if (sym->isConstant()) {
|
||||
rpnexpr[rpnptr++] = RPN_CONST;
|
||||
value = sym_GetConstantValue(symName);
|
||||
value = sym->getConstantValue();
|
||||
} else {
|
||||
rpnexpr[rpnptr++] = RPN_SYM;
|
||||
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
||||
@@ -188,8 +193,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
symName.clear();
|
||||
for (;;) {
|
||||
uint8_t c = rpn[offset++];
|
||||
if (c == 0)
|
||||
if (c == 0) {
|
||||
break;
|
||||
}
|
||||
symName += c;
|
||||
}
|
||||
|
||||
@@ -282,7 +288,7 @@ void out_CreateAssert(
|
||||
assertion.message = message;
|
||||
}
|
||||
|
||||
static void writeAssert(Assertion &assert, FILE *file) {
|
||||
static void writeAssert(Assertion const &assert, FILE *file) {
|
||||
writePatch(assert.patch, file);
|
||||
putString(assert.message, file);
|
||||
}
|
||||
@@ -298,14 +304,16 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
||||
|
||||
putLong(nodeIters.size(), file);
|
||||
// Iters are stored by decreasing depth, so reverse the order for output
|
||||
for (uint32_t i = nodeIters.size(); i--;)
|
||||
for (uint32_t i = nodeIters.size(); i--;) {
|
||||
putLong(nodeIters[i], file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void out_WriteObject() {
|
||||
if (objectFileName.empty())
|
||||
if (objectFileName.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file;
|
||||
if (objectFileName != "-") {
|
||||
@@ -315,14 +323,15 @@ void out_WriteObject() {
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file)
|
||||
err("Failed to open object file '%s'", objectFileName.c_str());
|
||||
if (!file) {
|
||||
err("Failed to open object file '%s'", objectFileName.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
// Also write symbols that weren't written above
|
||||
sym_ForEach(registerUnregisteredSymbol);
|
||||
|
||||
fprintf(file, RGBDS_OBJECT_VERSION_STRING);
|
||||
fputs(RGBDS_OBJECT_VERSION_STRING, file);
|
||||
putLong(RGBDS_OBJECT_REV, file);
|
||||
|
||||
putLong(objectSymbols.size(), file);
|
||||
@@ -335,33 +344,34 @@ void out_WriteObject() {
|
||||
writeFileStackNode(node, file);
|
||||
|
||||
// The list is supposed to have decrementing IDs
|
||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1)
|
||||
fatalerror(
|
||||
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
||||
". Please report this to the developers!\n",
|
||||
it[1]->ID,
|
||||
node.ID
|
||||
);
|
||||
assume(it + 1 == fileStackNodes.end() || it[1]->ID == node.ID - 1);
|
||||
}
|
||||
|
||||
for (Symbol const *sym : objectSymbols)
|
||||
for (Symbol const *sym : objectSymbols) {
|
||||
writeSymbol(*sym, file);
|
||||
}
|
||||
|
||||
for (auto it = sectionList.rbegin(); it != sectionList.rend(); it++)
|
||||
writeSection(*it, file);
|
||||
for (Section const § : sectionList) {
|
||||
writeSection(sect, file);
|
||||
}
|
||||
|
||||
putLong(assertions.size(), file);
|
||||
|
||||
for (Assertion &assert : assertions)
|
||||
for (Assertion const &assert : assertions) {
|
||||
writeAssert(assert, file);
|
||||
}
|
||||
}
|
||||
|
||||
void out_SetFileName(std::string const &name) {
|
||||
if (!objectFileName.empty())
|
||||
if (!objectFileName.empty()) {
|
||||
warnx("Overriding output filename %s", objectFileName.c_str());
|
||||
}
|
||||
objectFileName = name;
|
||||
if (verbose)
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Output filename %s\n", objectFileName.c_str());
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
static void dumpString(std::string const &escape, FILE *file) {
|
||||
@@ -397,8 +407,9 @@ static bool dumpEquConstants(FILE *file) {
|
||||
equConstants.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQU)
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQU) {
|
||||
equConstants.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Constants are ordered by file, then by definition order
|
||||
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -418,8 +429,9 @@ static bool dumpVariables(FILE *file) {
|
||||
variables.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_VAR)
|
||||
if (!sym.isBuiltin && sym.type == SYM_VAR) {
|
||||
variables.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Variables are ordered by file, then by definition order
|
||||
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -439,8 +451,9 @@ static bool dumpEqusConstants(FILE *file) {
|
||||
equsConstants.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQUS)
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
|
||||
equsConstants.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Constants are ordered by file, then by definition order
|
||||
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -467,8 +480,9 @@ static bool dumpCharmaps(FILE *file) {
|
||||
fputs("charmap \"", charmapFile);
|
||||
dumpString(mapping, charmapFile);
|
||||
putc('"', charmapFile);
|
||||
for (int32_t v : value)
|
||||
for (int32_t v : value) {
|
||||
fprintf(charmapFile, ", $%" PRIx32, v);
|
||||
}
|
||||
putc('\n', charmapFile);
|
||||
}
|
||||
);
|
||||
@@ -479,8 +493,9 @@ static bool dumpMacros(FILE *file) {
|
||||
macros.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_MACRO)
|
||||
if (!sym.isBuiltin && sym.type == SYM_MACRO) {
|
||||
macros.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Macros are ordered by file, then by definition order
|
||||
std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -508,8 +523,9 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file)
|
||||
err("Failed to open state file '%s'", name.c_str());
|
||||
if (!file) {
|
||||
err("Failed to open state file '%s'", name.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
static char const *dumpHeadings[NB_STATE_FEATURES] = {
|
||||
@@ -530,7 +546,8 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
||||
fputs("; File generated by rgbasm\n", file);
|
||||
for (StateFeature feature : features) {
|
||||
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
||||
if (!dumpFuncs[feature](file))
|
||||
if (!dumpFuncs[feature](file)) {
|
||||
fprintf(file, "; No values\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1266
src/asm/parser.y
1266
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
195
src/asm/rpn.cpp
195
src/asm/rpn.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/rpn.hpp"
|
||||
|
||||
@@ -46,8 +46,9 @@ int32_t Expression::getConstVal() const {
|
||||
}
|
||||
|
||||
Symbol const *Expression::symbolOf() const {
|
||||
if (!isSymbol)
|
||||
if (!isSymbol) {
|
||||
return nullptr;
|
||||
}
|
||||
return sym_FindScopedSymbol(reinterpret_cast<char const *>(&rpn[1]));
|
||||
}
|
||||
|
||||
@@ -55,8 +56,9 @@ bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||
// Check if both expressions only refer to a single symbol
|
||||
Symbol const *sym1 = symbolOf();
|
||||
|
||||
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
|
||||
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Section const *sect1 = sym1->getSection();
|
||||
Section const *sect2 = sym->getSection();
|
||||
@@ -73,13 +75,16 @@ void Expression::makeSymbol(std::string const &symName) {
|
||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||
error("PC has no value outside of a section\n");
|
||||
data = 0;
|
||||
} else if (sym && !sym->isNumeric() && !sym->isLabel()) {
|
||||
error("'%s' is not a numeric symbol\n", symName.c_str());
|
||||
data = 0;
|
||||
} else if (!sym || !sym->isConstant()) {
|
||||
isSymbol = true;
|
||||
|
||||
data = sym_IsPC(sym) ? "PC is not constant at assembly time"
|
||||
: sym_IsPurgedScoped(symName)
|
||||
? "'"s + symName + "' is not constant at assembly time; it was purged"
|
||||
: "'"s + symName + "' is not constant at assembly time";
|
||||
: sym_IsPurgedScoped(symName)
|
||||
? "'"s + symName + "' is not constant at assembly time; it was purged"
|
||||
: "'"s + symName + "' is not constant at assembly time";
|
||||
sym = sym_Ref(symName);
|
||||
|
||||
size_t nameLen = sym->name.length() + 1; // Don't forget NUL!
|
||||
@@ -89,7 +94,7 @@ void Expression::makeSymbol(std::string const &symName) {
|
||||
*ptr++ = RPN_SYM;
|
||||
memcpy(ptr, sym->name.c_str(), nameLen);
|
||||
} else {
|
||||
data = static_cast<int32_t>(sym_GetConstantValue(symName));
|
||||
data = static_cast<int32_t>(sym->getConstantValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,8 +125,8 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
data = static_cast<int32_t>(sym->getSection()->bank);
|
||||
} else {
|
||||
data = sym_IsPurgedScoped(symName)
|
||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||
: "\""s + symName + "\"'s bank is not known";
|
||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||
: "\""s + symName + "\"'s bank is not known";
|
||||
|
||||
size_t nameLen = sym->name.length() + 1; // Room for NUL!
|
||||
|
||||
@@ -208,8 +213,9 @@ static bool tryConstNonzero(Expression const &lhs, Expression const &rhs) {
|
||||
|
||||
static bool tryConstLogNot(Expression const &expr) {
|
||||
Symbol const *sym = expr.symbolOf();
|
||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
||||
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assume(sym->isNumeric());
|
||||
|
||||
@@ -225,23 +231,21 @@ static bool tryConstLogNot(Expression const &expr) {
|
||||
return knownBits != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to compute a constant LOW() from non-constant argument
|
||||
* This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
|
||||
*
|
||||
* @return The constant `LOW(expr)` result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
// Returns a constant LOW() from non-constant argument, or -1 if it cannot be computed.
|
||||
// This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
|
||||
static int32_t tryConstLow(Expression const &expr) {
|
||||
Symbol const *sym = expr.symbolOf();
|
||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
||||
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assume(sym->isNumeric());
|
||||
|
||||
// The low byte must not cover any unknown bits
|
||||
Section const § = *sym->getSection();
|
||||
if (sect.align < 8)
|
||||
if (sect.align < 8) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
@@ -251,28 +255,26 @@ static int32_t tryConstLow(Expression const &expr) {
|
||||
return (symbolOfs + sect.alignOfs) & 0xFF;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to compute a constant binary AND with one non-constant operands
|
||||
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
||||
* a constant that only keeps (some of) the lower N bits.
|
||||
*
|
||||
* @return The constant `lhs & rhs` result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
// Returns a constant binary AND with one non-constant operand, or -1 if it cannot be computed.
|
||||
// This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
||||
// a constant that only keeps (some of) the lower N bits.
|
||||
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
Symbol const *lhsSymbol = lhs.symbolOf();
|
||||
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
|
||||
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
|
||||
bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection();
|
||||
|
||||
if (!lhsIsSymbol && !rhsIsSymbol)
|
||||
if (!lhsIsSymbol && !rhsIsSymbol) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If the lhs isn't a symbol, try again the other way around
|
||||
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
|
||||
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
|
||||
|
||||
if (!sym.isDefined() || !expr.isKnown())
|
||||
if (!sym.isDefined() || !expr.isKnown()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assume(sym.isNumeric());
|
||||
|
||||
@@ -281,8 +283,9 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
|
||||
// The mask must not cover any unknown bits
|
||||
Section const § = *sym.getSection();
|
||||
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0)
|
||||
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// `sym.getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
@@ -322,40 +325,12 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
case RPN_TZCOUNT:
|
||||
data = val != 0 ? ctz(uval) : 32;
|
||||
break;
|
||||
|
||||
case RPN_LOGOR:
|
||||
case RPN_LOGAND:
|
||||
case RPN_LOGEQ:
|
||||
case RPN_LOGGT:
|
||||
case RPN_LOGLT:
|
||||
case RPN_LOGGE:
|
||||
case RPN_LOGLE:
|
||||
case RPN_LOGNE:
|
||||
case RPN_ADD:
|
||||
case RPN_SUB:
|
||||
case RPN_XOR:
|
||||
case RPN_OR:
|
||||
case RPN_AND:
|
||||
case RPN_SHL:
|
||||
case RPN_SHR:
|
||||
case RPN_USHR:
|
||||
case RPN_MUL:
|
||||
case RPN_DIV:
|
||||
case RPN_MOD:
|
||||
case RPN_EXP:
|
||||
case RPN_BANK_SYM:
|
||||
case RPN_BANK_SECT:
|
||||
case RPN_BANK_SELF:
|
||||
case RPN_SIZEOF_SECT:
|
||||
case RPN_STARTOF_SECT:
|
||||
case RPN_SIZEOF_SECTTYPE:
|
||||
case RPN_STARTOF_SECTTYPE:
|
||||
case RPN_HRAM:
|
||||
case RPN_RST:
|
||||
case RPN_CONST:
|
||||
case RPN_SYM:
|
||||
fatalerror("%d is not an unary operator\n", op);
|
||||
default:
|
||||
// `makeUnaryOp` should never be called with a non-unary operator!
|
||||
// LCOV_EXCL_START
|
||||
unreachable_();
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (op == RPN_LOGNOT && tryConstLogNot(src)) {
|
||||
data = 0;
|
||||
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) {
|
||||
@@ -418,38 +393,45 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
data = lval & rval;
|
||||
break;
|
||||
case RPN_SHL:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
}
|
||||
|
||||
if (rval >= 32)
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", rval);
|
||||
}
|
||||
|
||||
data = op_shift_left(lval, rval);
|
||||
break;
|
||||
case RPN_SHR:
|
||||
if (lval < 0)
|
||||
if (lval < 0) {
|
||||
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval);
|
||||
}
|
||||
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
}
|
||||
|
||||
if (rval >= 32)
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||
}
|
||||
|
||||
data = op_shift_right(lval, rval);
|
||||
break;
|
||||
case RPN_USHR:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
}
|
||||
|
||||
if (rval >= 32)
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||
}
|
||||
|
||||
data = op_shift_right_unsigned(lval, rval);
|
||||
break;
|
||||
@@ -457,8 +439,9 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
data = static_cast<int32_t>(ulval * urval);
|
||||
break;
|
||||
case RPN_DIV:
|
||||
if (rval == 0)
|
||||
if (rval == 0) {
|
||||
fatalerror("Division by zero\n");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
warning(
|
||||
@@ -473,41 +456,29 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
}
|
||||
break;
|
||||
case RPN_MOD:
|
||||
if (rval == 0)
|
||||
if (rval == 0) {
|
||||
fatalerror("Modulo by zero\n");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1)
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
data = 0;
|
||||
else
|
||||
} else {
|
||||
data = op_modulo(lval, rval);
|
||||
}
|
||||
break;
|
||||
case RPN_EXP:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
fatalerror("Exponentiation by negative power\n");
|
||||
}
|
||||
|
||||
data = op_exponent(lval, rval);
|
||||
break;
|
||||
|
||||
case RPN_NEG:
|
||||
case RPN_NOT:
|
||||
case RPN_LOGNOT:
|
||||
case RPN_BANK_SYM:
|
||||
case RPN_BANK_SECT:
|
||||
case RPN_BANK_SELF:
|
||||
case RPN_SIZEOF_SECT:
|
||||
case RPN_STARTOF_SECT:
|
||||
case RPN_SIZEOF_SECTTYPE:
|
||||
case RPN_STARTOF_SECTTYPE:
|
||||
case RPN_HRAM:
|
||||
case RPN_RST:
|
||||
case RPN_HIGH:
|
||||
case RPN_LOW:
|
||||
case RPN_BITWIDTH:
|
||||
case RPN_TZCOUNT:
|
||||
case RPN_CONST:
|
||||
case RPN_SYM:
|
||||
fatalerror("%d is not a binary operator\n", op);
|
||||
default:
|
||||
// `makeBinaryOp` should never be called with a non-binary operator!
|
||||
// LCOV_EXCL_START
|
||||
unreachable_();
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (op == RPN_SUB && src1.isDiffConstant(src2.symbolOf())) {
|
||||
data = src1.symbolOf()->getValue() - src2.symbolOf()->getValue();
|
||||
} else if ((op == RPN_LOGAND || op == RPN_AND) && tryConstZero(src1, src2)) {
|
||||
@@ -560,9 +531,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
// Copy the right RPN and append the operator
|
||||
uint32_t rightRpnSize = src2.rpn.size();
|
||||
uint8_t *ptr = reserveSpace(rightRpnSize + 1, src2.rpnPatchSize + 1);
|
||||
if (rightRpnSize > 0)
|
||||
if (rightRpnSize > 0) {
|
||||
// If `rightRpnSize == 0`, then `memcpy(ptr, nullptr, rightRpnSize)` would be UB
|
||||
memcpy(ptr, src2.rpn.data(), rightRpnSize);
|
||||
}
|
||||
ptr[rightRpnSize] = op;
|
||||
}
|
||||
}
|
||||
@@ -593,10 +565,25 @@ void Expression::makeCheckRST() {
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeCheckBitIndex(uint8_t mask) {
|
||||
assume((mask & 0xC0) != 0x00); // The high two bits must correspond to BIT, RES, or SET
|
||||
|
||||
if (!isKnown()) {
|
||||
uint8_t *ptr = reserveSpace(2);
|
||||
*ptr++ = RPN_BIT_INDEX;
|
||||
*ptr = mask;
|
||||
} else if (int32_t val = value(); val & ~0x07) {
|
||||
// A valid bit index must be masked with 0x07
|
||||
static char const *instructions[4] = {"instruction", "BIT", "RES", "SET"};
|
||||
error("Invalid bit index %" PRId32 " for %s\n", val, instructions[mask >> 6]);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||
void Expression::checkNBit(uint8_t n) const {
|
||||
if (isKnown())
|
||||
::checkNBit(value(), n, "Expression");
|
||||
if (isKnown()) {
|
||||
::checkNBit(value(), n, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
||||
@@ -604,11 +591,23 @@ bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
||||
assume(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
||||
|
||||
if (v < -(1 << n) || v >= 1 << n) {
|
||||
warning(WARNING_TRUNCATION_1, "%s must be %u-bit\n", name, n);
|
||||
warning(
|
||||
WARNING_TRUNCATION_1,
|
||||
n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n"
|
||||
: "%s must be %u-bit\n",
|
||||
name ? name : "Expression",
|
||||
n
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (v < -(1 << (n - 1))) {
|
||||
warning(WARNING_TRUNCATION_2, "%s must be %u-bit\n", name, n);
|
||||
warning(
|
||||
WARNING_TRUNCATION_2,
|
||||
n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n"
|
||||
: "%s must be %u-bit\n",
|
||||
name ? name : "Expression",
|
||||
n
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/section.hpp"
|
||||
|
||||
@@ -49,9 +49,11 @@ static std::pair<Symbol const *, Symbol const *> currentLoadLabelScopes = {nullp
|
||||
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
|
||||
|
||||
// A quick check to see if we have an initialized section
|
||||
[[nodiscard]] static bool requireSection() {
|
||||
if (currentSection)
|
||||
[[nodiscard]]
|
||||
static bool requireSection() {
|
||||
if (currentSection) {
|
||||
return true;
|
||||
}
|
||||
|
||||
error("Cannot output data outside of a SECTION\n");
|
||||
return false;
|
||||
@@ -59,12 +61,15 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
|
||||
|
||||
// A quick check to see if we have an initialized section that can contain
|
||||
// this much initialized data
|
||||
[[nodiscard]] static bool requireCodeSection() {
|
||||
if (!requireSection())
|
||||
[[nodiscard]]
|
||||
static bool requireCodeSection() {
|
||||
if (!requireSection()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sect_HasData(currentSection->type))
|
||||
if (sect_HasData(currentSection->type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
error(
|
||||
"Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
||||
@@ -75,14 +80,15 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
|
||||
|
||||
void sect_CheckSizes() {
|
||||
for (Section const § : sectionList) {
|
||||
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize)
|
||||
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize) {
|
||||
error(
|
||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
||||
").\n",
|
||||
")\n",
|
||||
sect.name.c_str(),
|
||||
maxSize,
|
||||
sect.size
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,33 +112,36 @@ static unsigned int mergeSectUnion(
|
||||
|
||||
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
||||
// combination of both.
|
||||
if (sect_HasData(type))
|
||||
if (sect_HasData(type)) {
|
||||
sectError("Cannot declare ROM sections as UNION\n");
|
||||
}
|
||||
|
||||
if (org != UINT32_MAX) {
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != UINT32_MAX && sect.org != org)
|
||||
if (sect.org != UINT32_MAX && sect.org != org) {
|
||||
sectError(
|
||||
"Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org
|
||||
);
|
||||
else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs)))
|
||||
} else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs))) {
|
||||
sectError(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
else
|
||||
} else {
|
||||
// Otherwise, just override
|
||||
sect.org = org;
|
||||
}
|
||||
|
||||
} else if (alignment != 0) {
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - alignOffset) & mask(alignment))
|
||||
if ((sect.org - alignOffset) & mask(alignment)) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
sectError(
|
||||
@@ -163,34 +172,37 @@ static unsigned int
|
||||
uint16_t curOrg = org - sect.size;
|
||||
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != UINT32_MAX && sect.org != curOrg)
|
||||
if (sect.org != UINT32_MAX && sect.org != curOrg) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs)))
|
||||
} else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) {
|
||||
sectError(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
else
|
||||
} else {
|
||||
// Otherwise, just override
|
||||
sect.org = curOrg;
|
||||
}
|
||||
|
||||
} else if (alignment != 0) {
|
||||
int32_t curOfs = (alignOffset - sect.size) % (1U << alignment);
|
||||
|
||||
if (curOfs < 0)
|
||||
if (curOfs < 0) {
|
||||
curOfs += 1U << alignment;
|
||||
}
|
||||
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - curOfs) & mask(alignment))
|
||||
if ((sect.org - curOfs) & mask(alignment)) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
sectError(
|
||||
@@ -220,13 +232,14 @@ static void mergeSections(
|
||||
) {
|
||||
unsigned int nbSectErrors = 0;
|
||||
|
||||
if (type != sect.type)
|
||||
if (type != sect.type) {
|
||||
sectError(
|
||||
"Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (sect.modifier != mod) {
|
||||
sectError("Section already declared as %s section\n", sectionModNames[sect.modifier]);
|
||||
sectError("Section already declared as SECTION %s\n", sectionModNames[sect.modifier]);
|
||||
} else {
|
||||
switch (mod) {
|
||||
case SECTION_UNION:
|
||||
@@ -238,11 +251,13 @@ static void mergeSections(
|
||||
// Common checks
|
||||
|
||||
// If the section's bank is unspecified, override it
|
||||
if (sect.bank == UINT32_MAX)
|
||||
if (sect.bank == UINT32_MAX) {
|
||||
sect.bank = bank;
|
||||
}
|
||||
// If both specify a bank, it must be the same one
|
||||
else if (bank != UINT32_MAX && sect.bank != bank)
|
||||
else if (bank != UINT32_MAX && sect.bank != bank) {
|
||||
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
|
||||
}
|
||||
break;
|
||||
|
||||
case SECTION_NORMAL:
|
||||
@@ -253,13 +268,14 @@ static void mergeSections(
|
||||
}
|
||||
}
|
||||
|
||||
if (nbSectErrors)
|
||||
if (nbSectErrors) {
|
||||
fatalerror(
|
||||
"Cannot create section \"%s\" (%u error%s)\n",
|
||||
sect.name.c_str(),
|
||||
nbSectErrors,
|
||||
nbSectErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#undef sectError
|
||||
@@ -292,8 +308,9 @@ static Section *createSection(
|
||||
out_RegisterNode(sect.src);
|
||||
|
||||
// It is only needed to allocate memory for ROM sections.
|
||||
if (sect_HasData(type))
|
||||
if (sect_HasData(type)) {
|
||||
sect.data.resize(sectionTypeInfo[type].size);
|
||||
}
|
||||
|
||||
return §
|
||||
}
|
||||
@@ -314,9 +331,10 @@ static Section *getSection(
|
||||
|
||||
if (bank != UINT32_MAX) {
|
||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
||||
&& type != SECTTYPE_WRAMX)
|
||||
&& type != SECTTYPE_WRAMX) {
|
||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
||||
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
|
||||
} else if (bank < sectionTypeInfo[type].firstBank
|
||||
|| bank > sectionTypeInfo[type].lastBank) {
|
||||
error(
|
||||
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
|
||||
sectionTypeInfo[type].name.c_str(),
|
||||
@@ -324,6 +342,7 @@ static Section *getSection(
|
||||
sectionTypeInfo[type].firstBank,
|
||||
sectionTypeInfo[type].lastBank
|
||||
);
|
||||
}
|
||||
} else if (nbbanks(type) == 1) {
|
||||
// If the section type only has a single bank, implicitly force it
|
||||
bank = sectionTypeInfo[type].firstBank;
|
||||
@@ -339,7 +358,7 @@ static Section *getSection(
|
||||
}
|
||||
|
||||
if (org != UINT32_MAX) {
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) {
|
||||
error(
|
||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||
"; $%04" PRIx16 "]\n",
|
||||
@@ -348,6 +367,7 @@ static Section *getSection(
|
||||
sectionTypeInfo[type].startAddr,
|
||||
endaddr(type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (alignment != 0) {
|
||||
@@ -359,8 +379,9 @@ static Section *getSection(
|
||||
uint32_t mask = mask(alignment);
|
||||
|
||||
if (org != UINT32_MAX) {
|
||||
if ((org - alignOffset) & mask)
|
||||
if ((org - alignOffset) & mask) {
|
||||
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str());
|
||||
}
|
||||
alignment = 0; // Ignore it if it's satisfied
|
||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||
error(
|
||||
@@ -393,25 +414,29 @@ static Section *getSection(
|
||||
|
||||
// Set the current section
|
||||
static void changeSection() {
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatalerror("Cannot change the section within a UNION\n");
|
||||
}
|
||||
|
||||
sym_ResetCurrentLabelScopes();
|
||||
}
|
||||
|
||||
bool Section::isSizeKnown() const {
|
||||
// SECTION UNION and SECTION FRAGMENT can still grow
|
||||
if (modifier != SECTION_NORMAL)
|
||||
if (modifier != SECTION_NORMAL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The current section (or current load section if within one) is still growing
|
||||
if (this == currentSection || this == currentLoadSection)
|
||||
if (this == currentSection || this == currentLoadSection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any section on the stack is still growing
|
||||
for (SectionStackEntry &entry : sectionStack) {
|
||||
if (entry.section && entry.section->name == name)
|
||||
if (entry.section && entry.section->name == name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -426,12 +451,14 @@ void sect_NewSection(
|
||||
SectionModifier mod
|
||||
) {
|
||||
for (SectionStackEntry &entry : sectionStack) {
|
||||
if (entry.section && entry.section->name == name)
|
||||
if (entry.section && entry.section->name == name) {
|
||||
fatalerror("Section '%s' is already on the stack\n", name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("SECTION");
|
||||
}
|
||||
|
||||
Section *sect = getSection(name, type, org, attrs, mod);
|
||||
|
||||
@@ -454,16 +481,18 @@ void sect_SetLoadSection(
|
||||
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
|
||||
// your own peril! ^^
|
||||
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sect_HasData(type)) {
|
||||
error("`LOAD` blocks cannot create a ROM section\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("LOAD");
|
||||
}
|
||||
|
||||
Section *sect = getSection(name, type, org, attrs, mod);
|
||||
|
||||
@@ -475,12 +504,11 @@ void sect_SetLoadSection(
|
||||
}
|
||||
|
||||
void sect_EndLoadSection(char const *cause) {
|
||||
if (cause)
|
||||
if (cause) {
|
||||
warning(
|
||||
WARNING_UNTERMINATED_LOAD,
|
||||
"`LOAD` block without `ENDL` terminated by `%s`\n",
|
||||
cause
|
||||
WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`\n", cause
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentLoadSection) {
|
||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
||||
@@ -495,8 +523,9 @@ void sect_EndLoadSection(char const *cause) {
|
||||
}
|
||||
|
||||
void sect_CheckLoadClosed() {
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
|
||||
}
|
||||
}
|
||||
|
||||
Section *sect_GetSymbolSection() {
|
||||
@@ -515,16 +544,18 @@ uint32_t sect_GetOutputOffset() {
|
||||
// Returns how many bytes need outputting for the specified alignment and offset to succeed
|
||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
if (!sect)
|
||||
if (!sect) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool isFixed = sect->org != UINT32_MAX;
|
||||
|
||||
// If the section is not aligned, no bytes are needed
|
||||
// (fixed sections count as being maximally aligned for this purpose)
|
||||
uint8_t curAlignment = isFixed ? 16 : sect->align;
|
||||
if (curAlignment == 0)
|
||||
if (curAlignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
|
||||
uint16_t pcValue = isFixed ? sect->org : sect->alignOfs;
|
||||
@@ -533,18 +564,20 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
}
|
||||
|
||||
void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
if (!requireSection())
|
||||
if (!requireSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
|
||||
|
||||
if (sect->org != UINT32_MAX) {
|
||||
if ((sect->org + curOffset - offset) % alignSize)
|
||||
if ((sect->org + curOffset - offset) % alignSize) {
|
||||
error(
|
||||
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
||||
sect->org + curOffset
|
||||
);
|
||||
}
|
||||
} else if (sect->align != 0
|
||||
&& (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
||||
error(
|
||||
@@ -568,18 +601,22 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
}
|
||||
|
||||
static void growSection(uint32_t growth) {
|
||||
if (growth > 0 && curOffset > UINT32_MAX - growth)
|
||||
if (growth > 0 && curOffset > UINT32_MAX - growth) {
|
||||
fatalerror("Section size would overflow internal counter\n");
|
||||
}
|
||||
curOffset += growth;
|
||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size)
|
||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) {
|
||||
currentSection->size = outOffset;
|
||||
if (currentLoadSection && curOffset > currentLoadSection->size)
|
||||
}
|
||||
if (currentLoadSection && curOffset > currentLoadSection->size) {
|
||||
currentLoadSection->size = curOffset;
|
||||
}
|
||||
}
|
||||
|
||||
static void writeByte(uint8_t byte) {
|
||||
if (uint32_t index = sect_GetOutputOffset(); index < currentSection->data.size())
|
||||
if (uint32_t index = sect_GetOutputOffset(); index < currentSection->data.size()) {
|
||||
currentSection->data[index] = byte;
|
||||
}
|
||||
growSection(1);
|
||||
}
|
||||
|
||||
@@ -621,8 +658,9 @@ static void endUnionMember() {
|
||||
UnionStackEntry &member = currentUnionStack.top();
|
||||
uint32_t memberSize = curOffset - member.start;
|
||||
|
||||
if (memberSize > member.size)
|
||||
if (memberSize > member.size) {
|
||||
member.size = memberSize;
|
||||
}
|
||||
curOffset = member.start;
|
||||
}
|
||||
|
||||
@@ -645,64 +683,75 @@ void sect_EndUnion() {
|
||||
}
|
||||
|
||||
void sect_CheckUnionClosed() {
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
error("Unterminated UNION construct\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Output a constant byte
|
||||
void sect_ConstByte(uint8_t byte) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
writeByte(byte);
|
||||
}
|
||||
|
||||
// Output a string's character units as bytes
|
||||
void sect_ByteString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 8, "All character units"))
|
||||
break;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 8, "All character units")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
writeByte(static_cast<uint8_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a string's character units as words
|
||||
void sect_WordString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 16, "All character units"))
|
||||
break;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 16, "All character units")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
writeWord(static_cast<uint16_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a string's character units as longs
|
||||
void sect_LongString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
writeLong(static_cast<uint32_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Skip this many bytes
|
||||
void sect_Skip(uint32_t skip, bool ds) {
|
||||
if (!requireSection())
|
||||
if (!requireSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sect_HasData(currentSection->type)) {
|
||||
growSection(skip);
|
||||
} else {
|
||||
if (!ds)
|
||||
if (!ds) {
|
||||
warning(
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
"%s directive without data in ROM\n",
|
||||
@@ -710,16 +759,19 @@ void sect_Skip(uint32_t skip, bool ds) {
|
||||
: (skip == 2) ? "DW"
|
||||
: "DB"
|
||||
);
|
||||
}
|
||||
// We know we're in a code SECTION
|
||||
while (skip--)
|
||||
while (skip--) {
|
||||
writeByte(fillByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output a byte that can be relocatable or constant
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
void sect_RelByte(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_BYTE, expr, pcShift);
|
||||
@@ -730,14 +782,13 @@ void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
|
||||
// Output several bytes that can be relocatable or constant
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
if (!requireCodeSection())
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
Expression &expr = exprs[i % exprs.size()];
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
if (Expression const &expr = exprs[i % exprs.size()]; !expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_BYTE, expr, i);
|
||||
writeByte(0);
|
||||
} else {
|
||||
@@ -747,9 +798,10 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
}
|
||||
|
||||
// Output a word that can be relocatable or constant
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
void sect_RelWord(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_WORD, expr, pcShift);
|
||||
@@ -760,9 +812,10 @@ void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
|
||||
// Output a long that can be relocatable or constant
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
void sect_RelLong(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
createPatch(PATCHTYPE_LONG, expr, pcShift);
|
||||
@@ -773,9 +826,10 @@ void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
}
|
||||
|
||||
// Output a PC-relative byte that can be relocatable or constant
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
void sect_PCRelByte(Expression const &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Symbol const *pc = sym_GetPC(); !expr.isDiffConstant(pc)) {
|
||||
createPatch(PATCHTYPE_JR, expr, pcShift);
|
||||
@@ -786,15 +840,16 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
int16_t offset;
|
||||
|
||||
// Offset is relative to the byte *after* the operand
|
||||
if (sym == pc)
|
||||
if (sym == pc) {
|
||||
offset = -2; // PC as operand to `jr` is lower than reference PC by 2
|
||||
else
|
||||
} else {
|
||||
offset = sym->getValue() - (pc->getValue() + 1);
|
||||
}
|
||||
|
||||
if (offset < -128 || offset > 127) {
|
||||
error(
|
||||
"jr target must be between -128 and 127 bytes away, not %" PRId16
|
||||
"; use jp instead\n",
|
||||
"JR target must be between -128 and 127 bytes away, not %" PRId16
|
||||
"; use JP instead\n",
|
||||
offset
|
||||
);
|
||||
writeByte(0);
|
||||
@@ -810,16 +865,21 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
||||
startPos = 0;
|
||||
}
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file = nullptr;
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath)
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
file = fopen(fullPath->c_str(), "rb");
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (verbose)
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
@@ -836,10 +896,11 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
// The file is seekable; skip to the specified start position
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
if (errno != ESPIPE) {
|
||||
error(
|
||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||
);
|
||||
}
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
@@ -851,11 +912,13 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int byte; (byte = fgetc(file)) != EOF;)
|
||||
for (int byte; (byte = fgetc(file)) != EOF;) {
|
||||
writeByte(byte);
|
||||
}
|
||||
|
||||
if (ferror(file))
|
||||
if (ferror(file)) {
|
||||
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a slice of a binary file
|
||||
@@ -868,18 +931,24 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length);
|
||||
length = 0;
|
||||
}
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
if (length == 0) // Don't even bother with 0-byte slices
|
||||
}
|
||||
if (length == 0) { // Don't even bother with 0-byte slices
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file = nullptr;
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath)
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
file = fopen(fullPath->c_str(), "rb");
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (verbose)
|
||||
// LCOV_EXCL_START
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
@@ -906,10 +975,11 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
// The file is seekable; skip to the specified start position
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
if (errno != ESPIPE) {
|
||||
error(
|
||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||
);
|
||||
}
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
@@ -955,11 +1025,13 @@ void sect_PushSection() {
|
||||
}
|
||||
|
||||
void sect_PopSection() {
|
||||
if (sectionStack.empty())
|
||||
if (sectionStack.empty()) {
|
||||
fatalerror("No entries in the section stack\n");
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("POPS");
|
||||
}
|
||||
|
||||
SectionStackEntry entry = sectionStack.front();
|
||||
sectionStack.pop_front();
|
||||
@@ -980,14 +1052,17 @@ void sect_CheckStack() {
|
||||
}
|
||||
|
||||
void sect_EndSection() {
|
||||
if (!currentSection)
|
||||
if (!currentSection) {
|
||||
fatalerror("Cannot end the section outside of a SECTION\n");
|
||||
}
|
||||
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatalerror("Cannot end the section within a UNION\n");
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("ENDSECTION");
|
||||
}
|
||||
|
||||
// Reset the section scope
|
||||
currentSection = nullptr;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/symbol.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <unordered_map>
|
||||
@@ -9,6 +10,7 @@
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp" // assume
|
||||
#include "util.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
@@ -40,8 +42,9 @@ bool sym_IsPC(Symbol const *sym) {
|
||||
}
|
||||
|
||||
void sym_ForEach(void (*callback)(Symbol &)) {
|
||||
for (auto &it : symbols)
|
||||
for (auto &it : symbols) {
|
||||
callback(it.second);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t NARGCallback() {
|
||||
@@ -92,7 +95,7 @@ int32_t Symbol::getOutputValue() const {
|
||||
}
|
||||
|
||||
ContentSpan const &Symbol::getMacro() const {
|
||||
assume((std::holds_alternative<ContentSpan>(data)));
|
||||
assume(std::holds_alternative<ContentSpan>(data));
|
||||
return std::get<ContentSpan>(data);
|
||||
}
|
||||
|
||||
@@ -101,8 +104,9 @@ std::shared_ptr<std::string> Symbol::getEqus() const {
|
||||
std::holds_alternative<std::shared_ptr<std::string>>(data)
|
||||
|| std::holds_alternative<std::shared_ptr<std::string> (*)()>(data)
|
||||
);
|
||||
if (auto *callback = std::get_if<std::shared_ptr<std::string> (*)()>(&data); callback)
|
||||
if (auto *callback = std::get_if<std::shared_ptr<std::string> (*)()>(&data); callback) {
|
||||
return (*callback)();
|
||||
}
|
||||
return std::get<std::shared_ptr<std::string>>(data);
|
||||
}
|
||||
|
||||
@@ -123,8 +127,14 @@ static void updateSymbolFilename(Symbol &sym) {
|
||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||
|
||||
// If the old node was registered, ensure the new one is too
|
||||
if (oldSrc && oldSrc->ID != UINT32_MAX)
|
||||
if (oldSrc && oldSrc->ID != UINT32_MAX) {
|
||||
out_RegisterNode(sym.src);
|
||||
}
|
||||
}
|
||||
|
||||
static bool isValidIdentifier(std::string const &s) {
|
||||
return !s.empty() && startsIdentifier(s[0])
|
||||
&& std::all_of(s.begin() + 1, s.end(), [](char c) { return continuesIdentifier(c); });
|
||||
}
|
||||
|
||||
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||
@@ -133,10 +143,20 @@ static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||
error("'%s' is reserved for a built-in symbol\n", sym.name.c_str());
|
||||
} else {
|
||||
error("'%s' already defined", sym.name.c_str());
|
||||
if (asType)
|
||||
if (asType) {
|
||||
fprintf(stderr, " as %s", asType);
|
||||
}
|
||||
fputs(" at ", stderr);
|
||||
dumpFilename(sym);
|
||||
if (sym.type == SYM_EQUS) {
|
||||
if (std::string const &contents = *sym.getEqus(); isValidIdentifier(contents)) {
|
||||
fprintf(
|
||||
stderr,
|
||||
" (should it be {interpolated} to define its contents \"%s\"?)\n",
|
||||
contents.c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,28 +204,34 @@ static bool isAutoScoped(std::string const &symName) {
|
||||
size_t dotPos = symName.find('.');
|
||||
|
||||
// If there are no dots, it's not a local label
|
||||
if (dotPos == std::string::npos)
|
||||
if (dotPos == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
||||
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos)
|
||||
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for nothing after the dot
|
||||
if (dotPos == symName.length() - 1)
|
||||
if (dotPos == symName.length() - 1) {
|
||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str());
|
||||
}
|
||||
|
||||
// Check for more than one dot
|
||||
if (symName.find('.', dotPos + 1) != std::string::npos)
|
||||
if (symName.find('.', dotPos + 1) != std::string::npos) {
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str());
|
||||
}
|
||||
|
||||
// Check for already-qualified local label
|
||||
if (dotPos > 0)
|
||||
if (dotPos > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for unqualifiable local label
|
||||
if (!globalScope)
|
||||
if (!globalScope) {
|
||||
fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -252,24 +278,28 @@ void sym_Purge(std::string const &symName) {
|
||||
Symbol *sym = sym_FindScopedValidSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
if (sym_IsPurgedScoped(symName))
|
||||
if (sym_IsPurgedScoped(symName)) {
|
||||
error("'%s' was already purged\n", symName.c_str());
|
||||
else
|
||||
} else {
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
}
|
||||
} else if (sym->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
|
||||
} else if (sym->ID != UINT32_MAX) {
|
||||
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
|
||||
} else {
|
||||
if (sym->isExported)
|
||||
if (sym->isExported) {
|
||||
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str());
|
||||
else if (sym->isLabel())
|
||||
} else if (sym->isLabel()) {
|
||||
warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str());
|
||||
}
|
||||
// Do not keep a reference to the label after purging it
|
||||
if (sym == globalScope)
|
||||
if (sym == globalScope) {
|
||||
globalScope = nullptr;
|
||||
if (sym == localScope)
|
||||
}
|
||||
if (sym == localScope) {
|
||||
localScope = nullptr;
|
||||
}
|
||||
purgedSymbols.emplace(sym->name);
|
||||
symbols.erase(sym->name);
|
||||
}
|
||||
@@ -295,31 +325,22 @@ void sym_SetRSValue(int32_t value) {
|
||||
}
|
||||
|
||||
uint32_t Symbol::getConstantValue() const {
|
||||
if (isConstant())
|
||||
if (isConstant()) {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
if (sym_IsPC(this)) {
|
||||
if (!getSection())
|
||||
if (!getSection()) {
|
||||
error("PC has no value outside of a section\n");
|
||||
else
|
||||
} else {
|
||||
error("PC does not have a constant value; the current section is not fixed\n");
|
||||
}
|
||||
} else {
|
||||
error("\"%s\" does not have a constant value\n", name.c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t sym_GetConstantValue(std::string const &symName) {
|
||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym)
|
||||
return sym->getConstantValue();
|
||||
|
||||
if (sym_IsPurgedScoped(symName))
|
||||
error("'%s' not defined; it was purged\n", symName.c_str());
|
||||
else
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::pair<Symbol const *, Symbol const *> sym_GetCurrentLabelScopes() {
|
||||
return {globalScope, localScope};
|
||||
}
|
||||
@@ -361,8 +382,9 @@ static Symbol *createNonrelocSymbol(std::string const &symName, bool numeric) {
|
||||
Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, true);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_EQU;
|
||||
sym->data = value;
|
||||
@@ -373,8 +395,9 @@ Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return sym_AddEqu(symName, value);
|
||||
}
|
||||
|
||||
if (sym->isDefined() && sym->type != SYM_EQU) {
|
||||
alreadyDefinedError(*sym, "non-EQU");
|
||||
@@ -394,8 +417,9 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
|
||||
Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> str) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_EQUS;
|
||||
sym->data = str;
|
||||
@@ -405,8 +429,9 @@ Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> s
|
||||
Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string> str) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return sym_AddString(symName, str);
|
||||
}
|
||||
|
||||
if (sym->type != SYM_EQUS) {
|
||||
if (sym->isDefined()) {
|
||||
@@ -462,12 +487,14 @@ static Symbol *addLabel(std::string const &symName) {
|
||||
sym->type = SYM_LABEL;
|
||||
sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
|
||||
// Don't export anonymous labels
|
||||
if (exportAll && !symName.starts_with('!'))
|
||||
if (exportAll && !symName.starts_with('!')) {
|
||||
sym->isExported = true;
|
||||
}
|
||||
sym->section = sect_GetSymbolSection();
|
||||
|
||||
if (sym && !sym->section)
|
||||
if (sym && !sym->section) {
|
||||
error("Label \"%s\" created outside of a SECTION\n", symName.c_str());
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
@@ -478,8 +505,9 @@ Symbol *sym_AddLocalLabel(std::string const &symName) {
|
||||
|
||||
Symbol *sym = addLabel(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
||||
|
||||
if (sym)
|
||||
if (sym) {
|
||||
localScope = sym;
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
@@ -503,8 +531,10 @@ static uint32_t anonLabelID = 0;
|
||||
|
||||
Symbol *sym_AddAnonLabel() {
|
||||
if (anonLabelID == UINT32_MAX) {
|
||||
// LCOV_EXCL_START
|
||||
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
|
||||
return nullptr;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
std::string anon = sym_MakeAnonLabelName(0, true); // The direction is important!
|
||||
@@ -516,7 +546,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
uint32_t id = 0;
|
||||
|
||||
if (neg) {
|
||||
if (ofs > anonLabelID)
|
||||
if (ofs > anonLabelID) {
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
||||
" ha%s been created so far\n",
|
||||
@@ -524,45 +554,52 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
anonLabelID,
|
||||
anonLabelID == 1 ? "s" : "ve"
|
||||
);
|
||||
else
|
||||
} else {
|
||||
id = anonLabelID - ofs;
|
||||
}
|
||||
} else {
|
||||
ofs--; // We're referencing symbols that haven't been created yet...
|
||||
if (ofs > UINT32_MAX - anonLabelID)
|
||||
if (ofs > UINT32_MAX - anonLabelID) {
|
||||
// LCOV_EXCL_START
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||
" may still be created\n",
|
||||
ofs + 1,
|
||||
UINT32_MAX - anonLabelID
|
||||
);
|
||||
else
|
||||
} else {
|
||||
// LCOV_EXCL_STOP
|
||||
id = anonLabelID + ofs;
|
||||
}
|
||||
}
|
||||
|
||||
std::string anon("!");
|
||||
anon += std::to_string(id);
|
||||
return anon;
|
||||
return "!"s + std::to_string(id);
|
||||
}
|
||||
|
||||
void sym_Export(std::string const &symName) {
|
||||
if (symName.starts_with('!')) {
|
||||
// LCOV_EXCL_START
|
||||
// The parser does not accept anonymous labels for an `EXPORT` directive
|
||||
error("Anonymous labels cannot be exported\n");
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
|
||||
// If the symbol doesn't exist, create a ref that can be purged
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
sym = sym_Ref(symName);
|
||||
}
|
||||
sym->isExported = true;
|
||||
}
|
||||
|
||||
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_MACRO;
|
||||
sym->data = span;
|
||||
@@ -627,11 +664,13 @@ void sym_Init(time_t now) {
|
||||
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
|
||||
#endif
|
||||
|
||||
// LCOV_EXCL_START
|
||||
if (now == static_cast<time_t>(-1)) {
|
||||
warn("Failed to determine current time");
|
||||
// Fall back by pretending we are at the Epoch
|
||||
now = 0;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
tm const *time_local = localtime(&now);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
@@ -35,13 +35,13 @@ struct WarningFlag {
|
||||
WarningLevel level;
|
||||
};
|
||||
|
||||
static const WarningFlag metaWarnings[] = {
|
||||
static WarningFlag const metaWarnings[] = {
|
||||
{"all", LEVEL_ALL },
|
||||
{"extra", LEVEL_EXTRA },
|
||||
{"everything", LEVEL_EVERYTHING},
|
||||
};
|
||||
|
||||
static const WarningFlag warningFlags[NB_WARNINGS] = {
|
||||
static WarningFlag const warningFlags[NB_WARNINGS] = {
|
||||
{"assert", LEVEL_DEFAULT },
|
||||
{"backwards-for", LEVEL_ALL },
|
||||
{"builtin-args", LEVEL_ALL },
|
||||
@@ -85,8 +85,9 @@ enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
||||
|
||||
static WarningBehavior getWarningBehavior(WarningID id) {
|
||||
// Check if warnings are globally disabled
|
||||
if (!warnings)
|
||||
if (!warnings) {
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
|
||||
// Get the state of this warning flag
|
||||
WarningState const &flagState = warningStates.flagStates[id];
|
||||
@@ -100,34 +101,43 @@ static WarningBehavior getWarningBehavior(WarningID id) {
|
||||
warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED;
|
||||
|
||||
// First, check the state of the specific warning flag
|
||||
if (flagState.state == WARNING_DISABLED) // -Wno-<flag>
|
||||
if (flagState.state == WARNING_DISABLED) { // -Wno-<flag>
|
||||
return WarningBehavior::DISABLED;
|
||||
if (flagState.error == WARNING_ENABLED) // -Werror=<flag>
|
||||
}
|
||||
if (flagState.error == WARNING_ENABLED) { // -Werror=<flag>
|
||||
return WarningBehavior::ERROR;
|
||||
if (flagState.state == WARNING_ENABLED) // -W<flag>
|
||||
}
|
||||
if (flagState.state == WARNING_ENABLED) { // -W<flag>
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// If no flag is specified, check the state of the "meta" flags that affect this warning flag
|
||||
if (metaState.state == WARNING_DISABLED) // -Wno-<meta>
|
||||
if (metaState.state == WARNING_DISABLED) { // -Wno-<meta>
|
||||
return WarningBehavior::DISABLED;
|
||||
if (metaState.error == WARNING_ENABLED) // -Werror=<meta>
|
||||
}
|
||||
if (metaState.error == WARNING_ENABLED) { // -Werror=<meta>
|
||||
return WarningBehavior::ERROR;
|
||||
if (metaState.state == WARNING_ENABLED) // -W<meta>
|
||||
}
|
||||
if (metaState.state == WARNING_ENABLED) { // -W<meta>
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// If no meta flag is specified, check the default state of this warning flag
|
||||
if (warningFlags[id].level == LEVEL_DEFAULT) // enabled by default
|
||||
if (warningFlags[id].level == LEVEL_DEFAULT) { // enabled by default
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// No flag enables this warning, explicitly or implicitly
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
|
||||
void WarningState::update(WarningState other) {
|
||||
if (other.state != WARNING_DEFAULT)
|
||||
if (other.state != WARNING_DEFAULT) {
|
||||
state = other.state;
|
||||
if (other.error != WARNING_DEFAULT)
|
||||
}
|
||||
if (other.error != WARNING_DEFAULT) {
|
||||
error = other.error;
|
||||
}
|
||||
}
|
||||
|
||||
void processWarningFlag(char const *flag) {
|
||||
@@ -149,16 +159,16 @@ void processWarningFlag(char const *flag) {
|
||||
if (rootFlag.starts_with("error=")) {
|
||||
// `-Werror=<flag>` enables the flag as an error
|
||||
state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED};
|
||||
rootFlag.erase(0, QUOTEDSTRLEN("error="));
|
||||
rootFlag.erase(0, literal_strlen("error="));
|
||||
} else if (rootFlag.starts_with("no-error=")) {
|
||||
// `-Wno-error=<flag>` prevents the flag from being an error,
|
||||
// without affecting whether it is enabled
|
||||
state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED};
|
||||
rootFlag.erase(0, QUOTEDSTRLEN("no-error="));
|
||||
rootFlag.erase(0, literal_strlen("no-error="));
|
||||
} else if (rootFlag.starts_with("no-")) {
|
||||
// `-Wno-<flag>` disables the flag
|
||||
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
|
||||
rootFlag.erase(0, QUOTEDSTRLEN("no-"));
|
||||
rootFlag.erase(0, literal_strlen("no-"));
|
||||
} else {
|
||||
// `-W<flag>` enables the flag
|
||||
state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
|
||||
@@ -184,12 +194,14 @@ void processWarningFlag(char const *flag) {
|
||||
// The `if`'s condition above ensures that this will run at least once
|
||||
do {
|
||||
// If we don't have a digit, bail
|
||||
if (*ptr < '0' || *ptr > '9')
|
||||
if (*ptr < '0' || *ptr > '9') {
|
||||
break;
|
||||
}
|
||||
// Avoid overflowing!
|
||||
if (param > UINT8_MAX - (*ptr - '0')) {
|
||||
if (!warned)
|
||||
if (!warned) {
|
||||
warnx("Invalid warning flag \"%s\": capping parameter at 255", flag);
|
||||
}
|
||||
warned = true; // Only warn once, cap always
|
||||
param = 255;
|
||||
continue;
|
||||
@@ -203,8 +215,9 @@ void processWarningFlag(char const *flag) {
|
||||
if (*ptr == '\0') {
|
||||
rootFlag.resize(equals);
|
||||
// `-W<flag>=0` is equivalent to `-Wno-<flag>`
|
||||
if (param == 0)
|
||||
if (param == 0) {
|
||||
state.state = WARNING_DISABLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,8 +231,9 @@ void processWarningFlag(char const *flag) {
|
||||
assume(paramWarning.defaultLevel <= maxParam);
|
||||
|
||||
if (rootFlag == warningFlags[baseID].name) { // Match!
|
||||
if (rootFlag == "numeric-string")
|
||||
if (rootFlag == "numeric-string") {
|
||||
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
|
||||
}
|
||||
|
||||
// If making the warning an error but param is 0, set to the maximum
|
||||
// This accommodates `-Werror=<flag>`, but also `-Werror=<flag>=0`, which is
|
||||
@@ -229,7 +243,7 @@ void processWarningFlag(char const *flag) {
|
||||
if (param == 0) {
|
||||
param = paramWarning.defaultLevel;
|
||||
} else if (param > maxParam) {
|
||||
if (param != 255) // Don't warn if already capped
|
||||
if (param != 255) { // Don't warn if already capped
|
||||
warnx(
|
||||
"Invalid parameter %" PRIu8
|
||||
" for warning flag \"%s\"; capping at maximum %" PRIu8,
|
||||
@@ -237,16 +251,18 @@ void processWarningFlag(char const *flag) {
|
||||
rootFlag.c_str(),
|
||||
maxParam
|
||||
);
|
||||
}
|
||||
param = maxParam;
|
||||
}
|
||||
|
||||
// Set the first <param> to enabled/error, and disable the rest
|
||||
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
||||
WarningState &warning = warningStates.flagStates[baseID + ofs];
|
||||
if (ofs < param)
|
||||
if (ofs < param) {
|
||||
warning.update(state);
|
||||
else
|
||||
} else {
|
||||
warning.state = WARNING_DISABLED;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -259,8 +275,9 @@ void processWarningFlag(char const *flag) {
|
||||
if (rootFlag == metaWarning.name) {
|
||||
// Set each of the warning flags that meets this level
|
||||
for (WarningID id : EnumSeq(NB_WARNINGS)) {
|
||||
if (metaWarning.level >= warningFlags[id].level)
|
||||
if (metaWarning.level >= warningFlags[id].level) {
|
||||
warningStates.metaStates[id].update(state);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -299,16 +316,18 @@ void error(char const *fmt, ...) {
|
||||
|
||||
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
|
||||
nbErrors++;
|
||||
if (nbErrors == maxErrors)
|
||||
if (nbErrors == maxErrors) {
|
||||
errx(
|
||||
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
|
||||
"aborted!",
|
||||
maxErrors,
|
||||
maxErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void fatalerror(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void fatalerror(char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
|
||||
11
src/bison.sh
11
src/bison.sh
@@ -12,17 +12,20 @@ if [ "$BISON_MAJOR" -lt 3 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BISON_FLAGS="-Wall -Dparse.lac=full -Dlr.type=ielr"
|
||||
BISON_FLAGS="-Wall -Dlr.type=ielr"
|
||||
|
||||
# Set some optimization flags on versions that support them
|
||||
if [ "$BISON_MAJOR" -eq 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 5 ]; then
|
||||
BISON_FLAGS="$BISON_FLAGS -Dapi.token.raw=true"
|
||||
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 5 ]; then
|
||||
BISON_FLAGS="$BISON_FLAGS -Dparse.lac=full -Dapi.token.raw=true"
|
||||
fi
|
||||
if [ "$BISON_MAJOR" -eq 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 6 ]; then
|
||||
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 6 ]; then
|
||||
BISON_FLAGS="$BISON_FLAGS -Dparse.error=detailed"
|
||||
else
|
||||
BISON_FLAGS="$BISON_FLAGS -Dparse.error=verbose"
|
||||
fi
|
||||
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 7 ]; then
|
||||
BISON_FLAGS="$BISON_FLAGS -Wcounterexamples"
|
||||
fi
|
||||
|
||||
# Replace the arguments to this script ($@) with the ones in $BISON_FLAGS
|
||||
eval "set -- $BISON_FLAGS"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "error.hpp"
|
||||
|
||||
@@ -22,7 +22,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
[[noreturn]] static void verr(char const *fmt, va_list ap) {
|
||||
[[noreturn]]
|
||||
static void verr(char const *fmt, va_list ap) {
|
||||
char const *error = strerror(errno);
|
||||
|
||||
fprintf(stderr, "error: ");
|
||||
@@ -32,7 +33,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
[[noreturn]] static void verrx(char const *fmt, va_list ap) {
|
||||
[[noreturn]]
|
||||
static void verrx(char const *fmt, va_list ap) {
|
||||
fprintf(stderr, "error: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
putc('\n', stderr);
|
||||
@@ -56,14 +58,16 @@ void warnx(char const *fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
[[noreturn]] void err(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void err(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
verr(fmt, ap);
|
||||
}
|
||||
|
||||
[[noreturn]] void errx(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void errx(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
|
||||
77
src/extern/getopt.cpp
vendored
77
src/extern/getopt.cpp
vendored
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* This implementation was taken from musl and modified for RGBDS */
|
||||
// This implementation was taken from musl and modified for RGBDS
|
||||
|
||||
#include "extern/getopt.hpp"
|
||||
|
||||
@@ -19,8 +19,9 @@ static int musl_optpos;
|
||||
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
|
||||
FILE *f = stderr;
|
||||
|
||||
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l)
|
||||
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l) {
|
||||
putc('\n', f);
|
||||
}
|
||||
}
|
||||
|
||||
static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
@@ -35,8 +36,9 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
musl_optind = 1;
|
||||
}
|
||||
|
||||
if (musl_optind >= argc || !argv[musl_optind])
|
||||
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argv[musl_optind][0] != '-') {
|
||||
if (optstring[0] == '-') {
|
||||
@@ -46,18 +48,21 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!argv[musl_optind][1])
|
||||
if (!argv[musl_optind][1]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2])
|
||||
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2]) {
|
||||
return musl_optind++, -1;
|
||||
}
|
||||
|
||||
if (!musl_optpos)
|
||||
if (!musl_optpos) {
|
||||
musl_optpos++;
|
||||
}
|
||||
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
||||
if (k < 0) {
|
||||
k = 1;
|
||||
c = 0xFFFD; /* replacement char */
|
||||
c = 0xFFFD; // replacement char
|
||||
}
|
||||
optchar = argv[musl_optind] + musl_optpos;
|
||||
musl_optpos += k;
|
||||
@@ -67,23 +72,26 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
musl_optpos = 0;
|
||||
}
|
||||
|
||||
if (optstring[0] == '-' || optstring[0] == '+')
|
||||
if (optstring[0] == '-' || optstring[0] == '+') {
|
||||
optstring++;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
d = 0;
|
||||
do {
|
||||
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
||||
if (l > 0)
|
||||
if (l > 0) {
|
||||
i += l;
|
||||
else
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
} while (l && d != c);
|
||||
|
||||
if (d != c || c == ':') {
|
||||
musl_optopt = c;
|
||||
if (optstring[0] != ':' && musl_opterr)
|
||||
if (optstring[0] != ':' && musl_opterr) {
|
||||
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
if (optstring[i] == ':') {
|
||||
@@ -94,10 +102,12 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
}
|
||||
if (musl_optind > argc) {
|
||||
musl_optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
if (optstring[0] == ':') {
|
||||
return ':';
|
||||
if (musl_opterr)
|
||||
}
|
||||
if (musl_opterr) {
|
||||
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
@@ -108,8 +118,9 @@ static void permute(char **argv, int dest, int src) {
|
||||
char *tmp = argv[src];
|
||||
int i;
|
||||
|
||||
for (i = src; i > dest; i--)
|
||||
for (i = src; i > dest; i--) {
|
||||
argv[i] = argv[i - 1];
|
||||
}
|
||||
argv[dest] = tmp;
|
||||
}
|
||||
|
||||
@@ -128,17 +139,20 @@ static int musl_getopt_long(
|
||||
musl_optind = 1;
|
||||
}
|
||||
|
||||
if (musl_optind >= argc || !argv[musl_optind])
|
||||
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
skipped = musl_optind;
|
||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||
int i;
|
||||
for (i = musl_optind;; i++) {
|
||||
if (i >= argc || !argv[i])
|
||||
if (i >= argc || !argv[i]) {
|
||||
return -1;
|
||||
if (argv[i][0] == '-' && argv[i][1])
|
||||
}
|
||||
if (argv[i][0] == '-' && argv[i][1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
musl_optind = i;
|
||||
}
|
||||
@@ -147,8 +161,9 @@ static int musl_getopt_long(
|
||||
if (resumed > skipped) {
|
||||
int i, cnt = musl_optind - resumed;
|
||||
|
||||
for (i = 0; i < cnt; i++)
|
||||
for (i = 0; i < cnt; i++) {
|
||||
permute(argv, skipped, musl_optind - 1);
|
||||
}
|
||||
musl_optind = skipped + cnt;
|
||||
}
|
||||
return ret;
|
||||
@@ -169,14 +184,16 @@ static int musl_getopt_long_core(
|
||||
char const *name = longopts[i].name;
|
||||
|
||||
opt = start;
|
||||
if (*opt == '-')
|
||||
if (*opt == '-') {
|
||||
opt++;
|
||||
}
|
||||
while (*opt && *opt != '=' && *opt == *name) {
|
||||
name++;
|
||||
opt++;
|
||||
}
|
||||
if (*opt && *opt != '=')
|
||||
if (*opt && *opt != '=') {
|
||||
continue;
|
||||
}
|
||||
arg = opt;
|
||||
match = i;
|
||||
if (!*name) {
|
||||
@@ -191,8 +208,9 @@ static int musl_getopt_long_core(
|
||||
for (i = 0; optstring[i]; i++) {
|
||||
int j = 0;
|
||||
|
||||
while (j < l && start[j] == optstring[i + j])
|
||||
while (j < l && start[j] == optstring[i + j]) {
|
||||
j++;
|
||||
}
|
||||
if (j == l) {
|
||||
cnt++;
|
||||
break;
|
||||
@@ -206,8 +224,9 @@ static int musl_getopt_long_core(
|
||||
if (*opt == '=') {
|
||||
if (!longopts[i].has_arg) {
|
||||
musl_optopt = longopts[i].val;
|
||||
if (colon || !musl_opterr)
|
||||
if (colon || !musl_opterr) {
|
||||
return '?';
|
||||
}
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option does not take an argument: ",
|
||||
@@ -221,10 +240,12 @@ static int musl_getopt_long_core(
|
||||
musl_optarg = argv[musl_optind];
|
||||
if (!musl_optarg) {
|
||||
musl_optopt = longopts[i].val;
|
||||
if (colon)
|
||||
if (colon) {
|
||||
return ':';
|
||||
if (!musl_opterr)
|
||||
}
|
||||
if (!musl_opterr) {
|
||||
return '?';
|
||||
}
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option requires an argument: ",
|
||||
@@ -235,8 +256,9 @@ static int musl_getopt_long_core(
|
||||
}
|
||||
musl_optind++;
|
||||
}
|
||||
if (idx)
|
||||
if (idx) {
|
||||
*idx = i;
|
||||
}
|
||||
if (longopts[i].flag) {
|
||||
*longopts[i].flag = longopts[i].val;
|
||||
return 0;
|
||||
@@ -245,13 +267,14 @@ static int musl_getopt_long_core(
|
||||
}
|
||||
if (argv[musl_optind][1] == '-') {
|
||||
musl_optopt = 0;
|
||||
if (!colon && musl_opterr)
|
||||
if (!colon && musl_opterr) {
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
|
||||
argv[musl_optind] + 2,
|
||||
strlen(argv[musl_optind] + 2)
|
||||
);
|
||||
}
|
||||
musl_optind++;
|
||||
return '?';
|
||||
}
|
||||
|
||||
54
src/extern/utf8decoder.cpp
vendored
54
src/extern/utf8decoder.cpp
vendored
@@ -1,35 +1,35 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */
|
||||
// UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
|
||||
static uint8_t const utf8d[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00..0f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10..1f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20..2f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 30..3f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40..4f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50..5f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60..6f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70..7f */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80..8f */
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, /* 90..9f */
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* a0..af */
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* b0..bf */
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* c0..cf */
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* d0..df */
|
||||
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, /* e0..ef */
|
||||
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* f0..ff */
|
||||
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, /* s0 */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, /* s3 */
|
||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, /* s4 */
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, /* s5 */
|
||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s6 */
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s7 */
|
||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s8 */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..0f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10..1f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..2f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 30..3f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..4f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 50..5f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..6f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 70..7f
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80..8f
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 90..9f
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..af
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // b0..bf
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..cf
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // d0..df
|
||||
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, // e0..ef
|
||||
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // f0..ff
|
||||
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, // s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s1
|
||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, // s3
|
||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, // s5
|
||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s7
|
||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s8
|
||||
};
|
||||
|
||||
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) {
|
||||
|
||||
426
src/fix/main.cpp
426
src/fix/main.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -17,27 +17,26 @@
|
||||
#include "platform.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#define UNSPECIFIED 0x200 // Should not be in byte range
|
||||
static constexpr uint16_t UNSPECIFIED = 0x200;
|
||||
static_assert(UNSPECIFIED > 0xFF, "UNSPECIFIED should not be in byte range!");
|
||||
|
||||
#define BANK_SIZE 0x4000
|
||||
static constexpr off_t BANK_SIZE = 0x4000;
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "Ccf:i:jk:L:l:m:n:Op:r:st:Vv";
|
||||
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:Vv";
|
||||
|
||||
/*
|
||||
* Equivalent long options
|
||||
* Please keep in the same order as short opts
|
||||
*
|
||||
* Also, make sure long opts don't create ambiguity:
|
||||
* A long opt's name should start with the same letter as its short opt,
|
||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
* This is because long opt matching, even to a single char, is prioritized
|
||||
* over short opt matching
|
||||
*/
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"color-only", no_argument, nullptr, 'C'},
|
||||
{"color-compatible", no_argument, nullptr, 'c'},
|
||||
{"fix-spec", required_argument, nullptr, 'f'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"game-id", required_argument, nullptr, 'i'},
|
||||
{"non-japanese", no_argument, nullptr, 'j'},
|
||||
{"new-licensee", required_argument, nullptr, 'k'},
|
||||
@@ -46,6 +45,7 @@ static option const longopts[] = {
|
||||
{"mbc-type", required_argument, nullptr, 'm'},
|
||||
{"rom-version", required_argument, nullptr, 'n'},
|
||||
{"overwrite", no_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
{"ram-size", required_argument, nullptr, 'r'},
|
||||
{"sgb-compatible", no_argument, nullptr, 's'},
|
||||
@@ -55,9 +55,10 @@ static option const longopts[] = {
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
// LCOV_EXCL_START
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
"Usage: rgbfix [-hjOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
" [-L <logo_file>] [-l <licensee_byte>] [-m <mbc_type>]\n"
|
||||
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
|
||||
" <file> ...\n"
|
||||
@@ -66,6 +67,7 @@ static void printUsage() {
|
||||
" to the man page for a list of values\n"
|
||||
" -p, --pad-value <value> pad to the next valid size using this value\n"
|
||||
" -r, --ram-size <code> set the cart RAM size byte to this value\n"
|
||||
" -o, --output <path> set the output file\n"
|
||||
" -V, --version print RGBFIX version and exit\n"
|
||||
" -v, --validate fix the header logo and both checksums (-f lhg)\n"
|
||||
"\n"
|
||||
@@ -73,18 +75,21 @@ static void printUsage() {
|
||||
stderr
|
||||
);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
static uint8_t nbErrors;
|
||||
|
||||
[[gnu::format(printf, 1, 2)]] static void report(char const *fmt, ...) {
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
static void report(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (nbErrors != UINT8_MAX)
|
||||
if (nbErrors != UINT8_MAX) {
|
||||
nbErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
enum MbcType {
|
||||
@@ -184,23 +189,23 @@ static void printAcceptedMBCNames() {
|
||||
|
||||
static uint8_t tpp1Rev[2];
|
||||
|
||||
/*
|
||||
* @return False on failure
|
||||
*/
|
||||
static bool readMBCSlice(char const *&name, char const *expected) {
|
||||
while (*expected) {
|
||||
char c = *name++;
|
||||
|
||||
if (c == '\0') // Name too short
|
||||
if (c == '\0') { // Name too short
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c >= 'a' && c <= 'z') // Perform the comparison case-insensitive
|
||||
if (c >= 'a' && c <= 'z') { // Perform the comparison case-insensitive
|
||||
c = c - 'a' + 'A';
|
||||
else if (c == '_') // Treat underscores as spaces
|
||||
} else if (c == '_') { // Treat underscores as spaces
|
||||
c = ' ';
|
||||
}
|
||||
|
||||
if (c != *expected++)
|
||||
if (c != *expected++) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -223,10 +228,12 @@ static MbcType parseMBC(char const *name) {
|
||||
char *endptr;
|
||||
unsigned long mbc = strtoul(name, &endptr, base);
|
||||
|
||||
if (*endptr)
|
||||
if (*endptr) {
|
||||
return MBC_BAD;
|
||||
if (mbc > 0xFF)
|
||||
}
|
||||
if (mbc > 0xFF) {
|
||||
return MBC_BAD_RANGE;
|
||||
}
|
||||
return static_cast<MbcType>(mbc);
|
||||
|
||||
} else {
|
||||
@@ -235,13 +242,15 @@ static MbcType parseMBC(char const *name) {
|
||||
char const *ptr = name;
|
||||
|
||||
// Trim off leading whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t')
|
||||
while (*ptr == ' ' || *ptr == '\t') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
#define tryReadSlice(expected) \
|
||||
do { \
|
||||
if (!readMBCSlice(ptr, expected)) \
|
||||
if (!readMBCSlice(ptr, expected)) { \
|
||||
return MBC_BAD; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
switch (*ptr++) {
|
||||
@@ -249,8 +258,9 @@ static MbcType parseMBC(char const *name) {
|
||||
case 'r':
|
||||
tryReadSlice("OM");
|
||||
// Handle optional " ONLY"
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
if (*ptr == 'O' || *ptr == 'o') {
|
||||
ptr++;
|
||||
tryReadSlice("NLY");
|
||||
@@ -325,8 +335,9 @@ static MbcType parseMBC(char const *name) {
|
||||
case 'P': {
|
||||
tryReadSlice("P1");
|
||||
// Parse version
|
||||
while (*ptr == ' ' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
// Major
|
||||
char *endptr;
|
||||
unsigned long val = strtoul(ptr, &endptr, 10);
|
||||
@@ -383,27 +394,33 @@ static MbcType parseMBC(char const *name) {
|
||||
|
||||
// Read "additional features"
|
||||
uint8_t features = 0;
|
||||
#define RAM (1 << 7)
|
||||
#define BATTERY (1 << 6)
|
||||
#define TIMER (1 << 5)
|
||||
#define RUMBLE (1 << 4)
|
||||
#define SENSOR (1 << 3)
|
||||
#define MULTIRUMBLE (1 << 2)
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t RAM = 1 << 7;
|
||||
static constexpr uint8_t BATTERY = 1 << 6;
|
||||
static constexpr uint8_t TIMER = 1 << 5;
|
||||
static constexpr uint8_t RUMBLE = 1 << 4;
|
||||
static constexpr uint8_t SENSOR = 1 << 3;
|
||||
static constexpr uint8_t MULTIRUMBLE = 1 << 2;
|
||||
// clang-format on
|
||||
|
||||
for (;;) {
|
||||
// Trim off trailing whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
// If done, start processing "features"
|
||||
if (!*ptr)
|
||||
if (!*ptr) {
|
||||
break;
|
||||
}
|
||||
// We expect a '+' at this point
|
||||
if (*ptr++ != '+')
|
||||
if (*ptr++ != '+') {
|
||||
return MBC_BAD;
|
||||
}
|
||||
// Trim off leading whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
switch (*ptr++) {
|
||||
case 'B': // BATTERY
|
||||
@@ -428,8 +445,9 @@ static MbcType parseMBC(char const *name) {
|
||||
break;
|
||||
case 'A':
|
||||
case 'a':
|
||||
if (*ptr != 'M' && *ptr != 'm')
|
||||
if (*ptr != 'M' && *ptr != 'm') {
|
||||
return MBC_BAD;
|
||||
}
|
||||
ptr++;
|
||||
features |= RAM;
|
||||
break;
|
||||
@@ -458,8 +476,9 @@ static MbcType parseMBC(char const *name) {
|
||||
|
||||
switch (mbc) {
|
||||
case ROM:
|
||||
if (!features)
|
||||
if (!features) {
|
||||
break;
|
||||
}
|
||||
mbc = ROM_RAM - 1;
|
||||
static_assert(ROM_RAM + 1 == ROM_RAM_BATTERY, "Enum sanity check failed!");
|
||||
static_assert(MBC1 + 1 == MBC1_RAM, "Enum sanity check failed!");
|
||||
@@ -469,26 +488,29 @@ static MbcType parseMBC(char const *name) {
|
||||
[[fallthrough]];
|
||||
case MBC1:
|
||||
case MMM01:
|
||||
if (features == RAM)
|
||||
if (features == RAM) {
|
||||
mbc++;
|
||||
else if (features == (RAM | BATTERY))
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC2:
|
||||
if (features == BATTERY)
|
||||
if (features == BATTERY) {
|
||||
mbc = MBC2_BATTERY;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC3:
|
||||
// Handle timer, which also requires battery
|
||||
if (features & TIMER) {
|
||||
if (!(features & BATTERY))
|
||||
if (!(features & BATTERY)) {
|
||||
fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n");
|
||||
}
|
||||
features &= ~(TIMER | BATTERY); // Reset those bits
|
||||
mbc = MBC3_TIMER_BATTERY;
|
||||
// RAM is handled below
|
||||
@@ -498,12 +520,13 @@ static MbcType parseMBC(char const *name) {
|
||||
static_assert(
|
||||
MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!"
|
||||
);
|
||||
if (features == RAM)
|
||||
if (features == RAM) {
|
||||
mbc++;
|
||||
else if (features == (RAM | BATTERY))
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC5:
|
||||
@@ -515,12 +538,13 @@ static MbcType parseMBC(char const *name) {
|
||||
static_assert(MBC5 + 2 == MBC5_RAM_BATTERY, "Enum sanity check failed!");
|
||||
static_assert(MBC5_RUMBLE + 1 == MBC5_RUMBLE_RAM, "Enum sanity check failed!");
|
||||
static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY, "Enum sanity check failed!");
|
||||
if (features == RAM)
|
||||
if (features == RAM) {
|
||||
mbc++;
|
||||
else if (features == (RAM | BATTERY))
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC6:
|
||||
@@ -528,45 +552,56 @@ static MbcType parseMBC(char const *name) {
|
||||
case BANDAI_TAMA5:
|
||||
case HUC3:
|
||||
// No extra features accepted
|
||||
if (features)
|
||||
if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||
if (features != (SENSOR | RUMBLE | RAM | BATTERY))
|
||||
if (features != (SENSOR | RUMBLE | RAM | BATTERY)) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case HUC1_RAM_BATTERY:
|
||||
if (features != (RAM | BATTERY)) // HuC1 expects RAM+BATTERY
|
||||
if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case TPP1:
|
||||
if (features & RAM)
|
||||
if (features & RAM) {
|
||||
fprintf(
|
||||
stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size"
|
||||
);
|
||||
if (features & BATTERY)
|
||||
}
|
||||
if (features & BATTERY) {
|
||||
mbc |= 0x08;
|
||||
if (features & TIMER)
|
||||
}
|
||||
if (features & TIMER) {
|
||||
mbc |= 0x04;
|
||||
if (features & MULTIRUMBLE)
|
||||
}
|
||||
if (features & MULTIRUMBLE) {
|
||||
mbc |= 0x03; // Also set the rumble flag
|
||||
if (features & RUMBLE)
|
||||
}
|
||||
if (features & RUMBLE) {
|
||||
mbc |= 0x01;
|
||||
if (features & SENSOR)
|
||||
}
|
||||
if (features & SENSOR) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Trim off trailing whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t')
|
||||
while (*ptr == ' ' || *ptr == '\t') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
// If there is still something past the whitespace, error out
|
||||
if (*ptr)
|
||||
if (*ptr) {
|
||||
return MBC_BAD;
|
||||
}
|
||||
|
||||
return static_cast<MbcType>(mbc);
|
||||
}
|
||||
@@ -665,10 +700,12 @@ static char const *mbcName(MbcType type) {
|
||||
case MBC_WRONG_FEATURES:
|
||||
case MBC_BAD_RANGE:
|
||||
case MBC_BAD_TPP1:
|
||||
// LCOV_EXCL_START
|
||||
unreachable_();
|
||||
}
|
||||
|
||||
unreachable_();
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
static bool hasRAM(MbcType type) {
|
||||
@@ -682,8 +719,7 @@ static bool hasRAM(MbcType type) {
|
||||
case MBC3_TIMER_BATTERY:
|
||||
case MBC5:
|
||||
case MBC5_RUMBLE:
|
||||
case MBC6: // TODO: not sure
|
||||
case BANDAI_TAMA5: // TODO: not sure
|
||||
case BANDAI_TAMA5: // "Game de Hakken!! Tamagotchi - Osutchi to Mesutchi" has RAM size 0
|
||||
case MBC_NONE:
|
||||
case MBC_BAD:
|
||||
case MBC_WRONG_FEATURES:
|
||||
@@ -704,6 +740,7 @@ static bool hasRAM(MbcType type) {
|
||||
case MBC5_RAM_BATTERY:
|
||||
case MBC5_RUMBLE_RAM:
|
||||
case MBC5_RUMBLE_RAM_BATTERY:
|
||||
case MBC6: // "Net de Get - Minigame @ 100" has RAM size 3 (32 KiB)
|
||||
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||
case POCKET_CAMERA:
|
||||
case HUC3:
|
||||
@@ -730,7 +767,7 @@ static bool hasRAM(MbcType type) {
|
||||
break;
|
||||
}
|
||||
|
||||
unreachable_();
|
||||
unreachable_(); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
static uint8_t const nintendoLogo[] = {
|
||||
@@ -740,12 +777,14 @@ static uint8_t const nintendoLogo[] = {
|
||||
};
|
||||
|
||||
static uint8_t fixSpec = 0;
|
||||
#define FIX_LOGO (1 << 7)
|
||||
#define TRASH_LOGO (1 << 6)
|
||||
#define FIX_HEADER_SUM (1 << 5)
|
||||
#define TRASH_HEADER_SUM (1 << 4)
|
||||
#define FIX_GLOBAL_SUM (1 << 3)
|
||||
#define TRASH_GLOBAL_SUM (1 << 2)
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t FIX_LOGO = 1 << 7;
|
||||
static constexpr uint8_t TRASH_LOGO = 1 << 6;
|
||||
static constexpr uint8_t FIX_HEADER_SUM = 1 << 5;
|
||||
static constexpr uint8_t TRASH_HEADER_SUM = 1 << 4;
|
||||
static constexpr uint8_t FIX_GLOBAL_SUM = 1 << 3;
|
||||
static constexpr uint8_t TRASH_GLOBAL_SUM = 1 << 2;
|
||||
// clang-format on
|
||||
|
||||
static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone
|
||||
static char const *gameID = nullptr;
|
||||
@@ -778,11 +817,14 @@ static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
|
||||
while (len) {
|
||||
ssize_t ret = read(fd, buf, len);
|
||||
|
||||
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted
|
||||
return -1;
|
||||
// Return errors, unless we only were interrupted
|
||||
if (ret == -1 && errno != EINTR) {
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
}
|
||||
// EOF reached
|
||||
if (ret == 0)
|
||||
if (ret == 0) {
|
||||
return total;
|
||||
}
|
||||
// If anything was read, accumulate it, and continue
|
||||
if (ret != -1) {
|
||||
total += ret;
|
||||
@@ -803,43 +845,31 @@ static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
|
||||
while (len) {
|
||||
ssize_t ret = write(fd, buf, len);
|
||||
|
||||
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted
|
||||
return -1;
|
||||
// EOF reached
|
||||
if (ret == 0)
|
||||
return total;
|
||||
// If anything was read, accumulate it, and continue
|
||||
// Return errors, unless we only were interrupted
|
||||
if (ret == -1 && errno != EINTR) {
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
}
|
||||
// If anything was written, accumulate it, and continue
|
||||
if (ret != -1) {
|
||||
total += ret;
|
||||
len -= ret;
|
||||
buf += ret;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param rom0 A pointer to rom0
|
||||
* @param addr What address to check
|
||||
* @param fixedByte The fixed byte at the address
|
||||
* @param areaName Name to be displayed in the warning message
|
||||
*/
|
||||
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName) {
|
||||
uint8_t origByte = rom0[addr];
|
||||
|
||||
if (!overwriteRom && origByte != 0 && origByte != fixedByte)
|
||||
if (!overwriteRom && origByte != 0 && origByte != fixedByte) {
|
||||
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
|
||||
}
|
||||
|
||||
rom0[addr] = fixedByte;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param rom0 A pointer to rom0
|
||||
* @param startAddr What address to begin checking from
|
||||
* @param fixed The fixed bytes at the address
|
||||
* @param size How many bytes to check
|
||||
* @param areaName Name to be displayed in the warning message
|
||||
*/
|
||||
static void overwriteBytes(
|
||||
uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size, char const *areaName
|
||||
) {
|
||||
@@ -857,18 +887,13 @@ static void overwriteBytes(
|
||||
memcpy(&rom0[startAddr], fixed, size);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param input File descriptor to be used for reading
|
||||
* @param output File descriptor to be used for writing, may be equal to `input`
|
||||
* @param name The file's name, to be displayed for error output
|
||||
* @param fileSize The file's size if known, 0 if not.
|
||||
*/
|
||||
static void processFile(int input, int output, char const *name, off_t fileSize) {
|
||||
// Both of these should be true for seekable files, and neither otherwise
|
||||
if (input == output)
|
||||
static void
|
||||
processFile(int input, int output, char const *name, off_t fileSize, bool expectFileSize) {
|
||||
if (expectFileSize) {
|
||||
assume(fileSize != 0);
|
||||
else
|
||||
} else {
|
||||
assume(fileSize == 0);
|
||||
}
|
||||
|
||||
uint8_t rom0[BANK_SIZE];
|
||||
ssize_t rom0Len = readBytes(input, rom0, sizeof(rom0));
|
||||
@@ -876,8 +901,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
ssize_t headerSize = (cartridgeType & 0xFF00) == TPP1 ? 0x154 : 0x150;
|
||||
|
||||
if (rom0Len == -1) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (rom0Len < headerSize) {
|
||||
report(
|
||||
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
||||
@@ -890,25 +917,25 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
}
|
||||
// Accept partial reads if the file contains at least the header
|
||||
|
||||
if (fixSpec & (FIX_LOGO | TRASH_LOGO))
|
||||
if (fixSpec & (FIX_LOGO | TRASH_LOGO)) {
|
||||
overwriteBytes(rom0, 0x0104, logo, sizeof(logo), logoFilename ? "logo" : "Nintendo logo");
|
||||
}
|
||||
|
||||
if (title)
|
||||
if (title) {
|
||||
overwriteBytes(rom0, 0x134, reinterpret_cast<uint8_t const *>(title), titleLen, "title");
|
||||
}
|
||||
|
||||
if (gameID)
|
||||
if (gameID) {
|
||||
overwriteBytes(
|
||||
rom0,
|
||||
0x13F,
|
||||
reinterpret_cast<uint8_t const *>(gameID),
|
||||
gameIDLen,
|
||||
"manufacturer code"
|
||||
rom0, 0x13F, reinterpret_cast<uint8_t const *>(gameID), gameIDLen, "manufacturer code"
|
||||
);
|
||||
}
|
||||
|
||||
if (model != DMG)
|
||||
if (model != DMG) {
|
||||
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
|
||||
}
|
||||
|
||||
if (newLicensee)
|
||||
if (newLicensee) {
|
||||
overwriteBytes(
|
||||
rom0,
|
||||
0x144,
|
||||
@@ -916,9 +943,11 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
newLicenseeLen,
|
||||
"new licensee code"
|
||||
);
|
||||
}
|
||||
|
||||
if (sgb)
|
||||
if (sgb) {
|
||||
overwriteByte(rom0, 0x146, 0x03, "SGB flag");
|
||||
}
|
||||
|
||||
// If a valid MBC was specified...
|
||||
if (cartridgeType < MBC_NONE) {
|
||||
@@ -941,31 +970,36 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
|
||||
overwriteBytes(rom0, 0x150, tpp1Rev, sizeof(tpp1Rev), "TPP1 revision number");
|
||||
|
||||
if (ramSize != UNSPECIFIED)
|
||||
if (ramSize != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x152, ramSize, "RAM size");
|
||||
}
|
||||
|
||||
overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags");
|
||||
} else {
|
||||
// Regular mappers
|
||||
|
||||
if (ramSize != UNSPECIFIED)
|
||||
if (ramSize != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x149, ramSize, "RAM size");
|
||||
}
|
||||
|
||||
if (!japanese)
|
||||
if (!japanese) {
|
||||
overwriteByte(rom0, 0x14A, 0x01, "destination code");
|
||||
}
|
||||
}
|
||||
|
||||
if (oldLicensee != UNSPECIFIED)
|
||||
if (oldLicensee != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
|
||||
else if (sgb && rom0[0x14B] != 0x33)
|
||||
} else if (sgb && rom0[0x14B] != 0x33) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n",
|
||||
rom0[0x14B]
|
||||
);
|
||||
}
|
||||
|
||||
if (romVersion != UNSPECIFIED)
|
||||
if (romVersion != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
|
||||
}
|
||||
|
||||
// Remain to be handled the ROM size, and header checksum.
|
||||
// The latter depends on the former, and so will be handled after it.
|
||||
@@ -1013,13 +1047,15 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
nbBanks++;
|
||||
|
||||
// Update global checksum, too
|
||||
for (uint16_t i = 0; i < bankLen; i++)
|
||||
for (uint16_t i = 0; i < bankLen; i++) {
|
||||
globalSum += romx[totalRomxLen + i];
|
||||
}
|
||||
totalRomxLen += bankLen;
|
||||
}
|
||||
// Stop when an incomplete bank has been read
|
||||
if (bankLen != BANK_SIZE)
|
||||
if (bankLen != BANK_SIZE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1046,8 +1082,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// Alter number of banks to reflect required value
|
||||
// x&(x-1) is zero iff x is a power of 2, or 0; we know for sure it's non-zero,
|
||||
// so this is true (non-zero) when we don't have a power of 2
|
||||
if (nbBanks & (nbBanks - 1))
|
||||
if (nbBanks & (nbBanks - 1)) {
|
||||
nbBanks = 1 << (CHAR_BIT * sizeof(nbBanks) - clz(nbBanks));
|
||||
}
|
||||
// Write final ROM size
|
||||
rom0[0x148] = ctz(nbBanks / 2);
|
||||
// Alter global checksum based on how many bytes will be added (not counting ROM0)
|
||||
@@ -1058,8 +1095,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (fixSpec & (FIX_HEADER_SUM | TRASH_HEADER_SUM)) {
|
||||
uint8_t sum = 0;
|
||||
|
||||
for (uint16_t i = 0x134; i < 0x14D; i++)
|
||||
for (uint16_t i = 0x134; i < 0x14D; i++) {
|
||||
sum -= rom0[i] + 1;
|
||||
}
|
||||
|
||||
overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum, "header checksum");
|
||||
}
|
||||
@@ -1067,28 +1105,32 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
|
||||
// Computation of the global checksum does not include the checksum bytes
|
||||
assume(rom0Len >= 0x14E);
|
||||
for (uint16_t i = 0; i < 0x14E; i++)
|
||||
for (uint16_t i = 0; i < 0x14E; i++) {
|
||||
globalSum += rom0[i];
|
||||
for (uint16_t i = 0x150; i < rom0Len; i++)
|
||||
}
|
||||
for (uint16_t i = 0x150; i < rom0Len; i++) {
|
||||
globalSum += rom0[i];
|
||||
}
|
||||
// Pipes have already read ROMX and updated globalSum, but not regular files
|
||||
if (input == output) {
|
||||
for (;;) {
|
||||
ssize_t bankLen = readBytes(input, bank, sizeof(bank));
|
||||
|
||||
for (uint16_t i = 0; i < bankLen; i++)
|
||||
for (uint16_t i = 0; i < bankLen; i++) {
|
||||
globalSum += bank[i];
|
||||
if (bankLen != sizeof(bank))
|
||||
}
|
||||
if (bankLen != sizeof(bank)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fixSpec & TRASH_GLOBAL_SUM)
|
||||
if (fixSpec & TRASH_GLOBAL_SUM) {
|
||||
globalSum = ~globalSum;
|
||||
}
|
||||
|
||||
uint8_t bytes[2] = {
|
||||
static_cast<uint8_t>(globalSum >> 8),
|
||||
static_cast<uint8_t>(globalSum & 0xFF)
|
||||
static_cast<uint8_t>(globalSum >> 8), static_cast<uint8_t>(globalSum & 0xFF)
|
||||
};
|
||||
|
||||
overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
|
||||
@@ -1100,20 +1142,26 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// write the header
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_SET) == static_cast<off_t>(-1)) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
// If modifying the file in-place, we only need to edit the header
|
||||
// However, padding may have modified ROM0 (added padding), so don't in that case
|
||||
if (padValue == UNSPECIFIED)
|
||||
if (padValue == UNSPECIFIED) {
|
||||
rom0Len = headerSize;
|
||||
}
|
||||
}
|
||||
writeLen = writeBytes(output, rom0, rom0Len);
|
||||
|
||||
if (writeLen == -1) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (writeLen < rom0Len) {
|
||||
// LCOV_EXCL_START
|
||||
report(
|
||||
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
||||
static_cast<intmax_t>(writeLen),
|
||||
@@ -1121,6 +1169,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
static_cast<intmax_t>(rom0Len)
|
||||
);
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Output ROMX if it was buffered
|
||||
@@ -1129,9 +1178,12 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// so it's fine to cast to `size_t`
|
||||
writeLen = writeBytes(output, romx.data(), totalRomxLen);
|
||||
if (writeLen == -1) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (static_cast<size_t>(writeLen) < totalRomxLen) {
|
||||
// LCOV_EXCL_START
|
||||
report(
|
||||
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
||||
static_cast<intmax_t>(writeLen),
|
||||
@@ -1139,6 +1191,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
totalRomxLen
|
||||
);
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1146,8 +1199,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (padValue != UNSPECIFIED) {
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_END) == static_cast<off_t>(-1)) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
memset(bank, padValue, sizeof(bank));
|
||||
@@ -1161,40 +1216,73 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// The return value is either -1, or at most `thisLen`,
|
||||
// so it's fine to cast to `size_t`
|
||||
if (static_cast<size_t>(ret) != thisLen) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
len -= thisLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool processFilename(char const *name) {
|
||||
static bool processFilename(char const *name, char const *outputName) {
|
||||
nbErrors = 0;
|
||||
|
||||
if (!strcmp(name, "-")) {
|
||||
bool inputStdin = !strcmp(name, "-");
|
||||
if (inputStdin && !outputName) {
|
||||
outputName = "-";
|
||||
}
|
||||
|
||||
int output = -1;
|
||||
bool openedOutput = false;
|
||||
if (outputName) {
|
||||
if (!strcmp(outputName, "-")) {
|
||||
output = STDOUT_FILENO;
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
} else {
|
||||
output = open(outputName, O_WRONLY | O_BINARY | O_CREAT, 0600);
|
||||
if (output == -1) {
|
||||
report(
|
||||
"FATAL: Failed to open \"%s\" for writing: %s\n", outputName, strerror(errno)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
openedOutput = true;
|
||||
}
|
||||
}
|
||||
Defer closeOutput{[&] {
|
||||
if (openedOutput) {
|
||||
close(output);
|
||||
}
|
||||
}};
|
||||
|
||||
if (inputStdin) {
|
||||
name = "<stdin>";
|
||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
|
||||
processFile(STDIN_FILENO, output, name, 0, false);
|
||||
} else {
|
||||
// POSIX specifies that the results of O_RDWR on a FIFO are undefined.
|
||||
// However, this is necessary to avoid a TOCTTOU, if the file was changed between
|
||||
// `stat()` and `open(O_RDWR)`, which could trigger the UB anyway.
|
||||
// Thus, we're going to hope that either the `open` fails, or it succeeds but IO
|
||||
// operations may fail, all of which we handle.
|
||||
if (int input = open(name, O_RDWR | O_BINARY); input == -1) {
|
||||
if (int input = open(name, (outputName ? O_RDONLY : O_RDWR) | O_BINARY); input == -1) {
|
||||
report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", name, strerror(errno));
|
||||
} else {
|
||||
Defer closeInput{[&] { close(input); }};
|
||||
struct stat stat;
|
||||
if (fstat(input, &stat) == -1) {
|
||||
// LCOV_EXCL_START
|
||||
report("FATAL: Failed to stat \"%s\": %s\n", name, strerror(errno));
|
||||
} else if (!S_ISREG(stat.st_mode)) { // TODO: Do we want to support other types?
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (!S_ISREG(stat.st_mode)) { // We do not support FIFOs or symlinks
|
||||
// LCOV_EXCL_START
|
||||
report(
|
||||
"FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n",
|
||||
name
|
||||
);
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (stat.st_size < 0x150) {
|
||||
// This check is in theory redundant with the one in `processFile`, but it
|
||||
// prevents passing a file size of 0, which usually indicates pipes
|
||||
@@ -1204,12 +1292,15 @@ static bool processFilename(char const *name) {
|
||||
static_cast<intmax_t>(stat.st_size)
|
||||
);
|
||||
} else {
|
||||
processFile(input, input, name, stat.st_size);
|
||||
if (!outputName) {
|
||||
output = input;
|
||||
}
|
||||
processFile(input, output, name, stat.st_size, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nbErrors)
|
||||
if (nbErrors) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Fixing \"%s\" failed with %u error%s\n",
|
||||
@@ -1217,6 +1308,7 @@ static bool processFilename(char const *name) {
|
||||
nbErrors,
|
||||
nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
return nbErrors;
|
||||
}
|
||||
|
||||
@@ -1247,6 +1339,7 @@ static void parseByte(uint16_t &output, char name) {
|
||||
int main(int argc, char *argv[]) {
|
||||
nbErrors = 0;
|
||||
|
||||
char const *outputFilename = nullptr;
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
switch (ch) {
|
||||
size_t len;
|
||||
@@ -1266,16 +1359,17 @@ int main(int argc, char *argv[]) {
|
||||
switch (*musl_optarg) {
|
||||
#define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \
|
||||
case STR(cur)[0]: \
|
||||
if (fixSpec & badFlag) \
|
||||
if (fixSpec & badFlag) { \
|
||||
fprintf(stderr, "warning: '" STR(cur) "' overriding '" STR(bad) "' in fix spec\n"); \
|
||||
} \
|
||||
fixSpec = (fixSpec & ~badFlag) | curFlag; \
|
||||
break
|
||||
#define overrideSpecs(fix, fixFlag, trash, trashFlag) \
|
||||
OVERRIDE_SPEC(fix, trash, fixFlag, trashFlag); \
|
||||
OVERRIDE_SPEC(trash, fix, trashFlag, fixFlag)
|
||||
overrideSpecs(l, FIX_LOGO, L, TRASH_LOGO);
|
||||
overrideSpecs(h, FIX_HEADER_SUM, H, TRASH_HEADER_SUM);
|
||||
overrideSpecs(g, FIX_GLOBAL_SUM, G, TRASH_GLOBAL_SUM);
|
||||
overrideSpecs(l, FIX_LOGO, L, TRASH_LOGO);
|
||||
overrideSpecs(h, FIX_HEADER_SUM, H, TRASH_HEADER_SUM);
|
||||
overrideSpecs(g, FIX_GLOBAL_SUM, G, TRASH_GLOBAL_SUM);
|
||||
#undef OVERRIDE_SPEC
|
||||
#undef overrideSpecs
|
||||
|
||||
@@ -1286,6 +1380,12 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'i':
|
||||
gameID = musl_optarg;
|
||||
len = strlen(gameID);
|
||||
@@ -1354,6 +1454,10 @@ int main(int argc, char *argv[]) {
|
||||
overwriteRom = true;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
outputFilename = musl_optarg;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
parseByte(padValue, 'p');
|
||||
break;
|
||||
@@ -1380,34 +1484,40 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
case 'V':
|
||||
// LCOV_EXCL_START
|
||||
printf("rgbfix %s\n", get_package_version_string());
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'v':
|
||||
fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
|
||||
break;
|
||||
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(1);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
if ((cartridgeType & 0xFF00) == TPP1 && !japanese)
|
||||
if ((cartridgeType & 0xFF00) == TPP1 && !japanese) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"
|
||||
);
|
||||
}
|
||||
|
||||
// Check that RAM size is correct for "standard" mappers
|
||||
if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) {
|
||||
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
|
||||
if (ramSize != 1)
|
||||
if (ramSize != 1) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n",
|
||||
mbcName(cartridgeType)
|
||||
);
|
||||
}
|
||||
} else if (hasRAM(cartridgeType)) {
|
||||
if (!ramSize) {
|
||||
fprintf(
|
||||
@@ -1421,7 +1531,7 @@ int main(int argc, char *argv[]) {
|
||||
"warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n",
|
||||
mbcName(cartridgeType)
|
||||
);
|
||||
} // TODO: check possible values?
|
||||
}
|
||||
} else if (ramSize) {
|
||||
fprintf(
|
||||
stderr,
|
||||
@@ -1432,12 +1542,13 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33)
|
||||
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n",
|
||||
oldLicensee
|
||||
);
|
||||
}
|
||||
|
||||
argv += musl_optind;
|
||||
bool failed = nbErrors;
|
||||
@@ -1488,8 +1599,9 @@ int main(int argc, char *argv[]) {
|
||||
memcpy(logo, nintendoLogo, sizeof(nintendoLogo));
|
||||
}
|
||||
if (fixSpec & TRASH_LOGO) {
|
||||
for (uint16_t i = 0; i < sizeof(logo); i++)
|
||||
for (uint16_t i = 0; i < sizeof(logo); i++) {
|
||||
logo[i] = 0xFF ^ logo[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!*argv) {
|
||||
@@ -1500,8 +1612,14 @@ int main(int argc, char *argv[]) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (outputFilename && argc != musl_optind + 1) {
|
||||
fputs("FATAL: If `-o` is set then only a single input file may be specified\n", stderr);
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
do {
|
||||
failed |= processFilename(*argv);
|
||||
failed |= processFilename(*argv, outputFilename);
|
||||
} while (*++argv);
|
||||
|
||||
return failed;
|
||||
|
||||
175
src/gfx/main.cpp
175
src/gfx/main.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "gfx/main.hpp"
|
||||
|
||||
@@ -12,12 +12,10 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "extern/getopt.hpp"
|
||||
#include "file.hpp"
|
||||
#include "helpers.hpp" // assume
|
||||
#include "platform.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
@@ -41,7 +39,8 @@ static struct LocalOptions {
|
||||
|
||||
static uintmax_t nbErrors;
|
||||
|
||||
[[noreturn]] void giveUp() {
|
||||
[[noreturn]]
|
||||
void giveUp() {
|
||||
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
||||
exit(1);
|
||||
}
|
||||
@@ -71,18 +70,21 @@ void error(char const *fmt, ...) {
|
||||
va_end(ap);
|
||||
putc('\n', stderr);
|
||||
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||
nbErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
void errorMessage(char const *msg) {
|
||||
fprintf(stderr, "error: %s\n", msg);
|
||||
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||
nbErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void fatal(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void fatal(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
fputs("FATAL: ", stderr);
|
||||
@@ -91,13 +93,15 @@ void errorMessage(char const *msg) {
|
||||
va_end(ap);
|
||||
putc('\n', stderr);
|
||||
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||
nbErrors++;
|
||||
}
|
||||
|
||||
giveUp();
|
||||
}
|
||||
|
||||
void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
||||
// LCOV_EXCL_START
|
||||
if (verbosity >= level) {
|
||||
va_list ap;
|
||||
|
||||
@@ -105,28 +109,28 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "-Aa:b:Cc:d:i:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ";
|
||||
static char const *optstring = "-Aa:B:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ";
|
||||
|
||||
/*
|
||||
* Equivalent long options
|
||||
* Please keep in the same order as short opts
|
||||
*
|
||||
* Also, make sure long opts don't create ambiguity:
|
||||
* A long opt's name should start with the same letter as its short opt,
|
||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
* This is because long opt matching, even to a single char, is prioritized
|
||||
* over short opt matching
|
||||
*/
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||
{"attr-map", required_argument, nullptr, 'a'},
|
||||
{"background-color", required_argument, nullptr, 'B'},
|
||||
{"base-tiles", required_argument, nullptr, 'b'},
|
||||
{"color-curve", no_argument, nullptr, 'C'},
|
||||
{"colors", required_argument, nullptr, 'c'},
|
||||
{"depth", required_argument, nullptr, 'd'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"input-tileset", required_argument, nullptr, 'i'},
|
||||
{"slice", required_argument, nullptr, 'L'},
|
||||
{"mirror-tiles", no_argument, nullptr, 'm'},
|
||||
@@ -153,9 +157,10 @@ static option const longopts[] = {
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
// LCOV_EXCL_START
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbgfx [-r stride] [-CmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
"Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-i <tileset_file>]\n"
|
||||
" [-L <slice>] [-N <nb_tiles>] [-n <nb_pals>] [-o <out_file>]\n"
|
||||
" [-p <pal_file> | -P] [-q <pal_map> | -Q] [-s <nb_colors>]\n"
|
||||
@@ -171,11 +176,10 @@ static void printUsage() {
|
||||
stderr
|
||||
);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
/*
|
||||
* Parses a number at the beginning of a string, moving the pointer to skip the parsed characters
|
||||
* Returns the provided errVal on error
|
||||
*/
|
||||
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
|
||||
// Returns the provided errVal on error.
|
||||
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
|
||||
uint8_t base = 10;
|
||||
if (*string == '\0') {
|
||||
@@ -198,12 +202,10 @@ static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVa
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Turns a digit into its numeric value in the current base, if it has one.
|
||||
* Maximum is inclusive. The string_view is modified to "consume" all digits.
|
||||
* Returns 255 on parse failure (including wrong char for base), in which case
|
||||
* the string_view may be pointing on garbage.
|
||||
*/
|
||||
// Turns a digit into its numeric value in the current base, if it has one.
|
||||
// Maximum is inclusive. The string_view is modified to "consume" all digits.
|
||||
// Returns 255 on parse failure (including wrong char for base), in which case
|
||||
// the string_view may be pointing on garbage.
|
||||
auto charIndex = [&base](unsigned char c) -> uint8_t {
|
||||
unsigned char index = c - '0'; // Use wrapping semantics
|
||||
if (base == 2 && index >= 2) {
|
||||
@@ -271,10 +273,7 @@ static void registerInput(char const *arg) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Turn an "at-file"'s contents into an argv that `getopt` can handle
|
||||
* @param argPool Argument characters will be appended to this vector, for storage purposes.
|
||||
*/
|
||||
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
|
||||
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
||||
File file;
|
||||
if (!file.open(path, std::ios_base::in)) {
|
||||
@@ -283,8 +282,8 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
||||
|
||||
// We only filter out `EOF`, but calling `isblank()` on anything else is UB!
|
||||
static_assert(
|
||||
std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
|
||||
"isblank(char_traits<...>::eof()) is UB!"
|
||||
std::streambuf::traits_type::eof() == EOF,
|
||||
"isblank(std::streambuf::traits_type::eof()) is UB!"
|
||||
);
|
||||
std::vector<size_t> argvOfs;
|
||||
|
||||
@@ -346,13 +345,10 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses an arg vector, modifying `options` and `localOptions` as options are read.
|
||||
* The `localOptions` struct is for flags which must be processed after the option parsing finishes.
|
||||
*
|
||||
* Returns `nullptr` if the vector was fully parsed, or a pointer (which is part of the arg vector)
|
||||
* to an "at-file" path if one is encountered.
|
||||
*/
|
||||
// Parses an arg vector, modifying `options` and `localOptions` as options are read.
|
||||
// The `localOptions` struct is for flags which must be processed after the option parsing finishes.
|
||||
// Returns `nullptr` if the vector was fully parsed, or a pointer (which is part of the arg vector)
|
||||
// to an "at-file" path if one is encountered.
|
||||
static char *parseArgv(int argc, char *argv[]) {
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
char *arg = musl_optarg; // Make a copy for scanning
|
||||
@@ -363,10 +359,14 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 'a':
|
||||
localOptions.autoAttrmap = false;
|
||||
if (!options.attrmap.empty())
|
||||
if (!options.attrmap.empty()) {
|
||||
warning("Overriding attrmap file %s", options.attrmap.c_str());
|
||||
}
|
||||
options.attrmap = musl_optarg;
|
||||
break;
|
||||
case 'B':
|
||||
parseBackgroundPalSpec(musl_optarg);
|
||||
break;
|
||||
case 'b':
|
||||
number = parseNumber(arg, "Bank 0 base tile ID", 0);
|
||||
if (number >= 256) {
|
||||
@@ -406,18 +406,18 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
options.useColorCurve = true;
|
||||
break;
|
||||
case 'c':
|
||||
localOptions.externalPalSpec = nullptr; // Allow overriding a previous pal spec
|
||||
if (musl_optarg[0] == '#') {
|
||||
options.palSpecType = Options::EXPLICIT;
|
||||
parseInlinePalSpec(musl_optarg);
|
||||
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
|
||||
// Use PLTE, error out if missing
|
||||
options.palSpecType = Options::EMBEDDED;
|
||||
} else if (strncasecmp(musl_optarg, "dmg=", literal_strlen("dmg=")) == 0) {
|
||||
options.palSpecType = Options::DMG;
|
||||
parseDmgPalSpec(&musl_optarg[literal_strlen("dmg=")]);
|
||||
} else {
|
||||
options.palSpecType = Options::EXPLICIT;
|
||||
// Can't parse the file yet, as "flat" color collections need to know the palette
|
||||
// size to be split; thus, we defer that
|
||||
// TODO: this does not validate the `fmt` part of any external spec but the last
|
||||
// one, but I guess that's okay
|
||||
localOptions.externalPalSpec = musl_optarg;
|
||||
}
|
||||
break;
|
||||
@@ -430,9 +430,15 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
options.bitDepth = 2;
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
case 'i':
|
||||
if (!options.inputTileset.empty())
|
||||
if (!options.inputTileset.empty()) {
|
||||
warning("Overriding input tileset file %s", options.inputTileset.c_str());
|
||||
}
|
||||
options.inputTileset = musl_optarg;
|
||||
break;
|
||||
case 'L':
|
||||
@@ -530,8 +536,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
localOptions.groupOutputs = true;
|
||||
break;
|
||||
case 'o':
|
||||
if (!options.output.empty())
|
||||
if (!options.output.empty()) {
|
||||
warning("Overriding tile data file %s", options.output.c_str());
|
||||
}
|
||||
options.output = musl_optarg;
|
||||
break;
|
||||
case 'P':
|
||||
@@ -539,8 +546,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 'p':
|
||||
localOptions.autoPalettes = false;
|
||||
if (!options.palettes.empty())
|
||||
if (!options.palettes.empty()) {
|
||||
warning("Overriding palettes file %s", options.palettes.c_str());
|
||||
}
|
||||
options.palettes = musl_optarg;
|
||||
break;
|
||||
case 'Q':
|
||||
@@ -548,8 +556,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 'q':
|
||||
localOptions.autoPalmap = false;
|
||||
if (!options.palmap.empty())
|
||||
if (!options.palmap.empty()) {
|
||||
warning("Overriding palette map file %s", options.palmap.c_str());
|
||||
}
|
||||
options.palmap = musl_optarg;
|
||||
break;
|
||||
case 'r':
|
||||
@@ -575,18 +584,23 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 't':
|
||||
localOptions.autoTilemap = false;
|
||||
if (!options.tilemap.empty())
|
||||
if (!options.tilemap.empty()) {
|
||||
warning("Overriding tilemap file %s", options.tilemap.c_str());
|
||||
}
|
||||
options.tilemap = musl_optarg;
|
||||
break;
|
||||
case 'V':
|
||||
// LCOV_EXCL_START
|
||||
printf("rgbgfx %s\n", get_package_version_string());
|
||||
exit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
case 'v':
|
||||
// LCOV_EXCL_START
|
||||
if (options.verbosity < Options::VERB_VVVVVV) {
|
||||
++options.verbosity;
|
||||
}
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
case 'x':
|
||||
options.trim = parseNumber(arg, "Number of tiles to trim", 0);
|
||||
if (*arg != '\0') {
|
||||
@@ -613,8 +627,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
printUsage();
|
||||
exit(1);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,15 +758,37 @@ int main(int argc, char *argv[]) {
|
||||
parseExternalPalSpec(localOptions.externalPalSpec);
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
if (options.verbosity >= Options::VERB_CFG) {
|
||||
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
|
||||
|
||||
if (options.verbosity >= Options::VERB_VVVVVV) {
|
||||
putc('\n', stderr);
|
||||
// clang-format off: vertically align values
|
||||
static std::array<uint16_t, 21> gfx{
|
||||
0x1FE, 0x3FF, 0x399, 0x399, 0x3FF, 0x3FF, 0x381, 0x3C3, 0x1FE, 0x078, 0x1FE,
|
||||
0x3FF, 0x3FF, 0x3FF, 0x37B, 0x37B, 0x0FC, 0x0CC, 0x1CE, 0x1CE, 0x1CE,
|
||||
0b0111111110,
|
||||
0b1111111111,
|
||||
0b1110011001,
|
||||
0b1110011001,
|
||||
0b1111111111,
|
||||
0b1111111111,
|
||||
0b1110000001,
|
||||
0b1111000011,
|
||||
0b0111111110,
|
||||
0b0001111000,
|
||||
0b0111111110,
|
||||
0b1111111111,
|
||||
0b1111111111,
|
||||
0b1111111111,
|
||||
0b1101111011,
|
||||
0b1101111011,
|
||||
0b0011111100,
|
||||
0b0011001100,
|
||||
0b0111001110,
|
||||
0b0111001110,
|
||||
0b0111001110,
|
||||
};
|
||||
// clang-format on
|
||||
static std::array<char const *, 3> textbox{
|
||||
" ,----------------------------------------.",
|
||||
" | Augh, dimensional interference again?! |",
|
||||
@@ -774,19 +812,25 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
fputs("Options:\n", stderr);
|
||||
if (options.columnMajor)
|
||||
if (options.columnMajor) {
|
||||
fputs("\tVisit image in column-major order\n", stderr);
|
||||
if (options.allowDedup)
|
||||
}
|
||||
if (options.allowDedup) {
|
||||
fputs("\tAllow deduplicating tiles\n", stderr);
|
||||
if (options.allowMirroringX)
|
||||
}
|
||||
if (options.allowMirroringX) {
|
||||
fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr);
|
||||
if (options.allowMirroringY)
|
||||
}
|
||||
if (options.allowMirroringY) {
|
||||
fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr);
|
||||
if (options.useColorCurve)
|
||||
}
|
||||
if (options.useColorCurve) {
|
||||
fputs("\tUse color curve\n", stderr);
|
||||
}
|
||||
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
|
||||
if (options.trim != 0)
|
||||
if (options.trim != 0) {
|
||||
fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
|
||||
}
|
||||
fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes);
|
||||
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
||||
fprintf(stderr, "\t%s palette spec\n", [] {
|
||||
@@ -797,6 +841,8 @@ int main(int argc, char *argv[]) {
|
||||
return "Explicit";
|
||||
case Options::EMBEDDED:
|
||||
return "Embedded";
|
||||
case Options::DMG:
|
||||
return "DMG";
|
||||
}
|
||||
return "???";
|
||||
}());
|
||||
@@ -817,7 +863,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
fprintf(
|
||||
stderr,
|
||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32 ", %" PRIi32
|
||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRId32 ", %" PRId32
|
||||
")\n",
|
||||
options.inputSlice.width,
|
||||
options.inputSlice.height,
|
||||
@@ -848,6 +894,7 @@ int main(int argc, char *argv[]) {
|
||||
printPath("Output palettes", options.palettes);
|
||||
fputs("Ready.\n", stderr);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
// Do not do anything if option parsing went wrong.
|
||||
requireZeroErrors();
|
||||
@@ -883,9 +930,7 @@ void Palette::addColor(uint16_t color) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the ID of the color in the palette, or `size()` if the color is not in
|
||||
*/
|
||||
// Returns the ID of the color in the palette, or `size()` if the color is not in
|
||||
uint8_t Palette::indexOf(uint16_t color) const {
|
||||
return color == Rgba::transparent
|
||||
? 0
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user