mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 10:42:07 +00:00
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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=
|
||||||
66
.github/workflows/testing.yml
vendored
66
.github/workflows/testing.yml
vendored
@@ -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
|
||||||
|
|||||||
@@ -45,11 +45,8 @@ 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)
|
||||||
@@ -78,7 +75,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.1
|
||||||
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
|
||||||
|
|||||||
12
Makefile
12
Makefile
@@ -24,7 +24,7 @@ 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
|
||||||
|
|
||||||
@@ -206,12 +206,8 @@ develop:
|
|||||||
-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.
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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]'
|
||||||
|
|||||||
@@ -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]'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -31,12 +31,8 @@ struct Expression {
|
|||||||
|
|
||||||
Expression &operator=(Expression &&) = default;
|
Expression &operator=(Expression &&) = default;
|
||||||
|
|
||||||
bool isKnown() const {
|
bool isKnown() const { return data.holds<int32_t>(); }
|
||||||
return data.holds<int32_t>();
|
int32_t value() const { return data.get<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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -25,10 +25,8 @@ public:
|
|||||||
File() {}
|
File() {}
|
||||||
~File() { close(); }
|
~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>();
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -48,6 +48,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 +56,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 +66,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
|
||||||
|
|||||||
@@ -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,10 +30,8 @@ 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);
|
||||||
@@ -43,19 +39,15 @@ struct Rgba {
|
|||||||
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(); }
|
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
|
||||||
* 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.
|
||||||
* 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,7 @@ 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; }
|
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -92,29 +92,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 1
|
||||||
|
|
||||||
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 February 2, 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 February 2, 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 February 2, 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 ;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd February 2, 2025
|
||||||
.Dt RGBASM 5
|
.Dt RGBASM 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd February 2, 2025
|
||||||
.Dt RGBDS 5
|
.Dt RGBDS 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd February 2, 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.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd February 2, 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
|
||||||
@@ -91,6 +91,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
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd February 2, 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
|
||||||
@@ -165,6 +165,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 February 2, 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.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd December 25, 2024
|
.Dd February 2, 2025
|
||||||
.Dt RGBLINK 5
|
.Dt RGBLINK 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
@@ -45,20 +46,26 @@ bool charmap_ForEach(
|
|||||||
for (Charmap const &charmap : charmapList) {
|
for (Charmap const &charmap : charmapList) {
|
||||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
// 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();) {
|
// 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());
|
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||||
prefixes.pop();
|
prefixes.pop();
|
||||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||||
if (node.isTerminal())
|
if (node.isTerminal()) {
|
||||||
mappings[nodeIdx] = mapping;
|
mappings[nodeIdx] = mapping;
|
||||||
|
}
|
||||||
for (unsigned c = 0; c < 256; c++) {
|
for (unsigned c = 0; c < 256; c++) {
|
||||||
if (size_t nextIdx = node.next[c]; nextIdx)
|
if (size_t nextIdx = node.next[c]; nextIdx) {
|
||||||
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
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 +74,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 +90,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 +102,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,8 +156,9 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -159,8 +170,9 @@ bool charmap_HasChar(std::string const &input) {
|
|||||||
for (char c : input) {
|
for (char c : input) {
|
||||||
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();
|
||||||
@@ -168,8 +180,7 @@ bool charmap_HasChar(std::string const &input) {
|
|||||||
|
|
||||||
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 +197,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 +219,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;
|
||||||
|
|||||||
@@ -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;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_EXACT;
|
state = FORMAT_EXACT;
|
||||||
sign = c;
|
sign = c;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// exact
|
// exact
|
||||||
case '#':
|
case '#':
|
||||||
if (state > FORMAT_EXACT)
|
if (state > FORMAT_EXACT) {
|
||||||
goto invalid;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_ALIGN;
|
state = FORMAT_ALIGN;
|
||||||
exact = true;
|
exact = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// align
|
// align
|
||||||
case '-':
|
case '-':
|
||||||
if (state > FORMAT_ALIGN)
|
if (state > FORMAT_ALIGN) {
|
||||||
goto invalid;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_WIDTH;
|
state = FORMAT_WIDTH;
|
||||||
alignLeft = true;
|
alignLeft = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// 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':
|
||||||
@@ -72,16 +77,18 @@ void FormatSpec::useCharacter(int c) {
|
|||||||
|
|
||||||
// width
|
// width
|
||||||
case '.':
|
case '.':
|
||||||
if (state > FORMAT_WIDTH)
|
if (state > FORMAT_WIDTH) {
|
||||||
goto invalid;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_FRAC;
|
state = FORMAT_FRAC;
|
||||||
hasFrac = true;
|
hasFrac = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// prec
|
// prec
|
||||||
case 'q':
|
case 'q':
|
||||||
if (state > FORMAT_PREC)
|
if (state > FORMAT_PREC) {
|
||||||
goto invalid;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_PREC;
|
state = FORMAT_PREC;
|
||||||
hasPrec = true;
|
hasPrec = true;
|
||||||
break;
|
break;
|
||||||
@@ -95,8 +102,9 @@ 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;
|
goto invalid;
|
||||||
|
}
|
||||||
state = FORMAT_DONE;
|
state = FORMAT_DONE;
|
||||||
valid = true;
|
valid = true;
|
||||||
type = c;
|
type = c;
|
||||||
@@ -110,8 +118,9 @@ invalid:
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 +160,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 +201,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 +269,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 +298,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,32 @@ 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)
|
if (verbose) {
|
||||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +147,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 +185,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 +208,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 +220,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 +308,9 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
|||||||
|
|
||||||
if (!fullPath) {
|
if (!fullPath) {
|
||||||
if (generatedMissingIncludes && !preInclude) {
|
if (generatedMissingIncludes && !preInclude) {
|
||||||
if (verbose)
|
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));
|
||||||
|
}
|
||||||
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 +318,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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +343,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 +358,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 +403,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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/lexer.hpp"
|
#include "asm/lexer.hpp"
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "helpers.hpp" // assume, QUOTEDSTRLEN
|
#include "helpers.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
|
||||||
#include "asm/fixpoint.hpp"
|
#include "asm/fixpoint.hpp"
|
||||||
@@ -35,15 +35,14 @@
|
|||||||
|
|
||||||
// Neither MSVC nor MinGW provide `mmap`
|
// Neither MSVC nor MinGW provide `mmap`
|
||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
// clang-format off
|
// clang-format off: maintain `include` order
|
||||||
// (we need these `include`s in this order)
|
#define WIN32_LEAN_AND_MEAN // Include less from `windows.h`
|
||||||
#define WIN32_LEAN_AND_MEAN // include less from windows.h
|
|
||||||
#include <windows.h> // target architecture
|
#include <windows.h> // target architecture
|
||||||
#include <fileapi.h> // CreateFileA
|
|
||||||
#include <winbase.h> // CreateFileMappingA
|
|
||||||
#include <memoryapi.h> // MapViewOfFile
|
|
||||||
#include <handleapi.h> // CloseHandle
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
#include <fileapi.h> // CreateFileA
|
||||||
|
#include <handleapi.h> // CloseHandle
|
||||||
|
#include <memoryapi.h> // MapViewOfFile
|
||||||
|
#include <winbase.h> // CreateFileMappingA
|
||||||
|
|
||||||
static char *mapFile(int fd, std::string const &path, size_t) {
|
static char *mapFile(int fd, std::string const &path, size_t) {
|
||||||
void *mappingAddr = nullptr;
|
void *mappingAddr = nullptr;
|
||||||
@@ -64,7 +63,7 @@ static char *mapFile(int fd, std::string const &path, size_t) {
|
|||||||
}
|
}
|
||||||
CloseHandle(file);
|
CloseHandle(file);
|
||||||
}
|
}
|
||||||
return (char *)mappingAddr;
|
return static_cast<char *>(mappingAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FileUnmapDeleter {
|
struct FileUnmapDeleter {
|
||||||
@@ -82,8 +81,9 @@ static char *mapFile(int fd, std::string const &path, size_t size) {
|
|||||||
// The implementation may not support MAP_PRIVATE; try again with MAP_SHARED
|
// The implementation may not support MAP_PRIVATE; try again with MAP_SHARED
|
||||||
// instead, offering, I believe, weaker guarantees about external modifications to
|
// instead, offering, I believe, weaker guarantees about external modifications to
|
||||||
// the file while reading it. That's still better than not opening it at all, though.
|
// the file while reading it. That's still better than not opening it at all, though.
|
||||||
if (verbose)
|
if (verbose) {
|
||||||
printf("mmap(%s, MAP_PRIVATE) failed, retrying with MAP_SHARED\n", path.c_str());
|
printf("mmap(%s, MAP_PRIVATE) failed, retrying with MAP_SHARED\n", path.c_str());
|
||||||
|
}
|
||||||
mappingAddr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
|
mappingAddr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
}
|
}
|
||||||
return mappingAddr != MAP_FAILED ? static_cast<char *>(mappingAddr) : nullptr;
|
return mappingAddr != MAP_FAILED ? static_cast<char *>(mappingAddr) : nullptr;
|
||||||
@@ -120,8 +120,9 @@ struct CaseInsensitive {
|
|||||||
size_t operator()(std::string const &str) const {
|
size_t operator()(std::string const &str) const {
|
||||||
size_t hash = 0x811C9DC5;
|
size_t hash = 0x811C9DC5;
|
||||||
|
|
||||||
for (char const &c : str)
|
for (char const &c : str) {
|
||||||
hash = (hash ^ toupper(c)) * 16777619;
|
hash = (hash ^ toupper(c)) * 16777619;
|
||||||
|
}
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,58 +140,58 @@ struct CaseInsensitive {
|
|||||||
// Tokens / keywords not handled here are handled in `yylex_NORMAL`'s switch.
|
// Tokens / keywords not handled here are handled in `yylex_NORMAL`'s switch.
|
||||||
// This assumes that no two keywords have the same name.
|
// This assumes that no two keywords have the same name.
|
||||||
static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> keywordDict = {
|
static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> keywordDict = {
|
||||||
{"ADC", T_(Z80_ADC) },
|
{"ADC", T_(SM83_ADC) },
|
||||||
{"ADD", T_(Z80_ADD) },
|
{"ADD", T_(SM83_ADD) },
|
||||||
{"AND", T_(Z80_AND) },
|
{"AND", T_(SM83_AND) },
|
||||||
{"BIT", T_(Z80_BIT) },
|
{"BIT", T_(SM83_BIT) },
|
||||||
{"CALL", T_(Z80_CALL) },
|
{"CALL", T_(SM83_CALL) },
|
||||||
{"CCF", T_(Z80_CCF) },
|
{"CCF", T_(SM83_CCF) },
|
||||||
{"CPL", T_(Z80_CPL) },
|
{"CPL", T_(SM83_CPL) },
|
||||||
{"CP", T_(Z80_CP) },
|
{"CP", T_(SM83_CP) },
|
||||||
{"DAA", T_(Z80_DAA) },
|
{"DAA", T_(SM83_DAA) },
|
||||||
{"DEC", T_(Z80_DEC) },
|
{"DEC", T_(SM83_DEC) },
|
||||||
{"DI", T_(Z80_DI) },
|
{"DI", T_(SM83_DI) },
|
||||||
{"EI", T_(Z80_EI) },
|
{"EI", T_(SM83_EI) },
|
||||||
{"HALT", T_(Z80_HALT) },
|
{"HALT", T_(SM83_HALT) },
|
||||||
{"INC", T_(Z80_INC) },
|
{"INC", T_(SM83_INC) },
|
||||||
{"JP", T_(Z80_JP) },
|
{"JP", T_(SM83_JP) },
|
||||||
{"JR", T_(Z80_JR) },
|
{"JR", T_(SM83_JR) },
|
||||||
{"LD", T_(Z80_LD) },
|
{"LD", T_(SM83_LD) },
|
||||||
{"LDI", T_(Z80_LDI) },
|
{"LDI", T_(SM83_LDI) },
|
||||||
{"LDD", T_(Z80_LDD) },
|
{"LDD", T_(SM83_LDD) },
|
||||||
{"LDIO", T_(Z80_LDH) },
|
{"LDIO", T_(SM83_LDH) },
|
||||||
{"LDH", T_(Z80_LDH) },
|
{"LDH", T_(SM83_LDH) },
|
||||||
{"NOP", T_(Z80_NOP) },
|
{"NOP", T_(SM83_NOP) },
|
||||||
{"OR", T_(Z80_OR) },
|
{"OR", T_(SM83_OR) },
|
||||||
{"POP", T_(Z80_POP) },
|
{"POP", T_(SM83_POP) },
|
||||||
{"PUSH", T_(Z80_PUSH) },
|
{"PUSH", T_(SM83_PUSH) },
|
||||||
{"RES", T_(Z80_RES) },
|
{"RES", T_(SM83_RES) },
|
||||||
{"RETI", T_(Z80_RETI) },
|
{"RETI", T_(SM83_RETI) },
|
||||||
{"RET", T_(Z80_RET) },
|
{"RET", T_(SM83_RET) },
|
||||||
{"RLCA", T_(Z80_RLCA) },
|
{"RLCA", T_(SM83_RLCA) },
|
||||||
{"RLC", T_(Z80_RLC) },
|
{"RLC", T_(SM83_RLC) },
|
||||||
{"RLA", T_(Z80_RLA) },
|
{"RLA", T_(SM83_RLA) },
|
||||||
{"RL", T_(Z80_RL) },
|
{"RL", T_(SM83_RL) },
|
||||||
{"RRC", T_(Z80_RRC) },
|
{"RRC", T_(SM83_RRC) },
|
||||||
{"RRCA", T_(Z80_RRCA) },
|
{"RRCA", T_(SM83_RRCA) },
|
||||||
{"RRA", T_(Z80_RRA) },
|
{"RRA", T_(SM83_RRA) },
|
||||||
{"RR", T_(Z80_RR) },
|
{"RR", T_(SM83_RR) },
|
||||||
{"RST", T_(Z80_RST) },
|
{"RST", T_(SM83_RST) },
|
||||||
{"SBC", T_(Z80_SBC) },
|
{"SBC", T_(SM83_SBC) },
|
||||||
{"SCF", T_(Z80_SCF) },
|
{"SCF", T_(SM83_SCF) },
|
||||||
{"SET", T_(Z80_SET) },
|
{"SET", T_(SM83_SET) },
|
||||||
{"SLA", T_(Z80_SLA) },
|
{"SLA", T_(SM83_SLA) },
|
||||||
{"SRA", T_(Z80_SRA) },
|
{"SRA", T_(SM83_SRA) },
|
||||||
{"SRL", T_(Z80_SRL) },
|
{"SRL", T_(SM83_SRL) },
|
||||||
{"STOP", T_(Z80_STOP) },
|
{"STOP", T_(SM83_STOP) },
|
||||||
{"SUB", T_(Z80_SUB) },
|
{"SUB", T_(SM83_SUB) },
|
||||||
{"SWAP", T_(Z80_SWAP) },
|
{"SWAP", T_(SM83_SWAP) },
|
||||||
{"XOR", T_(Z80_XOR) },
|
{"XOR", T_(SM83_XOR) },
|
||||||
|
|
||||||
{"NZ", T_(CC_NZ) },
|
{"NZ", T_(CC_NZ) },
|
||||||
{"Z", T_(CC_Z) },
|
{"Z", T_(CC_Z) },
|
||||||
{"NC", T_(CC_NC) },
|
{"NC", T_(CC_NC) },
|
||||||
// There is no `T_(CC_C)`; it's handled before as `T_(TOKEN_C)`
|
// There is no `T_(CC_C)`; it's handled before as `T_(TOKEN_C)`
|
||||||
|
|
||||||
{"AF", T_(MODE_AF) },
|
{"AF", T_(MODE_AF) },
|
||||||
{"BC", T_(MODE_BC) },
|
{"BC", T_(MODE_BC) },
|
||||||
@@ -315,7 +316,7 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
|
|||||||
|
|
||||||
{"RB", T_(POP_RB) },
|
{"RB", T_(POP_RB) },
|
||||||
{"RW", T_(POP_RW) },
|
{"RW", T_(POP_RW) },
|
||||||
// There is no `T_(POP_RL)`; it's handled before as `T_(Z80_RL)`
|
// There is no `T_(POP_RL)`; it's handled before as `T_(SM83_RL)`
|
||||||
|
|
||||||
{"EQU", T_(POP_EQU) },
|
{"EQU", T_(POP_EQU) },
|
||||||
{"EQUS", T_(POP_EQUS) },
|
{"EQUS", T_(POP_EQUS) },
|
||||||
@@ -344,7 +345,7 @@ bool lexer_AtTopLevel() {
|
|||||||
|
|
||||||
void LexerState::clear(uint32_t lineNo_) {
|
void LexerState::clear(uint32_t lineNo_) {
|
||||||
mode = LEXER_NORMAL;
|
mode = LEXER_NORMAL;
|
||||||
atLineStart = true; // yylex() will init colNo due to this
|
atLineStart = true;
|
||||||
lastToken = T_(YYEOF);
|
lastToken = T_(YYEOF);
|
||||||
|
|
||||||
ifStack.clear();
|
ifStack.clear();
|
||||||
@@ -364,7 +365,6 @@ void LexerState::clear(uint32_t lineNo_) {
|
|||||||
|
|
||||||
static void nextLine() {
|
static void nextLine() {
|
||||||
lexerState->lineNo++;
|
lexerState->lineNo++;
|
||||||
lexerState->colNo = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t lexer_GetIFDepth() {
|
uint32_t lexer_GetIFDepth() {
|
||||||
@@ -376,8 +376,9 @@ void lexer_IncIFDepth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void lexer_DecIFDepth() {
|
void lexer_DecIFDepth() {
|
||||||
if (lexerState->ifStack.empty())
|
if (lexerState->ifStack.empty()) {
|
||||||
fatalerror("Found ENDC outside of an IF construct\n");
|
fatalerror("Found ENDC outside of an IF construct\n");
|
||||||
|
}
|
||||||
|
|
||||||
lexerState->ifStack.pop_front();
|
lexerState->ifStack.pop_front();
|
||||||
}
|
}
|
||||||
@@ -406,8 +407,9 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
|||||||
if (filePath == "-") {
|
if (filePath == "-") {
|
||||||
path = "<stdin>";
|
path = "<stdin>";
|
||||||
content.emplace<BufferedContent>(STDIN_FILENO);
|
content.emplace<BufferedContent>(STDIN_FILENO);
|
||||||
if (verbose)
|
if (verbose) {
|
||||||
printf("Opening stdin\n");
|
printf("Opening stdin\n");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
struct stat statBuf;
|
struct stat statBuf;
|
||||||
if (stat(filePath.c_str(), &statBuf) != 0) {
|
if (stat(filePath.c_str(), &statBuf) != 0) {
|
||||||
@@ -431,8 +433,9 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
|||||||
content.emplace<ViewedContent>(
|
content.emplace<ViewedContent>(
|
||||||
std::shared_ptr<char[]>(mappingAddr, FileUnmapDeleter(size)), size
|
std::shared_ptr<char[]>(mappingAddr, FileUnmapDeleter(size)), size
|
||||||
);
|
);
|
||||||
if (verbose)
|
if (verbose) {
|
||||||
printf("File \"%s\" is mmap()ped\n", path.c_str());
|
printf("File \"%s\" is mmap()ped\n", path.c_str());
|
||||||
|
}
|
||||||
isMmapped = true;
|
isMmapped = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -453,10 +456,11 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear(0);
|
clear(0);
|
||||||
if (updateStateNow)
|
if (updateStateNow) {
|
||||||
lexerState = this;
|
lexerState = this;
|
||||||
else
|
} else {
|
||||||
lexerStateEOL = this;
|
lexerStateEOL = this;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,45 +504,51 @@ BufferedContent::~BufferedContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BufferedContent::advance() {
|
void BufferedContent::advance() {
|
||||||
assume(offset < LEXER_BUF_SIZE);
|
assume(offset < std::size(buf));
|
||||||
offset++;
|
offset++;
|
||||||
if (offset == LEXER_BUF_SIZE)
|
if (offset == std::size(buf)) {
|
||||||
offset = 0; // Wrap around if necessary
|
offset = 0; // Wrap around if necessary
|
||||||
assume(size > 0);
|
}
|
||||||
size--;
|
if (size > 0) {
|
||||||
|
size--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferedContent::refill() {
|
void BufferedContent::refill() {
|
||||||
size_t target = LEXER_BUF_SIZE - size; // Aim: making the buf full
|
size_t target = std::size(buf) - size; // Aim: making the buf full
|
||||||
|
|
||||||
// Compute the index we'll start writing to
|
// Compute the index we'll start writing to
|
||||||
size_t startIndex = (offset + size) % LEXER_BUF_SIZE;
|
size_t startIndex = (offset + size) % std::size(buf);
|
||||||
|
|
||||||
// If the range to fill passes over the buffer wrapping point, we need two reads
|
// If the range to fill passes over the buffer wrapping point, we need two reads
|
||||||
if (startIndex + target > LEXER_BUF_SIZE) {
|
if (startIndex + target > std::size(buf)) {
|
||||||
size_t nbExpectedChars = LEXER_BUF_SIZE - startIndex;
|
size_t nbExpectedChars = std::size(buf) - startIndex;
|
||||||
size_t nbReadChars = readMore(startIndex, nbExpectedChars);
|
size_t nbReadChars = readMore(startIndex, nbExpectedChars);
|
||||||
|
|
||||||
startIndex += nbReadChars;
|
startIndex += nbReadChars;
|
||||||
if (startIndex == LEXER_BUF_SIZE)
|
if (startIndex == std::size(buf)) {
|
||||||
startIndex = 0;
|
startIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// If the read was incomplete, don't perform a second read
|
// If the read was incomplete, don't perform a second read
|
||||||
target -= nbReadChars;
|
target -= nbReadChars;
|
||||||
if (nbReadChars < nbExpectedChars)
|
if (nbReadChars < nbExpectedChars) {
|
||||||
target = 0;
|
target = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (target != 0)
|
if (target != 0) {
|
||||||
readMore(startIndex, target);
|
readMore(startIndex, target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t BufferedContent::readMore(size_t startIndex, size_t nbChars) {
|
size_t BufferedContent::readMore(size_t startIndex, size_t nbChars) {
|
||||||
// This buffer overflow made me lose WEEKS of my life. Never again.
|
// This buffer overflow made me lose WEEKS of my life. Never again.
|
||||||
assume(startIndex + nbChars <= LEXER_BUF_SIZE);
|
assume(startIndex + nbChars <= std::size(buf));
|
||||||
ssize_t nbReadChars = read(fd, &buf[startIndex], nbChars);
|
ssize_t nbReadChars = read(fd, &buf[startIndex], nbChars);
|
||||||
|
|
||||||
if (nbReadChars == -1)
|
if (nbReadChars == -1) {
|
||||||
fatalerror("Error while reading \"%s\": %s\n", lexerState->path.c_str(), strerror(errno));
|
fatalerror("Error while reading \"%s\": %s\n", lexerState->path.c_str(), strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
size += nbReadChars;
|
size += nbReadChars;
|
||||||
|
|
||||||
@@ -557,19 +567,22 @@ void lexer_ToggleStringExpansion(bool enable) {
|
|||||||
// Functions for the actual lexer to obtain characters
|
// Functions for the actual lexer to obtain characters
|
||||||
|
|
||||||
static void beginExpansion(std::shared_ptr<std::string> str, std::optional<std::string> name) {
|
static void beginExpansion(std::shared_ptr<std::string> str, std::optional<std::string> name) {
|
||||||
if (name)
|
if (name) {
|
||||||
lexer_CheckRecursionDepth();
|
lexer_CheckRecursionDepth();
|
||||||
|
}
|
||||||
|
|
||||||
// Do not expand empty strings
|
// Do not expand empty strings
|
||||||
if (str->empty())
|
if (str->empty()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lexerState->expansions.push_front({.name = name, .contents = str, .offset = 0});
|
lexerState->expansions.push_front({.name = name, .contents = str, .offset = 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
void lexer_CheckRecursionDepth() {
|
void lexer_CheckRecursionDepth() {
|
||||||
if (lexerState->expansions.size() > maxRecursionDepth + 1)
|
if (lexerState->expansions.size() > maxRecursionDepth + 1) {
|
||||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isMacroChar(char c) {
|
static bool isMacroChar(char c) {
|
||||||
@@ -620,10 +633,11 @@ static uint32_t readBracketedMacroArgNum() {
|
|||||||
Symbol const *sym = sym_FindScopedValidSymbol(symName);
|
Symbol const *sym = sym_FindScopedValidSymbol(symName);
|
||||||
|
|
||||||
if (!sym) {
|
if (!sym) {
|
||||||
if (sym_IsPurgedScoped(symName))
|
if (sym_IsPurgedScoped(symName)) {
|
||||||
error("Bracketed symbol \"%s\" does not exist; it was purged\n", symName.c_str());
|
error("Bracketed symbol \"%s\" does not exist; it was purged\n", symName.c_str());
|
||||||
else
|
} else {
|
||||||
error("Bracketed symbol \"%s\" does not exist\n", symName.c_str());
|
error("Bracketed symbol \"%s\" does not exist\n", symName.c_str());
|
||||||
|
}
|
||||||
num = 0;
|
num = 0;
|
||||||
symbolError = true;
|
symbolError = true;
|
||||||
} else if (!sym->isNumeric()) {
|
} else if (!sym->isNumeric()) {
|
||||||
@@ -708,21 +722,25 @@ static std::shared_ptr<std::string> readMacroArg(char name) {
|
|||||||
int LexerState::peekChar() {
|
int LexerState::peekChar() {
|
||||||
// This is `.peekCharAhead()` modified for zero lookahead distance
|
// This is `.peekCharAhead()` modified for zero lookahead distance
|
||||||
for (Expansion &exp : expansions) {
|
for (Expansion &exp : expansions) {
|
||||||
if (exp.offset < exp.size())
|
if (exp.offset < exp.size()) {
|
||||||
return static_cast<uint8_t>((*exp.contents)[exp.offset]);
|
return static_cast<uint8_t>((*exp.contents)[exp.offset]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.holds<ViewedContent>()) {
|
if (content.holds<ViewedContent>()) {
|
||||||
auto &view = content.get<ViewedContent>();
|
auto &view = content.get<ViewedContent>();
|
||||||
if (view.offset < view.span.size)
|
if (view.offset < view.span.size) {
|
||||||
return static_cast<uint8_t>(view.span.ptr[view.offset]);
|
return static_cast<uint8_t>(view.span.ptr[view.offset]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
auto &cbuf = content.get<BufferedContent>();
|
auto &cbuf = content.get<BufferedContent>();
|
||||||
if (cbuf.size == 0)
|
if (cbuf.size == 0) {
|
||||||
cbuf.refill();
|
cbuf.refill();
|
||||||
assume(cbuf.offset < LEXER_BUF_SIZE);
|
}
|
||||||
if (cbuf.size > 0)
|
assume(cbuf.offset < std::size(cbuf.buf));
|
||||||
|
if (cbuf.size > 0) {
|
||||||
return static_cast<uint8_t>(cbuf.buf[cbuf.offset]);
|
return static_cast<uint8_t>(cbuf.buf[cbuf.offset]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there aren't enough chars, give up
|
// If there aren't enough chars, give up
|
||||||
@@ -737,22 +755,26 @@ int LexerState::peekCharAhead() {
|
|||||||
// An expansion that has reached its end will have `exp.offset` == `exp.size()`,
|
// An expansion that has reached its end will have `exp.offset` == `exp.size()`,
|
||||||
// and `.peekCharAhead()` will continue with its parent
|
// and `.peekCharAhead()` will continue with its parent
|
||||||
assume(exp.offset <= exp.size());
|
assume(exp.offset <= exp.size());
|
||||||
if (exp.offset + distance < exp.size())
|
if (exp.offset + distance < exp.size()) {
|
||||||
return static_cast<uint8_t>((*exp.contents)[exp.offset + distance]);
|
return static_cast<uint8_t>((*exp.contents)[exp.offset + distance]);
|
||||||
|
}
|
||||||
distance -= exp.size() - exp.offset;
|
distance -= exp.size() - exp.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.holds<ViewedContent>()) {
|
if (content.holds<ViewedContent>()) {
|
||||||
auto &view = content.get<ViewedContent>();
|
auto &view = content.get<ViewedContent>();
|
||||||
if (view.offset + distance < view.span.size)
|
if (view.offset + distance < view.span.size) {
|
||||||
return static_cast<uint8_t>(view.span.ptr[view.offset + distance]);
|
return static_cast<uint8_t>(view.span.ptr[view.offset + distance]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
auto &cbuf = content.get<BufferedContent>();
|
auto &cbuf = content.get<BufferedContent>();
|
||||||
assume(distance < LEXER_BUF_SIZE);
|
assume(distance < std::size(cbuf.buf));
|
||||||
if (cbuf.size <= distance)
|
if (cbuf.size <= distance) {
|
||||||
cbuf.refill();
|
cbuf.refill();
|
||||||
if (cbuf.size > distance)
|
}
|
||||||
return static_cast<uint8_t>(cbuf.buf[(cbuf.offset + distance) % LEXER_BUF_SIZE]);
|
if (cbuf.size > distance) {
|
||||||
|
return static_cast<uint8_t>(cbuf.buf[(cbuf.offset + distance) % std::size(cbuf.buf)]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there aren't enough chars, give up
|
// If there aren't enough chars, give up
|
||||||
@@ -766,8 +788,9 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth);
|
|||||||
static int peek() {
|
static int peek() {
|
||||||
int c = lexerState->peekChar();
|
int c = lexerState->peekChar();
|
||||||
|
|
||||||
if (lexerState->macroArgScanDistance > 0)
|
if (lexerState->macroArgScanDistance > 0) {
|
||||||
return c;
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
lexerState->macroArgScanDistance++; // Do not consider again
|
lexerState->macroArgScanDistance++; // Do not consider again
|
||||||
|
|
||||||
@@ -810,8 +833,9 @@ static int peek() {
|
|||||||
|
|
||||||
static void shiftChar() {
|
static void shiftChar() {
|
||||||
if (lexerState->capturing) {
|
if (lexerState->capturing) {
|
||||||
if (lexerState->captureBuf)
|
if (lexerState->captureBuf) {
|
||||||
lexerState->captureBuf->push_back(peek());
|
lexerState->captureBuf->push_back(peek());
|
||||||
|
}
|
||||||
lexerState->captureSize++;
|
lexerState->captureSize++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -828,7 +852,6 @@ restart:
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Advance within the file contents
|
// Advance within the file contents
|
||||||
lexerState->colNo++;
|
|
||||||
if (lexerState->content.holds<ViewedContent>()) {
|
if (lexerState->content.holds<ViewedContent>()) {
|
||||||
lexerState->content.get<ViewedContent>().offset++;
|
lexerState->content.get<ViewedContent>().offset++;
|
||||||
} else {
|
} else {
|
||||||
@@ -841,14 +864,16 @@ static int nextChar() {
|
|||||||
int c = peek();
|
int c = peek();
|
||||||
|
|
||||||
// If not at EOF, advance read position
|
// If not at EOF, advance read position
|
||||||
if (c != EOF)
|
if (c != EOF) {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleCRLF(int c) {
|
static void handleCRLF(int c) {
|
||||||
if (c == '\r' && peek() == '\n')
|
if (c == '\r' && peek() == '\n') {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto scopedDisableExpansions() {
|
static auto scopedDisableExpansions() {
|
||||||
@@ -866,18 +891,16 @@ uint32_t lexer_GetLineNo() {
|
|||||||
return lexerState->lineNo;
|
return lexerState->lineNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t lexer_GetColNo() {
|
|
||||||
return lexerState->colNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
void lexer_DumpStringExpansions() {
|
void lexer_DumpStringExpansions() {
|
||||||
if (!lexerState)
|
if (!lexerState) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (Expansion &exp : lexerState->expansions) {
|
for (Expansion &exp : lexerState->expansions) {
|
||||||
// Only register EQUS expansions, not string args
|
// Only register EQUS expansions, not string args
|
||||||
if (exp.name)
|
if (exp.name) {
|
||||||
fprintf(stderr, "while expanding symbol \"%s\"\n", exp.name->c_str());
|
fprintf(stderr, "while expanding symbol \"%s\"\n", exp.name->c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -893,12 +916,12 @@ static void discardBlockComment() {
|
|||||||
error("Unterminated block comment\n");
|
error("Unterminated block comment\n");
|
||||||
return;
|
return;
|
||||||
case '\r':
|
case '\r':
|
||||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
|
||||||
handleCRLF(c);
|
handleCRLF(c);
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case '\n':
|
case '\n':
|
||||||
if (lexerState->expansions.empty())
|
if (lexerState->expansions.empty()) {
|
||||||
nextLine();
|
nextLine();
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
case '/':
|
case '/':
|
||||||
if (peek() == '*') {
|
if (peek() == '*') {
|
||||||
@@ -922,8 +945,9 @@ static void discardComment() {
|
|||||||
for (;; shiftChar()) {
|
for (;; shiftChar()) {
|
||||||
int c = peek();
|
int c = peek();
|
||||||
|
|
||||||
if (c == EOF || c == '\r' || c == '\n')
|
if (c == EOF || c == '\r' || c == '\n') {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,10 +959,10 @@ static void discardLineContinuation() {
|
|||||||
shiftChar();
|
shiftChar();
|
||||||
} else if (c == '\r' || c == '\n') {
|
} else if (c == '\r' || c == '\n') {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
|
||||||
handleCRLF(c);
|
handleCRLF(c);
|
||||||
if (lexerState->expansions.empty())
|
if (lexerState->expansions.empty()) {
|
||||||
nextLine();
|
nextLine();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
} else if (c == ';') {
|
} else if (c == ';') {
|
||||||
discardComment();
|
discardComment();
|
||||||
@@ -969,12 +993,14 @@ static uint32_t readNumber(int radix, uint32_t baseValue) {
|
|||||||
for (;; shiftChar()) {
|
for (;; shiftChar()) {
|
||||||
int c = peek();
|
int c = peek();
|
||||||
|
|
||||||
if (c == '_')
|
if (c == '_') {
|
||||||
continue;
|
continue;
|
||||||
else if (c < '0' || c > '0' + radix - 1)
|
} else if (c < '0' || c > '0' + radix - 1) {
|
||||||
break;
|
break;
|
||||||
if (value > (UINT32_MAX - (c - '0')) / radix)
|
}
|
||||||
|
if (value > (UINT32_MAX - (c - '0')) / radix) {
|
||||||
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
||||||
|
}
|
||||||
value = value * radix + (c - '0');
|
value = value * radix + (c - '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1006,8 +1032,9 @@ static uint32_t readFractionalPart(uint32_t integer) {
|
|||||||
warning(WARNING_LARGE_CONSTANT, "Precision of fixed-point constant is too large\n");
|
warning(WARNING_LARGE_CONSTANT, "Precision of fixed-point constant is too large\n");
|
||||||
// Discard any additional digits
|
// Discard any additional digits
|
||||||
shiftChar();
|
shiftChar();
|
||||||
while (c = peek(), (c >= '0' && c <= '9') || c == '_')
|
while (c = peek(), (c >= '0' && c <= '9') || c == '_') {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
value = value * 10 + (c - '0');
|
value = value * 10 + (c - '0');
|
||||||
@@ -1024,16 +1051,18 @@ static uint32_t readFractionalPart(uint32_t integer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (precision == 0) {
|
if (precision == 0) {
|
||||||
if (state >= READFRACTIONALPART_PRECISION)
|
if (state >= READFRACTIONALPART_PRECISION) {
|
||||||
error("Invalid fixed-point constant, no significant digits after 'q'\n");
|
error("Invalid fixed-point constant, no significant digits after 'q'\n");
|
||||||
|
}
|
||||||
precision = fixPrecision;
|
precision = fixPrecision;
|
||||||
} else if (precision > 31) {
|
} else if (precision > 31) {
|
||||||
error("Fixed-point constant precision must be between 1 and 31\n");
|
error("Fixed-point constant precision must be between 1 and 31\n");
|
||||||
precision = fixPrecision;
|
precision = fixPrecision;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (integer >= (1ULL << (32 - precision)))
|
if (integer >= (1ULL << (32 - precision))) {
|
||||||
warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n");
|
warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n");
|
||||||
|
}
|
||||||
|
|
||||||
// Cast to unsigned avoids undefined overflow behavior
|
// Cast to unsigned avoids undefined overflow behavior
|
||||||
uint32_t fractional =
|
uint32_t fractional =
|
||||||
@@ -1052,16 +1081,18 @@ static uint32_t readBinaryNumber() {
|
|||||||
int bit;
|
int bit;
|
||||||
|
|
||||||
// Check for '_' after digits in case one of the digits is '_'
|
// Check for '_' after digits in case one of the digits is '_'
|
||||||
if (c == binDigits[0])
|
if (c == binDigits[0]) {
|
||||||
bit = 0;
|
bit = 0;
|
||||||
else if (c == binDigits[1])
|
} else if (c == binDigits[1]) {
|
||||||
bit = 1;
|
bit = 1;
|
||||||
else if (c == '_')
|
} else if (c == '_') {
|
||||||
continue;
|
continue;
|
||||||
else
|
} else {
|
||||||
break;
|
break;
|
||||||
if (value > (UINT32_MAX - bit) / 2)
|
}
|
||||||
|
if (value > (UINT32_MAX - bit) / 2) {
|
||||||
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
||||||
|
}
|
||||||
value = value * 2 + bit;
|
value = value * 2 + bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1075,26 +1106,29 @@ static uint32_t readHexNumber() {
|
|||||||
for (;; shiftChar()) {
|
for (;; shiftChar()) {
|
||||||
int c = peek();
|
int c = peek();
|
||||||
|
|
||||||
if (c >= 'a' && c <= 'f')
|
if (c >= 'a' && c <= 'f') {
|
||||||
c = c - 'a' + 10;
|
c = c - 'a' + 10;
|
||||||
else if (c >= 'A' && c <= 'F')
|
} else if (c >= 'A' && c <= 'F') {
|
||||||
c = c - 'A' + 10;
|
c = c - 'A' + 10;
|
||||||
else if (c >= '0' && c <= '9')
|
} else if (c >= '0' && c <= '9') {
|
||||||
c = c - '0';
|
c = c - '0';
|
||||||
else if (c == '_' && !empty)
|
} else if (c == '_' && !empty) {
|
||||||
continue;
|
continue;
|
||||||
else
|
} else {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (value > (UINT32_MAX - c) / 16)
|
if (value > (UINT32_MAX - c) / 16) {
|
||||||
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
||||||
|
}
|
||||||
value = value * 16 + c;
|
value = value * 16 + c;
|
||||||
|
|
||||||
empty = false;
|
empty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty)
|
if (empty) {
|
||||||
error("Invalid integer constant, no digits after '$'\n");
|
error("Invalid integer constant, no digits after '$'\n");
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -1110,34 +1144,37 @@ static uint32_t readGfxConstant() {
|
|||||||
uint32_t pixel;
|
uint32_t pixel;
|
||||||
|
|
||||||
// Check for '_' after digits in case one of the digits is '_'
|
// Check for '_' after digits in case one of the digits is '_'
|
||||||
if (c == gfxDigits[0])
|
if (c == gfxDigits[0]) {
|
||||||
pixel = 0;
|
pixel = 0;
|
||||||
else if (c == gfxDigits[1])
|
} else if (c == gfxDigits[1]) {
|
||||||
pixel = 1;
|
pixel = 1;
|
||||||
else if (c == gfxDigits[2])
|
} else if (c == gfxDigits[2]) {
|
||||||
pixel = 2;
|
pixel = 2;
|
||||||
else if (c == gfxDigits[3])
|
} else if (c == gfxDigits[3]) {
|
||||||
pixel = 3;
|
pixel = 3;
|
||||||
else if (c == '_' && width > 0)
|
} else if (c == '_' && width > 0) {
|
||||||
continue;
|
continue;
|
||||||
else
|
} else {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (width < 8) {
|
if (width < 8) {
|
||||||
bitPlaneLower = bitPlaneLower << 1 | (pixel & 1);
|
bitPlaneLower = bitPlaneLower << 1 | (pixel & 1);
|
||||||
bitPlaneUpper = bitPlaneUpper << 1 | (pixel >> 1);
|
bitPlaneUpper = bitPlaneUpper << 1 | (pixel >> 1);
|
||||||
}
|
}
|
||||||
if (width < 9)
|
if (width < 9) {
|
||||||
width++;
|
width++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (width == 0)
|
if (width == 0) {
|
||||||
error("Invalid graphics constant, no digits after '`'\n");
|
error("Invalid graphics constant, no digits after '`'\n");
|
||||||
else if (width == 9)
|
} else if (width == 9) {
|
||||||
warning(
|
warning(
|
||||||
WARNING_LARGE_CONSTANT,
|
WARNING_LARGE_CONSTANT,
|
||||||
"Graphics constant is too long, only first 8 pixels considered\n"
|
"Graphics constant is too long, only first 8 pixels considered\n"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return bitPlaneUpper << 8 | bitPlaneLower;
|
return bitPlaneUpper << 8 | bitPlaneLower;
|
||||||
}
|
}
|
||||||
@@ -1165,8 +1202,9 @@ static Token readIdentifier(char firstChar, bool raw) {
|
|||||||
identifier += c;
|
identifier += c;
|
||||||
|
|
||||||
// If the char was a dot, mark the identifier as local
|
// If the char was a dot, mark the identifier as local
|
||||||
if (c == '.')
|
if (c == '.') {
|
||||||
tokenType = T_(LOCAL_ID);
|
tokenType = T_(LOCAL_ID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to check for a keyword if the identifier is not raw
|
// Attempt to check for a keyword if the identifier is not raw
|
||||||
@@ -1180,8 +1218,9 @@ static Token readIdentifier(char firstChar, bool raw) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 (identifier.find_first_not_of('.') == identifier.npos)
|
if (identifier.find_first_not_of('.') == identifier.npos) {
|
||||||
tokenType = T_(ID);
|
tokenType = T_(ID);
|
||||||
|
}
|
||||||
|
|
||||||
return Token(tokenType, identifier);
|
return Token(tokenType, identifier);
|
||||||
}
|
}
|
||||||
@@ -1189,8 +1228,9 @@ static Token readIdentifier(char firstChar, bool raw) {
|
|||||||
// Functions to read strings
|
// Functions to read strings
|
||||||
|
|
||||||
static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
||||||
if (depth > maxRecursionDepth)
|
if (depth > maxRecursionDepth) {
|
||||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||||
|
}
|
||||||
|
|
||||||
std::string fmtBuf;
|
std::string fmtBuf;
|
||||||
FormatSpec fmt{};
|
FormatSpec fmt{};
|
||||||
@@ -1218,11 +1258,13 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
|||||||
break;
|
break;
|
||||||
} else if (c == ':' && !fmt.isFinished()) { // Format spec, only once
|
} else if (c == ':' && !fmt.isFinished()) { // Format spec, only once
|
||||||
shiftChar();
|
shiftChar();
|
||||||
for (char f : fmtBuf)
|
for (char f : fmtBuf) {
|
||||||
fmt.useCharacter(f);
|
fmt.useCharacter(f);
|
||||||
|
}
|
||||||
fmt.finishCharacters();
|
fmt.finishCharacters();
|
||||||
if (!fmt.isValid())
|
if (!fmt.isValid()) {
|
||||||
error("Invalid format spec '%s'\n", fmtBuf.c_str());
|
error("Invalid format spec '%s'\n", fmtBuf.c_str());
|
||||||
|
}
|
||||||
fmtBuf.clear(); // Now that format has been set, restart at beginning of string
|
fmtBuf.clear(); // Now that format has been set, restart at beginning of string
|
||||||
} else {
|
} else {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
@@ -1249,10 +1291,11 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
|||||||
Symbol const *sym = sym_FindScopedValidSymbol(fmtBuf);
|
Symbol const *sym = sym_FindScopedValidSymbol(fmtBuf);
|
||||||
|
|
||||||
if (!sym || !sym->isDefined()) {
|
if (!sym || !sym->isDefined()) {
|
||||||
if (sym_IsPurgedScoped(fmtBuf))
|
if (sym_IsPurgedScoped(fmtBuf)) {
|
||||||
error("Interpolated symbol \"%s\" does not exist; it was purged\n", fmtBuf.c_str());
|
error("Interpolated symbol \"%s\" does not exist; it was purged\n", fmtBuf.c_str());
|
||||||
else
|
} else {
|
||||||
error("Interpolated symbol \"%s\" does not exist\n", fmtBuf.c_str());
|
error("Interpolated symbol \"%s\" does not exist\n", fmtBuf.c_str());
|
||||||
|
}
|
||||||
} else if (sym->type == SYM_EQUS) {
|
} else if (sym->type == SYM_EQUS) {
|
||||||
auto buf = std::make_shared<std::string>();
|
auto buf = std::make_shared<std::string>();
|
||||||
fmt.appendString(*buf, *sym->getEqus());
|
fmt.appendString(*buf, *sym->getEqus());
|
||||||
@@ -1326,7 +1369,6 @@ static std::string readString(bool raw) {
|
|||||||
|
|
||||||
// Handle '\r' or '\n' (in multiline strings only, already handled above otherwise)
|
// Handle '\r' or '\n' (in multiline strings only, already handled above otherwise)
|
||||||
if (c == '\r' || c == '\n') {
|
if (c == '\r' || c == '\n') {
|
||||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
|
||||||
handleCRLF(c);
|
handleCRLF(c);
|
||||||
nextLine();
|
nextLine();
|
||||||
c = '\n';
|
c = '\n';
|
||||||
@@ -1336,8 +1378,9 @@ static std::string readString(bool raw) {
|
|||||||
case '"':
|
case '"':
|
||||||
if (multiline) {
|
if (multiline) {
|
||||||
// Only """ ends a multi-line string
|
// Only """ ends a multi-line string
|
||||||
if (peek() != '"')
|
if (peek() != '"') {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
shiftChar();
|
shiftChar();
|
||||||
if (peek() != '"') {
|
if (peek() != '"') {
|
||||||
str += '"';
|
str += '"';
|
||||||
@@ -1348,8 +1391,9 @@ static std::string readString(bool raw) {
|
|||||||
return str;
|
return str;
|
||||||
|
|
||||||
case '\\': // Character escape or macro arg
|
case '\\': // Character escape or macro arg
|
||||||
if (raw)
|
if (raw) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
c = peek();
|
c = peek();
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '\\':
|
case '\\':
|
||||||
@@ -1416,8 +1460,9 @@ static std::string readString(bool raw) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case '{': // Symbol interpolation
|
case '{': // Symbol interpolation
|
||||||
if (raw)
|
if (raw) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
// We'll be exiting the string scope, so re-enable expansions
|
// We'll be exiting the string scope, so re-enable expansions
|
||||||
// (Not interpolations, since they're handled by the function itself...)
|
// (Not interpolations, since they're handled by the function itself...)
|
||||||
lexerState->disableMacroArgs = false;
|
lexerState->disableMacroArgs = false;
|
||||||
@@ -1468,7 +1513,6 @@ static void appendStringLiteral(std::string &str, bool raw) {
|
|||||||
|
|
||||||
// Handle '\r' or '\n' (in multiline strings only, already handled above otherwise)
|
// Handle '\r' or '\n' (in multiline strings only, already handled above otherwise)
|
||||||
if (c == '\r' || c == '\n') {
|
if (c == '\r' || c == '\n') {
|
||||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
|
||||||
handleCRLF(c);
|
handleCRLF(c);
|
||||||
nextLine();
|
nextLine();
|
||||||
c = '\n';
|
c = '\n';
|
||||||
@@ -1478,12 +1522,14 @@ static void appendStringLiteral(std::string &str, bool raw) {
|
|||||||
case '"':
|
case '"':
|
||||||
if (multiline) {
|
if (multiline) {
|
||||||
// Only """ ends a multi-line string
|
// Only """ ends a multi-line string
|
||||||
if (peek() != '"')
|
if (peek() != '"') {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
str += '"';
|
str += '"';
|
||||||
shiftChar();
|
shiftChar();
|
||||||
if (peek() != '"')
|
if (peek() != '"') {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
str += '"';
|
str += '"';
|
||||||
shiftChar();
|
shiftChar();
|
||||||
}
|
}
|
||||||
@@ -1491,8 +1537,9 @@ static void appendStringLiteral(std::string &str, bool raw) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
case '\\': // Character escape or macro arg
|
case '\\': // Character escape or macro arg
|
||||||
if (raw)
|
if (raw) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
c = peek();
|
c = peek();
|
||||||
switch (c) {
|
switch (c) {
|
||||||
// Character escape
|
// Character escape
|
||||||
@@ -1550,8 +1597,9 @@ static void appendStringLiteral(std::string &str, bool raw) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case '{': // Symbol interpolation
|
case '{': // Symbol interpolation
|
||||||
if (raw)
|
if (raw) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
// We'll be exiting the string scope, so re-enable expansions
|
// We'll be exiting the string scope, so re-enable expansions
|
||||||
// (Not interpolations, since they're handled by the function itself...)
|
// (Not interpolations, since they're handled by the function itself...)
|
||||||
lexerState->disableMacroArgs = false;
|
lexerState->disableMacroArgs = false;
|
||||||
@@ -1850,12 +1898,14 @@ static Token yylex_NORMAL() {
|
|||||||
|
|
||||||
// An ELIF after a taken IF needs to not evaluate its condition
|
// An ELIF after a taken IF needs to not evaluate its condition
|
||||||
if (token.type == T_(POP_ELIF) && lexerState->lastToken == T_(NEWLINE)
|
if (token.type == T_(POP_ELIF) && lexerState->lastToken == T_(NEWLINE)
|
||||||
&& lexer_GetIFDepth() > 0 && lexer_RanIFBlock() && !lexer_ReachedELSEBlock())
|
&& lexer_GetIFDepth() > 0 && lexer_RanIFBlock() && !lexer_ReachedELSEBlock()) {
|
||||||
return yylex_SKIP_TO_ENDC();
|
return yylex_SKIP_TO_ENDC();
|
||||||
|
}
|
||||||
|
|
||||||
// If a keyword, don't try to expand
|
// If a keyword, don't try to expand
|
||||||
if (token.type != T_(ID) && token.type != T_(LOCAL_ID))
|
if (token.type != T_(ID) && token.type != T_(LOCAL_ID)) {
|
||||||
return token;
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
// `token` is either an `ID` or a `LOCAL_ID`, and both have a `std::string` value.
|
// `token` is either an `ID` or a `LOCAL_ID`, and both have a `std::string` value.
|
||||||
assume(token.value.holds<std::string>());
|
assume(token.value.holds<std::string>());
|
||||||
@@ -1886,8 +1936,9 @@ static Token yylex_NORMAL() {
|
|||||||
// character *immediately* follows the identifier. Thus, at the beginning of a line,
|
// character *immediately* follows the identifier. Thus, at the beginning of a line,
|
||||||
// "Label:" and "mac:" are treated as label definitions, but "Label :" and "mac :"
|
// "Label:" and "mac:" are treated as label definitions, but "Label :" and "mac :"
|
||||||
// are treated as macro invocations.
|
// are treated as macro invocations.
|
||||||
if (token.type == T_(ID) && peek() == ':')
|
if (token.type == T_(ID) && peek() == ':') {
|
||||||
token.type = T_(LABEL);
|
token.type = T_(LABEL);
|
||||||
|
}
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
@@ -1917,8 +1968,9 @@ static Token yylex_RAW() {
|
|||||||
shiftChar();
|
shiftChar();
|
||||||
c = peek();
|
c = peek();
|
||||||
// If not a line continuation, handle as a normal char
|
// If not a line continuation, handle as a normal char
|
||||||
if (!isWhitespace(c) && c != '\n' && c != '\r')
|
if (!isWhitespace(c) && c != '\n' && c != '\r') {
|
||||||
goto backslash;
|
goto backslash;
|
||||||
|
}
|
||||||
// Line continuations count as "whitespace"
|
// Line continuations count as "whitespace"
|
||||||
discardLineContinuation();
|
discardLineContinuation();
|
||||||
} else {
|
} else {
|
||||||
@@ -1964,18 +2016,21 @@ static Token yylex_RAW() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ',': // End of macro arg
|
case ',': // End of macro arg
|
||||||
if (parenDepth == 0)
|
if (parenDepth == 0) {
|
||||||
goto finish;
|
goto finish;
|
||||||
|
}
|
||||||
goto append;
|
goto append;
|
||||||
|
|
||||||
case '(': // Open parentheses inside macro args
|
case '(': // Open parentheses inside macro args
|
||||||
if (parenDepth < UINT_MAX)
|
if (parenDepth < UINT_MAX) {
|
||||||
parenDepth++;
|
parenDepth++;
|
||||||
|
}
|
||||||
goto append;
|
goto append;
|
||||||
|
|
||||||
case ')': // Close parentheses inside macro args
|
case ')': // Close parentheses inside macro args
|
||||||
if (parenDepth > 0)
|
if (parenDepth > 0) {
|
||||||
parenDepth--;
|
parenDepth--;
|
||||||
|
}
|
||||||
goto append;
|
goto append;
|
||||||
|
|
||||||
case '\\': // Character escape
|
case '\\': // Character escape
|
||||||
@@ -2055,8 +2110,9 @@ finish:
|
|||||||
// an empty raw string before it). This will not be treated as a
|
// an empty raw string before it). This will not be treated as a
|
||||||
// macro argument. To pass an empty last argument, use a second
|
// macro argument. To pass an empty last argument, use a second
|
||||||
// trailing comma.
|
// trailing comma.
|
||||||
if (!str.empty())
|
if (!str.empty()) {
|
||||||
return Token(T_(STRING), str);
|
return Token(T_(STRING), str);
|
||||||
|
}
|
||||||
lexer_SetMode(LEXER_NORMAL);
|
lexer_SetMode(LEXER_NORMAL);
|
||||||
|
|
||||||
if (c == '\r' || c == '\n') {
|
if (c == '\r' || c == '\n') {
|
||||||
@@ -2088,8 +2144,9 @@ static Token skipIfBlock(bool toEndc) {
|
|||||||
|
|
||||||
for (;; shiftChar()) {
|
for (;; shiftChar()) {
|
||||||
c = peek();
|
c = peek();
|
||||||
if (!isWhitespace(c))
|
if (!isWhitespace(c)) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startsIdentifier(c)) {
|
if (startsIdentifier(c)) {
|
||||||
@@ -2100,23 +2157,28 @@ static Token skipIfBlock(bool toEndc) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case T_(POP_ELIF):
|
case T_(POP_ELIF):
|
||||||
if (lexer_ReachedELSEBlock())
|
if (lexer_ReachedELSEBlock()) {
|
||||||
fatalerror("Found ELIF after an ELSE block\n");
|
fatalerror("Found ELIF after an ELSE block\n");
|
||||||
if (!toEndc && lexer_GetIFDepth() == startingDepth)
|
}
|
||||||
|
if (!toEndc && lexer_GetIFDepth() == startingDepth) {
|
||||||
return token;
|
return token;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case T_(POP_ELSE):
|
case T_(POP_ELSE):
|
||||||
if (lexer_ReachedELSEBlock())
|
if (lexer_ReachedELSEBlock()) {
|
||||||
fatalerror("Found ELSE after an ELSE block\n");
|
fatalerror("Found ELSE after an ELSE block\n");
|
||||||
|
}
|
||||||
lexer_ReachELSEBlock();
|
lexer_ReachELSEBlock();
|
||||||
if (!toEndc && lexer_GetIFDepth() == startingDepth)
|
if (!toEndc && lexer_GetIFDepth() == startingDepth) {
|
||||||
return token;
|
return token;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case T_(POP_ENDC):
|
case T_(POP_ENDC):
|
||||||
if (lexer_GetIFDepth() == startingDepth)
|
if (lexer_GetIFDepth() == startingDepth) {
|
||||||
return token;
|
return token;
|
||||||
|
}
|
||||||
lexer_DecIFDepth();
|
lexer_DecIFDepth();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -2141,7 +2203,6 @@ static Token skipIfBlock(bool toEndc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c == '\r' || c == '\n') {
|
if (c == '\r' || c == '\n') {
|
||||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
|
||||||
handleCRLF(c);
|
handleCRLF(c);
|
||||||
// Do this both on line continuations and plain EOLs
|
// Do this both on line continuations and plain EOLs
|
||||||
nextLine();
|
nextLine();
|
||||||
@@ -2173,8 +2234,9 @@ static Token yylex_SKIP_TO_ENDR() {
|
|||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
c = peek();
|
c = peek();
|
||||||
if (!isWhitespace(c))
|
if (!isWhitespace(c)) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
shiftChar();
|
shiftChar();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2188,8 +2250,9 @@ static Token yylex_SKIP_TO_ENDR() {
|
|||||||
|
|
||||||
case T_(POP_ENDR):
|
case T_(POP_ENDR):
|
||||||
depth--;
|
depth--;
|
||||||
if (!depth)
|
if (!depth) {
|
||||||
return Token(T_(YYEOF)); // yywrap() will finish the REPT/FOR loop
|
return Token(T_(YYEOF)); // yywrap() will finish the REPT/FOR loop
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case T_(POP_IF):
|
case T_(POP_IF):
|
||||||
@@ -2221,7 +2284,6 @@ static Token yylex_SKIP_TO_ENDR() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c == '\r' || c == '\n') {
|
if (c == '\r' || c == '\n') {
|
||||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
|
||||||
handleCRLF(c);
|
handleCRLF(c);
|
||||||
// Do this both on line continuations and plain EOLs
|
// Do this both on line continuations and plain EOLs
|
||||||
nextLine();
|
nextLine();
|
||||||
@@ -2235,11 +2297,13 @@ yy::parser::symbol_type yylex() {
|
|||||||
lexerState = lexerStateEOL;
|
lexerState = lexerStateEOL;
|
||||||
lexerStateEOL = nullptr;
|
lexerStateEOL = nullptr;
|
||||||
}
|
}
|
||||||
if (lexerState->lastToken == T_(EOB) && yywrap())
|
if (lexerState->lastToken == T_(EOB) && yywrap()) {
|
||||||
return yy::parser::make_YYEOF();
|
return yy::parser::make_YYEOF();
|
||||||
|
}
|
||||||
// Newlines read within an expansion should not increase the line count
|
// Newlines read within an expansion should not increase the line count
|
||||||
if (lexerState->atLineStart && lexerState->expansions.empty())
|
if (lexerState->atLineStart && lexerState->expansions.empty()) {
|
||||||
nextLine();
|
nextLine();
|
||||||
|
}
|
||||||
|
|
||||||
static Token (* const lexerModeFuncs[NB_LEXER_MODES])() = {
|
static Token (* const lexerModeFuncs[NB_LEXER_MODES])() = {
|
||||||
yylex_NORMAL,
|
yylex_NORMAL,
|
||||||
@@ -2286,22 +2350,23 @@ static Capture startCapture() {
|
|||||||
auto &view = lexerState->content.get<ViewedContent>();
|
auto &view = lexerState->content.get<ViewedContent>();
|
||||||
return {
|
return {
|
||||||
.lineNo = lineNo, .span = {.ptr = view.makeSharedContentPtr(), .size = 0}
|
.lineNo = lineNo, .span = {.ptr = view.makeSharedContentPtr(), .size = 0}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
assume(lexerState->captureBuf == nullptr);
|
assume(lexerState->captureBuf == nullptr);
|
||||||
lexerState->captureBuf = std::make_shared<std::vector<char>>();
|
lexerState->captureBuf = std::make_shared<std::vector<char>>();
|
||||||
// `.span.ptr == nullptr`; indicates to retrieve the capture buffer when done capturing
|
// `.span.ptr == nullptr`; indicates to retrieve the capture buffer when done capturing
|
||||||
return {
|
return {
|
||||||
.lineNo = lineNo, .span = {.ptr = nullptr, .size = 0}
|
.lineNo = lineNo, .span = {.ptr = nullptr, .size = 0}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void endCapture(Capture &capture) {
|
static void endCapture(Capture &capture) {
|
||||||
// This being `nullptr` means we're capturing from the capture buffer, which is reallocated
|
// This being `nullptr` means we're capturing from the capture buffer, which is reallocated
|
||||||
// during the whole capture process, and so MUST be retrieved at the end
|
// during the whole capture process, and so MUST be retrieved at the end
|
||||||
if (!capture.span.ptr)
|
if (!capture.span.ptr) {
|
||||||
capture.span.ptr = lexerState->makeSharedCaptureBufPtr();
|
capture.span.ptr = lexerState->makeSharedCaptureBufPtr();
|
||||||
|
}
|
||||||
capture.span.size = lexerState->captureSize;
|
capture.span.size = lexerState->captureSize;
|
||||||
|
|
||||||
// ENDR/ENDM or EOF puts us past the start of the line
|
// ENDR/ENDM or EOF puts us past the start of the line
|
||||||
@@ -2339,7 +2404,7 @@ Capture lexer_CaptureRept() {
|
|||||||
endCapture(capture);
|
endCapture(capture);
|
||||||
// The final ENDR has been captured, but we don't want it!
|
// The final ENDR has been captured, but we don't want it!
|
||||||
// We know we have read exactly "ENDR", not e.g. an EQUS
|
// We know we have read exactly "ENDR", not e.g. an EQUS
|
||||||
capture.span.size -= QUOTEDSTRLEN("ENDR");
|
capture.span.size -= literal_strlen("ENDR");
|
||||||
return capture;
|
return capture;
|
||||||
}
|
}
|
||||||
depth--;
|
depth--;
|
||||||
@@ -2385,7 +2450,7 @@ Capture lexer_CaptureMacro() {
|
|||||||
endCapture(capture);
|
endCapture(capture);
|
||||||
// The ENDM has been captured, but we don't want it!
|
// The ENDM has been captured, but we don't want it!
|
||||||
// We know we have read exactly "ENDM", not e.g. an EQUS
|
// We know we have read exactly "ENDM", not e.g. an EQUS
|
||||||
capture.span.size -= QUOTEDSTRLEN("ENDM");
|
capture.span.size -= literal_strlen("ENDM");
|
||||||
return capture;
|
return capture;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "asm/macro.hpp"
|
#include "asm/macro.hpp"
|
||||||
|
|
||||||
@@ -6,12 +6,8 @@
|
|||||||
#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(uint32_t i) const {
|
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
||||||
uint32_t realIndex = i + shift - 1;
|
uint32_t realIndex = i + shift - 1;
|
||||||
|
|
||||||
@@ -21,13 +17,15 @@ std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
|||||||
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 +36,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
134
src/asm/main.cpp
134
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'},
|
||||||
@@ -88,7 +89,7 @@ static option const longopts[] = {
|
|||||||
|
|
||||||
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"
|
||||||
@@ -111,12 +112,14 @@ 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 +131,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 +168,25 @@ 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':
|
||||||
|
printUsage();
|
||||||
|
exit(0);
|
||||||
|
|
||||||
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 +194,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);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'o':
|
case 'o':
|
||||||
@@ -199,11 +211,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 +226,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 +245,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 +299,12 @@ 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)
|
}
|
||||||
|
if (verbose) {
|
||||||
printf("State filename %s\n", name);
|
printf("State filename %s\n", name);
|
||||||
|
}
|
||||||
stateFileSpecs.emplace(name, std::move(features));
|
stateFileSpecs.emplace(name, std::move(features));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -304,11 +329,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 +354,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;
|
||||||
}
|
}
|
||||||
@@ -343,8 +372,9 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +390,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());
|
||||||
|
}
|
||||||
|
|
||||||
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 +409,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"
|
||||||
|
|
||||||
@@ -63,11 +63,13 @@ 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 is not in the list
|
||||||
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());
|
fatalerror("Unknown section '%s'\n", sect->name.c_str());
|
||||||
}
|
}
|
||||||
@@ -109,8 +111,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 +165,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,8 +192,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 +287,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 +303,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 +322,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());
|
||||||
|
}
|
||||||
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 +343,39 @@ 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)
|
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1) {
|
||||||
fatalerror(
|
fatalerror(
|
||||||
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
||||||
". Please report this to the developers!\n",
|
". Please report this to the developers!\n",
|
||||||
it[1]->ID,
|
it[1]->ID,
|
||||||
node.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)
|
if (verbose) {
|
||||||
printf("Output filename %s\n", objectFileName.c_str());
|
printf("Output filename %s\n", objectFileName.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dumpString(std::string const &escape, FILE *file) {
|
static void dumpString(std::string const &escape, FILE *file) {
|
||||||
@@ -397,8 +411,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 +433,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 +455,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 +484,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 +497,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 +527,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());
|
||||||
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
static char const *dumpHeadings[NB_STATE_FEATURES] = {
|
static char const *dumpHeadings[NB_STATE_FEATURES] = {
|
||||||
@@ -530,7 +550,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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
679
src/asm/parser.y
679
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||||
@@ -77,9 +79,9 @@ void Expression::makeSymbol(std::string const &symName) {
|
|||||||
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!
|
||||||
@@ -120,8 +122,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 +210,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 +228,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 +252,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 +280,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)
|
||||||
@@ -418,38 +418,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 +464,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,17 +481,20 @@ 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;
|
||||||
@@ -560,9 +571,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -595,8 +607,9 @@ void Expression::makeCheckRST() {
|
|||||||
|
|
||||||
// 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, "Expression");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
||||||
|
|||||||
@@ -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 &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);
|
||||||
@@ -731,8 +783,9 @@ 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> &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()];
|
Expression &expr = exprs[i % exprs.size()];
|
||||||
@@ -748,8 +801,9 @@ 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 &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);
|
||||||
@@ -761,8 +815,9 @@ 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 &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);
|
||||||
@@ -774,8 +829,9 @@ 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 &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 +842,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 +867,19 @@ 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)
|
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));
|
||||||
|
}
|
||||||
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,22 @@ 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)
|
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));
|
||||||
|
}
|
||||||
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 +973,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 +1023,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 +1050,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,14 +309,16 @@ 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());
|
||||||
}
|
}
|
||||||
@@ -310,13 +326,15 @@ uint32_t Symbol::getConstantValue() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t sym_GetConstantValue(std::string const &symName) {
|
uint32_t sym_GetConstantValue(std::string const &symName) {
|
||||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym)
|
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym) {
|
||||||
return sym->getConstantValue();
|
return sym->getConstantValue();
|
||||||
|
}
|
||||||
|
|
||||||
if (sym_IsPurgedScoped(symName))
|
if (sym_IsPurgedScoped(symName)) {
|
||||||
error("'%s' not defined; it was purged\n", symName.c_str());
|
error("'%s' not defined; it was purged\n", symName.c_str());
|
||||||
else
|
} else {
|
||||||
error("'%s' not defined\n", symName.c_str());
|
error("'%s' not defined\n", symName.c_str());
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,8 +379,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 +392,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 +414,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 +426,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 +484,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 +502,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;
|
||||||
}
|
}
|
||||||
@@ -516,7 +541,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,19 +549,21 @@ 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) {
|
||||||
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 {
|
||||||
id = anonLabelID + ofs;
|
id = anonLabelID + ofs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string anon("!");
|
std::string anon("!");
|
||||||
@@ -553,16 +580,18 @@ void sym_Export(std::string const &symName) {
|
|||||||
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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
319
src/fix/main.cpp
319
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:Op: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'},
|
||||||
@@ -57,7 +56,7 @@ static option const longopts[] = {
|
|||||||
|
|
||||||
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"
|
||||||
@@ -76,15 +75,17 @@ static void printUsage() {
|
|||||||
|
|
||||||
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 +185,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 +224,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 +238,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 +254,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 +331,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 +390,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 +441,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 +472,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 +484,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 +516,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 +534,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 +548,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);
|
||||||
}
|
}
|
||||||
@@ -740,12 +771,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 +811,13 @@ 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
|
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
// 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 +838,30 @@ 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
|
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
||||||
return -1;
|
return -1;
|
||||||
// EOF reached
|
}
|
||||||
if (ret == 0)
|
// If anything was written, accumulate it, and continue
|
||||||
return total;
|
|
||||||
// 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 +879,13 @@ static void overwriteBytes(
|
|||||||
memcpy(&rom0[startAddr], fixed, size);
|
memcpy(&rom0[startAddr], fixed, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @param input File descriptor to be used for reading
|
|
||||||
* @param output File descriptor to be used for writing, may be equal to `input`
|
|
||||||
* @param name The file's name, to be displayed for error output
|
|
||||||
* @param fileSize The file's size if known, 0 if not.
|
|
||||||
*/
|
|
||||||
static void processFile(int input, int output, char const *name, off_t fileSize) {
|
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
|
// Both of these should be true for seekable files, and neither otherwise
|
||||||
if (input == output)
|
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));
|
||||||
@@ -890,25 +907,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 +933,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 +960,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 +1037,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 +1072,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 +1085,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 +1095,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");
|
||||||
@@ -1105,8 +1137,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
}
|
}
|
||||||
// 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);
|
||||||
|
|
||||||
@@ -1209,7 +1242,7 @@ static bool processFilename(char const *name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +1250,7 @@ static bool processFilename(char const *name) {
|
|||||||
nbErrors,
|
nbErrors,
|
||||||
nbErrors == 1 ? "" : "s"
|
nbErrors == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return nbErrors;
|
return nbErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1266,16 +1300,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 +1321,10 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
printUsage();
|
||||||
|
exit(0);
|
||||||
|
|
||||||
case 'i':
|
case 'i':
|
||||||
gameID = musl_optarg;
|
gameID = musl_optarg;
|
||||||
len = strlen(gameID);
|
len = strlen(gameID);
|
||||||
@@ -1393,21 +1432,23 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
@@ -1432,12 +1473,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 +1530,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) {
|
||||||
|
|||||||
114
src/gfx/main.cpp
114
src/gfx/main.cpp
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
@@ -41,7 +41,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 +72,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,8 +95,9 @@ 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();
|
||||||
}
|
}
|
||||||
@@ -108,18 +113,15 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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: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'},
|
||||||
@@ -127,6 +129,7 @@ static option const longopts[] = {
|
|||||||
{"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'},
|
||||||
@@ -155,7 +158,7 @@ static option const longopts[] = {
|
|||||||
|
|
||||||
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"
|
||||||
@@ -172,10 +175,8 @@ static void printUsage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// 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 +199,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 +270,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)) {
|
||||||
@@ -346,13 +342,10 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Parses an arg vector, modifying `options` and `localOptions` as options are read.
|
||||||
* 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
|
||||||
@@ -363,8 +356,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
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':
|
case 'b':
|
||||||
@@ -430,9 +424,13 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
options.bitDepth = 2;
|
options.bitDepth = 2;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'h':
|
||||||
|
printUsage();
|
||||||
|
exit(0);
|
||||||
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 +528,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 +538,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 +548,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,8 +576,9 @@ 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':
|
||||||
@@ -774,19 +776,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", [] {
|
||||||
@@ -883,9 +891,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;
|
||||||
@@ -127,10 +121,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 =
|
||||||
@@ -192,9 +184,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 +208,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 +232,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 +240,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();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "gfx/pal_sorting.hpp"
|
#include "gfx/pal_sorting.hpp"
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ void sortIndexed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "gfx/pal_spec.hpp"
|
#include "gfx/pal_spec.hpp"
|
||||||
|
|
||||||
@@ -171,20 +171,6 @@ void parseInlinePalSpec(char const * const rawArg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Tries to read some magic bytes from the provided `file`.
|
|
||||||
* Returns whether the magic was correctly read.
|
|
||||||
*/
|
|
||||||
template<size_t n>
|
|
||||||
[[gnu::warn_unused_result]] // Ignoring failure to match is a bad idea.
|
|
||||||
static bool
|
|
||||||
readMagic(std::filebuf &file, char const *magic) {
|
|
||||||
assume(strlen(magic) == n);
|
|
||||||
|
|
||||||
char magicBuf[n];
|
|
||||||
return file.sgetn(magicBuf, n) == n && memcmp(magicBuf, magic, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename U>
|
template<typename T, typename U>
|
||||||
static T readBE(U const *bytes) {
|
static T readBE(U const *bytes) {
|
||||||
T val = 0;
|
T val = 0;
|
||||||
@@ -203,14 +189,10 @@ static T readLE(U const *bytes) {
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Appends the first line read from `file` to the end of the provided `buffer`.
|
||||||
* **Appends** the first line read from `file` to the end of the provided `buffer`.
|
// Returns true if a line was read.
|
||||||
*
|
[[gnu::warn_unused_result]]
|
||||||
* @return true if a line was read.
|
static bool readLine(std::filebuf &file, std::string &buffer) {
|
||||||
*/
|
|
||||||
[[gnu::warn_unused_result]] // Ignoring EOF is a bad idea.
|
|
||||||
static bool
|
|
||||||
readLine(std::filebuf &file, std::string &buffer) {
|
|
||||||
assume(buffer.empty());
|
assume(buffer.empty());
|
||||||
// TODO: maybe this can be optimized to bulk reads?
|
// TODO: maybe this can be optimized to bulk reads?
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -238,9 +220,7 @@ static bool
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
/*
|
// Parses the initial part of a string_view, advancing the "read index" as it does
|
||||||
* Parses the initial part of a string_view, advancing the "read index" as it does
|
|
||||||
*/
|
|
||||||
template<typename U> // Should be uint*_t
|
template<typename U> // Should be uint*_t
|
||||||
static std::optional<U> parseDec(std::string const &str, std::string::size_type &n) {
|
static std::optional<U> parseDec(std::string const &str, std::string::size_type &n) {
|
||||||
uintmax_t value = 0;
|
uintmax_t value = 0;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "gfx/process.hpp"
|
#include "gfx/process.hpp"
|
||||||
|
|
||||||
@@ -26,18 +26,16 @@
|
|||||||
#include "gfx/proto_palette.hpp"
|
#include "gfx/proto_palette.hpp"
|
||||||
|
|
||||||
class ImagePalette {
|
class ImagePalette {
|
||||||
// Use as many slots as there are CGB colors (plus transparency)
|
std::array<std::optional<Rgba>, NB_COLOR_SLOTS> _colors;
|
||||||
std::array<std::optional<Rgba>, 0x8001> _colors;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ImagePalette() = default;
|
ImagePalette() = default;
|
||||||
|
|
||||||
/*
|
// Registers a color in the palette.
|
||||||
* Registers a color in the palette.
|
// If the newly inserted color "conflicts" with another one (different color, but same CGB
|
||||||
* If the newly inserted color "conflicts" with another one (different color, but same CGB
|
// color), then the other color is returned. Otherwise, `nullptr` is returned.
|
||||||
* color), then the other color is returned. Otherwise, `nullptr` is returned.
|
[[nodiscard]]
|
||||||
*/
|
Rgba const *registerColor(Rgba const &rgba) {
|
||||||
[[nodiscard]] Rgba const *registerColor(Rgba const &rgba) {
|
|
||||||
decltype(_colors)::value_type &slot = _colors[rgba.cgbColor()];
|
decltype(_colors)::value_type &slot = _colors[rgba.cgbColor()];
|
||||||
|
|
||||||
if (rgba.cgbColor() == Rgba::transparent) {
|
if (rgba.cgbColor() == Rgba::transparent) {
|
||||||
@@ -80,7 +78,8 @@ class Png {
|
|||||||
int nbTransparentEntries;
|
int nbTransparentEntries;
|
||||||
png_bytep transparencyPal = nullptr;
|
png_bytep transparencyPal = nullptr;
|
||||||
|
|
||||||
[[noreturn]] static void handleError(png_structp png, char const *msg) {
|
[[noreturn]]
|
||||||
|
static void handleError(png_structp png, char const *msg) {
|
||||||
Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
|
Png *self = reinterpret_cast<Png *>(png_get_error_ptr(png));
|
||||||
|
|
||||||
fatal("Error reading input image (\"%s\"): %s", self->c_str(), msg);
|
fatal("Error reading input image (\"%s\"): %s", self->c_str(), msg);
|
||||||
@@ -166,15 +165,13 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Reads a PNG and notes all of its colors
|
||||||
* Reads a PNG and notes all of its colors
|
//
|
||||||
*
|
// This code is more complicated than strictly necessary, but that's because of the API
|
||||||
* This code is more complicated than strictly necessary, but that's because of the API
|
// being used: the "high-level" interface doesn't provide all the transformations we need,
|
||||||
* being used: the "high-level" interface doesn't provide all the transformations we need,
|
// so we use the "lower-level" one instead.
|
||||||
* so we use the "lower-level" one instead.
|
// We also use that occasion to only read the PNG one line at a time, since we store all of
|
||||||
* We also use that occasion to only read the PNG one line at a time, since we store all of
|
// the pixel data in `pixels`, which saves on memory allocations.
|
||||||
* the pixel data in `pixels`, which saves on memory allocations.
|
|
||||||
*/
|
|
||||||
explicit Png(std::string const &filePath) : path(filePath), colors() {
|
explicit Png(std::string const &filePath) : path(filePath), colors() {
|
||||||
if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) {
|
if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) {
|
||||||
fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno));
|
fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno));
|
||||||
@@ -491,9 +488,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class RawTiles {
|
class RawTiles {
|
||||||
/*
|
// A tile which only contains indices into the image's global palette
|
||||||
* A tile which only contains indices into the image's global palette
|
|
||||||
*/
|
|
||||||
class RawTile {
|
class RawTile {
|
||||||
std::array<std::array<size_t, 8>, 8> _pixelIndices{};
|
std::array<std::array<size_t, 8>, 8> _pixelIndices{};
|
||||||
|
|
||||||
@@ -506,9 +501,7 @@ private:
|
|||||||
std::vector<RawTile> _tiles;
|
std::vector<RawTile> _tiles;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/*
|
// Creates a new raw tile, and returns a reference to it so it can be filled in
|
||||||
* Creates a new raw tile, and returns a reference to it so it can be filled in
|
|
||||||
*/
|
|
||||||
RawTile &newTile() {
|
RawTile &newTile() {
|
||||||
_tiles.emplace_back();
|
_tiles.emplace_back();
|
||||||
return _tiles.back();
|
return _tiles.back();
|
||||||
@@ -516,11 +509,9 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct AttrmapEntry {
|
struct AttrmapEntry {
|
||||||
/*
|
// This field can either be a proto-palette ID, or `transparent` to indicate that the
|
||||||
* This field can either be a proto-palette ID, or `transparent` to indicate that the
|
// corresponding tile is fully transparent. If you are looking to get the palette ID for this
|
||||||
* corresponding tile is fully transparent. If you are looking to get the palette ID for this
|
// attrmap entry while correctly handling the above, use `getPalID`.
|
||||||
* attrmap entry while correctly handling the above, use `getPalID`.
|
|
||||||
*/
|
|
||||||
size_t protoPaletteID; // Only this field is used when outputting "unoptimized" data
|
size_t protoPaletteID; // Only this field is used when outputting "unoptimized" data
|
||||||
uint8_t tileID; // This is the ID as it will be output to the tilemap
|
uint8_t tileID; // This is the ID as it will be output to the tilemap
|
||||||
bool bank;
|
bool bank;
|
||||||
@@ -624,7 +615,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
|||||||
for (uint16_t cgbColor : list) {
|
for (uint16_t cgbColor : list) {
|
||||||
ptr += snprintf(ptr, sizeof(", $XXXX"), ", $%04x", cgbColor);
|
ptr += snprintf(ptr, sizeof(", $XXXX"), ", $%04x", cgbColor);
|
||||||
}
|
}
|
||||||
return &buf[QUOTEDSTRLEN(", ")];
|
return &buf[literal_strlen(", ")];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Iterate through proto-palettes, and try mapping them to the specified palettes
|
// Iterate through proto-palettes, and try mapping them to the specified palettes
|
||||||
@@ -713,8 +704,9 @@ static void hashBitplanes(uint16_t bitplanes, uint16_t &hash) {
|
|||||||
|
|
||||||
class TileData {
|
class TileData {
|
||||||
// Importantly, `TileData` is **always** 2bpp.
|
// Importantly, `TileData` is **always** 2bpp.
|
||||||
// If the active bit depth is 1bpp, all tiles are processed as 2bpp nonetheless, but emitted as 1bpp.
|
// If the active bit depth is 1bpp, all tiles are processed as 2bpp nonetheless, but emitted as
|
||||||
// This massively simplifies internal processing, since bit depth is always identical outside of I/O / serialization boundaries.
|
// 1bpp. This massively simplifies internal processing, since bit depth is always identical
|
||||||
|
// outside of I/O / serialization boundaries.
|
||||||
std::array<uint8_t, 16> _data;
|
std::array<uint8_t, 16> _data;
|
||||||
// The hash is a bit lax: it's the XOR of all lines, and every other nibble is identical
|
// The hash is a bit lax: it's the XOR of all lines, and every other nibble is identical
|
||||||
// if horizontal mirroring is in effect. It should still be a reasonable tie-breaker in
|
// if horizontal mirroring is in effect. It should still be a reasonable tie-breaker in
|
||||||
@@ -918,9 +910,7 @@ struct UniqueTiles {
|
|||||||
UniqueTiles(UniqueTiles const &) = delete;
|
UniqueTiles(UniqueTiles const &) = delete;
|
||||||
UniqueTiles(UniqueTiles &&) = default;
|
UniqueTiles(UniqueTiles &&) = default;
|
||||||
|
|
||||||
/*
|
// Adds a tile to the collection, and returns its ID
|
||||||
* Adds a tile to the collection, and returns its ID
|
|
||||||
*/
|
|
||||||
std::tuple<uint16_t, TileData::MatchType> addTile(TileData newTile) {
|
std::tuple<uint16_t, TileData::MatchType> addTile(TileData newTile) {
|
||||||
auto [tileData, inserted] = tileset.insert(newTile);
|
auto [tileData, inserted] = tileset.insert(newTile);
|
||||||
|
|
||||||
@@ -942,12 +932,10 @@ struct UniqueTiles {
|
|||||||
auto end() const { return tiles.end(); }
|
auto end() const { return tiles.end(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
// Generate tile data while deduplicating unique tiles (via mirroring if enabled)
|
||||||
* Generate tile data while deduplicating unique tiles (via mirroring if enabled)
|
// Additionally, while we have the info handy, convert from the 16-bit "global" tile IDs to
|
||||||
* Additionally, while we have the info handy, convert from the 16-bit "global" tile IDs to
|
// 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
|
||||||
* 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
|
// twice)
|
||||||
* twice)
|
|
||||||
*/
|
|
||||||
static UniqueTiles dedupTiles(
|
static UniqueTiles dedupTiles(
|
||||||
Png const &png,
|
Png const &png,
|
||||||
DefaultInitVec<AttrmapEntry> &attrmap,
|
DefaultInitVec<AttrmapEntry> &attrmap,
|
||||||
@@ -999,10 +987,11 @@ static UniqueTiles dedupTiles(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool inputWithoutOutput = !options.inputTileset.empty() && options.output.empty();
|
||||||
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
|
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
|
||||||
auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
|
auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
|
||||||
|
|
||||||
if (matchType == TileData::NOPE && options.output.empty()) {
|
if (inputWithoutOutput && matchType == TileData::NOPE) {
|
||||||
error(
|
error(
|
||||||
"Tile at (%" PRIu32 ", %" PRIu32
|
"Tile at (%" PRIu32 ", %" PRIu32
|
||||||
") is not within the input tileset, and `-o` was not given!",
|
") is not within the input tileset, and `-o` was not given!",
|
||||||
@@ -1163,18 +1152,17 @@ void process() {
|
|||||||
protoPalettes[n] = protoPalette; // Override them
|
protoPalettes[n] = protoPalette; // Override them
|
||||||
// Remove any other proto-palettes that we encompass
|
// Remove any other proto-palettes that we encompass
|
||||||
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
|
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
|
||||||
/*
|
//
|
||||||
* The following code does its job, except that references to the removed
|
// The following code does its job, except that references to the removed
|
||||||
* proto-palettes are not updated, causing issues.
|
// proto-palettes are not updated, causing issues.
|
||||||
* TODO: overlap might not be detrimental to the packing algorithm.
|
// TODO: overlap might not be detrimental to the packing algorithm.
|
||||||
* Investigation is necessary, especially if pathological cases are found.
|
// Investigation is necessary, especially if pathological cases are found.
|
||||||
*
|
//
|
||||||
* for (size_t i = protoPalettes.size(); --i != n;) {
|
// for (size_t i = protoPalettes.size(); --i != n;) {
|
||||||
* if (protoPalette.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
|
// if (protoPalette.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
|
||||||
* protoPalettes.erase(protoPalettes.begin() + i);
|
// protoPalettes.erase(protoPalettes.begin() + i);
|
||||||
* }
|
// }
|
||||||
* }
|
// }
|
||||||
*/
|
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
|
|
||||||
case ProtoPalette::THEY_BIGGER:
|
case ProtoPalette::THEY_BIGGER:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "gfx/proto_palette.hpp"
|
#include "gfx/proto_palette.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "gfx/reverse.hpp"
|
#include "gfx/reverse.hpp"
|
||||||
|
|
||||||
@@ -49,7 +49,8 @@ static DefaultInitVec<uint8_t> readInto(std::string const &path) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] static void pngError(png_structp png, char const *msg) {
|
[[noreturn]]
|
||||||
|
static void pngError(png_structp png, char const *msg) {
|
||||||
fatal(
|
fatal(
|
||||||
"Error writing reversed image (\"%s\"): %s",
|
"Error writing reversed image (\"%s\"): %s",
|
||||||
static_cast<char const *>(png_get_error_ptr(png)),
|
static_cast<char const *>(png_get_error_ptr(png)),
|
||||||
@@ -166,8 +167,9 @@ void reverse() {
|
|||||||
// This avoids redundancy with `-r 1` which results in a vertical column.
|
// This avoids redundancy with `-r 1` which results in a vertical column.
|
||||||
width = static_cast<size_t>(ceil(sqrt(mapSize)));
|
width = static_cast<size_t>(ceil(sqrt(mapSize)));
|
||||||
for (; width < mapSize; ++width) {
|
for (; width < mapSize; ++width) {
|
||||||
if (mapSize % width == 0)
|
if (mapSize % width == 0) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
options.verbosePrint(Options::VERB_INTERM, "Picked reversing width of %zu tiles\n", width);
|
options.verbosePrint(Options::VERB_INTERM, "Picked reversing width of %zu tiles\n", width);
|
||||||
}
|
}
|
||||||
@@ -467,24 +469,7 @@ void reverse() {
|
|||||||
assume(palID < palettes.size()); // Should be ensured on data read
|
assume(palID < palettes.size()); // Should be ensured on data read
|
||||||
|
|
||||||
// We do not have data for tiles trimmed with `-x`, so assume they are "blank"
|
// We do not have data for tiles trimmed with `-x`, so assume they are "blank"
|
||||||
static std::array<uint8_t, 16> const trimmedTile{
|
static std::array<uint8_t, 16> const trimmedTile{0x00};
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
};
|
|
||||||
uint8_t const *tileData =
|
uint8_t const *tileData =
|
||||||
tileOfs >= nbTiles ? trimmedTile.data() : &tiles[tileOfs * tileSize];
|
tileOfs >= nbTiles ? trimmedTile.data() : &tiles[tileOfs * tileSize];
|
||||||
auto const &palette = palettes[palID];
|
auto const &palette = palettes[palID];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
@@ -10,29 +10,29 @@
|
|||||||
|
|
||||||
#include "gfx/main.hpp" // options
|
#include "gfx/main.hpp" // options
|
||||||
|
|
||||||
/*
|
// Based on inverting the "Modern - Accurate" formula used by SameBoy
|
||||||
* Based on inverting the "Modern - Accurate" formula used by SameBoy
|
// since commit b5a611c5db46d6a0649d04d24d8d6339200f9ca1 (Dec 2020),
|
||||||
* since commit b5a611c5db46d6a0649d04d24d8d6339200f9ca1 (Dec 2020),
|
// with gaps in the scale curve filled by polynomial interpolation.
|
||||||
* with gaps in the scale curve filled by polynomial interpolation.
|
// clang-format off: vertically align columns of values
|
||||||
*/
|
|
||||||
static std::array<uint8_t, 256> reverse_curve{
|
static std::array<uint8_t, 256> reverse_curve{
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // These
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
|
||||||
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, // comments
|
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3,
|
||||||
3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, // prevent
|
3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5,
|
||||||
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, // clang-format
|
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7,
|
||||||
7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, // from
|
7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10,
|
||||||
10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, // reflowing
|
10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13,
|
||||||
13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, // these
|
13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16,
|
||||||
16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, // sixteen
|
16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19,
|
||||||
19, 19, 19, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 22, // 16-item
|
19, 19, 19, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 22,
|
||||||
22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, // lines,
|
22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24,
|
||||||
24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, // which,
|
24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26,
|
||||||
26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, // in
|
26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28,
|
||||||
28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, // my
|
28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||||
29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, // opinion,
|
29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
|
||||||
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // help
|
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
|
||||||
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // visualization!
|
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
|
||||||
};
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
uint16_t Rgba::cgbColor() const {
|
uint16_t Rgba::cgbColor() const {
|
||||||
if (isTransparent()) {
|
if (isTransparent()) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "link/assign.hpp"
|
#include "link/assign.hpp"
|
||||||
|
|
||||||
@@ -48,11 +48,7 @@ static void initFreeSpace() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Assigns a section to a given memory location
|
||||||
* Assigns a section to a given memory location
|
|
||||||
* @param section The section to assign
|
|
||||||
* @param location The location to assign the section to
|
|
||||||
*/
|
|
||||||
static void assignSection(Section §ion, MemoryLocation const &location) {
|
static void assignSection(Section §ion, MemoryLocation const &location) {
|
||||||
// Propagate the assigned location to all UNIONs/FRAGMENTs
|
// Propagate the assigned location to all UNIONs/FRAGMENTs
|
||||||
// so `jr` patches in them will have the correct offset
|
// so `jr` patches in them will have the correct offset
|
||||||
@@ -66,37 +62,29 @@ static void assignSection(Section §ion, MemoryLocation const &location) {
|
|||||||
out_AddSection(section);
|
out_AddSection(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Checks whether a given location is suitable for placing a given section
|
||||||
* Checks whether a given location is suitable for placing a given section
|
// This checks not only that the location has enough room for the section, but
|
||||||
* This checks not only that the location has enough room for the section, but
|
// also that the constraints (alignment...) are respected.
|
||||||
* also that the constraints (alignment...) are respected.
|
|
||||||
* @param section The section to be placed
|
|
||||||
* @param freeSpace The candidate free space to place the section into
|
|
||||||
* @param location The location to attempt placing the section at
|
|
||||||
* @return True if the location is suitable, false otherwise.
|
|
||||||
*/
|
|
||||||
static bool isLocationSuitable(
|
static bool isLocationSuitable(
|
||||||
Section const §ion, FreeSpace const &freeSpace, MemoryLocation const &location
|
Section const §ion, FreeSpace const &freeSpace, MemoryLocation const &location
|
||||||
) {
|
) {
|
||||||
if (section.isAddressFixed && section.org != location.address)
|
if (section.isAddressFixed && section.org != location.address) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (section.isAlignFixed && ((location.address - section.alignOfs) & section.alignMask))
|
if (section.isAlignFixed && ((location.address - section.alignOfs) & section.alignMask)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (location.address < freeSpace.address)
|
if (location.address < freeSpace.address) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return location.address + section.size <= freeSpace.address + freeSpace.size;
|
return location.address + section.size <= freeSpace.address + freeSpace.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Returns a suitable free space index into `memory[section->type]` at which to place the given
|
||||||
* Finds a suitable location to place a section at.
|
// section, or -1 if none was found.
|
||||||
* @param section The section to be placed
|
|
||||||
* @param location A pointer to a memory location that will be filled
|
|
||||||
* @return The index into `memory[section->type]` of the free space encompassing the location,
|
|
||||||
* or -1 if none was found
|
|
||||||
*/
|
|
||||||
static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
||||||
SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type];
|
SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type];
|
||||||
|
|
||||||
@@ -108,16 +96,19 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
|||||||
if (section.isBankFixed) {
|
if (section.isBankFixed) {
|
||||||
location.bank = section.bank;
|
location.bank = section.bank;
|
||||||
} else if (scrambleROMX && section.type == SECTTYPE_ROMX) {
|
} else if (scrambleROMX && section.type == SECTTYPE_ROMX) {
|
||||||
if (curScrambleROM < 1)
|
if (curScrambleROM < 1) {
|
||||||
curScrambleROM = scrambleROMX;
|
curScrambleROM = scrambleROMX;
|
||||||
|
}
|
||||||
location.bank = curScrambleROM--;
|
location.bank = curScrambleROM--;
|
||||||
} else if (scrambleWRAMX && section.type == SECTTYPE_WRAMX) {
|
} else if (scrambleWRAMX && section.type == SECTTYPE_WRAMX) {
|
||||||
if (curScrambleWRAM < 1)
|
if (curScrambleWRAM < 1) {
|
||||||
curScrambleWRAM = scrambleWRAMX;
|
curScrambleWRAM = scrambleWRAMX;
|
||||||
|
}
|
||||||
location.bank = curScrambleWRAM--;
|
location.bank = curScrambleWRAM--;
|
||||||
} else if (scrambleSRAM && section.type == SECTTYPE_SRAM) {
|
} else if (scrambleSRAM && section.type == SECTTYPE_SRAM) {
|
||||||
if (curScrambleSRAM < 0)
|
if (curScrambleSRAM < 0) {
|
||||||
curScrambleSRAM = scrambleSRAM;
|
curScrambleSRAM = scrambleSRAM;
|
||||||
|
}
|
||||||
location.bank = curScrambleSRAM--;
|
location.bank = curScrambleSRAM--;
|
||||||
} else {
|
} else {
|
||||||
location.bank = typeInfo.firstBank;
|
location.bank = typeInfo.firstBank;
|
||||||
@@ -134,18 +125,20 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
|||||||
// Process locations in that bank
|
// Process locations in that bank
|
||||||
while (spaceIdx < bankMem.size()) {
|
while (spaceIdx < bankMem.size()) {
|
||||||
// If that location is OK, return it
|
// If that location is OK, return it
|
||||||
if (isLocationSuitable(section, bankMem[spaceIdx], location))
|
if (isLocationSuitable(section, bankMem[spaceIdx], location)) {
|
||||||
return spaceIdx;
|
return spaceIdx;
|
||||||
|
}
|
||||||
|
|
||||||
// Go to the next *possible* location
|
// Go to the next *possible* location
|
||||||
if (section.isAddressFixed) {
|
if (section.isAddressFixed) {
|
||||||
// If the address is fixed, there can be only
|
// If the address is fixed, there can be only
|
||||||
// one candidate block per bank; if we already
|
// one candidate block per bank; if we already
|
||||||
// reached it, give up.
|
// reached it, give up.
|
||||||
if (location.address < section.org)
|
if (location.address < section.org) {
|
||||||
location.address = section.org;
|
location.address = section.org;
|
||||||
else
|
} else {
|
||||||
break; // Try again in next bank
|
break; // Try again in next bank
|
||||||
|
}
|
||||||
} else if (section.isAlignFixed) {
|
} else if (section.isAlignFixed) {
|
||||||
// Move to next aligned location
|
// Move to next aligned location
|
||||||
// Move back to alignment boundary
|
// Move back to alignment boundary
|
||||||
@@ -157,15 +150,17 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
|||||||
} else {
|
} else {
|
||||||
// Any location is fine, so, next free block
|
// Any location is fine, so, next free block
|
||||||
spaceIdx++;
|
spaceIdx++;
|
||||||
if (spaceIdx < bankMem.size())
|
if (spaceIdx < bankMem.size()) {
|
||||||
location.address = bankMem[spaceIdx].address;
|
location.address = bankMem[spaceIdx].address;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If that location is past the current block's end,
|
// If that location is past the current block's end,
|
||||||
// go forwards until that is no longer the case.
|
// go forwards until that is no longer the case.
|
||||||
while (spaceIdx < bankMem.size()
|
while (spaceIdx < bankMem.size()
|
||||||
&& location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size)
|
&& location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size) {
|
||||||
spaceIdx++;
|
spaceIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
// Try again with the new location/free space combo
|
// Try again with the new location/free space combo
|
||||||
}
|
}
|
||||||
@@ -177,26 +172,30 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
|||||||
if (section.isBankFixed) {
|
if (section.isBankFixed) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (scrambleROMX && section.type == SECTTYPE_ROMX && location.bank <= scrambleROMX) {
|
} else if (scrambleROMX && section.type == SECTTYPE_ROMX && location.bank <= scrambleROMX) {
|
||||||
if (location.bank > typeInfo.firstBank)
|
if (location.bank > typeInfo.firstBank) {
|
||||||
location.bank--;
|
location.bank--;
|
||||||
else if (scrambleROMX < typeInfo.lastBank)
|
} else if (scrambleROMX < typeInfo.lastBank) {
|
||||||
location.bank = scrambleROMX + 1;
|
location.bank = scrambleROMX + 1;
|
||||||
else
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (scrambleWRAMX && section.type == SECTTYPE_WRAMX && location.bank <= scrambleWRAMX) {
|
}
|
||||||
if (location.bank > typeInfo.firstBank)
|
} else if (scrambleWRAMX && section.type == SECTTYPE_WRAMX
|
||||||
|
&& location.bank <= scrambleWRAMX) {
|
||||||
|
if (location.bank > typeInfo.firstBank) {
|
||||||
location.bank--;
|
location.bank--;
|
||||||
else if (scrambleWRAMX < typeInfo.lastBank)
|
} else if (scrambleWRAMX < typeInfo.lastBank) {
|
||||||
location.bank = scrambleWRAMX + 1;
|
location.bank = scrambleWRAMX + 1;
|
||||||
else
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
} else if (scrambleSRAM && section.type == SECTTYPE_SRAM && location.bank <= scrambleSRAM) {
|
} else if (scrambleSRAM && section.type == SECTTYPE_SRAM && location.bank <= scrambleSRAM) {
|
||||||
if (location.bank > typeInfo.firstBank)
|
if (location.bank > typeInfo.firstBank) {
|
||||||
location.bank--;
|
location.bank--;
|
||||||
else if (scrambleSRAM < typeInfo.lastBank)
|
} else if (scrambleSRAM < typeInfo.lastBank) {
|
||||||
location.bank = scrambleSRAM + 1;
|
location.bank = scrambleSRAM + 1;
|
||||||
else
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
} else if (location.bank < typeInfo.lastBank) {
|
} else if (location.bank < typeInfo.lastBank) {
|
||||||
location.bank++;
|
location.bank++;
|
||||||
} else {
|
} else {
|
||||||
@@ -205,12 +204,8 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Places a section in a suitable location, or error out if it fails to.
|
||||||
* Places a section in a suitable location, or error out if it fails to.
|
// Due to the implemented algorithm, this should be called with sections of decreasing size!
|
||||||
* @warning Due to the implemented algorithm, this should be called with
|
|
||||||
* sections of decreasing size.
|
|
||||||
* @param section The section to place
|
|
||||||
*/
|
|
||||||
static void placeSection(Section §ion) {
|
static void placeSection(Section §ion) {
|
||||||
MemoryLocation location;
|
MemoryLocation location;
|
||||||
|
|
||||||
@@ -258,9 +253,10 @@ static void placeSection(Section §ion) {
|
|||||||
} else {
|
} else {
|
||||||
// The amount of free spaces doesn't change: resize!
|
// The amount of free spaces doesn't change: resize!
|
||||||
freeSpace.size -= section.size;
|
freeSpace.size -= section.size;
|
||||||
if (noLeftSpace)
|
if (noLeftSpace) {
|
||||||
// The free space is moved *and* resized
|
// The free space is moved *and* resized
|
||||||
freeSpace.address += section.size;
|
freeSpace.address += section.size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -269,11 +265,11 @@ static void placeSection(Section §ion) {
|
|||||||
char where[64];
|
char where[64];
|
||||||
|
|
||||||
if (section.isBankFixed && nbbanks(section.type) != 1) {
|
if (section.isBankFixed && nbbanks(section.type) != 1) {
|
||||||
if (section.isAddressFixed)
|
if (section.isAddressFixed) {
|
||||||
snprintf(
|
snprintf(
|
||||||
where, sizeof(where), "at $%02" PRIx32 ":%04" PRIx16, section.bank, section.org
|
where, sizeof(where), "at $%02" PRIx32 ":%04" PRIx16, section.bank, section.org
|
||||||
);
|
);
|
||||||
else if (section.isAlignFixed)
|
} else if (section.isAlignFixed) {
|
||||||
snprintf(
|
snprintf(
|
||||||
where,
|
where,
|
||||||
sizeof(where),
|
sizeof(where),
|
||||||
@@ -281,12 +277,13 @@ static void placeSection(Section §ion) {
|
|||||||
section.bank,
|
section.bank,
|
||||||
static_cast<uint16_t>(~section.alignMask)
|
static_cast<uint16_t>(~section.alignMask)
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank);
|
snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (section.isAddressFixed)
|
if (section.isAddressFixed) {
|
||||||
snprintf(where, sizeof(where), "at address $%04" PRIx16, section.org);
|
snprintf(where, sizeof(where), "at address $%04" PRIx16, section.org);
|
||||||
else if (section.isAlignFixed)
|
} else if (section.isAlignFixed) {
|
||||||
snprintf(
|
snprintf(
|
||||||
where,
|
where,
|
||||||
sizeof(where),
|
sizeof(where),
|
||||||
@@ -294,20 +291,22 @@ static void placeSection(Section §ion) {
|
|||||||
static_cast<uint16_t>(~section.alignMask),
|
static_cast<uint16_t>(~section.alignMask),
|
||||||
section.alignOfs
|
section.alignOfs
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
strcpy(where, "anywhere");
|
strcpy(where, "anywhere");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a section failed to go to several places, nothing we can report
|
// If a section failed to go to several places, nothing we can report
|
||||||
if (!section.isBankFixed || !section.isAddressFixed)
|
if (!section.isBankFixed || !section.isAddressFixed) {
|
||||||
errx(
|
errx(
|
||||||
"Unable to place \"%s\" (%s section) %s",
|
"Unable to place \"%s\" (%s section) %s",
|
||||||
section.name.c_str(),
|
section.name.c_str(),
|
||||||
sectionTypeInfo[section.type].name.c_str(),
|
sectionTypeInfo[section.type].name.c_str(),
|
||||||
where
|
where
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// If the section just can't fit the bank, report that
|
// If the section just can't fit the bank, report that
|
||||||
else if (section.org + section.size > endaddr(section.type) + 1)
|
else if (section.org + section.size > endaddr(section.type) + 1) {
|
||||||
errx(
|
errx(
|
||||||
"Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > "
|
"Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > "
|
||||||
"$%04x)",
|
"$%04x)",
|
||||||
@@ -317,8 +316,9 @@ static void placeSection(Section §ion) {
|
|||||||
section.org + section.size,
|
section.org + section.size,
|
||||||
endaddr(section.type) + 1
|
endaddr(section.type) + 1
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// Otherwise there is overlap with another section
|
// Otherwise there is overlap with another section
|
||||||
else
|
else {
|
||||||
errx(
|
errx(
|
||||||
"Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
|
"Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
|
||||||
section.name.c_str(),
|
section.name.c_str(),
|
||||||
@@ -326,35 +326,39 @@ static void placeSection(Section §ion) {
|
|||||||
where,
|
where,
|
||||||
out_OverlappingSection(section)->name.c_str()
|
out_OverlappingSection(section)->name.c_str()
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define BANK_CONSTRAINED (1 << 2)
|
// clang-format off: vertically align values
|
||||||
#define ORG_CONSTRAINED (1 << 1)
|
static constexpr uint8_t BANK_CONSTRAINED = 1 << 2;
|
||||||
#define ALIGN_CONSTRAINED (1 << 0)
|
static constexpr uint8_t ORG_CONSTRAINED = 1 << 1;
|
||||||
|
static constexpr uint8_t ALIGN_CONSTRAINED = 1 << 0;
|
||||||
|
// clang-format on
|
||||||
static std::deque<Section *> unassignedSections[1 << 3];
|
static std::deque<Section *> unassignedSections[1 << 3];
|
||||||
|
|
||||||
/*
|
// Categorize a section depending on how constrained it is.
|
||||||
* Categorize a section depending on how constrained it is
|
// This is so the most-constrained sections are placed first.
|
||||||
* This is so the most-constrained sections are placed first
|
|
||||||
* @param section The section to categorize
|
|
||||||
*/
|
|
||||||
static void categorizeSection(Section §ion) {
|
static void categorizeSection(Section §ion) {
|
||||||
uint8_t constraints = 0;
|
uint8_t constraints = 0;
|
||||||
|
|
||||||
if (section.isBankFixed)
|
if (section.isBankFixed) {
|
||||||
constraints |= BANK_CONSTRAINED;
|
constraints |= BANK_CONSTRAINED;
|
||||||
if (section.isAddressFixed)
|
}
|
||||||
|
if (section.isAddressFixed) {
|
||||||
constraints |= ORG_CONSTRAINED;
|
constraints |= ORG_CONSTRAINED;
|
||||||
|
}
|
||||||
// Can't have both!
|
// Can't have both!
|
||||||
else if (section.isAlignFixed)
|
else if (section.isAlignFixed) {
|
||||||
constraints |= ALIGN_CONSTRAINED;
|
constraints |= ALIGN_CONSTRAINED;
|
||||||
|
}
|
||||||
|
|
||||||
std::deque<Section *> §ions = unassignedSections[constraints];
|
std::deque<Section *> §ions = unassignedSections[constraints];
|
||||||
auto pos = sections.begin();
|
auto pos = sections.begin();
|
||||||
|
|
||||||
// Insert section while keeping the list sorted by decreasing size
|
// Insert section while keeping the list sorted by decreasing size
|
||||||
while (pos != sections.end() && (*pos)->size > section.size)
|
while (pos != sections.end() && (*pos)->size > section.size) {
|
||||||
pos++;
|
pos++;
|
||||||
|
}
|
||||||
sections.insert(pos, §ion);
|
sections.insert(pos, §ion);
|
||||||
|
|
||||||
nbSectionsToAssign++;
|
nbSectionsToAssign++;
|
||||||
@@ -375,12 +379,14 @@ void assign_AssignSections() {
|
|||||||
|
|
||||||
// Specially process fully-constrained sections because of overlaying
|
// Specially process fully-constrained sections because of overlaying
|
||||||
verbosePrint("Assigning bank+org-constrained...\n");
|
verbosePrint("Assigning bank+org-constrained...\n");
|
||||||
for (Section *section : unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED])
|
for (Section *section : unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED]) {
|
||||||
placeSection(*section);
|
placeSection(*section);
|
||||||
|
}
|
||||||
|
|
||||||
// If all sections were fully constrained, we have nothing left to do
|
// If all sections were fully constrained, we have nothing left to do
|
||||||
if (!nbSectionsToAssign)
|
if (!nbSectionsToAssign) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Overlaying requires only fully-constrained sections
|
// Overlaying requires only fully-constrained sections
|
||||||
verbosePrint("Assigning other sections...\n");
|
verbosePrint("Assigning other sections...\n");
|
||||||
@@ -392,14 +398,16 @@ void assign_AssignSections() {
|
|||||||
for (Section *section : unassignedSections[constraints]) {
|
for (Section *section : unassignedSections[constraints]) {
|
||||||
fprintf(stderr, "%c \"%s\"", nbSections == 0 ? ';' : ',', section->name.c_str());
|
fprintf(stderr, "%c \"%s\"", nbSections == 0 ? ';' : ',', section->name.c_str());
|
||||||
nbSections++;
|
nbSections++;
|
||||||
if (nbSections == 10)
|
if (nbSections == 10) {
|
||||||
goto max_out; // Can't `break` out of a nested loop
|
goto max_out; // Can't `break` out of a nested loop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
max_out:
|
max_out:
|
||||||
if (nbSectionsToAssign != nbSections)
|
if (nbSectionsToAssign != nbSections) {
|
||||||
fprintf(stderr, " and %" PRIu64 " more", nbSectionsToAssign - nbSections);
|
fprintf(stderr, " and %" PRIu64 " more", nbSectionsToAssign - nbSections);
|
||||||
|
}
|
||||||
fprintf(stderr, " %sn't\n", nbSectionsToAssign == 1 ? "is" : "are");
|
fprintf(stderr, " %sn't\n", nbSectionsToAssign == 1 ? "is" : "are");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@@ -407,11 +415,13 @@ max_out:
|
|||||||
// Assign all remaining sections by decreasing constraint order
|
// Assign all remaining sections by decreasing constraint order
|
||||||
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0;
|
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0;
|
||||||
constraints--) {
|
constraints--) {
|
||||||
for (Section *section : unassignedSections[constraints])
|
for (Section *section : unassignedSections[constraints]) {
|
||||||
placeSection(*section);
|
placeSection(*section);
|
||||||
|
}
|
||||||
|
|
||||||
if (!nbSectionsToAssign)
|
if (!nbSectionsToAssign) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable_();
|
unreachable_();
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -52,8 +52,9 @@ std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
|||||||
std::string const &lastName = parent->dump(lineNo);
|
std::string const &lastName = parent->dump(lineNo);
|
||||||
fputs(" -> ", stderr);
|
fputs(" -> ", stderr);
|
||||||
fputs(lastName.c_str(), stderr);
|
fputs(lastName.c_str(), stderr);
|
||||||
for (uint32_t iter : iters())
|
for (uint32_t iter : iters()) {
|
||||||
fprintf(stderr, "::REPT~%" PRIu32, iter);
|
fprintf(stderr, "::REPT~%" PRIu32, iter);
|
||||||
|
}
|
||||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||||
return lastName;
|
return lastName;
|
||||||
} else {
|
} else {
|
||||||
@@ -96,8 +97,9 @@ void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
|
|||||||
printDiag(fmt, args, "error", where, lineNo);
|
printDiag(fmt, args, "error", where, lineNo);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
if (nbErrors != UINT32_MAX)
|
if (nbErrors != UINT32_MAX) {
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void argErr(char flag, char const *fmt, ...) {
|
void argErr(char flag, char const *fmt, ...) {
|
||||||
@@ -109,19 +111,22 @@ void argErr(char flag, char const *fmt, ...) {
|
|||||||
va_end(args);
|
va_end(args);
|
||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
|
|
||||||
if (nbErrors != UINT32_MAX)
|
if (nbErrors != UINT32_MAX) {
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
|
[[noreturn]]
|
||||||
|
void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
printDiag(fmt, args, "FATAL", where, lineNo);
|
printDiag(fmt, args, "FATAL", where, lineNo);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
if (nbErrors != UINT32_MAX)
|
if (nbErrors != UINT32_MAX) {
|
||||||
nbErrors++;
|
nbErrors++;
|
||||||
|
}
|
||||||
|
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s"
|
stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s"
|
||||||
@@ -130,20 +135,18 @@ void argErr(char flag, char const *fmt, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "dl:m:Mn:O:o:p:S:tVvWwx";
|
static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvWwx";
|
||||||
|
|
||||||
/*
|
// 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[] = {
|
||||||
{"dmg", no_argument, nullptr, 'd'},
|
{"dmg", no_argument, nullptr, 'd'},
|
||||||
|
{"help", no_argument, nullptr, 'h'},
|
||||||
{"linkerscript", required_argument, nullptr, 'l'},
|
{"linkerscript", required_argument, nullptr, 'l'},
|
||||||
{"map", required_argument, nullptr, 'm'},
|
{"map", required_argument, nullptr, 'm'},
|
||||||
{"no-sym-in-map", no_argument, nullptr, 'M'},
|
{"no-sym-in-map", no_argument, nullptr, 'M'},
|
||||||
@@ -162,7 +165,7 @@ static option const longopts[] = {
|
|||||||
|
|
||||||
static void printUsage() {
|
static void printUsage() {
|
||||||
fputs(
|
fputs(
|
||||||
"Usage: rgblink [-dMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
|
"Usage: rgblink [-dhMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
|
||||||
" [-O overlay_file] [-o out_file] [-p pad_value]\n"
|
" [-O overlay_file] [-o out_file] [-p pad_value]\n"
|
||||||
" [-S spec] <file> ...\n"
|
" [-S spec] <file> ...\n"
|
||||||
"Useful options:\n"
|
"Useful options:\n"
|
||||||
@@ -217,19 +220,19 @@ static void parseScrambleSpec(char const *spec) {
|
|||||||
if (regionNameLen == 0) {
|
if (regionNameLen == 0) {
|
||||||
argErr('S', "Missing region name");
|
argErr('S', "Missing region name");
|
||||||
|
|
||||||
if (*spec == '\0')
|
if (*spec == '\0') {
|
||||||
break;
|
break;
|
||||||
if (*spec == '=') // Skip the limit, too
|
}
|
||||||
|
if (*spec == '=') { // Skip the limit, too
|
||||||
spec = strchr(&spec[1], ','); // Skip to next comma, if any
|
spec = strchr(&spec[1], ','); // Skip to next comma, if any
|
||||||
|
}
|
||||||
goto next;
|
goto next;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the next non-blank char after the region name's end
|
// Find the next non-blank char after the region name's end
|
||||||
spec += regionNameLen + strspn(&spec[regionNameLen], " \t");
|
spec += regionNameLen + strspn(&spec[regionNameLen], " \t");
|
||||||
if (*spec != '\0' && *spec != ',' && *spec != '=') {
|
if (*spec != '\0' && *spec != ',' && *spec != '=') {
|
||||||
argErr(
|
argErr('S', "Unexpected '%c' after region name \"%.*s\"", regionNameFmtLen, regionName);
|
||||||
'S', "Unexpected '%c' after region name \"%.*s\"", regionNameFmtLen, regionName
|
|
||||||
);
|
|
||||||
// Skip to next ',' or '=' (or NUL) and keep parsing
|
// Skip to next ',' or '=' (or NUL) and keep parsing
|
||||||
spec += 1 + strcspn(&spec[1], ",=");
|
spec += 1 + strcspn(&spec[1], ",=");
|
||||||
}
|
}
|
||||||
@@ -245,8 +248,9 @@ static void parseScrambleSpec(char const *spec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (region == SCRAMBLE_UNK)
|
if (region == SCRAMBLE_UNK) {
|
||||||
argErr('S', "Unknown region \"%.*s\"", regionNameFmtLen, regionName);
|
argErr('S', "Unknown region \"%.*s\"", regionNameFmtLen, regionName);
|
||||||
|
}
|
||||||
|
|
||||||
if (*spec == '=') {
|
if (*spec == '=') {
|
||||||
spec++; // `strtoul` will skip the whitespace on its own
|
spec++; // `strtoul` will skip the whitespace on its own
|
||||||
@@ -304,15 +308,18 @@ static void parseScrambleSpec(char const *spec) {
|
|||||||
next: // Can't `continue` a `for` loop with this nontrivial iteration logic
|
next: // Can't `continue` a `for` loop with this nontrivial iteration logic
|
||||||
if (spec) {
|
if (spec) {
|
||||||
assume(*spec == ',' || *spec == '\0');
|
assume(*spec == ',' || *spec == '\0');
|
||||||
if (*spec == ',')
|
if (*spec == ',') {
|
||||||
spec += 1 + strspn(&spec[1], " \t");
|
spec += 1 + strspn(&spec[1], " \t");
|
||||||
if (*spec == '\0')
|
}
|
||||||
|
if (*spec == '\0') {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void reportErrors() {
|
[[noreturn]]
|
||||||
|
void reportErrors() {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr, "Linking failed with %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s"
|
stderr, "Linking failed with %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
@@ -327,32 +334,40 @@ int main(int argc, char *argv[]) {
|
|||||||
isDmgMode = true;
|
isDmgMode = true;
|
||||||
isWRAM0Mode = true;
|
isWRAM0Mode = true;
|
||||||
break;
|
break;
|
||||||
|
case 'h':
|
||||||
|
printUsage();
|
||||||
|
exit(0);
|
||||||
case 'l':
|
case 'l':
|
||||||
if (linkerScriptName)
|
if (linkerScriptName) {
|
||||||
warnx("Overriding linker script %s", linkerScriptName);
|
warnx("Overriding linker script %s", linkerScriptName);
|
||||||
|
}
|
||||||
linkerScriptName = musl_optarg;
|
linkerScriptName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'M':
|
case 'M':
|
||||||
noSymInMap = true;
|
noSymInMap = true;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
if (mapFileName)
|
if (mapFileName) {
|
||||||
warnx("Overriding map file %s", mapFileName);
|
warnx("Overriding map file %s", mapFileName);
|
||||||
|
}
|
||||||
mapFileName = musl_optarg;
|
mapFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
if (symFileName)
|
if (symFileName) {
|
||||||
warnx("Overriding sym file %s", symFileName);
|
warnx("Overriding sym file %s", symFileName);
|
||||||
|
}
|
||||||
symFileName = musl_optarg;
|
symFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'O':
|
case 'O':
|
||||||
if (overlayFileName)
|
if (overlayFileName) {
|
||||||
warnx("Overriding overlay file %s", overlayFileName);
|
warnx("Overriding overlay file %s", overlayFileName);
|
||||||
|
}
|
||||||
overlayFileName = musl_optarg;
|
overlayFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
if (outputFileName)
|
if (outputFileName) {
|
||||||
warnx("Overriding output file %s", outputFileName);
|
warnx("Overriding output file %s", outputFileName);
|
||||||
|
}
|
||||||
outputFileName = musl_optarg;
|
outputFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'p': {
|
case 'p': {
|
||||||
@@ -409,18 +424,22 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Patch the size array depending on command-line options
|
// Patch the size array depending on command-line options
|
||||||
if (!is32kMode)
|
if (!is32kMode) {
|
||||||
sectionTypeInfo[SECTTYPE_ROM0].size = 0x4000;
|
sectionTypeInfo[SECTTYPE_ROM0].size = 0x4000;
|
||||||
if (!isWRAM0Mode)
|
}
|
||||||
|
if (!isWRAM0Mode) {
|
||||||
sectionTypeInfo[SECTTYPE_WRAM0].size = 0x1000;
|
sectionTypeInfo[SECTTYPE_WRAM0].size = 0x1000;
|
||||||
|
}
|
||||||
|
|
||||||
// Patch the bank ranges array depending on command-line options
|
// Patch the bank ranges array depending on command-line options
|
||||||
if (isDmgMode)
|
if (isDmgMode) {
|
||||||
sectionTypeInfo[SECTTYPE_VRAM].lastBank = 0;
|
sectionTypeInfo[SECTTYPE_VRAM].lastBank = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Read all object files first,
|
// Read all object files first,
|
||||||
for (obj_Setup(argc - curArgIndex); curArgIndex < argc; curArgIndex++)
|
for (obj_Setup(argc - curArgIndex); curArgIndex < argc; curArgIndex++) {
|
||||||
obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1);
|
obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// apply the linker script's modifications,
|
// apply the linker script's modifications,
|
||||||
if (linkerScriptName) {
|
if (linkerScriptName) {
|
||||||
@@ -429,20 +448,23 @@ int main(int argc, char *argv[]) {
|
|||||||
script_ProcessScript(linkerScriptName);
|
script_ProcessScript(linkerScriptName);
|
||||||
|
|
||||||
// If the linker script produced any errors, some sections may be in an invalid state
|
// If the linker script produced any errors, some sections may be in an invalid state
|
||||||
if (nbErrors != 0)
|
if (nbErrors != 0) {
|
||||||
reportErrors();
|
reportErrors();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// then process them,
|
// then process them,
|
||||||
sect_DoSanityChecks();
|
sect_DoSanityChecks();
|
||||||
if (nbErrors != 0)
|
if (nbErrors != 0) {
|
||||||
reportErrors();
|
reportErrors();
|
||||||
|
}
|
||||||
assign_AssignSections();
|
assign_AssignSections();
|
||||||
patch_CheckAssertions();
|
patch_CheckAssertions();
|
||||||
|
|
||||||
// and finally output the result.
|
// and finally output the result.
|
||||||
patch_ApplyPatches();
|
patch_ApplyPatches();
|
||||||
if (nbErrors != 0)
|
if (nbErrors != 0) {
|
||||||
reportErrors();
|
reportErrors();
|
||||||
|
}
|
||||||
out_WriteFiles();
|
out_WriteFiles();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: MIT */
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "link/object.hpp"
|
#include "link/object.hpp"
|
||||||
|
|
||||||
@@ -41,11 +41,7 @@ static std::vector<std::vector<FileStackNode>> nodes;
|
|||||||
var = static_cast<vartype>(tmpVal); \
|
var = static_cast<vartype>(tmpVal); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
/*
|
// Reads an unsigned long (32-bit) value from a file, or `INT64_MAX` on failure.
|
||||||
* Reads an unsigned long (32-bit) value from a file.
|
|
||||||
* @param file The file to read from. This will read 4 bytes from the file.
|
|
||||||
* @return The value read, cast to a int64_t, or `INT64_MAX` on failure.
|
|
||||||
*/
|
|
||||||
static int64_t readLong(FILE *file) {
|
static int64_t readLong(FILE *file) {
|
||||||
uint32_t value = 0;
|
uint32_t value = 0;
|
||||||
|
|
||||||
@@ -53,8 +49,9 @@ static int64_t readLong(FILE *file) {
|
|||||||
for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
|
for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
|
||||||
int byte = getc(file);
|
int byte = getc(file);
|
||||||
|
|
||||||
if (byte == EOF)
|
if (byte == EOF) {
|
||||||
return INT64_MAX;
|
return INT64_MAX;
|
||||||
|
}
|
||||||
// This must be casted to `unsigned`, not `uint8_t`. Rationale:
|
// This must be casted to `unsigned`, not `uint8_t`. Rationale:
|
||||||
// the type of the shift is the type of `byte` after undergoing
|
// the type of the shift is the type of `byte` after undergoing
|
||||||
// integer promotion, which would be `int` if this was casted to
|
// integer promotion, which would be `int` if this was casted to
|
||||||
@@ -66,37 +63,14 @@ static int64_t readLong(FILE *file) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Helper macro to read a long from a file to a var, or error out if it fails to.
|
||||||
* Helper macro for reading longs from a file, and errors out if it fails to.
|
|
||||||
* Not as a function to avoid overhead in the general case.
|
|
||||||
* @param var The variable to stash the number into
|
|
||||||
* @param file The file to read from. Its position will be advanced
|
|
||||||
* @param ... A format string and related arguments; note that an extra string
|
|
||||||
* argument is provided, the reason for failure
|
|
||||||
*/
|
|
||||||
#define tryReadLong(var, file, ...) \
|
#define tryReadLong(var, file, ...) \
|
||||||
tryRead(readLong, int64_t, INT64_MAX, long, var, file, __VA_ARGS__)
|
tryRead(readLong, int64_t, INT64_MAX, long, var, file, __VA_ARGS__)
|
||||||
|
|
||||||
// There is no `readbyte`, just use `fgetc` or `getc`.
|
// Helper macro to read a byte from a file to a var, or error out if it fails to.
|
||||||
|
|
||||||
/*
|
|
||||||
* Helper macro for reading bytes from a file, and errors out if it fails to.
|
|
||||||
* Not as a function to avoid overhead in the general case.
|
|
||||||
* @param var The variable to stash the number into
|
|
||||||
* @param file The file to read from. Its position will be advanced
|
|
||||||
* @param ... A format string and related arguments; note that an extra string
|
|
||||||
* argument is provided, the reason for failure
|
|
||||||
*/
|
|
||||||
#define tryGetc(type, var, file, ...) tryRead(getc, int, EOF, type, var, file, __VA_ARGS__)
|
#define tryGetc(type, var, file, ...) tryRead(getc, int, EOF, type, var, file, __VA_ARGS__)
|
||||||
|
|
||||||
/*
|
// Helper macro to read a '\0'-terminated string from a file, or error out if it fails to.
|
||||||
* Helper macro for readings '\0'-terminated strings from a file, and errors out if it fails to.
|
|
||||||
* Not as a function to avoid overhead in the general case.
|
|
||||||
* @param var The variable to stash the string into
|
|
||||||
* @param file The file to read from. Its position will be advanced
|
|
||||||
* @param ... A format string and related arguments; note that an extra string
|
|
||||||
* argument is provided, the reason for failure
|
|
||||||
*/
|
|
||||||
#define tryReadString(var, file, ...) \
|
#define tryReadString(var, file, ...) \
|
||||||
do { \
|
do { \
|
||||||
FILE *tmpFile = file; \
|
FILE *tmpFile = file; \
|
||||||
@@ -107,28 +81,24 @@ static int64_t readLong(FILE *file) {
|
|||||||
} else { \
|
} else { \
|
||||||
tmpVal.push_back(tmpByte); \
|
tmpVal.push_back(tmpByte); \
|
||||||
} \
|
} \
|
||||||
}; \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
// Functions to parse object files
|
// Functions to parse object files
|
||||||
|
|
||||||
/*
|
// Reads a file stack node from a file.
|
||||||
* Reads a file stack node form a file.
|
|
||||||
* @param file The file to read from
|
|
||||||
* @param nodes The file's array of nodes
|
|
||||||
* @param i The ID of the node in the array
|
|
||||||
* @param fileName The filename to report in errors
|
|
||||||
*/
|
|
||||||
static void readFileStackNode(
|
static void readFileStackNode(
|
||||||
FILE *file, std::vector<FileStackNode> &fileNodes, uint32_t i, char const *fileName
|
FILE *file, std::vector<FileStackNode> &fileNodes, uint32_t nodeID, char const *fileName
|
||||||
) {
|
) {
|
||||||
FileStackNode &node = fileNodes[i];
|
FileStackNode &node = fileNodes[nodeID];
|
||||||
uint32_t parentID;
|
uint32_t parentID;
|
||||||
|
|
||||||
tryReadLong(parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i);
|
tryReadLong(
|
||||||
|
parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, nodeID
|
||||||
|
);
|
||||||
node.parent = parentID != UINT32_MAX ? &fileNodes[parentID] : nullptr;
|
node.parent = parentID != UINT32_MAX ? &fileNodes[parentID] : nullptr;
|
||||||
tryReadLong(
|
tryReadLong(
|
||||||
node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i
|
node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, nodeID
|
||||||
);
|
);
|
||||||
tryGetc(
|
tryGetc(
|
||||||
FileStackNodeType,
|
FileStackNodeType,
|
||||||
@@ -136,47 +106,46 @@ static void readFileStackNode(
|
|||||||
file,
|
file,
|
||||||
"%s: Cannot read node #%" PRIu32 "'s type: %s",
|
"%s: Cannot read node #%" PRIu32 "'s type: %s",
|
||||||
fileName,
|
fileName,
|
||||||
i
|
nodeID
|
||||||
);
|
);
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case NODE_FILE:
|
case NODE_FILE:
|
||||||
case NODE_MACRO:
|
case NODE_MACRO:
|
||||||
node.data = "";
|
node.data = "";
|
||||||
tryReadString(
|
tryReadString(
|
||||||
node.name(), file, "%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, i
|
node.name(), file, "%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, nodeID
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
uint32_t depth;
|
uint32_t depth;
|
||||||
case NODE_REPT:
|
case NODE_REPT:
|
||||||
tryReadLong(depth, file, "%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, i);
|
tryReadLong(
|
||||||
|
depth, file, "%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, nodeID
|
||||||
|
);
|
||||||
node.data = std::vector<uint32_t>(depth);
|
node.data = std::vector<uint32_t>(depth);
|
||||||
for (uint32_t k = 0; k < depth; k++)
|
for (uint32_t i = 0; i < depth; i++) {
|
||||||
tryReadLong(
|
tryReadLong(
|
||||||
node.iters()[k],
|
node.iters()[i],
|
||||||
file,
|
file,
|
||||||
"%s: Cannot read node #%" PRIu32 "'s iter #%" PRIu32 ": %s",
|
"%s: Cannot read node #%" PRIu32 "'s iter #%" PRIu32 ": %s",
|
||||||
fileName,
|
fileName,
|
||||||
i,
|
nodeID,
|
||||||
k
|
i
|
||||||
);
|
);
|
||||||
if (!node.parent)
|
}
|
||||||
|
if (!node.parent) {
|
||||||
fatal(
|
fatal(
|
||||||
nullptr,
|
nullptr,
|
||||||
0,
|
0,
|
||||||
"%s is not a valid object file: root node (#%" PRIu32 ") may not be REPT",
|
"%s is not a valid object file: root node (#%" PRIu32 ") may not be REPT",
|
||||||
fileName,
|
fileName,
|
||||||
i
|
nodeID
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Reads a symbol from a file.
|
||||||
* Reads a symbol from a file.
|
|
||||||
* @param file The file to read from
|
|
||||||
* @param symbol The symbol to fill
|
|
||||||
* @param fileName The filename to report in errors
|
|
||||||
*/
|
|
||||||
static void readSymbol(
|
static void readSymbol(
|
||||||
FILE *file, Symbol &symbol, char const *fileName, std::vector<FileStackNode> const &fileNodes
|
FILE *file, Symbol &symbol, char const *fileName, std::vector<FileStackNode> const &fileNodes
|
||||||
) {
|
) {
|
||||||
@@ -229,19 +198,13 @@ static void readSymbol(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Reads a patch from a file.
|
||||||
* Reads a patch from a file.
|
|
||||||
* @param file The file to read from
|
|
||||||
* @param patch The patch to fill
|
|
||||||
* @param fileName The filename to report in errors
|
|
||||||
* @param i The number of the patch to report in errors
|
|
||||||
*/
|
|
||||||
static void readPatch(
|
static void readPatch(
|
||||||
FILE *file,
|
FILE *file,
|
||||||
Patch &patch,
|
Patch &patch,
|
||||||
char const *fileName,
|
char const *fileName,
|
||||||
std::string const §Name,
|
std::string const §Name,
|
||||||
uint32_t i,
|
uint32_t patchID,
|
||||||
std::vector<FileStackNode> const &fileNodes
|
std::vector<FileStackNode> const &fileNodes
|
||||||
) {
|
) {
|
||||||
uint32_t nodeID, rpnSize;
|
uint32_t nodeID, rpnSize;
|
||||||
@@ -250,92 +213,85 @@ static void readPatch(
|
|||||||
tryReadLong(
|
tryReadLong(
|
||||||
nodeID,
|
nodeID,
|
||||||
file,
|
file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s",
|
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s node ID: %s",
|
||||||
fileName,
|
fileName,
|
||||||
sectName.c_str(),
|
sectName.c_str(),
|
||||||
i
|
patchID
|
||||||
);
|
);
|
||||||
patch.src = &fileNodes[nodeID];
|
patch.src = &fileNodes[nodeID];
|
||||||
tryReadLong(
|
tryReadLong(
|
||||||
patch.lineNo,
|
patch.lineNo,
|
||||||
file,
|
file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s line number: %s",
|
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s line number: %s",
|
||||||
fileName,
|
fileName,
|
||||||
sectName.c_str(),
|
sectName.c_str(),
|
||||||
i
|
patchID
|
||||||
);
|
);
|
||||||
tryReadLong(
|
tryReadLong(
|
||||||
patch.offset,
|
patch.offset,
|
||||||
file,
|
file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s offset: %s",
|
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s offset: %s",
|
||||||
fileName,
|
fileName,
|
||||||
sectName.c_str(),
|
sectName.c_str(),
|
||||||
i
|
patchID
|
||||||
);
|
);
|
||||||
tryReadLong(
|
tryReadLong(
|
||||||
patch.pcSectionID,
|
patch.pcSectionID,
|
||||||
file,
|
file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
||||||
fileName,
|
fileName,
|
||||||
sectName.c_str(),
|
sectName.c_str(),
|
||||||
i
|
patchID
|
||||||
);
|
);
|
||||||
tryReadLong(
|
tryReadLong(
|
||||||
patch.pcOffset,
|
patch.pcOffset,
|
||||||
file,
|
file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
||||||
fileName,
|
fileName,
|
||||||
sectName.c_str(),
|
sectName.c_str(),
|
||||||
i
|
patchID
|
||||||
);
|
);
|
||||||
tryGetc(
|
tryGetc(
|
||||||
PatchType,
|
PatchType,
|
||||||
type,
|
type,
|
||||||
file,
|
file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s",
|
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s type: %s",
|
||||||
fileName,
|
fileName,
|
||||||
sectName.c_str(),
|
sectName.c_str(),
|
||||||
i
|
patchID
|
||||||
);
|
);
|
||||||
patch.type = type;
|
patch.type = type;
|
||||||
tryReadLong(
|
tryReadLong(
|
||||||
rpnSize,
|
rpnSize,
|
||||||
file,
|
file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s",
|
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s",
|
||||||
fileName,
|
fileName,
|
||||||
sectName.c_str(),
|
sectName.c_str(),
|
||||||
i
|
patchID
|
||||||
);
|
);
|
||||||
|
|
||||||
patch.rpnExpression.resize(rpnSize);
|
patch.rpnExpression.resize(rpnSize);
|
||||||
size_t nbElementsRead = fread(patch.rpnExpression.data(), 1, rpnSize, file);
|
size_t nbElementsRead = fread(patch.rpnExpression.data(), 1, rpnSize, file);
|
||||||
|
|
||||||
if (nbElementsRead != rpnSize)
|
if (nbElementsRead != rpnSize) {
|
||||||
errx(
|
errx(
|
||||||
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
|
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
|
||||||
fileName,
|
fileName,
|
||||||
sectName.c_str(),
|
sectName.c_str(),
|
||||||
i,
|
patchID,
|
||||||
feof(file) ? "Unexpected end of file" : strerror(errno)
|
feof(file) ? "Unexpected end of file" : strerror(errno)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Sets a patch's `pcSection` from its `pcSectionID`.
|
||||||
* Sets a patch's pcSection from its pcSectionID.
|
|
||||||
* @param patch The patch to fix
|
|
||||||
*/
|
|
||||||
static void
|
static void
|
||||||
linkPatchToPCSect(Patch &patch, std::vector<std::unique_ptr<Section>> const &fileSections) {
|
linkPatchToPCSect(Patch &patch, std::vector<std::unique_ptr<Section>> const &fileSections) {
|
||||||
patch.pcSection =
|
patch.pcSection =
|
||||||
patch.pcSectionID != UINT32_MAX ? fileSections[patch.pcSectionID].get() : nullptr;
|
patch.pcSectionID != UINT32_MAX ? fileSections[patch.pcSectionID].get() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Reads a section from a file.
|
||||||
* Reads a section from a file.
|
|
||||||
* @param file The file to read from
|
|
||||||
* @param section The section to fill
|
|
||||||
* @param fileName The filename to report in errors
|
|
||||||
*/
|
|
||||||
static void readSection(
|
static void readSection(
|
||||||
FILE *file, Section §ion, char const *fileName, std::vector<FileStackNode> const &fileNodes
|
FILE *file, Section §ion, char const *fileName, std::vector<FileStackNode> const &fileNodes
|
||||||
) {
|
) {
|
||||||
@@ -356,8 +312,9 @@ static void readSection(
|
|||||||
section.name.c_str()
|
section.name.c_str()
|
||||||
);
|
);
|
||||||
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str());
|
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str());
|
||||||
if (tmp < 0 || tmp > UINT16_MAX)
|
if (tmp < 0 || tmp > UINT16_MAX) {
|
||||||
errx("\"%s\"'s section size ($%" PRIx32 ") is invalid", section.name.c_str(), tmp);
|
errx("\"%s\"'s section size ($%" PRIx32 ") is invalid", section.name.c_str(), tmp);
|
||||||
|
}
|
||||||
section.size = tmp;
|
section.size = tmp;
|
||||||
section.offset = 0;
|
section.offset = 0;
|
||||||
tryGetc(
|
tryGetc(
|
||||||
@@ -368,12 +325,13 @@ static void readSection(
|
|||||||
} else {
|
} else {
|
||||||
section.type = SectionType(type);
|
section.type = SectionType(type);
|
||||||
}
|
}
|
||||||
if (byte >> 7)
|
if (byte >> 7) {
|
||||||
section.modifier = SECTION_UNION;
|
section.modifier = SECTION_UNION;
|
||||||
else if (byte >> 6)
|
} else if (byte >> 6) {
|
||||||
section.modifier = SECTION_FRAGMENT;
|
section.modifier = SECTION_FRAGMENT;
|
||||||
else
|
} else {
|
||||||
section.modifier = SECTION_NORMAL;
|
section.modifier = SECTION_NORMAL;
|
||||||
|
}
|
||||||
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str());
|
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str());
|
||||||
section.isAddressFixed = tmp >= 0;
|
section.isAddressFixed = tmp >= 0;
|
||||||
if (tmp > UINT16_MAX) {
|
if (tmp > UINT16_MAX) {
|
||||||
@@ -392,8 +350,9 @@ static void readSection(
|
|||||||
fileName,
|
fileName,
|
||||||
section.name.c_str()
|
section.name.c_str()
|
||||||
);
|
);
|
||||||
if (byte > 16)
|
if (byte > 16) {
|
||||||
byte = 16;
|
byte = 16;
|
||||||
|
}
|
||||||
section.isAlignFixed = byte != 0;
|
section.isAlignFixed = byte != 0;
|
||||||
section.alignMask = (1 << byte) - 1;
|
section.alignMask = (1 << byte) - 1;
|
||||||
tryReadLong(
|
tryReadLong(
|
||||||
@@ -415,13 +374,14 @@ static void readSection(
|
|||||||
if (section.size) {
|
if (section.size) {
|
||||||
section.data.resize(section.size);
|
section.data.resize(section.size);
|
||||||
if (size_t nbRead = fread(section.data.data(), 1, section.size, file);
|
if (size_t nbRead = fread(section.data.data(), 1, section.size, file);
|
||||||
nbRead != section.size)
|
nbRead != section.size) {
|
||||||
errx(
|
errx(
|
||||||
"%s: Cannot read \"%s\"'s data: %s",
|
"%s: Cannot read \"%s\"'s data: %s",
|
||||||
fileName,
|
fileName,
|
||||||
section.name.c_str(),
|
section.name.c_str(),
|
||||||
feof(file) ? "Unexpected end of file" : strerror(errno)
|
feof(file) ? "Unexpected end of file" : strerror(errno)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t nbPatches;
|
uint32_t nbPatches;
|
||||||
@@ -435,16 +395,13 @@ static void readSection(
|
|||||||
);
|
);
|
||||||
|
|
||||||
section.patches.resize(nbPatches);
|
section.patches.resize(nbPatches);
|
||||||
for (uint32_t i = 0; i < nbPatches; i++)
|
for (uint32_t i = 0; i < nbPatches; i++) {
|
||||||
readPatch(file, section.patches[i], fileName, section.name, i, fileNodes);
|
readPatch(file, section.patches[i], fileName, section.name, i, fileNodes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Links a symbol to a section, keeping the section's symbol list sorted.
|
||||||
* Links a symbol to a section, keeping the section's symbol list sorted.
|
|
||||||
* @param symbol The symbol to link
|
|
||||||
* @param section The section to link
|
|
||||||
*/
|
|
||||||
static void linkSymToSect(Symbol &symbol, Section §ion) {
|
static void linkSymToSect(Symbol &symbol, Section §ion) {
|
||||||
uint32_t a = 0, b = section.symbols.size();
|
uint32_t a = 0, b = section.symbols.size();
|
||||||
int32_t symbolOffset = symbol.label().offset;
|
int32_t symbolOffset = symbol.label().offset;
|
||||||
@@ -453,31 +410,27 @@ static void linkSymToSect(Symbol &symbol, Section §ion) {
|
|||||||
uint32_t c = (a + b) / 2;
|
uint32_t c = (a + b) / 2;
|
||||||
int32_t otherOffset = section.symbols[c]->label().offset;
|
int32_t otherOffset = section.symbols[c]->label().offset;
|
||||||
|
|
||||||
if (otherOffset > symbolOffset)
|
if (otherOffset > symbolOffset) {
|
||||||
b = c;
|
b = c;
|
||||||
else
|
} else {
|
||||||
a = c + 1;
|
a = c + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section.symbols.insert(section.symbols.begin() + a, &symbol);
|
section.symbols.insert(section.symbols.begin() + a, &symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Reads an assertion from a file.
|
||||||
* Reads an assertion from a file
|
|
||||||
* @param file The file to read from
|
|
||||||
* @param assert The assertion to fill
|
|
||||||
* @param fileName The filename to report in errors
|
|
||||||
*/
|
|
||||||
static void readAssertion(
|
static void readAssertion(
|
||||||
FILE *file,
|
FILE *file,
|
||||||
Assertion &assert,
|
Assertion &assert,
|
||||||
char const *fileName,
|
char const *fileName,
|
||||||
uint32_t i,
|
uint32_t assertID,
|
||||||
std::vector<FileStackNode> const &fileNodes
|
std::vector<FileStackNode> const &fileNodes
|
||||||
) {
|
) {
|
||||||
std::string assertName("Assertion #");
|
std::string assertName("Assertion #");
|
||||||
|
|
||||||
assertName += std::to_string(i);
|
assertName += std::to_string(assertID);
|
||||||
readPatch(file, assert.patch, fileName, assertName, 0, fileNodes);
|
readPatch(file, assert.patch, fileName, assertName, 0, fileNodes);
|
||||||
tryReadString(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
|
tryReadString(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
|
||||||
}
|
}
|
||||||
@@ -491,8 +444,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
file = stdin;
|
file = stdin;
|
||||||
}
|
}
|
||||||
if (!file)
|
if (!file) {
|
||||||
err("Failed to open file \"%s\"", fileName);
|
err("Failed to open file \"%s\"", fileName);
|
||||||
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
// First, check if the object is a RGBDS object or a SDCC one. If the first byte is 'R',
|
// First, check if the object is a RGBDS object or a SDCC one. If the first byte is 'R',
|
||||||
@@ -528,15 +482,16 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
int matchedElems;
|
int matchedElems;
|
||||||
|
|
||||||
if (fscanf(file, RGBDS_OBJECT_VERSION_STRING "%n", &matchedElems) == 1
|
if (fscanf(file, RGBDS_OBJECT_VERSION_STRING "%n", &matchedElems) == 1
|
||||||
&& matchedElems != strlen(RGBDS_OBJECT_VERSION_STRING))
|
&& matchedElems != literal_strlen(RGBDS_OBJECT_VERSION_STRING)) {
|
||||||
errx("%s: Not a RGBDS object file", fileName);
|
errx("%s: Not a RGBDS object file", fileName);
|
||||||
|
}
|
||||||
|
|
||||||
verbosePrint("Reading object file %s\n", fileName);
|
verbosePrint("Reading object file %s\n", fileName);
|
||||||
|
|
||||||
uint32_t revNum;
|
uint32_t revNum;
|
||||||
|
|
||||||
tryReadLong(revNum, file, "%s: Cannot read revision number: %s", fileName);
|
tryReadLong(revNum, file, "%s: Cannot read revision number: %s", fileName);
|
||||||
if (revNum != RGBDS_OBJECT_REV)
|
if (revNum != RGBDS_OBJECT_REV) {
|
||||||
errx(
|
errx(
|
||||||
"%s: Unsupported object file for rgblink %s; try rebuilding \"%s\"%s"
|
"%s: Unsupported object file for rgblink %s; try rebuilding \"%s\"%s"
|
||||||
" (expected revision %d, got %d)",
|
" (expected revision %d, got %d)",
|
||||||
@@ -547,6 +502,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
RGBDS_OBJECT_REV,
|
RGBDS_OBJECT_REV,
|
||||||
revNum
|
revNum
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t nbNodes;
|
uint32_t nbNodes;
|
||||||
uint32_t nbSymbols;
|
uint32_t nbSymbols;
|
||||||
@@ -560,8 +516,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
tryReadLong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
|
tryReadLong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
|
||||||
nodes[fileID].resize(nbNodes);
|
nodes[fileID].resize(nbNodes);
|
||||||
verbosePrint("Reading %u nodes...\n", nbNodes);
|
verbosePrint("Reading %u nodes...\n", nbNodes);
|
||||||
for (uint32_t i = nbNodes; i--;)
|
for (uint32_t i = nbNodes; i--;) {
|
||||||
readFileStackNode(file, nodes[fileID], i, fileName);
|
readFileStackNode(file, nodes[fileID], i, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
// This file's symbols, kept to link sections to them
|
// This file's symbols, kept to link sections to them
|
||||||
std::vector<Symbol> &fileSymbols = symbolLists.emplace_front(nbSymbols);
|
std::vector<Symbol> &fileSymbols = symbolLists.emplace_front(nbSymbols);
|
||||||
@@ -575,8 +532,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
readSymbol(file, symbol, fileName, nodes[fileID]);
|
readSymbol(file, symbol, fileName, nodes[fileID]);
|
||||||
|
|
||||||
sym_AddSymbol(symbol);
|
sym_AddSymbol(symbol);
|
||||||
if (symbol.data.holds<Label>())
|
if (symbol.data.holds<Label>()) {
|
||||||
nbSymPerSect[symbol.data.get<Label>().sectionID]++;
|
nbSymPerSect[symbol.data.get<Label>().sectionID]++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This file's sections, stored in a table to link symbols to them
|
// This file's sections, stored in a table to link symbols to them
|
||||||
@@ -607,8 +565,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
// Give patches' PC section pointers to their sections
|
// Give patches' PC section pointers to their sections
|
||||||
for (uint32_t i = 0; i < nbSections; i++) {
|
for (uint32_t i = 0; i < nbSections; i++) {
|
||||||
if (sect_HasData(fileSections[i]->type)) {
|
if (sect_HasData(fileSections[i]->type)) {
|
||||||
for (Patch &patch : fileSections[i]->patches)
|
for (Patch &patch : fileSections[i]->patches) {
|
||||||
linkPatchToPCSect(patch, fileSections);
|
linkPatchToPCSect(patch, fileSections);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,8 +582,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calling `sect_AddSection` invalidates the contents of `fileSections`!
|
// Calling `sect_AddSection` invalidates the contents of `fileSections`!
|
||||||
for (uint32_t i = 0; i < nbSections; i++)
|
for (uint32_t i = 0; i < nbSections; i++) {
|
||||||
sect_AddSection(std::move(fileSections[i]));
|
sect_AddSection(std::move(fileSections[i]));
|
||||||
|
}
|
||||||
|
|
||||||
// Fix symbols' section pointers to component sections
|
// Fix symbols' section pointers to component sections
|
||||||
// This has to run **after** all the `sect_AddSection()` calls,
|
// This has to run **after** all the `sect_AddSection()` calls,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user