mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 10:42:07 +00:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
BinPackArguments: false
|
||||||
BinPackParameters: false
|
BinPackParameters: false
|
||||||
BitFieldColonSpacing: Both
|
BitFieldColonSpacing: Both
|
||||||
|
BreakAfterAttributes: Always
|
||||||
BreakBeforeBinaryOperators: NonAssignment
|
BreakBeforeBinaryOperators: NonAssignment
|
||||||
BreakBeforeBraces: Attach
|
BreakBeforeBraces: Attach
|
||||||
BreakBeforeConceptDeclarations: true
|
BreakBeforeConceptDeclarations: true
|
||||||
@@ -54,12 +55,14 @@ IncludeCategories:
|
|||||||
IndentAccessModifiers: false
|
IndentAccessModifiers: false
|
||||||
IndentCaseBlocks: false
|
IndentCaseBlocks: false
|
||||||
IndentCaseLabels: false
|
IndentCaseLabels: false
|
||||||
IndentExternBlock: NoIndent
|
IndentExternBlock: Indent
|
||||||
IndentGotoLabels: false
|
IndentGotoLabels: false
|
||||||
IndentPPDirectives: BeforeHash
|
IndentPPDirectives: BeforeHash
|
||||||
IndentRequires: true
|
IndentRequires: true
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
IndentWrappedFunctionNames: true
|
IndentWrappedFunctionNames: true
|
||||||
|
InsertBraces: true
|
||||||
|
InsertNewlineAtEOF: true
|
||||||
# Only support for Javascript as of clang-format 14...
|
# Only support for Javascript as of clang-format 14...
|
||||||
# InsertTrailingCommas: Wrapped
|
# InsertTrailingCommas: Wrapped
|
||||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
@@ -71,6 +74,8 @@ PPIndentWidth: -1
|
|||||||
PointerAlignment: Right
|
PointerAlignment: Right
|
||||||
QualifierAlignment: Right
|
QualifierAlignment: Right
|
||||||
ReflowComments: true
|
ReflowComments: true
|
||||||
|
RemoveParentheses: ReturnStatement
|
||||||
|
RemoveSemicolon: true
|
||||||
SortIncludes: CaseSensitive
|
SortIncludes: CaseSensitive
|
||||||
SortUsingDeclarations: true
|
SortUsingDeclarations: true
|
||||||
SpaceAfterCStyleCast: false
|
SpaceAfterCStyleCast: false
|
||||||
|
|||||||
4
.github/scripts/build_libpng.sh
vendored
4
.github/scripts/build_libpng.sh
vendored
@@ -1,13 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
pngver=1.6.43
|
pngver=1.6.45
|
||||||
|
|
||||||
## Grab sources and check them
|
## Grab sources and check them
|
||||||
|
|
||||||
curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download"
|
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.
|
# 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
|
sha2 -256 libpng-$pngver.tar.xz
|
||||||
echo Checksum mismatch! Aborting. >&2
|
echo Checksum mismatch! Aborting. >&2
|
||||||
exit 1
|
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://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
|
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 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
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
pngver=1.6.43
|
pngver=1.6.45
|
||||||
arch="$1"
|
arch="$1"
|
||||||
|
|
||||||
## Grab sources and check them
|
## Grab sources and check them
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
d6bd2a3f43f17020918a4c1bd81c1a78111b6f759af9c1d3c754f704a1bf0429 libpng-1.6.43-apng.patch.gz
|
f7caa3b55f003ce23d6a087b1c2a643262647bfd1a1b31afdc9b18eabf1bbc7e libpng-1.6.45-apng.patch.gz
|
||||||
6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c libpng-1.6.43.tar.xz
|
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=
|
||||||
2
.github/workflows/checkdiff.yml
vendored
2
.github/workflows/checkdiff.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
cd rgbds
|
cd rgbds
|
||||||
git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}"
|
git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}"
|
||||||
git fetch upstream
|
git fetch upstream
|
||||||
- name: Checkdiff
|
- name: Check diff
|
||||||
working-directory: rgbds
|
working-directory: rgbds
|
||||||
run: |
|
run: |
|
||||||
make checkdiff "BASE_REF=${{ github.event.pull_request.base.sha }}" Q= | tee log
|
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
|
||||||
@@ -100,7 +100,7 @@ jobs:
|
|||||||
path: rgbds-${{ env.version }}-macos.zip
|
path: rgbds-${{ env.version }}-macos.zip
|
||||||
|
|
||||||
linux:
|
linux:
|
||||||
runs-on: ubuntu-20.04 # Oldest supported, for best glibc compatibility.
|
runs-on: ubuntu-22.04 # Oldest supported, for best glibc compatibility.
|
||||||
steps:
|
steps:
|
||||||
- name: Get version from tag
|
- name: Get version from tag
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -112,7 +112,7 @@ jobs:
|
|||||||
- name: Install deps
|
- name: Install deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
./.github/scripts/install_deps.sh ubuntu-20.04
|
./.github/scripts/install_deps.sh ubuntu-22.04
|
||||||
- name: Build binaries
|
- name: Build binaries
|
||||||
run: |
|
run: |
|
||||||
make -kj WARNFLAGS="-Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q=
|
make -kj WARNFLAGS="-Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q=
|
||||||
|
|||||||
68
.github/workflows/testing.yml
vendored
68
.github/workflows/testing.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
unix:
|
unix:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04, ubuntu-22.04, macos-14]
|
os: [ubuntu-22.04, macos-14]
|
||||||
cxx: [g++, clang++]
|
cxx: [g++, clang++]
|
||||||
buildsys: [make, cmake]
|
buildsys: [make, cmake]
|
||||||
exclude:
|
exclude:
|
||||||
@@ -320,3 +320,69 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
test/run-tests.sh
|
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/
|
CMakeFiles/
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
build/
|
build/
|
||||||
|
*.dSYM/
|
||||||
callgrind.out.*
|
callgrind.out.*
|
||||||
|
|||||||
@@ -45,25 +45,21 @@ else()
|
|||||||
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
|
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
|
||||||
endif()
|
endif()
|
||||||
if(SANITIZERS)
|
if(SANITIZERS)
|
||||||
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
|
set(SAN_FLAGS -fsanitize=address -fsanitize=undefined
|
||||||
-fsanitize=unreachable -fsanitize=vla-bound
|
-fsanitize=float-divide-by-zero)
|
||||||
-fsanitize=signed-integer-overflow -fsanitize=bounds
|
|
||||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
|
|
||||||
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
|
||||||
add_compile_options(${SAN_FLAGS})
|
add_compile_options(${SAN_FLAGS})
|
||||||
add_link_options(${SAN_FLAGS})
|
add_link_options(${SAN_FLAGS})
|
||||||
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
||||||
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
# 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 ${CMAKE_CXX_FLAGS_DEBUG}"
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
CACHE STRING "" FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(MORE_WARNINGS)
|
if(MORE_WARNINGS)
|
||||||
add_compile_options(-Werror -Wextra
|
add_compile_options(-Werror -Wextra
|
||||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
|
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
|
||||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
-Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow
|
||||||
-Wshadow # TODO: -Wshadow=compatible-local?
|
|
||||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||||
-Wno-format-nonliteral -Wno-strict-overflow
|
-Wno-format-nonliteral -Wno-strict-overflow
|
||||||
-Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused
|
-Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused
|
||||||
@@ -78,7 +74,7 @@ endif()
|
|||||||
|
|
||||||
find_program(GIT git)
|
find_program(GIT git)
|
||||||
if(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}
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
|
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
ERROR_QUIET)
|
ERROR_QUIET)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
FROM debian:11-slim
|
FROM debian:12-slim
|
||||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||||
ARG version=0.9.0
|
ARG version=0.9.2
|
||||||
WORKDIR /rgbds
|
WORKDIR /rgbds
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
@@ -8,7 +8,7 @@ COPY . .
|
|||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install sudo make cmake gcc build-essential -y
|
apt-get install sudo make cmake gcc build-essential -y
|
||||||
|
|
||||||
RUN ./.github/scripts/install_deps.sh ubuntu-20.04
|
RUN ./.github/scripts/install_deps.sh ubuntu-22.04
|
||||||
RUN make -j CXXFLAGS="-O3 -flto -DNDEBUG -static" PKG_CONFIG="pkg-config --static" Q=
|
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
|
RUN tar caf rgbds-${version}-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh
|
||||||
|
|||||||
54
Makefile
54
Makefile
@@ -7,43 +7,43 @@
|
|||||||
|
|
||||||
# User-defined variables
|
# User-defined variables
|
||||||
|
|
||||||
Q := @
|
Q := @
|
||||||
PREFIX := /usr/local
|
PREFIX := /usr/local
|
||||||
bindir := ${PREFIX}/bin
|
bindir := ${PREFIX}/bin
|
||||||
mandir := ${PREFIX}/share/man
|
mandir := ${PREFIX}/share/man
|
||||||
SUFFIX :=
|
SUFFIX :=
|
||||||
STRIP := -s
|
STRIP := -s
|
||||||
BINMODE := 755
|
BINMODE := 755
|
||||||
MANMODE := 644
|
MANMODE := 644
|
||||||
|
|
||||||
# Other variables
|
# Other variables
|
||||||
|
|
||||||
PKG_CONFIG := pkg-config
|
PKG_CONFIG := pkg-config
|
||||||
PNGCFLAGS := `${PKG_CONFIG} --cflags libpng`
|
PNGCFLAGS := `${PKG_CONFIG} --cflags libpng`
|
||||||
PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
|
PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
|
||||||
PNGLDLIBS := `${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
|
# 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
|
# Overridable CXXFLAGS
|
||||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||||
# Non-overridable CXXFLAGS
|
# 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
|
# Overridable LDFLAGS
|
||||||
LDFLAGS ?=
|
LDFLAGS ?=
|
||||||
# Non-overridable 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
|
# 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
|
# Used for checking pull requests
|
||||||
BASE_REF := origin/master
|
BASE_REF := origin/master
|
||||||
|
|
||||||
# Rules to build the RGBDS binaries
|
# Rules to build the RGBDS binaries
|
||||||
|
|
||||||
@@ -202,16 +202,12 @@ develop:
|
|||||||
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
||||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 \
|
-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 \
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
||||||
-D_GLIBCXX_ASSERTIONS \
|
-D_GLIBCXX_ASSERTIONS -fsanitize=address -fsanitize=undefined \
|
||||||
-fsanitize=shift -fsanitize=integer-divide-by-zero \
|
-fsanitize=float-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" \
|
|
||||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
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.
|
# This target is used during development in order to more easily debug with gdb.
|
||||||
@@ -269,4 +265,4 @@ wine-shim:
|
|||||||
|
|
||||||
dist:
|
dist:
|
||||||
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
|
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
|
||||||
| tar -czf rgbds-`git describe --tags | cut -c 2-`.tar.gz -C .. -T -
|
| tar -czf rgbds-`git -c safe.directory='*' describe --tags | cut -c 2-`.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).
|
this version as [rgbds-linux](https://github.com/vegard/rgbds-linux).
|
||||||
- 2010-01-12: Anthony J. Bentley [forks](https://github.com/bentley) Nossum's
|
- 2010-01-12: Anthony J. Bentley [forks](https://github.com/bentley) Nossum's
|
||||||
repository. The fork becomes the reference implementation of RGBDS.
|
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),
|
- 2015-01-18: stag019 begins implementing [RGBGFX](https://github.com/stag019/rgbgfx),
|
||||||
a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
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.
|
into Bentley's repository.
|
||||||
- 2017-02-23: Bentley's repository is moved to the [rednex](https://github.com/rednex)
|
- 2017-02-23: Bentley's repository is moved to the [rednex](https://github.com/rednex)
|
||||||
organization.
|
organization.
|
||||||
|
|||||||
17
RELEASE.md
17
RELEASE.md
@@ -72,9 +72,14 @@ GitHub.
|
|||||||
|
|
||||||
8. Update the following related projects.
|
8. Update the following related projects.
|
||||||
|
|
||||||
- [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
1. [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
||||||
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
||||||
If the object file revision has been updated, rgbobj will need a corresponding release.
|
to list the new release.
|
||||||
- [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
2. [rgbds-live](https://github.com/gbdev/rgbds-live): update the `rgbds` submodule (and
|
||||||
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
[patches/rgbds.patch](https://github.com/gbdev/rgbds-live/blob/master/patches/rgbds.patch)
|
||||||
to list the new release.
|
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`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
|
[h]="help:normal"
|
||||||
[E]="export-all:normal"
|
[E]="export-all:normal"
|
||||||
[v]="verbose:normal"
|
[v]="verbose:normal"
|
||||||
[w]=":normal"
|
[w]=":normal"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ _rgbfix_completions() {
|
|||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
|
[h]="help:normal"
|
||||||
[j]="non-japanese:normal"
|
[j]="non-japanese:normal"
|
||||||
[s]="sgb-compatible:normal"
|
[s]="sgb-compatible:normal"
|
||||||
[v]="validate:normal"
|
[v]="validate:normal"
|
||||||
@@ -20,6 +21,7 @@ _rgbfix_completions() {
|
|||||||
[l]="old-licensee:unk"
|
[l]="old-licensee:unk"
|
||||||
[m]="mbc-type:mbc"
|
[m]="mbc-type:mbc"
|
||||||
[n]="rom-version:unk"
|
[n]="rom-version:unk"
|
||||||
|
[o]="output:glob-*.gb *.gbc *.sgb"
|
||||||
[p]="pad-value:unk"
|
[p]="pad-value:unk"
|
||||||
[r]="ram-size:unk"
|
[r]="ram-size:unk"
|
||||||
[t]="title:unk"
|
[t]="title:unk"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ _rgbgfx_completions() {
|
|||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
|
[h]="help:normal"
|
||||||
[C]="color-curve:normal"
|
[C]="color-curve:normal"
|
||||||
[m]="mirror-tiles:normal"
|
[m]="mirror-tiles:normal"
|
||||||
[O]="group-outputs:normal"
|
[O]="group-outputs:normal"
|
||||||
@@ -18,6 +19,7 @@ _rgbgfx_completions() {
|
|||||||
[Z]="columns:normal"
|
[Z]="columns:normal"
|
||||||
[a]="attr-map:glob-*.attrmap"
|
[a]="attr-map:glob-*.attrmap"
|
||||||
[A]="auto-attr-map:normal"
|
[A]="auto-attr-map:normal"
|
||||||
|
[B]="background-color:unk"
|
||||||
[b]="base-tiles:unk"
|
[b]="base-tiles:unk"
|
||||||
[c]="colors:unk"
|
[c]="colors:unk"
|
||||||
[d]="depth:unk"
|
[d]="depth:unk"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ _rgblink_completions() {
|
|||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
|
[h]="help:normal"
|
||||||
[d]="dmg:normal"
|
[d]="dmg:normal"
|
||||||
[t]="tiny:normal"
|
[t]="tiny:normal"
|
||||||
[v]="verbose: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
|
# Ignore comment lines, only pick matching bank
|
||||||
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
||||||
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
|
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
|
||||||
|
sed "s/$(printf "^%02x:" $BANK)/0x/g" |
|
||||||
cut -d ';' -f 1 |
|
cut -d ';' -f 1 |
|
||||||
tr -d "\r" |
|
tr -d "\r" |
|
||||||
|
sort -g |
|
||||||
while read -r SYMADDR SYM; do
|
while read -r SYMADDR SYM; do
|
||||||
SYMADDR=$((0x${SYMADDR#*:}))
|
SYMADDR=$(($SYMADDR))
|
||||||
if [[ $SYMADDR -le $ADDR ]]; then
|
if [[ $SYMADDR -le $ADDR ]]; then
|
||||||
printf " (%s+0x%x)\n" "$SYM" $((ADDR - SYMADDR))
|
printf " (%s+0x%x)\n" "$SYM" $((ADDR - SYMADDR))
|
||||||
fi
|
fi
|
||||||
# TODO: assumes sorted sym files
|
|
||||||
done | tail -n 1
|
done | tail -n 1
|
||||||
fi)
|
fi)
|
||||||
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ _rgbasm_warnings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local args=(
|
local args=(
|
||||||
# Arguments are listed here in the same order as in the manual, except for the version
|
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||||
'(- : * options)'{-V,--version}'[Print version number]'
|
'(- : * 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]'
|
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
|
||||||
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
||||||
|
|||||||
@@ -35,8 +35,9 @@ _mbc_names() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local args=(
|
local args=(
|
||||||
# Arguments are listed here in the same order as in the manual, except for the version
|
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||||
'(- : * options)'{-V,--version}'[Print version number]'
|
'(- : * 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-only}'[Mark ROM as GBC-only]'
|
||||||
'(-C --color-only -c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'
|
'(-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:'
|
'(-L --logo)'{-L,--logo}'+[Set custom logo]:1bpp image:'
|
||||||
'(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names"
|
'(-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:'
|
'(-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:'
|
'(-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:'
|
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'
|
||||||
'(-t --title)'{-t,--title}'+[Set title string]:11-char title string:'
|
'(-t --title)'{-t,--title}'+[Set title string]:11-char title string:'
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ _depths() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local args=(
|
local args=(
|
||||||
# Arguments are listed here in the same order as in the manual, except for the version
|
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||||
'(- : * options)'{-V,--version}'[Print version number]'
|
'(- : * 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]'
|
'(-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]'
|
'(-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]'
|
'(-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'
|
'(-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:'
|
'(-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:'
|
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
|
||||||
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#compdef rgblink
|
#compdef rgblink
|
||||||
|
|
||||||
local args=(
|
local args=(
|
||||||
# Arguments are listed here in the same order as in the manual, except for the version
|
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||||
'(- : * options)'{-V,--version}'[Print version number]'
|
'(- : * 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)]'
|
'(-d --dmg)'{-d,--dmg}'[Enable DMG mode (-w + no VRAM banking)]'
|
||||||
'(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]'
|
'(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_CHARMAP_HPP
|
#ifndef RGBDS_ASM_CHARMAP_HPP
|
||||||
#define RGBDS_ASM_CHARMAP_HPP
|
#define RGBDS_ASM_CHARMAP_HPP
|
||||||
@@ -20,8 +20,10 @@ void charmap_Push();
|
|||||||
void charmap_Pop();
|
void charmap_Pop();
|
||||||
void charmap_CheckStack();
|
void charmap_CheckStack();
|
||||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
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::vector<int32_t> charmap_Convert(std::string const &input);
|
std::vector<int32_t> charmap_Convert(std::string const &input);
|
||||||
size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output);
|
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
|
#endif // RGBDS_ASM_CHARMAP_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_FIXPOINT_HPP
|
#ifndef RGBDS_ASM_FIXPOINT_HPP
|
||||||
#define RGBDS_ASM_FIXPOINT_HPP
|
#define RGBDS_ASM_FIXPOINT_HPP
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
extern uint8_t fixPrecision;
|
extern uint8_t fixPrecision;
|
||||||
|
|
||||||
uint8_t fix_Precision();
|
uint8_t fix_Precision();
|
||||||
double fix_PrecisionFactor();
|
|
||||||
int32_t fix_Sin(int32_t i, int32_t q);
|
int32_t fix_Sin(int32_t i, int32_t q);
|
||||||
int32_t fix_Cos(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);
|
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
|
#ifndef RGBDS_ASM_FORMAT_HPP
|
||||||
#define 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
|
// Contains some assembler-wide defines and externs
|
||||||
|
|
||||||
@@ -41,15 +41,11 @@ struct FileStackNode {
|
|||||||
std::string const &name() const { return data.get<std::string>(); }
|
std::string const &name() const { return data.get<std::string>(); }
|
||||||
|
|
||||||
FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_)
|
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;
|
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;
|
extern size_t maxRecursionDepth;
|
||||||
|
|
||||||
struct MacroArgs;
|
struct MacroArgs;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_LEXER_HPP
|
#ifndef RGBDS_ASM_LEXER_HPP
|
||||||
#define 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
|
// 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).
|
// 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
|
// 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");
|
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
|
// 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;
|
LexerMode mode;
|
||||||
bool atLineStart;
|
bool atLineStart;
|
||||||
uint32_t lineNo;
|
uint32_t lineNo;
|
||||||
uint32_t colNo;
|
|
||||||
int lastToken;
|
int lastToken;
|
||||||
|
|
||||||
std::deque<IfStackEntry> ifStack;
|
std::deque<IfStackEntry> ifStack;
|
||||||
@@ -147,7 +146,6 @@ void lexer_ReachELSEBlock();
|
|||||||
|
|
||||||
void lexer_CheckRecursionDepth();
|
void lexer_CheckRecursionDepth();
|
||||||
uint32_t lexer_GetLineNo();
|
uint32_t lexer_GetLineNo();
|
||||||
uint32_t lexer_GetColNo();
|
|
||||||
void lexer_DumpStringExpansions();
|
void lexer_DumpStringExpansions();
|
||||||
|
|
||||||
struct Capture {
|
struct Capture {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_MACRO_HPP
|
#ifndef RGBDS_ASM_MACRO_HPP
|
||||||
#define RGBDS_ASM_MACRO_HPP
|
#define RGBDS_ASM_MACRO_HPP
|
||||||
@@ -9,11 +9,11 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct MacroArgs {
|
struct MacroArgs {
|
||||||
unsigned int shift;
|
uint32_t shift;
|
||||||
std::vector<std::shared_ptr<std::string>> args;
|
std::vector<std::shared_ptr<std::string>> args;
|
||||||
|
|
||||||
uint32_t nbArgs() const { return args.size() - shift; }
|
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;
|
std::shared_ptr<std::string> getAllArgs() const;
|
||||||
|
|
||||||
void appendArg(std::shared_ptr<std::string> arg);
|
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
|
#ifndef RGBDS_ASM_MAIN_HPP
|
||||||
#define 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
|
#ifndef RGBDS_ASM_OPT_HPP
|
||||||
#define 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
|
#ifndef RGBDS_ASM_OUTPUT_HPP
|
||||||
#define RGBDS_ASM_OUTPUT_HPP
|
#define RGBDS_ASM_OUTPUT_HPP
|
||||||
@@ -14,14 +14,7 @@
|
|||||||
struct Expression;
|
struct Expression;
|
||||||
struct FileStackNode;
|
struct FileStackNode;
|
||||||
|
|
||||||
enum StateFeature {
|
enum StateFeature { STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO, NB_STATE_FEATURES };
|
||||||
STATE_EQU,
|
|
||||||
STATE_VAR,
|
|
||||||
STATE_EQUS,
|
|
||||||
STATE_CHAR,
|
|
||||||
STATE_MACRO,
|
|
||||||
NB_STATE_FEATURES
|
|
||||||
};
|
|
||||||
|
|
||||||
extern std::string objectFileName;
|
extern std::string objectFileName;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_RPN_HPP
|
#ifndef RGBDS_ASM_RPN_HPP
|
||||||
#define 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
|
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
||||||
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
||||||
|
|
||||||
Expression() = default;
|
bool isKnown() const { return data.holds<int32_t>(); }
|
||||||
Expression(Expression &&) = default;
|
int32_t value() const { return data.get<int32_t>(); }
|
||||||
#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>();
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t getConstVal() const;
|
int32_t getConstVal() const;
|
||||||
Symbol const *symbolOf() const;
|
Symbol const *symbolOf() const;
|
||||||
@@ -55,6 +42,7 @@ struct Expression {
|
|||||||
|
|
||||||
bool makeCheckHRAM();
|
bool makeCheckHRAM();
|
||||||
void makeCheckRST();
|
void makeCheckRST();
|
||||||
|
void makeCheckBitIndex(uint8_t mask);
|
||||||
|
|
||||||
void checkNBit(uint8_t n) const;
|
void checkNBit(uint8_t n) const;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_SECTION_HPP
|
#ifndef RGBDS_ASM_SECTION_HPP
|
||||||
#define 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_WordString(std::vector<int32_t> const &string);
|
||||||
void sect_LongString(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_Skip(uint32_t skip, bool ds);
|
||||||
void sect_RelByte(Expression &expr, uint32_t pcShift);
|
void sect_RelByte(Expression const &expr, uint32_t pcShift);
|
||||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs);
|
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs);
|
||||||
void sect_RelWord(Expression &expr, uint32_t pcShift);
|
void sect_RelWord(Expression const &expr, uint32_t pcShift);
|
||||||
void sect_RelLong(Expression &expr, uint32_t pcShift);
|
void sect_RelLong(Expression const &expr, uint32_t pcShift);
|
||||||
void sect_PCRelByte(Expression &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_BinaryFile(std::string const &name, int32_t startPos);
|
||||||
void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length);
|
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
|
#ifndef RGBDS_ASM_SYMBOL_HPP
|
||||||
#define 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);
|
Symbol *sym_AddVar(std::string const &symName, int32_t value);
|
||||||
int32_t sym_GetRSValue();
|
int32_t sym_GetRSValue();
|
||||||
void sym_SetRSValue(int32_t value);
|
void sym_SetRSValue(int32_t value);
|
||||||
uint32_t sym_GetConstantValue(std::string const &symName);
|
|
||||||
// Find a symbol by exact name, bypassing expansion checks
|
// Find a symbol by exact name, bypassing expansion checks
|
||||||
Symbol *sym_FindExactSymbol(std::string const &symName);
|
Symbol *sym_FindExactSymbol(std::string const &symName);
|
||||||
// Find a symbol, possibly scoped, by name
|
// Find a symbol, possibly scoped, by name
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_WARNING_HPP
|
#ifndef RGBDS_ASM_WARNING_HPP
|
||||||
#define RGBDS_ASM_WARNING_HPP
|
#define RGBDS_ASM_WARNING_HPP
|
||||||
@@ -62,27 +62,24 @@ extern bool warningsAreErrors;
|
|||||||
|
|
||||||
void processWarningFlag(char const *flag);
|
void processWarningFlag(char const *flag);
|
||||||
|
|
||||||
/*
|
// Used to warn the user about problems that don't prevent the generation of
|
||||||
* Used to warn the user about problems that don't prevent the generation of
|
// valid code.
|
||||||
* valid code.
|
[[gnu::format(printf, 2, 3)]]
|
||||||
*/
|
void warning(WarningID id, char const *fmt, ...);
|
||||||
[[gnu::format(printf, 2, 3)]] void warning(WarningID id, char const *fmt, ...);
|
|
||||||
|
|
||||||
/*
|
// Used for errors that compromise the whole assembly process by affecting the
|
||||||
* Used for errors that compromise the whole assembly process by affecting the
|
// following code, potencially making the assembler generate errors caused by
|
||||||
* following code, potencially making the assembler generate errors caused by
|
// the first one and unrelated to the code that the assembler complains about.
|
||||||
* 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,
|
||||||
* It is also used when the assembler goes into an invalid state (for example,
|
// when it fails to allocate memory).
|
||||||
* when it fails to allocate memory).
|
[[gnu::format(printf, 1, 2), noreturn]]
|
||||||
*/
|
void fatalerror(char const *fmt, ...);
|
||||||
[[gnu::format(printf, 1, 2), noreturn]] void fatalerror(char const *fmt, ...);
|
|
||||||
|
|
||||||
/*
|
// Used for errors that make it impossible to assemble correctly, but don't
|
||||||
* Used for errors that make it impossible to assemble correctly, but don't
|
// affect the following code. The code will fail to assemble but the user will
|
||||||
* 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
|
||||||
* get a list of all errors at the end, making it easier to fix all of them at
|
// once.
|
||||||
* once.
|
[[gnu::format(printf, 1, 2)]]
|
||||||
*/
|
void error(char const *fmt, ...);
|
||||||
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
|
|
||||||
|
|
||||||
#endif // RGBDS_ASM_WARNING_HPP
|
#endif // RGBDS_ASM_WARNING_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_DEFAULT_INIT_ALLOC_HPP
|
#ifndef RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||||
#define RGBDS_DEFAULT_INIT_ALLOC_HPP
|
#define RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||||
@@ -6,14 +6,11 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
/*
|
// Allocator adaptor that interposes construct() calls to convert value-initialization
|
||||||
* 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
|
||||||
* (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
|
// zero out non-class types).
|
||||||
* zero out non-class types).
|
// From
|
||||||
* From
|
// https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
|
||||||
* 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>>
|
template<typename T, typename A = std::allocator<T>>
|
||||||
class default_init_allocator : public A {
|
class default_init_allocator : public A {
|
||||||
using a_t = std::allocator_traits<A>;
|
using a_t = std::allocator_traits<A>;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_EITHER_HPP
|
#ifndef RGBDS_EITHER_HPP
|
||||||
#define RGBDS_EITHER_HPP
|
#define RGBDS_EITHER_HPP
|
||||||
@@ -103,7 +103,7 @@ public:
|
|||||||
} else if (other._tag == other._t2.tag_value) {
|
} else if (other._tag == other._t2.tag_value) {
|
||||||
*this = other._t2.value;
|
*this = other._t2.value;
|
||||||
} else {
|
} else {
|
||||||
_tag = nulltag;
|
_tag = nulltag; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ERROR_HPP
|
#ifndef RGBDS_ERROR_HPP
|
||||||
#define RGBDS_ERROR_HPP
|
#define RGBDS_ERROR_HPP
|
||||||
|
|
||||||
extern "C" {
|
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), noreturn]]
|
||||||
[[gnu::format(printf, 1, 2)]] void warnx(char const *fmt, ...);
|
void err(char const *fmt, ...);
|
||||||
|
[[gnu::format(printf, 1, 2), noreturn]]
|
||||||
[[gnu::format(printf, 1, 2), noreturn]] void err(char const *fmt, ...);
|
void errx(char const *fmt, ...);
|
||||||
[[gnu::format(printf, 1, 2), noreturn]] void errx(char const *fmt, ...);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // RGBDS_ERROR_HPP
|
#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
|
#ifndef RGBDS_EXTERN_GETOPT_HPP
|
||||||
#define 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 char *musl_optarg;
|
||||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
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
|
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
|
#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
|
#ifndef RGBDS_EXTERN_UTF8DECODER_HPP
|
||||||
#define 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
|
#ifndef RGBDS_FILE_HPP
|
||||||
#define RGBDS_FILE_HPP
|
#define RGBDS_FILE_HPP
|
||||||
@@ -18,17 +18,13 @@
|
|||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
class File {
|
class File {
|
||||||
// Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`.
|
|
||||||
Either<std::streambuf *, std::filebuf> _file;
|
Either<std::streambuf *, std::filebuf> _file;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
File() {}
|
File() : _file(nullptr) {}
|
||||||
~File() { close(); }
|
|
||||||
|
|
||||||
/**
|
// This should only be called once, and before doing any `->` operations.
|
||||||
* This should only be called once, and before doing any `->` operations.
|
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||||
* Returns `nullptr` on error, and a non-null pointer otherwise.
|
|
||||||
*/
|
|
||||||
File *open(std::string const &path, std::ios_base::openmode mode) {
|
File *open(std::string const &path, std::ios_base::openmode mode) {
|
||||||
if (path != "-") {
|
if (path != "-") {
|
||||||
_file.emplace<std::filebuf>();
|
_file.emplace<std::filebuf>();
|
||||||
@@ -63,20 +59,6 @@ public:
|
|||||||
return const_cast<File *>(this)->operator->();
|
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 {
|
char const *c_str(std::string const &path) const {
|
||||||
return _file.holds<std::filebuf>() ? path.c_str()
|
return _file.holds<std::filebuf>() ? path.c_str()
|
||||||
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"
|
: _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
|
#ifndef RGBDS_GFX_MAIN_HPP
|
||||||
#define RGBDS_GFX_MAIN_HPP
|
#define RGBDS_GFX_MAIN_HPP
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
@@ -21,6 +23,7 @@ struct Options {
|
|||||||
uint8_t verbosity = 0; // -v
|
uint8_t verbosity = 0; // -v
|
||||||
|
|
||||||
std::string attrmap{}; // -a, -A
|
std::string attrmap{}; // -a, -A
|
||||||
|
std::optional<Rgba> bgColor{}; // -B
|
||||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
||||||
enum {
|
enum {
|
||||||
NO_SPEC,
|
NO_SPEC,
|
||||||
@@ -48,6 +51,7 @@ struct Options {
|
|||||||
|
|
||||||
std::string input{}; // positional arg
|
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_NONE = 0; // Normal, no extra output
|
||||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||||
@@ -55,7 +59,9 @@ struct Options {
|
|||||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
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_TRACE = 5; // Step-by-step algorithm details
|
||||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
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;
|
mutable bool hasTransparentPixels = false;
|
||||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
||||||
@@ -63,32 +69,24 @@ struct Options {
|
|||||||
|
|
||||||
extern Options options;
|
extern Options options;
|
||||||
|
|
||||||
/*
|
// Prints the error count, and exits with failure
|
||||||
* Prints the error count, and exits with failure
|
[[noreturn]]
|
||||||
*/
|
void giveUp();
|
||||||
[[noreturn]] void giveUp();
|
// If any error has been emitted thus far, calls `giveUp()`.
|
||||||
/*
|
|
||||||
* If any error has been emitted thus far, calls `giveUp()`.
|
|
||||||
*/
|
|
||||||
void requireZeroErrors();
|
void requireZeroErrors();
|
||||||
/*
|
// Prints a warning, and does not change the error count
|
||||||
* Prints a warning, and does not change the error count
|
[[gnu::format(printf, 1, 2)]]
|
||||||
*/
|
void warning(char const *fmt, ...);
|
||||||
[[gnu::format(printf, 1, 2)]] void warning(char const *fmt, ...);
|
// Prints an error, and increments the error count
|
||||||
/*
|
[[gnu::format(printf, 1, 2)]]
|
||||||
* Prints an error, and increments the error count
|
void error(char const *fmt, ...);
|
||||||
*/
|
// Prints an error, and increments the error count
|
||||||
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
|
// Does not take format arguments so `format_` and `-Wformat-security` won't complain about
|
||||||
/*
|
// calling `errorMessage(msg)`.
|
||||||
* 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);
|
void errorMessage(char const *msg);
|
||||||
/*
|
// Prints a fatal error, increments the error count, and gives up
|
||||||
* Prints a fatal error, increments the error count, and gives up
|
[[gnu::format(printf, 1, 2), noreturn]]
|
||||||
*/
|
void fatal(char const *fmt, ...);
|
||||||
[[gnu::format(printf, 1, 2), noreturn]] void fatal(char const *fmt, ...);
|
|
||||||
|
|
||||||
struct Palette {
|
struct Palette {
|
||||||
// An array of 4 GBC-native (RGB555) colors
|
// An array of 4 GBC-native (RGB555) colors
|
||||||
@@ -121,4 +119,27 @@ static constexpr auto flipTable = ([]() constexpr {
|
|||||||
return table;
|
return table;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// Parsing helpers.
|
||||||
|
|
||||||
|
static constexpr uint8_t nibble(char c) {
|
||||||
|
if (c >= 'a') {
|
||||||
|
assume(c <= 'f');
|
||||||
|
return c - 'a' + 10;
|
||||||
|
} else if (c >= 'A') {
|
||||||
|
assume(c <= 'F');
|
||||||
|
return c - 'A' + 10;
|
||||||
|
} else {
|
||||||
|
assume(c >= '0' && c <= '9');
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr uint8_t toHex(char c1, char c2) {
|
||||||
|
return nibble(c1) * 16 + nibble(c2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr uint8_t singleToHex(char c) {
|
||||||
|
return toHex(c, c);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // RGBDS_GFX_MAIN_HPP
|
#endif // RGBDS_GFX_MAIN_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
||||||
#define RGBDS_GFX_PAL_PACKING_HPP
|
#define RGBDS_GFX_PAL_PACKING_HPP
|
||||||
@@ -11,9 +11,7 @@
|
|||||||
struct Palette;
|
struct Palette;
|
||||||
class ProtoPalette;
|
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>
|
std::tuple<DefaultInitVec<size_t>, size_t>
|
||||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
||||||
#define RGBDS_GFX_PAL_SORTING_HPP
|
#define RGBDS_GFX_PAL_SORTING_HPP
|
||||||
@@ -10,6 +10,10 @@
|
|||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
#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;
|
struct Palette;
|
||||||
|
|
||||||
void sortIndexed(
|
void sortIndexed(
|
||||||
@@ -20,7 +24,7 @@ void sortIndexed(
|
|||||||
png_byte *palAlpha
|
png_byte *palAlpha
|
||||||
);
|
);
|
||||||
void sortGrayscale(
|
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);
|
void sortRgb(std::vector<Palette> &palettes);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
||||||
#define RGBDS_GFX_PAL_SPEC_HPP
|
#define RGBDS_GFX_PAL_SPEC_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PROCESS_HPP
|
#ifndef RGBDS_GFX_PROCESS_HPP
|
||||||
#define 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
|
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
|
||||||
#define 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
|
#ifndef RGBDS_GFX_REVERSE_HPP
|
||||||
#define 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
|
#ifndef RGBDS_GFX_RGBA_HPP
|
||||||
#define 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)
|
constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||||
: red(r), green(g), blue(b), alpha(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)
|
explicit constexpr Rgba(uint32_t rgba = 0)
|
||||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
: 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
|
||||||
* Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
// representation
|
||||||
* representation
|
|
||||||
*/
|
|
||||||
uint32_t toCSS() const {
|
uint32_t toCSS() const {
|
||||||
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
|
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);
|
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(); }
|
|
||||||
|
|
||||||
/*
|
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.
|
// 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 uint16_t transparent = 0b1'00000'00000'00000;
|
||||||
|
|
||||||
static constexpr uint8_t transparency_threshold = 0x10;
|
static constexpr uint8_t transparency_threshold = 0x10;
|
||||||
bool isTransparent() const { return alpha < transparency_threshold; }
|
bool isTransparent() const { return alpha < transparency_threshold; }
|
||||||
static constexpr uint8_t opacity_threshold = 0xF0;
|
static constexpr uint8_t opacity_threshold = 0xF0;
|
||||||
bool isOpaque() const { return alpha >= opacity_threshold; }
|
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;
|
uint16_t cgbColor() const;
|
||||||
|
|
||||||
bool isGray() const { return red == green && green == blue; }
|
bool isGray() const { return red == green && green == blue; }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_HELPERS_HPP
|
#ifndef RGBDS_HELPERS_HPP
|
||||||
#define RGBDS_HELPERS_HPP
|
#define RGBDS_HELPERS_HPP
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
#else
|
#else
|
||||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||||
// Note that executing this is undefined behavior (declared [[noreturn]], but does return)
|
// Note that executing this is undefined behavior (declared [[noreturn]], but does return)
|
||||||
[[noreturn]] static inline void unreachable_() {
|
[[noreturn]]
|
||||||
|
static inline void unreachable_() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -26,8 +27,9 @@
|
|||||||
// `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
|
// `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
|
||||||
#define assume(x) \
|
#define assume(x) \
|
||||||
do { \
|
do { \
|
||||||
if (!(x)) \
|
if (!(x)) { \
|
||||||
unreachable_(); \
|
unreachable_(); \
|
||||||
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
@@ -93,15 +95,14 @@ static inline int clz(unsigned int x) {
|
|||||||
#define CAT(x, y) x##y
|
#define CAT(x, y) x##y
|
||||||
#define EXPAND_AND_CAT(x, y) CAT(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
|
// For lack of <ranges>, this adds some more brevity
|
||||||
#define RANGE(s) std::begin(s), std::end(s)
|
#define RANGE(s) std::begin(s), std::end(s)
|
||||||
|
|
||||||
// MSVC does not inline `strlen()` or `.length()` of a constant string, so we use `sizeof`
|
// MSVC does not inline `strlen()` or `.length()` of a constant string
|
||||||
#define QUOTEDSTRLEN(s) (sizeof(s) - 1)
|
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))`
|
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_ITERTOOLS_HPP
|
#ifndef RGBDS_ITERTOOLS_HPP
|
||||||
#define RGBDS_ITERTOOLS_HPP
|
#define RGBDS_ITERTOOLS_HPP
|
||||||
@@ -25,7 +25,6 @@ class EnumSeq {
|
|||||||
auto operator*() const { return _value; }
|
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; }
|
||||||
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -59,10 +58,6 @@ public:
|
|||||||
bool operator==(ZipIterator const &rhs) const {
|
bool operator==(ZipIterator const &rhs) const {
|
||||||
return std::get<0>(_iters) == std::get<0>(rhs._iters);
|
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>
|
template<typename... Ts>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_ASSIGN_HPP
|
#ifndef RGBDS_LINK_ASSIGN_HPP
|
||||||
#define 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
|
#ifndef RGBDS_LINK_MAIN_HPP
|
||||||
#define RGBDS_LINK_MAIN_HPP
|
#define RGBDS_LINK_MAIN_HPP
|
||||||
@@ -32,8 +32,9 @@ extern bool disablePadding;
|
|||||||
// Helper macro for printing verbose-mode messages
|
// Helper macro for printing verbose-mode messages
|
||||||
#define verbosePrint(...) \
|
#define verbosePrint(...) \
|
||||||
do { \
|
do { \
|
||||||
if (beVerbose) \
|
if (beVerbose) { \
|
||||||
fprintf(stderr, __VA_ARGS__); \
|
fprintf(stderr, __VA_ARGS__); \
|
||||||
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
struct FileStackNode {
|
struct FileStackNode {
|
||||||
@@ -58,11 +59,11 @@ struct FileStackNode {
|
|||||||
std::string const &dump(uint32_t curLineNo) const;
|
std::string const &dump(uint32_t curLineNo) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[gnu::format(printf, 3, 4)]] void
|
[[gnu::format(printf, 3, 4)]]
|
||||||
warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||||
[[gnu::format(printf, 3, 4)]] void
|
[[gnu::format(printf, 3, 4)]]
|
||||||
error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||||
[[gnu::format(printf, 3, 4), noreturn]] void
|
[[gnu::format(printf, 3, 4), noreturn]]
|
||||||
fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_MAIN_HPP
|
#endif // RGBDS_LINK_MAIN_HPP
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_OBJECT_HPP
|
#ifndef RGBDS_LINK_OBJECT_HPP
|
||||||
#define RGBDS_LINK_OBJECT_HPP
|
#define RGBDS_LINK_OBJECT_HPP
|
||||||
|
|
||||||
/*
|
// Read an object (.o) file, and add its info to the data structures.
|
||||||
* Read an object (.o) file, and add its info to the data structures.
|
void obj_ReadFile(char const *fileName, unsigned int fileID);
|
||||||
* @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);
|
|
||||||
|
|
||||||
/*
|
// Sets up object file reading
|
||||||
* Sets up object file reading
|
|
||||||
* @param nbFiles The number of object files that will be read
|
|
||||||
*/
|
|
||||||
void obj_Setup(unsigned int nbFiles);
|
void obj_Setup(unsigned int nbFiles);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_OBJECT_HPP
|
#endif // RGBDS_LINK_OBJECT_HPP
|
||||||
|
|||||||
@@ -1,26 +1,17 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_OUTPUT_HPP
|
#ifndef RGBDS_LINK_OUTPUT_HPP
|
||||||
#define RGBDS_LINK_OUTPUT_HPP
|
#define RGBDS_LINK_OUTPUT_HPP
|
||||||
|
|
||||||
struct Section;
|
struct Section;
|
||||||
|
|
||||||
/*
|
// Registers a section for output.
|
||||||
* Registers a section for output.
|
|
||||||
* @param section The section to add
|
|
||||||
*/
|
|
||||||
void out_AddSection(Section const §ion);
|
void out_AddSection(Section const §ion);
|
||||||
|
|
||||||
/*
|
// Finds an assigned section overlapping another one.
|
||||||
* Finds an assigned section overlapping another one.
|
|
||||||
* @param section The section that is being overlapped
|
|
||||||
* @return A section overlapping it
|
|
||||||
*/
|
|
||||||
Section const *out_OverlappingSection(Section const §ion);
|
Section const *out_OverlappingSection(Section const §ion);
|
||||||
|
|
||||||
/*
|
// Writes all output (bin, sym, map) files.
|
||||||
* Writes all output (bin, sym, map) files.
|
|
||||||
*/
|
|
||||||
void out_WriteFiles();
|
void out_WriteFiles();
|
||||||
|
|
||||||
#endif // RGBDS_LINK_OUTPUT_HPP
|
#endif // RGBDS_LINK_OUTPUT_HPP
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_PATCH_HPP
|
#ifndef RGBDS_LINK_PATCH_HPP
|
||||||
#define RGBDS_LINK_PATCH_HPP
|
#define RGBDS_LINK_PATCH_HPP
|
||||||
|
|
||||||
/*
|
// Checks all assertions
|
||||||
* Checks all assertions
|
|
||||||
* @return true if assertion failed
|
|
||||||
*/
|
|
||||||
void patch_CheckAssertions();
|
void patch_CheckAssertions();
|
||||||
|
|
||||||
/*
|
// Applies all SECTIONs' patches to them
|
||||||
* Applies all SECTIONs' patches to them
|
|
||||||
*/
|
|
||||||
void patch_ApplyPatches();
|
void patch_ApplyPatches();
|
||||||
|
|
||||||
#endif // RGBDS_LINK_PATCH_HPP
|
#endif // RGBDS_LINK_PATCH_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_SDAS_OBJ_HPP
|
#ifndef RGBDS_LINK_SDAS_OBJ_HPP
|
||||||
#define 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
|
#ifndef RGBDS_LINK_SECTION_HPP
|
||||||
#define RGBDS_LINK_SECTION_HPP
|
#define RGBDS_LINK_SECTION_HPP
|
||||||
@@ -65,29 +65,17 @@ struct Assertion {
|
|||||||
|
|
||||||
extern std::deque<Assertion> assertions;
|
extern std::deque<Assertion> assertions;
|
||||||
|
|
||||||
/*
|
// Execute a callback for each section currently registered.
|
||||||
* Execute a callback for each section currently registered.
|
// This is to avoid exposing the data structure in which sections are stored.
|
||||||
* This is to avoid exposing the data structure in which sections are stored.
|
|
||||||
* @param callback The function to call for each structure.
|
|
||||||
*/
|
|
||||||
void sect_ForEach(void (*callback)(Section &));
|
void sect_ForEach(void (*callback)(Section &));
|
||||||
|
|
||||||
/*
|
// Registers a section to be processed.
|
||||||
* Registers a section to be processed.
|
|
||||||
* @param section The section to register.
|
|
||||||
*/
|
|
||||||
void sect_AddSection(std::unique_ptr<Section> &§ion);
|
void sect_AddSection(std::unique_ptr<Section> &§ion);
|
||||||
|
|
||||||
/*
|
// Finds a section by its name.
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
Section *sect_GetSection(std::string const &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();
|
void sect_DoSanityChecks();
|
||||||
|
|
||||||
#endif // RGBDS_LINK_SECTION_HPP
|
#endif // RGBDS_LINK_SECTION_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_SYMBOL_HPP
|
#ifndef RGBDS_LINK_SYMBOL_HPP
|
||||||
#define RGBDS_LINK_SYMBOL_HPP
|
#define RGBDS_LINK_SYMBOL_HPP
|
||||||
@@ -41,11 +41,7 @@ void sym_ForEach(void (*callback)(Symbol &));
|
|||||||
|
|
||||||
void sym_AddSymbol(Symbol &symbol);
|
void sym_AddSymbol(Symbol &symbol);
|
||||||
|
|
||||||
/*
|
// Finds a symbol in all the defined symbols.
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
Symbol *sym_GetSymbol(std::string const &name);
|
Symbol *sym_GetSymbol(std::string const &name);
|
||||||
|
|
||||||
void sym_DumpLocalAliasedSymbols(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
|
#ifndef RGBDS_LINKDEFS_HPP
|
||||||
#define RGBDS_LINKDEFS_HPP
|
#define RGBDS_LINKDEFS_HPP
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "helpers.hpp" // assume
|
#include "helpers.hpp" // assume
|
||||||
|
|
||||||
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
||||||
#define RGBDS_OBJECT_REV 11U
|
#define RGBDS_OBJECT_REV 12U
|
||||||
|
|
||||||
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
|
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
|
||||||
|
|
||||||
@@ -52,6 +52,7 @@ enum RPNCommand {
|
|||||||
|
|
||||||
RPN_HRAM = 0x60,
|
RPN_HRAM = 0x60,
|
||||||
RPN_RST = 0x61,
|
RPN_RST = 0x61,
|
||||||
|
RPN_BIT_INDEX = 0x62,
|
||||||
|
|
||||||
RPN_HIGH = 0x70,
|
RPN_HIGH = 0x70,
|
||||||
RPN_LOW = 0x71,
|
RPN_LOW = 0x71,
|
||||||
@@ -92,29 +93,19 @@ extern struct SectionTypeInfo {
|
|||||||
uint32_t lastBank;
|
uint32_t lastBank;
|
||||||
} sectionTypeInfo[SECTTYPE_INVALID];
|
} sectionTypeInfo[SECTTYPE_INVALID];
|
||||||
|
|
||||||
/*
|
// Tells whether a section has data in its object file definition,
|
||||||
* Tells whether a section has data in its object file definition,
|
// depending on type.
|
||||||
* depending on type.
|
|
||||||
* @param type The section's type
|
|
||||||
* @return `true` if the section's definition includes data
|
|
||||||
*/
|
|
||||||
static inline bool sect_HasData(SectionType type) {
|
static inline bool sect_HasData(SectionType type) {
|
||||||
assume(type != SECTTYPE_INVALID);
|
assume(type != SECTTYPE_INVALID);
|
||||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Returns a memory region's end address (last byte), e.g. 0x7FFF
|
||||||
* Computes a memory region's end address (last byte), eg. 0x7FFF
|
|
||||||
* @return The address of the last byte in that memory region
|
|
||||||
*/
|
|
||||||
static inline uint16_t endaddr(SectionType type) {
|
static inline uint16_t endaddr(SectionType type) {
|
||||||
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Returns a memory region's number of banks, or 1 for regions without banking
|
||||||
* Computes a memory region's number of banks
|
|
||||||
* @return The number of banks, 1 for regions without banking
|
|
||||||
*/
|
|
||||||
static inline uint32_t nbbanks(SectionType type) {
|
static inline uint32_t nbbanks(SectionType type) {
|
||||||
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
|
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
|
#ifndef RGBDS_OP_MATH_HPP
|
||||||
#define RGBDS_OP_MATH_HPP
|
#define RGBDS_OP_MATH_HPP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// platform-specific hacks
|
// platform-specific hacks
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_UTIL_HPP
|
#ifndef RGBDS_UTIL_HPP
|
||||||
#define RGBDS_UTIL_HPP
|
#define RGBDS_UTIL_HPP
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
char const *printChar(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
|
#endif // RGBDS_UTIL_HPP
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef RGBDS_VERSION_HPP
|
#ifndef RGBDS_VERSION_HPP
|
||||||
#define RGBDS_VERSION_HPP
|
#define RGBDS_VERSION_HPP
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
#define PACKAGE_VERSION_MAJOR 0
|
#define PACKAGE_VERSION_MAJOR 0
|
||||||
#define PACKAGE_VERSION_MINOR 9
|
#define PACKAGE_VERSION_MINOR 9
|
||||||
#define PACKAGE_VERSION_PATCH 0
|
#define PACKAGE_VERSION_PATCH 2
|
||||||
|
|
||||||
char const *get_package_version_string();
|
char const *get_package_version_string();
|
||||||
}
|
|
||||||
|
|
||||||
#endif // RGBDS_VERSION_H
|
#endif // RGBDS_VERSION_H
|
||||||
|
|||||||
11
man/gbz80.7
11
man/gbz80.7
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd May 4, 2025
|
||||||
.Dt GBZ80 7
|
.Dt GBZ80 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -615,9 +615,6 @@ to the adjustment.
|
|||||||
.It
|
.It
|
||||||
Subtract the adjustment from
|
Subtract the adjustment from
|
||||||
.Sy A .
|
.Sy A .
|
||||||
.It
|
|
||||||
Set the carry flag if borrow (i.e. if adjustment >
|
|
||||||
.Sy A ) .
|
|
||||||
.El
|
.El
|
||||||
.It If the subtract flag Sy N No is not set:
|
.It If the subtract flag Sy N No is not set:
|
||||||
.Bl -enum -compact
|
.Bl -enum -compact
|
||||||
@@ -639,15 +636,13 @@ to the adjustment.
|
|||||||
If the carry flag is set or
|
If the carry flag is set or
|
||||||
.Sy A
|
.Sy A
|
||||||
>
|
>
|
||||||
.Ad $9F ,
|
.Ad $99 ,
|
||||||
then add
|
then add
|
||||||
.Ad $60
|
.Ad $60
|
||||||
to the adjustment.
|
to the adjustment and set the carry flag.
|
||||||
.It
|
.It
|
||||||
Add the adjustment to
|
Add the adjustment to
|
||||||
.Sy A .
|
.Sy A .
|
||||||
.It
|
|
||||||
Set the carry flag if overflow from bit 7.
|
|
||||||
.El
|
.El
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd May 4, 2025
|
||||||
.Dt RGBASM-OLD 5
|
.Dt RGBASM-OLD 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.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.
|
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
|
It does
|
||||||
.Em not
|
.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
|
.Sh REMOVED
|
||||||
These are features which have been completely removed, without any direct alternatives.
|
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.
|
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
|
.Ql LDH [C], A
|
||||||
and
|
and
|
||||||
.Ql LDH A, [C] .
|
.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]
|
.Ss LDH [n8], A and LDH A, [n8]
|
||||||
Deprecated in 0.9.0.
|
Deprecated in 0.9.0.
|
||||||
.Pp
|
.Pp
|
||||||
@@ -288,7 +296,7 @@ Deprecated in 0.3.0, removed in 0.4.0.
|
|||||||
.Pp
|
.Pp
|
||||||
Instead, use
|
Instead, use
|
||||||
.Ql LD HL, SP + e8 .
|
.Ql LD HL, SP + e8 .
|
||||||
.Ss LDHL, SP, e8
|
.Ss LDHL SP, e8
|
||||||
Supported in ASMotor, removed in RGBDS.
|
Supported in ASMotor, removed in RGBDS.
|
||||||
.Pp
|
.Pp
|
||||||
Instead, use
|
Instead, use
|
||||||
@@ -360,6 +368,68 @@ Previously we had
|
|||||||
.Pp
|
.Pp
|
||||||
Instead, now we have
|
Instead, now we have
|
||||||
.Ql p ** q ** r == p ** (q ** r) .
|
.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
|
.Sh SEE ALSO
|
||||||
.Xr rgbasm 1 ,
|
.Xr rgbasm 1 ,
|
||||||
.Xr gbz80 7 ,
|
.Xr gbz80 7 ,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd May 4, 2025
|
||||||
.Dt RGBASM 1
|
.Dt RGBASM 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
.Nd Game Boy assembler
|
.Nd Game Boy assembler
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl EVvw
|
.Op Fl EhVvw
|
||||||
.Op Fl b Ar chars
|
.Op Fl b Ar chars
|
||||||
.Op Fl D Ar name Ns Op = Ns Ar value
|
.Op Fl D Ar name Ns Op = Ns Ar value
|
||||||
.Op Fl g Ar chars
|
.Op Fl g Ar chars
|
||||||
@@ -67,6 +67,8 @@ Export all labels, including unreferenced and local labels.
|
|||||||
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
|
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
|
||||||
Change the four characters used for gfx constants.
|
Change the four characters used for gfx constants.
|
||||||
The defaults are 0123.
|
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
|
.It Fl I Ar path , Fl \-include Ar path
|
||||||
Add a new
|
Add a new
|
||||||
.Dq include path ;
|
.Dq include path ;
|
||||||
|
|||||||
57
man/rgbasm.5
57
man/rgbasm.5
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd May 4, 2025
|
||||||
.Dt RGBASM 5
|
.Dt RGBASM 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -564,22 +564,17 @@ is equivalent to the regular string
|
|||||||
(Note that this prevents raw strings from including the double quote character.)
|
(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).
|
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
|
.Pp
|
||||||
The following functions operate on string expressions.
|
The following functions operate on string expressions, and return strings themselves.
|
||||||
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 "STRSLICE(str, start, stop)"
|
||||||
.Bl -column "STRSUB(str, pos, len)"
|
|
||||||
.It Sy Name Ta Sy Operation
|
.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 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
|
.It Fn STRUPR str Ta Returns Ar str No with all ASCII letters
|
||||||
.Pq Ql a-z
|
.Pq Ql a-z
|
||||||
in uppercase.
|
in uppercase.
|
||||||
.It Fn STRLWR str Ta Returns Ar str No with all ASCII letters
|
.It Fn STRLWR str Ta Returns Ar str No with all ASCII letters
|
||||||
.Pq Ql A-Z
|
.Pq Ql A-Z
|
||||||
in lowercase.
|
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 Ns .
|
||||||
.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 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
|
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
||||||
.Ql %spec
|
.Ql %spec
|
||||||
@@ -589,9 +584,35 @@ pattern replaced by interpolating the format
|
|||||||
with its corresponding argument in
|
with its corresponding argument in
|
||||||
.Ar args
|
.Ar args
|
||||||
.Pq So %% Sc is replaced by the So % Sc character .
|
.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 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 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 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.
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
Note that the first character of a string is at index 0, and the last is at index -1.
|
||||||
|
.Pp
|
||||||
|
The following legacy functions are similar to other functions that operate on string expressions, but for historical reasons, they count characters starting from
|
||||||
|
.Em position 1 ,
|
||||||
|
not from index 0!
|
||||||
|
(Position -1 still counts from the last character.)
|
||||||
|
.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
|
.El
|
||||||
.Ss Character maps
|
.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.
|
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 +1073,7 @@ and
|
|||||||
.Ic WRAMX
|
.Ic WRAMX
|
||||||
types are still considered different.
|
types are still considered different.
|
||||||
.It
|
.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.
|
For example, alignment must be compatible with any fixed address, all specified banks must be the same, etc.
|
||||||
.It
|
.It
|
||||||
A section fragment may not be unionized; after all, that wouldn't make much sense.
|
A section fragment may not be unionized; after all, that wouldn't make much sense.
|
||||||
@@ -1104,7 +1125,9 @@ Additionally, label names can contain up to a single dot
|
|||||||
.Ql \&. ,
|
.Ql \&. ,
|
||||||
which may not be the first character.
|
which may not be the first character.
|
||||||
.Pp
|
.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 # .
|
.Sq # .
|
||||||
For example,
|
For example,
|
||||||
.Ql #load
|
.Ql #load
|
||||||
@@ -1279,7 +1302,7 @@ it at the same time.
|
|||||||
below).
|
below).
|
||||||
.Ss Numeric constants
|
.Ss Numeric constants
|
||||||
.Ic EQU
|
.Ic EQU
|
||||||
is used to define immutable numeric symbols.
|
is used to define numeric constant symbols.
|
||||||
Unlike
|
Unlike
|
||||||
.Sq =
|
.Sq =
|
||||||
above, constants defined this way cannot be redefined.
|
above, constants defined this way cannot be redefined.
|
||||||
@@ -1387,6 +1410,8 @@ This expansion is disabled in a few contexts:
|
|||||||
and
|
and
|
||||||
.Ql MACRO name
|
.Ql MACRO name
|
||||||
will not expand string constants in their names.
|
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
|
.Bd -literal -offset indent
|
||||||
DEF COUNTREG EQUS "[hl+]"
|
DEF COUNTREG EQUS "[hl+]"
|
||||||
ld a, COUNTREG
|
ld a, COUNTREG
|
||||||
@@ -1852,9 +1877,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
|
To use the rest, you put the argument number in angle brackets, like
|
||||||
.Ic \e<10> .
|
.Ic \e<10> .
|
||||||
.Pp
|
.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,
|
For example,
|
||||||
.Ql \e<_NARG>
|
.Ql \e<_NARG>
|
||||||
|
or
|
||||||
|
.Ql \e<-1>
|
||||||
will get the last argument.
|
will get the last argument.
|
||||||
.Pp
|
.Pp
|
||||||
Other macro arguments and symbol interpolations will also be expanded inside the angle brackets.
|
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
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd May 4, 2025
|
||||||
.Dt RGBDS 5
|
.Dt RGBDS 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -388,10 +388,19 @@ The value is then ANDed with $00FF
|
|||||||
check.
|
check.
|
||||||
Checks if the value is a valid
|
Checks if the value is a valid
|
||||||
.Ql rst
|
.Ql rst
|
||||||
.Pq see Do RST vec Dc in Xr gbz80 7
|
vector
|
||||||
vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
.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
|
The value is then ORed with $C7
|
||||||
.Pq Ql \&| $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
|
.It Li $80 Ta Integer literal; followed by the
|
||||||
.Cm LONG
|
.Cm LONG
|
||||||
integer.
|
integer.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd May 4, 2025
|
||||||
.Dt RGBDS 7
|
.Dt RGBDS 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.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.
|
forks Nossum's repository.
|
||||||
The fork becomes the reference implementation of RGBDS.
|
The fork becomes the reference implementation of RGBDS.
|
||||||
.It
|
.It
|
||||||
|
2010-09-25: S\(/orensen continues development of
|
||||||
|
.Lk https://github.com/asmotor/asmotor ASMotor
|
||||||
|
to this day.
|
||||||
|
.It
|
||||||
2015-01-18:
|
2015-01-18:
|
||||||
.An stag019
|
.An stag019
|
||||||
begins implementing RGBGFX, a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
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
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd May 4, 2025
|
||||||
.Dt RGBFIX 1
|
.Dt RGBFIX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
.Nd Game Boy header utility and checksum fixer
|
.Nd Game Boy header utility and checksum fixer
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl jOsVv
|
.Op Fl hjOsVv
|
||||||
.Op Fl C | c
|
.Op Fl C | c
|
||||||
.Op Fl f Ar fix_spec
|
.Op Fl f Ar fix_spec
|
||||||
.Op Fl i Ar game_id
|
.Op Fl i Ar game_id
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
.Op Fl l Ar licensee_id
|
.Op Fl l Ar licensee_id
|
||||||
.Op Fl m Ar mbc_type
|
.Op Fl m Ar mbc_type
|
||||||
.Op Fl n Ar rom_version
|
.Op Fl n Ar rom_version
|
||||||
|
.Op Fl o Ar out_file
|
||||||
.Op Fl p Ar pad_value
|
.Op Fl p Ar pad_value
|
||||||
.Op Fl r Ar ram_size
|
.Op Fl r Ar ram_size
|
||||||
.Op Fl t Ar title_str
|
.Op Fl t Ar title_str
|
||||||
@@ -91,6 +92,8 @@ Fix the global checksum
|
|||||||
.It Cm G
|
.It Cm G
|
||||||
Trash the global checksum.
|
Trash the global checksum.
|
||||||
.El
|
.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
|
.It Fl i Ar game_id , Fl \-game-id Ar game_id
|
||||||
Set the game ID string
|
Set the game ID string
|
||||||
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
||||||
@@ -132,6 +135,9 @@ Set the ROM version
|
|||||||
to a given value from 0 to 0xFF.
|
to a given value from 0 to 0xFF.
|
||||||
.It Fl O , Fl \-overwrite
|
.It Fl O , Fl \-overwrite
|
||||||
Allow overwriting different non-zero bytes in the header without a warning being emitted.
|
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
|
.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).
|
Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
|
||||||
.Nm
|
.Nm
|
||||||
|
|||||||
21
man/rgbgfx.1
21
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd May 4, 2025
|
||||||
.Dt RGBGFX 1
|
.Dt RGBGFX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
.Nd Game Boy graphics converter
|
.Nd Game Boy graphics converter
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl CmOuVXYZ
|
.Op Fl CmhOuVXYZ
|
||||||
.Op Fl v Op Fl v No ...
|
.Op Fl v Op Fl v No ...
|
||||||
.Op Fl a Ar attrmap | Fl A
|
.Op Fl a Ar attrmap | Fl A
|
||||||
.Op Fl b Ar base_ids
|
.Op Fl b Ar base_ids
|
||||||
@@ -103,6 +103,19 @@ and has the same size.
|
|||||||
Same as
|
Same as
|
||||||
.Fl a Ar base_path Ns .attrmap
|
.Fl a Ar base_path Ns .attrmap
|
||||||
.Pq see Sx Automatic output paths .
|
.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
|
.It Fl b Ar base_ids , Fl \-base-tiles Ar base_ids
|
||||||
Set the base IDs for tile map output.
|
Set the base IDs for tile map output.
|
||||||
.Ar base_ids
|
.Ar base_ids
|
||||||
@@ -126,7 +139,7 @@ begins with a hash character
|
|||||||
.Ql # ,
|
.Ql # ,
|
||||||
it is treated as an inline palette specification.
|
it is treated as an inline palette specification.
|
||||||
It should contain a comma-separated list of hexadecimal colors, each beginning with a hash.
|
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
|
.Ql #rgb
|
||||||
or
|
or
|
||||||
.Ql #rrggbb
|
.Ql #rrggbb
|
||||||
@@ -165,6 +178,8 @@ for a list of formats and their descriptions.
|
|||||||
.It Fl d Ar depth , Fl \-depth Ar depth
|
.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).
|
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).
|
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
|
.It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles
|
||||||
Use the specified input tiles in addition to having
|
Use the specified input tiles in addition to having
|
||||||
.Nm
|
.Nm
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd May 4, 2025
|
||||||
.Dt RGBLINK 1
|
.Dt RGBLINK 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
.Nd Game Boy linker
|
.Nd Game Boy linker
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl dMtVvwx
|
.Op Fl dhMtVvwx
|
||||||
.Op Fl l Ar linker_script
|
.Op Fl l Ar linker_script
|
||||||
.Op Fl m Ar map_file
|
.Op Fl m Ar map_file
|
||||||
.Op Fl n Ar sym_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.
|
Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1.
|
||||||
This option automatically enables
|
This option automatically enables
|
||||||
.Fl w .
|
.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
|
.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.
|
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.
|
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.
|
Disables padding the end of the final file.
|
||||||
This option automatically enables
|
This option automatically enables
|
||||||
.Fl t .
|
.Fl t .
|
||||||
You can use this when not not making a ROM.
|
You can use this to make binary files that are not a ROM.
|
||||||
When making a ROM, be careful that not using this is not a replacement for
|
When making a ROM, note that not using this is not a replacement for
|
||||||
.Xr rgbfix 1 Ap s Fl p
|
.Xr rgbfix 1 Ap s Fl p
|
||||||
option!
|
option!
|
||||||
.El
|
.El
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd May 4, 2025
|
||||||
.Dt RGBLINK 5
|
.Dt RGBLINK 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -120,7 +120,7 @@ causes all sections between it and the next
|
|||||||
.Ic ORG
|
.Ic ORG
|
||||||
or bank specification to be placed at addresses automatically determined by
|
or bank specification to be placed at addresses automatically determined by
|
||||||
.Nm .
|
.Nm .
|
||||||
.Pq It is, however, compatible with Ic ALIGN No below.
|
.Pq \&It is, however, compatible with Ic ALIGN No below.
|
||||||
.Pp
|
.Pp
|
||||||
.Ql Ic ALIGN Ar addr , Ar offset
|
.Ql Ic ALIGN Ar addr , Ar offset
|
||||||
increases the
|
increases the
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/charmap.hpp"
|
#include "asm/charmap.hpp"
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "extern/utf8decoder.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
|
||||||
@@ -30,6 +31,29 @@ struct CharmapNode {
|
|||||||
struct Charmap {
|
struct Charmap {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::vector<CharmapNode> nodes; // first node is reserved for the root node
|
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;
|
static std::deque<Charmap> charmapList;
|
||||||
@@ -43,22 +67,16 @@ bool charmap_ForEach(
|
|||||||
void (*charFunc)(std::string const &, std::vector<int32_t>)
|
void (*charFunc)(std::string const &, std::vector<int32_t>)
|
||||||
) {
|
) {
|
||||||
for (Charmap const &charmap : charmapList) {
|
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;
|
std::map<size_t, std::string> mappings;
|
||||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
charmap.forEachChar([&mappings](size_t nodeIdx, std::string const &mapping) {
|
||||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
mappings[nodeIdx] = mapping;
|
||||||
prefixes.pop();
|
return true;
|
||||||
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)});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mapFunc(charmap.name);
|
mapFunc(charmap.name);
|
||||||
for (auto [nodeIdx, mapping] : mappings)
|
for (auto [nodeIdx, mapping] : mappings) {
|
||||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return !charmapList.empty();
|
return !charmapList.empty();
|
||||||
}
|
}
|
||||||
@@ -67,10 +85,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
|||||||
size_t baseIdx = SIZE_MAX;
|
size_t baseIdx = SIZE_MAX;
|
||||||
|
|
||||||
if (baseName != nullptr) {
|
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());
|
error("Base charmap '%s' doesn't exist\n", baseName->c_str());
|
||||||
else
|
} else {
|
||||||
baseIdx = search->second;
|
baseIdx = search->second;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (charmapMap.find(name) != charmapMap.end()) {
|
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();
|
charmapMap[name] = charmapList.size();
|
||||||
Charmap &charmap = charmapList.emplace_back();
|
Charmap &charmap = charmapList.emplace_back();
|
||||||
|
|
||||||
if (baseIdx != SIZE_MAX)
|
if (baseIdx != SIZE_MAX) {
|
||||||
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
|
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
|
||||||
else
|
} else {
|
||||||
charmap.nodes.emplace_back(); // Zero-init the root node
|
charmap.nodes.emplace_back(); // Zero-init the root node
|
||||||
|
}
|
||||||
|
|
||||||
charmap.name = name;
|
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) {
|
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());
|
error("Charmap '%s' doesn't exist\n", name.c_str());
|
||||||
else
|
} else {
|
||||||
currentCharmap = &charmapList[search->second];
|
currentCharmap = &charmapList[search->second];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void charmap_Push() {
|
void charmap_Push() {
|
||||||
@@ -146,30 +167,47 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
|||||||
|
|
||||||
CharmapNode &node = charmap.nodes[nodeIdx];
|
CharmapNode &node = charmap.nodes[nodeIdx];
|
||||||
|
|
||||||
if (node.isTerminal())
|
if (node.isTerminal()) {
|
||||||
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
||||||
|
}
|
||||||
|
|
||||||
std::swap(node.value, value);
|
std::swap(node.value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool charmap_HasChar(std::string const &input) {
|
bool charmap_HasChar(std::string const &mapping) {
|
||||||
Charmap const &charmap = *currentCharmap;
|
Charmap const &charmap = *currentCharmap;
|
||||||
size_t nodeIdx = 0;
|
size_t nodeIdx = 0;
|
||||||
|
|
||||||
for (char c : input) {
|
for (char c : mapping) {
|
||||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||||
|
|
||||||
if (!nodeIdx)
|
if (!nodeIdx) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return charmap.nodes[nodeIdx].isTerminal();
|
return charmap.nodes[nodeIdx].isTerminal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t charmap_CharSize(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 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||||
|
return node.isTerminal() ? node.value.size() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
||||||
std::vector<int32_t> output;
|
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;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,8 +224,9 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
|||||||
for (size_t nodeIdx = 0; inputIdx < input.length();) {
|
for (size_t nodeIdx = 0; inputIdx < input.length();) {
|
||||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(input[inputIdx])];
|
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(input[inputIdx])];
|
||||||
|
|
||||||
if (!nodeIdx)
|
if (!nodeIdx) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
inputIdx++; // Consume that char
|
inputIdx++; // Consume that char
|
||||||
|
|
||||||
@@ -207,27 +246,42 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
|||||||
if (matchIdx) { // A match was found, use it
|
if (matchIdx) { // A match was found, use it
|
||||||
std::vector<int32_t> const &value = charmap.nodes[matchIdx].value;
|
std::vector<int32_t> const &value = charmap.nodes[matchIdx].value;
|
||||||
|
|
||||||
if (output)
|
if (output) {
|
||||||
output->insert(output->end(), RANGE(value));
|
output->insert(output->end(), RANGE(value));
|
||||||
|
}
|
||||||
|
|
||||||
matchLen = value.size();
|
matchLen = value.size();
|
||||||
} else if (inputIdx < input.length()) { // No match found, but there is some input left
|
} 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
|
// 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)
|
if (output) {
|
||||||
error("Input string is not valid UTF-8\n");
|
output->insert(
|
||||||
|
output->end(), input.data() + inputIdx, input.data() + inputIdx + codepointLen
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Warn if this character is not mapped but any others are
|
// 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));
|
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(
|
||||||
WARNING_UNMAPPED_CHAR_2,
|
WARNING_UNMAPPED_CHAR_2,
|
||||||
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
|
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
|
||||||
printChar(firstChar)
|
printChar(firstChar)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
inputIdx += codepointLen;
|
inputIdx += codepointLen;
|
||||||
matchLen = codepointLen;
|
matchLen = codepointLen;
|
||||||
@@ -236,3 +290,20 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
|||||||
input = input.substr(inputIdx);
|
input = input.substr(inputIdx);
|
||||||
return matchLen;
|
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
|
// Fixed-point math routines
|
||||||
|
|
||||||
@@ -16,19 +16,17 @@ uint8_t fix_Precision() {
|
|||||||
return fixPrecision;
|
return fixPrecision;
|
||||||
}
|
}
|
||||||
|
|
||||||
double fix_PrecisionFactor() {
|
|
||||||
return pow(2.0, fixPrecision);
|
|
||||||
}
|
|
||||||
|
|
||||||
static double fix2double(int32_t i, int32_t q) {
|
static double fix2double(int32_t i, int32_t q) {
|
||||||
return i / pow(2.0, q);
|
return i / pow(2.0, q);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t double2fix(double d, int32_t q) {
|
static int32_t double2fix(double d, int32_t q) {
|
||||||
if (isnan(d))
|
if (isnan(d)) {
|
||||||
return 0;
|
return 0;
|
||||||
if (isinf(d))
|
}
|
||||||
|
if (isinf(d)) {
|
||||||
return d < 0 ? INT32_MIN : INT32_MAX;
|
return d < 0 ? INT32_MIN : INT32_MAX;
|
||||||
|
}
|
||||||
return static_cast<int32_t>(round(d * pow(2.0, q)));
|
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) {
|
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) {
|
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) {
|
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) {
|
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"
|
#include "asm/format.hpp"
|
||||||
|
|
||||||
@@ -13,39 +13,44 @@
|
|||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
void FormatSpec::useCharacter(int c) {
|
void FormatSpec::useCharacter(int c) {
|
||||||
if (state == FORMAT_INVALID)
|
if (state == FORMAT_INVALID) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
// sign
|
// sign
|
||||||
case ' ':
|
case ' ':
|
||||||
case '+':
|
case '+':
|
||||||
if (state > FORMAT_SIGN)
|
if (state > FORMAT_SIGN) {
|
||||||
goto invalid;
|
break;
|
||||||
|
}
|
||||||
state = FORMAT_EXACT;
|
state = FORMAT_EXACT;
|
||||||
sign = c;
|
sign = c;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// exact
|
// exact
|
||||||
case '#':
|
case '#':
|
||||||
if (state > FORMAT_EXACT)
|
if (state > FORMAT_EXACT) {
|
||||||
goto invalid;
|
break;
|
||||||
|
}
|
||||||
state = FORMAT_ALIGN;
|
state = FORMAT_ALIGN;
|
||||||
exact = true;
|
exact = true;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// align
|
// align
|
||||||
case '-':
|
case '-':
|
||||||
if (state > FORMAT_ALIGN)
|
if (state > FORMAT_ALIGN) {
|
||||||
goto invalid;
|
break;
|
||||||
|
}
|
||||||
state = FORMAT_WIDTH;
|
state = FORMAT_WIDTH;
|
||||||
alignLeft = true;
|
alignLeft = true;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// pad, width, and prec values
|
// pad, width, and prec values
|
||||||
case '0':
|
case '0':
|
||||||
if (state < FORMAT_WIDTH)
|
if (state < FORMAT_WIDTH) {
|
||||||
padZero = true;
|
padZero = true;
|
||||||
|
}
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case '1':
|
case '1':
|
||||||
case '2':
|
case '2':
|
||||||
@@ -66,25 +71,27 @@ void FormatSpec::useCharacter(int c) {
|
|||||||
} else if (state == FORMAT_PREC) {
|
} else if (state == FORMAT_PREC) {
|
||||||
precision = precision * 10 + (c - '0');
|
precision = precision * 10 + (c - '0');
|
||||||
} else {
|
} else {
|
||||||
goto invalid;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// width
|
// width
|
||||||
case '.':
|
case '.':
|
||||||
if (state > FORMAT_WIDTH)
|
if (state > FORMAT_WIDTH) {
|
||||||
goto invalid;
|
break;
|
||||||
|
}
|
||||||
state = FORMAT_FRAC;
|
state = FORMAT_FRAC;
|
||||||
hasFrac = true;
|
hasFrac = true;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// prec
|
// prec
|
||||||
case 'q':
|
case 'q':
|
||||||
if (state > FORMAT_PREC)
|
if (state > FORMAT_PREC) {
|
||||||
goto invalid;
|
break;
|
||||||
|
}
|
||||||
state = FORMAT_PREC;
|
state = FORMAT_PREC;
|
||||||
hasPrec = true;
|
hasPrec = true;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// type
|
// type
|
||||||
case 'd':
|
case 'd':
|
||||||
@@ -95,23 +102,26 @@ void FormatSpec::useCharacter(int c) {
|
|||||||
case 'o':
|
case 'o':
|
||||||
case 'f':
|
case 'f':
|
||||||
case 's':
|
case 's':
|
||||||
if (state >= FORMAT_DONE)
|
if (state >= FORMAT_DONE) {
|
||||||
goto invalid;
|
break;
|
||||||
|
}
|
||||||
state = FORMAT_DONE;
|
state = FORMAT_DONE;
|
||||||
valid = true;
|
valid = true;
|
||||||
type = c;
|
type = c;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
invalid:
|
break;
|
||||||
state = FORMAT_INVALID;
|
|
||||||
valid = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = FORMAT_INVALID;
|
||||||
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FormatSpec::finishCharacters() {
|
void FormatSpec::finishCharacters() {
|
||||||
if (!isValid())
|
if (!isValid()) {
|
||||||
state = FORMAT_INVALID;
|
state = FORMAT_INVALID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string escapeString(std::string const &str) {
|
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';
|
useType = 's';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sign)
|
if (sign) {
|
||||||
error("Formatting string with sign flag '%c'\n", sign);
|
error("Formatting string with sign flag '%c'\n", sign);
|
||||||
if (padZero)
|
}
|
||||||
|
if (padZero) {
|
||||||
error("Formatting string with padding flag '0'\n");
|
error("Formatting string with padding flag '0'\n");
|
||||||
if (hasFrac)
|
}
|
||||||
|
if (hasFrac) {
|
||||||
error("Formatting string with fractional width\n");
|
error("Formatting string with fractional width\n");
|
||||||
if (hasPrec)
|
}
|
||||||
|
if (hasPrec) {
|
||||||
error("Formatting string with fractional precision\n");
|
error("Formatting string with fractional precision\n");
|
||||||
if (useType != 's')
|
}
|
||||||
|
if (useType != 's') {
|
||||||
error("Formatting string as type '%c'\n", useType);
|
error("Formatting string as type '%c'\n", useType);
|
||||||
|
}
|
||||||
|
|
||||||
std::string useValue = exact ? escapeString(value) : value;
|
std::string useValue = exact ? escapeString(value) : value;
|
||||||
size_t valueLen = useValue.length();
|
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'
|
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
|
||||||
&& useExact)
|
&& useExact) {
|
||||||
error("Formatting type '%c' with exact flag '#'\n", useType);
|
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);
|
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);
|
error("Formatting type '%c' with fractional precision\n", useType);
|
||||||
if (useType == 's')
|
}
|
||||||
|
if (useType == 's') {
|
||||||
error("Formatting number as type 's'\n");
|
error("Formatting number as type 's'\n");
|
||||||
|
}
|
||||||
|
|
||||||
char signChar = sign; // 0 or ' ' or '+'
|
char signChar = sign; // 0 or ' ' or '+'
|
||||||
|
|
||||||
if (useType == 'd' || useType == 'f') {
|
if (useType == 'd' || useType == 'f') {
|
||||||
if (int32_t v = value; v < 0) {
|
if (int32_t v = value; v < 0) {
|
||||||
signChar = '-';
|
signChar = '-';
|
||||||
if (v != INT32_MIN)
|
if (v != INT32_MIN) {
|
||||||
value = -v;
|
value = -v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,10 +270,11 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double fval = fabs(value / pow(2.0, usePrec));
|
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);
|
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
||||||
else
|
} else {
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
||||||
|
}
|
||||||
} else if (useType == 'd') {
|
} else if (useType == 'd') {
|
||||||
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
|
// 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
|
// 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);
|
str.reserve(str.length() + totalLen);
|
||||||
if (alignLeft) {
|
if (alignLeft) {
|
||||||
if (signChar)
|
if (signChar) {
|
||||||
str += signChar;
|
str += signChar;
|
||||||
if (prefixChar)
|
}
|
||||||
|
if (prefixChar) {
|
||||||
str += prefixChar;
|
str += prefixChar;
|
||||||
|
}
|
||||||
str.append(valueBuf);
|
str.append(valueBuf);
|
||||||
str.append(padLen, ' ');
|
str.append(padLen, ' ');
|
||||||
} else {
|
} else {
|
||||||
if (padZero) {
|
if (padZero) {
|
||||||
// sign, then prefix, then zero padding
|
// sign, then prefix, then zero padding
|
||||||
if (signChar)
|
if (signChar) {
|
||||||
str += signChar;
|
str += signChar;
|
||||||
if (prefixChar)
|
}
|
||||||
|
if (prefixChar) {
|
||||||
str += prefixChar;
|
str += prefixChar;
|
||||||
|
}
|
||||||
str.append(padLen, '0');
|
str.append(padLen, '0');
|
||||||
} else {
|
} else {
|
||||||
// space padding, then sign, then prefix
|
// space padding, then sign, then prefix
|
||||||
str.append(padLen, ' ');
|
str.append(padLen, ' ');
|
||||||
if (signChar)
|
if (signChar) {
|
||||||
str += signChar;
|
str += signChar;
|
||||||
if (prefixChar)
|
}
|
||||||
|
if (prefixChar) {
|
||||||
str += prefixChar;
|
str += prefixChar;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
str.append(valueBuf);
|
str.append(valueBuf);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/fstack.hpp"
|
#include "asm/fstack.hpp"
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -90,8 +90,9 @@ std::shared_ptr<std::string> fstk_GetUniqueIDStr() {
|
|||||||
std::shared_ptr<std::string> &str = contextStack.top().uniqueIDStr;
|
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 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++);
|
*str = "_u"s + std::to_string(nextUniqueID++);
|
||||||
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
@@ -103,27 +104,34 @@ MacroArgs *fstk_GetCurrentMacroArgs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void fstk_AddIncludePath(std::string const &path) {
|
void fstk_AddIncludePath(std::string const &path) {
|
||||||
if (path.empty())
|
if (path.empty()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::string &includePath = includePaths.emplace_back(path);
|
std::string &includePath = includePaths.emplace_back(path);
|
||||||
if (includePath.back() != '/')
|
if (includePath.back() != '/') {
|
||||||
includePath += '/';
|
includePath += '/';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_SetPreIncludeFile(std::string const &path) {
|
void fstk_SetPreIncludeFile(std::string const &path) {
|
||||||
if (!preIncludeName.empty())
|
if (!preIncludeName.empty()) {
|
||||||
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
||||||
|
}
|
||||||
preIncludeName = path;
|
preIncludeName = path;
|
||||||
if (verbose)
|
// LCOV_EXCL_START
|
||||||
|
if (verbose) {
|
||||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
||||||
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
static void printDep(std::string const &path) {
|
static void printDep(std::string const &path) {
|
||||||
if (dependFile) {
|
if (dependFile) {
|
||||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
||||||
if (generatePhonyDeps)
|
if (generatePhonyDeps) {
|
||||||
fprintf(dependFile, "%s:\n", path.c_str());
|
fprintf(dependFile, "%s:\n", path.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,20 +149,22 @@ std::optional<std::string> fstk_FindFile(std::string const &path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errno = ENOENT;
|
errno = ENOENT;
|
||||||
if (generatedMissingIncludes)
|
if (generatedMissingIncludes) {
|
||||||
printDep(path);
|
printDep(path);
|
||||||
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool yywrap() {
|
bool yywrap() {
|
||||||
uint32_t ifDepth = lexer_GetIFDepth();
|
uint32_t ifDepth = lexer_GetIFDepth();
|
||||||
|
|
||||||
if (ifDepth != 0)
|
if (ifDepth != 0) {
|
||||||
fatalerror(
|
fatalerror(
|
||||||
"Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
"Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||||
ifDepth,
|
ifDepth,
|
||||||
ifDepth == 1 ? "" : "s"
|
ifDepth == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) {
|
if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) {
|
||||||
// The context is a REPT or FOR block, which may loop
|
// 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);
|
Symbol *sym = sym_AddVar(context.forName, context.forValue);
|
||||||
|
|
||||||
// This error message will refer to the current iteration
|
// 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");
|
fatalerror("Failed to update FOR symbol value\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Advance to the next iteration
|
// Advance to the next iteration
|
||||||
fileInfoIters.front()++;
|
fileInfoIters.front()++;
|
||||||
@@ -199,8 +210,9 @@ bool yywrap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void checkRecursionDepth() {
|
static void checkRecursionDepth() {
|
||||||
if (contextStack.size() > maxRecursionDepth)
|
if (contextStack.size() > maxRecursionDepth) {
|
||||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
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;
|
std::shared_ptr<MacroArgs> macroArgs = nullptr;
|
||||||
|
|
||||||
auto fileInfo =
|
auto fileInfo =
|
||||||
std::make_shared<FileStackNode>(NODE_MACRO, filePath == "-" ? "<stdin>" : filePath);
|
std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath);
|
||||||
if (!contextStack.empty()) {
|
if (!contextStack.empty()) {
|
||||||
Context &oldContext = contextStack.top();
|
Context &oldContext = contextStack.top();
|
||||||
fileInfo->parent = oldContext.fileInfo;
|
fileInfo->parent = oldContext.fileInfo;
|
||||||
@@ -298,8 +310,11 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
|||||||
|
|
||||||
if (!fullPath) {
|
if (!fullPath) {
|
||||||
if (generatedMissingIncludes && !preInclude) {
|
if (generatedMissingIncludes && !preInclude) {
|
||||||
if (verbose)
|
// LCOV_EXCL_START
|
||||||
|
if (verbose) {
|
||||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
failedOnMissingInclude = true;
|
failedOnMissingInclude = true;
|
||||||
} else {
|
} else {
|
||||||
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newFileContext(*fullPath, false))
|
if (!newFileContext(*fullPath, false)) {
|
||||||
fatalerror("Failed to set up lexer for file include\n");
|
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) {
|
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs) {
|
||||||
Symbol *macro = sym_FindExactSymbol(macroName);
|
Symbol *macro = sym_FindExactSymbol(macroName);
|
||||||
|
|
||||||
if (!macro) {
|
if (!macro) {
|
||||||
if (sym_IsPurgedExact(macroName))
|
if (sym_IsPurgedExact(macroName)) {
|
||||||
error("Macro \"%s\" not defined; it was purged\n", macroName.c_str());
|
error("Macro \"%s\" not defined; it was purged\n", macroName.c_str());
|
||||||
else
|
} else {
|
||||||
error("Macro \"%s\" not defined\n", macroName.c_str());
|
error("Macro \"%s\" not defined\n", macroName.c_str());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (macro->type != SYM_MACRO) {
|
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) {
|
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span) {
|
||||||
if (count == 0)
|
if (count == 0) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
newReptContext(reptLineNo, span, count);
|
newReptContext(reptLineNo, span, count);
|
||||||
}
|
}
|
||||||
@@ -344,24 +362,28 @@ void fstk_RunFor(
|
|||||||
int32_t reptLineNo,
|
int32_t reptLineNo,
|
||||||
ContentSpan const &span
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
if (step > 0 && start < stop)
|
if (step > 0 && start < stop) {
|
||||||
count = (static_cast<int64_t>(stop) - start - 1) / step + 1;
|
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;
|
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");
|
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(
|
||||||
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
|
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (count == 0)
|
if (count == 0) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Context &context = newReptContext(reptLineNo, span, count);
|
Context &context = newReptContext(reptLineNo, span, count);
|
||||||
context.isForLoop = true;
|
context.isForLoop = true;
|
||||||
@@ -385,17 +407,20 @@ bool fstk_Break() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void fstk_NewRecursionDepth(size_t newDepth) {
|
void fstk_NewRecursionDepth(size_t newDepth) {
|
||||||
if (contextStack.size() > newDepth + 1)
|
if (contextStack.size() > newDepth + 1) {
|
||||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||||
|
}
|
||||||
maxRecursionDepth = newDepth;
|
maxRecursionDepth = newDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_Init(std::string const &mainPath, size_t maxDepth) {
|
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");
|
fatalerror("Failed to open main file\n");
|
||||||
|
}
|
||||||
|
|
||||||
maxRecursionDepth = maxDepth;
|
maxRecursionDepth = maxDepth;
|
||||||
|
|
||||||
if (!preIncludeName.empty())
|
if (!preIncludeName.empty()) {
|
||||||
fstk_RunInclude(preIncludeName, true);
|
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"
|
#include "asm/macro.hpp"
|
||||||
|
|
||||||
@@ -6,28 +6,32 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "helpers.hpp"
|
|
||||||
|
|
||||||
#include "asm/warning.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 {
|
int32_t realIndex = i + shift - 1;
|
||||||
uint32_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 {
|
std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||||
size_t nbArgs = args.size();
|
size_t nbArgs = args.size();
|
||||||
|
|
||||||
if (shift >= nbArgs)
|
if (shift >= nbArgs) {
|
||||||
return std::make_shared<std::string>("");
|
return std::make_shared<std::string>("");
|
||||||
|
}
|
||||||
|
|
||||||
size_t len = 0;
|
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
|
len += args[i]->length() + 1; // 1 for comma
|
||||||
|
}
|
||||||
|
|
||||||
auto str = std::make_shared<std::string>();
|
auto str = std::make_shared<std::string>();
|
||||||
str->reserve(len + 1); // 1 for comma
|
str->reserve(len + 1); // 1 for comma
|
||||||
@@ -38,18 +42,18 @@ std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
|||||||
str->append(*arg);
|
str->append(*arg);
|
||||||
|
|
||||||
// Commas go between args and after a last empty 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
|
str->push_back(','); // no space after comma
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
||||||
if (arg->empty())
|
if (arg->empty()) {
|
||||||
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
|
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);
|
args.push_back(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
148
src/asm/main.cpp
148
src/asm/main.cpp
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/main.hpp"
|
#include "asm/main.hpp"
|
||||||
|
|
||||||
@@ -37,30 +37,30 @@ static std::string make_escape(std::string &str) {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
// All dollars needs to be doubled
|
// All dollars needs to be doubled
|
||||||
size_t nextPos = str.find("$", pos);
|
size_t nextPos = str.find("$", pos);
|
||||||
if (nextPos == std::string::npos)
|
if (nextPos == std::string::npos) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
escaped.append(str, pos, nextPos - pos);
|
escaped.append(str, pos, nextPos - pos);
|
||||||
escaped.append("$$");
|
escaped.append("$$");
|
||||||
pos = nextPos + QUOTEDSTRLEN("$");
|
pos = nextPos + literal_strlen("$");
|
||||||
}
|
}
|
||||||
escaped.append(str, pos, str.length() - pos);
|
escaped.append(str, pos, str.length() - pos);
|
||||||
return escaped;
|
return escaped;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short options
|
// 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
|
// Variables for the long-only options
|
||||||
static int depType; // Variants of `-M`
|
static int depType; // Variants of `-M`
|
||||||
|
|
||||||
// Equivalent long options
|
// 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:
|
// Also, make sure long opts don't create ambiguity:
|
||||||
// A long opt's name should start with the same letter as its short opt,
|
// 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`).
|
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||||
// This is because long opt matching, even to a single char, is prioritized
|
// 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[] = {
|
static option const longopts[] = {
|
||||||
{"binary-digits", required_argument, nullptr, 'b'},
|
{"binary-digits", required_argument, nullptr, 'b'},
|
||||||
{"define", required_argument, nullptr, 'D'},
|
{"define", required_argument, nullptr, 'D'},
|
||||||
@@ -69,6 +69,7 @@ static option const longopts[] = {
|
|||||||
{"include", required_argument, nullptr, 'I'},
|
{"include", required_argument, nullptr, 'I'},
|
||||||
{"dependfile", required_argument, nullptr, 'M'},
|
{"dependfile", required_argument, nullptr, 'M'},
|
||||||
{"MG", no_argument, &depType, 'G'},
|
{"MG", no_argument, &depType, 'G'},
|
||||||
|
{"help", no_argument, nullptr, 'h'},
|
||||||
{"MP", no_argument, &depType, 'P'},
|
{"MP", no_argument, &depType, 'P'},
|
||||||
{"MT", required_argument, &depType, 'T'},
|
{"MT", required_argument, &depType, 'T'},
|
||||||
{"warning", required_argument, nullptr, 'W'},
|
{"warning", required_argument, nullptr, 'W'},
|
||||||
@@ -86,9 +87,10 @@ static option const longopts[] = {
|
|||||||
{nullptr, no_argument, nullptr, 0 }
|
{nullptr, no_argument, nullptr, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
static void printUsage() {
|
static void printUsage() {
|
||||||
fputs(
|
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"
|
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\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"
|
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
|
||||||
@@ -106,17 +108,20 @@ static void printUsage() {
|
|||||||
stderr
|
stderr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
time_t now = time(nullptr);
|
time_t now = time(nullptr);
|
||||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
// 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));
|
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
||||||
|
}
|
||||||
|
|
||||||
Defer closeDependFile{[&] {
|
Defer closeDependFile{[&] {
|
||||||
if (dependFile)
|
if (dependFile) {
|
||||||
fclose(dependFile);
|
fclose(dependFile);
|
||||||
|
}
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// Perform some init for below
|
// Perform some init for below
|
||||||
@@ -128,23 +133,25 @@ int main(int argc, char *argv[]) {
|
|||||||
opt_P(0);
|
opt_P(0);
|
||||||
opt_Q(16);
|
opt_Q(16);
|
||||||
sym_SetExportAll(false);
|
sym_SetExportAll(false);
|
||||||
uint32_t maxDepth = DEFAULT_MAX_DEPTH;
|
uint32_t maxDepth = 64;
|
||||||
char const *dependFileName = nullptr;
|
char const *dependFileName = nullptr;
|
||||||
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs;
|
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs;
|
||||||
std::string newTarget;
|
std::string newTarget;
|
||||||
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal.
|
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal.
|
||||||
if (isatty(STDERR_FILENO))
|
if (isatty(STDERR_FILENO)) {
|
||||||
maxErrors = 100;
|
maxErrors = 100;
|
||||||
|
}
|
||||||
|
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
char *endptr;
|
char *endptr;
|
||||||
|
|
||||||
case 'b':
|
case 'b':
|
||||||
if (strlen(musl_optarg) == 2)
|
if (strlen(musl_optarg) == 2) {
|
||||||
opt_B(musl_optarg);
|
opt_B(musl_optarg);
|
||||||
else
|
} else {
|
||||||
errx("Must specify exactly 2 characters for option 'b'");
|
errx("Must specify exactly 2 characters for option 'b'");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
char *equals;
|
char *equals;
|
||||||
@@ -163,19 +170,27 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'g':
|
case 'g':
|
||||||
if (strlen(musl_optarg) == 4)
|
if (strlen(musl_optarg) == 4) {
|
||||||
opt_G(musl_optarg);
|
opt_G(musl_optarg);
|
||||||
else
|
} else {
|
||||||
errx("Must specify exactly 4 characters for option 'g'");
|
errx("Must specify exactly 4 characters for option 'g'");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
printUsage();
|
||||||
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'I':
|
case 'I':
|
||||||
fstk_AddIncludePath(musl_optarg);
|
fstk_AddIncludePath(musl_optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
if (dependFile)
|
if (dependFile) {
|
||||||
warnx("Overriding dependfile %s", dependFileName);
|
warnx("Overriding dependfile %s", dependFileName);
|
||||||
|
}
|
||||||
if (strcmp("-", musl_optarg)) {
|
if (strcmp("-", musl_optarg)) {
|
||||||
dependFile = fopen(musl_optarg, "w");
|
dependFile = fopen(musl_optarg, "w");
|
||||||
dependFileName = musl_optarg;
|
dependFileName = musl_optarg;
|
||||||
@@ -183,8 +198,9 @@ int main(int argc, char *argv[]) {
|
|||||||
dependFile = stdout;
|
dependFile = stdout;
|
||||||
dependFileName = "<stdout>";
|
dependFileName = "<stdout>";
|
||||||
}
|
}
|
||||||
if (dependFile == nullptr)
|
if (dependFile == nullptr) {
|
||||||
err("Failed to open dependfile \"%s\"", dependFileName);
|
err("Failed to open dependfile \"%s\"", dependFileName); // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'o':
|
case 'o':
|
||||||
@@ -199,11 +215,13 @@ int main(int argc, char *argv[]) {
|
|||||||
case 'p':
|
case 'p':
|
||||||
padByte = strtoul(musl_optarg, &endptr, 0);
|
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'");
|
errx("Invalid argument for option 'p'");
|
||||||
|
}
|
||||||
|
|
||||||
if (padByte > 0xFF)
|
if (padByte > 0xFF) {
|
||||||
errx("Argument for option 'p' must be between 0 and 0xFF");
|
errx("Argument for option 'p' must be between 0 and 0xFF");
|
||||||
|
}
|
||||||
|
|
||||||
opt_P(padByte);
|
opt_P(padByte);
|
||||||
break;
|
break;
|
||||||
@@ -212,15 +230,18 @@ int main(int argc, char *argv[]) {
|
|||||||
char const *precisionArg;
|
char const *precisionArg;
|
||||||
case 'Q':
|
case 'Q':
|
||||||
precisionArg = musl_optarg;
|
precisionArg = musl_optarg;
|
||||||
if (precisionArg[0] == '.')
|
if (precisionArg[0] == '.') {
|
||||||
precisionArg++;
|
precisionArg++;
|
||||||
|
}
|
||||||
precision = strtoul(precisionArg, &endptr, 0);
|
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'");
|
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");
|
errx("Argument for option 'Q' must be between 1 and 31");
|
||||||
|
}
|
||||||
|
|
||||||
opt_Q(precision);
|
opt_Q(precision);
|
||||||
break;
|
break;
|
||||||
@@ -228,35 +249,41 @@ int main(int argc, char *argv[]) {
|
|||||||
case 'r':
|
case 'r':
|
||||||
maxDepth = strtoul(musl_optarg, &endptr, 0);
|
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'");
|
errx("Invalid argument for option 'r'");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 's': {
|
case 's': {
|
||||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
||||||
char *name = strchr(musl_optarg, ':');
|
char *name = strchr(musl_optarg, ':');
|
||||||
if (!name)
|
if (!name) {
|
||||||
errx("Invalid argument for option 's'");
|
errx("Invalid argument for option 's'");
|
||||||
|
}
|
||||||
*name++ = '\0';
|
*name++ = '\0';
|
||||||
|
|
||||||
std::vector<StateFeature> features;
|
std::vector<StateFeature> features;
|
||||||
for (char *feature = musl_optarg; feature;) {
|
for (char *feature = musl_optarg; feature;) {
|
||||||
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
|
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
|
||||||
char *next = strchr(feature, ',');
|
char *next = strchr(feature, ',');
|
||||||
if (next)
|
if (next) {
|
||||||
*next++ = '\0';
|
*next++ = '\0';
|
||||||
|
}
|
||||||
// Trim whitespace from the beginning of `feature`...
|
// Trim whitespace from the beginning of `feature`...
|
||||||
feature += strspn(feature, " \t");
|
feature += strspn(feature, " \t");
|
||||||
// ...and from the end
|
// ...and from the end
|
||||||
if (char *end = strpbrk(feature, " \t"); end)
|
if (char *end = strpbrk(feature, " \t"); end) {
|
||||||
*end = '\0';
|
*end = '\0';
|
||||||
|
}
|
||||||
// A feature must be specified
|
// A feature must be specified
|
||||||
if (*feature == '\0')
|
if (*feature == '\0') {
|
||||||
errx("Empty feature for option 's'");
|
errx("Empty feature for option 's'");
|
||||||
|
}
|
||||||
// Parse the `feature` and update the `features` list
|
// Parse the `feature` and update the `features` list
|
||||||
if (!strcasecmp(feature, "all")) {
|
if (!strcasecmp(feature, "all")) {
|
||||||
if (!features.empty())
|
if (!features.empty()) {
|
||||||
warnx("Redundant feature before \"%s\" for option 's'", feature);
|
warnx("Redundant feature before \"%s\" for option 's'", feature);
|
||||||
|
}
|
||||||
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
|
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
|
||||||
} else {
|
} else {
|
||||||
StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU
|
StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU
|
||||||
@@ -276,10 +303,14 @@ int main(int argc, char *argv[]) {
|
|||||||
feature = next;
|
feature = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateFileSpecs.find(name) != stateFileSpecs.end())
|
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
||||||
warnx("Overriding state filename %s", name);
|
warnx("Overriding state filename %s", name);
|
||||||
if (verbose)
|
}
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
if (verbose) {
|
||||||
printf("State filename %s\n", name);
|
printf("State filename %s\n", name);
|
||||||
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
stateFileSpecs.emplace(name, std::move(features));
|
stateFileSpecs.emplace(name, std::move(features));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -289,8 +320,10 @@ int main(int argc, char *argv[]) {
|
|||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
|
// LCOV_EXCL_START
|
||||||
verbose = true;
|
verbose = true;
|
||||||
break;
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
opt_W(musl_optarg);
|
opt_W(musl_optarg);
|
||||||
@@ -304,11 +337,13 @@ int main(int argc, char *argv[]) {
|
|||||||
case 'X':
|
case 'X':
|
||||||
maxValue = strtoul(musl_optarg, &endptr, 0);
|
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'");
|
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);
|
errx("Argument for option 'X' must be between 0 and %u", UINT_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
maxErrors = maxValue;
|
maxErrors = maxValue;
|
||||||
break;
|
break;
|
||||||
@@ -327,10 +362,12 @@ int main(int argc, char *argv[]) {
|
|||||||
case 'Q':
|
case 'Q':
|
||||||
case 'T':
|
case 'T':
|
||||||
newTarget = musl_optarg;
|
newTarget = musl_optarg;
|
||||||
if (depType == 'Q')
|
if (depType == 'Q') {
|
||||||
newTarget = make_escape(newTarget);
|
newTarget = make_escape(newTarget);
|
||||||
if (!targetFileName.empty())
|
}
|
||||||
|
if (!targetFileName.empty()) {
|
||||||
targetFileName += ' ';
|
targetFileName += ' ';
|
||||||
|
}
|
||||||
targetFileName += newTarget;
|
targetFileName += newTarget;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -338,13 +375,16 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
// Unrecognized options
|
// Unrecognized options
|
||||||
default:
|
default:
|
||||||
|
// LCOV_EXCL_START
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(1);
|
exit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetFileName.empty() && !objectFileName.empty())
|
if (targetFileName.empty() && !objectFileName.empty()) {
|
||||||
targetFileName = objectFileName;
|
targetFileName = objectFileName;
|
||||||
|
}
|
||||||
|
|
||||||
if (argc == musl_optind) {
|
if (argc == musl_optind) {
|
||||||
fputs(
|
fputs(
|
||||||
@@ -360,13 +400,15 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
std::string mainFileName = argv[musl_optind];
|
std::string mainFileName = argv[musl_optind];
|
||||||
|
|
||||||
if (verbose)
|
if (verbose) {
|
||||||
printf("Assembling %s\n", mainFileName.c_str());
|
printf("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
|
||||||
if (dependFile) {
|
if (dependFile) {
|
||||||
if (targetFileName.empty())
|
if (targetFileName.empty()) {
|
||||||
errx("Dependency files can only be created if a target file is specified with either "
|
errx("Dependency files can only be created if a target file is specified with either "
|
||||||
"-o, -MQ or -MT");
|
"-o, -MQ or -MT");
|
||||||
|
}
|
||||||
|
|
||||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), mainFileName.c_str());
|
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);
|
fstk_Init(mainFileName, maxDepth);
|
||||||
|
|
||||||
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
// 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;
|
nbErrors = 1;
|
||||||
|
}
|
||||||
|
|
||||||
sect_CheckUnionClosed();
|
if (!failedOnMissingInclude) {
|
||||||
sect_CheckLoadClosed();
|
sect_CheckUnionClosed();
|
||||||
sect_CheckSizes();
|
sect_CheckLoadClosed();
|
||||||
|
sect_CheckSizes();
|
||||||
|
|
||||||
charmap_CheckStack();
|
charmap_CheckStack();
|
||||||
opt_CheckStack();
|
opt_CheckStack();
|
||||||
sect_CheckStack();
|
sect_CheckStack();
|
||||||
|
}
|
||||||
|
|
||||||
if (nbErrors != 0)
|
if (nbErrors != 0) {
|
||||||
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");
|
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 parse aborted due to missing an include, and `-MG` was given, exit normally
|
||||||
if (failedOnMissingInclude)
|
if (failedOnMissingInclude) {
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
out_WriteObject();
|
out_WriteObject();
|
||||||
|
|
||||||
for (auto [name, features] : stateFileSpecs)
|
for (auto [name, features] : stateFileSpecs) {
|
||||||
out_WriteState(name, features);
|
out_WriteState(name, features);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -53,17 +53,19 @@ void opt_W(char const *flag) {
|
|||||||
void opt_Parse(char const *s) {
|
void opt_Parse(char const *s) {
|
||||||
switch (s[0]) {
|
switch (s[0]) {
|
||||||
case 'b':
|
case 'b':
|
||||||
if (strlen(&s[1]) == 2)
|
if (strlen(&s[1]) == 2) {
|
||||||
opt_B(&s[1]);
|
opt_B(&s[1]);
|
||||||
else
|
} else {
|
||||||
error("Must specify exactly 2 characters for option 'b'\n");
|
error("Must specify exactly 2 characters for option 'b'\n");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'g':
|
case 'g':
|
||||||
if (strlen(&s[1]) == 4)
|
if (strlen(&s[1]) == 4) {
|
||||||
opt_G(&s[1]);
|
opt_G(&s[1]);
|
||||||
else
|
} else {
|
||||||
error("Must specify exactly 4 characters for option 'g'\n");
|
error("Must specify exactly 4 characters for option 'g'\n");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
@@ -72,12 +74,13 @@ void opt_Parse(char const *s) {
|
|||||||
unsigned int padByte;
|
unsigned int padByte;
|
||||||
|
|
||||||
result = sscanf(&s[1], "%x", &padByte);
|
result = sscanf(&s[1], "%x", &padByte);
|
||||||
if (result != 1)
|
if (result != 1) {
|
||||||
error("Invalid argument for option 'p'\n");
|
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");
|
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
||||||
else
|
} else {
|
||||||
opt_P(padByte);
|
opt_P(padByte);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
error("Invalid argument for option 'p'\n");
|
error("Invalid argument for option 'p'\n");
|
||||||
}
|
}
|
||||||
@@ -86,19 +89,21 @@ void opt_Parse(char const *s) {
|
|||||||
char const *precisionArg;
|
char const *precisionArg;
|
||||||
case 'Q':
|
case 'Q':
|
||||||
precisionArg = &s[1];
|
precisionArg = &s[1];
|
||||||
if (precisionArg[0] == '.')
|
if (precisionArg[0] == '.') {
|
||||||
precisionArg++;
|
precisionArg++;
|
||||||
|
}
|
||||||
if (strlen(precisionArg) <= 2) {
|
if (strlen(precisionArg) <= 2) {
|
||||||
int result;
|
int result;
|
||||||
unsigned int precision;
|
unsigned int precision;
|
||||||
|
|
||||||
result = sscanf(precisionArg, "%u", &precision);
|
result = sscanf(precisionArg, "%u", &precision);
|
||||||
if (result != 1)
|
if (result != 1) {
|
||||||
error("Invalid argument for option 'Q'\n");
|
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");
|
error("Argument for option 'Q' must be between 1 and 31\n");
|
||||||
else
|
} else {
|
||||||
opt_Q(precision);
|
opt_Q(precision);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
error("Invalid argument for option 'Q'\n");
|
error("Invalid argument for option 'Q'\n");
|
||||||
}
|
}
|
||||||
@@ -106,8 +111,9 @@ void opt_Parse(char const *s) {
|
|||||||
|
|
||||||
case 'r': {
|
case 'r': {
|
||||||
++s; // Skip 'r'
|
++s; // Skip 'r'
|
||||||
while (isblank(*s))
|
while (isblank(*s)) {
|
||||||
++s; // Skip leading whitespace
|
++s; // Skip leading whitespace
|
||||||
|
}
|
||||||
|
|
||||||
if (s[0] == '\0') {
|
if (s[0] == '\0') {
|
||||||
error("Missing argument to option 'r'\n");
|
error("Missing argument to option 'r'\n");
|
||||||
@@ -128,10 +134,11 @@ void opt_Parse(char const *s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
if (strlen(&s[1]) > 0)
|
if (strlen(&s[1]) > 0) {
|
||||||
opt_W(&s[1]);
|
opt_W(&s[1]);
|
||||||
else
|
} else {
|
||||||
error("Must specify an argument for option 'W'\n");
|
error("Must specify an argument for option 'W'\n");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/output.hpp"
|
#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) {
|
static uint32_t getSectIDIfAny(Section *sect) {
|
||||||
if (!sect)
|
if (!sect) {
|
||||||
return UINT32_MAX;
|
return UINT32_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end())
|
if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) {
|
||||||
return static_cast<uint32_t>(sectionMap.size() - search->second - 1);
|
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) {
|
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);
|
fwrite(sect.data.data(), 1, sect.size, file);
|
||||||
putLong(sect.patches.size(), file);
|
putLong(sect.patches.size(), file);
|
||||||
|
|
||||||
for (Patch const &patch : sect.patches)
|
for (Patch const &patch : sect.patches) {
|
||||||
writePatch(patch, file);
|
writePatch(patch, file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,8 +166,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
|||||||
symName.clear();
|
symName.clear();
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint8_t c = rpn[offset++];
|
uint8_t c = rpn[offset++];
|
||||||
if (c == 0)
|
if (c == 0) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
symName += c;
|
symName += c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +176,7 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
|||||||
sym = sym_FindExactSymbol(symName);
|
sym = sym_FindExactSymbol(symName);
|
||||||
if (sym->isConstant()) {
|
if (sym->isConstant()) {
|
||||||
rpnexpr[rpnptr++] = RPN_CONST;
|
rpnexpr[rpnptr++] = RPN_CONST;
|
||||||
value = sym_GetConstantValue(symName);
|
value = sym->getConstantValue();
|
||||||
} else {
|
} else {
|
||||||
rpnexpr[rpnptr++] = RPN_SYM;
|
rpnexpr[rpnptr++] = RPN_SYM;
|
||||||
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
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();
|
symName.clear();
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint8_t c = rpn[offset++];
|
uint8_t c = rpn[offset++];
|
||||||
if (c == 0)
|
if (c == 0) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
symName += c;
|
symName += c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +288,7 @@ void out_CreateAssert(
|
|||||||
assertion.message = message;
|
assertion.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeAssert(Assertion &assert, FILE *file) {
|
static void writeAssert(Assertion const &assert, FILE *file) {
|
||||||
writePatch(assert.patch, file);
|
writePatch(assert.patch, file);
|
||||||
putString(assert.message, file);
|
putString(assert.message, file);
|
||||||
}
|
}
|
||||||
@@ -298,14 +304,16 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
|||||||
|
|
||||||
putLong(nodeIters.size(), file);
|
putLong(nodeIters.size(), file);
|
||||||
// Iters are stored by decreasing depth, so reverse the order for output
|
// Iters are stored by decreasing depth, so reverse the order for output
|
||||||
for (uint32_t i = nodeIters.size(); i--;)
|
for (uint32_t i = nodeIters.size(); i--;) {
|
||||||
putLong(nodeIters[i], file);
|
putLong(nodeIters[i], file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void out_WriteObject() {
|
void out_WriteObject() {
|
||||||
if (objectFileName.empty())
|
if (objectFileName.empty()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FILE *file;
|
FILE *file;
|
||||||
if (objectFileName != "-") {
|
if (objectFileName != "-") {
|
||||||
@@ -315,14 +323,15 @@ void out_WriteObject() {
|
|||||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
file = stdout;
|
file = stdout;
|
||||||
}
|
}
|
||||||
if (!file)
|
if (!file) {
|
||||||
err("Failed to open object file '%s'", objectFileName.c_str());
|
err("Failed to open object file '%s'", objectFileName.c_str()); // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
// Also write symbols that weren't written above
|
// Also write symbols that weren't written above
|
||||||
sym_ForEach(registerUnregisteredSymbol);
|
sym_ForEach(registerUnregisteredSymbol);
|
||||||
|
|
||||||
fprintf(file, RGBDS_OBJECT_VERSION_STRING);
|
fputs(RGBDS_OBJECT_VERSION_STRING, file);
|
||||||
putLong(RGBDS_OBJECT_REV, file);
|
putLong(RGBDS_OBJECT_REV, file);
|
||||||
|
|
||||||
putLong(objectSymbols.size(), file);
|
putLong(objectSymbols.size(), file);
|
||||||
@@ -335,33 +344,34 @@ void out_WriteObject() {
|
|||||||
writeFileStackNode(node, file);
|
writeFileStackNode(node, file);
|
||||||
|
|
||||||
// The list is supposed to have decrementing IDs
|
// The list is supposed to have decrementing IDs
|
||||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1)
|
assume(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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Symbol const *sym : objectSymbols)
|
for (Symbol const *sym : objectSymbols) {
|
||||||
writeSymbol(*sym, file);
|
writeSymbol(*sym, file);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto it = sectionList.rbegin(); it != sectionList.rend(); it++)
|
for (Section const § : sectionList) {
|
||||||
writeSection(*it, file);
|
writeSection(sect, file);
|
||||||
|
}
|
||||||
|
|
||||||
putLong(assertions.size(), file);
|
putLong(assertions.size(), file);
|
||||||
|
|
||||||
for (Assertion &assert : assertions)
|
for (Assertion const &assert : assertions) {
|
||||||
writeAssert(assert, file);
|
writeAssert(assert, file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void out_SetFileName(std::string const &name) {
|
void out_SetFileName(std::string const &name) {
|
||||||
if (!objectFileName.empty())
|
if (!objectFileName.empty()) {
|
||||||
warnx("Overriding output filename %s", objectFileName.c_str());
|
warnx("Overriding output filename %s", objectFileName.c_str());
|
||||||
|
}
|
||||||
objectFileName = name;
|
objectFileName = name;
|
||||||
if (verbose)
|
// LCOV_EXCL_START
|
||||||
|
if (verbose) {
|
||||||
printf("Output filename %s\n", objectFileName.c_str());
|
printf("Output filename %s\n", objectFileName.c_str());
|
||||||
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dumpString(std::string const &escape, FILE *file) {
|
static void dumpString(std::string const &escape, FILE *file) {
|
||||||
@@ -397,8 +407,9 @@ static bool dumpEquConstants(FILE *file) {
|
|||||||
equConstants.clear();
|
equConstants.clear();
|
||||||
|
|
||||||
sym_ForEach([](Symbol &sym) {
|
sym_ForEach([](Symbol &sym) {
|
||||||
if (!sym.isBuiltin && sym.type == SYM_EQU)
|
if (!sym.isBuiltin && sym.type == SYM_EQU) {
|
||||||
equConstants.push_back(&sym);
|
equConstants.push_back(&sym);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Constants are ordered by file, then by definition order
|
// Constants are ordered by file, then by definition order
|
||||||
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||||
@@ -418,8 +429,9 @@ static bool dumpVariables(FILE *file) {
|
|||||||
variables.clear();
|
variables.clear();
|
||||||
|
|
||||||
sym_ForEach([](Symbol &sym) {
|
sym_ForEach([](Symbol &sym) {
|
||||||
if (!sym.isBuiltin && sym.type == SYM_VAR)
|
if (!sym.isBuiltin && sym.type == SYM_VAR) {
|
||||||
variables.push_back(&sym);
|
variables.push_back(&sym);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Variables are ordered by file, then by definition order
|
// Variables are ordered by file, then by definition order
|
||||||
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
|
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||||
@@ -439,8 +451,9 @@ static bool dumpEqusConstants(FILE *file) {
|
|||||||
equsConstants.clear();
|
equsConstants.clear();
|
||||||
|
|
||||||
sym_ForEach([](Symbol &sym) {
|
sym_ForEach([](Symbol &sym) {
|
||||||
if (!sym.isBuiltin && sym.type == SYM_EQUS)
|
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
|
||||||
equsConstants.push_back(&sym);
|
equsConstants.push_back(&sym);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Constants are ordered by file, then by definition order
|
// Constants are ordered by file, then by definition order
|
||||||
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||||
@@ -467,8 +480,9 @@ static bool dumpCharmaps(FILE *file) {
|
|||||||
fputs("charmap \"", charmapFile);
|
fputs("charmap \"", charmapFile);
|
||||||
dumpString(mapping, charmapFile);
|
dumpString(mapping, charmapFile);
|
||||||
putc('"', charmapFile);
|
putc('"', charmapFile);
|
||||||
for (int32_t v : value)
|
for (int32_t v : value) {
|
||||||
fprintf(charmapFile, ", $%" PRIx32, v);
|
fprintf(charmapFile, ", $%" PRIx32, v);
|
||||||
|
}
|
||||||
putc('\n', charmapFile);
|
putc('\n', charmapFile);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -479,8 +493,9 @@ static bool dumpMacros(FILE *file) {
|
|||||||
macros.clear();
|
macros.clear();
|
||||||
|
|
||||||
sym_ForEach([](Symbol &sym) {
|
sym_ForEach([](Symbol &sym) {
|
||||||
if (!sym.isBuiltin && sym.type == SYM_MACRO)
|
if (!sym.isBuiltin && sym.type == SYM_MACRO) {
|
||||||
macros.push_back(&sym);
|
macros.push_back(&sym);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Macros are ordered by file, then by definition order
|
// Macros are ordered by file, then by definition order
|
||||||
std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool {
|
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);
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
file = stdout;
|
file = stdout;
|
||||||
}
|
}
|
||||||
if (!file)
|
if (!file) {
|
||||||
err("Failed to open state file '%s'", name.c_str());
|
err("Failed to open state file '%s'", name.c_str()); // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
static char const *dumpHeadings[NB_STATE_FEATURES] = {
|
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);
|
fputs("; File generated by rgbasm\n", file);
|
||||||
for (StateFeature feature : features) {
|
for (StateFeature feature : features) {
|
||||||
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
||||||
if (!dumpFuncs[feature](file))
|
if (!dumpFuncs[feature](file)) {
|
||||||
fprintf(file, "; No values\n");
|
fprintf(file, "; No values\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1228
src/asm/parser.y
1228
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"
|
#include "asm/rpn.hpp"
|
||||||
|
|
||||||
@@ -46,8 +46,9 @@ int32_t Expression::getConstVal() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Symbol const *Expression::symbolOf() const {
|
Symbol const *Expression::symbolOf() const {
|
||||||
if (!isSymbol)
|
if (!isSymbol) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
return sym_FindScopedSymbol(reinterpret_cast<char const *>(&rpn[1]));
|
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
|
// Check if both expressions only refer to a single symbol
|
||||||
Symbol const *sym1 = symbolOf();
|
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;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Section const *sect1 = sym1->getSection();
|
Section const *sect1 = sym1->getSection();
|
||||||
Section const *sect2 = sym->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()) {
|
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||||
error("PC has no value outside of a section\n");
|
error("PC has no value outside of a section\n");
|
||||||
data = 0;
|
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()) {
|
} else if (!sym || !sym->isConstant()) {
|
||||||
isSymbol = true;
|
isSymbol = true;
|
||||||
|
|
||||||
data = sym_IsPC(sym) ? "PC is not constant at assembly time"
|
data = sym_IsPC(sym) ? "PC is not constant at assembly time"
|
||||||
: sym_IsPurgedScoped(symName)
|
: sym_IsPurgedScoped(symName)
|
||||||
? "'"s + symName + "' is not constant at assembly time; it was purged"
|
? "'"s + symName + "' is not constant at assembly time; it was purged"
|
||||||
: "'"s + symName + "' is not constant at assembly time";
|
: "'"s + symName + "' is not constant at assembly time";
|
||||||
sym = sym_Ref(symName);
|
sym = sym_Ref(symName);
|
||||||
|
|
||||||
size_t nameLen = sym->name.length() + 1; // Don't forget NUL!
|
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;
|
*ptr++ = RPN_SYM;
|
||||||
memcpy(ptr, sym->name.c_str(), nameLen);
|
memcpy(ptr, sym->name.c_str(), nameLen);
|
||||||
} else {
|
} 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);
|
data = static_cast<int32_t>(sym->getSection()->bank);
|
||||||
} else {
|
} else {
|
||||||
data = sym_IsPurgedScoped(symName)
|
data = sym_IsPurgedScoped(symName)
|
||||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||||
: "\""s + symName + "\"'s bank is not known";
|
: "\""s + symName + "\"'s bank is not known";
|
||||||
|
|
||||||
size_t nameLen = sym->name.length() + 1; // Room for NUL!
|
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) {
|
static bool tryConstLogNot(Expression const &expr) {
|
||||||
Symbol const *sym = expr.symbolOf();
|
Symbol const *sym = expr.symbolOf();
|
||||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
assume(sym->isNumeric());
|
assume(sym->isNumeric());
|
||||||
|
|
||||||
@@ -225,23 +231,21 @@ static bool tryConstLogNot(Expression const &expr) {
|
|||||||
return knownBits != 0;
|
return knownBits != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Returns a constant LOW() from non-constant argument, or -1 if it cannot be computed.
|
||||||
* 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.
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static int32_t tryConstLow(Expression const &expr) {
|
static int32_t tryConstLow(Expression const &expr) {
|
||||||
Symbol const *sym = expr.symbolOf();
|
Symbol const *sym = expr.symbolOf();
|
||||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
assume(sym->isNumeric());
|
assume(sym->isNumeric());
|
||||||
|
|
||||||
// The low byte must not cover any unknown bits
|
// The low byte must not cover any unknown bits
|
||||||
Section const § = *sym->getSection();
|
Section const § = *sym->getSection();
|
||||||
if (sect.align < 8)
|
if (sect.align < 8) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
// `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)
|
// 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;
|
return (symbolOfs + sect.alignOfs) & 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Returns a constant binary AND with one non-constant operand, or -1 if it cannot be computed.
|
||||||
* 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
|
||||||
* 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.
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||||
Symbol const *lhsSymbol = lhs.symbolOf();
|
Symbol const *lhsSymbol = lhs.symbolOf();
|
||||||
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
|
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
|
||||||
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
|
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
|
||||||
bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection();
|
bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection();
|
||||||
|
|
||||||
if (!lhsIsSymbol && !rhsIsSymbol)
|
if (!lhsIsSymbol && !rhsIsSymbol) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// If the lhs isn't a symbol, try again the other way around
|
// If the lhs isn't a symbol, try again the other way around
|
||||||
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
|
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
|
||||||
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
|
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
|
||||||
|
|
||||||
if (!sym.isDefined() || !expr.isKnown())
|
if (!sym.isDefined() || !expr.isKnown()) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
assume(sym.isNumeric());
|
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
|
// The mask must not cover any unknown bits
|
||||||
Section const § = *sym.getSection();
|
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;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// `sym.getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
// `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)
|
// 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:
|
case RPN_TZCOUNT:
|
||||||
data = val != 0 ? ctz(uval) : 32;
|
data = val != 0 ? ctz(uval) : 32;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
case RPN_LOGOR:
|
// `makeUnaryOp` should never be called with a non-unary operator!
|
||||||
case RPN_LOGAND:
|
// LCOV_EXCL_START
|
||||||
case RPN_LOGEQ:
|
unreachable_();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (op == RPN_LOGNOT && tryConstLogNot(src)) {
|
} else if (op == RPN_LOGNOT && tryConstLogNot(src)) {
|
||||||
data = 0;
|
data = 0;
|
||||||
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) {
|
} 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;
|
data = lval & rval;
|
||||||
break;
|
break;
|
||||||
case RPN_SHL:
|
case RPN_SHL:
|
||||||
if (rval < 0)
|
if (rval < 0) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval
|
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);
|
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", rval);
|
||||||
|
}
|
||||||
|
|
||||||
data = op_shift_left(lval, rval);
|
data = op_shift_left(lval, rval);
|
||||||
break;
|
break;
|
||||||
case RPN_SHR:
|
case RPN_SHR:
|
||||||
if (lval < 0)
|
if (lval < 0) {
|
||||||
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval);
|
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval);
|
||||||
|
}
|
||||||
|
|
||||||
if (rval < 0)
|
if (rval < 0) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
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);
|
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||||
|
}
|
||||||
|
|
||||||
data = op_shift_right(lval, rval);
|
data = op_shift_right(lval, rval);
|
||||||
break;
|
break;
|
||||||
case RPN_USHR:
|
case RPN_USHR:
|
||||||
if (rval < 0)
|
if (rval < 0) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
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);
|
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||||
|
}
|
||||||
|
|
||||||
data = op_shift_right_unsigned(lval, rval);
|
data = op_shift_right_unsigned(lval, rval);
|
||||||
break;
|
break;
|
||||||
@@ -457,8 +439,9 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
data = static_cast<int32_t>(ulval * urval);
|
data = static_cast<int32_t>(ulval * urval);
|
||||||
break;
|
break;
|
||||||
case RPN_DIV:
|
case RPN_DIV:
|
||||||
if (rval == 0)
|
if (rval == 0) {
|
||||||
fatalerror("Division by zero\n");
|
fatalerror("Division by zero\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (lval == INT32_MIN && rval == -1) {
|
if (lval == INT32_MIN && rval == -1) {
|
||||||
warning(
|
warning(
|
||||||
@@ -473,41 +456,29 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case RPN_MOD:
|
case RPN_MOD:
|
||||||
if (rval == 0)
|
if (rval == 0) {
|
||||||
fatalerror("Modulo by zero\n");
|
fatalerror("Modulo by zero\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (lval == INT32_MIN && rval == -1)
|
if (lval == INT32_MIN && rval == -1) {
|
||||||
data = 0;
|
data = 0;
|
||||||
else
|
} else {
|
||||||
data = op_modulo(lval, rval);
|
data = op_modulo(lval, rval);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case RPN_EXP:
|
case RPN_EXP:
|
||||||
if (rval < 0)
|
if (rval < 0) {
|
||||||
fatalerror("Exponentiation by negative power\n");
|
fatalerror("Exponentiation by negative power\n");
|
||||||
|
}
|
||||||
|
|
||||||
data = op_exponent(lval, rval);
|
data = op_exponent(lval, rval);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
case RPN_NEG:
|
// `makeBinaryOp` should never be called with a non-binary operator!
|
||||||
case RPN_NOT:
|
// LCOV_EXCL_START
|
||||||
case RPN_LOGNOT:
|
unreachable_();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (op == RPN_SUB && src1.isDiffConstant(src2.symbolOf())) {
|
} else if (op == RPN_SUB && src1.isDiffConstant(src2.symbolOf())) {
|
||||||
data = src1.symbolOf()->getValue() - src2.symbolOf()->getValue();
|
data = src1.symbolOf()->getValue() - src2.symbolOf()->getValue();
|
||||||
} else if ((op == RPN_LOGAND || op == RPN_AND) && tryConstZero(src1, src2)) {
|
} 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
|
// Copy the right RPN and append the operator
|
||||||
uint32_t rightRpnSize = src2.rpn.size();
|
uint32_t rightRpnSize = src2.rpn.size();
|
||||||
uint8_t *ptr = reserveSpace(rightRpnSize + 1, src2.rpnPatchSize + 1);
|
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
|
// If `rightRpnSize == 0`, then `memcpy(ptr, nullptr, rightRpnSize)` would be UB
|
||||||
memcpy(ptr, src2.rpn.data(), rightRpnSize);
|
memcpy(ptr, src2.rpn.data(), rightRpnSize);
|
||||||
|
}
|
||||||
ptr[rightRpnSize] = op;
|
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)
|
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||||
void Expression::checkNBit(uint8_t n) const {
|
void Expression::checkNBit(uint8_t n) const {
|
||||||
if (isKnown())
|
if (isKnown()) {
|
||||||
::checkNBit(value(), n, "Expression");
|
::checkNBit(value(), n, nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
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
|
assume(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
||||||
|
|
||||||
if (v < -(1 << n) || v >= 1 << n) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (v < -(1 << (n - 1))) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/section.hpp"
|
#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)
|
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
|
||||||
|
|
||||||
// A quick check to see if we have an initialized section
|
// A quick check to see if we have an initialized section
|
||||||
[[nodiscard]] static bool requireSection() {
|
[[nodiscard]]
|
||||||
if (currentSection)
|
static bool requireSection() {
|
||||||
|
if (currentSection) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
error("Cannot output data outside of a SECTION\n");
|
error("Cannot output data outside of a SECTION\n");
|
||||||
return false;
|
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
|
// A quick check to see if we have an initialized section that can contain
|
||||||
// this much initialized data
|
// this much initialized data
|
||||||
[[nodiscard]] static bool requireCodeSection() {
|
[[nodiscard]]
|
||||||
if (!requireSection())
|
static bool requireCodeSection() {
|
||||||
|
if (!requireSection()) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (sect_HasData(currentSection->type))
|
if (sect_HasData(currentSection->type)) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
error(
|
error(
|
||||||
"Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
"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() {
|
void sect_CheckSizes() {
|
||||||
for (Section const § : sectionList) {
|
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(
|
error(
|
||||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
||||||
").\n",
|
")\n",
|
||||||
sect.name.c_str(),
|
sect.name.c_str(),
|
||||||
maxSize,
|
maxSize,
|
||||||
sect.size
|
sect.size
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,33 +112,36 @@ static unsigned int mergeSectUnion(
|
|||||||
|
|
||||||
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
||||||
// combination of both.
|
// combination of both.
|
||||||
if (sect_HasData(type))
|
if (sect_HasData(type)) {
|
||||||
sectError("Cannot declare ROM sections as UNION\n");
|
sectError("Cannot declare ROM sections as UNION\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (org != UINT32_MAX) {
|
if (org != UINT32_MAX) {
|
||||||
// If both are fixed, they must be the same
|
// 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(
|
sectError(
|
||||||
"Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org
|
"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(
|
sectError(
|
||||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||||
1U << sect.align,
|
1U << sect.align,
|
||||||
sect.alignOfs
|
sect.alignOfs
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
// Otherwise, just override
|
// Otherwise, just override
|
||||||
sect.org = org;
|
sect.org = org;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (alignment != 0) {
|
} else if (alignment != 0) {
|
||||||
// Make sure any fixed address given is compatible
|
// Make sure any fixed address given is compatible
|
||||||
if (sect.org != UINT32_MAX) {
|
if (sect.org != UINT32_MAX) {
|
||||||
if ((sect.org - alignOffset) & mask(alignment))
|
if ((sect.org - alignOffset) & mask(alignment)) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||||
sect.org
|
sect.org
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// Check if alignment offsets are compatible
|
// Check if alignment offsets are compatible
|
||||||
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||||
sectError(
|
sectError(
|
||||||
@@ -163,34 +172,37 @@ static unsigned int
|
|||||||
uint16_t curOrg = org - sect.size;
|
uint16_t curOrg = org - sect.size;
|
||||||
|
|
||||||
// If both are fixed, they must be the same
|
// If both are fixed, they must be the same
|
||||||
if (sect.org != UINT32_MAX && sect.org != curOrg)
|
if (sect.org != UINT32_MAX && sect.org != curOrg) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||||
sect.org
|
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(
|
sectError(
|
||||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||||
1U << sect.align,
|
1U << sect.align,
|
||||||
sect.alignOfs
|
sect.alignOfs
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
// Otherwise, just override
|
// Otherwise, just override
|
||||||
sect.org = curOrg;
|
sect.org = curOrg;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (alignment != 0) {
|
} else if (alignment != 0) {
|
||||||
int32_t curOfs = (alignOffset - sect.size) % (1U << alignment);
|
int32_t curOfs = (alignOffset - sect.size) % (1U << alignment);
|
||||||
|
|
||||||
if (curOfs < 0)
|
if (curOfs < 0) {
|
||||||
curOfs += 1U << alignment;
|
curOfs += 1U << alignment;
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure any fixed address given is compatible
|
// Make sure any fixed address given is compatible
|
||||||
if (sect.org != UINT32_MAX) {
|
if (sect.org != UINT32_MAX) {
|
||||||
if ((sect.org - curOfs) & mask(alignment))
|
if ((sect.org - curOfs) & mask(alignment)) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||||
sect.org
|
sect.org
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// Check if alignment offsets are compatible
|
// Check if alignment offsets are compatible
|
||||||
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||||
sectError(
|
sectError(
|
||||||
@@ -220,13 +232,14 @@ static void mergeSections(
|
|||||||
) {
|
) {
|
||||||
unsigned int nbSectErrors = 0;
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
if (type != sect.type)
|
if (type != sect.type) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str()
|
"Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str()
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (sect.modifier != mod) {
|
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 {
|
} else {
|
||||||
switch (mod) {
|
switch (mod) {
|
||||||
case SECTION_UNION:
|
case SECTION_UNION:
|
||||||
@@ -238,11 +251,13 @@ static void mergeSections(
|
|||||||
// Common checks
|
// Common checks
|
||||||
|
|
||||||
// If the section's bank is unspecified, override it
|
// If the section's bank is unspecified, override it
|
||||||
if (sect.bank == UINT32_MAX)
|
if (sect.bank == UINT32_MAX) {
|
||||||
sect.bank = bank;
|
sect.bank = bank;
|
||||||
|
}
|
||||||
// If both specify a bank, it must be the same one
|
// If both specify a bank, it must be the same one
|
||||||
else if (bank != UINT32_MAX && sect.bank != bank)
|
else if (bank != UINT32_MAX && sect.bank != bank) {
|
||||||
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
|
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SECTION_NORMAL:
|
case SECTION_NORMAL:
|
||||||
@@ -253,13 +268,14 @@ static void mergeSections(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nbSectErrors)
|
if (nbSectErrors) {
|
||||||
fatalerror(
|
fatalerror(
|
||||||
"Cannot create section \"%s\" (%u error%s)\n",
|
"Cannot create section \"%s\" (%u error%s)\n",
|
||||||
sect.name.c_str(),
|
sect.name.c_str(),
|
||||||
nbSectErrors,
|
nbSectErrors,
|
||||||
nbSectErrors == 1 ? "" : "s"
|
nbSectErrors == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef sectError
|
#undef sectError
|
||||||
@@ -292,8 +308,9 @@ static Section *createSection(
|
|||||||
out_RegisterNode(sect.src);
|
out_RegisterNode(sect.src);
|
||||||
|
|
||||||
// It is only needed to allocate memory for ROM sections.
|
// It is only needed to allocate memory for ROM sections.
|
||||||
if (sect_HasData(type))
|
if (sect_HasData(type)) {
|
||||||
sect.data.resize(sectionTypeInfo[type].size);
|
sect.data.resize(sectionTypeInfo[type].size);
|
||||||
|
}
|
||||||
|
|
||||||
return §
|
return §
|
||||||
}
|
}
|
||||||
@@ -314,9 +331,10 @@ static Section *getSection(
|
|||||||
|
|
||||||
if (bank != UINT32_MAX) {
|
if (bank != UINT32_MAX) {
|
||||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
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");
|
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(
|
error(
|
||||||
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
|
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
|
||||||
sectionTypeInfo[type].name.c_str(),
|
sectionTypeInfo[type].name.c_str(),
|
||||||
@@ -324,6 +342,7 @@ static Section *getSection(
|
|||||||
sectionTypeInfo[type].firstBank,
|
sectionTypeInfo[type].firstBank,
|
||||||
sectionTypeInfo[type].lastBank
|
sectionTypeInfo[type].lastBank
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else if (nbbanks(type) == 1) {
|
} else if (nbbanks(type) == 1) {
|
||||||
// If the section type only has a single bank, implicitly force it
|
// If the section type only has a single bank, implicitly force it
|
||||||
bank = sectionTypeInfo[type].firstBank;
|
bank = sectionTypeInfo[type].firstBank;
|
||||||
@@ -339,7 +358,7 @@ static Section *getSection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (org != UINT32_MAX) {
|
if (org != UINT32_MAX) {
|
||||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
|
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) {
|
||||||
error(
|
error(
|
||||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||||
"; $%04" PRIx16 "]\n",
|
"; $%04" PRIx16 "]\n",
|
||||||
@@ -348,6 +367,7 @@ static Section *getSection(
|
|||||||
sectionTypeInfo[type].startAddr,
|
sectionTypeInfo[type].startAddr,
|
||||||
endaddr(type)
|
endaddr(type)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alignment != 0) {
|
if (alignment != 0) {
|
||||||
@@ -359,8 +379,9 @@ static Section *getSection(
|
|||||||
uint32_t mask = mask(alignment);
|
uint32_t mask = mask(alignment);
|
||||||
|
|
||||||
if (org != UINT32_MAX) {
|
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());
|
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str());
|
||||||
|
}
|
||||||
alignment = 0; // Ignore it if it's satisfied
|
alignment = 0; // Ignore it if it's satisfied
|
||||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||||
error(
|
error(
|
||||||
@@ -393,25 +414,29 @@ static Section *getSection(
|
|||||||
|
|
||||||
// Set the current section
|
// Set the current section
|
||||||
static void changeSection() {
|
static void changeSection() {
|
||||||
if (!currentUnionStack.empty())
|
if (!currentUnionStack.empty()) {
|
||||||
fatalerror("Cannot change the section within a UNION\n");
|
fatalerror("Cannot change the section within a UNION\n");
|
||||||
|
}
|
||||||
|
|
||||||
sym_ResetCurrentLabelScopes();
|
sym_ResetCurrentLabelScopes();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Section::isSizeKnown() const {
|
bool Section::isSizeKnown() const {
|
||||||
// SECTION UNION and SECTION FRAGMENT can still grow
|
// SECTION UNION and SECTION FRAGMENT can still grow
|
||||||
if (modifier != SECTION_NORMAL)
|
if (modifier != SECTION_NORMAL) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// The current section (or current load section if within one) is still growing
|
// 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;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Any section on the stack is still growing
|
// Any section on the stack is still growing
|
||||||
for (SectionStackEntry &entry : sectionStack) {
|
for (SectionStackEntry &entry : sectionStack) {
|
||||||
if (entry.section && entry.section->name == name)
|
if (entry.section && entry.section->name == name) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -426,12 +451,14 @@ void sect_NewSection(
|
|||||||
SectionModifier mod
|
SectionModifier mod
|
||||||
) {
|
) {
|
||||||
for (SectionStackEntry &entry : sectionStack) {
|
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());
|
fatalerror("Section '%s' is already on the stack\n", name.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentLoadSection)
|
if (currentLoadSection) {
|
||||||
sect_EndLoadSection("SECTION");
|
sect_EndLoadSection("SECTION");
|
||||||
|
}
|
||||||
|
|
||||||
Section *sect = getSection(name, type, org, attrs, mod);
|
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
|
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
|
||||||
// your own peril! ^^
|
// your own peril! ^^
|
||||||
|
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (sect_HasData(type)) {
|
if (sect_HasData(type)) {
|
||||||
error("`LOAD` blocks cannot create a ROM section\n");
|
error("`LOAD` blocks cannot create a ROM section\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentLoadSection)
|
if (currentLoadSection) {
|
||||||
sect_EndLoadSection("LOAD");
|
sect_EndLoadSection("LOAD");
|
||||||
|
}
|
||||||
|
|
||||||
Section *sect = getSection(name, type, org, attrs, mod);
|
Section *sect = getSection(name, type, org, attrs, mod);
|
||||||
|
|
||||||
@@ -475,12 +504,11 @@ void sect_SetLoadSection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sect_EndLoadSection(char const *cause) {
|
void sect_EndLoadSection(char const *cause) {
|
||||||
if (cause)
|
if (cause) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_UNTERMINATED_LOAD,
|
WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`\n", cause
|
||||||
"`LOAD` block without `ENDL` terminated by `%s`\n",
|
|
||||||
cause
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!currentLoadSection) {
|
if (!currentLoadSection) {
|
||||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
error("Found `ENDL` outside of a `LOAD` block\n");
|
||||||
@@ -495,8 +523,9 @@ void sect_EndLoadSection(char const *cause) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sect_CheckLoadClosed() {
|
void sect_CheckLoadClosed() {
|
||||||
if (currentLoadSection)
|
if (currentLoadSection) {
|
||||||
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
|
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section *sect_GetSymbolSection() {
|
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
|
// Returns how many bytes need outputting for the specified alignment and offset to succeed
|
||||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||||
Section *sect = sect_GetSymbolSection();
|
Section *sect = sect_GetSymbolSection();
|
||||||
if (!sect)
|
if (!sect) {
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool isFixed = sect->org != UINT32_MAX;
|
bool isFixed = sect->org != UINT32_MAX;
|
||||||
|
|
||||||
// If the section is not aligned, no bytes are needed
|
// If the section is not aligned, no bytes are needed
|
||||||
// (fixed sections count as being maximally aligned for this purpose)
|
// (fixed sections count as being maximally aligned for this purpose)
|
||||||
uint8_t curAlignment = isFixed ? 16 : sect->align;
|
uint8_t curAlignment = isFixed ? 16 : sect->align;
|
||||||
if (curAlignment == 0)
|
if (curAlignment == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
|
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
|
||||||
uint16_t pcValue = isFixed ? sect->org : sect->alignOfs;
|
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) {
|
void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||||
if (!requireSection())
|
if (!requireSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Section *sect = sect_GetSymbolSection();
|
Section *sect = sect_GetSymbolSection();
|
||||||
uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
|
uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
|
||||||
|
|
||||||
if (sect->org != UINT32_MAX) {
|
if (sect->org != UINT32_MAX) {
|
||||||
if ((sect->org + curOffset - offset) % alignSize)
|
if ((sect->org + curOffset - offset) % alignSize) {
|
||||||
error(
|
error(
|
||||||
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
||||||
sect->org + curOffset
|
sect->org + curOffset
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else if (sect->align != 0
|
} else if (sect->align != 0
|
||||||
&& (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
&& (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
||||||
error(
|
error(
|
||||||
@@ -568,18 +601,22 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void growSection(uint32_t growth) {
|
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");
|
fatalerror("Section size would overflow internal counter\n");
|
||||||
|
}
|
||||||
curOffset += growth;
|
curOffset += growth;
|
||||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size)
|
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) {
|
||||||
currentSection->size = outOffset;
|
currentSection->size = outOffset;
|
||||||
if (currentLoadSection && curOffset > currentLoadSection->size)
|
}
|
||||||
|
if (currentLoadSection && curOffset > currentLoadSection->size) {
|
||||||
currentLoadSection->size = curOffset;
|
currentLoadSection->size = curOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeByte(uint8_t byte) {
|
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;
|
currentSection->data[index] = byte;
|
||||||
|
}
|
||||||
growSection(1);
|
growSection(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,8 +658,9 @@ static void endUnionMember() {
|
|||||||
UnionStackEntry &member = currentUnionStack.top();
|
UnionStackEntry &member = currentUnionStack.top();
|
||||||
uint32_t memberSize = curOffset - member.start;
|
uint32_t memberSize = curOffset - member.start;
|
||||||
|
|
||||||
if (memberSize > member.size)
|
if (memberSize > member.size) {
|
||||||
member.size = memberSize;
|
member.size = memberSize;
|
||||||
|
}
|
||||||
curOffset = member.start;
|
curOffset = member.start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,64 +683,75 @@ void sect_EndUnion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sect_CheckUnionClosed() {
|
void sect_CheckUnionClosed() {
|
||||||
if (!currentUnionStack.empty())
|
if (!currentUnionStack.empty()) {
|
||||||
error("Unterminated UNION construct\n");
|
error("Unterminated UNION construct\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a constant byte
|
// Output a constant byte
|
||||||
void sect_ConstByte(uint8_t byte) {
|
void sect_ConstByte(uint8_t byte) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
writeByte(byte);
|
writeByte(byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a string's character units as bytes
|
// Output a string's character units as bytes
|
||||||
void sect_ByteString(std::vector<int32_t> const &string) {
|
void sect_ByteString(std::vector<int32_t> const &string) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
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));
|
writeByte(static_cast<uint8_t>(unit));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a string's character units as words
|
// Output a string's character units as words
|
||||||
void sect_WordString(std::vector<int32_t> const &string) {
|
void sect_WordString(std::vector<int32_t> const &string) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
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));
|
writeWord(static_cast<uint16_t>(unit));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a string's character units as longs
|
// Output a string's character units as longs
|
||||||
void sect_LongString(std::vector<int32_t> const &string) {
|
void sect_LongString(std::vector<int32_t> const &string) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int32_t unit : string)
|
for (int32_t unit : string) {
|
||||||
writeLong(static_cast<uint32_t>(unit));
|
writeLong(static_cast<uint32_t>(unit));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip this many bytes
|
// Skip this many bytes
|
||||||
void sect_Skip(uint32_t skip, bool ds) {
|
void sect_Skip(uint32_t skip, bool ds) {
|
||||||
if (!requireSection())
|
if (!requireSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!sect_HasData(currentSection->type)) {
|
if (!sect_HasData(currentSection->type)) {
|
||||||
growSection(skip);
|
growSection(skip);
|
||||||
} else {
|
} else {
|
||||||
if (!ds)
|
if (!ds) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||||
"%s directive without data in ROM\n",
|
"%s directive without data in ROM\n",
|
||||||
@@ -710,16 +759,19 @@ void sect_Skip(uint32_t skip, bool ds) {
|
|||||||
: (skip == 2) ? "DW"
|
: (skip == 2) ? "DW"
|
||||||
: "DB"
|
: "DB"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// We know we're in a code SECTION
|
// We know we're in a code SECTION
|
||||||
while (skip--)
|
while (skip--) {
|
||||||
writeByte(fillByte);
|
writeByte(fillByte);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a byte that can be relocatable or constant
|
// Output a byte that can be relocatable or constant
|
||||||
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
void sect_RelByte(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!expr.isKnown()) {
|
if (!expr.isKnown()) {
|
||||||
createPatch(PATCHTYPE_BYTE, expr, pcShift);
|
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
|
// Output several bytes that can be relocatable or constant
|
||||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (uint32_t i = 0; i < n; i++) {
|
for (uint32_t i = 0; i < n; i++) {
|
||||||
Expression &expr = exprs[i % exprs.size()];
|
if (Expression const &expr = exprs[i % exprs.size()]; !expr.isKnown()) {
|
||||||
|
|
||||||
if (!expr.isKnown()) {
|
|
||||||
createPatch(PATCHTYPE_BYTE, expr, i);
|
createPatch(PATCHTYPE_BYTE, expr, i);
|
||||||
writeByte(0);
|
writeByte(0);
|
||||||
} else {
|
} else {
|
||||||
@@ -747,9 +798,10 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Output a word that can be relocatable or constant
|
// Output a word that can be relocatable or constant
|
||||||
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
void sect_RelWord(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!expr.isKnown()) {
|
if (!expr.isKnown()) {
|
||||||
createPatch(PATCHTYPE_WORD, expr, pcShift);
|
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
|
// Output a long that can be relocatable or constant
|
||||||
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
void sect_RelLong(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!expr.isKnown()) {
|
if (!expr.isKnown()) {
|
||||||
createPatch(PATCHTYPE_LONG, expr, pcShift);
|
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
|
// Output a PC-relative byte that can be relocatable or constant
|
||||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
void sect_PCRelByte(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Symbol const *pc = sym_GetPC(); !expr.isDiffConstant(pc)) {
|
if (Symbol const *pc = sym_GetPC(); !expr.isDiffConstant(pc)) {
|
||||||
createPatch(PATCHTYPE_JR, expr, pcShift);
|
createPatch(PATCHTYPE_JR, expr, pcShift);
|
||||||
@@ -786,15 +840,16 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
|||||||
int16_t offset;
|
int16_t offset;
|
||||||
|
|
||||||
// Offset is relative to the byte *after* the operand
|
// 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
|
offset = -2; // PC as operand to `jr` is lower than reference PC by 2
|
||||||
else
|
} else {
|
||||||
offset = sym->getValue() - (pc->getValue() + 1);
|
offset = sym->getValue() - (pc->getValue() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (offset < -128 || offset > 127) {
|
if (offset < -128 || offset > 127) {
|
||||||
error(
|
error(
|
||||||
"jr target must be between -128 and 127 bytes away, not %" PRId16
|
"JR target must be between -128 and 127 bytes away, not %" PRId16
|
||||||
"; use jp instead\n",
|
"; use JP instead\n",
|
||||||
offset
|
offset
|
||||||
);
|
);
|
||||||
writeByte(0);
|
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);
|
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
||||||
startPos = 0;
|
startPos = 0;
|
||||||
}
|
}
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FILE *file = nullptr;
|
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");
|
file = fopen(fullPath->c_str(), "rb");
|
||||||
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
if (generatedMissingIncludes) {
|
if (generatedMissingIncludes) {
|
||||||
if (verbose)
|
// LCOV_EXCL_START
|
||||||
|
if (verbose) {
|
||||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
failedOnMissingInclude = true;
|
failedOnMissingInclude = true;
|
||||||
} else {
|
} else {
|
||||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
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
|
// The file is seekable; skip to the specified start position
|
||||||
fseek(file, startPos, SEEK_SET);
|
fseek(file, startPos, SEEK_SET);
|
||||||
} else {
|
} else {
|
||||||
if (errno != ESPIPE)
|
if (errno != ESPIPE) {
|
||||||
error(
|
error(
|
||||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
"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
|
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||||
while (startPos--) {
|
while (startPos--) {
|
||||||
if (fgetc(file) == EOF) {
|
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);
|
writeByte(byte);
|
||||||
|
}
|
||||||
|
|
||||||
if (ferror(file))
|
if (ferror(file)) {
|
||||||
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a slice of a binary file
|
// 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);
|
error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length);
|
||||||
length = 0;
|
length = 0;
|
||||||
}
|
}
|
||||||
if (!requireCodeSection())
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
if (length == 0) // Don't even bother with 0-byte slices
|
}
|
||||||
|
if (length == 0) { // Don't even bother with 0-byte slices
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FILE *file = nullptr;
|
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");
|
file = fopen(fullPath->c_str(), "rb");
|
||||||
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
if (generatedMissingIncludes) {
|
if (generatedMissingIncludes) {
|
||||||
if (verbose)
|
// LCOV_EXCL_START
|
||||||
|
if (verbose) {
|
||||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
failedOnMissingInclude = true;
|
failedOnMissingInclude = true;
|
||||||
} else {
|
} else {
|
||||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
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
|
// The file is seekable; skip to the specified start position
|
||||||
fseek(file, startPos, SEEK_SET);
|
fseek(file, startPos, SEEK_SET);
|
||||||
} else {
|
} else {
|
||||||
if (errno != ESPIPE)
|
if (errno != ESPIPE) {
|
||||||
error(
|
error(
|
||||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
"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
|
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||||
while (startPos--) {
|
while (startPos--) {
|
||||||
if (fgetc(file) == EOF) {
|
if (fgetc(file) == EOF) {
|
||||||
@@ -955,11 +1025,13 @@ void sect_PushSection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sect_PopSection() {
|
void sect_PopSection() {
|
||||||
if (sectionStack.empty())
|
if (sectionStack.empty()) {
|
||||||
fatalerror("No entries in the section stack\n");
|
fatalerror("No entries in the section stack\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (currentLoadSection)
|
if (currentLoadSection) {
|
||||||
sect_EndLoadSection("POPS");
|
sect_EndLoadSection("POPS");
|
||||||
|
}
|
||||||
|
|
||||||
SectionStackEntry entry = sectionStack.front();
|
SectionStackEntry entry = sectionStack.front();
|
||||||
sectionStack.pop_front();
|
sectionStack.pop_front();
|
||||||
@@ -980,14 +1052,17 @@ void sect_CheckStack() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sect_EndSection() {
|
void sect_EndSection() {
|
||||||
if (!currentSection)
|
if (!currentSection) {
|
||||||
fatalerror("Cannot end the section outside of a SECTION\n");
|
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");
|
fatalerror("Cannot end the section within a UNION\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (currentLoadSection)
|
if (currentLoadSection) {
|
||||||
sect_EndLoadSection("ENDSECTION");
|
sect_EndLoadSection("ENDSECTION");
|
||||||
|
}
|
||||||
|
|
||||||
// Reset the section scope
|
// Reset the section scope
|
||||||
currentSection = nullptr;
|
currentSection = nullptr;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/symbol.hpp"
|
#include "asm/symbol.hpp"
|
||||||
|
|
||||||
@@ -40,8 +40,9 @@ bool sym_IsPC(Symbol const *sym) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sym_ForEach(void (*callback)(Symbol &)) {
|
void sym_ForEach(void (*callback)(Symbol &)) {
|
||||||
for (auto &it : symbols)
|
for (auto &it : symbols) {
|
||||||
callback(it.second);
|
callback(it.second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t NARGCallback() {
|
static int32_t NARGCallback() {
|
||||||
@@ -92,7 +93,7 @@ int32_t Symbol::getOutputValue() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContentSpan const &Symbol::getMacro() const {
|
ContentSpan const &Symbol::getMacro() const {
|
||||||
assume((std::holds_alternative<ContentSpan>(data)));
|
assume(std::holds_alternative<ContentSpan>(data));
|
||||||
return std::get<ContentSpan>(data);
|
return std::get<ContentSpan>(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,8 +102,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)
|
||||||
|| 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 (*callback)();
|
||||||
|
}
|
||||||
return std::get<std::shared_ptr<std::string>>(data);
|
return std::get<std::shared_ptr<std::string>>(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,8 +125,9 @@ static void updateSymbolFilename(Symbol &sym) {
|
|||||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||||
|
|
||||||
// If the old node was registered, ensure the new one is too
|
// 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);
|
out_RegisterNode(sym.src);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||||
@@ -133,8 +136,9 @@ static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
|||||||
error("'%s' is reserved for a built-in symbol\n", sym.name.c_str());
|
error("'%s' is reserved for a built-in symbol\n", sym.name.c_str());
|
||||||
} else {
|
} else {
|
||||||
error("'%s' already defined", sym.name.c_str());
|
error("'%s' already defined", sym.name.c_str());
|
||||||
if (asType)
|
if (asType) {
|
||||||
fprintf(stderr, " as %s", asType);
|
fprintf(stderr, " as %s", asType);
|
||||||
|
}
|
||||||
fputs(" at ", stderr);
|
fputs(" at ", stderr);
|
||||||
dumpFilename(sym);
|
dumpFilename(sym);
|
||||||
}
|
}
|
||||||
@@ -184,28 +188,34 @@ static bool isAutoScoped(std::string const &symName) {
|
|||||||
size_t dotPos = symName.find('.');
|
size_t dotPos = symName.find('.');
|
||||||
|
|
||||||
// If there are no dots, it's not a local label
|
// If there are no dots, it's not a local label
|
||||||
if (dotPos == std::string::npos)
|
if (dotPos == std::string::npos) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
// 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;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for nothing after the dot
|
// 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());
|
fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
// Check for more than one dot
|
// 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());
|
fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
// Check for already-qualified local label
|
// Check for already-qualified local label
|
||||||
if (dotPos > 0)
|
if (dotPos > 0) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for unqualifiable local label
|
// Check for unqualifiable local label
|
||||||
if (!globalScope)
|
if (!globalScope) {
|
||||||
fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str());
|
fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -252,24 +262,28 @@ void sym_Purge(std::string const &symName) {
|
|||||||
Symbol *sym = sym_FindScopedValidSymbol(symName);
|
Symbol *sym = sym_FindScopedValidSymbol(symName);
|
||||||
|
|
||||||
if (!sym) {
|
if (!sym) {
|
||||||
if (sym_IsPurgedScoped(symName))
|
if (sym_IsPurgedScoped(symName)) {
|
||||||
error("'%s' was already purged\n", symName.c_str());
|
error("'%s' was already purged\n", symName.c_str());
|
||||||
else
|
} else {
|
||||||
error("'%s' not defined\n", symName.c_str());
|
error("'%s' not defined\n", symName.c_str());
|
||||||
|
}
|
||||||
} else if (sym->isBuiltin) {
|
} else if (sym->isBuiltin) {
|
||||||
error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
|
error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
|
||||||
} else if (sym->ID != UINT32_MAX) {
|
} else if (sym->ID != UINT32_MAX) {
|
||||||
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
|
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
|
||||||
} else {
|
} else {
|
||||||
if (sym->isExported)
|
if (sym->isExported) {
|
||||||
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str());
|
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());
|
warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str());
|
||||||
|
}
|
||||||
// Do not keep a reference to the label after purging it
|
// Do not keep a reference to the label after purging it
|
||||||
if (sym == globalScope)
|
if (sym == globalScope) {
|
||||||
globalScope = nullptr;
|
globalScope = nullptr;
|
||||||
if (sym == localScope)
|
}
|
||||||
|
if (sym == localScope) {
|
||||||
localScope = nullptr;
|
localScope = nullptr;
|
||||||
|
}
|
||||||
purgedSymbols.emplace(sym->name);
|
purgedSymbols.emplace(sym->name);
|
||||||
symbols.erase(sym->name);
|
symbols.erase(sym->name);
|
||||||
}
|
}
|
||||||
@@ -295,31 +309,22 @@ void sym_SetRSValue(int32_t value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Symbol::getConstantValue() const {
|
uint32_t Symbol::getConstantValue() const {
|
||||||
if (isConstant())
|
if (isConstant()) {
|
||||||
return getValue();
|
return getValue();
|
||||||
|
}
|
||||||
|
|
||||||
if (sym_IsPC(this)) {
|
if (sym_IsPC(this)) {
|
||||||
if (!getSection())
|
if (!getSection()) {
|
||||||
error("PC has no value outside of a section\n");
|
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");
|
error("PC does not have a constant value; the current section is not fixed\n");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
error("\"%s\" does not have a constant value\n", name.c_str());
|
error("\"%s\" does not have a constant value\n", name.c_str());
|
||||||
}
|
}
|
||||||
return 0;
|
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() {
|
std::pair<Symbol const *, Symbol const *> sym_GetCurrentLabelScopes() {
|
||||||
return {globalScope, localScope};
|
return {globalScope, localScope};
|
||||||
}
|
}
|
||||||
@@ -361,8 +366,9 @@ static Symbol *createNonrelocSymbol(std::string const &symName, bool numeric) {
|
|||||||
Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
|
Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
|
||||||
Symbol *sym = createNonrelocSymbol(symName, true);
|
Symbol *sym = createNonrelocSymbol(symName, true);
|
||||||
|
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
sym->type = SYM_EQU;
|
sym->type = SYM_EQU;
|
||||||
sym->data = value;
|
sym->data = value;
|
||||||
@@ -373,8 +379,9 @@ Symbol *sym_AddEqu(std::string const &symName, int32_t value) {
|
|||||||
Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
|
Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
|
||||||
Symbol *sym = sym_FindExactSymbol(symName);
|
Symbol *sym = sym_FindExactSymbol(symName);
|
||||||
|
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
return sym_AddEqu(symName, value);
|
return sym_AddEqu(symName, value);
|
||||||
|
}
|
||||||
|
|
||||||
if (sym->isDefined() && sym->type != SYM_EQU) {
|
if (sym->isDefined() && sym->type != SYM_EQU) {
|
||||||
alreadyDefinedError(*sym, "non-EQU");
|
alreadyDefinedError(*sym, "non-EQU");
|
||||||
@@ -394,8 +401,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_AddString(std::string const &symName, std::shared_ptr<std::string> str) {
|
||||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||||
|
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
sym->type = SYM_EQUS;
|
sym->type = SYM_EQUS;
|
||||||
sym->data = str;
|
sym->data = str;
|
||||||
@@ -405,8 +413,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_RedefString(std::string const &symName, std::shared_ptr<std::string> str) {
|
||||||
Symbol *sym = sym_FindExactSymbol(symName);
|
Symbol *sym = sym_FindExactSymbol(symName);
|
||||||
|
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
return sym_AddString(symName, str);
|
return sym_AddString(symName, str);
|
||||||
|
}
|
||||||
|
|
||||||
if (sym->type != SYM_EQUS) {
|
if (sym->type != SYM_EQUS) {
|
||||||
if (sym->isDefined()) {
|
if (sym->isDefined()) {
|
||||||
@@ -462,12 +471,14 @@ static Symbol *addLabel(std::string const &symName) {
|
|||||||
sym->type = SYM_LABEL;
|
sym->type = SYM_LABEL;
|
||||||
sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
|
sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
|
||||||
// Don't export anonymous labels
|
// Don't export anonymous labels
|
||||||
if (exportAll && !symName.starts_with('!'))
|
if (exportAll && !symName.starts_with('!')) {
|
||||||
sym->isExported = true;
|
sym->isExported = true;
|
||||||
|
}
|
||||||
sym->section = sect_GetSymbolSection();
|
sym->section = sect_GetSymbolSection();
|
||||||
|
|
||||||
if (sym && !sym->section)
|
if (sym && !sym->section) {
|
||||||
error("Label \"%s\" created outside of a SECTION\n", symName.c_str());
|
error("Label \"%s\" created outside of a SECTION\n", symName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
@@ -478,8 +489,9 @@ Symbol *sym_AddLocalLabel(std::string const &symName) {
|
|||||||
|
|
||||||
Symbol *sym = addLabel(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
Symbol *sym = addLabel(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
||||||
|
|
||||||
if (sym)
|
if (sym) {
|
||||||
localScope = sym;
|
localScope = sym;
|
||||||
|
}
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
@@ -503,8 +515,10 @@ static uint32_t anonLabelID = 0;
|
|||||||
|
|
||||||
Symbol *sym_AddAnonLabel() {
|
Symbol *sym_AddAnonLabel() {
|
||||||
if (anonLabelID == UINT32_MAX) {
|
if (anonLabelID == UINT32_MAX) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
|
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string anon = sym_MakeAnonLabelName(0, true); // The direction is important!
|
std::string anon = sym_MakeAnonLabelName(0, true); // The direction is important!
|
||||||
@@ -516,7 +530,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
|||||||
uint32_t id = 0;
|
uint32_t id = 0;
|
||||||
|
|
||||||
if (neg) {
|
if (neg) {
|
||||||
if (ofs > anonLabelID)
|
if (ofs > anonLabelID) {
|
||||||
error(
|
error(
|
||||||
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
||||||
" ha%s been created so far\n",
|
" ha%s been created so far\n",
|
||||||
@@ -524,45 +538,52 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
|||||||
anonLabelID,
|
anonLabelID,
|
||||||
anonLabelID == 1 ? "s" : "ve"
|
anonLabelID == 1 ? "s" : "ve"
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
id = anonLabelID - ofs;
|
id = anonLabelID - ofs;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ofs--; // We're referencing symbols that haven't been created yet...
|
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(
|
error(
|
||||||
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||||
" may still be created\n",
|
" may still be created\n",
|
||||||
ofs + 1,
|
ofs + 1,
|
||||||
UINT32_MAX - anonLabelID
|
UINT32_MAX - anonLabelID
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
id = anonLabelID + ofs;
|
id = anonLabelID + ofs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string anon("!");
|
return "!"s + std::to_string(id);
|
||||||
anon += std::to_string(id);
|
|
||||||
return anon;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sym_Export(std::string const &symName) {
|
void sym_Export(std::string const &symName) {
|
||||||
if (symName.starts_with('!')) {
|
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");
|
error("Anonymous labels cannot be exported\n");
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||||
|
|
||||||
// If the symbol doesn't exist, create a ref that can be purged
|
// If the symbol doesn't exist, create a ref that can be purged
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
sym = sym_Ref(symName);
|
sym = sym_Ref(symName);
|
||||||
|
}
|
||||||
sym->isExported = true;
|
sym->isExported = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) {
|
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) {
|
||||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||||
|
|
||||||
if (!sym)
|
if (!sym) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
sym->type = SYM_MACRO;
|
sym->type = SYM_MACRO;
|
||||||
sym->data = span;
|
sym->data = span;
|
||||||
@@ -627,11 +648,13 @@ void sym_Init(time_t now) {
|
|||||||
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
|
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (now == static_cast<time_t>(-1)) {
|
if (now == static_cast<time_t>(-1)) {
|
||||||
warn("Failed to determine current time");
|
warn("Failed to determine current time");
|
||||||
// Fall back by pretending we are at the Epoch
|
// Fall back by pretending we are at the Epoch
|
||||||
now = 0;
|
now = 0;
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
tm const *time_local = localtime(&now);
|
tm const *time_local = localtime(&now);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
@@ -35,13 +35,13 @@ struct WarningFlag {
|
|||||||
WarningLevel level;
|
WarningLevel level;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const WarningFlag metaWarnings[] = {
|
static WarningFlag const metaWarnings[] = {
|
||||||
{"all", LEVEL_ALL },
|
{"all", LEVEL_ALL },
|
||||||
{"extra", LEVEL_EXTRA },
|
{"extra", LEVEL_EXTRA },
|
||||||
{"everything", LEVEL_EVERYTHING},
|
{"everything", LEVEL_EVERYTHING},
|
||||||
};
|
};
|
||||||
|
|
||||||
static const WarningFlag warningFlags[NB_WARNINGS] = {
|
static WarningFlag const warningFlags[NB_WARNINGS] = {
|
||||||
{"assert", LEVEL_DEFAULT },
|
{"assert", LEVEL_DEFAULT },
|
||||||
{"backwards-for", LEVEL_ALL },
|
{"backwards-for", LEVEL_ALL },
|
||||||
{"builtin-args", LEVEL_ALL },
|
{"builtin-args", LEVEL_ALL },
|
||||||
@@ -85,8 +85,9 @@ enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
|||||||
|
|
||||||
static WarningBehavior getWarningBehavior(WarningID id) {
|
static WarningBehavior getWarningBehavior(WarningID id) {
|
||||||
// Check if warnings are globally disabled
|
// Check if warnings are globally disabled
|
||||||
if (!warnings)
|
if (!warnings) {
|
||||||
return WarningBehavior::DISABLED;
|
return WarningBehavior::DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the state of this warning flag
|
// Get the state of this warning flag
|
||||||
WarningState const &flagState = warningStates.flagStates[id];
|
WarningState const &flagState = warningStates.flagStates[id];
|
||||||
@@ -100,34 +101,43 @@ static WarningBehavior getWarningBehavior(WarningID id) {
|
|||||||
warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED;
|
warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED;
|
||||||
|
|
||||||
// First, check the state of the specific warning flag
|
// 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;
|
return WarningBehavior::DISABLED;
|
||||||
if (flagState.error == WARNING_ENABLED) // -Werror=<flag>
|
}
|
||||||
|
if (flagState.error == WARNING_ENABLED) { // -Werror=<flag>
|
||||||
return WarningBehavior::ERROR;
|
return WarningBehavior::ERROR;
|
||||||
if (flagState.state == WARNING_ENABLED) // -W<flag>
|
}
|
||||||
|
if (flagState.state == WARNING_ENABLED) { // -W<flag>
|
||||||
return enabledBehavior;
|
return enabledBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
// If no flag is specified, check the state of the "meta" flags that affect this warning flag
|
// 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;
|
return WarningBehavior::DISABLED;
|
||||||
if (metaState.error == WARNING_ENABLED) // -Werror=<meta>
|
}
|
||||||
|
if (metaState.error == WARNING_ENABLED) { // -Werror=<meta>
|
||||||
return WarningBehavior::ERROR;
|
return WarningBehavior::ERROR;
|
||||||
if (metaState.state == WARNING_ENABLED) // -W<meta>
|
}
|
||||||
|
if (metaState.state == WARNING_ENABLED) { // -W<meta>
|
||||||
return enabledBehavior;
|
return enabledBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
// If no meta flag is specified, check the default state of this warning flag
|
// 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;
|
return enabledBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
// No flag enables this warning, explicitly or implicitly
|
// No flag enables this warning, explicitly or implicitly
|
||||||
return WarningBehavior::DISABLED;
|
return WarningBehavior::DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WarningState::update(WarningState other) {
|
void WarningState::update(WarningState other) {
|
||||||
if (other.state != WARNING_DEFAULT)
|
if (other.state != WARNING_DEFAULT) {
|
||||||
state = other.state;
|
state = other.state;
|
||||||
if (other.error != WARNING_DEFAULT)
|
}
|
||||||
|
if (other.error != WARNING_DEFAULT) {
|
||||||
error = other.error;
|
error = other.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void processWarningFlag(char const *flag) {
|
void processWarningFlag(char const *flag) {
|
||||||
@@ -149,16 +159,16 @@ void processWarningFlag(char const *flag) {
|
|||||||
if (rootFlag.starts_with("error=")) {
|
if (rootFlag.starts_with("error=")) {
|
||||||
// `-Werror=<flag>` enables the flag as an error
|
// `-Werror=<flag>` enables the flag as an error
|
||||||
state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED};
|
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=")) {
|
} else if (rootFlag.starts_with("no-error=")) {
|
||||||
// `-Wno-error=<flag>` prevents the flag from being an error,
|
// `-Wno-error=<flag>` prevents the flag from being an error,
|
||||||
// without affecting whether it is enabled
|
// without affecting whether it is enabled
|
||||||
state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED};
|
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-")) {
|
} else if (rootFlag.starts_with("no-")) {
|
||||||
// `-Wno-<flag>` disables the flag
|
// `-Wno-<flag>` disables the flag
|
||||||
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
|
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
|
||||||
rootFlag.erase(0, QUOTEDSTRLEN("no-"));
|
rootFlag.erase(0, literal_strlen("no-"));
|
||||||
} else {
|
} else {
|
||||||
// `-W<flag>` enables the flag
|
// `-W<flag>` enables the flag
|
||||||
state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
|
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
|
// The `if`'s condition above ensures that this will run at least once
|
||||||
do {
|
do {
|
||||||
// If we don't have a digit, bail
|
// If we don't have a digit, bail
|
||||||
if (*ptr < '0' || *ptr > '9')
|
if (*ptr < '0' || *ptr > '9') {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
// Avoid overflowing!
|
// Avoid overflowing!
|
||||||
if (param > UINT8_MAX - (*ptr - '0')) {
|
if (param > UINT8_MAX - (*ptr - '0')) {
|
||||||
if (!warned)
|
if (!warned) {
|
||||||
warnx("Invalid warning flag \"%s\": capping parameter at 255", flag);
|
warnx("Invalid warning flag \"%s\": capping parameter at 255", flag);
|
||||||
|
}
|
||||||
warned = true; // Only warn once, cap always
|
warned = true; // Only warn once, cap always
|
||||||
param = 255;
|
param = 255;
|
||||||
continue;
|
continue;
|
||||||
@@ -203,8 +215,9 @@ void processWarningFlag(char const *flag) {
|
|||||||
if (*ptr == '\0') {
|
if (*ptr == '\0') {
|
||||||
rootFlag.resize(equals);
|
rootFlag.resize(equals);
|
||||||
// `-W<flag>=0` is equivalent to `-Wno-<flag>`
|
// `-W<flag>=0` is equivalent to `-Wno-<flag>`
|
||||||
if (param == 0)
|
if (param == 0) {
|
||||||
state.state = WARNING_DISABLED;
|
state.state = WARNING_DISABLED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,8 +231,9 @@ void processWarningFlag(char const *flag) {
|
|||||||
assume(paramWarning.defaultLevel <= maxParam);
|
assume(paramWarning.defaultLevel <= maxParam);
|
||||||
|
|
||||||
if (rootFlag == warningFlags[baseID].name) { // Match!
|
if (rootFlag == warningFlags[baseID].name) { // Match!
|
||||||
if (rootFlag == "numeric-string")
|
if (rootFlag == "numeric-string") {
|
||||||
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
|
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
|
||||||
|
}
|
||||||
|
|
||||||
// If making the warning an error but param is 0, set to the maximum
|
// 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
|
// This accommodates `-Werror=<flag>`, but also `-Werror=<flag>=0`, which is
|
||||||
@@ -229,7 +243,7 @@ void processWarningFlag(char const *flag) {
|
|||||||
if (param == 0) {
|
if (param == 0) {
|
||||||
param = paramWarning.defaultLevel;
|
param = paramWarning.defaultLevel;
|
||||||
} else if (param > maxParam) {
|
} else if (param > maxParam) {
|
||||||
if (param != 255) // Don't warn if already capped
|
if (param != 255) { // Don't warn if already capped
|
||||||
warnx(
|
warnx(
|
||||||
"Invalid parameter %" PRIu8
|
"Invalid parameter %" PRIu8
|
||||||
" for warning flag \"%s\"; capping at maximum %" PRIu8,
|
" for warning flag \"%s\"; capping at maximum %" PRIu8,
|
||||||
@@ -237,16 +251,18 @@ void processWarningFlag(char const *flag) {
|
|||||||
rootFlag.c_str(),
|
rootFlag.c_str(),
|
||||||
maxParam
|
maxParam
|
||||||
);
|
);
|
||||||
|
}
|
||||||
param = maxParam;
|
param = maxParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the first <param> to enabled/error, and disable the rest
|
// Set the first <param> to enabled/error, and disable the rest
|
||||||
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
||||||
WarningState &warning = warningStates.flagStates[baseID + ofs];
|
WarningState &warning = warningStates.flagStates[baseID + ofs];
|
||||||
if (ofs < param)
|
if (ofs < param) {
|
||||||
warning.update(state);
|
warning.update(state);
|
||||||
else
|
} else {
|
||||||
warning.state = WARNING_DISABLED;
|
warning.state = WARNING_DISABLED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -259,8 +275,9 @@ void processWarningFlag(char const *flag) {
|
|||||||
if (rootFlag == metaWarning.name) {
|
if (rootFlag == metaWarning.name) {
|
||||||
// Set each of the warning flags that meets this level
|
// Set each of the warning flags that meets this level
|
||||||
for (WarningID id : EnumSeq(NB_WARNINGS)) {
|
for (WarningID id : EnumSeq(NB_WARNINGS)) {
|
||||||
if (metaWarning.level >= warningFlags[id].level)
|
if (metaWarning.level >= warningFlags[id].level) {
|
||||||
warningStates.metaStates[id].update(state);
|
warningStates.metaStates[id].update(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -299,16 +316,18 @@ void error(char const *fmt, ...) {
|
|||||||
|
|
||||||
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
|
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
if (nbErrors == maxErrors)
|
if (nbErrors == maxErrors) {
|
||||||
errx(
|
errx(
|
||||||
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
|
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
|
||||||
"aborted!",
|
"aborted!",
|
||||||
maxErrors,
|
maxErrors,
|
||||||
maxErrors == 1 ? "" : "s"
|
maxErrors == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void fatalerror(char const *fmt, ...) {
|
[[noreturn]]
|
||||||
|
void fatalerror(char const *fmt, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|||||||
@@ -15,14 +15,17 @@ fi
|
|||||||
BISON_FLAGS="-Wall -Dparse.lac=full -Dlr.type=ielr"
|
BISON_FLAGS="-Wall -Dparse.lac=full -Dlr.type=ielr"
|
||||||
|
|
||||||
# Set some optimization flags on versions that support them
|
# Set some optimization flags on versions that support them
|
||||||
if [ "$BISON_MAJOR" -eq 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 5 ]; then
|
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 5 ]; then
|
||||||
BISON_FLAGS="$BISON_FLAGS -Dapi.token.raw=true"
|
BISON_FLAGS="$BISON_FLAGS -Dapi.token.raw=true"
|
||||||
fi
|
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"
|
BISON_FLAGS="$BISON_FLAGS -Dparse.error=detailed"
|
||||||
else
|
else
|
||||||
BISON_FLAGS="$BISON_FLAGS -Dparse.error=verbose"
|
BISON_FLAGS="$BISON_FLAGS -Dparse.error=verbose"
|
||||||
fi
|
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
|
# Replace the arguments to this script ($@) with the ones in $BISON_FLAGS
|
||||||
eval "set -- $BISON_FLAGS"
|
eval "set -- $BISON_FLAGS"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "error.hpp"
|
#include "error.hpp"
|
||||||
|
|
||||||
@@ -22,7 +22,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
|||||||
putc('\n', stderr);
|
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);
|
char const *error = strerror(errno);
|
||||||
|
|
||||||
fprintf(stderr, "error: ");
|
fprintf(stderr, "error: ");
|
||||||
@@ -32,7 +33,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
|||||||
exit(1);
|
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: ");
|
fprintf(stderr, "error: ");
|
||||||
vfprintf(stderr, fmt, ap);
|
vfprintf(stderr, fmt, ap);
|
||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
@@ -56,14 +58,16 @@ void warnx(char const *fmt, ...) {
|
|||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void err(char const *fmt, ...) {
|
[[noreturn]]
|
||||||
|
void err(char const *fmt, ...) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
verr(fmt, ap);
|
verr(fmt, ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void errx(char const *fmt, ...) {
|
[[noreturn]]
|
||||||
|
void errx(char const *fmt, ...) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
va_start(ap, fmt);
|
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"
|
#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) {
|
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
|
||||||
FILE *f = stderr;
|
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);
|
putc('\n', f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int getopt(int argc, char *argv[], char const *optstring) {
|
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;
|
musl_optind = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (musl_optind >= argc || !argv[musl_optind])
|
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (argv[musl_optind][0] != '-') {
|
if (argv[musl_optind][0] != '-') {
|
||||||
if (optstring[0] == '-') {
|
if (optstring[0] == '-') {
|
||||||
@@ -46,18 +48,21 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!argv[musl_optind][1])
|
if (!argv[musl_optind][1]) {
|
||||||
return -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;
|
return musl_optind++, -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (!musl_optpos)
|
if (!musl_optpos) {
|
||||||
musl_optpos++;
|
musl_optpos++;
|
||||||
|
}
|
||||||
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
||||||
if (k < 0) {
|
if (k < 0) {
|
||||||
k = 1;
|
k = 1;
|
||||||
c = 0xFFFD; /* replacement char */
|
c = 0xFFFD; // replacement char
|
||||||
}
|
}
|
||||||
optchar = argv[musl_optind] + musl_optpos;
|
optchar = argv[musl_optind] + musl_optpos;
|
||||||
musl_optpos += k;
|
musl_optpos += k;
|
||||||
@@ -67,23 +72,26 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
musl_optpos = 0;
|
musl_optpos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (optstring[0] == '-' || optstring[0] == '+')
|
if (optstring[0] == '-' || optstring[0] == '+') {
|
||||||
optstring++;
|
optstring++;
|
||||||
|
}
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
d = 0;
|
d = 0;
|
||||||
do {
|
do {
|
||||||
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
||||||
if (l > 0)
|
if (l > 0) {
|
||||||
i += l;
|
i += l;
|
||||||
else
|
} else {
|
||||||
i++;
|
i++;
|
||||||
|
}
|
||||||
} while (l && d != c);
|
} while (l && d != c);
|
||||||
|
|
||||||
if (d != c || c == ':') {
|
if (d != c || c == ':') {
|
||||||
musl_optopt = c;
|
musl_optopt = c;
|
||||||
if (optstring[0] != ':' && musl_opterr)
|
if (optstring[0] != ':' && musl_opterr) {
|
||||||
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
||||||
|
}
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
if (optstring[i] == ':') {
|
if (optstring[i] == ':') {
|
||||||
@@ -94,10 +102,12 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
|||||||
}
|
}
|
||||||
if (musl_optind > argc) {
|
if (musl_optind > argc) {
|
||||||
musl_optopt = c;
|
musl_optopt = c;
|
||||||
if (optstring[0] == ':')
|
if (optstring[0] == ':') {
|
||||||
return ':';
|
return ':';
|
||||||
if (musl_opterr)
|
}
|
||||||
|
if (musl_opterr) {
|
||||||
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
|
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
|
||||||
|
}
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,8 +118,9 @@ static void permute(char **argv, int dest, int src) {
|
|||||||
char *tmp = argv[src];
|
char *tmp = argv[src];
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = src; i > dest; i--)
|
for (i = src; i > dest; i--) {
|
||||||
argv[i] = argv[i - 1];
|
argv[i] = argv[i - 1];
|
||||||
|
}
|
||||||
argv[dest] = tmp;
|
argv[dest] = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,17 +139,20 @@ static int musl_getopt_long(
|
|||||||
musl_optind = 1;
|
musl_optind = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (musl_optind >= argc || !argv[musl_optind])
|
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
skipped = musl_optind;
|
skipped = musl_optind;
|
||||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||||
int i;
|
int i;
|
||||||
for (i = musl_optind;; i++) {
|
for (i = musl_optind;; i++) {
|
||||||
if (i >= argc || !argv[i])
|
if (i >= argc || !argv[i]) {
|
||||||
return -1;
|
return -1;
|
||||||
if (argv[i][0] == '-' && argv[i][1])
|
}
|
||||||
|
if (argv[i][0] == '-' && argv[i][1]) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
musl_optind = i;
|
musl_optind = i;
|
||||||
}
|
}
|
||||||
@@ -147,8 +161,9 @@ static int musl_getopt_long(
|
|||||||
if (resumed > skipped) {
|
if (resumed > skipped) {
|
||||||
int i, cnt = musl_optind - resumed;
|
int i, cnt = musl_optind - resumed;
|
||||||
|
|
||||||
for (i = 0; i < cnt; i++)
|
for (i = 0; i < cnt; i++) {
|
||||||
permute(argv, skipped, musl_optind - 1);
|
permute(argv, skipped, musl_optind - 1);
|
||||||
|
}
|
||||||
musl_optind = skipped + cnt;
|
musl_optind = skipped + cnt;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@@ -169,14 +184,16 @@ static int musl_getopt_long_core(
|
|||||||
char const *name = longopts[i].name;
|
char const *name = longopts[i].name;
|
||||||
|
|
||||||
opt = start;
|
opt = start;
|
||||||
if (*opt == '-')
|
if (*opt == '-') {
|
||||||
opt++;
|
opt++;
|
||||||
|
}
|
||||||
while (*opt && *opt != '=' && *opt == *name) {
|
while (*opt && *opt != '=' && *opt == *name) {
|
||||||
name++;
|
name++;
|
||||||
opt++;
|
opt++;
|
||||||
}
|
}
|
||||||
if (*opt && *opt != '=')
|
if (*opt && *opt != '=') {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
arg = opt;
|
arg = opt;
|
||||||
match = i;
|
match = i;
|
||||||
if (!*name) {
|
if (!*name) {
|
||||||
@@ -191,8 +208,9 @@ static int musl_getopt_long_core(
|
|||||||
for (i = 0; optstring[i]; i++) {
|
for (i = 0; optstring[i]; i++) {
|
||||||
int j = 0;
|
int j = 0;
|
||||||
|
|
||||||
while (j < l && start[j] == optstring[i + j])
|
while (j < l && start[j] == optstring[i + j]) {
|
||||||
j++;
|
j++;
|
||||||
|
}
|
||||||
if (j == l) {
|
if (j == l) {
|
||||||
cnt++;
|
cnt++;
|
||||||
break;
|
break;
|
||||||
@@ -206,8 +224,9 @@ static int musl_getopt_long_core(
|
|||||||
if (*opt == '=') {
|
if (*opt == '=') {
|
||||||
if (!longopts[i].has_arg) {
|
if (!longopts[i].has_arg) {
|
||||||
musl_optopt = longopts[i].val;
|
musl_optopt = longopts[i].val;
|
||||||
if (colon || !musl_opterr)
|
if (colon || !musl_opterr) {
|
||||||
return '?';
|
return '?';
|
||||||
|
}
|
||||||
musl_getopt_msg(
|
musl_getopt_msg(
|
||||||
argv[0],
|
argv[0],
|
||||||
": option does not take an argument: ",
|
": option does not take an argument: ",
|
||||||
@@ -221,10 +240,12 @@ static int musl_getopt_long_core(
|
|||||||
musl_optarg = argv[musl_optind];
|
musl_optarg = argv[musl_optind];
|
||||||
if (!musl_optarg) {
|
if (!musl_optarg) {
|
||||||
musl_optopt = longopts[i].val;
|
musl_optopt = longopts[i].val;
|
||||||
if (colon)
|
if (colon) {
|
||||||
return ':';
|
return ':';
|
||||||
if (!musl_opterr)
|
}
|
||||||
|
if (!musl_opterr) {
|
||||||
return '?';
|
return '?';
|
||||||
|
}
|
||||||
musl_getopt_msg(
|
musl_getopt_msg(
|
||||||
argv[0],
|
argv[0],
|
||||||
": option requires an argument: ",
|
": option requires an argument: ",
|
||||||
@@ -235,8 +256,9 @@ static int musl_getopt_long_core(
|
|||||||
}
|
}
|
||||||
musl_optind++;
|
musl_optind++;
|
||||||
}
|
}
|
||||||
if (idx)
|
if (idx) {
|
||||||
*idx = i;
|
*idx = i;
|
||||||
|
}
|
||||||
if (longopts[i].flag) {
|
if (longopts[i].flag) {
|
||||||
*longopts[i].flag = longopts[i].val;
|
*longopts[i].flag = longopts[i].val;
|
||||||
return 0;
|
return 0;
|
||||||
@@ -245,13 +267,14 @@ static int musl_getopt_long_core(
|
|||||||
}
|
}
|
||||||
if (argv[musl_optind][1] == '-') {
|
if (argv[musl_optind][1] == '-') {
|
||||||
musl_optopt = 0;
|
musl_optopt = 0;
|
||||||
if (!colon && musl_opterr)
|
if (!colon && musl_opterr) {
|
||||||
musl_getopt_msg(
|
musl_getopt_msg(
|
||||||
argv[0],
|
argv[0],
|
||||||
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
|
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
|
||||||
argv[musl_optind] + 2,
|
argv[musl_optind] + 2,
|
||||||
strlen(argv[musl_optind] + 2)
|
strlen(argv[musl_optind] + 2)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
musl_optind++;
|
musl_optind++;
|
||||||
return '?';
|
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"
|
#include "extern/utf8decoder.hpp"
|
||||||
|
|
||||||
static uint8_t const utf8d[] = {
|
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, // 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, // 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, // 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, // 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, // 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, // 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, // 60..6f
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70..7f */
|
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 */
|
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 */
|
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, // a0..af
|
||||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* b0..bf */
|
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 */
|
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 */
|
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 */
|
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 */
|
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 */
|
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, 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, 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, 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, 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, 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, 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, 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 */
|
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) {
|
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/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@@ -17,27 +17,26 @@
|
|||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
#include "version.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
|
// 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
|
||||||
* 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,
|
||||||
* Also, make sure long opts don't create ambiguity:
|
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||||
* A long opt's name should start with the same letter as its short opt,
|
// This is because long opt matching, even to a single char, is prioritized
|
||||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
// over short opt matching.
|
||||||
* This is because long opt matching, even to a single char, is prioritized
|
|
||||||
* over short opt matching
|
|
||||||
*/
|
|
||||||
static option const longopts[] = {
|
static option const longopts[] = {
|
||||||
{"color-only", no_argument, nullptr, 'C'},
|
{"color-only", no_argument, nullptr, 'C'},
|
||||||
{"color-compatible", no_argument, nullptr, 'c'},
|
{"color-compatible", no_argument, nullptr, 'c'},
|
||||||
{"fix-spec", required_argument, nullptr, 'f'},
|
{"fix-spec", required_argument, nullptr, 'f'},
|
||||||
|
{"help", no_argument, nullptr, 'h'},
|
||||||
{"game-id", required_argument, nullptr, 'i'},
|
{"game-id", required_argument, nullptr, 'i'},
|
||||||
{"non-japanese", no_argument, nullptr, 'j'},
|
{"non-japanese", no_argument, nullptr, 'j'},
|
||||||
{"new-licensee", required_argument, nullptr, 'k'},
|
{"new-licensee", required_argument, nullptr, 'k'},
|
||||||
@@ -46,6 +45,7 @@ static option const longopts[] = {
|
|||||||
{"mbc-type", required_argument, nullptr, 'm'},
|
{"mbc-type", required_argument, nullptr, 'm'},
|
||||||
{"rom-version", required_argument, nullptr, 'n'},
|
{"rom-version", required_argument, nullptr, 'n'},
|
||||||
{"overwrite", no_argument, nullptr, 'O'},
|
{"overwrite", no_argument, nullptr, 'O'},
|
||||||
|
{"output", required_argument, nullptr, 'o'},
|
||||||
{"pad-value", required_argument, nullptr, 'p'},
|
{"pad-value", required_argument, nullptr, 'p'},
|
||||||
{"ram-size", required_argument, nullptr, 'r'},
|
{"ram-size", required_argument, nullptr, 'r'},
|
||||||
{"sgb-compatible", no_argument, nullptr, 's'},
|
{"sgb-compatible", no_argument, nullptr, 's'},
|
||||||
@@ -55,9 +55,10 @@ static option const longopts[] = {
|
|||||||
{nullptr, no_argument, nullptr, 0 }
|
{nullptr, no_argument, nullptr, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
static void printUsage() {
|
static void printUsage() {
|
||||||
fputs(
|
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"
|
" [-L <logo_file>] [-l <licensee_byte>] [-m <mbc_type>]\n"
|
||||||
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
|
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
|
||||||
" <file> ...\n"
|
" <file> ...\n"
|
||||||
@@ -66,6 +67,7 @@ static void printUsage() {
|
|||||||
" to the man page for a list of values\n"
|
" to the man page for a list of values\n"
|
||||||
" -p, --pad-value <value> pad to the next valid size using this value\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"
|
" -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, --version print RGBFIX version and exit\n"
|
||||||
" -v, --validate fix the header logo and both checksums (-f lhg)\n"
|
" -v, --validate fix the header logo and both checksums (-f lhg)\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -73,18 +75,21 @@ static void printUsage() {
|
|||||||
stderr
|
stderr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
static uint8_t nbErrors;
|
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_list ap;
|
||||||
|
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
vfprintf(stderr, fmt, ap);
|
vfprintf(stderr, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
if (nbErrors != UINT8_MAX)
|
if (nbErrors != UINT8_MAX) {
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MbcType {
|
enum MbcType {
|
||||||
@@ -184,23 +189,23 @@ static void printAcceptedMBCNames() {
|
|||||||
|
|
||||||
static uint8_t tpp1Rev[2];
|
static uint8_t tpp1Rev[2];
|
||||||
|
|
||||||
/*
|
|
||||||
* @return False on failure
|
|
||||||
*/
|
|
||||||
static bool readMBCSlice(char const *&name, char const *expected) {
|
static bool readMBCSlice(char const *&name, char const *expected) {
|
||||||
while (*expected) {
|
while (*expected) {
|
||||||
char c = *name++;
|
char c = *name++;
|
||||||
|
|
||||||
if (c == '\0') // Name too short
|
if (c == '\0') { // Name too short
|
||||||
return false;
|
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';
|
c = c - 'a' + 'A';
|
||||||
else if (c == '_') // Treat underscores as spaces
|
} else if (c == '_') { // Treat underscores as spaces
|
||||||
c = ' ';
|
c = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
if (c != *expected++)
|
if (c != *expected++) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -223,10 +228,12 @@ static MbcType parseMBC(char const *name) {
|
|||||||
char *endptr;
|
char *endptr;
|
||||||
unsigned long mbc = strtoul(name, &endptr, base);
|
unsigned long mbc = strtoul(name, &endptr, base);
|
||||||
|
|
||||||
if (*endptr)
|
if (*endptr) {
|
||||||
return MBC_BAD;
|
return MBC_BAD;
|
||||||
if (mbc > 0xFF)
|
}
|
||||||
|
if (mbc > 0xFF) {
|
||||||
return MBC_BAD_RANGE;
|
return MBC_BAD_RANGE;
|
||||||
|
}
|
||||||
return static_cast<MbcType>(mbc);
|
return static_cast<MbcType>(mbc);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -235,13 +242,15 @@ static MbcType parseMBC(char const *name) {
|
|||||||
char const *ptr = name;
|
char const *ptr = name;
|
||||||
|
|
||||||
// Trim off leading whitespace
|
// Trim off leading whitespace
|
||||||
while (*ptr == ' ' || *ptr == '\t')
|
while (*ptr == ' ' || *ptr == '\t') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
#define tryReadSlice(expected) \
|
#define tryReadSlice(expected) \
|
||||||
do { \
|
do { \
|
||||||
if (!readMBCSlice(ptr, expected)) \
|
if (!readMBCSlice(ptr, expected)) { \
|
||||||
return MBC_BAD; \
|
return MBC_BAD; \
|
||||||
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
switch (*ptr++) {
|
switch (*ptr++) {
|
||||||
@@ -249,8 +258,9 @@ static MbcType parseMBC(char const *name) {
|
|||||||
case 'r':
|
case 'r':
|
||||||
tryReadSlice("OM");
|
tryReadSlice("OM");
|
||||||
// Handle optional " ONLY"
|
// Handle optional " ONLY"
|
||||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
if (*ptr == 'O' || *ptr == 'o') {
|
if (*ptr == 'O' || *ptr == 'o') {
|
||||||
ptr++;
|
ptr++;
|
||||||
tryReadSlice("NLY");
|
tryReadSlice("NLY");
|
||||||
@@ -325,8 +335,9 @@ static MbcType parseMBC(char const *name) {
|
|||||||
case 'P': {
|
case 'P': {
|
||||||
tryReadSlice("P1");
|
tryReadSlice("P1");
|
||||||
// Parse version
|
// Parse version
|
||||||
while (*ptr == ' ' || *ptr == '_')
|
while (*ptr == ' ' || *ptr == '_') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
// Major
|
// Major
|
||||||
char *endptr;
|
char *endptr;
|
||||||
unsigned long val = strtoul(ptr, &endptr, 10);
|
unsigned long val = strtoul(ptr, &endptr, 10);
|
||||||
@@ -383,27 +394,33 @@ static MbcType parseMBC(char const *name) {
|
|||||||
|
|
||||||
// Read "additional features"
|
// Read "additional features"
|
||||||
uint8_t features = 0;
|
uint8_t features = 0;
|
||||||
#define RAM (1 << 7)
|
// clang-format off: vertically align values
|
||||||
#define BATTERY (1 << 6)
|
static constexpr uint8_t RAM = 1 << 7;
|
||||||
#define TIMER (1 << 5)
|
static constexpr uint8_t BATTERY = 1 << 6;
|
||||||
#define RUMBLE (1 << 4)
|
static constexpr uint8_t TIMER = 1 << 5;
|
||||||
#define SENSOR (1 << 3)
|
static constexpr uint8_t RUMBLE = 1 << 4;
|
||||||
#define MULTIRUMBLE (1 << 2)
|
static constexpr uint8_t SENSOR = 1 << 3;
|
||||||
|
static constexpr uint8_t MULTIRUMBLE = 1 << 2;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// Trim off trailing whitespace
|
// Trim off trailing whitespace
|
||||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
// If done, start processing "features"
|
// If done, start processing "features"
|
||||||
if (!*ptr)
|
if (!*ptr) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
// We expect a '+' at this point
|
// We expect a '+' at this point
|
||||||
if (*ptr++ != '+')
|
if (*ptr++ != '+') {
|
||||||
return MBC_BAD;
|
return MBC_BAD;
|
||||||
|
}
|
||||||
// Trim off leading whitespace
|
// Trim off leading whitespace
|
||||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
switch (*ptr++) {
|
switch (*ptr++) {
|
||||||
case 'B': // BATTERY
|
case 'B': // BATTERY
|
||||||
@@ -428,8 +445,9 @@ static MbcType parseMBC(char const *name) {
|
|||||||
break;
|
break;
|
||||||
case 'A':
|
case 'A':
|
||||||
case 'a':
|
case 'a':
|
||||||
if (*ptr != 'M' && *ptr != 'm')
|
if (*ptr != 'M' && *ptr != 'm') {
|
||||||
return MBC_BAD;
|
return MBC_BAD;
|
||||||
|
}
|
||||||
ptr++;
|
ptr++;
|
||||||
features |= RAM;
|
features |= RAM;
|
||||||
break;
|
break;
|
||||||
@@ -458,8 +476,9 @@ static MbcType parseMBC(char const *name) {
|
|||||||
|
|
||||||
switch (mbc) {
|
switch (mbc) {
|
||||||
case ROM:
|
case ROM:
|
||||||
if (!features)
|
if (!features) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
mbc = ROM_RAM - 1;
|
mbc = ROM_RAM - 1;
|
||||||
static_assert(ROM_RAM + 1 == ROM_RAM_BATTERY, "Enum sanity check failed!");
|
static_assert(ROM_RAM + 1 == ROM_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
static_assert(MBC1 + 1 == MBC1_RAM, "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]];
|
[[fallthrough]];
|
||||||
case MBC1:
|
case MBC1:
|
||||||
case MMM01:
|
case MMM01:
|
||||||
if (features == RAM)
|
if (features == RAM) {
|
||||||
mbc++;
|
mbc++;
|
||||||
else if (features == (RAM | BATTERY))
|
} else if (features == (RAM | BATTERY)) {
|
||||||
mbc += 2;
|
mbc += 2;
|
||||||
else if (features)
|
} else if (features) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MBC2:
|
case MBC2:
|
||||||
if (features == BATTERY)
|
if (features == BATTERY) {
|
||||||
mbc = MBC2_BATTERY;
|
mbc = MBC2_BATTERY;
|
||||||
else if (features)
|
} else if (features) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MBC3:
|
case MBC3:
|
||||||
// Handle timer, which also requires battery
|
// Handle timer, which also requires battery
|
||||||
if (features & TIMER) {
|
if (features & TIMER) {
|
||||||
if (!(features & BATTERY))
|
if (!(features & BATTERY)) {
|
||||||
fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n");
|
fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n");
|
||||||
|
}
|
||||||
features &= ~(TIMER | BATTERY); // Reset those bits
|
features &= ~(TIMER | BATTERY); // Reset those bits
|
||||||
mbc = MBC3_TIMER_BATTERY;
|
mbc = MBC3_TIMER_BATTERY;
|
||||||
// RAM is handled below
|
// RAM is handled below
|
||||||
@@ -498,12 +520,13 @@ static MbcType parseMBC(char const *name) {
|
|||||||
static_assert(
|
static_assert(
|
||||||
MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!"
|
MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!"
|
||||||
);
|
);
|
||||||
if (features == RAM)
|
if (features == RAM) {
|
||||||
mbc++;
|
mbc++;
|
||||||
else if (features == (RAM | BATTERY))
|
} else if (features == (RAM | BATTERY)) {
|
||||||
mbc += 2;
|
mbc += 2;
|
||||||
else if (features)
|
} else if (features) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MBC5:
|
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 + 2 == MBC5_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
static_assert(MBC5_RUMBLE + 1 == MBC5_RUMBLE_RAM, "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!");
|
static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY, "Enum sanity check failed!");
|
||||||
if (features == RAM)
|
if (features == RAM) {
|
||||||
mbc++;
|
mbc++;
|
||||||
else if (features == (RAM | BATTERY))
|
} else if (features == (RAM | BATTERY)) {
|
||||||
mbc += 2;
|
mbc += 2;
|
||||||
else if (features)
|
} else if (features) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MBC6:
|
case MBC6:
|
||||||
@@ -528,45 +552,56 @@ static MbcType parseMBC(char const *name) {
|
|||||||
case BANDAI_TAMA5:
|
case BANDAI_TAMA5:
|
||||||
case HUC3:
|
case HUC3:
|
||||||
// No extra features accepted
|
// No extra features accepted
|
||||||
if (features)
|
if (features) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||||
if (features != (SENSOR | RUMBLE | RAM | BATTERY))
|
if (features != (SENSOR | RUMBLE | RAM | BATTERY)) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HUC1_RAM_BATTERY:
|
case HUC1_RAM_BATTERY:
|
||||||
if (features != (RAM | BATTERY)) // HuC1 expects RAM+BATTERY
|
if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TPP1:
|
case TPP1:
|
||||||
if (features & RAM)
|
if (features & RAM) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size"
|
stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size"
|
||||||
);
|
);
|
||||||
if (features & BATTERY)
|
}
|
||||||
|
if (features & BATTERY) {
|
||||||
mbc |= 0x08;
|
mbc |= 0x08;
|
||||||
if (features & TIMER)
|
}
|
||||||
|
if (features & TIMER) {
|
||||||
mbc |= 0x04;
|
mbc |= 0x04;
|
||||||
if (features & MULTIRUMBLE)
|
}
|
||||||
|
if (features & MULTIRUMBLE) {
|
||||||
mbc |= 0x03; // Also set the rumble flag
|
mbc |= 0x03; // Also set the rumble flag
|
||||||
if (features & RUMBLE)
|
}
|
||||||
|
if (features & RUMBLE) {
|
||||||
mbc |= 0x01;
|
mbc |= 0x01;
|
||||||
if (features & SENSOR)
|
}
|
||||||
|
if (features & SENSOR) {
|
||||||
return MBC_WRONG_FEATURES;
|
return MBC_WRONG_FEATURES;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim off trailing whitespace
|
// Trim off trailing whitespace
|
||||||
while (*ptr == ' ' || *ptr == '\t')
|
while (*ptr == ' ' || *ptr == '\t') {
|
||||||
ptr++;
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
// If there is still something past the whitespace, error out
|
// If there is still something past the whitespace, error out
|
||||||
if (*ptr)
|
if (*ptr) {
|
||||||
return MBC_BAD;
|
return MBC_BAD;
|
||||||
|
}
|
||||||
|
|
||||||
return static_cast<MbcType>(mbc);
|
return static_cast<MbcType>(mbc);
|
||||||
}
|
}
|
||||||
@@ -665,10 +700,12 @@ static char const *mbcName(MbcType type) {
|
|||||||
case MBC_WRONG_FEATURES:
|
case MBC_WRONG_FEATURES:
|
||||||
case MBC_BAD_RANGE:
|
case MBC_BAD_RANGE:
|
||||||
case MBC_BAD_TPP1:
|
case MBC_BAD_TPP1:
|
||||||
|
// LCOV_EXCL_START
|
||||||
unreachable_();
|
unreachable_();
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable_();
|
unreachable_();
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool hasRAM(MbcType type) {
|
static bool hasRAM(MbcType type) {
|
||||||
@@ -682,8 +719,7 @@ static bool hasRAM(MbcType type) {
|
|||||||
case MBC3_TIMER_BATTERY:
|
case MBC3_TIMER_BATTERY:
|
||||||
case MBC5:
|
case MBC5:
|
||||||
case MBC5_RUMBLE:
|
case MBC5_RUMBLE:
|
||||||
case MBC6: // TODO: not sure
|
case BANDAI_TAMA5: // "Game de Hakken!! Tamagotchi - Osutchi to Mesutchi" has RAM size 0
|
||||||
case BANDAI_TAMA5: // TODO: not sure
|
|
||||||
case MBC_NONE:
|
case MBC_NONE:
|
||||||
case MBC_BAD:
|
case MBC_BAD:
|
||||||
case MBC_WRONG_FEATURES:
|
case MBC_WRONG_FEATURES:
|
||||||
@@ -704,6 +740,7 @@ static bool hasRAM(MbcType type) {
|
|||||||
case MBC5_RAM_BATTERY:
|
case MBC5_RAM_BATTERY:
|
||||||
case MBC5_RUMBLE_RAM:
|
case MBC5_RUMBLE_RAM:
|
||||||
case MBC5_RUMBLE_RAM_BATTERY:
|
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 MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||||
case POCKET_CAMERA:
|
case POCKET_CAMERA:
|
||||||
case HUC3:
|
case HUC3:
|
||||||
@@ -730,7 +767,7 @@ static bool hasRAM(MbcType type) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable_();
|
unreachable_(); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t const nintendoLogo[] = {
|
static uint8_t const nintendoLogo[] = {
|
||||||
@@ -740,12 +777,14 @@ static uint8_t const nintendoLogo[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static uint8_t fixSpec = 0;
|
static uint8_t fixSpec = 0;
|
||||||
#define FIX_LOGO (1 << 7)
|
// clang-format off: vertically align values
|
||||||
#define TRASH_LOGO (1 << 6)
|
static constexpr uint8_t FIX_LOGO = 1 << 7;
|
||||||
#define FIX_HEADER_SUM (1 << 5)
|
static constexpr uint8_t TRASH_LOGO = 1 << 6;
|
||||||
#define TRASH_HEADER_SUM (1 << 4)
|
static constexpr uint8_t FIX_HEADER_SUM = 1 << 5;
|
||||||
#define FIX_GLOBAL_SUM (1 << 3)
|
static constexpr uint8_t TRASH_HEADER_SUM = 1 << 4;
|
||||||
#define TRASH_GLOBAL_SUM (1 << 2)
|
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 enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone
|
||||||
static char const *gameID = nullptr;
|
static char const *gameID = nullptr;
|
||||||
@@ -778,11 +817,14 @@ static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
|
|||||||
while (len) {
|
while (len) {
|
||||||
ssize_t ret = read(fd, buf, len);
|
ssize_t ret = read(fd, buf, len);
|
||||||
|
|
||||||
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted
|
// Return errors, unless we only were interrupted
|
||||||
return -1;
|
if (ret == -1 && errno != EINTR) {
|
||||||
|
return -1; // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
// EOF reached
|
// EOF reached
|
||||||
if (ret == 0)
|
if (ret == 0) {
|
||||||
return total;
|
return total;
|
||||||
|
}
|
||||||
// If anything was read, accumulate it, and continue
|
// If anything was read, accumulate it, and continue
|
||||||
if (ret != -1) {
|
if (ret != -1) {
|
||||||
total += ret;
|
total += ret;
|
||||||
@@ -803,43 +845,31 @@ static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
|
|||||||
while (len) {
|
while (len) {
|
||||||
ssize_t ret = write(fd, buf, len);
|
ssize_t ret = write(fd, buf, len);
|
||||||
|
|
||||||
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted
|
// Return errors, unless we only were interrupted
|
||||||
return -1;
|
if (ret == -1 && errno != EINTR) {
|
||||||
// EOF reached
|
return -1; // LCOV_EXCL_LINE
|
||||||
if (ret == 0)
|
}
|
||||||
return total;
|
// If anything was written, accumulate it, and continue
|
||||||
// If anything was read, accumulate it, and continue
|
|
||||||
if (ret != -1) {
|
if (ret != -1) {
|
||||||
total += ret;
|
total += ret;
|
||||||
len -= ret;
|
len -= ret;
|
||||||
|
buf += ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return total;
|
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) {
|
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName) {
|
||||||
uint8_t origByte = rom0[addr];
|
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);
|
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
|
||||||
|
}
|
||||||
|
|
||||||
rom0[addr] = fixedByte;
|
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(
|
static void overwriteBytes(
|
||||||
uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size, char const *areaName
|
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);
|
memcpy(&rom0[startAddr], fixed, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static void
|
||||||
* @param input File descriptor to be used for reading
|
processFile(int input, int output, char const *name, off_t fileSize, bool expectFileSize) {
|
||||||
* @param output File descriptor to be used for writing, may be equal to `input`
|
if (expectFileSize) {
|
||||||
* @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)
|
|
||||||
assume(fileSize != 0);
|
assume(fileSize != 0);
|
||||||
else
|
} else {
|
||||||
assume(fileSize == 0);
|
assume(fileSize == 0);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t rom0[BANK_SIZE];
|
uint8_t rom0[BANK_SIZE];
|
||||||
ssize_t rom0Len = readBytes(input, rom0, sizeof(rom0));
|
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;
|
ssize_t headerSize = (cartridgeType & 0xFF00) == TPP1 ? 0x154 : 0x150;
|
||||||
|
|
||||||
if (rom0Len == -1) {
|
if (rom0Len == -1) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
|
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (rom0Len < headerSize) {
|
} else if (rom0Len < headerSize) {
|
||||||
report(
|
report(
|
||||||
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
"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
|
// 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");
|
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");
|
overwriteBytes(rom0, 0x134, reinterpret_cast<uint8_t const *>(title), titleLen, "title");
|
||||||
|
}
|
||||||
|
|
||||||
if (gameID)
|
if (gameID) {
|
||||||
overwriteBytes(
|
overwriteBytes(
|
||||||
rom0,
|
rom0, 0x13F, reinterpret_cast<uint8_t const *>(gameID), gameIDLen, "manufacturer code"
|
||||||
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");
|
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
|
||||||
|
}
|
||||||
|
|
||||||
if (newLicensee)
|
if (newLicensee) {
|
||||||
overwriteBytes(
|
overwriteBytes(
|
||||||
rom0,
|
rom0,
|
||||||
0x144,
|
0x144,
|
||||||
@@ -916,9 +943,11 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
newLicenseeLen,
|
newLicenseeLen,
|
||||||
"new licensee code"
|
"new licensee code"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (sgb)
|
if (sgb) {
|
||||||
overwriteByte(rom0, 0x146, 0x03, "SGB flag");
|
overwriteByte(rom0, 0x146, 0x03, "SGB flag");
|
||||||
|
}
|
||||||
|
|
||||||
// If a valid MBC was specified...
|
// If a valid MBC was specified...
|
||||||
if (cartridgeType < MBC_NONE) {
|
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");
|
overwriteBytes(rom0, 0x150, tpp1Rev, sizeof(tpp1Rev), "TPP1 revision number");
|
||||||
|
|
||||||
if (ramSize != UNSPECIFIED)
|
if (ramSize != UNSPECIFIED) {
|
||||||
overwriteByte(rom0, 0x152, ramSize, "RAM size");
|
overwriteByte(rom0, 0x152, ramSize, "RAM size");
|
||||||
|
}
|
||||||
|
|
||||||
overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags");
|
overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags");
|
||||||
} else {
|
} else {
|
||||||
// Regular mappers
|
// Regular mappers
|
||||||
|
|
||||||
if (ramSize != UNSPECIFIED)
|
if (ramSize != UNSPECIFIED) {
|
||||||
overwriteByte(rom0, 0x149, ramSize, "RAM size");
|
overwriteByte(rom0, 0x149, ramSize, "RAM size");
|
||||||
|
}
|
||||||
|
|
||||||
if (!japanese)
|
if (!japanese) {
|
||||||
overwriteByte(rom0, 0x14A, 0x01, "destination code");
|
overwriteByte(rom0, 0x14A, 0x01, "destination code");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldLicensee != UNSPECIFIED)
|
if (oldLicensee != UNSPECIFIED) {
|
||||||
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
|
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
|
||||||
else if (sgb && rom0[0x14B] != 0x33)
|
} else if (sgb && rom0[0x14B] != 0x33) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n",
|
"warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n",
|
||||||
rom0[0x14B]
|
rom0[0x14B]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (romVersion != UNSPECIFIED)
|
if (romVersion != UNSPECIFIED) {
|
||||||
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
|
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
|
||||||
|
}
|
||||||
|
|
||||||
// Remain to be handled the ROM size, and header checksum.
|
// Remain to be handled the ROM size, and header checksum.
|
||||||
// The latter depends on the former, and so will be handled after it.
|
// 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++;
|
nbBanks++;
|
||||||
|
|
||||||
// Update global checksum, too
|
// Update global checksum, too
|
||||||
for (uint16_t i = 0; i < bankLen; i++)
|
for (uint16_t i = 0; i < bankLen; i++) {
|
||||||
globalSum += romx[totalRomxLen + i];
|
globalSum += romx[totalRomxLen + i];
|
||||||
|
}
|
||||||
totalRomxLen += bankLen;
|
totalRomxLen += bankLen;
|
||||||
}
|
}
|
||||||
// Stop when an incomplete bank has been read
|
// Stop when an incomplete bank has been read
|
||||||
if (bankLen != BANK_SIZE)
|
if (bankLen != BANK_SIZE) {
|
||||||
break;
|
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
|
// 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,
|
// 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
|
// 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));
|
nbBanks = 1 << (CHAR_BIT * sizeof(nbBanks) - clz(nbBanks));
|
||||||
|
}
|
||||||
// Write final ROM size
|
// Write final ROM size
|
||||||
rom0[0x148] = ctz(nbBanks / 2);
|
rom0[0x148] = ctz(nbBanks / 2);
|
||||||
// Alter global checksum based on how many bytes will be added (not counting ROM0)
|
// 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)) {
|
if (fixSpec & (FIX_HEADER_SUM | TRASH_HEADER_SUM)) {
|
||||||
uint8_t sum = 0;
|
uint8_t sum = 0;
|
||||||
|
|
||||||
for (uint16_t i = 0x134; i < 0x14D; i++)
|
for (uint16_t i = 0x134; i < 0x14D; i++) {
|
||||||
sum -= rom0[i] + 1;
|
sum -= rom0[i] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum, "header checksum");
|
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)) {
|
if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
|
||||||
// Computation of the global checksum does not include the checksum bytes
|
// Computation of the global checksum does not include the checksum bytes
|
||||||
assume(rom0Len >= 0x14E);
|
assume(rom0Len >= 0x14E);
|
||||||
for (uint16_t i = 0; i < 0x14E; i++)
|
for (uint16_t i = 0; i < 0x14E; i++) {
|
||||||
globalSum += rom0[i];
|
globalSum += rom0[i];
|
||||||
for (uint16_t i = 0x150; i < rom0Len; i++)
|
}
|
||||||
|
for (uint16_t i = 0x150; i < rom0Len; i++) {
|
||||||
globalSum += rom0[i];
|
globalSum += rom0[i];
|
||||||
|
}
|
||||||
// Pipes have already read ROMX and updated globalSum, but not regular files
|
// Pipes have already read ROMX and updated globalSum, but not regular files
|
||||||
if (input == output) {
|
if (input == output) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
ssize_t bankLen = readBytes(input, bank, sizeof(bank));
|
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];
|
globalSum += bank[i];
|
||||||
if (bankLen != sizeof(bank))
|
}
|
||||||
|
if (bankLen != sizeof(bank)) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fixSpec & TRASH_GLOBAL_SUM)
|
if (fixSpec & TRASH_GLOBAL_SUM) {
|
||||||
globalSum = ~globalSum;
|
globalSum = ~globalSum;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t bytes[2] = {
|
uint8_t bytes[2] = {
|
||||||
static_cast<uint8_t>(globalSum >> 8),
|
static_cast<uint8_t>(globalSum >> 8), static_cast<uint8_t>(globalSum & 0xFF)
|
||||||
static_cast<uint8_t>(globalSum & 0xFF)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
|
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
|
// write the header
|
||||||
if (input == output) {
|
if (input == output) {
|
||||||
if (lseek(output, 0, SEEK_SET) == static_cast<off_t>(-1)) {
|
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));
|
report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
// If modifying the file in-place, we only need to edit the header
|
// 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
|
// However, padding may have modified ROM0 (added padding), so don't in that case
|
||||||
if (padValue == UNSPECIFIED)
|
if (padValue == UNSPECIFIED) {
|
||||||
rom0Len = headerSize;
|
rom0Len = headerSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
writeLen = writeBytes(output, rom0, rom0Len);
|
writeLen = writeBytes(output, rom0, rom0Len);
|
||||||
|
|
||||||
if (writeLen == -1) {
|
if (writeLen == -1) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
|
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (writeLen < rom0Len) {
|
} else if (writeLen < rom0Len) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report(
|
report(
|
||||||
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
||||||
static_cast<intmax_t>(writeLen),
|
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)
|
static_cast<intmax_t>(rom0Len)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output ROMX if it was buffered
|
// 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`
|
// so it's fine to cast to `size_t`
|
||||||
writeLen = writeBytes(output, romx.data(), totalRomxLen);
|
writeLen = writeBytes(output, romx.data(), totalRomxLen);
|
||||||
if (writeLen == -1) {
|
if (writeLen == -1) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (static_cast<size_t>(writeLen) < totalRomxLen) {
|
} else if (static_cast<size_t>(writeLen) < totalRomxLen) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report(
|
report(
|
||||||
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
||||||
static_cast<intmax_t>(writeLen),
|
static_cast<intmax_t>(writeLen),
|
||||||
@@ -1139,6 +1191,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
totalRomxLen
|
totalRomxLen
|
||||||
);
|
);
|
||||||
return;
|
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 (padValue != UNSPECIFIED) {
|
||||||
if (input == output) {
|
if (input == output) {
|
||||||
if (lseek(output, 0, SEEK_END) == static_cast<off_t>(-1)) {
|
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));
|
report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
memset(bank, padValue, sizeof(bank));
|
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`,
|
// The return value is either -1, or at most `thisLen`,
|
||||||
// so it's fine to cast to `size_t`
|
// so it's fine to cast to `size_t`
|
||||||
if (static_cast<size_t>(ret) != thisLen) {
|
if (static_cast<size_t>(ret) != thisLen) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
|
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
|
||||||
break;
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
len -= thisLen;
|
len -= thisLen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool processFilename(char const *name) {
|
static bool processFilename(char const *name, char const *outputName) {
|
||||||
nbErrors = 0;
|
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>";
|
name = "<stdin>";
|
||||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
processFile(STDIN_FILENO, output, name, 0, false);
|
||||||
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
|
|
||||||
} else {
|
} else {
|
||||||
// POSIX specifies that the results of O_RDWR on a FIFO are undefined.
|
// 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
|
// 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.
|
// `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
|
// Thus, we're going to hope that either the `open` fails, or it succeeds but IO
|
||||||
// operations may fail, all of which we handle.
|
// 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));
|
report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", name, strerror(errno));
|
||||||
} else {
|
} else {
|
||||||
Defer closeInput{[&] { close(input); }};
|
Defer closeInput{[&] { close(input); }};
|
||||||
struct stat stat;
|
struct stat stat;
|
||||||
if (fstat(input, &stat) == -1) {
|
if (fstat(input, &stat) == -1) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to stat \"%s\": %s\n", name, strerror(errno));
|
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(
|
report(
|
||||||
"FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n",
|
"FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n",
|
||||||
name
|
name
|
||||||
);
|
);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (stat.st_size < 0x150) {
|
} else if (stat.st_size < 0x150) {
|
||||||
// This check is in theory redundant with the one in `processFile`, but it
|
// This check is in theory redundant with the one in `processFile`, but it
|
||||||
// prevents passing a file size of 0, which usually indicates pipes
|
// 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)
|
static_cast<intmax_t>(stat.st_size)
|
||||||
);
|
);
|
||||||
} else {
|
} 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(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"Fixing \"%s\" failed with %u error%s\n",
|
"Fixing \"%s\" failed with %u error%s\n",
|
||||||
@@ -1217,6 +1308,7 @@ static bool processFilename(char const *name) {
|
|||||||
nbErrors,
|
nbErrors,
|
||||||
nbErrors == 1 ? "" : "s"
|
nbErrors == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return nbErrors;
|
return nbErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1247,6 +1339,7 @@ static void parseByte(uint16_t &output, char name) {
|
|||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
nbErrors = 0;
|
nbErrors = 0;
|
||||||
|
|
||||||
|
char const *outputFilename = nullptr;
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
size_t len;
|
size_t len;
|
||||||
@@ -1266,16 +1359,17 @@ int main(int argc, char *argv[]) {
|
|||||||
switch (*musl_optarg) {
|
switch (*musl_optarg) {
|
||||||
#define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \
|
#define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \
|
||||||
case STR(cur)[0]: \
|
case STR(cur)[0]: \
|
||||||
if (fixSpec & badFlag) \
|
if (fixSpec & badFlag) { \
|
||||||
fprintf(stderr, "warning: '" STR(cur) "' overriding '" STR(bad) "' in fix spec\n"); \
|
fprintf(stderr, "warning: '" STR(cur) "' overriding '" STR(bad) "' in fix spec\n"); \
|
||||||
|
} \
|
||||||
fixSpec = (fixSpec & ~badFlag) | curFlag; \
|
fixSpec = (fixSpec & ~badFlag) | curFlag; \
|
||||||
break
|
break
|
||||||
#define overrideSpecs(fix, fixFlag, trash, trashFlag) \
|
#define overrideSpecs(fix, fixFlag, trash, trashFlag) \
|
||||||
OVERRIDE_SPEC(fix, trash, fixFlag, trashFlag); \
|
OVERRIDE_SPEC(fix, trash, fixFlag, trashFlag); \
|
||||||
OVERRIDE_SPEC(trash, fix, trashFlag, fixFlag)
|
OVERRIDE_SPEC(trash, fix, trashFlag, fixFlag)
|
||||||
overrideSpecs(l, FIX_LOGO, L, TRASH_LOGO);
|
overrideSpecs(l, FIX_LOGO, L, TRASH_LOGO);
|
||||||
overrideSpecs(h, FIX_HEADER_SUM, H, TRASH_HEADER_SUM);
|
overrideSpecs(h, FIX_HEADER_SUM, H, TRASH_HEADER_SUM);
|
||||||
overrideSpecs(g, FIX_GLOBAL_SUM, G, TRASH_GLOBAL_SUM);
|
overrideSpecs(g, FIX_GLOBAL_SUM, G, TRASH_GLOBAL_SUM);
|
||||||
#undef OVERRIDE_SPEC
|
#undef OVERRIDE_SPEC
|
||||||
#undef overrideSpecs
|
#undef overrideSpecs
|
||||||
|
|
||||||
@@ -1286,6 +1380,12 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
printUsage();
|
||||||
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'i':
|
case 'i':
|
||||||
gameID = musl_optarg;
|
gameID = musl_optarg;
|
||||||
len = strlen(gameID);
|
len = strlen(gameID);
|
||||||
@@ -1354,6 +1454,10 @@ int main(int argc, char *argv[]) {
|
|||||||
overwriteRom = true;
|
overwriteRom = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
outputFilename = musl_optarg;
|
||||||
|
break;
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
parseByte(padValue, 'p');
|
parseByte(padValue, 'p');
|
||||||
break;
|
break;
|
||||||
@@ -1380,34 +1484,40 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'V':
|
case 'V':
|
||||||
|
// LCOV_EXCL_START
|
||||||
printf("rgbfix %s\n", get_package_version_string());
|
printf("rgbfix %s\n", get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
|
fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
// LCOV_EXCL_START
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(1);
|
exit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((cartridgeType & 0xFF00) == TPP1 && !japanese)
|
if ((cartridgeType & 0xFF00) == TPP1 && !japanese) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"
|
"warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Check that RAM size is correct for "standard" mappers
|
// Check that RAM size is correct for "standard" mappers
|
||||||
if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) {
|
if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) {
|
||||||
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
|
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
|
||||||
if (ramSize != 1)
|
if (ramSize != 1) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n",
|
"warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n",
|
||||||
mbcName(cartridgeType)
|
mbcName(cartridgeType)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else if (hasRAM(cartridgeType)) {
|
} else if (hasRAM(cartridgeType)) {
|
||||||
if (!ramSize) {
|
if (!ramSize) {
|
||||||
fprintf(
|
fprintf(
|
||||||
@@ -1421,7 +1531,7 @@ int main(int argc, char *argv[]) {
|
|||||||
"warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n",
|
"warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n",
|
||||||
mbcName(cartridgeType)
|
mbcName(cartridgeType)
|
||||||
);
|
);
|
||||||
} // TODO: check possible values?
|
}
|
||||||
} else if (ramSize) {
|
} else if (ramSize) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
@@ -1432,12 +1542,13 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33)
|
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n",
|
"warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n",
|
||||||
oldLicensee
|
oldLicensee
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
argv += musl_optind;
|
argv += musl_optind;
|
||||||
bool failed = nbErrors;
|
bool failed = nbErrors;
|
||||||
@@ -1488,8 +1599,9 @@ int main(int argc, char *argv[]) {
|
|||||||
memcpy(logo, nintendoLogo, sizeof(nintendoLogo));
|
memcpy(logo, nintendoLogo, sizeof(nintendoLogo));
|
||||||
}
|
}
|
||||||
if (fixSpec & TRASH_LOGO) {
|
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];
|
logo[i] = 0xFF ^ logo[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!*argv) {
|
if (!*argv) {
|
||||||
@@ -1500,8 +1612,14 @@ int main(int argc, char *argv[]) {
|
|||||||
exit(1);
|
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 {
|
do {
|
||||||
failed |= processFilename(*argv);
|
failed |= processFilename(*argv, outputFilename);
|
||||||
} while (*++argv);
|
} while (*++argv);
|
||||||
|
|
||||||
return failed;
|
return failed;
|
||||||
|
|||||||
180
src/gfx/main.cpp
180
src/gfx/main.cpp
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <type_traits>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "extern/getopt.hpp"
|
#include "extern/getopt.hpp"
|
||||||
@@ -41,7 +40,8 @@ static struct LocalOptions {
|
|||||||
|
|
||||||
static uintmax_t nbErrors;
|
static uintmax_t nbErrors;
|
||||||
|
|
||||||
[[noreturn]] void giveUp() {
|
[[noreturn]]
|
||||||
|
void giveUp() {
|
||||||
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@@ -71,18 +71,21 @@ void error(char const *fmt, ...) {
|
|||||||
va_end(ap);
|
va_end(ap);
|
||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void errorMessage(char const *msg) {
|
void errorMessage(char const *msg) {
|
||||||
fprintf(stderr, "error: %s\n", msg);
|
fprintf(stderr, "error: %s\n", msg);
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void fatal(char const *fmt, ...) {
|
[[noreturn]]
|
||||||
|
void fatal(char const *fmt, ...) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
fputs("FATAL: ", stderr);
|
fputs("FATAL: ", stderr);
|
||||||
@@ -91,13 +94,15 @@ void errorMessage(char const *msg) {
|
|||||||
va_end(ap);
|
va_end(ap);
|
||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
|
}
|
||||||
|
|
||||||
giveUp();
|
giveUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (verbosity >= level) {
|
if (verbosity >= level) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
@@ -105,28 +110,28 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
|||||||
vfprintf(stderr, fmt, ap);
|
vfprintf(stderr, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short options
|
// 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
|
||||||
* 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,
|
||||||
* Also, make sure long opts don't create ambiguity:
|
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||||
* A long opt's name should start with the same letter as its short opt,
|
// This is because long opt matching, even to a single char, is prioritized
|
||||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
// over short opt matching.
|
||||||
* This is because long opt matching, even to a single char, is prioritized
|
|
||||||
* over short opt matching
|
|
||||||
*/
|
|
||||||
static option const longopts[] = {
|
static option const longopts[] = {
|
||||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||||
{"attr-map", required_argument, nullptr, 'a'},
|
{"attr-map", required_argument, nullptr, 'a'},
|
||||||
|
{"background-color", required_argument, nullptr, 'B'},
|
||||||
{"base-tiles", required_argument, nullptr, 'b'},
|
{"base-tiles", required_argument, nullptr, 'b'},
|
||||||
{"color-curve", no_argument, nullptr, 'C'},
|
{"color-curve", no_argument, nullptr, 'C'},
|
||||||
{"colors", required_argument, nullptr, 'c'},
|
{"colors", required_argument, nullptr, 'c'},
|
||||||
{"depth", required_argument, nullptr, 'd'},
|
{"depth", required_argument, nullptr, 'd'},
|
||||||
|
{"help", no_argument, nullptr, 'h'},
|
||||||
{"input-tileset", required_argument, nullptr, 'i'},
|
{"input-tileset", required_argument, nullptr, 'i'},
|
||||||
{"slice", required_argument, nullptr, 'L'},
|
{"slice", required_argument, nullptr, 'L'},
|
||||||
{"mirror-tiles", no_argument, nullptr, 'm'},
|
{"mirror-tiles", no_argument, nullptr, 'm'},
|
||||||
@@ -153,9 +158,10 @@ static option const longopts[] = {
|
|||||||
{nullptr, no_argument, nullptr, 0 }
|
{nullptr, no_argument, nullptr, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
static void printUsage() {
|
static void printUsage() {
|
||||||
fputs(
|
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"
|
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-i <tileset_file>]\n"
|
||||||
" [-L <slice>] [-N <nb_tiles>] [-n <nb_pals>] [-o <out_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"
|
" [-p <pal_file> | -P] [-q <pal_map> | -Q] [-s <nb_colors>]\n"
|
||||||
@@ -171,11 +177,10 @@ static void printUsage() {
|
|||||||
stderr
|
stderr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
/*
|
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
|
||||||
* Parses a number at the beginning of a string, moving the pointer to skip the parsed characters
|
// Returns the provided errVal on error.
|
||||||
* Returns the provided errVal on error
|
|
||||||
*/
|
|
||||||
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
|
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
|
||||||
uint8_t base = 10;
|
uint8_t base = 10;
|
||||||
if (*string == '\0') {
|
if (*string == '\0') {
|
||||||
@@ -198,12 +203,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.
|
||||||
* 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.
|
||||||
* 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
|
||||||
* Returns 255 on parse failure (including wrong char for base), in which case
|
// the string_view may be pointing on garbage.
|
||||||
* the string_view may be pointing on garbage.
|
|
||||||
*/
|
|
||||||
auto charIndex = [&base](unsigned char c) -> uint8_t {
|
auto charIndex = [&base](unsigned char c) -> uint8_t {
|
||||||
unsigned char index = c - '0'; // Use wrapping semantics
|
unsigned char index = c - '0'; // Use wrapping semantics
|
||||||
if (base == 2 && index >= 2) {
|
if (base == 2 && index >= 2) {
|
||||||
@@ -271,10 +274,7 @@ static void registerInput(char const *arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
|
||||||
* Turn an "at-file"'s contents into an argv that `getopt` can handle
|
|
||||||
* @param argPool Argument characters will be appended to this vector, for storage purposes.
|
|
||||||
*/
|
|
||||||
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
||||||
File file;
|
File file;
|
||||||
if (!file.open(path, std::ios_base::in)) {
|
if (!file.open(path, std::ios_base::in)) {
|
||||||
@@ -283,8 +283,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!
|
// We only filter out `EOF`, but calling `isblank()` on anything else is UB!
|
||||||
static_assert(
|
static_assert(
|
||||||
std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
|
std::streambuf::traits_type::eof() == EOF,
|
||||||
"isblank(char_traits<...>::eof()) is UB!"
|
"isblank(std::streambuf::traits_type::eof()) is UB!"
|
||||||
);
|
);
|
||||||
std::vector<size_t> argvOfs;
|
std::vector<size_t> argvOfs;
|
||||||
|
|
||||||
@@ -346,27 +346,63 @@ 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.
|
||||||
* 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.
|
||||||
* 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.
|
||||||
* 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[]) {
|
static char *parseArgv(int argc, char *argv[]) {
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||||
char *arg = musl_optarg; // Make a copy for scanning
|
char *arg = musl_optarg; // Make a copy for scanning
|
||||||
uint16_t number;
|
uint16_t number;
|
||||||
|
size_t size;
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 'A':
|
case 'A':
|
||||||
localOptions.autoAttrmap = true;
|
localOptions.autoAttrmap = true;
|
||||||
break;
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
localOptions.autoAttrmap = false;
|
localOptions.autoAttrmap = false;
|
||||||
if (!options.attrmap.empty())
|
if (!options.attrmap.empty()) {
|
||||||
warning("Overriding attrmap file %s", options.attrmap.c_str());
|
warning("Overriding attrmap file %s", options.attrmap.c_str());
|
||||||
|
}
|
||||||
options.attrmap = musl_optarg;
|
options.attrmap = musl_optarg;
|
||||||
break;
|
break;
|
||||||
|
case 'B':
|
||||||
|
if (strcasecmp(musl_optarg, "transparent") == 0) {
|
||||||
|
options.bgColor = Rgba(0x00, 0x00, 0x00, 0x00);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (musl_optarg[0] != '#') {
|
||||||
|
error("Background color specification must be `#rgb`, `#rrggbb`, or `transparent`");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
size = strspn(&musl_optarg[1], "0123456789ABCDEFabcdef");
|
||||||
|
switch (size) {
|
||||||
|
case 3:
|
||||||
|
options.bgColor = Rgba(
|
||||||
|
singleToHex(musl_optarg[1]),
|
||||||
|
singleToHex(musl_optarg[2]),
|
||||||
|
singleToHex(musl_optarg[3]),
|
||||||
|
0xFF
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
options.bgColor = Rgba(
|
||||||
|
toHex(musl_optarg[1], musl_optarg[2]),
|
||||||
|
toHex(musl_optarg[3], musl_optarg[4]),
|
||||||
|
toHex(musl_optarg[5], musl_optarg[6]),
|
||||||
|
0xFF
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error("Unknown background color specification \"%s\"", musl_optarg);
|
||||||
|
}
|
||||||
|
if (musl_optarg[size + 1] != '\0') {
|
||||||
|
error(
|
||||||
|
"Unexpected text \"%s\" after background color specification",
|
||||||
|
&musl_optarg[size + 1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'b':
|
case 'b':
|
||||||
number = parseNumber(arg, "Bank 0 base tile ID", 0);
|
number = parseNumber(arg, "Bank 0 base tile ID", 0);
|
||||||
if (number >= 256) {
|
if (number >= 256) {
|
||||||
@@ -415,9 +451,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
} else {
|
} else {
|
||||||
options.palSpecType = Options::EXPLICIT;
|
options.palSpecType = Options::EXPLICIT;
|
||||||
// Can't parse the file yet, as "flat" color collections need to know the palette
|
// Can't parse the file yet, as "flat" color collections need to know the palette
|
||||||
// size to be split; thus, we defer that
|
// size to be split; thus, we defer that.
|
||||||
// TODO: this does not validate the `fmt` part of any external spec but the last
|
// If a following `-c` overrides a previous one, the `fmt` part of an overridden
|
||||||
// one, but I guess that's okay
|
// external palette spec will not be validated, but I guess that's okay.
|
||||||
localOptions.externalPalSpec = musl_optarg;
|
localOptions.externalPalSpec = musl_optarg;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -430,9 +466,15 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
options.bitDepth = 2;
|
options.bitDepth = 2;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'h':
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
printUsage();
|
||||||
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
case 'i':
|
case 'i':
|
||||||
if (!options.inputTileset.empty())
|
if (!options.inputTileset.empty()) {
|
||||||
warning("Overriding input tileset file %s", options.inputTileset.c_str());
|
warning("Overriding input tileset file %s", options.inputTileset.c_str());
|
||||||
|
}
|
||||||
options.inputTileset = musl_optarg;
|
options.inputTileset = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'L':
|
case 'L':
|
||||||
@@ -530,8 +572,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
localOptions.groupOutputs = true;
|
localOptions.groupOutputs = true;
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
if (!options.output.empty())
|
if (!options.output.empty()) {
|
||||||
warning("Overriding tile data file %s", options.output.c_str());
|
warning("Overriding tile data file %s", options.output.c_str());
|
||||||
|
}
|
||||||
options.output = musl_optarg;
|
options.output = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
@@ -539,8 +582,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
localOptions.autoPalettes = false;
|
localOptions.autoPalettes = false;
|
||||||
if (!options.palettes.empty())
|
if (!options.palettes.empty()) {
|
||||||
warning("Overriding palettes file %s", options.palettes.c_str());
|
warning("Overriding palettes file %s", options.palettes.c_str());
|
||||||
|
}
|
||||||
options.palettes = musl_optarg;
|
options.palettes = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'Q':
|
case 'Q':
|
||||||
@@ -548,8 +592,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
case 'q':
|
case 'q':
|
||||||
localOptions.autoPalmap = false;
|
localOptions.autoPalmap = false;
|
||||||
if (!options.palmap.empty())
|
if (!options.palmap.empty()) {
|
||||||
warning("Overriding palette map file %s", options.palmap.c_str());
|
warning("Overriding palette map file %s", options.palmap.c_str());
|
||||||
|
}
|
||||||
options.palmap = musl_optarg;
|
options.palmap = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
@@ -575,18 +620,23 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
localOptions.autoTilemap = false;
|
localOptions.autoTilemap = false;
|
||||||
if (!options.tilemap.empty())
|
if (!options.tilemap.empty()) {
|
||||||
warning("Overriding tilemap file %s", options.tilemap.c_str());
|
warning("Overriding tilemap file %s", options.tilemap.c_str());
|
||||||
|
}
|
||||||
options.tilemap = musl_optarg;
|
options.tilemap = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
|
// LCOV_EXCL_START
|
||||||
printf("rgbgfx %s\n", get_package_version_string());
|
printf("rgbgfx %s\n", get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
case 'v':
|
case 'v':
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity < Options::VERB_VVVVVV) {
|
if (options.verbosity < Options::VERB_VVVVVV) {
|
||||||
++options.verbosity;
|
++options.verbosity;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
case 'x':
|
case 'x':
|
||||||
options.trim = parseNumber(arg, "Number of tiles to trim", 0);
|
options.trim = parseNumber(arg, "Number of tiles to trim", 0);
|
||||||
if (*arg != '\0') {
|
if (*arg != '\0') {
|
||||||
@@ -613,8 +663,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
// LCOV_EXCL_START
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(1);
|
exit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -742,6 +794,7 @@ int main(int argc, char *argv[]) {
|
|||||||
parseExternalPalSpec(localOptions.externalPalSpec);
|
parseExternalPalSpec(localOptions.externalPalSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity >= Options::VERB_CFG) {
|
if (options.verbosity >= Options::VERB_CFG) {
|
||||||
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
|
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
|
||||||
|
|
||||||
@@ -774,19 +827,25 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fputs("Options:\n", stderr);
|
fputs("Options:\n", stderr);
|
||||||
if (options.columnMajor)
|
if (options.columnMajor) {
|
||||||
fputs("\tVisit image in column-major order\n", stderr);
|
fputs("\tVisit image in column-major order\n", stderr);
|
||||||
if (options.allowDedup)
|
}
|
||||||
|
if (options.allowDedup) {
|
||||||
fputs("\tAllow deduplicating tiles\n", stderr);
|
fputs("\tAllow deduplicating tiles\n", stderr);
|
||||||
if (options.allowMirroringX)
|
}
|
||||||
|
if (options.allowMirroringX) {
|
||||||
fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr);
|
fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr);
|
||||||
if (options.allowMirroringY)
|
}
|
||||||
|
if (options.allowMirroringY) {
|
||||||
fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr);
|
fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr);
|
||||||
if (options.useColorCurve)
|
}
|
||||||
|
if (options.useColorCurve) {
|
||||||
fputs("\tUse color curve\n", stderr);
|
fputs("\tUse color curve\n", stderr);
|
||||||
|
}
|
||||||
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
|
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, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
|
||||||
|
}
|
||||||
fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes);
|
fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes);
|
||||||
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
||||||
fprintf(stderr, "\t%s palette spec\n", [] {
|
fprintf(stderr, "\t%s palette spec\n", [] {
|
||||||
@@ -817,7 +876,7 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32 ", %" PRIi32
|
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRId32 ", %" PRId32
|
||||||
")\n",
|
")\n",
|
||||||
options.inputSlice.width,
|
options.inputSlice.width,
|
||||||
options.inputSlice.height,
|
options.inputSlice.height,
|
||||||
@@ -848,6 +907,7 @@ int main(int argc, char *argv[]) {
|
|||||||
printPath("Output palettes", options.palettes);
|
printPath("Output palettes", options.palettes);
|
||||||
fputs("Ready.\n", stderr);
|
fputs("Ready.\n", stderr);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
// Do not do anything if option parsing went wrong.
|
// Do not do anything if option parsing went wrong.
|
||||||
requireZeroErrors();
|
requireZeroErrors();
|
||||||
@@ -883,9 +943,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 {
|
uint8_t Palette::indexOf(uint16_t color) const {
|
||||||
return color == Rgba::transparent
|
return color == Rgba::transparent
|
||||||
? 0
|
? 0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "gfx/pal_packing.hpp"
|
#include "gfx/pal_packing.hpp"
|
||||||
|
|
||||||
@@ -27,15 +27,11 @@
|
|||||||
// Tile | Proto-palette
|
// Tile | Proto-palette
|
||||||
// Page | Palette
|
// Page | Palette
|
||||||
|
|
||||||
/*
|
// A reference to a proto-palette, and attached attributes for sorting purposes
|
||||||
* A reference to a proto-palette, and attached attributes for sorting purposes
|
|
||||||
*/
|
|
||||||
struct ProtoPalAttrs {
|
struct ProtoPalAttrs {
|
||||||
size_t protoPalIndex;
|
size_t protoPalIndex;
|
||||||
/*
|
// Pages from which we are banned (to prevent infinite loops)
|
||||||
* Pages from which we are banned (to prevent infinite loops)
|
// This is dynamic because we wish not to hard-cap the amount of palettes
|
||||||
* This is dynamic because we wish not to hard-cap the amount of palettes
|
|
||||||
*/
|
|
||||||
std::vector<bool> bannedPages;
|
std::vector<bool> bannedPages;
|
||||||
|
|
||||||
explicit ProtoPalAttrs(size_t index) : protoPalIndex(index) {}
|
explicit ProtoPalAttrs(size_t index) : protoPalIndex(index) {}
|
||||||
@@ -50,10 +46,8 @@ struct ProtoPalAttrs {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
// A collection of proto-palettes assigned to a palette
|
||||||
* A collection of proto-palettes assigned to a palette
|
// Does not contain the actual color indices because we need to be able to remove elements
|
||||||
* Does not contain the actual color indices because we need to be able to remove elements
|
|
||||||
*/
|
|
||||||
class AssignedProtos {
|
class AssignedProtos {
|
||||||
// We leave room for emptied slots to avoid copying the structs around on removal
|
// We leave room for emptied slots to avoid copying the structs around on removal
|
||||||
std::vector<std::optional<ProtoPalAttrs>> _assigned;
|
std::vector<std::optional<ProtoPalAttrs>> _assigned;
|
||||||
@@ -92,8 +86,8 @@ private:
|
|||||||
public:
|
public:
|
||||||
Iter() = default;
|
Iter() = default;
|
||||||
|
|
||||||
bool operator==(Iter const &other) const { return _iter == other._iter; }
|
bool operator==(Iter const &rhs) const { return _iter == rhs._iter; }
|
||||||
bool operator!=(Iter const &other) const { return !(*this == other); }
|
|
||||||
Iter &operator++() {
|
Iter &operator++() {
|
||||||
++_iter;
|
++_iter;
|
||||||
skipEmpty();
|
skipEmpty();
|
||||||
@@ -104,6 +98,7 @@ private:
|
|||||||
++(*this);
|
++(*this);
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
reference operator*() const {
|
reference operator*() const {
|
||||||
assume((*_iter).has_value());
|
assume((*_iter).has_value());
|
||||||
return **_iter;
|
return **_iter;
|
||||||
@@ -127,10 +122,8 @@ public:
|
|||||||
}
|
}
|
||||||
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
|
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
|
||||||
|
|
||||||
/*
|
// Assigns a new ProtoPalAttrs in a free slot, assuming there is one
|
||||||
* Assigns a new ProtoPalAttrs in a free slot, assuming there is one
|
// Args are passed to the `ProtoPalAttrs`'s constructor
|
||||||
* Args are passed to the `ProtoPalAttrs`'s constructor
|
|
||||||
*/
|
|
||||||
template<typename... Ts>
|
template<typename... Ts>
|
||||||
void assign(Ts &&...args) {
|
void assign(Ts &&...args) {
|
||||||
auto freeSlot =
|
auto freeSlot =
|
||||||
@@ -175,16 +168,6 @@ private:
|
|||||||
std::unordered_set<uint16_t> &uniqueColors() const {
|
std::unordered_set<uint16_t> &uniqueColors() const {
|
||||||
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
||||||
// faster than "back-checking" on every element (O(n²))
|
// faster than "back-checking" on every element (O(n²))
|
||||||
//
|
|
||||||
// TODO: calc84maniac suggested another approach; try implementing it, see if it
|
|
||||||
// performs better:
|
|
||||||
// > So basically you make a priority queue that takes iterators into each of your sets
|
|
||||||
// > (paired with end iterators so you'll know where to stop), and the comparator tests the
|
|
||||||
// > values pointed to by each iterator
|
|
||||||
// > Then each iteration you pop from the queue,
|
|
||||||
// > optionally add one to your count, increment the iterator and push it back into the
|
|
||||||
// > queue if it didn't reach the end
|
|
||||||
// > And you do this until the priority queue is empty
|
|
||||||
static std::unordered_set<uint16_t> colors;
|
static std::unordered_set<uint16_t> colors;
|
||||||
|
|
||||||
colors.clear();
|
colors.clear();
|
||||||
@@ -192,9 +175,7 @@ private:
|
|||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
/*
|
// Returns the number of distinct colors
|
||||||
* Returns the number of distinct colors
|
|
||||||
*/
|
|
||||||
size_t volume() const { return uniqueColors().size(); }
|
size_t volume() const { return uniqueColors().size(); }
|
||||||
bool canFit(ProtoPalette const &protoPal) const {
|
bool canFit(ProtoPalette const &protoPal) const {
|
||||||
auto &colors = uniqueColors();
|
auto &colors = uniqueColors();
|
||||||
@@ -218,10 +199,8 @@ public:
|
|||||||
return factor;
|
return factor;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
/*
|
// Computes the "relative size" of a proto-palette on this palette;
|
||||||
* Computes the "relative size" of a proto-palette on this palette;
|
// it's a measure of how much this proto-palette would "cost" to introduce.
|
||||||
* it's a measure of how much this proto-palette would "cost" to introduce.
|
|
||||||
*/
|
|
||||||
uint32_t relSizeOf(ProtoPalette const &protoPal) const {
|
uint32_t relSizeOf(ProtoPalette const &protoPal) const {
|
||||||
// NOTE: this function must not call `uniqueColors`, or one of its callers will break!
|
// NOTE: this function must not call `uniqueColors`, or one of its callers will break!
|
||||||
|
|
||||||
@@ -244,9 +223,7 @@ public:
|
|||||||
return relSize;
|
return relSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Computes the "relative size" of a set of proto-palettes on this palette
|
||||||
* Computes the "relative size" of a set of proto-palettes on this palette
|
|
||||||
*/
|
|
||||||
template<typename Iter>
|
template<typename Iter>
|
||||||
auto combinedVolume(Iter &&begin, Iter const &end, std::vector<ProtoPalette> const &protoPals)
|
auto combinedVolume(Iter &&begin, Iter const &end, std::vector<ProtoPalette> const &protoPals)
|
||||||
const {
|
const {
|
||||||
@@ -254,9 +231,7 @@ public:
|
|||||||
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
|
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
|
||||||
return colors.size();
|
return colors.size();
|
||||||
}
|
}
|
||||||
/*
|
// Computes the "relative size" of a set of colors on this palette
|
||||||
* Computes the "relative size" of a set of colors on this palette
|
|
||||||
*/
|
|
||||||
template<typename Iter>
|
template<typename Iter>
|
||||||
auto combinedVolume(Iter &&begin, Iter &&end) const {
|
auto combinedVolume(Iter &&begin, Iter &&end) const {
|
||||||
auto &colors = uniqueColors();
|
auto &colors = uniqueColors();
|
||||||
@@ -492,7 +467,6 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
);
|
);
|
||||||
|
|
||||||
// All efficiencies are identical iff min equals max
|
// All efficiencies are identical iff min equals max
|
||||||
// TODO: maybe not ideal to re-compute these two?
|
|
||||||
ProtoPalette const &minProtoPal = protoPalettes[minEfficiencyIter->protoPalIndex];
|
ProtoPalette const &minProtoPal = protoPalettes[minEfficiencyIter->protoPalIndex];
|
||||||
ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex];
|
ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex];
|
||||||
size_t minSize = minProtoPal.size();
|
size_t minSize = minProtoPal.size();
|
||||||
@@ -574,6 +548,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
if (options.verbosity >= Options::VERB_INTERM) {
|
||||||
for (auto &&assignment : assignments) {
|
for (auto &&assignment : assignments) {
|
||||||
fprintf(stderr, "{ ");
|
fprintf(stderr, "{ ");
|
||||||
@@ -586,11 +561,13 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
// "Decant" the result
|
// "Decant" the result
|
||||||
decant(assignments, protoPalettes);
|
decant(assignments, protoPalettes);
|
||||||
// Note that the result does not contain any empty palettes
|
// Note that the result does not contain any empty palettes
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
if (options.verbosity >= Options::VERB_INTERM) {
|
||||||
for (auto &&assignment : assignments) {
|
for (auto &&assignment : assignments) {
|
||||||
fprintf(stderr, "{ ");
|
fprintf(stderr, "{ ");
|
||||||
@@ -603,6 +580,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
DefaultInitVec<size_t> mappings(protoPalettes.size());
|
DefaultInitVec<size_t> mappings(protoPalettes.size());
|
||||||
for (size_t i = 0; i < assignments.size(); ++i) {
|
for (size_t i = 0; i < assignments.size(); ++i) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "gfx/pal_sorting.hpp"
|
#include "gfx/pal_sorting.hpp"
|
||||||
|
|
||||||
@@ -40,13 +40,13 @@ void sortIndexed(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unreachable_(); // This should not be possible
|
unreachable_(); // LCOV_EXCL_LINE
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sortGrayscale(
|
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
|
||||||
) {
|
) {
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palette by grayscale bins...\n");
|
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palette by grayscale bins...\n");
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user