mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 02:32:06 +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
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BreakAfterAttributes: Always
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeConceptDeclarations: true
|
||||
@@ -54,12 +55,14 @@ IncludeCategories:
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: NoIndent
|
||||
IndentExternBlock: Indent
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: BeforeHash
|
||||
IndentRequires: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
InsertBraces: true
|
||||
InsertNewlineAtEOF: true
|
||||
# Only support for Javascript as of clang-format 14...
|
||||
# InsertTrailingCommas: Wrapped
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
@@ -71,6 +74,8 @@ PPIndentWidth: -1
|
||||
PointerAlignment: Right
|
||||
QualifierAlignment: Right
|
||||
ReflowComments: true
|
||||
RemoveParentheses: ReturnStatement
|
||||
RemoveSemicolon: true
|
||||
SortIncludes: CaseSensitive
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
|
||||
4
.github/scripts/build_libpng.sh
vendored
4
.github/scripts/build_libpng.sh
vendored
@@ -1,13 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.43
|
||||
pngver=1.6.45
|
||||
|
||||
## Grab sources and check them
|
||||
|
||||
curl -LOJ "http://prdownloads.sourceforge.net/libpng/libpng-$pngver.tar.xz?download"
|
||||
# Brew doesn't provide any sha256sum, so we're making do with `sha2` instead.
|
||||
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c ]; then
|
||||
if [ "$(sha2 -q -256 libpng-$pngver.tar.xz)" != 926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 ]; then
|
||||
sha2 -256 libpng-$pngver.tar.xz
|
||||
echo Checksum mismatch! Aborting. >&2
|
||||
exit 1
|
||||
|
||||
4
.github/scripts/get_win_deps.ps1
vendored
4
.github/scripts/get_win_deps.ps1
vendored
@@ -16,8 +16,8 @@ function getlibrary ([string] $URI, [string] $filename, [string] $hash, [string]
|
||||
}
|
||||
|
||||
getlibrary 'https://www.zlib.net/zlib131.zip' 'zlib.zip' '72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17' .
|
||||
getlibrary 'https://github.com/glennrp/libpng/archive/refs/tags/v1.6.43.zip' 'libpng.zip' '5e18474a26814ae479e02ca6432da32d19dc6e615551d140c954a68d63b3f192' .
|
||||
getlibrary 'https://github.com/glennrp/libpng/archive/refs/tags/v1.6.45.zip' 'libpng.zip' '1b3d94b2f1d137db1bf1842cb9f03df179772a517f7b86e26351742190632785' .
|
||||
getlibrary 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip' 'winflexbison.zip' '8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135' install_dir
|
||||
|
||||
Move-Item zlib-1.3.1 zlib
|
||||
Move-Item libpng-1.6.43 libpng
|
||||
Move-Item libpng-1.6.45 libpng
|
||||
|
||||
2
.github/scripts/mingw-w64-libpng-dev.sh
vendored
2
.github/scripts/mingw-w64-libpng-dev.sh
vendored
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
pngver=1.6.43
|
||||
pngver=1.6.45
|
||||
arch="$1"
|
||||
|
||||
## Grab sources and check them
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
d6bd2a3f43f17020918a4c1bd81c1a78111b6f759af9c1d3c754f704a1bf0429 libpng-1.6.43-apng.patch.gz
|
||||
6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c libpng-1.6.43.tar.xz
|
||||
f7caa3b55f003ce23d6a087b1c2a643262647bfd1a1b31afdc9b18eabf1bbc7e libpng-1.6.45-apng.patch.gz
|
||||
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-1.6.45.tar.xz
|
||||
|
||||
18
.github/workflows/analysis.yml
vendored
Normal file
18
.github/workflows/analysis.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Static analysis
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/install_deps.sh ubuntu-latest
|
||||
- name: Static analysis
|
||||
run: | # Silence warnings with too many false positives (https://stackoverflow.com/a/73913076)
|
||||
make -kj CXX=g++-14 CXXFLAGS="-fanalyzer -fanalyzer-verbosity=0 -Wno-analyzer-use-of-uninitialized-value -DNDEBUG" Q=
|
||||
66
.github/workflows/testing.yml
vendored
66
.github/workflows/testing.yml
vendored
@@ -320,3 +320,69 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
test/run-tests.sh
|
||||
|
||||
cygwin:
|
||||
strategy:
|
||||
matrix:
|
||||
bits: [32, 64]
|
||||
include:
|
||||
- bits: 32
|
||||
arch: x86
|
||||
- bits: 64
|
||||
arch: x86_64
|
||||
fail-fast: false
|
||||
runs-on: windows-2019
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Setup Cygwin
|
||||
uses: cygwin/cygwin-install-action@v4
|
||||
with:
|
||||
platform: ${{ matrix.arch }}
|
||||
packages: >-
|
||||
bison
|
||||
gcc-g++
|
||||
git
|
||||
libpng-devel
|
||||
make
|
||||
pkg-config
|
||||
- name: Build & install using Make
|
||||
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
|
||||
run: | # Cygwin does not support `make develop` sanitizers ASan or UBSan
|
||||
make -kj Q=
|
||||
make install -j Q=
|
||||
- name: Run tests
|
||||
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -o igncr '{0}'
|
||||
run: |
|
||||
test/run-tests.sh --only-internal
|
||||
|
||||
freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Build & test using CMake on FreeBSD
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "15.0"
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
bash \
|
||||
bison \
|
||||
cmake \
|
||||
git \
|
||||
png
|
||||
run: | # FreeBSD `c++` compiler does not support `make develop` sanitizers ASan or UBSan
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=c++ -DUSE_EXTERNAL_TESTS=OFF
|
||||
cmake --build build -j4 --verbose
|
||||
cmake --install build --verbose
|
||||
cmake --build build --target test
|
||||
|
||||
@@ -45,11 +45,8 @@ else()
|
||||
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
|
||||
endif()
|
||||
if(SANITIZERS)
|
||||
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
|
||||
-fsanitize=unreachable -fsanitize=vla-bound
|
||||
-fsanitize=signed-integer-overflow -fsanitize=bounds
|
||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
|
||||
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
||||
set(SAN_FLAGS -fsanitize=address -fsanitize=undefined
|
||||
-fsanitize=float-divide-by-zero)
|
||||
add_compile_options(${SAN_FLAGS})
|
||||
add_link_options(${SAN_FLAGS})
|
||||
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
||||
@@ -78,7 +75,7 @@ endif()
|
||||
|
||||
find_program(GIT git)
|
||||
if(GIT)
|
||||
execute_process(COMMAND ${GIT} --git-dir=.git describe --tags --dirty --always
|
||||
execute_process(COMMAND ${GIT} --git-dir=.git -c safe.directory='*' describe --tags --dirty --always
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM debian:11-slim
|
||||
FROM debian:12-slim
|
||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||
ARG version=0.9.0
|
||||
ARG version=0.9.1
|
||||
WORKDIR /rgbds
|
||||
|
||||
COPY . .
|
||||
@@ -8,7 +8,7 @@ COPY . .
|
||||
RUN apt-get update && \
|
||||
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 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`
|
||||
|
||||
# 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
|
||||
|
||||
@@ -206,12 +206,8 @@ develop:
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
||||
-D_GLIBCXX_ASSERTIONS \
|
||||
-fsanitize=shift -fsanitize=integer-divide-by-zero \
|
||||
-fsanitize=unreachable -fsanitize=vla-bound \
|
||||
-fsanitize=signed-integer-overflow -fsanitize=bounds \
|
||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
|
||||
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
|
||||
-D_GLIBCXX_ASSERTIONS -fsanitize=address -fsanitize=undefined \
|
||||
-fsanitize=float-divide-by-zero" \
|
||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||
|
||||
# This target is used during development in order to more easily debug with gdb.
|
||||
@@ -269,4 +265,4 @@ wine-shim:
|
||||
|
||||
dist:
|
||||
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
|
||||
| tar -czf rgbds-`git describe --tags | cut -c 2-`.tar.gz -C .. -T -
|
||||
| tar -czf rgbds-`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).
|
||||
- 2010-01-12: Anthony J. Bentley [forks](https://github.com/bentley) Nossum's
|
||||
repository. The fork becomes the reference implementation of RGBDS.
|
||||
- 2010-09-25: Sørensen continues development of
|
||||
[ASMotor](https://github.com/asmotor/asmotor) to this day.
|
||||
- 2015-01-18: stag019 begins implementing [RGBGFX](https://github.com/stag019/rgbgfx),
|
||||
a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
- 2016-09-05: RGBGFX is [integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||
- 2016-09-05: RGBGFX is
|
||||
[integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||
into Bentley's repository.
|
||||
- 2017-02-23: Bentley's repository is moved to the [rednex](https://github.com/rednex)
|
||||
organization.
|
||||
|
||||
@@ -25,6 +25,7 @@ _rgbasm_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[E]="export-all:normal"
|
||||
[v]="verbose:normal"
|
||||
[w]=":normal"
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgbfix_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[j]="non-japanese:normal"
|
||||
[s]="sgb-compatible:normal"
|
||||
[v]="validate:normal"
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgbgfx_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[C]="color-curve:normal"
|
||||
[m]="mirror-tiles:normal"
|
||||
[O]="group-outputs:normal"
|
||||
|
||||
@@ -8,6 +8,7 @@ _rgblink_completions() {
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[h]="help:normal"
|
||||
[d]="dmg:normal"
|
||||
[t]="tiny:normal"
|
||||
[v]="verbose:normal"
|
||||
|
||||
@@ -36,8 +36,9 @@ _rgbasm_warnings() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
|
||||
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
||||
|
||||
@@ -35,8 +35,9 @@ _mbc_names() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-C --color-only -c --color-compatible)'{-C,--color-only}'[Mark ROM as GBC-only]'
|
||||
'(-C --color-only -c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'
|
||||
|
||||
@@ -10,8 +10,9 @@ _depths() {
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-a --attr-map -A --auto-attr-map)'{-A,--auto-attr-map}'[Shortcut for -a <file>.attrmap]'
|
||||
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#compdef rgblink
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
# Arguments are listed here in the same order as in the manual, except for the version and help
|
||||
'(- : * options)'{-V,--version}'[Print version number and exit]'
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-d --dmg)'{-d,--dmg}'[Enable DMG mode (-w + no VRAM banking)]'
|
||||
'(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef 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
|
||||
#define RGBDS_ASM_FIXPOINT_HPP
|
||||
@@ -8,7 +8,6 @@
|
||||
extern uint8_t fixPrecision;
|
||||
|
||||
uint8_t fix_Precision();
|
||||
double fix_PrecisionFactor();
|
||||
int32_t fix_Sin(int32_t i, int32_t q);
|
||||
int32_t fix_Cos(int32_t i, int32_t q);
|
||||
int32_t fix_Tan(int32_t i, int32_t q);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_FORMAT_HPP
|
||||
#define RGBDS_ASM_FORMAT_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Contains some assembler-wide defines and externs
|
||||
|
||||
@@ -41,15 +41,11 @@ struct FileStackNode {
|
||||
std::string const &name() const { return data.get<std::string>(); }
|
||||
|
||||
FileStackNode(FileStackNodeType type_, Either<std::vector<uint32_t>, std::string> data_)
|
||||
: type(type_), data(data_){};
|
||||
: type(type_), data(data_) {}
|
||||
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
|
||||
// If true, entering this context generates a new unique ID.
|
||||
bool generatesUniqueID() const { return type == NODE_REPT || type == NODE_MACRO; }
|
||||
};
|
||||
|
||||
#define DEFAULT_MAX_DEPTH 64
|
||||
extern size_t maxRecursionDepth;
|
||||
|
||||
struct MacroArgs;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_LEXER_HPP
|
||||
#define RGBDS_ASM_LEXER_HPP
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
// This value is a compromise between `LexerState` allocation performance when `mmap` works, and
|
||||
// buffering performance when it doesn't/can't (e.g. when piping a file into RGBASM).
|
||||
#define LEXER_BUF_SIZE 64
|
||||
static constexpr size_t LEXER_BUF_SIZE = 64;
|
||||
// The buffer needs to be large enough for the maximum `lexerState->peek()` lookahead distance
|
||||
static_assert(LEXER_BUF_SIZE > 1, "Lexer buffer size is too small");
|
||||
// This caps the size of buffer reads, and according to POSIX, passing more than SSIZE_MAX is UB
|
||||
@@ -83,7 +83,6 @@ struct LexerState {
|
||||
LexerMode mode;
|
||||
bool atLineStart;
|
||||
uint32_t lineNo;
|
||||
uint32_t colNo;
|
||||
int lastToken;
|
||||
|
||||
std::deque<IfStackEntry> ifStack;
|
||||
@@ -147,7 +146,6 @@ void lexer_ReachELSEBlock();
|
||||
|
||||
void lexer_CheckRecursionDepth();
|
||||
uint32_t lexer_GetLineNo();
|
||||
uint32_t lexer_GetColNo();
|
||||
void lexer_DumpStringExpansions();
|
||||
|
||||
struct Capture {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_MACRO_HPP
|
||||
#define RGBDS_ASM_MACRO_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_MAIN_HPP
|
||||
#define RGBDS_ASM_MAIN_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_OPT_HPP
|
||||
#define RGBDS_ASM_OPT_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_OUTPUT_HPP
|
||||
#define RGBDS_ASM_OUTPUT_HPP
|
||||
@@ -14,14 +14,7 @@
|
||||
struct Expression;
|
||||
struct FileStackNode;
|
||||
|
||||
enum StateFeature {
|
||||
STATE_EQU,
|
||||
STATE_VAR,
|
||||
STATE_EQUS,
|
||||
STATE_CHAR,
|
||||
STATE_MACRO,
|
||||
NB_STATE_FEATURES
|
||||
};
|
||||
enum StateFeature { STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO, NB_STATE_FEATURES };
|
||||
|
||||
extern std::string objectFileName;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_RPN_HPP
|
||||
#define RGBDS_ASM_RPN_HPP
|
||||
@@ -31,12 +31,8 @@ struct Expression {
|
||||
|
||||
Expression &operator=(Expression &&) = default;
|
||||
|
||||
bool isKnown() const {
|
||||
return data.holds<int32_t>();
|
||||
}
|
||||
int32_t value() const {
|
||||
return data.get<int32_t>();
|
||||
}
|
||||
bool isKnown() const { return data.holds<int32_t>(); }
|
||||
int32_t value() const { return data.get<int32_t>(); }
|
||||
|
||||
int32_t getConstVal() const;
|
||||
Symbol const *symbolOf() const;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef 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
|
||||
#define RGBDS_ASM_SYMBOL_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ASM_WARNING_HPP
|
||||
#define RGBDS_ASM_WARNING_HPP
|
||||
@@ -62,27 +62,24 @@ extern bool warningsAreErrors;
|
||||
|
||||
void processWarningFlag(char const *flag);
|
||||
|
||||
/*
|
||||
* Used to warn the user about problems that don't prevent the generation of
|
||||
* valid code.
|
||||
*/
|
||||
[[gnu::format(printf, 2, 3)]] void warning(WarningID id, char const *fmt, ...);
|
||||
// Used to warn the user about problems that don't prevent the generation of
|
||||
// valid code.
|
||||
[[gnu::format(printf, 2, 3)]]
|
||||
void warning(WarningID id, char const *fmt, ...);
|
||||
|
||||
/*
|
||||
* Used for errors that compromise the whole assembly process by affecting the
|
||||
* following code, potencially making the assembler generate errors caused by
|
||||
* the first one and unrelated to the code that the assembler complains about.
|
||||
* It is also used when the assembler goes into an invalid state (for example,
|
||||
* when it fails to allocate memory).
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void fatalerror(char const *fmt, ...);
|
||||
// Used for errors that compromise the whole assembly process by affecting the
|
||||
// following code, potencially making the assembler generate errors caused by
|
||||
// the first one and unrelated to the code that the assembler complains about.
|
||||
// It is also used when the assembler goes into an invalid state (for example,
|
||||
// when it fails to allocate memory).
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatalerror(char const *fmt, ...);
|
||||
|
||||
/*
|
||||
* Used for errors that make it impossible to assemble correctly, but don't
|
||||
* affect the following code. The code will fail to assemble but the user will
|
||||
* get a list of all errors at the end, making it easier to fix all of them at
|
||||
* once.
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
|
||||
// Used for errors that make it impossible to assemble correctly, but don't
|
||||
// affect the following code. The code will fail to assemble but the user will
|
||||
// get a list of all errors at the end, making it easier to fix all of them at
|
||||
// once.
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
|
||||
#endif // RGBDS_ASM_WARNING_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||
#define RGBDS_DEFAULT_INIT_ALLOC_HPP
|
||||
@@ -6,14 +6,11 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
* Allocator adaptor that interposes construct() calls to convert value-initialization
|
||||
* (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
|
||||
* zero out non-class types).
|
||||
* From
|
||||
* https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
|
||||
*/
|
||||
|
||||
// Allocator adaptor that interposes construct() calls to convert value-initialization
|
||||
// (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
|
||||
// zero out non-class types).
|
||||
// From
|
||||
// https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
|
||||
template<typename T, typename A = std::allocator<T>>
|
||||
class default_init_allocator : public A {
|
||||
using a_t = std::allocator_traits<A>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_EITHER_HPP
|
||||
#define RGBDS_EITHER_HPP
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ERROR_HPP
|
||||
#define RGBDS_ERROR_HPP
|
||||
|
||||
extern "C" {
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warn(char const *fmt...);
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warnx(char const *fmt, ...);
|
||||
|
||||
[[gnu::format(printf, 1, 2)]] void warn(char const *fmt...);
|
||||
[[gnu::format(printf, 1, 2)]] void warnx(char const *fmt, ...);
|
||||
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void err(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void errx(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void err(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void errx(char const *fmt, ...);
|
||||
}
|
||||
|
||||
#endif // RGBDS_ERROR_HPP
|
||||
|
||||
16
include/extern/getopt.hpp
vendored
16
include/extern/getopt.hpp
vendored
@@ -1,11 +1,15 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* This implementation was taken from musl and modified for RGBDS */
|
||||
// This implementation was taken from musl and modified for RGBDS
|
||||
|
||||
#ifndef RGBDS_EXTERN_GETOPT_HPP
|
||||
#define RGBDS_EXTERN_GETOPT_HPP
|
||||
|
||||
extern "C" {
|
||||
// clang-format off: vertically align values
|
||||
static constexpr int no_argument = 0;
|
||||
static constexpr int required_argument = 1;
|
||||
static constexpr int optional_argument = 2;
|
||||
// clang-format on
|
||||
|
||||
extern char *musl_optarg;
|
||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
||||
@@ -21,10 +25,4 @@ int musl_getopt_long_only(
|
||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx
|
||||
);
|
||||
|
||||
#define no_argument 0
|
||||
#define required_argument 1
|
||||
#define optional_argument 2
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // RGBDS_EXTERN_GETOPT_HPP
|
||||
|
||||
2
include/extern/utf8decoder.hpp
vendored
2
include/extern/utf8decoder.hpp
vendored
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_EXTERN_UTF8DECODER_HPP
|
||||
#define RGBDS_EXTERN_UTF8DECODER_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_FILE_HPP
|
||||
#define RGBDS_FILE_HPP
|
||||
@@ -25,10 +25,8 @@ public:
|
||||
File() {}
|
||||
~File() { close(); }
|
||||
|
||||
/**
|
||||
* This should only be called once, and before doing any `->` operations.
|
||||
* Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||
*/
|
||||
// This should only be called once, and before doing any `->` operations.
|
||||
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||
File *open(std::string const &path, std::ios_base::openmode mode) {
|
||||
if (path != "-") {
|
||||
_file.emplace<std::filebuf>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_MAIN_HPP
|
||||
#define RGBDS_GFX_MAIN_HPP
|
||||
@@ -48,6 +48,7 @@ struct Options {
|
||||
|
||||
std::string input{}; // positional arg
|
||||
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||
@@ -55,7 +56,9 @@ struct Options {
|
||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
||||
static constexpr uint8_t VERB_TRACE = 5; // Step-by-step algorithm details
|
||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
||||
[[gnu::format(printf, 3, 4)]] void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||
// clang-format on
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||
|
||||
mutable bool hasTransparentPixels = false;
|
||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
||||
@@ -63,32 +66,24 @@ struct Options {
|
||||
|
||||
extern Options options;
|
||||
|
||||
/*
|
||||
* Prints the error count, and exits with failure
|
||||
*/
|
||||
[[noreturn]] void giveUp();
|
||||
/*
|
||||
* If any error has been emitted thus far, calls `giveUp()`.
|
||||
*/
|
||||
// Prints the error count, and exits with failure
|
||||
[[noreturn]]
|
||||
void giveUp();
|
||||
// If any error has been emitted thus far, calls `giveUp()`.
|
||||
void requireZeroErrors();
|
||||
/*
|
||||
* Prints a warning, and does not change the error count
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2)]] void warning(char const *fmt, ...);
|
||||
/*
|
||||
* Prints an error, and increments the error count
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...);
|
||||
/*
|
||||
* Prints an error, and increments the error count
|
||||
* Does not take format arguments so `format_` and `-Wformat-security` won't complain about
|
||||
* calling `errorMessage(msg)`.
|
||||
*/
|
||||
// Prints a warning, and does not change the error count
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void warning(char const *fmt, ...);
|
||||
// Prints an error, and increments the error count
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
// Prints an error, and increments the error count
|
||||
// Does not take format arguments so `format_` and `-Wformat-security` won't complain about
|
||||
// calling `errorMessage(msg)`.
|
||||
void errorMessage(char const *msg);
|
||||
/*
|
||||
* Prints a fatal error, increments the error count, and gives up
|
||||
*/
|
||||
[[gnu::format(printf, 1, 2), noreturn]] void fatal(char const *fmt, ...);
|
||||
// Prints a fatal error, increments the error count, and gives up
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatal(char const *fmt, ...);
|
||||
|
||||
struct Palette {
|
||||
// An array of 4 GBC-native (RGB555) colors
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
||||
#define RGBDS_GFX_PAL_PACKING_HPP
|
||||
@@ -11,9 +11,7 @@
|
||||
struct Palette;
|
||||
class ProtoPalette;
|
||||
|
||||
/*
|
||||
* Returns which palette each proto-palette maps to, and how many palettes are necessary
|
||||
*/
|
||||
// Returns which palette each proto-palette maps to, and how many palettes are necessary
|
||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
||||
#define RGBDS_GFX_PAL_SORTING_HPP
|
||||
@@ -10,6 +10,10 @@
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
// Allow a slot for every possible CGB color, plus one for transparency
|
||||
// 32 (1 << 5) per channel, times 3 RGB channels = 32768 CGB colors
|
||||
static constexpr size_t NB_COLOR_SLOTS = (1 << (5 * 3)) + 1;
|
||||
|
||||
struct Palette;
|
||||
|
||||
void sortIndexed(
|
||||
@@ -20,7 +24,7 @@ void sortIndexed(
|
||||
png_byte *palAlpha
|
||||
);
|
||||
void sortGrayscale(
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, NB_COLOR_SLOTS> const &colors
|
||||
);
|
||||
void sortRgb(std::vector<Palette> &palettes);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef 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
|
||||
#define RGBDS_GFX_PROCESS_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
|
||||
#define RGBDS_GFX_PROTO_PALETTE_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_REVERSE_HPP
|
||||
#define RGBDS_GFX_REVERSE_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_GFX_RGBA_HPP
|
||||
#define RGBDS_GFX_RGBA_HPP
|
||||
@@ -13,9 +13,7 @@ struct Rgba {
|
||||
|
||||
constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
: red(r), green(g), blue(b), alpha(a) {}
|
||||
/*
|
||||
* Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
|
||||
*/
|
||||
// Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
|
||||
explicit constexpr Rgba(uint32_t rgba = 0)
|
||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||
|
||||
@@ -32,10 +30,8 @@ struct Rgba {
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
||||
* representation
|
||||
*/
|
||||
// Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
||||
// representation
|
||||
uint32_t toCSS() const {
|
||||
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
|
||||
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
||||
@@ -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(); }
|
||||
|
||||
/*
|
||||
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||
* Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||
*/
|
||||
// CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||
// Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||
static constexpr uint16_t transparent = 0b1'00000'00000'00000;
|
||||
|
||||
static constexpr uint8_t transparency_threshold = 0x10;
|
||||
bool isTransparent() const { return alpha < transparency_threshold; }
|
||||
static constexpr uint8_t opacity_threshold = 0xF0;
|
||||
bool isOpaque() const { return alpha >= opacity_threshold; }
|
||||
/*
|
||||
* Computes the equivalent CGB color, respects the color curve depending on options
|
||||
*/
|
||||
// Computes the equivalent CGB color, respects the color curve depending on options
|
||||
uint16_t cgbColor() const;
|
||||
|
||||
bool isGray() const { return red == green && green == blue; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_HELPERS_HPP
|
||||
#define RGBDS_HELPERS_HPP
|
||||
@@ -14,7 +14,8 @@
|
||||
#else
|
||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||
// Note that executing this is undefined behavior (declared [[noreturn]], but does return)
|
||||
[[noreturn]] static inline void unreachable_() {
|
||||
[[noreturn]]
|
||||
static inline void unreachable_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -26,8 +27,9 @@
|
||||
// `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
|
||||
#define assume(x) \
|
||||
do { \
|
||||
if (!(x)) \
|
||||
if (!(x)) { \
|
||||
unreachable_(); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
#else
|
||||
@@ -93,15 +95,14 @@ static inline int clz(unsigned int x) {
|
||||
#define CAT(x, y) x##y
|
||||
#define EXPAND_AND_CAT(x, y) CAT(x, y)
|
||||
|
||||
// Obtaining the size of an array; `arr` must be an expression, not a type!
|
||||
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
|
||||
|
||||
// For lack of <ranges>, this adds some more brevity
|
||||
#define RANGE(s) std::begin(s), std::end(s)
|
||||
|
||||
// MSVC does not inline `strlen()` or `.length()` of a constant string, so we use `sizeof`
|
||||
#define QUOTEDSTRLEN(s) (sizeof(s) - 1)
|
||||
// MSVC does not inline `strlen()` or `.length()` of a constant string
|
||||
template<int N>
|
||||
static constexpr int literal_strlen(char const (&)[N]) {
|
||||
return N - 1;
|
||||
}
|
||||
|
||||
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
|
||||
template<typename T>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_ITERTOOLS_HPP
|
||||
#define RGBDS_ITERTOOLS_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_ASSIGN_HPP
|
||||
#define RGBDS_LINK_ASSIGN_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_MAIN_HPP
|
||||
#define RGBDS_LINK_MAIN_HPP
|
||||
@@ -32,8 +32,9 @@ extern bool disablePadding;
|
||||
// Helper macro for printing verbose-mode messages
|
||||
#define verbosePrint(...) \
|
||||
do { \
|
||||
if (beVerbose) \
|
||||
if (beVerbose) { \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
struct FileStackNode {
|
||||
@@ -58,11 +59,11 @@ struct FileStackNode {
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
};
|
||||
|
||||
[[gnu::format(printf, 3, 4)]] void
|
||||
warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4)]] void
|
||||
error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4), noreturn]] void
|
||||
fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 3, 4), noreturn]]
|
||||
void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...);
|
||||
|
||||
#endif // RGBDS_LINK_MAIN_HPP
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_OBJECT_HPP
|
||||
#define RGBDS_LINK_OBJECT_HPP
|
||||
|
||||
/*
|
||||
* Read an object (.o) file, and add its info to the data structures.
|
||||
* @param fileName A path to the object file to be read
|
||||
* @param i The ID of the file
|
||||
*/
|
||||
void obj_ReadFile(char const *fileName, unsigned int i);
|
||||
// Read an object (.o) file, and add its info to the data structures.
|
||||
void obj_ReadFile(char const *fileName, unsigned int fileID);
|
||||
|
||||
/*
|
||||
* Sets up object file reading
|
||||
* @param nbFiles The number of object files that will be read
|
||||
*/
|
||||
// Sets up object file reading
|
||||
void obj_Setup(unsigned int nbFiles);
|
||||
|
||||
#endif // RGBDS_LINK_OBJECT_HPP
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_OUTPUT_HPP
|
||||
#define RGBDS_LINK_OUTPUT_HPP
|
||||
|
||||
struct Section;
|
||||
|
||||
/*
|
||||
* Registers a section for output.
|
||||
* @param section The section to add
|
||||
*/
|
||||
// Registers a section for output.
|
||||
void out_AddSection(Section const §ion);
|
||||
|
||||
/*
|
||||
* Finds an assigned section overlapping another one.
|
||||
* @param section The section that is being overlapped
|
||||
* @return A section overlapping it
|
||||
*/
|
||||
// Finds an assigned section overlapping another one.
|
||||
Section const *out_OverlappingSection(Section const §ion);
|
||||
|
||||
/*
|
||||
* Writes all output (bin, sym, map) files.
|
||||
*/
|
||||
// Writes all output (bin, sym, map) files.
|
||||
void out_WriteFiles();
|
||||
|
||||
#endif // RGBDS_LINK_OUTPUT_HPP
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_PATCH_HPP
|
||||
#define RGBDS_LINK_PATCH_HPP
|
||||
|
||||
/*
|
||||
* Checks all assertions
|
||||
* @return true if assertion failed
|
||||
*/
|
||||
// Checks all assertions
|
||||
void patch_CheckAssertions();
|
||||
|
||||
/*
|
||||
* Applies all SECTIONs' patches to them
|
||||
*/
|
||||
// Applies all SECTIONs' patches to them
|
||||
void patch_ApplyPatches();
|
||||
|
||||
#endif // RGBDS_LINK_PATCH_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SDAS_OBJ_HPP
|
||||
#define RGBDS_LINK_SDAS_OBJ_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SECTION_HPP
|
||||
#define RGBDS_LINK_SECTION_HPP
|
||||
@@ -65,29 +65,17 @@ struct Assertion {
|
||||
|
||||
extern std::deque<Assertion> assertions;
|
||||
|
||||
/*
|
||||
* Execute a callback for each section currently registered.
|
||||
* This is to avoid exposing the data structure in which sections are stored.
|
||||
* @param callback The function to call for each structure.
|
||||
*/
|
||||
// Execute a callback for each section currently registered.
|
||||
// This is to avoid exposing the data structure in which sections are stored.
|
||||
void sect_ForEach(void (*callback)(Section &));
|
||||
|
||||
/*
|
||||
* Registers a section to be processed.
|
||||
* @param section The section to register.
|
||||
*/
|
||||
// Registers a section to be processed.
|
||||
void sect_AddSection(std::unique_ptr<Section> &§ion);
|
||||
|
||||
/*
|
||||
* Finds a section by its name.
|
||||
* @param name The name of the section to look for
|
||||
* @return A pointer to the section, or `nullptr` if it wasn't found
|
||||
*/
|
||||
// Finds a section by its name.
|
||||
Section *sect_GetSection(std::string const &name);
|
||||
|
||||
/*
|
||||
* Checks if all sections meet reasonable criteria, such as max size
|
||||
*/
|
||||
// Checks if all sections meet reasonable criteria, such as max size
|
||||
void sect_DoSanityChecks();
|
||||
|
||||
#endif // RGBDS_LINK_SECTION_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_SYMBOL_HPP
|
||||
#define RGBDS_LINK_SYMBOL_HPP
|
||||
@@ -41,11 +41,7 @@ void sym_ForEach(void (*callback)(Symbol &));
|
||||
|
||||
void sym_AddSymbol(Symbol &symbol);
|
||||
|
||||
/*
|
||||
* Finds a symbol in all the defined symbols.
|
||||
* @param name The name of the symbol to look for
|
||||
* @return A pointer to the symbol, or `nullptr` if not found.
|
||||
*/
|
||||
// Finds a symbol in all the defined symbols.
|
||||
Symbol *sym_GetSymbol(std::string const &name);
|
||||
|
||||
void sym_DumpLocalAliasedSymbols(std::string const &name);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINKDEFS_HPP
|
||||
#define RGBDS_LINKDEFS_HPP
|
||||
@@ -92,29 +92,19 @@ extern struct SectionTypeInfo {
|
||||
uint32_t lastBank;
|
||||
} sectionTypeInfo[SECTTYPE_INVALID];
|
||||
|
||||
/*
|
||||
* Tells whether a section has data in its object file definition,
|
||||
* depending on type.
|
||||
* @param type The section's type
|
||||
* @return `true` if the section's definition includes data
|
||||
*/
|
||||
// Tells whether a section has data in its object file definition,
|
||||
// depending on type.
|
||||
static inline bool sect_HasData(SectionType type) {
|
||||
assume(type != SECTTYPE_INVALID);
|
||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes a memory region's end address (last byte), eg. 0x7FFF
|
||||
* @return The address of the last byte in that memory region
|
||||
*/
|
||||
// Returns a memory region's end address (last byte), e.g. 0x7FFF
|
||||
static inline uint16_t endaddr(SectionType type) {
|
||||
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes a memory region's number of banks
|
||||
* @return The number of banks, 1 for regions without banking
|
||||
*/
|
||||
// Returns a memory region's number of banks, or 1 for regions without banking
|
||||
static inline uint32_t nbbanks(SectionType type) {
|
||||
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_OP_MATH_HPP
|
||||
#define RGBDS_OP_MATH_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// platform-specific hacks
|
||||
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_UTIL_HPP
|
||||
#define RGBDS_UTIL_HPP
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
char const *printChar(int c);
|
||||
|
||||
/*
|
||||
* @return The number of bytes read, or 0 if invalid data was found
|
||||
*/
|
||||
size_t readUTF8Char(std::vector<int32_t> *dest, char const *src);
|
||||
|
||||
#endif // RGBDS_UTIL_HPP
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_VERSION_HPP
|
||||
#define RGBDS_VERSION_HPP
|
||||
|
||||
extern "C" {
|
||||
|
||||
#define PACKAGE_VERSION_MAJOR 0
|
||||
#define PACKAGE_VERSION_MINOR 9
|
||||
#define PACKAGE_VERSION_PATCH 0
|
||||
#define PACKAGE_VERSION_PATCH 1
|
||||
|
||||
char const *get_package_version_string();
|
||||
}
|
||||
|
||||
#endif // RGBDS_VERSION_H
|
||||
|
||||
11
man/gbz80.7
11
man/gbz80.7
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt GBZ80 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -615,9 +615,6 @@ to the adjustment.
|
||||
.It
|
||||
Subtract the adjustment from
|
||||
.Sy A .
|
||||
.It
|
||||
Set the carry flag if borrow (i.e. if adjustment >
|
||||
.Sy A ) .
|
||||
.El
|
||||
.It If the subtract flag Sy N No is not set:
|
||||
.Bl -enum -compact
|
||||
@@ -639,15 +636,13 @@ to the adjustment.
|
||||
If the carry flag is set or
|
||||
.Sy A
|
||||
>
|
||||
.Ad $9F ,
|
||||
.Ad $99 ,
|
||||
then add
|
||||
.Ad $60
|
||||
to the adjustment.
|
||||
to the adjustment and set the carry flag.
|
||||
.It
|
||||
Add the adjustment to
|
||||
.Sy A .
|
||||
.It
|
||||
Set the carry flag if overflow from bit 7.
|
||||
.El
|
||||
.El
|
||||
.Pp
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBASM-OLD 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -15,7 +15,7 @@ assembly language over its decades of evolution, along with their modern alterna
|
||||
Its goal is to be a reference for backwards incompatibility, when upgrading an old assembly codebase to work with the latest RGBDS release.
|
||||
It does
|
||||
.Em not
|
||||
attempt to list syntax bugs that were fixed, nor new reserved keywords that may conflict with old identifiers.
|
||||
attempt to list every syntax bug that was ever fixed (with some notable exceptions), nor new reserved keywords that may conflict with old identifiers.
|
||||
.Sh REMOVED
|
||||
These are features which have been completely removed, without any direct alternatives.
|
||||
Usually these features were limiting the addition of other features, or had awkward limits on their own intended effects.
|
||||
@@ -266,6 +266,14 @@ Instead, use
|
||||
.Ql LDH [C], A
|
||||
and
|
||||
.Ql LDH A, [C] .
|
||||
.Pp
|
||||
Note that
|
||||
.Ql LD [$FF00+C], A
|
||||
and
|
||||
.Ql LD A, [$FF00+C]
|
||||
were also deprecated in 0.9.0, but were
|
||||
.Em undeprecated
|
||||
in 0.9.1.
|
||||
.Ss LDH [n8], A and LDH A, [n8]
|
||||
Deprecated in 0.9.0.
|
||||
.Pp
|
||||
@@ -288,7 +296,7 @@ Deprecated in 0.3.0, removed in 0.4.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LD HL, SP + e8 .
|
||||
.Ss LDHL, SP, e8
|
||||
.Ss LDHL SP, e8
|
||||
Supported in ASMotor, removed in RGBDS.
|
||||
.Pp
|
||||
Instead, use
|
||||
@@ -360,6 +368,68 @@ Previously we had
|
||||
.Pp
|
||||
Instead, now we have
|
||||
.Ql p ** q ** r == p ** (q ** r) .
|
||||
.Sh BUGS
|
||||
These are misfeatures that may have been possible by mistake.
|
||||
They do not get deprecated, just fixed.
|
||||
.Ss Space between exported labels' colons
|
||||
Fixed in 0.7.0.
|
||||
.Pp
|
||||
Labels with two colons used to ignore a space between them; for example,
|
||||
.Ql Label:\ : .
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql Label:: .
|
||||
.Ss Space between label and colon
|
||||
Fixed in 0.9.0.
|
||||
.Pp
|
||||
Space between a label and its colon(s) used to be ignored; for example,
|
||||
.Ql Label\ :
|
||||
and
|
||||
.Ql Label\ :: .
|
||||
Now they are treated as invocations of the
|
||||
.Ql Label
|
||||
macro with
|
||||
.Ql \&:
|
||||
and
|
||||
.Ql ::
|
||||
as arguments.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql Label:
|
||||
and
|
||||
.Ql Label:: .
|
||||
.Ss ADD r16 with implicit first HL operand
|
||||
Fixed in 0.5.0.
|
||||
.Pp
|
||||
For example,
|
||||
.Ql ADD BC
|
||||
used to be treated as
|
||||
.Ql ADD HL, BC ,
|
||||
and likewise for
|
||||
.Ql DE ,
|
||||
.Ql HL ,
|
||||
and
|
||||
.Ql SP .
|
||||
.Pp
|
||||
Instead, use an explicit first
|
||||
.Ql HL
|
||||
operand.
|
||||
.Ss = instead of SET
|
||||
Fixed in 0.4.0.
|
||||
.Pp
|
||||
The
|
||||
.Ic =
|
||||
operator used to be an alias for the
|
||||
.Ic SET
|
||||
keyword, which included using
|
||||
.Ic =
|
||||
for the
|
||||
.Ic SET
|
||||
.Em instruction .
|
||||
.Pp
|
||||
Instead, just use
|
||||
.Ic SET
|
||||
for the instruction.
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 1 ,
|
||||
.Xr gbz80 7 ,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBASM 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy assembler
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl EVvw
|
||||
.Op Fl EhVvw
|
||||
.Op Fl b Ar chars
|
||||
.Op Fl D Ar name Ns Op = Ns Ar value
|
||||
.Op Fl g Ar chars
|
||||
@@ -67,6 +67,8 @@ Export all labels, including unreferenced and local labels.
|
||||
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
|
||||
Change the four characters used for gfx constants.
|
||||
The defaults are 0123.
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl I Ar path , Fl \-include Ar path
|
||||
Add a new
|
||||
.Dq include path ;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBASM 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBDS 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBDS 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -51,6 +51,10 @@ adapts the code to be more UNIX-like and releases this version as rgbds-linux.
|
||||
forks Nossum's repository.
|
||||
The fork becomes the reference implementation of RGBDS.
|
||||
.It
|
||||
2010-09-25: S\(/orensen continues development of
|
||||
.Lk https://github.com/asmotor/asmotor ASMotor
|
||||
to this day.
|
||||
.It
|
||||
2015-01-18:
|
||||
.An stag019
|
||||
begins implementing RGBGFX, a PNG‐to‐Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBFIX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy header utility and checksum fixer
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl jOsVv
|
||||
.Op Fl hjOsVv
|
||||
.Op Fl C | c
|
||||
.Op Fl f Ar fix_spec
|
||||
.Op Fl i Ar game_id
|
||||
@@ -91,6 +91,8 @@ Fix the global checksum
|
||||
.It Cm G
|
||||
Trash the global checksum.
|
||||
.El
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl i Ar game_id , Fl \-game-id Ar game_id
|
||||
Set the game ID string
|
||||
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBGFX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -10,7 +10,7 @@
|
||||
.Nd Game Boy graphics converter
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl CmOuVXYZ
|
||||
.Op Fl CmhOuVXYZ
|
||||
.Op Fl v Op Fl v No ...
|
||||
.Op Fl a Ar attrmap | Fl A
|
||||
.Op Fl b Ar base_ids
|
||||
@@ -165,6 +165,8 @@ for a list of formats and their descriptions.
|
||||
.It Fl d Ar depth , Fl \-depth Ar depth
|
||||
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
|
||||
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles
|
||||
Use the specified input tiles in addition to having
|
||||
.Nm
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBLINK 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,7 +8,7 @@
|
||||
.Nd Game Boy linker
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl dMtVvwx
|
||||
.Op Fl dhMtVvwx
|
||||
.Op Fl l Ar linker_script
|
||||
.Op Fl m Ar map_file
|
||||
.Op Fl n Ar sym_file
|
||||
@@ -67,6 +67,8 @@ Enable DMG mode.
|
||||
Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1.
|
||||
This option automatically enables
|
||||
.Fl w .
|
||||
.It Fl h , Fl \-help
|
||||
Print help text for the program and exit.
|
||||
.It Fl l Ar linker_script , Fl \-linkerscript Ar linker_script
|
||||
Specify a linker script file that tells the linker how sections must be placed in the ROM.
|
||||
The attributes assigned in the linker script must be consistent with any assigned in the code.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd December 25, 2024
|
||||
.Dd February 2, 2025
|
||||
.Dt RGBLINK 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
@@ -45,21 +46,27 @@ bool charmap_ForEach(
|
||||
for (Charmap const &charmap : charmapList) {
|
||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||
std::map<size_t, std::string> mappings;
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||
// clang-format off: nested initializers
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}});
|
||||
!prefixes.empty();) {
|
||||
// clang-format on
|
||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||
prefixes.pop();
|
||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
||||
if (node.isTerminal())
|
||||
if (node.isTerminal()) {
|
||||
mappings[nodeIdx] = mapping;
|
||||
}
|
||||
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)});
|
||||
}
|
||||
}
|
||||
}
|
||||
mapFunc(charmap.name);
|
||||
for (auto [nodeIdx, mapping] : mappings)
|
||||
for (auto [nodeIdx, mapping] : mappings) {
|
||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||
}
|
||||
}
|
||||
return !charmapList.empty();
|
||||
}
|
||||
|
||||
@@ -67,11 +74,12 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
size_t baseIdx = SIZE_MAX;
|
||||
|
||||
if (baseName != nullptr) {
|
||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end())
|
||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end()) {
|
||||
error("Base charmap '%s' doesn't exist\n", baseName->c_str());
|
||||
else
|
||||
} else {
|
||||
baseIdx = search->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (charmapMap.find(name) != charmapMap.end()) {
|
||||
error("Charmap '%s' already exists\n", name.c_str());
|
||||
@@ -82,10 +90,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
charmapMap[name] = charmapList.size();
|
||||
Charmap &charmap = charmapList.emplace_back();
|
||||
|
||||
if (baseIdx != SIZE_MAX)
|
||||
if (baseIdx != SIZE_MAX) {
|
||||
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
|
||||
else
|
||||
} else {
|
||||
charmap.nodes.emplace_back(); // Zero-init the root node
|
||||
}
|
||||
|
||||
charmap.name = name;
|
||||
|
||||
@@ -93,10 +102,11 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
}
|
||||
|
||||
void charmap_Set(std::string const &name) {
|
||||
if (auto search = charmapMap.find(name); search == charmapMap.end())
|
||||
if (auto search = charmapMap.find(name); search == charmapMap.end()) {
|
||||
error("Charmap '%s' doesn't exist\n", name.c_str());
|
||||
else
|
||||
} else {
|
||||
currentCharmap = &charmapList[search->second];
|
||||
}
|
||||
}
|
||||
|
||||
void charmap_Push() {
|
||||
@@ -146,8 +156,9 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
|
||||
CharmapNode &node = charmap.nodes[nodeIdx];
|
||||
|
||||
if (node.isTerminal())
|
||||
if (node.isTerminal()) {
|
||||
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
||||
}
|
||||
|
||||
std::swap(node.value, value);
|
||||
}
|
||||
@@ -159,17 +170,17 @@ bool charmap_HasChar(std::string const &input) {
|
||||
for (char c : input) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
|
||||
if (!nodeIdx)
|
||||
if (!nodeIdx) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return charmap.nodes[nodeIdx].isTerminal();
|
||||
}
|
||||
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
||||
std::vector<int32_t> output;
|
||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);)
|
||||
;
|
||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);) {}
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -186,8 +197,9 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
for (size_t nodeIdx = 0; inputIdx < input.length();) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(input[inputIdx])];
|
||||
|
||||
if (!nodeIdx)
|
||||
if (!nodeIdx) {
|
||||
break;
|
||||
}
|
||||
|
||||
inputIdx++; // Consume that char
|
||||
|
||||
@@ -207,27 +219,42 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
if (matchIdx) { // A match was found, use it
|
||||
std::vector<int32_t> const &value = charmap.nodes[matchIdx].value;
|
||||
|
||||
if (output)
|
||||
if (output) {
|
||||
output->insert(output->end(), RANGE(value));
|
||||
}
|
||||
|
||||
matchLen = value.size();
|
||||
} else if (inputIdx < input.length()) { // No match found, but there is some input left
|
||||
int firstChar = input[inputIdx];
|
||||
size_t codepointLen = 0;
|
||||
// This will write the codepoint's value to `output`, little-endian
|
||||
size_t codepointLen = readUTF8Char(output, input.data() + inputIdx);
|
||||
|
||||
if (codepointLen == 0)
|
||||
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 (output) {
|
||||
output->insert(
|
||||
output->end(), input.data() + inputIdx, input.data() + inputIdx + codepointLen
|
||||
);
|
||||
}
|
||||
|
||||
// Warn if this character is not mapped but any others are
|
||||
if (charmap.nodes.size() > 1)
|
||||
if (int firstChar = input[inputIdx]; charmap.nodes.size() > 1) {
|
||||
warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n", printChar(firstChar));
|
||||
else if (charmap.name != DEFAULT_CHARMAP_NAME)
|
||||
} else if (charmap.name != DEFAULT_CHARMAP_NAME) {
|
||||
warning(
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
|
||||
printChar(firstChar)
|
||||
);
|
||||
}
|
||||
|
||||
inputIdx += codepointLen;
|
||||
matchLen = codepointLen;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Fixed-point math routines
|
||||
|
||||
@@ -16,19 +16,17 @@ uint8_t fix_Precision() {
|
||||
return fixPrecision;
|
||||
}
|
||||
|
||||
double fix_PrecisionFactor() {
|
||||
return pow(2.0, fixPrecision);
|
||||
}
|
||||
|
||||
static double fix2double(int32_t i, int32_t q) {
|
||||
return i / pow(2.0, q);
|
||||
}
|
||||
|
||||
static int32_t double2fix(double d, int32_t q) {
|
||||
if (isnan(d))
|
||||
if (isnan(d)) {
|
||||
return 0;
|
||||
if (isinf(d))
|
||||
}
|
||||
if (isinf(d)) {
|
||||
return d < 0 ? INT32_MIN : INT32_MAX;
|
||||
}
|
||||
return static_cast<int32_t>(round(d * pow(2.0, q)));
|
||||
}
|
||||
|
||||
@@ -73,7 +71,12 @@ int32_t fix_Mul(int32_t i, int32_t j, int32_t q) {
|
||||
}
|
||||
|
||||
int32_t fix_Div(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(fix2double(i, q) / fix2double(j, q), q);
|
||||
double dividend = fix2double(i, q);
|
||||
double divisor = fix2double(j, q);
|
||||
if (fpclassify(divisor) == FP_ZERO) {
|
||||
return dividend < 0 ? INT32_MIN : dividend > 0 ? INT32_MAX : 0;
|
||||
}
|
||||
return double2fix(dividend / divisor, q);
|
||||
}
|
||||
|
||||
int32_t fix_Mod(int32_t i, int32_t j, int32_t q) {
|
||||
@@ -85,7 +88,11 @@ int32_t fix_Pow(int32_t i, int32_t j, int32_t q) {
|
||||
}
|
||||
|
||||
int32_t fix_Log(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(log(fix2double(i, q)) / log(fix2double(j, q)), q);
|
||||
double divisor = log(fix2double(j, q));
|
||||
if (fpclassify(divisor) == FP_ZERO) {
|
||||
return INT32_MAX;
|
||||
}
|
||||
return double2fix(log(fix2double(i, q)) / divisor, q);
|
||||
}
|
||||
|
||||
int32_t fix_Round(int32_t i, int32_t q) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/format.hpp"
|
||||
|
||||
@@ -13,39 +13,44 @@
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
void FormatSpec::useCharacter(int c) {
|
||||
if (state == FORMAT_INVALID)
|
||||
if (state == FORMAT_INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
// sign
|
||||
case ' ':
|
||||
case '+':
|
||||
if (state > FORMAT_SIGN)
|
||||
if (state > FORMAT_SIGN) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_EXACT;
|
||||
sign = c;
|
||||
break;
|
||||
|
||||
// exact
|
||||
case '#':
|
||||
if (state > FORMAT_EXACT)
|
||||
if (state > FORMAT_EXACT) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_ALIGN;
|
||||
exact = true;
|
||||
break;
|
||||
|
||||
// align
|
||||
case '-':
|
||||
if (state > FORMAT_ALIGN)
|
||||
if (state > FORMAT_ALIGN) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_WIDTH;
|
||||
alignLeft = true;
|
||||
break;
|
||||
|
||||
// pad, width, and prec values
|
||||
case '0':
|
||||
if (state < FORMAT_WIDTH)
|
||||
if (state < FORMAT_WIDTH) {
|
||||
padZero = true;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case '1':
|
||||
case '2':
|
||||
@@ -72,16 +77,18 @@ void FormatSpec::useCharacter(int c) {
|
||||
|
||||
// width
|
||||
case '.':
|
||||
if (state > FORMAT_WIDTH)
|
||||
if (state > FORMAT_WIDTH) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_FRAC;
|
||||
hasFrac = true;
|
||||
break;
|
||||
|
||||
// prec
|
||||
case 'q':
|
||||
if (state > FORMAT_PREC)
|
||||
if (state > FORMAT_PREC) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_PREC;
|
||||
hasPrec = true;
|
||||
break;
|
||||
@@ -95,8 +102,9 @@ void FormatSpec::useCharacter(int c) {
|
||||
case 'o':
|
||||
case 'f':
|
||||
case 's':
|
||||
if (state >= FORMAT_DONE)
|
||||
if (state >= FORMAT_DONE) {
|
||||
goto invalid;
|
||||
}
|
||||
state = FORMAT_DONE;
|
||||
valid = true;
|
||||
type = c;
|
||||
@@ -110,8 +118,9 @@ invalid:
|
||||
}
|
||||
|
||||
void FormatSpec::finishCharacters() {
|
||||
if (!isValid())
|
||||
if (!isValid()) {
|
||||
state = FORMAT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
if (sign)
|
||||
if (sign) {
|
||||
error("Formatting string with sign flag '%c'\n", sign);
|
||||
if (padZero)
|
||||
}
|
||||
if (padZero) {
|
||||
error("Formatting string with padding flag '0'\n");
|
||||
if (hasFrac)
|
||||
}
|
||||
if (hasFrac) {
|
||||
error("Formatting string with fractional width\n");
|
||||
if (hasPrec)
|
||||
}
|
||||
if (hasPrec) {
|
||||
error("Formatting string with fractional precision\n");
|
||||
if (useType != 's')
|
||||
}
|
||||
if (useType != 's') {
|
||||
error("Formatting string as type '%c'\n", useType);
|
||||
}
|
||||
|
||||
std::string useValue = exact ? escapeString(value) : value;
|
||||
size_t valueLen = useValue.length();
|
||||
@@ -187,24 +201,29 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
}
|
||||
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
|
||||
&& useExact)
|
||||
&& useExact) {
|
||||
error("Formatting type '%c' with exact flag '#'\n", useType);
|
||||
if (useType != 'f' && hasFrac)
|
||||
}
|
||||
if (useType != 'f' && hasFrac) {
|
||||
error("Formatting type '%c' with fractional width\n", useType);
|
||||
if (useType != 'f' && hasPrec)
|
||||
}
|
||||
if (useType != 'f' && hasPrec) {
|
||||
error("Formatting type '%c' with fractional precision\n", useType);
|
||||
if (useType == 's')
|
||||
}
|
||||
if (useType == 's') {
|
||||
error("Formatting number as type 's'\n");
|
||||
}
|
||||
|
||||
char signChar = sign; // 0 or ' ' or '+'
|
||||
|
||||
if (useType == 'd' || useType == 'f') {
|
||||
if (int32_t v = value; v < 0) {
|
||||
signChar = '-';
|
||||
if (v != INT32_MIN)
|
||||
if (v != INT32_MIN) {
|
||||
value = -v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char prefixChar = !useExact ? 0
|
||||
: useType == 'X' ? '$'
|
||||
@@ -250,10 +269,11 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
}
|
||||
|
||||
double fval = fabs(value / pow(2.0, usePrec));
|
||||
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact)
|
||||
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
||||
else
|
||||
} else {
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
||||
}
|
||||
} else if (useType == 'd') {
|
||||
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
|
||||
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
|
||||
@@ -278,28 +298,34 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
|
||||
str.reserve(str.length() + totalLen);
|
||||
if (alignLeft) {
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
str.append(valueBuf);
|
||||
str.append(padLen, ' ');
|
||||
} else {
|
||||
if (padZero) {
|
||||
// sign, then prefix, then zero padding
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
str.append(padLen, '0');
|
||||
} else {
|
||||
// space padding, then sign, then prefix
|
||||
str.append(padLen, ' ');
|
||||
if (signChar)
|
||||
if (signChar) {
|
||||
str += signChar;
|
||||
if (prefixChar)
|
||||
}
|
||||
if (prefixChar) {
|
||||
str += prefixChar;
|
||||
}
|
||||
}
|
||||
str.append(valueBuf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
#include <sys/stat.h>
|
||||
@@ -90,8 +90,9 @@ std::shared_ptr<std::string> fstk_GetUniqueIDStr() {
|
||||
std::shared_ptr<std::string> &str = contextStack.top().uniqueIDStr;
|
||||
|
||||
// If a unique ID is allowed but has not been generated yet, generate one now.
|
||||
if (str && str->empty())
|
||||
if (str && str->empty()) {
|
||||
*str = "_u"s + std::to_string(nextUniqueID++);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
@@ -103,28 +104,33 @@ MacroArgs *fstk_GetCurrentMacroArgs() {
|
||||
}
|
||||
|
||||
void fstk_AddIncludePath(std::string const &path) {
|
||||
if (path.empty())
|
||||
if (path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string &includePath = includePaths.emplace_back(path);
|
||||
if (includePath.back() != '/')
|
||||
if (includePath.back() != '/') {
|
||||
includePath += '/';
|
||||
}
|
||||
}
|
||||
|
||||
void fstk_SetPreIncludeFile(std::string const &path) {
|
||||
if (!preIncludeName.empty())
|
||||
if (!preIncludeName.empty()) {
|
||||
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
||||
}
|
||||
preIncludeName = path;
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void printDep(std::string const &path) {
|
||||
if (dependFile) {
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
||||
if (generatePhonyDeps)
|
||||
if (generatePhonyDeps) {
|
||||
fprintf(dependFile, "%s:\n", path.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool isValidFilePath(std::string const &path) {
|
||||
@@ -141,20 +147,22 @@ std::optional<std::string> fstk_FindFile(std::string const &path) {
|
||||
}
|
||||
|
||||
errno = ENOENT;
|
||||
if (generatedMissingIncludes)
|
||||
if (generatedMissingIncludes) {
|
||||
printDep(path);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool yywrap() {
|
||||
uint32_t ifDepth = lexer_GetIFDepth();
|
||||
|
||||
if (ifDepth != 0)
|
||||
if (ifDepth != 0) {
|
||||
fatalerror(
|
||||
"Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||
ifDepth,
|
||||
ifDepth == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
|
||||
if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) {
|
||||
// The context is a REPT or FOR block, which may loop
|
||||
@@ -177,9 +185,10 @@ bool yywrap() {
|
||||
Symbol *sym = sym_AddVar(context.forName, context.forValue);
|
||||
|
||||
// This error message will refer to the current iteration
|
||||
if (sym->type != SYM_VAR)
|
||||
if (sym->type != SYM_VAR) {
|
||||
fatalerror("Failed to update FOR symbol value\n");
|
||||
}
|
||||
}
|
||||
// Advance to the next iteration
|
||||
fileInfoIters.front()++;
|
||||
// If this wasn't the last iteration, wrap instead of popping
|
||||
@@ -199,8 +208,9 @@ bool yywrap() {
|
||||
}
|
||||
|
||||
static void checkRecursionDepth() {
|
||||
if (contextStack.size() > maxRecursionDepth)
|
||||
if (contextStack.size() > maxRecursionDepth) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||
}
|
||||
}
|
||||
|
||||
static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
@@ -210,7 +220,7 @@ static bool newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
std::shared_ptr<MacroArgs> macroArgs = nullptr;
|
||||
|
||||
auto fileInfo =
|
||||
std::make_shared<FileStackNode>(NODE_MACRO, filePath == "-" ? "<stdin>" : filePath);
|
||||
std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath);
|
||||
if (!contextStack.empty()) {
|
||||
Context &oldContext = contextStack.top();
|
||||
fileInfo->parent = oldContext.fileInfo;
|
||||
@@ -298,8 +308,9 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
||||
|
||||
if (!fullPath) {
|
||||
if (generatedMissingIncludes && !preInclude) {
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
||||
}
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
if (!newFileContext(*fullPath, false))
|
||||
if (!newFileContext(*fullPath, false)) {
|
||||
fatalerror("Failed to set up lexer for file include\n");
|
||||
}
|
||||
}
|
||||
|
||||
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs) {
|
||||
Symbol *macro = sym_FindExactSymbol(macroName);
|
||||
|
||||
if (!macro) {
|
||||
if (sym_IsPurgedExact(macroName))
|
||||
if (sym_IsPurgedExact(macroName)) {
|
||||
error("Macro \"%s\" not defined; it was purged\n", macroName.c_str());
|
||||
else
|
||||
} else {
|
||||
error("Macro \"%s\" not defined\n", macroName.c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (macro->type != SYM_MACRO) {
|
||||
@@ -330,8 +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) {
|
||||
if (count == 0)
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
newReptContext(reptLineNo, span, count);
|
||||
}
|
||||
@@ -344,24 +358,28 @@ void fstk_RunFor(
|
||||
int32_t reptLineNo,
|
||||
ContentSpan const &span
|
||||
) {
|
||||
if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR)
|
||||
if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t count = 0;
|
||||
if (step > 0 && start < stop)
|
||||
if (step > 0 && start < stop) {
|
||||
count = (static_cast<int64_t>(stop) - start - 1) / step + 1;
|
||||
else if (step < 0 && stop < start)
|
||||
} else if (step < 0 && stop < start) {
|
||||
count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1;
|
||||
else if (step == 0)
|
||||
} else if (step == 0) {
|
||||
error("FOR cannot have a step value of 0\n");
|
||||
}
|
||||
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop))
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop)) {
|
||||
warning(
|
||||
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
|
||||
);
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context &context = newReptContext(reptLineNo, span, count);
|
||||
context.isForLoop = true;
|
||||
@@ -385,17 +403,20 @@ bool fstk_Break() {
|
||||
}
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth) {
|
||||
if (contextStack.size() > newDepth + 1)
|
||||
if (contextStack.size() > newDepth + 1) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||
}
|
||||
maxRecursionDepth = newDepth;
|
||||
}
|
||||
|
||||
void fstk_Init(std::string const &mainPath, size_t maxDepth) {
|
||||
if (!newFileContext(mainPath, true))
|
||||
if (!newFileContext(mainPath, true)) {
|
||||
fatalerror("Failed to open main file\n");
|
||||
}
|
||||
|
||||
maxRecursionDepth = maxDepth;
|
||||
|
||||
if (!preIncludeName.empty())
|
||||
if (!preIncludeName.empty()) {
|
||||
fstk_RunInclude(preIncludeName, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/lexer.hpp"
|
||||
#include <sys/stat.h>
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "helpers.hpp" // assume, QUOTEDSTRLEN
|
||||
#include "helpers.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include "asm/fixpoint.hpp"
|
||||
@@ -35,15 +35,14 @@
|
||||
|
||||
// Neither MSVC nor MinGW provide `mmap`
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
// clang-format off
|
||||
// (we need these `include`s in this order)
|
||||
#define WIN32_LEAN_AND_MEAN // include less from windows.h
|
||||
// clang-format off: maintain `include` order
|
||||
#define WIN32_LEAN_AND_MEAN // Include less from `windows.h`
|
||||
#include <windows.h> // target architecture
|
||||
#include <fileapi.h> // CreateFileA
|
||||
#include <winbase.h> // CreateFileMappingA
|
||||
#include <memoryapi.h> // MapViewOfFile
|
||||
#include <handleapi.h> // CloseHandle
|
||||
// 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) {
|
||||
void *mappingAddr = nullptr;
|
||||
@@ -64,7 +63,7 @@ static char *mapFile(int fd, std::string const &path, size_t) {
|
||||
}
|
||||
CloseHandle(file);
|
||||
}
|
||||
return (char *)mappingAddr;
|
||||
return static_cast<char *>(mappingAddr);
|
||||
}
|
||||
|
||||
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
|
||||
// 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.
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("mmap(%s, MAP_PRIVATE) failed, retrying with MAP_SHARED\n", path.c_str());
|
||||
}
|
||||
mappingAddr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
}
|
||||
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 hash = 0x811C9DC5;
|
||||
|
||||
for (char const &c : str)
|
||||
for (char const &c : str) {
|
||||
hash = (hash ^ toupper(c)) * 16777619;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
@@ -139,53 +140,53 @@ struct CaseInsensitive {
|
||||
// Tokens / keywords not handled here are handled in `yylex_NORMAL`'s switch.
|
||||
// This assumes that no two keywords have the same name.
|
||||
static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> keywordDict = {
|
||||
{"ADC", T_(Z80_ADC) },
|
||||
{"ADD", T_(Z80_ADD) },
|
||||
{"AND", T_(Z80_AND) },
|
||||
{"BIT", T_(Z80_BIT) },
|
||||
{"CALL", T_(Z80_CALL) },
|
||||
{"CCF", T_(Z80_CCF) },
|
||||
{"CPL", T_(Z80_CPL) },
|
||||
{"CP", T_(Z80_CP) },
|
||||
{"DAA", T_(Z80_DAA) },
|
||||
{"DEC", T_(Z80_DEC) },
|
||||
{"DI", T_(Z80_DI) },
|
||||
{"EI", T_(Z80_EI) },
|
||||
{"HALT", T_(Z80_HALT) },
|
||||
{"INC", T_(Z80_INC) },
|
||||
{"JP", T_(Z80_JP) },
|
||||
{"JR", T_(Z80_JR) },
|
||||
{"LD", T_(Z80_LD) },
|
||||
{"LDI", T_(Z80_LDI) },
|
||||
{"LDD", T_(Z80_LDD) },
|
||||
{"LDIO", T_(Z80_LDH) },
|
||||
{"LDH", T_(Z80_LDH) },
|
||||
{"NOP", T_(Z80_NOP) },
|
||||
{"OR", T_(Z80_OR) },
|
||||
{"POP", T_(Z80_POP) },
|
||||
{"PUSH", T_(Z80_PUSH) },
|
||||
{"RES", T_(Z80_RES) },
|
||||
{"RETI", T_(Z80_RETI) },
|
||||
{"RET", T_(Z80_RET) },
|
||||
{"RLCA", T_(Z80_RLCA) },
|
||||
{"RLC", T_(Z80_RLC) },
|
||||
{"RLA", T_(Z80_RLA) },
|
||||
{"RL", T_(Z80_RL) },
|
||||
{"RRC", T_(Z80_RRC) },
|
||||
{"RRCA", T_(Z80_RRCA) },
|
||||
{"RRA", T_(Z80_RRA) },
|
||||
{"RR", T_(Z80_RR) },
|
||||
{"RST", T_(Z80_RST) },
|
||||
{"SBC", T_(Z80_SBC) },
|
||||
{"SCF", T_(Z80_SCF) },
|
||||
{"SET", T_(Z80_SET) },
|
||||
{"SLA", T_(Z80_SLA) },
|
||||
{"SRA", T_(Z80_SRA) },
|
||||
{"SRL", T_(Z80_SRL) },
|
||||
{"STOP", T_(Z80_STOP) },
|
||||
{"SUB", T_(Z80_SUB) },
|
||||
{"SWAP", T_(Z80_SWAP) },
|
||||
{"XOR", T_(Z80_XOR) },
|
||||
{"ADC", T_(SM83_ADC) },
|
||||
{"ADD", T_(SM83_ADD) },
|
||||
{"AND", T_(SM83_AND) },
|
||||
{"BIT", T_(SM83_BIT) },
|
||||
{"CALL", T_(SM83_CALL) },
|
||||
{"CCF", T_(SM83_CCF) },
|
||||
{"CPL", T_(SM83_CPL) },
|
||||
{"CP", T_(SM83_CP) },
|
||||
{"DAA", T_(SM83_DAA) },
|
||||
{"DEC", T_(SM83_DEC) },
|
||||
{"DI", T_(SM83_DI) },
|
||||
{"EI", T_(SM83_EI) },
|
||||
{"HALT", T_(SM83_HALT) },
|
||||
{"INC", T_(SM83_INC) },
|
||||
{"JP", T_(SM83_JP) },
|
||||
{"JR", T_(SM83_JR) },
|
||||
{"LD", T_(SM83_LD) },
|
||||
{"LDI", T_(SM83_LDI) },
|
||||
{"LDD", T_(SM83_LDD) },
|
||||
{"LDIO", T_(SM83_LDH) },
|
||||
{"LDH", T_(SM83_LDH) },
|
||||
{"NOP", T_(SM83_NOP) },
|
||||
{"OR", T_(SM83_OR) },
|
||||
{"POP", T_(SM83_POP) },
|
||||
{"PUSH", T_(SM83_PUSH) },
|
||||
{"RES", T_(SM83_RES) },
|
||||
{"RETI", T_(SM83_RETI) },
|
||||
{"RET", T_(SM83_RET) },
|
||||
{"RLCA", T_(SM83_RLCA) },
|
||||
{"RLC", T_(SM83_RLC) },
|
||||
{"RLA", T_(SM83_RLA) },
|
||||
{"RL", T_(SM83_RL) },
|
||||
{"RRC", T_(SM83_RRC) },
|
||||
{"RRCA", T_(SM83_RRCA) },
|
||||
{"RRA", T_(SM83_RRA) },
|
||||
{"RR", T_(SM83_RR) },
|
||||
{"RST", T_(SM83_RST) },
|
||||
{"SBC", T_(SM83_SBC) },
|
||||
{"SCF", T_(SM83_SCF) },
|
||||
{"SET", T_(SM83_SET) },
|
||||
{"SLA", T_(SM83_SLA) },
|
||||
{"SRA", T_(SM83_SRA) },
|
||||
{"SRL", T_(SM83_SRL) },
|
||||
{"STOP", T_(SM83_STOP) },
|
||||
{"SUB", T_(SM83_SUB) },
|
||||
{"SWAP", T_(SM83_SWAP) },
|
||||
{"XOR", T_(SM83_XOR) },
|
||||
|
||||
{"NZ", T_(CC_NZ) },
|
||||
{"Z", T_(CC_Z) },
|
||||
@@ -315,7 +316,7 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
|
||||
|
||||
{"RB", T_(POP_RB) },
|
||||
{"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) },
|
||||
{"EQUS", T_(POP_EQUS) },
|
||||
@@ -344,7 +345,7 @@ bool lexer_AtTopLevel() {
|
||||
|
||||
void LexerState::clear(uint32_t lineNo_) {
|
||||
mode = LEXER_NORMAL;
|
||||
atLineStart = true; // yylex() will init colNo due to this
|
||||
atLineStart = true;
|
||||
lastToken = T_(YYEOF);
|
||||
|
||||
ifStack.clear();
|
||||
@@ -364,7 +365,6 @@ void LexerState::clear(uint32_t lineNo_) {
|
||||
|
||||
static void nextLine() {
|
||||
lexerState->lineNo++;
|
||||
lexerState->colNo = 1;
|
||||
}
|
||||
|
||||
uint32_t lexer_GetIFDepth() {
|
||||
@@ -376,8 +376,9 @@ void lexer_IncIFDepth() {
|
||||
}
|
||||
|
||||
void lexer_DecIFDepth() {
|
||||
if (lexerState->ifStack.empty())
|
||||
if (lexerState->ifStack.empty()) {
|
||||
fatalerror("Found ENDC outside of an IF construct\n");
|
||||
}
|
||||
|
||||
lexerState->ifStack.pop_front();
|
||||
}
|
||||
@@ -406,8 +407,9 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
||||
if (filePath == "-") {
|
||||
path = "<stdin>";
|
||||
content.emplace<BufferedContent>(STDIN_FILENO);
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Opening stdin\n");
|
||||
}
|
||||
} else {
|
||||
struct stat statBuf;
|
||||
if (stat(filePath.c_str(), &statBuf) != 0) {
|
||||
@@ -431,8 +433,9 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
||||
content.emplace<ViewedContent>(
|
||||
std::shared_ptr<char[]>(mappingAddr, FileUnmapDeleter(size)), size
|
||||
);
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("File \"%s\" is mmap()ped\n", path.c_str());
|
||||
}
|
||||
isMmapped = true;
|
||||
}
|
||||
}
|
||||
@@ -453,10 +456,11 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
||||
}
|
||||
|
||||
clear(0);
|
||||
if (updateStateNow)
|
||||
if (updateStateNow) {
|
||||
lexerState = this;
|
||||
else
|
||||
} else {
|
||||
lexerStateEOL = this;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -500,45 +504,51 @@ BufferedContent::~BufferedContent() {
|
||||
}
|
||||
|
||||
void BufferedContent::advance() {
|
||||
assume(offset < LEXER_BUF_SIZE);
|
||||
assume(offset < std::size(buf));
|
||||
offset++;
|
||||
if (offset == LEXER_BUF_SIZE)
|
||||
if (offset == std::size(buf)) {
|
||||
offset = 0; // Wrap around if necessary
|
||||
assume(size > 0);
|
||||
}
|
||||
if (size > 0) {
|
||||
size--;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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 (startIndex + target > LEXER_BUF_SIZE) {
|
||||
size_t nbExpectedChars = LEXER_BUF_SIZE - startIndex;
|
||||
if (startIndex + target > std::size(buf)) {
|
||||
size_t nbExpectedChars = std::size(buf) - startIndex;
|
||||
size_t nbReadChars = readMore(startIndex, nbExpectedChars);
|
||||
|
||||
startIndex += nbReadChars;
|
||||
if (startIndex == LEXER_BUF_SIZE)
|
||||
if (startIndex == std::size(buf)) {
|
||||
startIndex = 0;
|
||||
}
|
||||
|
||||
// If the read was incomplete, don't perform a second read
|
||||
target -= nbReadChars;
|
||||
if (nbReadChars < nbExpectedChars)
|
||||
if (nbReadChars < nbExpectedChars) {
|
||||
target = 0;
|
||||
}
|
||||
if (target != 0)
|
||||
}
|
||||
if (target != 0) {
|
||||
readMore(startIndex, target);
|
||||
}
|
||||
}
|
||||
|
||||
size_t BufferedContent::readMore(size_t startIndex, size_t nbChars) {
|
||||
// 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);
|
||||
|
||||
if (nbReadChars == -1)
|
||||
if (nbReadChars == -1) {
|
||||
fatalerror("Error while reading \"%s\": %s\n", lexerState->path.c_str(), strerror(errno));
|
||||
}
|
||||
|
||||
size += nbReadChars;
|
||||
|
||||
@@ -557,19 +567,22 @@ void lexer_ToggleStringExpansion(bool enable) {
|
||||
// Functions for the actual lexer to obtain characters
|
||||
|
||||
static void beginExpansion(std::shared_ptr<std::string> str, std::optional<std::string> name) {
|
||||
if (name)
|
||||
if (name) {
|
||||
lexer_CheckRecursionDepth();
|
||||
}
|
||||
|
||||
// Do not expand empty strings
|
||||
if (str->empty())
|
||||
if (str->empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
lexerState->expansions.push_front({.name = name, .contents = str, .offset = 0});
|
||||
}
|
||||
|
||||
void lexer_CheckRecursionDepth() {
|
||||
if (lexerState->expansions.size() > maxRecursionDepth + 1)
|
||||
if (lexerState->expansions.size() > maxRecursionDepth + 1) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||
}
|
||||
}
|
||||
|
||||
static bool isMacroChar(char c) {
|
||||
@@ -620,10 +633,11 @@ static uint32_t readBracketedMacroArgNum() {
|
||||
Symbol const *sym = sym_FindScopedValidSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
if (sym_IsPurgedScoped(symName))
|
||||
if (sym_IsPurgedScoped(symName)) {
|
||||
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());
|
||||
}
|
||||
num = 0;
|
||||
symbolError = true;
|
||||
} else if (!sym->isNumeric()) {
|
||||
@@ -708,22 +722,26 @@ static std::shared_ptr<std::string> readMacroArg(char name) {
|
||||
int LexerState::peekChar() {
|
||||
// This is `.peekCharAhead()` modified for zero lookahead distance
|
||||
for (Expansion &exp : expansions) {
|
||||
if (exp.offset < exp.size())
|
||||
if (exp.offset < exp.size()) {
|
||||
return static_cast<uint8_t>((*exp.contents)[exp.offset]);
|
||||
}
|
||||
}
|
||||
|
||||
if (content.holds<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]);
|
||||
}
|
||||
} else {
|
||||
auto &cbuf = content.get<BufferedContent>();
|
||||
if (cbuf.size == 0)
|
||||
if (cbuf.size == 0) {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
// If there aren't enough chars, give up
|
||||
return EOF;
|
||||
@@ -737,22 +755,26 @@ int LexerState::peekCharAhead() {
|
||||
// An expansion that has reached its end will have `exp.offset` == `exp.size()`,
|
||||
// and `.peekCharAhead()` will continue with its parent
|
||||
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]);
|
||||
}
|
||||
distance -= exp.size() - exp.offset;
|
||||
}
|
||||
|
||||
if (content.holds<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]);
|
||||
}
|
||||
} else {
|
||||
auto &cbuf = content.get<BufferedContent>();
|
||||
assume(distance < LEXER_BUF_SIZE);
|
||||
if (cbuf.size <= distance)
|
||||
assume(distance < std::size(cbuf.buf));
|
||||
if (cbuf.size <= distance) {
|
||||
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
|
||||
@@ -766,8 +788,9 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth);
|
||||
static int peek() {
|
||||
int c = lexerState->peekChar();
|
||||
|
||||
if (lexerState->macroArgScanDistance > 0)
|
||||
if (lexerState->macroArgScanDistance > 0) {
|
||||
return c;
|
||||
}
|
||||
|
||||
lexerState->macroArgScanDistance++; // Do not consider again
|
||||
|
||||
@@ -810,8 +833,9 @@ static int peek() {
|
||||
|
||||
static void shiftChar() {
|
||||
if (lexerState->capturing) {
|
||||
if (lexerState->captureBuf)
|
||||
if (lexerState->captureBuf) {
|
||||
lexerState->captureBuf->push_back(peek());
|
||||
}
|
||||
lexerState->captureSize++;
|
||||
}
|
||||
|
||||
@@ -828,7 +852,6 @@ restart:
|
||||
}
|
||||
} else {
|
||||
// Advance within the file contents
|
||||
lexerState->colNo++;
|
||||
if (lexerState->content.holds<ViewedContent>()) {
|
||||
lexerState->content.get<ViewedContent>().offset++;
|
||||
} else {
|
||||
@@ -841,14 +864,16 @@ static int nextChar() {
|
||||
int c = peek();
|
||||
|
||||
// If not at EOF, advance read position
|
||||
if (c != EOF)
|
||||
if (c != EOF) {
|
||||
shiftChar();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static void handleCRLF(int c) {
|
||||
if (c == '\r' && peek() == '\n')
|
||||
if (c == '\r' && peek() == '\n') {
|
||||
shiftChar();
|
||||
}
|
||||
}
|
||||
|
||||
static auto scopedDisableExpansions() {
|
||||
@@ -866,19 +891,17 @@ uint32_t lexer_GetLineNo() {
|
||||
return lexerState->lineNo;
|
||||
}
|
||||
|
||||
uint32_t lexer_GetColNo() {
|
||||
return lexerState->colNo;
|
||||
}
|
||||
|
||||
void lexer_DumpStringExpansions() {
|
||||
if (!lexerState)
|
||||
if (!lexerState) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Expansion &exp : lexerState->expansions) {
|
||||
// Only register EQUS expansions, not string args
|
||||
if (exp.name)
|
||||
if (exp.name) {
|
||||
fprintf(stderr, "while expanding symbol \"%s\"\n", exp.name->c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Functions to discard non-tokenized characters
|
||||
@@ -893,12 +916,12 @@ static void discardBlockComment() {
|
||||
error("Unterminated block comment\n");
|
||||
return;
|
||||
case '\r':
|
||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
||||
handleCRLF(c);
|
||||
[[fallthrough]];
|
||||
case '\n':
|
||||
if (lexerState->expansions.empty())
|
||||
if (lexerState->expansions.empty()) {
|
||||
nextLine();
|
||||
}
|
||||
continue;
|
||||
case '/':
|
||||
if (peek() == '*') {
|
||||
@@ -922,9 +945,10 @@ static void discardComment() {
|
||||
for (;; shiftChar()) {
|
||||
int c = peek();
|
||||
|
||||
if (c == EOF || c == '\r' || c == '\n')
|
||||
if (c == EOF || c == '\r' || c == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void discardLineContinuation() {
|
||||
@@ -935,10 +959,10 @@ static void discardLineContinuation() {
|
||||
shiftChar();
|
||||
} else if (c == '\r' || c == '\n') {
|
||||
shiftChar();
|
||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
||||
handleCRLF(c);
|
||||
if (lexerState->expansions.empty())
|
||||
if (lexerState->expansions.empty()) {
|
||||
nextLine();
|
||||
}
|
||||
break;
|
||||
} else if (c == ';') {
|
||||
discardComment();
|
||||
@@ -969,12 +993,14 @@ static uint32_t readNumber(int radix, uint32_t baseValue) {
|
||||
for (;; shiftChar()) {
|
||||
int c = peek();
|
||||
|
||||
if (c == '_')
|
||||
if (c == '_') {
|
||||
continue;
|
||||
else if (c < '0' || c > '0' + radix - 1)
|
||||
} else if (c < '0' || c > '0' + radix - 1) {
|
||||
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");
|
||||
}
|
||||
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");
|
||||
// Discard any additional digits
|
||||
shiftChar();
|
||||
while (c = peek(), (c >= '0' && c <= '9') || c == '_')
|
||||
while (c = peek(), (c >= '0' && c <= '9') || c == '_') {
|
||||
shiftChar();
|
||||
}
|
||||
break;
|
||||
}
|
||||
value = value * 10 + (c - '0');
|
||||
@@ -1024,16 +1051,18 @@ static uint32_t readFractionalPart(uint32_t integer) {
|
||||
}
|
||||
|
||||
if (precision == 0) {
|
||||
if (state >= READFRACTIONALPART_PRECISION)
|
||||
if (state >= READFRACTIONALPART_PRECISION) {
|
||||
error("Invalid fixed-point constant, no significant digits after 'q'\n");
|
||||
}
|
||||
precision = fixPrecision;
|
||||
} else if (precision > 31) {
|
||||
error("Fixed-point constant precision must be between 1 and 31\n");
|
||||
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");
|
||||
}
|
||||
|
||||
// Cast to unsigned avoids undefined overflow behavior
|
||||
uint32_t fractional =
|
||||
@@ -1052,16 +1081,18 @@ static uint32_t readBinaryNumber() {
|
||||
int bit;
|
||||
|
||||
// Check for '_' after digits in case one of the digits is '_'
|
||||
if (c == binDigits[0])
|
||||
if (c == binDigits[0]) {
|
||||
bit = 0;
|
||||
else if (c == binDigits[1])
|
||||
} else if (c == binDigits[1]) {
|
||||
bit = 1;
|
||||
else if (c == '_')
|
||||
} else if (c == '_') {
|
||||
continue;
|
||||
else
|
||||
} else {
|
||||
break;
|
||||
if (value > (UINT32_MAX - bit) / 2)
|
||||
}
|
||||
if (value > (UINT32_MAX - bit) / 2) {
|
||||
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
||||
}
|
||||
value = value * 2 + bit;
|
||||
}
|
||||
|
||||
@@ -1075,26 +1106,29 @@ static uint32_t readHexNumber() {
|
||||
for (;; shiftChar()) {
|
||||
int c = peek();
|
||||
|
||||
if (c >= 'a' && c <= 'f')
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
c = c - 'a' + 10;
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
c = c - 'A' + 10;
|
||||
else if (c >= '0' && c <= '9')
|
||||
} else if (c >= '0' && c <= '9') {
|
||||
c = c - '0';
|
||||
else if (c == '_' && !empty)
|
||||
} else if (c == '_' && !empty) {
|
||||
continue;
|
||||
else
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (value > (UINT32_MAX - c) / 16)
|
||||
if (value > (UINT32_MAX - c) / 16) {
|
||||
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
||||
}
|
||||
value = value * 16 + c;
|
||||
|
||||
empty = false;
|
||||
}
|
||||
|
||||
if (empty)
|
||||
if (empty) {
|
||||
error("Invalid integer constant, no digits after '$'\n");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -1110,34 +1144,37 @@ static uint32_t readGfxConstant() {
|
||||
uint32_t pixel;
|
||||
|
||||
// Check for '_' after digits in case one of the digits is '_'
|
||||
if (c == gfxDigits[0])
|
||||
if (c == gfxDigits[0]) {
|
||||
pixel = 0;
|
||||
else if (c == gfxDigits[1])
|
||||
} else if (c == gfxDigits[1]) {
|
||||
pixel = 1;
|
||||
else if (c == gfxDigits[2])
|
||||
} else if (c == gfxDigits[2]) {
|
||||
pixel = 2;
|
||||
else if (c == gfxDigits[3])
|
||||
} else if (c == gfxDigits[3]) {
|
||||
pixel = 3;
|
||||
else if (c == '_' && width > 0)
|
||||
} else if (c == '_' && width > 0) {
|
||||
continue;
|
||||
else
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (width < 8) {
|
||||
bitPlaneLower = bitPlaneLower << 1 | (pixel & 1);
|
||||
bitPlaneUpper = bitPlaneUpper << 1 | (pixel >> 1);
|
||||
}
|
||||
if (width < 9)
|
||||
if (width < 9) {
|
||||
width++;
|
||||
}
|
||||
}
|
||||
|
||||
if (width == 0)
|
||||
if (width == 0) {
|
||||
error("Invalid graphics constant, no digits after '`'\n");
|
||||
else if (width == 9)
|
||||
} else if (width == 9) {
|
||||
warning(
|
||||
WARNING_LARGE_CONSTANT,
|
||||
"Graphics constant is too long, only first 8 pixels considered\n"
|
||||
);
|
||||
}
|
||||
|
||||
return bitPlaneUpper << 8 | bitPlaneLower;
|
||||
}
|
||||
@@ -1165,9 +1202,10 @@ static Token readIdentifier(char firstChar, bool raw) {
|
||||
identifier += c;
|
||||
|
||||
// If the char was a dot, mark the identifier as local
|
||||
if (c == '.')
|
||||
if (c == '.') {
|
||||
tokenType = T_(LOCAL_ID);
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to check for a keyword if the identifier is not raw
|
||||
if (!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
|
||||
if (identifier.find_first_not_of('.') == identifier.npos)
|
||||
if (identifier.find_first_not_of('.') == identifier.npos) {
|
||||
tokenType = T_(ID);
|
||||
}
|
||||
|
||||
return Token(tokenType, identifier);
|
||||
}
|
||||
@@ -1189,8 +1228,9 @@ static Token readIdentifier(char firstChar, bool raw) {
|
||||
// Functions to read strings
|
||||
|
||||
static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
||||
if (depth > maxRecursionDepth)
|
||||
if (depth > maxRecursionDepth) {
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||
}
|
||||
|
||||
std::string fmtBuf;
|
||||
FormatSpec fmt{};
|
||||
@@ -1218,11 +1258,13 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
||||
break;
|
||||
} else if (c == ':' && !fmt.isFinished()) { // Format spec, only once
|
||||
shiftChar();
|
||||
for (char f : fmtBuf)
|
||||
for (char f : fmtBuf) {
|
||||
fmt.useCharacter(f);
|
||||
}
|
||||
fmt.finishCharacters();
|
||||
if (!fmt.isValid())
|
||||
if (!fmt.isValid()) {
|
||||
error("Invalid format spec '%s'\n", fmtBuf.c_str());
|
||||
}
|
||||
fmtBuf.clear(); // Now that format has been set, restart at beginning of string
|
||||
} else {
|
||||
shiftChar();
|
||||
@@ -1249,10 +1291,11 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
||||
Symbol const *sym = sym_FindScopedValidSymbol(fmtBuf);
|
||||
|
||||
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());
|
||||
else
|
||||
} else {
|
||||
error("Interpolated symbol \"%s\" does not exist\n", fmtBuf.c_str());
|
||||
}
|
||||
} else if (sym->type == SYM_EQUS) {
|
||||
auto buf = std::make_shared<std::string>();
|
||||
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)
|
||||
if (c == '\r' || c == '\n') {
|
||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
||||
handleCRLF(c);
|
||||
nextLine();
|
||||
c = '\n';
|
||||
@@ -1336,8 +1378,9 @@ static std::string readString(bool raw) {
|
||||
case '"':
|
||||
if (multiline) {
|
||||
// Only """ ends a multi-line string
|
||||
if (peek() != '"')
|
||||
if (peek() != '"') {
|
||||
break;
|
||||
}
|
||||
shiftChar();
|
||||
if (peek() != '"') {
|
||||
str += '"';
|
||||
@@ -1348,8 +1391,9 @@ static std::string readString(bool raw) {
|
||||
return str;
|
||||
|
||||
case '\\': // Character escape or macro arg
|
||||
if (raw)
|
||||
if (raw) {
|
||||
break;
|
||||
}
|
||||
c = peek();
|
||||
switch (c) {
|
||||
case '\\':
|
||||
@@ -1416,8 +1460,9 @@ static std::string readString(bool raw) {
|
||||
break;
|
||||
|
||||
case '{': // Symbol interpolation
|
||||
if (raw)
|
||||
if (raw) {
|
||||
break;
|
||||
}
|
||||
// We'll be exiting the string scope, so re-enable expansions
|
||||
// (Not interpolations, since they're handled by the function itself...)
|
||||
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)
|
||||
if (c == '\r' || c == '\n') {
|
||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
||||
handleCRLF(c);
|
||||
nextLine();
|
||||
c = '\n';
|
||||
@@ -1478,12 +1522,14 @@ static void appendStringLiteral(std::string &str, bool raw) {
|
||||
case '"':
|
||||
if (multiline) {
|
||||
// Only """ ends a multi-line string
|
||||
if (peek() != '"')
|
||||
if (peek() != '"') {
|
||||
break;
|
||||
}
|
||||
str += '"';
|
||||
shiftChar();
|
||||
if (peek() != '"')
|
||||
if (peek() != '"') {
|
||||
break;
|
||||
}
|
||||
str += '"';
|
||||
shiftChar();
|
||||
}
|
||||
@@ -1491,8 +1537,9 @@ static void appendStringLiteral(std::string &str, bool raw) {
|
||||
return;
|
||||
|
||||
case '\\': // Character escape or macro arg
|
||||
if (raw)
|
||||
if (raw) {
|
||||
break;
|
||||
}
|
||||
c = peek();
|
||||
switch (c) {
|
||||
// Character escape
|
||||
@@ -1550,8 +1597,9 @@ static void appendStringLiteral(std::string &str, bool raw) {
|
||||
break;
|
||||
|
||||
case '{': // Symbol interpolation
|
||||
if (raw)
|
||||
if (raw) {
|
||||
break;
|
||||
}
|
||||
// We'll be exiting the string scope, so re-enable expansions
|
||||
// (Not interpolations, since they're handled by the function itself...)
|
||||
lexerState->disableMacroArgs = false;
|
||||
@@ -1850,12 +1898,14 @@ static Token yylex_NORMAL() {
|
||||
|
||||
// An ELIF after a taken IF needs to not evaluate its condition
|
||||
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();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// `token` is either an `ID` or a `LOCAL_ID`, and both have a `std::string` value.
|
||||
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,
|
||||
// "Label:" and "mac:" are treated as label definitions, but "Label :" and "mac :"
|
||||
// are treated as macro invocations.
|
||||
if (token.type == T_(ID) && peek() == ':')
|
||||
if (token.type == T_(ID) && peek() == ':') {
|
||||
token.type = T_(LABEL);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
@@ -1917,8 +1968,9 @@ static Token yylex_RAW() {
|
||||
shiftChar();
|
||||
c = peek();
|
||||
// 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;
|
||||
}
|
||||
// Line continuations count as "whitespace"
|
||||
discardLineContinuation();
|
||||
} else {
|
||||
@@ -1964,18 +2016,21 @@ static Token yylex_RAW() {
|
||||
break;
|
||||
|
||||
case ',': // End of macro arg
|
||||
if (parenDepth == 0)
|
||||
if (parenDepth == 0) {
|
||||
goto finish;
|
||||
}
|
||||
goto append;
|
||||
|
||||
case '(': // Open parentheses inside macro args
|
||||
if (parenDepth < UINT_MAX)
|
||||
if (parenDepth < UINT_MAX) {
|
||||
parenDepth++;
|
||||
}
|
||||
goto append;
|
||||
|
||||
case ')': // Close parentheses inside macro args
|
||||
if (parenDepth > 0)
|
||||
if (parenDepth > 0) {
|
||||
parenDepth--;
|
||||
}
|
||||
goto append;
|
||||
|
||||
case '\\': // Character escape
|
||||
@@ -2055,8 +2110,9 @@ finish:
|
||||
// an empty raw string before it). This will not be treated as a
|
||||
// macro argument. To pass an empty last argument, use a second
|
||||
// trailing comma.
|
||||
if (!str.empty())
|
||||
if (!str.empty()) {
|
||||
return Token(T_(STRING), str);
|
||||
}
|
||||
lexer_SetMode(LEXER_NORMAL);
|
||||
|
||||
if (c == '\r' || c == '\n') {
|
||||
@@ -2088,9 +2144,10 @@ static Token skipIfBlock(bool toEndc) {
|
||||
|
||||
for (;; shiftChar()) {
|
||||
c = peek();
|
||||
if (!isWhitespace(c))
|
||||
if (!isWhitespace(c)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startsIdentifier(c)) {
|
||||
shiftChar();
|
||||
@@ -2100,23 +2157,28 @@ static Token skipIfBlock(bool toEndc) {
|
||||
break;
|
||||
|
||||
case T_(POP_ELIF):
|
||||
if (lexer_ReachedELSEBlock())
|
||||
if (lexer_ReachedELSEBlock()) {
|
||||
fatalerror("Found ELIF after an ELSE block\n");
|
||||
if (!toEndc && lexer_GetIFDepth() == startingDepth)
|
||||
}
|
||||
if (!toEndc && lexer_GetIFDepth() == startingDepth) {
|
||||
return token;
|
||||
}
|
||||
break;
|
||||
|
||||
case T_(POP_ELSE):
|
||||
if (lexer_ReachedELSEBlock())
|
||||
if (lexer_ReachedELSEBlock()) {
|
||||
fatalerror("Found ELSE after an ELSE block\n");
|
||||
}
|
||||
lexer_ReachELSEBlock();
|
||||
if (!toEndc && lexer_GetIFDepth() == startingDepth)
|
||||
if (!toEndc && lexer_GetIFDepth() == startingDepth) {
|
||||
return token;
|
||||
}
|
||||
break;
|
||||
|
||||
case T_(POP_ENDC):
|
||||
if (lexer_GetIFDepth() == startingDepth)
|
||||
if (lexer_GetIFDepth() == startingDepth) {
|
||||
return token;
|
||||
}
|
||||
lexer_DecIFDepth();
|
||||
break;
|
||||
|
||||
@@ -2141,7 +2203,6 @@ static Token skipIfBlock(bool toEndc) {
|
||||
}
|
||||
|
||||
if (c == '\r' || c == '\n') {
|
||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
||||
handleCRLF(c);
|
||||
// Do this both on line continuations and plain EOLs
|
||||
nextLine();
|
||||
@@ -2173,8 +2234,9 @@ static Token yylex_SKIP_TO_ENDR() {
|
||||
|
||||
for (;;) {
|
||||
c = peek();
|
||||
if (!isWhitespace(c))
|
||||
if (!isWhitespace(c)) {
|
||||
break;
|
||||
}
|
||||
shiftChar();
|
||||
}
|
||||
|
||||
@@ -2188,8 +2250,9 @@ static Token yylex_SKIP_TO_ENDR() {
|
||||
|
||||
case T_(POP_ENDR):
|
||||
depth--;
|
||||
if (!depth)
|
||||
if (!depth) {
|
||||
return Token(T_(YYEOF)); // yywrap() will finish the REPT/FOR loop
|
||||
}
|
||||
break;
|
||||
|
||||
case T_(POP_IF):
|
||||
@@ -2221,7 +2284,6 @@ static Token yylex_SKIP_TO_ENDR() {
|
||||
}
|
||||
|
||||
if (c == '\r' || c == '\n') {
|
||||
// Handle CRLF before nextLine() since shiftChar updates colNo
|
||||
handleCRLF(c);
|
||||
// Do this both on line continuations and plain EOLs
|
||||
nextLine();
|
||||
@@ -2235,11 +2297,13 @@ yy::parser::symbol_type yylex() {
|
||||
lexerState = lexerStateEOL;
|
||||
lexerStateEOL = nullptr;
|
||||
}
|
||||
if (lexerState->lastToken == T_(EOB) && yywrap())
|
||||
if (lexerState->lastToken == T_(EOB) && yywrap()) {
|
||||
return yy::parser::make_YYEOF();
|
||||
}
|
||||
// Newlines read within an expansion should not increase the line count
|
||||
if (lexerState->atLineStart && lexerState->expansions.empty())
|
||||
if (lexerState->atLineStart && lexerState->expansions.empty()) {
|
||||
nextLine();
|
||||
}
|
||||
|
||||
static Token (* const lexerModeFuncs[NB_LEXER_MODES])() = {
|
||||
yylex_NORMAL,
|
||||
@@ -2300,8 +2364,9 @@ static Capture startCapture() {
|
||||
static void endCapture(Capture &capture) {
|
||||
// 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
|
||||
if (!capture.span.ptr)
|
||||
if (!capture.span.ptr) {
|
||||
capture.span.ptr = lexerState->makeSharedCaptureBufPtr();
|
||||
}
|
||||
capture.span.size = lexerState->captureSize;
|
||||
|
||||
// ENDR/ENDM or EOF puts us past the start of the line
|
||||
@@ -2339,7 +2404,7 @@ Capture lexer_CaptureRept() {
|
||||
endCapture(capture);
|
||||
// The final ENDR has been captured, but we don't want it!
|
||||
// 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;
|
||||
}
|
||||
depth--;
|
||||
@@ -2385,7 +2450,7 @@ Capture lexer_CaptureMacro() {
|
||||
endCapture(capture);
|
||||
// The ENDM has been captured, but we don't want it!
|
||||
// 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;
|
||||
|
||||
default:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/macro.hpp"
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#define MAXMACROARGS 99999
|
||||
|
||||
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
||||
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 {
|
||||
size_t nbArgs = args.size();
|
||||
|
||||
if (shift >= nbArgs)
|
||||
if (shift >= nbArgs) {
|
||||
return std::make_shared<std::string>("");
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
|
||||
for (uint32_t i = shift; i < nbArgs; i++)
|
||||
for (uint32_t i = shift; i < nbArgs; i++) {
|
||||
len += args[i]->length() + 1; // 1 for comma
|
||||
}
|
||||
|
||||
auto str = std::make_shared<std::string>();
|
||||
str->reserve(len + 1); // 1 for comma
|
||||
@@ -38,18 +36,18 @@ std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||
str->append(*arg);
|
||||
|
||||
// Commas go between args and after a last empty arg
|
||||
if (i < nbArgs - 1 || arg->empty())
|
||||
if (i < nbArgs - 1 || arg->empty()) {
|
||||
str->push_back(','); // no space after comma
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
||||
if (arg->empty())
|
||||
if (arg->empty()) {
|
||||
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
|
||||
if (args.size() == MAXMACROARGS)
|
||||
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
|
||||
}
|
||||
args.push_back(arg);
|
||||
}
|
||||
|
||||
|
||||
122
src/asm/main.cpp
122
src/asm/main.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/main.hpp"
|
||||
|
||||
@@ -37,30 +37,30 @@ static std::string make_escape(std::string &str) {
|
||||
for (;;) {
|
||||
// All dollars needs to be doubled
|
||||
size_t nextPos = str.find("$", pos);
|
||||
if (nextPos == std::string::npos)
|
||||
if (nextPos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
escaped.append(str, pos, nextPos - pos);
|
||||
escaped.append("$$");
|
||||
pos = nextPos + QUOTEDSTRLEN("$");
|
||||
pos = nextPos + literal_strlen("$");
|
||||
}
|
||||
escaped.append(str, pos, str.length() - pos);
|
||||
return escaped;
|
||||
}
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "b:D:Eg:I:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
static char const *optstring = "b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int depType; // Variants of `-M`
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts
|
||||
//
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"binary-digits", required_argument, nullptr, 'b'},
|
||||
{"define", required_argument, nullptr, 'D'},
|
||||
@@ -69,6 +69,7 @@ static option const longopts[] = {
|
||||
{"include", required_argument, nullptr, 'I'},
|
||||
{"dependfile", required_argument, nullptr, 'M'},
|
||||
{"MG", no_argument, &depType, 'G'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"MP", no_argument, &depType, 'P'},
|
||||
{"MT", required_argument, &depType, 'T'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
@@ -88,7 +89,7 @@ static option const longopts[] = {
|
||||
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbasm [-EVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
||||
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
|
||||
@@ -111,12 +112,14 @@ int main(int argc, char *argv[]) {
|
||||
time_t now = time(nullptr);
|
||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch)
|
||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
||||
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
||||
}
|
||||
|
||||
Defer closeDependFile{[&] {
|
||||
if (dependFile)
|
||||
if (dependFile) {
|
||||
fclose(dependFile);
|
||||
}
|
||||
}};
|
||||
|
||||
// Perform some init for below
|
||||
@@ -128,23 +131,25 @@ int main(int argc, char *argv[]) {
|
||||
opt_P(0);
|
||||
opt_Q(16);
|
||||
sym_SetExportAll(false);
|
||||
uint32_t maxDepth = DEFAULT_MAX_DEPTH;
|
||||
uint32_t maxDepth = 64;
|
||||
char const *dependFileName = nullptr;
|
||||
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs;
|
||||
std::string newTarget;
|
||||
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal.
|
||||
if (isatty(STDERR_FILENO))
|
||||
if (isatty(STDERR_FILENO)) {
|
||||
maxErrors = 100;
|
||||
}
|
||||
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
switch (ch) {
|
||||
char *endptr;
|
||||
|
||||
case 'b':
|
||||
if (strlen(musl_optarg) == 2)
|
||||
if (strlen(musl_optarg) == 2) {
|
||||
opt_B(musl_optarg);
|
||||
else
|
||||
} else {
|
||||
errx("Must specify exactly 2 characters for option 'b'");
|
||||
}
|
||||
break;
|
||||
|
||||
char *equals;
|
||||
@@ -163,19 +168,25 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
if (strlen(musl_optarg) == 4)
|
||||
if (strlen(musl_optarg) == 4) {
|
||||
opt_G(musl_optarg);
|
||||
else
|
||||
} else {
|
||||
errx("Must specify exactly 4 characters for option 'g'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
printUsage();
|
||||
exit(0);
|
||||
|
||||
case 'I':
|
||||
fstk_AddIncludePath(musl_optarg);
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
if (dependFile)
|
||||
if (dependFile) {
|
||||
warnx("Overriding dependfile %s", dependFileName);
|
||||
}
|
||||
if (strcmp("-", musl_optarg)) {
|
||||
dependFile = fopen(musl_optarg, "w");
|
||||
dependFileName = musl_optarg;
|
||||
@@ -183,8 +194,9 @@ int main(int argc, char *argv[]) {
|
||||
dependFile = stdout;
|
||||
dependFileName = "<stdout>";
|
||||
}
|
||||
if (dependFile == nullptr)
|
||||
if (dependFile == nullptr) {
|
||||
err("Failed to open dependfile \"%s\"", dependFileName);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
@@ -199,11 +211,13 @@ int main(int argc, char *argv[]) {
|
||||
case 'p':
|
||||
padByte = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'p'");
|
||||
}
|
||||
|
||||
if (padByte > 0xFF)
|
||||
if (padByte > 0xFF) {
|
||||
errx("Argument for option 'p' must be between 0 and 0xFF");
|
||||
}
|
||||
|
||||
opt_P(padByte);
|
||||
break;
|
||||
@@ -212,15 +226,18 @@ int main(int argc, char *argv[]) {
|
||||
char const *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = musl_optarg;
|
||||
if (precisionArg[0] == '.')
|
||||
if (precisionArg[0] == '.') {
|
||||
precisionArg++;
|
||||
}
|
||||
precision = strtoul(precisionArg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'Q'");
|
||||
}
|
||||
|
||||
if (precision < 1 || precision > 31)
|
||||
if (precision < 1 || precision > 31) {
|
||||
errx("Argument for option 'Q' must be between 1 and 31");
|
||||
}
|
||||
|
||||
opt_Q(precision);
|
||||
break;
|
||||
@@ -228,35 +245,41 @@ int main(int argc, char *argv[]) {
|
||||
case 'r':
|
||||
maxDepth = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'r'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 's': {
|
||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
||||
char *name = strchr(musl_optarg, ':');
|
||||
if (!name)
|
||||
if (!name) {
|
||||
errx("Invalid argument for option 's'");
|
||||
}
|
||||
*name++ = '\0';
|
||||
|
||||
std::vector<StateFeature> features;
|
||||
for (char *feature = musl_optarg; feature;) {
|
||||
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
|
||||
char *next = strchr(feature, ',');
|
||||
if (next)
|
||||
if (next) {
|
||||
*next++ = '\0';
|
||||
}
|
||||
// Trim whitespace from the beginning of `feature`...
|
||||
feature += strspn(feature, " \t");
|
||||
// ...and from the end
|
||||
if (char *end = strpbrk(feature, " \t"); end)
|
||||
if (char *end = strpbrk(feature, " \t"); end) {
|
||||
*end = '\0';
|
||||
}
|
||||
// A feature must be specified
|
||||
if (*feature == '\0')
|
||||
if (*feature == '\0') {
|
||||
errx("Empty feature for option 's'");
|
||||
}
|
||||
// Parse the `feature` and update the `features` list
|
||||
if (!strcasecmp(feature, "all")) {
|
||||
if (!features.empty())
|
||||
if (!features.empty()) {
|
||||
warnx("Redundant feature before \"%s\" for option 's'", feature);
|
||||
}
|
||||
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
|
||||
} else {
|
||||
StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU
|
||||
@@ -276,10 +299,12 @@ int main(int argc, char *argv[]) {
|
||||
feature = next;
|
||||
}
|
||||
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end())
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
||||
warnx("Overriding state filename %s", name);
|
||||
if (verbose)
|
||||
}
|
||||
if (verbose) {
|
||||
printf("State filename %s\n", name);
|
||||
}
|
||||
stateFileSpecs.emplace(name, std::move(features));
|
||||
break;
|
||||
}
|
||||
@@ -304,11 +329,13 @@ int main(int argc, char *argv[]) {
|
||||
case 'X':
|
||||
maxValue = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0')
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
errx("Invalid argument for option 'X'");
|
||||
}
|
||||
|
||||
if (maxValue > UINT_MAX)
|
||||
if (maxValue > UINT_MAX) {
|
||||
errx("Argument for option 'X' must be between 0 and %u", UINT_MAX);
|
||||
}
|
||||
|
||||
maxErrors = maxValue;
|
||||
break;
|
||||
@@ -327,10 +354,12 @@ int main(int argc, char *argv[]) {
|
||||
case 'Q':
|
||||
case 'T':
|
||||
newTarget = musl_optarg;
|
||||
if (depType == 'Q')
|
||||
if (depType == 'Q') {
|
||||
newTarget = make_escape(newTarget);
|
||||
if (!targetFileName.empty())
|
||||
}
|
||||
if (!targetFileName.empty()) {
|
||||
targetFileName += ' ';
|
||||
}
|
||||
targetFileName += newTarget;
|
||||
break;
|
||||
}
|
||||
@@ -343,8 +372,9 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (targetFileName.empty() && !objectFileName.empty())
|
||||
if (targetFileName.empty() && !objectFileName.empty()) {
|
||||
targetFileName = objectFileName;
|
||||
}
|
||||
|
||||
if (argc == musl_optind) {
|
||||
fputs(
|
||||
@@ -360,13 +390,15 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
std::string mainFileName = argv[musl_optind];
|
||||
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Assembling %s\n", mainFileName.c_str());
|
||||
}
|
||||
|
||||
if (dependFile) {
|
||||
if (targetFileName.empty())
|
||||
if (targetFileName.empty()) {
|
||||
errx("Dependency files can only be created if a target file is specified with either "
|
||||
"-o, -MQ or -MT");
|
||||
}
|
||||
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), mainFileName.c_str());
|
||||
}
|
||||
@@ -377,9 +409,11 @@ int main(int argc, char *argv[]) {
|
||||
fstk_Init(mainFileName, maxDepth);
|
||||
|
||||
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
||||
if (yy::parser parser; parser.parse() != 0 && nbErrors == 0)
|
||||
if (yy::parser parser; parser.parse() != 0 && nbErrors == 0) {
|
||||
nbErrors = 1;
|
||||
}
|
||||
|
||||
if (!failedOnMissingInclude) {
|
||||
sect_CheckUnionClosed();
|
||||
sect_CheckLoadClosed();
|
||||
sect_CheckSizes();
|
||||
@@ -387,18 +421,22 @@ int main(int argc, char *argv[]) {
|
||||
charmap_CheckStack();
|
||||
opt_CheckStack();
|
||||
sect_CheckStack();
|
||||
}
|
||||
|
||||
if (nbErrors != 0)
|
||||
if (nbErrors != 0) {
|
||||
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
||||
if (failedOnMissingInclude)
|
||||
if (failedOnMissingInclude) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
out_WriteObject();
|
||||
|
||||
for (auto [name, features] : stateFileSpecs)
|
||||
for (auto [name, features] : stateFileSpecs) {
|
||||
out_WriteState(name, features);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
@@ -53,17 +53,19 @@ void opt_W(char const *flag) {
|
||||
void opt_Parse(char const *s) {
|
||||
switch (s[0]) {
|
||||
case 'b':
|
||||
if (strlen(&s[1]) == 2)
|
||||
if (strlen(&s[1]) == 2) {
|
||||
opt_B(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify exactly 2 characters for option 'b'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
if (strlen(&s[1]) == 4)
|
||||
if (strlen(&s[1]) == 4) {
|
||||
opt_G(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify exactly 4 characters for option 'g'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
@@ -72,12 +74,13 @@ void opt_Parse(char const *s) {
|
||||
unsigned int padByte;
|
||||
|
||||
result = sscanf(&s[1], "%x", &padByte);
|
||||
if (result != 1)
|
||||
if (result != 1) {
|
||||
error("Invalid argument for option 'p'\n");
|
||||
else if (padByte > 0xFF)
|
||||
} else if (padByte > 0xFF) {
|
||||
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
||||
else
|
||||
} else {
|
||||
opt_P(padByte);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'p'\n");
|
||||
}
|
||||
@@ -86,19 +89,21 @@ void opt_Parse(char const *s) {
|
||||
char const *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = &s[1];
|
||||
if (precisionArg[0] == '.')
|
||||
if (precisionArg[0] == '.') {
|
||||
precisionArg++;
|
||||
}
|
||||
if (strlen(precisionArg) <= 2) {
|
||||
int result;
|
||||
unsigned int precision;
|
||||
|
||||
result = sscanf(precisionArg, "%u", &precision);
|
||||
if (result != 1)
|
||||
if (result != 1) {
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
else if (precision < 1 || precision > 31)
|
||||
} else if (precision < 1 || precision > 31) {
|
||||
error("Argument for option 'Q' must be between 1 and 31\n");
|
||||
else
|
||||
} else {
|
||||
opt_Q(precision);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'Q'\n");
|
||||
}
|
||||
@@ -106,8 +111,9 @@ void opt_Parse(char const *s) {
|
||||
|
||||
case 'r': {
|
||||
++s; // Skip 'r'
|
||||
while (isblank(*s))
|
||||
while (isblank(*s)) {
|
||||
++s; // Skip leading whitespace
|
||||
}
|
||||
|
||||
if (s[0] == '\0') {
|
||||
error("Missing argument to option 'r'\n");
|
||||
@@ -128,10 +134,11 @@ void opt_Parse(char const *s) {
|
||||
}
|
||||
|
||||
case 'W':
|
||||
if (strlen(&s[1]) > 0)
|
||||
if (strlen(&s[1]) > 0) {
|
||||
opt_W(&s[1]);
|
||||
else
|
||||
} else {
|
||||
error("Must specify an argument for option 'W'\n");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#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
|
||||
static uint32_t getSectIDIfAny(Section *sect) {
|
||||
if (!sect)
|
||||
if (!sect) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end())
|
||||
return static_cast<uint32_t>(sectionMap.size() - search->second - 1);
|
||||
if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) {
|
||||
return static_cast<uint32_t>(search->second);
|
||||
}
|
||||
|
||||
fatalerror("Unknown section '%s'\n", sect->name.c_str());
|
||||
}
|
||||
@@ -109,9 +111,10 @@ static void writeSection(Section const §, FILE *file) {
|
||||
fwrite(sect.data.data(), 1, sect.size, file);
|
||||
putLong(sect.patches.size(), file);
|
||||
|
||||
for (Patch const &patch : sect.patches)
|
||||
for (Patch const &patch : sect.patches) {
|
||||
writePatch(patch, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void writeSymbol(Symbol const &sym, FILE *file) {
|
||||
@@ -162,8 +165,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
symName.clear();
|
||||
for (;;) {
|
||||
uint8_t c = rpn[offset++];
|
||||
if (c == 0)
|
||||
if (c == 0) {
|
||||
break;
|
||||
}
|
||||
symName += c;
|
||||
}
|
||||
|
||||
@@ -188,8 +192,9 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
||||
symName.clear();
|
||||
for (;;) {
|
||||
uint8_t c = rpn[offset++];
|
||||
if (c == 0)
|
||||
if (c == 0) {
|
||||
break;
|
||||
}
|
||||
symName += c;
|
||||
}
|
||||
|
||||
@@ -282,7 +287,7 @@ void out_CreateAssert(
|
||||
assertion.message = message;
|
||||
}
|
||||
|
||||
static void writeAssert(Assertion &assert, FILE *file) {
|
||||
static void writeAssert(Assertion const &assert, FILE *file) {
|
||||
writePatch(assert.patch, file);
|
||||
putString(assert.message, file);
|
||||
}
|
||||
@@ -298,14 +303,16 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
||||
|
||||
putLong(nodeIters.size(), file);
|
||||
// Iters are stored by decreasing depth, so reverse the order for output
|
||||
for (uint32_t i = nodeIters.size(); i--;)
|
||||
for (uint32_t i = nodeIters.size(); i--;) {
|
||||
putLong(nodeIters[i], file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void out_WriteObject() {
|
||||
if (objectFileName.empty())
|
||||
if (objectFileName.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file;
|
||||
if (objectFileName != "-") {
|
||||
@@ -315,14 +322,15 @@ void out_WriteObject() {
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file)
|
||||
if (!file) {
|
||||
err("Failed to open object file '%s'", objectFileName.c_str());
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
// Also write symbols that weren't written above
|
||||
sym_ForEach(registerUnregisteredSymbol);
|
||||
|
||||
fprintf(file, RGBDS_OBJECT_VERSION_STRING);
|
||||
fputs(RGBDS_OBJECT_VERSION_STRING, file);
|
||||
putLong(RGBDS_OBJECT_REV, file);
|
||||
|
||||
putLong(objectSymbols.size(), file);
|
||||
@@ -335,7 +343,7 @@ void out_WriteObject() {
|
||||
writeFileStackNode(node, file);
|
||||
|
||||
// The list is supposed to have decrementing IDs
|
||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1)
|
||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1) {
|
||||
fatalerror(
|
||||
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
||||
". Please report this to the developers!\n",
|
||||
@@ -343,25 +351,31 @@ void out_WriteObject() {
|
||||
node.ID
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (Symbol const *sym : objectSymbols)
|
||||
for (Symbol const *sym : objectSymbols) {
|
||||
writeSymbol(*sym, file);
|
||||
}
|
||||
|
||||
for (auto it = sectionList.rbegin(); it != sectionList.rend(); it++)
|
||||
writeSection(*it, file);
|
||||
for (Section const § : sectionList) {
|
||||
writeSection(sect, file);
|
||||
}
|
||||
|
||||
putLong(assertions.size(), file);
|
||||
|
||||
for (Assertion &assert : assertions)
|
||||
for (Assertion const &assert : assertions) {
|
||||
writeAssert(assert, file);
|
||||
}
|
||||
}
|
||||
|
||||
void out_SetFileName(std::string const &name) {
|
||||
if (!objectFileName.empty())
|
||||
if (!objectFileName.empty()) {
|
||||
warnx("Overriding output filename %s", objectFileName.c_str());
|
||||
}
|
||||
objectFileName = name;
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Output filename %s\n", objectFileName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void dumpString(std::string const &escape, FILE *file) {
|
||||
@@ -397,8 +411,9 @@ static bool dumpEquConstants(FILE *file) {
|
||||
equConstants.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQU)
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQU) {
|
||||
equConstants.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Constants are ordered by file, then by definition order
|
||||
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -418,8 +433,9 @@ static bool dumpVariables(FILE *file) {
|
||||
variables.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_VAR)
|
||||
if (!sym.isBuiltin && sym.type == SYM_VAR) {
|
||||
variables.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Variables are ordered by file, then by definition order
|
||||
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -439,8 +455,9 @@ static bool dumpEqusConstants(FILE *file) {
|
||||
equsConstants.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQUS)
|
||||
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
|
||||
equsConstants.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Constants are ordered by file, then by definition order
|
||||
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -467,8 +484,9 @@ static bool dumpCharmaps(FILE *file) {
|
||||
fputs("charmap \"", charmapFile);
|
||||
dumpString(mapping, charmapFile);
|
||||
putc('"', charmapFile);
|
||||
for (int32_t v : value)
|
||||
for (int32_t v : value) {
|
||||
fprintf(charmapFile, ", $%" PRIx32, v);
|
||||
}
|
||||
putc('\n', charmapFile);
|
||||
}
|
||||
);
|
||||
@@ -479,8 +497,9 @@ static bool dumpMacros(FILE *file) {
|
||||
macros.clear();
|
||||
|
||||
sym_ForEach([](Symbol &sym) {
|
||||
if (!sym.isBuiltin && sym.type == SYM_MACRO)
|
||||
if (!sym.isBuiltin && sym.type == SYM_MACRO) {
|
||||
macros.push_back(&sym);
|
||||
}
|
||||
});
|
||||
// Macros are ordered by file, then by definition order
|
||||
std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool {
|
||||
@@ -508,8 +527,9 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file)
|
||||
if (!file) {
|
||||
err("Failed to open state file '%s'", name.c_str());
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
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);
|
||||
for (StateFeature feature : features) {
|
||||
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
|
||||
if (!dumpFuncs[feature](file))
|
||||
if (!dumpFuncs[feature](file)) {
|
||||
fprintf(file, "; No values\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
675
src/asm/parser.y
675
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"
|
||||
|
||||
@@ -46,8 +46,9 @@ int32_t Expression::getConstVal() const {
|
||||
}
|
||||
|
||||
Symbol const *Expression::symbolOf() const {
|
||||
if (!isSymbol)
|
||||
if (!isSymbol) {
|
||||
return nullptr;
|
||||
}
|
||||
return sym_FindScopedSymbol(reinterpret_cast<char const *>(&rpn[1]));
|
||||
}
|
||||
|
||||
@@ -55,8 +56,9 @@ bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||
// Check if both expressions only refer to a single symbol
|
||||
Symbol const *sym1 = symbolOf();
|
||||
|
||||
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
|
||||
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Section const *sect1 = sym1->getSection();
|
||||
Section const *sect2 = sym->getSection();
|
||||
@@ -208,8 +210,9 @@ static bool tryConstNonzero(Expression const &lhs, Expression const &rhs) {
|
||||
|
||||
static bool tryConstLogNot(Expression const &expr) {
|
||||
Symbol const *sym = expr.symbolOf();
|
||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
||||
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assume(sym->isNumeric());
|
||||
|
||||
@@ -225,23 +228,21 @@ static bool tryConstLogNot(Expression const &expr) {
|
||||
return knownBits != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to compute a constant LOW() from non-constant argument
|
||||
* This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
|
||||
*
|
||||
* @return The constant `LOW(expr)` result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
// Returns a constant LOW() from non-constant argument, or -1 if it cannot be computed.
|
||||
// This is possible if the argument is a symbol belonging to an `ALIGN[8]` section.
|
||||
static int32_t tryConstLow(Expression const &expr) {
|
||||
Symbol const *sym = expr.symbolOf();
|
||||
if (!sym || !sym->getSection() || !sym->isDefined())
|
||||
if (!sym || !sym->getSection() || !sym->isDefined()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assume(sym->isNumeric());
|
||||
|
||||
// The low byte must not cover any unknown bits
|
||||
Section const § = *sym->getSection();
|
||||
if (sect.align < 8)
|
||||
if (sect.align < 8) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
@@ -251,28 +252,26 @@ static int32_t tryConstLow(Expression const &expr) {
|
||||
return (symbolOfs + sect.alignOfs) & 0xFF;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempts to compute a constant binary AND with one non-constant operands
|
||||
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
||||
* a constant that only keeps (some of) the lower N bits.
|
||||
*
|
||||
* @return The constant `lhs & rhs` result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
// Returns a constant binary AND with one non-constant operand, or -1 if it cannot be computed.
|
||||
// This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
||||
// a constant that only keeps (some of) the lower N bits.
|
||||
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
Symbol const *lhsSymbol = lhs.symbolOf();
|
||||
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
|
||||
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
|
||||
bool rhsIsSymbol = rhsSymbol && rhsSymbol->getSection();
|
||||
|
||||
if (!lhsIsSymbol && !rhsIsSymbol)
|
||||
if (!lhsIsSymbol && !rhsIsSymbol) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If the lhs isn't a symbol, try again the other way around
|
||||
Symbol const &sym = lhsIsSymbol ? *lhsSymbol : *rhsSymbol;
|
||||
Expression const &expr = lhsIsSymbol ? rhs : lhs; // Opposite side of `sym`
|
||||
|
||||
if (!sym.isDefined() || !expr.isKnown())
|
||||
if (!sym.isDefined() || !expr.isKnown()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assume(sym.isNumeric());
|
||||
|
||||
@@ -281,8 +280,9 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
|
||||
// The mask must not cover any unknown bits
|
||||
Section const § = *sym.getSection();
|
||||
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0)
|
||||
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// `sym.getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
@@ -418,38 +418,45 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
data = lval & rval;
|
||||
break;
|
||||
case RPN_SHL:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
}
|
||||
|
||||
if (rval >= 32)
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", rval);
|
||||
}
|
||||
|
||||
data = op_shift_left(lval, rval);
|
||||
break;
|
||||
case RPN_SHR:
|
||||
if (lval < 0)
|
||||
if (lval < 0) {
|
||||
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval);
|
||||
}
|
||||
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
}
|
||||
|
||||
if (rval >= 32)
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||
}
|
||||
|
||||
data = op_shift_right(lval, rval);
|
||||
break;
|
||||
case RPN_USHR:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval
|
||||
);
|
||||
}
|
||||
|
||||
if (rval >= 32)
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval);
|
||||
}
|
||||
|
||||
data = op_shift_right_unsigned(lval, rval);
|
||||
break;
|
||||
@@ -457,8 +464,9 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
data = static_cast<int32_t>(ulval * urval);
|
||||
break;
|
||||
case RPN_DIV:
|
||||
if (rval == 0)
|
||||
if (rval == 0) {
|
||||
fatalerror("Division by zero\n");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
warning(
|
||||
@@ -473,17 +481,20 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
}
|
||||
break;
|
||||
case RPN_MOD:
|
||||
if (rval == 0)
|
||||
if (rval == 0) {
|
||||
fatalerror("Modulo by zero\n");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1)
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
data = 0;
|
||||
else
|
||||
} else {
|
||||
data = op_modulo(lval, rval);
|
||||
}
|
||||
break;
|
||||
case RPN_EXP:
|
||||
if (rval < 0)
|
||||
if (rval < 0) {
|
||||
fatalerror("Exponentiation by negative power\n");
|
||||
}
|
||||
|
||||
data = op_exponent(lval, rval);
|
||||
break;
|
||||
@@ -560,9 +571,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
// Copy the right RPN and append the operator
|
||||
uint32_t rightRpnSize = src2.rpn.size();
|
||||
uint8_t *ptr = reserveSpace(rightRpnSize + 1, src2.rpnPatchSize + 1);
|
||||
if (rightRpnSize > 0)
|
||||
if (rightRpnSize > 0) {
|
||||
// If `rightRpnSize == 0`, then `memcpy(ptr, nullptr, rightRpnSize)` would be UB
|
||||
memcpy(ptr, src2.rpn.data(), rightRpnSize);
|
||||
}
|
||||
ptr[rightRpnSize] = op;
|
||||
}
|
||||
}
|
||||
@@ -595,8 +607,9 @@ void Expression::makeCheckRST() {
|
||||
|
||||
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||
void Expression::checkNBit(uint8_t n) const {
|
||||
if (isKnown())
|
||||
if (isKnown()) {
|
||||
::checkNBit(value(), n, "Expression");
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
@@ -49,9 +49,11 @@ static std::pair<Symbol const *, Symbol const *> currentLoadLabelScopes = {nullp
|
||||
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
|
||||
|
||||
// A quick check to see if we have an initialized section
|
||||
[[nodiscard]] static bool requireSection() {
|
||||
if (currentSection)
|
||||
[[nodiscard]]
|
||||
static bool requireSection() {
|
||||
if (currentSection) {
|
||||
return true;
|
||||
}
|
||||
|
||||
error("Cannot output data outside of a SECTION\n");
|
||||
return false;
|
||||
@@ -59,12 +61,15 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
|
||||
|
||||
// A quick check to see if we have an initialized section that can contain
|
||||
// this much initialized data
|
||||
[[nodiscard]] static bool requireCodeSection() {
|
||||
if (!requireSection())
|
||||
[[nodiscard]]
|
||||
static bool requireCodeSection() {
|
||||
if (!requireSection()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sect_HasData(currentSection->type))
|
||||
if (sect_HasData(currentSection->type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
error(
|
||||
"Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
||||
@@ -75,15 +80,16 @@ int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutput
|
||||
|
||||
void sect_CheckSizes() {
|
||||
for (Section const § : sectionList) {
|
||||
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize)
|
||||
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize) {
|
||||
error(
|
||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
||||
").\n",
|
||||
")\n",
|
||||
sect.name.c_str(),
|
||||
maxSize,
|
||||
sect.size
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section *sect_FindSectionByName(std::string const &name) {
|
||||
@@ -106,33 +112,36 @@ static unsigned int mergeSectUnion(
|
||||
|
||||
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
||||
// combination of both.
|
||||
if (sect_HasData(type))
|
||||
if (sect_HasData(type)) {
|
||||
sectError("Cannot declare ROM sections as UNION\n");
|
||||
}
|
||||
|
||||
if (org != UINT32_MAX) {
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != UINT32_MAX && sect.org != org)
|
||||
if (sect.org != UINT32_MAX && sect.org != org) {
|
||||
sectError(
|
||||
"Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org
|
||||
);
|
||||
else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs)))
|
||||
} else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs))) {
|
||||
sectError(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
else
|
||||
} else {
|
||||
// Otherwise, just override
|
||||
sect.org = org;
|
||||
}
|
||||
|
||||
} else if (alignment != 0) {
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - alignOffset) & mask(alignment))
|
||||
if ((sect.org - alignOffset) & mask(alignment)) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
sectError(
|
||||
@@ -163,34 +172,37 @@ static unsigned int
|
||||
uint16_t curOrg = org - sect.size;
|
||||
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != UINT32_MAX && sect.org != curOrg)
|
||||
if (sect.org != UINT32_MAX && sect.org != curOrg) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs)))
|
||||
} else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) {
|
||||
sectError(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
else
|
||||
} else {
|
||||
// Otherwise, just override
|
||||
sect.org = curOrg;
|
||||
}
|
||||
|
||||
} else if (alignment != 0) {
|
||||
int32_t curOfs = (alignOffset - sect.size) % (1U << alignment);
|
||||
|
||||
if (curOfs < 0)
|
||||
if (curOfs < 0) {
|
||||
curOfs += 1U << alignment;
|
||||
}
|
||||
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - curOfs) & mask(alignment))
|
||||
if ((sect.org - curOfs) & mask(alignment)) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
sectError(
|
||||
@@ -220,13 +232,14 @@ static void mergeSections(
|
||||
) {
|
||||
unsigned int nbSectErrors = 0;
|
||||
|
||||
if (type != sect.type)
|
||||
if (type != sect.type) {
|
||||
sectError(
|
||||
"Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (sect.modifier != mod) {
|
||||
sectError("Section already declared as %s section\n", sectionModNames[sect.modifier]);
|
||||
sectError("Section already declared as SECTION %s\n", sectionModNames[sect.modifier]);
|
||||
} else {
|
||||
switch (mod) {
|
||||
case SECTION_UNION:
|
||||
@@ -238,11 +251,13 @@ static void mergeSections(
|
||||
// Common checks
|
||||
|
||||
// If the section's bank is unspecified, override it
|
||||
if (sect.bank == UINT32_MAX)
|
||||
if (sect.bank == UINT32_MAX) {
|
||||
sect.bank = bank;
|
||||
}
|
||||
// If both specify a bank, it must be the same one
|
||||
else if (bank != UINT32_MAX && sect.bank != bank)
|
||||
else if (bank != UINT32_MAX && sect.bank != bank) {
|
||||
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
|
||||
}
|
||||
break;
|
||||
|
||||
case SECTION_NORMAL:
|
||||
@@ -253,13 +268,14 @@ static void mergeSections(
|
||||
}
|
||||
}
|
||||
|
||||
if (nbSectErrors)
|
||||
if (nbSectErrors) {
|
||||
fatalerror(
|
||||
"Cannot create section \"%s\" (%u error%s)\n",
|
||||
sect.name.c_str(),
|
||||
nbSectErrors,
|
||||
nbSectErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#undef sectError
|
||||
@@ -292,8 +308,9 @@ static Section *createSection(
|
||||
out_RegisterNode(sect.src);
|
||||
|
||||
// It is only needed to allocate memory for ROM sections.
|
||||
if (sect_HasData(type))
|
||||
if (sect_HasData(type)) {
|
||||
sect.data.resize(sectionTypeInfo[type].size);
|
||||
}
|
||||
|
||||
return §
|
||||
}
|
||||
@@ -314,9 +331,10 @@ static Section *getSection(
|
||||
|
||||
if (bank != UINT32_MAX) {
|
||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
||||
&& type != SECTTYPE_WRAMX)
|
||||
&& type != SECTTYPE_WRAMX) {
|
||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
||||
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
|
||||
} else if (bank < sectionTypeInfo[type].firstBank
|
||||
|| bank > sectionTypeInfo[type].lastBank) {
|
||||
error(
|
||||
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
|
||||
sectionTypeInfo[type].name.c_str(),
|
||||
@@ -324,6 +342,7 @@ static Section *getSection(
|
||||
sectionTypeInfo[type].firstBank,
|
||||
sectionTypeInfo[type].lastBank
|
||||
);
|
||||
}
|
||||
} else if (nbbanks(type) == 1) {
|
||||
// If the section type only has a single bank, implicitly force it
|
||||
bank = sectionTypeInfo[type].firstBank;
|
||||
@@ -339,7 +358,7 @@ static Section *getSection(
|
||||
}
|
||||
|
||||
if (org != UINT32_MAX) {
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) {
|
||||
error(
|
||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||
"; $%04" PRIx16 "]\n",
|
||||
@@ -349,6 +368,7 @@ static Section *getSection(
|
||||
endaddr(type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (alignment != 0) {
|
||||
if (alignment > 16) {
|
||||
@@ -359,8 +379,9 @@ static Section *getSection(
|
||||
uint32_t mask = mask(alignment);
|
||||
|
||||
if (org != UINT32_MAX) {
|
||||
if ((org - alignOffset) & mask)
|
||||
if ((org - alignOffset) & mask) {
|
||||
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str());
|
||||
}
|
||||
alignment = 0; // Ignore it if it's satisfied
|
||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||
error(
|
||||
@@ -393,26 +414,30 @@ static Section *getSection(
|
||||
|
||||
// Set the current section
|
||||
static void changeSection() {
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatalerror("Cannot change the section within a UNION\n");
|
||||
}
|
||||
|
||||
sym_ResetCurrentLabelScopes();
|
||||
}
|
||||
|
||||
bool Section::isSizeKnown() const {
|
||||
// SECTION UNION and SECTION FRAGMENT can still grow
|
||||
if (modifier != SECTION_NORMAL)
|
||||
if (modifier != SECTION_NORMAL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The current section (or current load section if within one) is still growing
|
||||
if (this == currentSection || this == currentLoadSection)
|
||||
if (this == currentSection || this == currentLoadSection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any section on the stack is still growing
|
||||
for (SectionStackEntry &entry : sectionStack) {
|
||||
if (entry.section && entry.section->name == name)
|
||||
if (entry.section && entry.section->name == name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -426,12 +451,14 @@ void sect_NewSection(
|
||||
SectionModifier mod
|
||||
) {
|
||||
for (SectionStackEntry &entry : sectionStack) {
|
||||
if (entry.section && entry.section->name == name)
|
||||
if (entry.section && entry.section->name == name) {
|
||||
fatalerror("Section '%s' is already on the stack\n", name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("SECTION");
|
||||
}
|
||||
|
||||
Section *sect = getSection(name, type, org, attrs, mod);
|
||||
|
||||
@@ -454,16 +481,18 @@ void sect_SetLoadSection(
|
||||
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
|
||||
// your own peril! ^^
|
||||
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sect_HasData(type)) {
|
||||
error("`LOAD` blocks cannot create a ROM section\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("LOAD");
|
||||
}
|
||||
|
||||
Section *sect = getSection(name, type, org, attrs, mod);
|
||||
|
||||
@@ -475,12 +504,11 @@ void sect_SetLoadSection(
|
||||
}
|
||||
|
||||
void sect_EndLoadSection(char const *cause) {
|
||||
if (cause)
|
||||
if (cause) {
|
||||
warning(
|
||||
WARNING_UNTERMINATED_LOAD,
|
||||
"`LOAD` block without `ENDL` terminated by `%s`\n",
|
||||
cause
|
||||
WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`\n", cause
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentLoadSection) {
|
||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
||||
@@ -495,8 +523,9 @@ void sect_EndLoadSection(char const *cause) {
|
||||
}
|
||||
|
||||
void sect_CheckLoadClosed() {
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
|
||||
}
|
||||
}
|
||||
|
||||
Section *sect_GetSymbolSection() {
|
||||
@@ -515,16 +544,18 @@ uint32_t sect_GetOutputOffset() {
|
||||
// Returns how many bytes need outputting for the specified alignment and offset to succeed
|
||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
if (!sect)
|
||||
if (!sect) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool isFixed = sect->org != UINT32_MAX;
|
||||
|
||||
// If the section is not aligned, no bytes are needed
|
||||
// (fixed sections count as being maximally aligned for this purpose)
|
||||
uint8_t curAlignment = isFixed ? 16 : sect->align;
|
||||
if (curAlignment == 0)
|
||||
if (curAlignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
|
||||
uint16_t pcValue = isFixed ? sect->org : sect->alignOfs;
|
||||
@@ -533,18 +564,20 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
}
|
||||
|
||||
void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
if (!requireSection())
|
||||
if (!requireSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
|
||||
|
||||
if (sect->org != UINT32_MAX) {
|
||||
if ((sect->org + curOffset - offset) % alignSize)
|
||||
if ((sect->org + curOffset - offset) % alignSize) {
|
||||
error(
|
||||
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
||||
sect->org + curOffset
|
||||
);
|
||||
}
|
||||
} else if (sect->align != 0
|
||||
&& (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
||||
error(
|
||||
@@ -568,18 +601,22 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
}
|
||||
|
||||
static void growSection(uint32_t growth) {
|
||||
if (growth > 0 && curOffset > UINT32_MAX - growth)
|
||||
if (growth > 0 && curOffset > UINT32_MAX - growth) {
|
||||
fatalerror("Section size would overflow internal counter\n");
|
||||
}
|
||||
curOffset += growth;
|
||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size)
|
||||
if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) {
|
||||
currentSection->size = outOffset;
|
||||
if (currentLoadSection && curOffset > currentLoadSection->size)
|
||||
}
|
||||
if (currentLoadSection && curOffset > currentLoadSection->size) {
|
||||
currentLoadSection->size = curOffset;
|
||||
}
|
||||
}
|
||||
|
||||
static void writeByte(uint8_t byte) {
|
||||
if (uint32_t index = sect_GetOutputOffset(); index < currentSection->data.size())
|
||||
if (uint32_t index = sect_GetOutputOffset(); index < currentSection->data.size()) {
|
||||
currentSection->data[index] = byte;
|
||||
}
|
||||
growSection(1);
|
||||
}
|
||||
|
||||
@@ -621,8 +658,9 @@ static void endUnionMember() {
|
||||
UnionStackEntry &member = currentUnionStack.top();
|
||||
uint32_t memberSize = curOffset - member.start;
|
||||
|
||||
if (memberSize > member.size)
|
||||
if (memberSize > member.size) {
|
||||
member.size = memberSize;
|
||||
}
|
||||
curOffset = member.start;
|
||||
}
|
||||
|
||||
@@ -645,64 +683,75 @@ void sect_EndUnion() {
|
||||
}
|
||||
|
||||
void sect_CheckUnionClosed() {
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
error("Unterminated UNION construct\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Output a constant byte
|
||||
void sect_ConstByte(uint8_t byte) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
writeByte(byte);
|
||||
}
|
||||
|
||||
// Output a string's character units as bytes
|
||||
void sect_ByteString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 8, "All character units"))
|
||||
break;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 8, "All character units")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
writeByte(static_cast<uint8_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a string's character units as words
|
||||
void sect_WordString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 16, "All character units"))
|
||||
break;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
if (!checkNBit(unit, 16, "All character units")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t unit : string) {
|
||||
writeWord(static_cast<uint16_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a string's character units as longs
|
||||
void sect_LongString(std::vector<int32_t> const &string) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t unit : string)
|
||||
for (int32_t unit : string) {
|
||||
writeLong(static_cast<uint32_t>(unit));
|
||||
}
|
||||
}
|
||||
|
||||
// Skip this many bytes
|
||||
void sect_Skip(uint32_t skip, bool ds) {
|
||||
if (!requireSection())
|
||||
if (!requireSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sect_HasData(currentSection->type)) {
|
||||
growSection(skip);
|
||||
} else {
|
||||
if (!ds)
|
||||
if (!ds) {
|
||||
warning(
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
"%s directive without data in ROM\n",
|
||||
@@ -710,16 +759,19 @@ void sect_Skip(uint32_t skip, bool ds) {
|
||||
: (skip == 2) ? "DW"
|
||||
: "DB"
|
||||
);
|
||||
}
|
||||
// We know we're in a code SECTION
|
||||
while (skip--)
|
||||
while (skip--) {
|
||||
writeByte(fillByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output a byte that can be relocatable or constant
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
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
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
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
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
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
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expr.isKnown()) {
|
||||
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
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Symbol const *pc = sym_GetPC(); !expr.isDiffConstant(pc)) {
|
||||
createPatch(PATCHTYPE_JR, expr, pcShift);
|
||||
@@ -786,15 +842,16 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
int16_t offset;
|
||||
|
||||
// Offset is relative to the byte *after* the operand
|
||||
if (sym == pc)
|
||||
if (sym == pc) {
|
||||
offset = -2; // PC as operand to `jr` is lower than reference PC by 2
|
||||
else
|
||||
} else {
|
||||
offset = sym->getValue() - (pc->getValue() + 1);
|
||||
}
|
||||
|
||||
if (offset < -128 || offset > 127) {
|
||||
error(
|
||||
"jr target must be between -128 and 127 bytes away, not %" PRId16
|
||||
"; use jp instead\n",
|
||||
"JR target must be between -128 and 127 bytes away, not %" PRId16
|
||||
"; use JP instead\n",
|
||||
offset
|
||||
);
|
||||
writeByte(0);
|
||||
@@ -810,16 +867,19 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
||||
startPos = 0;
|
||||
}
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file = nullptr;
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath)
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
file = fopen(fullPath->c_str(), "rb");
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
@@ -836,10 +896,11 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
// The file is seekable; skip to the specified start position
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
if (errno != ESPIPE) {
|
||||
error(
|
||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||
);
|
||||
}
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
@@ -851,11 +912,13 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int byte; (byte = fgetc(file)) != EOF;)
|
||||
for (int byte; (byte = fgetc(file)) != EOF;) {
|
||||
writeByte(byte);
|
||||
}
|
||||
|
||||
if (ferror(file))
|
||||
if (ferror(file)) {
|
||||
error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
// Output a slice of a binary file
|
||||
@@ -868,18 +931,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);
|
||||
length = 0;
|
||||
}
|
||||
if (!requireCodeSection())
|
||||
if (!requireCodeSection()) {
|
||||
return;
|
||||
if (length == 0) // Don't even bother with 0-byte slices
|
||||
}
|
||||
if (length == 0) { // Don't even bother with 0-byte slices
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file = nullptr;
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath)
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
file = fopen(fullPath->c_str(), "rb");
|
||||
}
|
||||
if (!file) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||
}
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
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
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
if (errno != ESPIPE) {
|
||||
error(
|
||||
"Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno)
|
||||
);
|
||||
}
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
@@ -955,11 +1023,13 @@ void sect_PushSection() {
|
||||
}
|
||||
|
||||
void sect_PopSection() {
|
||||
if (sectionStack.empty())
|
||||
if (sectionStack.empty()) {
|
||||
fatalerror("No entries in the section stack\n");
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("POPS");
|
||||
}
|
||||
|
||||
SectionStackEntry entry = sectionStack.front();
|
||||
sectionStack.pop_front();
|
||||
@@ -980,14 +1050,17 @@ void sect_CheckStack() {
|
||||
}
|
||||
|
||||
void sect_EndSection() {
|
||||
if (!currentSection)
|
||||
if (!currentSection) {
|
||||
fatalerror("Cannot end the section outside of a SECTION\n");
|
||||
}
|
||||
|
||||
if (!currentUnionStack.empty())
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatalerror("Cannot end the section within a UNION\n");
|
||||
}
|
||||
|
||||
if (currentLoadSection)
|
||||
if (currentLoadSection) {
|
||||
sect_EndLoadSection("ENDSECTION");
|
||||
}
|
||||
|
||||
// Reset the section scope
|
||||
currentSection = nullptr;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/symbol.hpp"
|
||||
|
||||
@@ -40,8 +40,9 @@ bool sym_IsPC(Symbol const *sym) {
|
||||
}
|
||||
|
||||
void sym_ForEach(void (*callback)(Symbol &)) {
|
||||
for (auto &it : symbols)
|
||||
for (auto &it : symbols) {
|
||||
callback(it.second);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t NARGCallback() {
|
||||
@@ -92,7 +93,7 @@ int32_t Symbol::getOutputValue() const {
|
||||
}
|
||||
|
||||
ContentSpan const &Symbol::getMacro() const {
|
||||
assume((std::holds_alternative<ContentSpan>(data)));
|
||||
assume(std::holds_alternative<ContentSpan>(data));
|
||||
return std::get<ContentSpan>(data);
|
||||
}
|
||||
|
||||
@@ -101,8 +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)
|
||||
);
|
||||
if (auto *callback = std::get_if<std::shared_ptr<std::string> (*)()>(&data); callback)
|
||||
if (auto *callback = std::get_if<std::shared_ptr<std::string> (*)()>(&data); callback) {
|
||||
return (*callback)();
|
||||
}
|
||||
return std::get<std::shared_ptr<std::string>>(data);
|
||||
}
|
||||
|
||||
@@ -123,8 +125,9 @@ static void updateSymbolFilename(Symbol &sym) {
|
||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||
|
||||
// If the old node was registered, ensure the new one is too
|
||||
if (oldSrc && oldSrc->ID != UINT32_MAX)
|
||||
if (oldSrc && oldSrc->ID != UINT32_MAX) {
|
||||
out_RegisterNode(sym.src);
|
||||
}
|
||||
}
|
||||
|
||||
static 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());
|
||||
} else {
|
||||
error("'%s' already defined", sym.name.c_str());
|
||||
if (asType)
|
||||
if (asType) {
|
||||
fprintf(stderr, " as %s", asType);
|
||||
}
|
||||
fputs(" at ", stderr);
|
||||
dumpFilename(sym);
|
||||
}
|
||||
@@ -184,28 +188,34 @@ static bool isAutoScoped(std::string const &symName) {
|
||||
size_t dotPos = symName.find('.');
|
||||
|
||||
// If there are no dots, it's not a local label
|
||||
if (dotPos == std::string::npos)
|
||||
if (dotPos == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
||||
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos)
|
||||
if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for nothing after the dot
|
||||
if (dotPos == symName.length() - 1)
|
||||
if (dotPos == symName.length() - 1) {
|
||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str());
|
||||
}
|
||||
|
||||
// Check for more than one dot
|
||||
if (symName.find('.', dotPos + 1) != std::string::npos)
|
||||
if (symName.find('.', dotPos + 1) != std::string::npos) {
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str());
|
||||
}
|
||||
|
||||
// Check for already-qualified local label
|
||||
if (dotPos > 0)
|
||||
if (dotPos > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for unqualifiable local label
|
||||
if (!globalScope)
|
||||
if (!globalScope) {
|
||||
fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -252,24 +262,28 @@ void sym_Purge(std::string const &symName) {
|
||||
Symbol *sym = sym_FindScopedValidSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
if (sym_IsPurgedScoped(symName))
|
||||
if (sym_IsPurgedScoped(symName)) {
|
||||
error("'%s' was already purged\n", symName.c_str());
|
||||
else
|
||||
} else {
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
}
|
||||
} else if (sym->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
|
||||
} else if (sym->ID != UINT32_MAX) {
|
||||
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
|
||||
} else {
|
||||
if (sym->isExported)
|
||||
if (sym->isExported) {
|
||||
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str());
|
||||
else if (sym->isLabel())
|
||||
} else if (sym->isLabel()) {
|
||||
warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str());
|
||||
}
|
||||
// Do not keep a reference to the label after purging it
|
||||
if (sym == globalScope)
|
||||
if (sym == globalScope) {
|
||||
globalScope = nullptr;
|
||||
if (sym == localScope)
|
||||
}
|
||||
if (sym == localScope) {
|
||||
localScope = nullptr;
|
||||
}
|
||||
purgedSymbols.emplace(sym->name);
|
||||
symbols.erase(sym->name);
|
||||
}
|
||||
@@ -295,14 +309,16 @@ void sym_SetRSValue(int32_t value) {
|
||||
}
|
||||
|
||||
uint32_t Symbol::getConstantValue() const {
|
||||
if (isConstant())
|
||||
if (isConstant()) {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
if (sym_IsPC(this)) {
|
||||
if (!getSection())
|
||||
if (!getSection()) {
|
||||
error("PC has no value outside of a section\n");
|
||||
else
|
||||
} else {
|
||||
error("PC does not have a constant value; the current section is not fixed\n");
|
||||
}
|
||||
} else {
|
||||
error("\"%s\" does not have a constant value\n", name.c_str());
|
||||
}
|
||||
@@ -310,13 +326,15 @@ uint32_t Symbol::getConstantValue() const {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (sym_IsPurgedScoped(symName))
|
||||
if (sym_IsPurgedScoped(symName)) {
|
||||
error("'%s' not defined; it was purged\n", symName.c_str());
|
||||
else
|
||||
} else {
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
}
|
||||
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 = createNonrelocSymbol(symName, true);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_EQU;
|
||||
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 = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return sym_AddEqu(symName, value);
|
||||
}
|
||||
|
||||
if (sym->isDefined() && sym->type != SYM_EQU) {
|
||||
alreadyDefinedError(*sym, "non-EQU");
|
||||
@@ -394,8 +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 = createNonrelocSymbol(symName, false);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_EQUS;
|
||||
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 = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return sym_AddString(symName, str);
|
||||
}
|
||||
|
||||
if (sym->type != SYM_EQUS) {
|
||||
if (sym->isDefined()) {
|
||||
@@ -462,12 +484,14 @@ static Symbol *addLabel(std::string const &symName) {
|
||||
sym->type = SYM_LABEL;
|
||||
sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
|
||||
// Don't export anonymous labels
|
||||
if (exportAll && !symName.starts_with('!'))
|
||||
if (exportAll && !symName.starts_with('!')) {
|
||||
sym->isExported = true;
|
||||
}
|
||||
sym->section = sect_GetSymbolSection();
|
||||
|
||||
if (sym && !sym->section)
|
||||
if (sym && !sym->section) {
|
||||
error("Label \"%s\" created outside of a SECTION\n", symName.c_str());
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
@@ -478,8 +502,9 @@ Symbol *sym_AddLocalLabel(std::string const &symName) {
|
||||
|
||||
Symbol *sym = addLabel(isAutoScoped(symName) ? globalScope->name + symName : symName);
|
||||
|
||||
if (sym)
|
||||
if (sym) {
|
||||
localScope = sym;
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
@@ -516,7 +541,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
uint32_t id = 0;
|
||||
|
||||
if (neg) {
|
||||
if (ofs > anonLabelID)
|
||||
if (ofs > anonLabelID) {
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
||||
" ha%s been created so far\n",
|
||||
@@ -524,20 +549,22 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
anonLabelID,
|
||||
anonLabelID == 1 ? "s" : "ve"
|
||||
);
|
||||
else
|
||||
} else {
|
||||
id = anonLabelID - ofs;
|
||||
}
|
||||
} else {
|
||||
ofs--; // We're referencing symbols that haven't been created yet...
|
||||
if (ofs > UINT32_MAX - anonLabelID)
|
||||
if (ofs > UINT32_MAX - anonLabelID) {
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||
" may still be created\n",
|
||||
ofs + 1,
|
||||
UINT32_MAX - anonLabelID
|
||||
);
|
||||
else
|
||||
} else {
|
||||
id = anonLabelID + ofs;
|
||||
}
|
||||
}
|
||||
|
||||
std::string anon("!");
|
||||
anon += std::to_string(id);
|
||||
@@ -553,16 +580,18 @@ void sym_Export(std::string const &symName) {
|
||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
|
||||
// If the symbol doesn't exist, create a ref that can be purged
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
sym = sym_Ref(symName);
|
||||
}
|
||||
sym->isExported = true;
|
||||
}
|
||||
|
||||
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
if (!sym)
|
||||
if (!sym) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sym->type = SYM_MACRO;
|
||||
sym->data = span;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
@@ -35,13 +35,13 @@ struct WarningFlag {
|
||||
WarningLevel level;
|
||||
};
|
||||
|
||||
static const WarningFlag metaWarnings[] = {
|
||||
static WarningFlag const metaWarnings[] = {
|
||||
{"all", LEVEL_ALL },
|
||||
{"extra", LEVEL_EXTRA },
|
||||
{"everything", LEVEL_EVERYTHING},
|
||||
};
|
||||
|
||||
static const WarningFlag warningFlags[NB_WARNINGS] = {
|
||||
static WarningFlag const warningFlags[NB_WARNINGS] = {
|
||||
{"assert", LEVEL_DEFAULT },
|
||||
{"backwards-for", LEVEL_ALL },
|
||||
{"builtin-args", LEVEL_ALL },
|
||||
@@ -85,8 +85,9 @@ enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
||||
|
||||
static WarningBehavior getWarningBehavior(WarningID id) {
|
||||
// Check if warnings are globally disabled
|
||||
if (!warnings)
|
||||
if (!warnings) {
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
|
||||
// Get the state of this warning flag
|
||||
WarningState const &flagState = warningStates.flagStates[id];
|
||||
@@ -100,34 +101,43 @@ static WarningBehavior getWarningBehavior(WarningID id) {
|
||||
warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED;
|
||||
|
||||
// First, check the state of the specific warning flag
|
||||
if (flagState.state == WARNING_DISABLED) // -Wno-<flag>
|
||||
if (flagState.state == WARNING_DISABLED) { // -Wno-<flag>
|
||||
return WarningBehavior::DISABLED;
|
||||
if (flagState.error == WARNING_ENABLED) // -Werror=<flag>
|
||||
}
|
||||
if (flagState.error == WARNING_ENABLED) { // -Werror=<flag>
|
||||
return WarningBehavior::ERROR;
|
||||
if (flagState.state == WARNING_ENABLED) // -W<flag>
|
||||
}
|
||||
if (flagState.state == WARNING_ENABLED) { // -W<flag>
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// If no flag is specified, check the state of the "meta" flags that affect this warning flag
|
||||
if (metaState.state == WARNING_DISABLED) // -Wno-<meta>
|
||||
if (metaState.state == WARNING_DISABLED) { // -Wno-<meta>
|
||||
return WarningBehavior::DISABLED;
|
||||
if (metaState.error == WARNING_ENABLED) // -Werror=<meta>
|
||||
}
|
||||
if (metaState.error == WARNING_ENABLED) { // -Werror=<meta>
|
||||
return WarningBehavior::ERROR;
|
||||
if (metaState.state == WARNING_ENABLED) // -W<meta>
|
||||
}
|
||||
if (metaState.state == WARNING_ENABLED) { // -W<meta>
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// If no meta flag is specified, check the default state of this warning flag
|
||||
if (warningFlags[id].level == LEVEL_DEFAULT) // enabled by default
|
||||
if (warningFlags[id].level == LEVEL_DEFAULT) { // enabled by default
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
// No flag enables this warning, explicitly or implicitly
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
|
||||
void WarningState::update(WarningState other) {
|
||||
if (other.state != WARNING_DEFAULT)
|
||||
if (other.state != WARNING_DEFAULT) {
|
||||
state = other.state;
|
||||
if (other.error != WARNING_DEFAULT)
|
||||
}
|
||||
if (other.error != WARNING_DEFAULT) {
|
||||
error = other.error;
|
||||
}
|
||||
}
|
||||
|
||||
void processWarningFlag(char const *flag) {
|
||||
@@ -149,16 +159,16 @@ void processWarningFlag(char const *flag) {
|
||||
if (rootFlag.starts_with("error=")) {
|
||||
// `-Werror=<flag>` enables the flag as an error
|
||||
state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED};
|
||||
rootFlag.erase(0, QUOTEDSTRLEN("error="));
|
||||
rootFlag.erase(0, literal_strlen("error="));
|
||||
} else if (rootFlag.starts_with("no-error=")) {
|
||||
// `-Wno-error=<flag>` prevents the flag from being an error,
|
||||
// without affecting whether it is enabled
|
||||
state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED};
|
||||
rootFlag.erase(0, QUOTEDSTRLEN("no-error="));
|
||||
rootFlag.erase(0, literal_strlen("no-error="));
|
||||
} else if (rootFlag.starts_with("no-")) {
|
||||
// `-Wno-<flag>` disables the flag
|
||||
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
|
||||
rootFlag.erase(0, QUOTEDSTRLEN("no-"));
|
||||
rootFlag.erase(0, literal_strlen("no-"));
|
||||
} else {
|
||||
// `-W<flag>` enables the flag
|
||||
state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
|
||||
@@ -184,12 +194,14 @@ void processWarningFlag(char const *flag) {
|
||||
// The `if`'s condition above ensures that this will run at least once
|
||||
do {
|
||||
// If we don't have a digit, bail
|
||||
if (*ptr < '0' || *ptr > '9')
|
||||
if (*ptr < '0' || *ptr > '9') {
|
||||
break;
|
||||
}
|
||||
// Avoid overflowing!
|
||||
if (param > UINT8_MAX - (*ptr - '0')) {
|
||||
if (!warned)
|
||||
if (!warned) {
|
||||
warnx("Invalid warning flag \"%s\": capping parameter at 255", flag);
|
||||
}
|
||||
warned = true; // Only warn once, cap always
|
||||
param = 255;
|
||||
continue;
|
||||
@@ -203,11 +215,12 @@ void processWarningFlag(char const *flag) {
|
||||
if (*ptr == '\0') {
|
||||
rootFlag.resize(equals);
|
||||
// `-W<flag>=0` is equivalent to `-Wno-<flag>`
|
||||
if (param == 0)
|
||||
if (param == 0) {
|
||||
state.state = WARNING_DISABLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to match the flag against a parametric warning
|
||||
// If there was an equals sign, it will have set `param`; if not, `param` will be 0, which
|
||||
@@ -218,8 +231,9 @@ void processWarningFlag(char const *flag) {
|
||||
assume(paramWarning.defaultLevel <= maxParam);
|
||||
|
||||
if (rootFlag == warningFlags[baseID].name) { // Match!
|
||||
if (rootFlag == "numeric-string")
|
||||
if (rootFlag == "numeric-string") {
|
||||
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
|
||||
}
|
||||
|
||||
// If making the warning an error but param is 0, set to the maximum
|
||||
// This accommodates `-Werror=<flag>`, but also `-Werror=<flag>=0`, which is
|
||||
@@ -229,7 +243,7 @@ void processWarningFlag(char const *flag) {
|
||||
if (param == 0) {
|
||||
param = paramWarning.defaultLevel;
|
||||
} else if (param > maxParam) {
|
||||
if (param != 255) // Don't warn if already capped
|
||||
if (param != 255) { // Don't warn if already capped
|
||||
warnx(
|
||||
"Invalid parameter %" PRIu8
|
||||
" for warning flag \"%s\"; capping at maximum %" PRIu8,
|
||||
@@ -237,17 +251,19 @@ void processWarningFlag(char const *flag) {
|
||||
rootFlag.c_str(),
|
||||
maxParam
|
||||
);
|
||||
}
|
||||
param = maxParam;
|
||||
}
|
||||
|
||||
// Set the first <param> to enabled/error, and disable the rest
|
||||
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
||||
WarningState &warning = warningStates.flagStates[baseID + ofs];
|
||||
if (ofs < param)
|
||||
if (ofs < param) {
|
||||
warning.update(state);
|
||||
else
|
||||
} else {
|
||||
warning.state = WARNING_DISABLED;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -259,9 +275,10 @@ void processWarningFlag(char const *flag) {
|
||||
if (rootFlag == metaWarning.name) {
|
||||
// Set each of the warning flags that meets this level
|
||||
for (WarningID id : EnumSeq(NB_WARNINGS)) {
|
||||
if (metaWarning.level >= warningFlags[id].level)
|
||||
if (metaWarning.level >= warningFlags[id].level) {
|
||||
warningStates.metaStates[id].update(state);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -299,16 +316,18 @@ void error(char const *fmt, ...) {
|
||||
|
||||
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
|
||||
nbErrors++;
|
||||
if (nbErrors == maxErrors)
|
||||
if (nbErrors == maxErrors) {
|
||||
errx(
|
||||
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
|
||||
"aborted!",
|
||||
maxErrors,
|
||||
maxErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void fatalerror(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void fatalerror(char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "error.hpp"
|
||||
|
||||
@@ -22,7 +22,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
[[noreturn]] static void verr(char const *fmt, va_list ap) {
|
||||
[[noreturn]]
|
||||
static void verr(char const *fmt, va_list ap) {
|
||||
char const *error = strerror(errno);
|
||||
|
||||
fprintf(stderr, "error: ");
|
||||
@@ -32,7 +33,8 @@ static void vwarnx(char const *fmt, va_list ap) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
[[noreturn]] static void verrx(char const *fmt, va_list ap) {
|
||||
[[noreturn]]
|
||||
static void verrx(char const *fmt, va_list ap) {
|
||||
fprintf(stderr, "error: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
putc('\n', stderr);
|
||||
@@ -56,14 +58,16 @@ void warnx(char const *fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
[[noreturn]] void err(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void err(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
verr(fmt, ap);
|
||||
}
|
||||
|
||||
[[noreturn]] void errx(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void errx(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
|
||||
77
src/extern/getopt.cpp
vendored
77
src/extern/getopt.cpp
vendored
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* This implementation was taken from musl and modified for RGBDS */
|
||||
// This implementation was taken from musl and modified for RGBDS
|
||||
|
||||
#include "extern/getopt.hpp"
|
||||
|
||||
@@ -19,8 +19,9 @@ static int musl_optpos;
|
||||
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
|
||||
FILE *f = stderr;
|
||||
|
||||
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l)
|
||||
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l) {
|
||||
putc('\n', f);
|
||||
}
|
||||
}
|
||||
|
||||
static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
@@ -35,8 +36,9 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
musl_optind = 1;
|
||||
}
|
||||
|
||||
if (musl_optind >= argc || !argv[musl_optind])
|
||||
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argv[musl_optind][0] != '-') {
|
||||
if (optstring[0] == '-') {
|
||||
@@ -46,18 +48,21 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!argv[musl_optind][1])
|
||||
if (!argv[musl_optind][1]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2])
|
||||
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2]) {
|
||||
return musl_optind++, -1;
|
||||
}
|
||||
|
||||
if (!musl_optpos)
|
||||
if (!musl_optpos) {
|
||||
musl_optpos++;
|
||||
}
|
||||
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
||||
if (k < 0) {
|
||||
k = 1;
|
||||
c = 0xFFFD; /* replacement char */
|
||||
c = 0xFFFD; // replacement char
|
||||
}
|
||||
optchar = argv[musl_optind] + musl_optpos;
|
||||
musl_optpos += k;
|
||||
@@ -67,23 +72,26 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
musl_optpos = 0;
|
||||
}
|
||||
|
||||
if (optstring[0] == '-' || optstring[0] == '+')
|
||||
if (optstring[0] == '-' || optstring[0] == '+') {
|
||||
optstring++;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
d = 0;
|
||||
do {
|
||||
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
||||
if (l > 0)
|
||||
if (l > 0) {
|
||||
i += l;
|
||||
else
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
} while (l && d != c);
|
||||
|
||||
if (d != c || c == ':') {
|
||||
musl_optopt = c;
|
||||
if (optstring[0] != ':' && musl_opterr)
|
||||
if (optstring[0] != ':' && musl_opterr) {
|
||||
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
if (optstring[i] == ':') {
|
||||
@@ -94,10 +102,12 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
}
|
||||
if (musl_optind > argc) {
|
||||
musl_optopt = c;
|
||||
if (optstring[0] == ':')
|
||||
if (optstring[0] == ':') {
|
||||
return ':';
|
||||
if (musl_opterr)
|
||||
}
|
||||
if (musl_opterr) {
|
||||
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
@@ -108,8 +118,9 @@ static void permute(char **argv, int dest, int src) {
|
||||
char *tmp = argv[src];
|
||||
int i;
|
||||
|
||||
for (i = src; i > dest; i--)
|
||||
for (i = src; i > dest; i--) {
|
||||
argv[i] = argv[i - 1];
|
||||
}
|
||||
argv[dest] = tmp;
|
||||
}
|
||||
|
||||
@@ -128,18 +139,21 @@ static int musl_getopt_long(
|
||||
musl_optind = 1;
|
||||
}
|
||||
|
||||
if (musl_optind >= argc || !argv[musl_optind])
|
||||
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
skipped = musl_optind;
|
||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||
int i;
|
||||
for (i = musl_optind;; i++) {
|
||||
if (i >= argc || !argv[i])
|
||||
if (i >= argc || !argv[i]) {
|
||||
return -1;
|
||||
if (argv[i][0] == '-' && argv[i][1])
|
||||
}
|
||||
if (argv[i][0] == '-' && argv[i][1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
musl_optind = i;
|
||||
}
|
||||
resumed = musl_optind;
|
||||
@@ -147,8 +161,9 @@ static int musl_getopt_long(
|
||||
if (resumed > skipped) {
|
||||
int i, cnt = musl_optind - resumed;
|
||||
|
||||
for (i = 0; i < cnt; i++)
|
||||
for (i = 0; i < cnt; i++) {
|
||||
permute(argv, skipped, musl_optind - 1);
|
||||
}
|
||||
musl_optind = skipped + cnt;
|
||||
}
|
||||
return ret;
|
||||
@@ -169,14 +184,16 @@ static int musl_getopt_long_core(
|
||||
char const *name = longopts[i].name;
|
||||
|
||||
opt = start;
|
||||
if (*opt == '-')
|
||||
if (*opt == '-') {
|
||||
opt++;
|
||||
}
|
||||
while (*opt && *opt != '=' && *opt == *name) {
|
||||
name++;
|
||||
opt++;
|
||||
}
|
||||
if (*opt && *opt != '=')
|
||||
if (*opt && *opt != '=') {
|
||||
continue;
|
||||
}
|
||||
arg = opt;
|
||||
match = i;
|
||||
if (!*name) {
|
||||
@@ -191,8 +208,9 @@ static int musl_getopt_long_core(
|
||||
for (i = 0; optstring[i]; i++) {
|
||||
int j = 0;
|
||||
|
||||
while (j < l && start[j] == optstring[i + j])
|
||||
while (j < l && start[j] == optstring[i + j]) {
|
||||
j++;
|
||||
}
|
||||
if (j == l) {
|
||||
cnt++;
|
||||
break;
|
||||
@@ -206,8 +224,9 @@ static int musl_getopt_long_core(
|
||||
if (*opt == '=') {
|
||||
if (!longopts[i].has_arg) {
|
||||
musl_optopt = longopts[i].val;
|
||||
if (colon || !musl_opterr)
|
||||
if (colon || !musl_opterr) {
|
||||
return '?';
|
||||
}
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option does not take an argument: ",
|
||||
@@ -221,10 +240,12 @@ static int musl_getopt_long_core(
|
||||
musl_optarg = argv[musl_optind];
|
||||
if (!musl_optarg) {
|
||||
musl_optopt = longopts[i].val;
|
||||
if (colon)
|
||||
if (colon) {
|
||||
return ':';
|
||||
if (!musl_opterr)
|
||||
}
|
||||
if (!musl_opterr) {
|
||||
return '?';
|
||||
}
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option requires an argument: ",
|
||||
@@ -235,8 +256,9 @@ static int musl_getopt_long_core(
|
||||
}
|
||||
musl_optind++;
|
||||
}
|
||||
if (idx)
|
||||
if (idx) {
|
||||
*idx = i;
|
||||
}
|
||||
if (longopts[i].flag) {
|
||||
*longopts[i].flag = longopts[i].val;
|
||||
return 0;
|
||||
@@ -245,13 +267,14 @@ static int musl_getopt_long_core(
|
||||
}
|
||||
if (argv[musl_optind][1] == '-') {
|
||||
musl_optopt = 0;
|
||||
if (!colon && musl_opterr)
|
||||
if (!colon && musl_opterr) {
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
|
||||
argv[musl_optind] + 2,
|
||||
strlen(argv[musl_optind] + 2)
|
||||
);
|
||||
}
|
||||
musl_optind++;
|
||||
return '?';
|
||||
}
|
||||
|
||||
54
src/extern/utf8decoder.cpp
vendored
54
src/extern/utf8decoder.cpp
vendored
@@ -1,35 +1,35 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/* UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */
|
||||
// UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
|
||||
static uint8_t const utf8d[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00..0f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10..1f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20..2f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 30..3f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40..4f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50..5f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60..6f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70..7f */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80..8f */
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, /* 90..9f */
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* a0..af */
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* b0..bf */
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* c0..cf */
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* d0..df */
|
||||
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, /* e0..ef */
|
||||
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* f0..ff */
|
||||
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, /* s0 */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, /* s3 */
|
||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, /* s4 */
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, /* s5 */
|
||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s6 */
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s7 */
|
||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s8 */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..0f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10..1f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..2f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 30..3f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..4f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 50..5f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..6f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 70..7f
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80..8f
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 90..9f
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..af
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // b0..bf
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..cf
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // d0..df
|
||||
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, // e0..ef
|
||||
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // f0..ff
|
||||
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, // s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s1
|
||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, // s3
|
||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, // s5
|
||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s7
|
||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s8
|
||||
};
|
||||
|
||||
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) {
|
||||
|
||||
317
src/fix/main.cpp
317
src/fix/main.cpp
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -17,27 +17,26 @@
|
||||
#include "platform.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#define UNSPECIFIED 0x200 // Should not be in byte range
|
||||
static constexpr uint16_t UNSPECIFIED = 0x200;
|
||||
static_assert(UNSPECIFIED > 0xFF, "UNSPECIFIED should not be in byte range!");
|
||||
|
||||
#define BANK_SIZE 0x4000
|
||||
static constexpr off_t BANK_SIZE = 0x4000;
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "Ccf:i:jk:L:l:m:n:Op:r:st:Vv";
|
||||
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Op:r:st:Vv";
|
||||
|
||||
/*
|
||||
* Equivalent long options
|
||||
* Please keep in the same order as short opts
|
||||
*
|
||||
* Also, make sure long opts don't create ambiguity:
|
||||
* A long opt's name should start with the same letter as its short opt,
|
||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
* This is because long opt matching, even to a single char, is prioritized
|
||||
* over short opt matching
|
||||
*/
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"color-only", no_argument, nullptr, 'C'},
|
||||
{"color-compatible", no_argument, nullptr, 'c'},
|
||||
{"fix-spec", required_argument, nullptr, 'f'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"game-id", required_argument, nullptr, 'i'},
|
||||
{"non-japanese", no_argument, nullptr, 'j'},
|
||||
{"new-licensee", required_argument, nullptr, 'k'},
|
||||
@@ -57,7 +56,7 @@ static option const longopts[] = {
|
||||
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
"Usage: rgbfix [-hjOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
" [-L <logo_file>] [-l <licensee_byte>] [-m <mbc_type>]\n"
|
||||
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
|
||||
" <file> ...\n"
|
||||
@@ -76,15 +75,17 @@ static void printUsage() {
|
||||
|
||||
static uint8_t nbErrors;
|
||||
|
||||
[[gnu::format(printf, 1, 2)]] static void report(char const *fmt, ...) {
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
static void report(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (nbErrors != UINT8_MAX)
|
||||
if (nbErrors != UINT8_MAX) {
|
||||
nbErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
enum MbcType {
|
||||
@@ -184,24 +185,24 @@ static void printAcceptedMBCNames() {
|
||||
|
||||
static uint8_t tpp1Rev[2];
|
||||
|
||||
/*
|
||||
* @return False on failure
|
||||
*/
|
||||
static bool readMBCSlice(char const *&name, char const *expected) {
|
||||
while (*expected) {
|
||||
char c = *name++;
|
||||
|
||||
if (c == '\0') // Name too short
|
||||
if (c == '\0') { // Name too short
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c >= 'a' && c <= 'z') // Perform the comparison case-insensitive
|
||||
if (c >= 'a' && c <= 'z') { // Perform the comparison case-insensitive
|
||||
c = c - 'a' + 'A';
|
||||
else if (c == '_') // Treat underscores as spaces
|
||||
} else if (c == '_') { // Treat underscores as spaces
|
||||
c = ' ';
|
||||
}
|
||||
|
||||
if (c != *expected++)
|
||||
if (c != *expected++) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -223,10 +224,12 @@ static MbcType parseMBC(char const *name) {
|
||||
char *endptr;
|
||||
unsigned long mbc = strtoul(name, &endptr, base);
|
||||
|
||||
if (*endptr)
|
||||
if (*endptr) {
|
||||
return MBC_BAD;
|
||||
if (mbc > 0xFF)
|
||||
}
|
||||
if (mbc > 0xFF) {
|
||||
return MBC_BAD_RANGE;
|
||||
}
|
||||
return static_cast<MbcType>(mbc);
|
||||
|
||||
} else {
|
||||
@@ -235,13 +238,15 @@ static MbcType parseMBC(char const *name) {
|
||||
char const *ptr = name;
|
||||
|
||||
// Trim off leading whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t')
|
||||
while (*ptr == ' ' || *ptr == '\t') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
#define tryReadSlice(expected) \
|
||||
do { \
|
||||
if (!readMBCSlice(ptr, expected)) \
|
||||
if (!readMBCSlice(ptr, expected)) { \
|
||||
return MBC_BAD; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
switch (*ptr++) {
|
||||
@@ -249,8 +254,9 @@ static MbcType parseMBC(char const *name) {
|
||||
case 'r':
|
||||
tryReadSlice("OM");
|
||||
// Handle optional " ONLY"
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
if (*ptr == 'O' || *ptr == 'o') {
|
||||
ptr++;
|
||||
tryReadSlice("NLY");
|
||||
@@ -325,8 +331,9 @@ static MbcType parseMBC(char const *name) {
|
||||
case 'P': {
|
||||
tryReadSlice("P1");
|
||||
// Parse version
|
||||
while (*ptr == ' ' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
// Major
|
||||
char *endptr;
|
||||
unsigned long val = strtoul(ptr, &endptr, 10);
|
||||
@@ -383,27 +390,33 @@ static MbcType parseMBC(char const *name) {
|
||||
|
||||
// Read "additional features"
|
||||
uint8_t features = 0;
|
||||
#define RAM (1 << 7)
|
||||
#define BATTERY (1 << 6)
|
||||
#define TIMER (1 << 5)
|
||||
#define RUMBLE (1 << 4)
|
||||
#define SENSOR (1 << 3)
|
||||
#define MULTIRUMBLE (1 << 2)
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t RAM = 1 << 7;
|
||||
static constexpr uint8_t BATTERY = 1 << 6;
|
||||
static constexpr uint8_t TIMER = 1 << 5;
|
||||
static constexpr uint8_t RUMBLE = 1 << 4;
|
||||
static constexpr uint8_t SENSOR = 1 << 3;
|
||||
static constexpr uint8_t MULTIRUMBLE = 1 << 2;
|
||||
// clang-format on
|
||||
|
||||
for (;;) {
|
||||
// Trim off trailing whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
// If done, start processing "features"
|
||||
if (!*ptr)
|
||||
if (!*ptr) {
|
||||
break;
|
||||
}
|
||||
// We expect a '+' at this point
|
||||
if (*ptr++ != '+')
|
||||
if (*ptr++ != '+') {
|
||||
return MBC_BAD;
|
||||
}
|
||||
// Trim off leading whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_')
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
switch (*ptr++) {
|
||||
case 'B': // BATTERY
|
||||
@@ -428,8 +441,9 @@ static MbcType parseMBC(char const *name) {
|
||||
break;
|
||||
case 'A':
|
||||
case 'a':
|
||||
if (*ptr != 'M' && *ptr != 'm')
|
||||
if (*ptr != 'M' && *ptr != 'm') {
|
||||
return MBC_BAD;
|
||||
}
|
||||
ptr++;
|
||||
features |= RAM;
|
||||
break;
|
||||
@@ -458,8 +472,9 @@ static MbcType parseMBC(char const *name) {
|
||||
|
||||
switch (mbc) {
|
||||
case ROM:
|
||||
if (!features)
|
||||
if (!features) {
|
||||
break;
|
||||
}
|
||||
mbc = ROM_RAM - 1;
|
||||
static_assert(ROM_RAM + 1 == ROM_RAM_BATTERY, "Enum sanity check failed!");
|
||||
static_assert(MBC1 + 1 == MBC1_RAM, "Enum sanity check failed!");
|
||||
@@ -469,26 +484,29 @@ static MbcType parseMBC(char const *name) {
|
||||
[[fallthrough]];
|
||||
case MBC1:
|
||||
case MMM01:
|
||||
if (features == RAM)
|
||||
if (features == RAM) {
|
||||
mbc++;
|
||||
else if (features == (RAM | BATTERY))
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC2:
|
||||
if (features == BATTERY)
|
||||
if (features == BATTERY) {
|
||||
mbc = MBC2_BATTERY;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC3:
|
||||
// Handle timer, which also requires battery
|
||||
if (features & TIMER) {
|
||||
if (!(features & BATTERY))
|
||||
if (!(features & BATTERY)) {
|
||||
fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n");
|
||||
}
|
||||
features &= ~(TIMER | BATTERY); // Reset those bits
|
||||
mbc = MBC3_TIMER_BATTERY;
|
||||
// RAM is handled below
|
||||
@@ -498,12 +516,13 @@ static MbcType parseMBC(char const *name) {
|
||||
static_assert(
|
||||
MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!"
|
||||
);
|
||||
if (features == RAM)
|
||||
if (features == RAM) {
|
||||
mbc++;
|
||||
else if (features == (RAM | BATTERY))
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC5:
|
||||
@@ -515,12 +534,13 @@ static MbcType parseMBC(char const *name) {
|
||||
static_assert(MBC5 + 2 == MBC5_RAM_BATTERY, "Enum sanity check failed!");
|
||||
static_assert(MBC5_RUMBLE + 1 == MBC5_RUMBLE_RAM, "Enum sanity check failed!");
|
||||
static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY, "Enum sanity check failed!");
|
||||
if (features == RAM)
|
||||
if (features == RAM) {
|
||||
mbc++;
|
||||
else if (features == (RAM | BATTERY))
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
else if (features)
|
||||
} else if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC6:
|
||||
@@ -528,45 +548,56 @@ static MbcType parseMBC(char const *name) {
|
||||
case BANDAI_TAMA5:
|
||||
case HUC3:
|
||||
// No extra features accepted
|
||||
if (features)
|
||||
if (features) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||
if (features != (SENSOR | RUMBLE | RAM | BATTERY))
|
||||
if (features != (SENSOR | RUMBLE | RAM | BATTERY)) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case HUC1_RAM_BATTERY:
|
||||
if (features != (RAM | BATTERY)) // HuC1 expects RAM+BATTERY
|
||||
if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
|
||||
case TPP1:
|
||||
if (features & RAM)
|
||||
if (features & RAM) {
|
||||
fprintf(
|
||||
stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size"
|
||||
);
|
||||
if (features & BATTERY)
|
||||
}
|
||||
if (features & BATTERY) {
|
||||
mbc |= 0x08;
|
||||
if (features & TIMER)
|
||||
}
|
||||
if (features & TIMER) {
|
||||
mbc |= 0x04;
|
||||
if (features & MULTIRUMBLE)
|
||||
}
|
||||
if (features & MULTIRUMBLE) {
|
||||
mbc |= 0x03; // Also set the rumble flag
|
||||
if (features & RUMBLE)
|
||||
}
|
||||
if (features & RUMBLE) {
|
||||
mbc |= 0x01;
|
||||
if (features & SENSOR)
|
||||
}
|
||||
if (features & SENSOR) {
|
||||
return MBC_WRONG_FEATURES;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Trim off trailing whitespace
|
||||
while (*ptr == ' ' || *ptr == '\t')
|
||||
while (*ptr == ' ' || *ptr == '\t') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
// If there is still something past the whitespace, error out
|
||||
if (*ptr)
|
||||
if (*ptr) {
|
||||
return MBC_BAD;
|
||||
}
|
||||
|
||||
return static_cast<MbcType>(mbc);
|
||||
}
|
||||
@@ -740,12 +771,14 @@ static uint8_t const nintendoLogo[] = {
|
||||
};
|
||||
|
||||
static uint8_t fixSpec = 0;
|
||||
#define FIX_LOGO (1 << 7)
|
||||
#define TRASH_LOGO (1 << 6)
|
||||
#define FIX_HEADER_SUM (1 << 5)
|
||||
#define TRASH_HEADER_SUM (1 << 4)
|
||||
#define FIX_GLOBAL_SUM (1 << 3)
|
||||
#define TRASH_GLOBAL_SUM (1 << 2)
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t FIX_LOGO = 1 << 7;
|
||||
static constexpr uint8_t TRASH_LOGO = 1 << 6;
|
||||
static constexpr uint8_t FIX_HEADER_SUM = 1 << 5;
|
||||
static constexpr uint8_t TRASH_HEADER_SUM = 1 << 4;
|
||||
static constexpr uint8_t FIX_GLOBAL_SUM = 1 << 3;
|
||||
static constexpr uint8_t TRASH_GLOBAL_SUM = 1 << 2;
|
||||
// clang-format on
|
||||
|
||||
static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone
|
||||
static char const *gameID = nullptr;
|
||||
@@ -778,11 +811,13 @@ static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
|
||||
while (len) {
|
||||
ssize_t ret = read(fd, buf, len);
|
||||
|
||||
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted
|
||||
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
||||
return -1;
|
||||
}
|
||||
// EOF reached
|
||||
if (ret == 0)
|
||||
if (ret == 0) {
|
||||
return total;
|
||||
}
|
||||
// If anything was read, accumulate it, and continue
|
||||
if (ret != -1) {
|
||||
total += ret;
|
||||
@@ -803,43 +838,30 @@ static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
|
||||
while (len) {
|
||||
ssize_t ret = write(fd, buf, len);
|
||||
|
||||
if (ret == -1 && errno != EINTR) // Return errors, unless we only were interrupted
|
||||
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
||||
return -1;
|
||||
// EOF reached
|
||||
if (ret == 0)
|
||||
return total;
|
||||
// If anything was read, accumulate it, and continue
|
||||
}
|
||||
// If anything was written, accumulate it, and continue
|
||||
if (ret != -1) {
|
||||
total += ret;
|
||||
len -= ret;
|
||||
buf += ret;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param rom0 A pointer to rom0
|
||||
* @param addr What address to check
|
||||
* @param fixedByte The fixed byte at the address
|
||||
* @param areaName Name to be displayed in the warning message
|
||||
*/
|
||||
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName) {
|
||||
uint8_t origByte = rom0[addr];
|
||||
|
||||
if (!overwriteRom && origByte != 0 && origByte != fixedByte)
|
||||
if (!overwriteRom && origByte != 0 && origByte != fixedByte) {
|
||||
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
|
||||
}
|
||||
|
||||
rom0[addr] = fixedByte;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param rom0 A pointer to rom0
|
||||
* @param startAddr What address to begin checking from
|
||||
* @param fixed The fixed bytes at the address
|
||||
* @param size How many bytes to check
|
||||
* @param areaName Name to be displayed in the warning message
|
||||
*/
|
||||
static void overwriteBytes(
|
||||
uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size, char const *areaName
|
||||
) {
|
||||
@@ -857,18 +879,13 @@ static void overwriteBytes(
|
||||
memcpy(&rom0[startAddr], fixed, size);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param input File descriptor to be used for reading
|
||||
* @param output File descriptor to be used for writing, may be equal to `input`
|
||||
* @param name The file's name, to be displayed for error output
|
||||
* @param fileSize The file's size if known, 0 if not.
|
||||
*/
|
||||
static void processFile(int input, int output, char const *name, off_t fileSize) {
|
||||
// Both of these should be true for seekable files, and neither otherwise
|
||||
if (input == output)
|
||||
if (input == output) {
|
||||
assume(fileSize != 0);
|
||||
else
|
||||
} else {
|
||||
assume(fileSize == 0);
|
||||
}
|
||||
|
||||
uint8_t rom0[BANK_SIZE];
|
||||
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
|
||||
|
||||
if (fixSpec & (FIX_LOGO | TRASH_LOGO))
|
||||
if (fixSpec & (FIX_LOGO | TRASH_LOGO)) {
|
||||
overwriteBytes(rom0, 0x0104, logo, sizeof(logo), logoFilename ? "logo" : "Nintendo logo");
|
||||
}
|
||||
|
||||
if (title)
|
||||
if (title) {
|
||||
overwriteBytes(rom0, 0x134, reinterpret_cast<uint8_t const *>(title), titleLen, "title");
|
||||
}
|
||||
|
||||
if (gameID)
|
||||
if (gameID) {
|
||||
overwriteBytes(
|
||||
rom0,
|
||||
0x13F,
|
||||
reinterpret_cast<uint8_t const *>(gameID),
|
||||
gameIDLen,
|
||||
"manufacturer code"
|
||||
rom0, 0x13F, reinterpret_cast<uint8_t const *>(gameID), gameIDLen, "manufacturer code"
|
||||
);
|
||||
}
|
||||
|
||||
if (model != DMG)
|
||||
if (model != DMG) {
|
||||
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
|
||||
}
|
||||
|
||||
if (newLicensee)
|
||||
if (newLicensee) {
|
||||
overwriteBytes(
|
||||
rom0,
|
||||
0x144,
|
||||
@@ -916,9 +933,11 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
newLicenseeLen,
|
||||
"new licensee code"
|
||||
);
|
||||
}
|
||||
|
||||
if (sgb)
|
||||
if (sgb) {
|
||||
overwriteByte(rom0, 0x146, 0x03, "SGB flag");
|
||||
}
|
||||
|
||||
// If a valid MBC was specified...
|
||||
if (cartridgeType < MBC_NONE) {
|
||||
@@ -941,31 +960,36 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
|
||||
overwriteBytes(rom0, 0x150, tpp1Rev, sizeof(tpp1Rev), "TPP1 revision number");
|
||||
|
||||
if (ramSize != UNSPECIFIED)
|
||||
if (ramSize != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x152, ramSize, "RAM size");
|
||||
}
|
||||
|
||||
overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags");
|
||||
} else {
|
||||
// Regular mappers
|
||||
|
||||
if (ramSize != UNSPECIFIED)
|
||||
if (ramSize != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x149, ramSize, "RAM size");
|
||||
|
||||
if (!japanese)
|
||||
overwriteByte(rom0, 0x14A, 0x01, "destination code");
|
||||
}
|
||||
|
||||
if (oldLicensee != UNSPECIFIED)
|
||||
if (!japanese) {
|
||||
overwriteByte(rom0, 0x14A, 0x01, "destination code");
|
||||
}
|
||||
}
|
||||
|
||||
if (oldLicensee != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
|
||||
else if (sgb && rom0[0x14B] != 0x33)
|
||||
} else if (sgb && rom0[0x14B] != 0x33) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n",
|
||||
rom0[0x14B]
|
||||
);
|
||||
}
|
||||
|
||||
if (romVersion != UNSPECIFIED)
|
||||
if (romVersion != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
|
||||
}
|
||||
|
||||
// Remain to be handled the ROM size, and header checksum.
|
||||
// The latter depends on the former, and so will be handled after it.
|
||||
@@ -1013,15 +1037,17 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
nbBanks++;
|
||||
|
||||
// Update global checksum, too
|
||||
for (uint16_t i = 0; i < bankLen; i++)
|
||||
for (uint16_t i = 0; i < bankLen; i++) {
|
||||
globalSum += romx[totalRomxLen + i];
|
||||
}
|
||||
totalRomxLen += bankLen;
|
||||
}
|
||||
// Stop when an incomplete bank has been read
|
||||
if (bankLen != BANK_SIZE)
|
||||
if (bankLen != BANK_SIZE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle setting the ROM size if padding was requested
|
||||
// Pad to the next valid power of 2. This is because padding is required by flashers, which
|
||||
@@ -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
|
||||
// x&(x-1) is zero iff x is a power of 2, or 0; we know for sure it's non-zero,
|
||||
// so this is true (non-zero) when we don't have a power of 2
|
||||
if (nbBanks & (nbBanks - 1))
|
||||
if (nbBanks & (nbBanks - 1)) {
|
||||
nbBanks = 1 << (CHAR_BIT * sizeof(nbBanks) - clz(nbBanks));
|
||||
}
|
||||
// Write final ROM size
|
||||
rom0[0x148] = ctz(nbBanks / 2);
|
||||
// Alter global checksum based on how many bytes will be added (not counting ROM0)
|
||||
@@ -1058,8 +1085,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (fixSpec & (FIX_HEADER_SUM | TRASH_HEADER_SUM)) {
|
||||
uint8_t sum = 0;
|
||||
|
||||
for (uint16_t i = 0x134; i < 0x14D; i++)
|
||||
for (uint16_t i = 0x134; i < 0x14D; i++) {
|
||||
sum -= rom0[i] + 1;
|
||||
}
|
||||
|
||||
overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum, "header checksum");
|
||||
}
|
||||
@@ -1067,28 +1095,32 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
|
||||
// Computation of the global checksum does not include the checksum bytes
|
||||
assume(rom0Len >= 0x14E);
|
||||
for (uint16_t i = 0; i < 0x14E; i++)
|
||||
for (uint16_t i = 0; i < 0x14E; i++) {
|
||||
globalSum += rom0[i];
|
||||
for (uint16_t i = 0x150; i < rom0Len; i++)
|
||||
}
|
||||
for (uint16_t i = 0x150; i < rom0Len; i++) {
|
||||
globalSum += rom0[i];
|
||||
}
|
||||
// Pipes have already read ROMX and updated globalSum, but not regular files
|
||||
if (input == output) {
|
||||
for (;;) {
|
||||
ssize_t bankLen = readBytes(input, bank, sizeof(bank));
|
||||
|
||||
for (uint16_t i = 0; i < bankLen; i++)
|
||||
for (uint16_t i = 0; i < bankLen; i++) {
|
||||
globalSum += bank[i];
|
||||
if (bankLen != sizeof(bank))
|
||||
}
|
||||
if (bankLen != sizeof(bank)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fixSpec & TRASH_GLOBAL_SUM)
|
||||
if (fixSpec & TRASH_GLOBAL_SUM) {
|
||||
globalSum = ~globalSum;
|
||||
}
|
||||
|
||||
uint8_t bytes[2] = {
|
||||
static_cast<uint8_t>(globalSum >> 8),
|
||||
static_cast<uint8_t>(globalSum & 0xFF)
|
||||
static_cast<uint8_t>(globalSum >> 8), static_cast<uint8_t>(globalSum & 0xFF)
|
||||
};
|
||||
|
||||
overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
|
||||
@@ -1105,9 +1137,10 @@ 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
|
||||
// However, padding may have modified ROM0 (added padding), so don't in that case
|
||||
if (padValue == UNSPECIFIED)
|
||||
if (padValue == UNSPECIFIED) {
|
||||
rom0Len = headerSize;
|
||||
}
|
||||
}
|
||||
writeLen = writeBytes(output, rom0, rom0Len);
|
||||
|
||||
if (writeLen == -1) {
|
||||
@@ -1209,7 +1242,7 @@ static bool processFilename(char const *name) {
|
||||
}
|
||||
}
|
||||
|
||||
if (nbErrors)
|
||||
if (nbErrors) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Fixing \"%s\" failed with %u error%s\n",
|
||||
@@ -1217,6 +1250,7 @@ static bool processFilename(char const *name) {
|
||||
nbErrors,
|
||||
nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
return nbErrors;
|
||||
}
|
||||
|
||||
@@ -1266,8 +1300,9 @@ int main(int argc, char *argv[]) {
|
||||
switch (*musl_optarg) {
|
||||
#define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \
|
||||
case STR(cur)[0]: \
|
||||
if (fixSpec & badFlag) \
|
||||
if (fixSpec & badFlag) { \
|
||||
fprintf(stderr, "warning: '" STR(cur) "' overriding '" STR(bad) "' in fix spec\n"); \
|
||||
} \
|
||||
fixSpec = (fixSpec & ~badFlag) | curFlag; \
|
||||
break
|
||||
#define overrideSpecs(fix, fixFlag, trash, trashFlag) \
|
||||
@@ -1286,6 +1321,10 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
printUsage();
|
||||
exit(0);
|
||||
|
||||
case 'i':
|
||||
gameID = musl_optarg;
|
||||
len = strlen(gameID);
|
||||
@@ -1393,21 +1432,23 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if ((cartridgeType & 0xFF00) == TPP1 && !japanese)
|
||||
if ((cartridgeType & 0xFF00) == TPP1 && !japanese) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"
|
||||
);
|
||||
}
|
||||
|
||||
// Check that RAM size is correct for "standard" mappers
|
||||
if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) {
|
||||
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
|
||||
if (ramSize != 1)
|
||||
if (ramSize != 1) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n",
|
||||
mbcName(cartridgeType)
|
||||
);
|
||||
}
|
||||
} else if (hasRAM(cartridgeType)) {
|
||||
if (!ramSize) {
|
||||
fprintf(
|
||||
@@ -1432,12 +1473,13 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33)
|
||||
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n",
|
||||
oldLicensee
|
||||
);
|
||||
}
|
||||
|
||||
argv += musl_optind;
|
||||
bool failed = nbErrors;
|
||||
@@ -1488,9 +1530,10 @@ int main(int argc, char *argv[]) {
|
||||
memcpy(logo, nintendoLogo, sizeof(nintendoLogo));
|
||||
}
|
||||
if (fixSpec & TRASH_LOGO) {
|
||||
for (uint16_t i = 0; i < sizeof(logo); i++)
|
||||
for (uint16_t i = 0; i < sizeof(logo); i++) {
|
||||
logo[i] = 0xFF ^ logo[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!*argv) {
|
||||
fputs(
|
||||
|
||||
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"
|
||||
|
||||
@@ -41,7 +41,8 @@ static struct LocalOptions {
|
||||
|
||||
static uintmax_t nbErrors;
|
||||
|
||||
[[noreturn]] void giveUp() {
|
||||
[[noreturn]]
|
||||
void giveUp() {
|
||||
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
||||
exit(1);
|
||||
}
|
||||
@@ -71,18 +72,21 @@ void error(char const *fmt, ...) {
|
||||
va_end(ap);
|
||||
putc('\n', stderr);
|
||||
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||
nbErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
void errorMessage(char const *msg) {
|
||||
fprintf(stderr, "error: %s\n", msg);
|
||||
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||
nbErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void fatal(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void fatal(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
fputs("FATAL: ", stderr);
|
||||
@@ -91,8 +95,9 @@ void errorMessage(char const *msg) {
|
||||
va_end(ap);
|
||||
putc('\n', stderr);
|
||||
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max()) {
|
||||
nbErrors++;
|
||||
}
|
||||
|
||||
giveUp();
|
||||
}
|
||||
@@ -108,18 +113,15 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
||||
}
|
||||
|
||||
// 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
|
||||
* Please keep in the same order as short opts
|
||||
*
|
||||
* Also, make sure long opts don't create ambiguity:
|
||||
* A long opt's name should start with the same letter as its short opt,
|
||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
* This is because long opt matching, even to a single char, is prioritized
|
||||
* over short opt matching
|
||||
*/
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||
{"attr-map", required_argument, nullptr, 'a'},
|
||||
@@ -127,6 +129,7 @@ static option const longopts[] = {
|
||||
{"color-curve", no_argument, nullptr, 'C'},
|
||||
{"colors", required_argument, nullptr, 'c'},
|
||||
{"depth", required_argument, nullptr, 'd'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"input-tileset", required_argument, nullptr, 'i'},
|
||||
{"slice", required_argument, nullptr, 'L'},
|
||||
{"mirror-tiles", no_argument, nullptr, 'm'},
|
||||
@@ -155,7 +158,7 @@ static option const longopts[] = {
|
||||
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbgfx [-r stride] [-CmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
"Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-i <tileset_file>]\n"
|
||||
" [-L <slice>] [-N <nb_tiles>] [-n <nb_pals>] [-o <out_file>]\n"
|
||||
" [-p <pal_file> | -P] [-q <pal_map> | -Q] [-s <nb_colors>]\n"
|
||||
@@ -172,10 +175,8 @@ static void printUsage() {
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses a number at the beginning of a string, moving the pointer to skip the parsed characters
|
||||
* Returns the provided errVal on error
|
||||
*/
|
||||
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
|
||||
// Returns the provided errVal on error.
|
||||
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
|
||||
uint8_t base = 10;
|
||||
if (*string == '\0') {
|
||||
@@ -198,12 +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.
|
||||
* Maximum is inclusive. The string_view is modified to "consume" all digits.
|
||||
* Returns 255 on parse failure (including wrong char for base), in which case
|
||||
* the string_view may be pointing on garbage.
|
||||
*/
|
||||
// Turns a digit into its numeric value in the current base, if it has one.
|
||||
// Maximum is inclusive. The string_view is modified to "consume" all digits.
|
||||
// Returns 255 on parse failure (including wrong char for base), in which case
|
||||
// the string_view may be pointing on garbage.
|
||||
auto charIndex = [&base](unsigned char c) -> uint8_t {
|
||||
unsigned char index = c - '0'; // Use wrapping semantics
|
||||
if (base == 2 && index >= 2) {
|
||||
@@ -271,10 +270,7 @@ static void registerInput(char const *arg) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Turn an "at-file"'s contents into an argv that `getopt` can handle
|
||||
* @param argPool Argument characters will be appended to this vector, for storage purposes.
|
||||
*/
|
||||
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
|
||||
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
||||
File file;
|
||||
if (!file.open(path, std::ios_base::in)) {
|
||||
@@ -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.
|
||||
* The `localOptions` struct is for flags which must be processed after the option parsing finishes.
|
||||
*
|
||||
* Returns `nullptr` if the vector was fully parsed, or a pointer (which is part of the arg vector)
|
||||
* to an "at-file" path if one is encountered.
|
||||
*/
|
||||
// Parses an arg vector, modifying `options` and `localOptions` as options are read.
|
||||
// The `localOptions` struct is for flags which must be processed after the option parsing finishes.
|
||||
// Returns `nullptr` if the vector was fully parsed, or a pointer (which is part of the arg vector)
|
||||
// to an "at-file" path if one is encountered.
|
||||
static char *parseArgv(int argc, char *argv[]) {
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
char *arg = musl_optarg; // Make a copy for scanning
|
||||
@@ -363,8 +356,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 'a':
|
||||
localOptions.autoAttrmap = false;
|
||||
if (!options.attrmap.empty())
|
||||
if (!options.attrmap.empty()) {
|
||||
warning("Overriding attrmap file %s", options.attrmap.c_str());
|
||||
}
|
||||
options.attrmap = musl_optarg;
|
||||
break;
|
||||
case 'b':
|
||||
@@ -430,9 +424,13 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
options.bitDepth = 2;
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
printUsage();
|
||||
exit(0);
|
||||
case 'i':
|
||||
if (!options.inputTileset.empty())
|
||||
if (!options.inputTileset.empty()) {
|
||||
warning("Overriding input tileset file %s", options.inputTileset.c_str());
|
||||
}
|
||||
options.inputTileset = musl_optarg;
|
||||
break;
|
||||
case 'L':
|
||||
@@ -530,8 +528,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
localOptions.groupOutputs = true;
|
||||
break;
|
||||
case 'o':
|
||||
if (!options.output.empty())
|
||||
if (!options.output.empty()) {
|
||||
warning("Overriding tile data file %s", options.output.c_str());
|
||||
}
|
||||
options.output = musl_optarg;
|
||||
break;
|
||||
case 'P':
|
||||
@@ -539,8 +538,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 'p':
|
||||
localOptions.autoPalettes = false;
|
||||
if (!options.palettes.empty())
|
||||
if (!options.palettes.empty()) {
|
||||
warning("Overriding palettes file %s", options.palettes.c_str());
|
||||
}
|
||||
options.palettes = musl_optarg;
|
||||
break;
|
||||
case 'Q':
|
||||
@@ -548,8 +548,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 'q':
|
||||
localOptions.autoPalmap = false;
|
||||
if (!options.palmap.empty())
|
||||
if (!options.palmap.empty()) {
|
||||
warning("Overriding palette map file %s", options.palmap.c_str());
|
||||
}
|
||||
options.palmap = musl_optarg;
|
||||
break;
|
||||
case 'r':
|
||||
@@ -575,8 +576,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
break;
|
||||
case 't':
|
||||
localOptions.autoTilemap = false;
|
||||
if (!options.tilemap.empty())
|
||||
if (!options.tilemap.empty()) {
|
||||
warning("Overriding tilemap file %s", options.tilemap.c_str());
|
||||
}
|
||||
options.tilemap = musl_optarg;
|
||||
break;
|
||||
case 'V':
|
||||
@@ -774,19 +776,25 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
fputs("Options:\n", stderr);
|
||||
if (options.columnMajor)
|
||||
if (options.columnMajor) {
|
||||
fputs("\tVisit image in column-major order\n", stderr);
|
||||
if (options.allowDedup)
|
||||
}
|
||||
if (options.allowDedup) {
|
||||
fputs("\tAllow deduplicating tiles\n", stderr);
|
||||
if (options.allowMirroringX)
|
||||
}
|
||||
if (options.allowMirroringX) {
|
||||
fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr);
|
||||
if (options.allowMirroringY)
|
||||
}
|
||||
if (options.allowMirroringY) {
|
||||
fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr);
|
||||
if (options.useColorCurve)
|
||||
}
|
||||
if (options.useColorCurve) {
|
||||
fputs("\tUse color curve\n", stderr);
|
||||
}
|
||||
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
|
||||
if (options.trim != 0)
|
||||
if (options.trim != 0) {
|
||||
fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
|
||||
}
|
||||
fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes);
|
||||
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
||||
fprintf(stderr, "\t%s palette spec\n", [] {
|
||||
@@ -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 {
|
||||
return color == Rgba::transparent
|
||||
? 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "gfx/pal_packing.hpp"
|
||||
|
||||
@@ -27,15 +27,11 @@
|
||||
// Tile | Proto-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 {
|
||||
size_t protoPalIndex;
|
||||
/*
|
||||
* Pages from which we are banned (to prevent infinite loops)
|
||||
* This is dynamic because we wish not to hard-cap the amount of palettes
|
||||
*/
|
||||
// Pages from which we are banned (to prevent infinite loops)
|
||||
// This is dynamic because we wish not to hard-cap the amount of palettes
|
||||
std::vector<bool> bannedPages;
|
||||
|
||||
explicit ProtoPalAttrs(size_t index) : protoPalIndex(index) {}
|
||||
@@ -50,10 +46,8 @@ struct ProtoPalAttrs {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
// A collection of proto-palettes assigned to a palette
|
||||
// Does not contain the actual color indices because we need to be able to remove elements
|
||||
class AssignedProtos {
|
||||
// We leave room for emptied slots to avoid copying the structs around on removal
|
||||
std::vector<std::optional<ProtoPalAttrs>> _assigned;
|
||||
@@ -127,10 +121,8 @@ public:
|
||||
}
|
||||
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
|
||||
|
||||
/*
|
||||
* Assigns a new ProtoPalAttrs in a free slot, assuming there is one
|
||||
* Args are passed to the `ProtoPalAttrs`'s constructor
|
||||
*/
|
||||
// Assigns a new ProtoPalAttrs in a free slot, assuming there is one
|
||||
// Args are passed to the `ProtoPalAttrs`'s constructor
|
||||
template<typename... Ts>
|
||||
void assign(Ts &&...args) {
|
||||
auto freeSlot =
|
||||
@@ -192,9 +184,7 @@ private:
|
||||
return colors;
|
||||
}
|
||||
public:
|
||||
/*
|
||||
* Returns the number of distinct colors
|
||||
*/
|
||||
// Returns the number of distinct colors
|
||||
size_t volume() const { return uniqueColors().size(); }
|
||||
bool canFit(ProtoPalette const &protoPal) const {
|
||||
auto &colors = uniqueColors();
|
||||
@@ -218,10 +208,8 @@ public:
|
||||
return factor;
|
||||
}();
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
// 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.
|
||||
uint32_t relSizeOf(ProtoPalette const &protoPal) const {
|
||||
// NOTE: this function must not call `uniqueColors`, or one of its callers will break!
|
||||
|
||||
@@ -244,9 +232,7 @@ public:
|
||||
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>
|
||||
auto combinedVolume(Iter &&begin, Iter const &end, std::vector<ProtoPalette> const &protoPals)
|
||||
const {
|
||||
@@ -254,9 +240,7 @@ public:
|
||||
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
|
||||
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>
|
||||
auto combinedVolume(Iter &&begin, Iter &&end) const {
|
||||
auto &colors = uniqueColors();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "gfx/pal_sorting.hpp"
|
||||
|
||||
@@ -46,7 +46,7 @@ void sortIndexed(
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#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>
|
||||
static T readBE(U const *bytes) {
|
||||
T val = 0;
|
||||
@@ -203,14 +189,10 @@ static T readLE(U const *bytes) {
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* **Appends** the first line read from `file` to the end of the provided `buffer`.
|
||||
*
|
||||
* @return true if a line was read.
|
||||
*/
|
||||
[[gnu::warn_unused_result]] // Ignoring EOF is a bad idea.
|
||||
static bool
|
||||
readLine(std::filebuf &file, std::string &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]]
|
||||
static bool readLine(std::filebuf &file, std::string &buffer) {
|
||||
assume(buffer.empty());
|
||||
// TODO: maybe this can be optimized to bulk reads?
|
||||
for (;;) {
|
||||
@@ -238,9 +220,7 @@ static bool
|
||||
} \
|
||||
} 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
|
||||
static std::optional<U> parseDec(std::string const &str, std::string::size_type &n) {
|
||||
uintmax_t value = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "gfx/process.hpp"
|
||||
|
||||
@@ -26,18 +26,16 @@
|
||||
#include "gfx/proto_palette.hpp"
|
||||
|
||||
class ImagePalette {
|
||||
// Use as many slots as there are CGB colors (plus transparency)
|
||||
std::array<std::optional<Rgba>, 0x8001> _colors;
|
||||
std::array<std::optional<Rgba>, NB_COLOR_SLOTS> _colors;
|
||||
|
||||
public:
|
||||
ImagePalette() = default;
|
||||
|
||||
/*
|
||||
* Registers a color in the palette.
|
||||
* 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.
|
||||
*/
|
||||
[[nodiscard]] Rgba const *registerColor(Rgba const &rgba) {
|
||||
// Registers a color in the palette.
|
||||
// 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.
|
||||
[[nodiscard]]
|
||||
Rgba const *registerColor(Rgba const &rgba) {
|
||||
decltype(_colors)::value_type &slot = _colors[rgba.cgbColor()];
|
||||
|
||||
if (rgba.cgbColor() == Rgba::transparent) {
|
||||
@@ -80,7 +78,8 @@ class Png {
|
||||
int nbTransparentEntries;
|
||||
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));
|
||||
|
||||
fatal("Error reading input image (\"%s\"): %s", self->c_str(), msg);
|
||||
@@ -166,15 +165,13 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads a PNG and notes all of its colors
|
||||
*
|
||||
* 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,
|
||||
* 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
|
||||
* the pixel data in `pixels`, which saves on memory allocations.
|
||||
*/
|
||||
// Reads a PNG and notes all of its colors
|
||||
//
|
||||
// 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,
|
||||
// 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
|
||||
// the pixel data in `pixels`, which saves on memory allocations.
|
||||
explicit Png(std::string const &filePath) : path(filePath), colors() {
|
||||
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));
|
||||
@@ -491,9 +488,7 @@ public:
|
||||
};
|
||||
|
||||
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 {
|
||||
std::array<std::array<size_t, 8>, 8> _pixelIndices{};
|
||||
|
||||
@@ -506,9 +501,7 @@ private:
|
||||
std::vector<RawTile> _tiles;
|
||||
|
||||
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() {
|
||||
_tiles.emplace_back();
|
||||
return _tiles.back();
|
||||
@@ -516,11 +509,9 @@ public:
|
||||
};
|
||||
|
||||
struct AttrmapEntry {
|
||||
/*
|
||||
* 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
|
||||
* attrmap entry while correctly handling the above, use `getPalID`.
|
||||
*/
|
||||
// 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
|
||||
// attrmap entry while correctly handling the above, use `getPalID`.
|
||||
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
|
||||
bool bank;
|
||||
@@ -624,7 +615,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||
for (uint16_t cgbColor : list) {
|
||||
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
|
||||
@@ -713,8 +704,9 @@ static void hashBitplanes(uint16_t bitplanes, uint16_t &hash) {
|
||||
|
||||
class TileData {
|
||||
// Importantly, `TileData` is **always** 2bpp.
|
||||
// If the active bit depth is 1bpp, all tiles are processed as 2bpp nonetheless, but emitted as 1bpp.
|
||||
// This massively simplifies internal processing, since bit depth is always identical outside of I/O / serialization boundaries.
|
||||
// If the active bit depth is 1bpp, all tiles are processed as 2bpp nonetheless, but emitted as
|
||||
// 1bpp. This massively simplifies internal processing, since bit depth is always identical
|
||||
// outside of I/O / serialization boundaries.
|
||||
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
|
||||
// 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 &&) = 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) {
|
||||
auto [tileData, inserted] = tileset.insert(newTile);
|
||||
|
||||
@@ -942,12 +932,10 @@ struct UniqueTiles {
|
||||
auto end() const { return tiles.end(); }
|
||||
};
|
||||
|
||||
/*
|
||||
* 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
|
||||
* 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
|
||||
* twice)
|
||||
*/
|
||||
// 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
|
||||
// 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
|
||||
// twice)
|
||||
static UniqueTiles dedupTiles(
|
||||
Png const &png,
|
||||
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)) {
|
||||
auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
|
||||
|
||||
if (matchType == TileData::NOPE && options.output.empty()) {
|
||||
if (inputWithoutOutput && matchType == TileData::NOPE) {
|
||||
error(
|
||||
"Tile at (%" PRIu32 ", %" PRIu32
|
||||
") is not within the input tileset, and `-o` was not given!",
|
||||
@@ -1163,18 +1152,17 @@ void process() {
|
||||
protoPalettes[n] = protoPalette; // Override them
|
||||
// Remove any other proto-palettes that we encompass
|
||||
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
|
||||
/*
|
||||
* The following code does its job, except that references to the removed
|
||||
* proto-palettes are not updated, causing issues.
|
||||
* TODO: overlap might not be detrimental to the packing algorithm.
|
||||
* Investigation is necessary, especially if pathological cases are found.
|
||||
*
|
||||
* for (size_t i = protoPalettes.size(); --i != n;) {
|
||||
* if (protoPalette.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
|
||||
* protoPalettes.erase(protoPalettes.begin() + i);
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
//
|
||||
// The following code does its job, except that references to the removed
|
||||
// proto-palettes are not updated, causing issues.
|
||||
// TODO: overlap might not be detrimental to the packing algorithm.
|
||||
// Investigation is necessary, especially if pathological cases are found.
|
||||
//
|
||||
// for (size_t i = protoPalettes.size(); --i != n;) {
|
||||
// if (protoPalette.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
|
||||
// protoPalettes.erase(protoPalettes.begin() + i);
|
||||
// }
|
||||
// }
|
||||
[[fallthrough]];
|
||||
|
||||
case ProtoPalette::THEY_BIGGER:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "gfx/proto_palette.hpp"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "gfx/reverse.hpp"
|
||||
|
||||
@@ -49,7 +49,8 @@ static DefaultInitVec<uint8_t> readInto(std::string const &path) {
|
||||
return data;
|
||||
}
|
||||
|
||||
[[noreturn]] static void pngError(png_structp png, char const *msg) {
|
||||
[[noreturn]]
|
||||
static void pngError(png_structp png, char const *msg) {
|
||||
fatal(
|
||||
"Error writing reversed image (\"%s\"): %s",
|
||||
static_cast<char const *>(png_get_error_ptr(png)),
|
||||
@@ -166,9 +167,10 @@ void reverse() {
|
||||
// This avoids redundancy with `-r 1` which results in a vertical column.
|
||||
width = static_cast<size_t>(ceil(sqrt(mapSize)));
|
||||
for (; width < mapSize; ++width) {
|
||||
if (mapSize % width == 0)
|
||||
if (mapSize % width == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
options.verbosePrint(Options::VERB_INTERM, "Picked reversing width of %zu tiles\n", width);
|
||||
}
|
||||
if (mapSize % width != 0) {
|
||||
@@ -467,24 +469,7 @@ void reverse() {
|
||||
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"
|
||||
static std::array<uint8_t, 16> const trimmedTile{
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static std::array<uint8_t, 16> const trimmedTile{0x00};
|
||||
uint8_t const *tileData =
|
||||
tileOfs >= nbTiles ? trimmedTile.data() : &tiles[tileOfs * tileSize];
|
||||
auto const &palette = palettes[palID];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
@@ -10,29 +10,29 @@
|
||||
|
||||
#include "gfx/main.hpp" // options
|
||||
|
||||
/*
|
||||
* Based on inverting the "Modern - Accurate" formula used by SameBoy
|
||||
* since commit b5a611c5db46d6a0649d04d24d8d6339200f9ca1 (Dec 2020),
|
||||
* with gaps in the scale curve filled by polynomial interpolation.
|
||||
*/
|
||||
// Based on inverting the "Modern - Accurate" formula used by SameBoy
|
||||
// since commit b5a611c5db46d6a0649d04d24d8d6339200f9ca1 (Dec 2020),
|
||||
// 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{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // These
|
||||
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, // comments
|
||||
3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, // prevent
|
||||
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, // clang-format
|
||||
7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, // from
|
||||
10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, // reflowing
|
||||
13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, // these
|
||||
16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, // sixteen
|
||||
19, 19, 19, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 22, // 16-item
|
||||
22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, // lines,
|
||||
24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, // which,
|
||||
26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, // in
|
||||
28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, // my
|
||||
29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, // opinion,
|
||||
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, // visualization!
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
uint16_t Rgba::cgbColor() const {
|
||||
if (isTransparent()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "link/assign.hpp"
|
||||
|
||||
@@ -48,11 +48,7 @@ static void initFreeSpace() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Assigns a section to a given memory location
|
||||
* @param section The section to assign
|
||||
* @param location The location to assign the section to
|
||||
*/
|
||||
// Assigns a section to a given memory location
|
||||
static void assignSection(Section §ion, MemoryLocation const &location) {
|
||||
// Propagate the assigned location to all UNIONs/FRAGMENTs
|
||||
// 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
* 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.
|
||||
*/
|
||||
// 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
|
||||
// also that the constraints (alignment...) are respected.
|
||||
static bool isLocationSuitable(
|
||||
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;
|
||||
}
|
||||
|
||||
if (section.isAlignFixed && ((location.address - section.alignOfs) & section.alignMask))
|
||||
if (section.isAlignFixed && ((location.address - section.alignOfs) & section.alignMask)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (location.address < freeSpace.address)
|
||||
if (location.address < freeSpace.address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return location.address + section.size <= freeSpace.address + freeSpace.size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finds a suitable location to place a section at.
|
||||
* @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
|
||||
*/
|
||||
// Returns a suitable free space index into `memory[section->type]` at which to place the given
|
||||
// section, or -1 if none was found.
|
||||
static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
||||
SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type];
|
||||
|
||||
@@ -108,16 +96,19 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
||||
if (section.isBankFixed) {
|
||||
location.bank = section.bank;
|
||||
} else if (scrambleROMX && section.type == SECTTYPE_ROMX) {
|
||||
if (curScrambleROM < 1)
|
||||
if (curScrambleROM < 1) {
|
||||
curScrambleROM = scrambleROMX;
|
||||
}
|
||||
location.bank = curScrambleROM--;
|
||||
} else if (scrambleWRAMX && section.type == SECTTYPE_WRAMX) {
|
||||
if (curScrambleWRAM < 1)
|
||||
if (curScrambleWRAM < 1) {
|
||||
curScrambleWRAM = scrambleWRAMX;
|
||||
}
|
||||
location.bank = curScrambleWRAM--;
|
||||
} else if (scrambleSRAM && section.type == SECTTYPE_SRAM) {
|
||||
if (curScrambleSRAM < 0)
|
||||
if (curScrambleSRAM < 0) {
|
||||
curScrambleSRAM = scrambleSRAM;
|
||||
}
|
||||
location.bank = curScrambleSRAM--;
|
||||
} else {
|
||||
location.bank = typeInfo.firstBank;
|
||||
@@ -134,18 +125,20 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
||||
// Process locations in that bank
|
||||
while (spaceIdx < bankMem.size()) {
|
||||
// If that location is OK, return it
|
||||
if (isLocationSuitable(section, bankMem[spaceIdx], location))
|
||||
if (isLocationSuitable(section, bankMem[spaceIdx], location)) {
|
||||
return spaceIdx;
|
||||
}
|
||||
|
||||
// Go to the next *possible* location
|
||||
if (section.isAddressFixed) {
|
||||
// If the address is fixed, there can be only
|
||||
// one candidate block per bank; if we already
|
||||
// reached it, give up.
|
||||
if (location.address < section.org)
|
||||
if (location.address < section.org) {
|
||||
location.address = section.org;
|
||||
else
|
||||
} else {
|
||||
break; // Try again in next bank
|
||||
}
|
||||
} else if (section.isAlignFixed) {
|
||||
// Move to next aligned location
|
||||
// Move back to alignment boundary
|
||||
@@ -157,15 +150,17 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
||||
} else {
|
||||
// Any location is fine, so, next free block
|
||||
spaceIdx++;
|
||||
if (spaceIdx < bankMem.size())
|
||||
if (spaceIdx < bankMem.size()) {
|
||||
location.address = bankMem[spaceIdx].address;
|
||||
}
|
||||
}
|
||||
|
||||
// If that location is past the current block's end,
|
||||
// go forwards until that is no longer the case.
|
||||
while (spaceIdx < bankMem.size()
|
||||
&& location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size)
|
||||
&& location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size) {
|
||||
spaceIdx++;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return -1;
|
||||
} else if (scrambleROMX && section.type == SECTTYPE_ROMX && location.bank <= scrambleROMX) {
|
||||
if (location.bank > typeInfo.firstBank)
|
||||
if (location.bank > typeInfo.firstBank) {
|
||||
location.bank--;
|
||||
else if (scrambleROMX < typeInfo.lastBank)
|
||||
} else if (scrambleROMX < typeInfo.lastBank) {
|
||||
location.bank = scrambleROMX + 1;
|
||||
else
|
||||
} else {
|
||||
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--;
|
||||
else if (scrambleWRAMX < typeInfo.lastBank)
|
||||
} else if (scrambleWRAMX < typeInfo.lastBank) {
|
||||
location.bank = scrambleWRAMX + 1;
|
||||
else
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (scrambleSRAM && section.type == SECTTYPE_SRAM && location.bank <= scrambleSRAM) {
|
||||
if (location.bank > typeInfo.firstBank)
|
||||
if (location.bank > typeInfo.firstBank) {
|
||||
location.bank--;
|
||||
else if (scrambleSRAM < typeInfo.lastBank)
|
||||
} else if (scrambleSRAM < typeInfo.lastBank) {
|
||||
location.bank = scrambleSRAM + 1;
|
||||
else
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (location.bank < typeInfo.lastBank) {
|
||||
location.bank++;
|
||||
} 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.
|
||||
* @warning Due to the implemented algorithm, this should be called with
|
||||
* sections of decreasing size.
|
||||
* @param section The section to place
|
||||
*/
|
||||
// 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!
|
||||
static void placeSection(Section §ion) {
|
||||
MemoryLocation location;
|
||||
|
||||
@@ -258,10 +253,11 @@ static void placeSection(Section §ion) {
|
||||
} else {
|
||||
// The amount of free spaces doesn't change: resize!
|
||||
freeSpace.size -= section.size;
|
||||
if (noLeftSpace)
|
||||
if (noLeftSpace) {
|
||||
// The free space is moved *and* resized
|
||||
freeSpace.address += section.size;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -269,11 +265,11 @@ static void placeSection(Section §ion) {
|
||||
char where[64];
|
||||
|
||||
if (section.isBankFixed && nbbanks(section.type) != 1) {
|
||||
if (section.isAddressFixed)
|
||||
if (section.isAddressFixed) {
|
||||
snprintf(
|
||||
where, sizeof(where), "at $%02" PRIx32 ":%04" PRIx16, section.bank, section.org
|
||||
);
|
||||
else if (section.isAlignFixed)
|
||||
} else if (section.isAlignFixed) {
|
||||
snprintf(
|
||||
where,
|
||||
sizeof(where),
|
||||
@@ -281,12 +277,13 @@ static void placeSection(Section §ion) {
|
||||
section.bank,
|
||||
static_cast<uint16_t>(~section.alignMask)
|
||||
);
|
||||
else
|
||||
snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank);
|
||||
} else {
|
||||
if (section.isAddressFixed)
|
||||
snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank);
|
||||
}
|
||||
} else {
|
||||
if (section.isAddressFixed) {
|
||||
snprintf(where, sizeof(where), "at address $%04" PRIx16, section.org);
|
||||
else if (section.isAlignFixed)
|
||||
} else if (section.isAlignFixed) {
|
||||
snprintf(
|
||||
where,
|
||||
sizeof(where),
|
||||
@@ -294,20 +291,22 @@ static void placeSection(Section §ion) {
|
||||
static_cast<uint16_t>(~section.alignMask),
|
||||
section.alignOfs
|
||||
);
|
||||
else
|
||||
} else {
|
||||
strcpy(where, "anywhere");
|
||||
}
|
||||
}
|
||||
|
||||
// If a section failed to go to several places, nothing we can report
|
||||
if (!section.isBankFixed || !section.isAddressFixed)
|
||||
if (!section.isBankFixed || !section.isAddressFixed) {
|
||||
errx(
|
||||
"Unable to place \"%s\" (%s section) %s",
|
||||
section.name.c_str(),
|
||||
sectionTypeInfo[section.type].name.c_str(),
|
||||
where
|
||||
);
|
||||
}
|
||||
// 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(
|
||||
"Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > "
|
||||
"$%04x)",
|
||||
@@ -317,8 +316,9 @@ static void placeSection(Section §ion) {
|
||||
section.org + section.size,
|
||||
endaddr(section.type) + 1
|
||||
);
|
||||
}
|
||||
// Otherwise there is overlap with another section
|
||||
else
|
||||
else {
|
||||
errx(
|
||||
"Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
|
||||
section.name.c_str(),
|
||||
@@ -326,35 +326,39 @@ static void placeSection(Section §ion) {
|
||||
where,
|
||||
out_OverlappingSection(section)->name.c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#define BANK_CONSTRAINED (1 << 2)
|
||||
#define ORG_CONSTRAINED (1 << 1)
|
||||
#define ALIGN_CONSTRAINED (1 << 0)
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t BANK_CONSTRAINED = 1 << 2;
|
||||
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];
|
||||
|
||||
/*
|
||||
* Categorize a section depending on how constrained it is
|
||||
* This is so the most-constrained sections are placed first
|
||||
* @param section The section to categorize
|
||||
*/
|
||||
// Categorize a section depending on how constrained it is.
|
||||
// This is so the most-constrained sections are placed first.
|
||||
static void categorizeSection(Section §ion) {
|
||||
uint8_t constraints = 0;
|
||||
|
||||
if (section.isBankFixed)
|
||||
if (section.isBankFixed) {
|
||||
constraints |= BANK_CONSTRAINED;
|
||||
if (section.isAddressFixed)
|
||||
}
|
||||
if (section.isAddressFixed) {
|
||||
constraints |= ORG_CONSTRAINED;
|
||||
}
|
||||
// Can't have both!
|
||||
else if (section.isAlignFixed)
|
||||
else if (section.isAlignFixed) {
|
||||
constraints |= ALIGN_CONSTRAINED;
|
||||
}
|
||||
|
||||
std::deque<Section *> §ions = unassignedSections[constraints];
|
||||
auto pos = sections.begin();
|
||||
|
||||
// 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++;
|
||||
}
|
||||
sections.insert(pos, §ion);
|
||||
|
||||
nbSectionsToAssign++;
|
||||
@@ -375,12 +379,14 @@ void assign_AssignSections() {
|
||||
|
||||
// Specially process fully-constrained sections because of overlaying
|
||||
verbosePrint("Assigning bank+org-constrained...\n");
|
||||
for (Section *section : unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED])
|
||||
for (Section *section : unassignedSections[BANK_CONSTRAINED | ORG_CONSTRAINED]) {
|
||||
placeSection(*section);
|
||||
}
|
||||
|
||||
// If all sections were fully constrained, we have nothing left to do
|
||||
if (!nbSectionsToAssign)
|
||||
if (!nbSectionsToAssign) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Overlaying requires only fully-constrained sections
|
||||
verbosePrint("Assigning other sections...\n");
|
||||
@@ -392,14 +398,16 @@ void assign_AssignSections() {
|
||||
for (Section *section : unassignedSections[constraints]) {
|
||||
fprintf(stderr, "%c \"%s\"", nbSections == 0 ? ';' : ',', section->name.c_str());
|
||||
nbSections++;
|
||||
if (nbSections == 10)
|
||||
if (nbSections == 10) {
|
||||
goto max_out; // Can't `break` out of a nested loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
max_out:
|
||||
if (nbSectionsToAssign != nbSections)
|
||||
if (nbSectionsToAssign != nbSections) {
|
||||
fprintf(stderr, " and %" PRIu64 " more", nbSectionsToAssign - nbSections);
|
||||
}
|
||||
fprintf(stderr, " %sn't\n", nbSectionsToAssign == 1 ? "is" : "are");
|
||||
exit(1);
|
||||
}
|
||||
@@ -407,12 +415,14 @@ max_out:
|
||||
// Assign all remaining sections by decreasing constraint order
|
||||
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0;
|
||||
constraints--) {
|
||||
for (Section *section : unassignedSections[constraints])
|
||||
for (Section *section : unassignedSections[constraints]) {
|
||||
placeSection(*section);
|
||||
}
|
||||
|
||||
if (!nbSectionsToAssign)
|
||||
if (!nbSectionsToAssign) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
unreachable_();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <sys/stat.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);
|
||||
fputs(" -> ", stderr);
|
||||
fputs(lastName.c_str(), stderr);
|
||||
for (uint32_t iter : iters())
|
||||
for (uint32_t iter : iters()) {
|
||||
fprintf(stderr, "::REPT~%" PRIu32, iter);
|
||||
}
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
return lastName;
|
||||
} else {
|
||||
@@ -96,8 +97,9 @@ void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
|
||||
printDiag(fmt, args, "error", where, lineNo);
|
||||
va_end(args);
|
||||
|
||||
if (nbErrors != UINT32_MAX)
|
||||
if (nbErrors != UINT32_MAX) {
|
||||
nbErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
void argErr(char flag, char const *fmt, ...) {
|
||||
@@ -109,19 +111,22 @@ void argErr(char flag, char const *fmt, ...) {
|
||||
va_end(args);
|
||||
putc('\n', stderr);
|
||||
|
||||
if (nbErrors != UINT32_MAX)
|
||||
if (nbErrors != UINT32_MAX) {
|
||||
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_start(args, fmt);
|
||||
printDiag(fmt, args, "FATAL", where, lineNo);
|
||||
va_end(args);
|
||||
|
||||
if (nbErrors != UINT32_MAX)
|
||||
if (nbErrors != UINT32_MAX) {
|
||||
nbErrors++;
|
||||
}
|
||||
|
||||
fprintf(
|
||||
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
|
||||
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
|
||||
* Please keep in the same order as short opts
|
||||
*
|
||||
* Also, make sure long opts don't create ambiguity:
|
||||
* A long opt's name should start with the same letter as its short opt,
|
||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
* This is because long opt matching, even to a single char, is prioritized
|
||||
* over short opt matching
|
||||
*/
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"dmg", no_argument, nullptr, 'd'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"linkerscript", required_argument, nullptr, 'l'},
|
||||
{"map", required_argument, nullptr, 'm'},
|
||||
{"no-sym-in-map", no_argument, nullptr, 'M'},
|
||||
@@ -162,7 +165,7 @@ static option const longopts[] = {
|
||||
|
||||
static void printUsage() {
|
||||
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"
|
||||
" [-S spec] <file> ...\n"
|
||||
"Useful options:\n"
|
||||
@@ -217,19 +220,19 @@ static void parseScrambleSpec(char const *spec) {
|
||||
if (regionNameLen == 0) {
|
||||
argErr('S', "Missing region name");
|
||||
|
||||
if (*spec == '\0')
|
||||
if (*spec == '\0') {
|
||||
break;
|
||||
if (*spec == '=') // Skip the limit, too
|
||||
}
|
||||
if (*spec == '=') { // Skip the limit, too
|
||||
spec = strchr(&spec[1], ','); // Skip to next comma, if any
|
||||
}
|
||||
goto next;
|
||||
}
|
||||
|
||||
// Find the next non-blank char after the region name's end
|
||||
spec += regionNameLen + strspn(&spec[regionNameLen], " \t");
|
||||
if (*spec != '\0' && *spec != ',' && *spec != '=') {
|
||||
argErr(
|
||||
'S', "Unexpected '%c' after region name \"%.*s\"", regionNameFmtLen, regionName
|
||||
);
|
||||
argErr('S', "Unexpected '%c' after region name \"%.*s\"", regionNameFmtLen, regionName);
|
||||
// Skip to next ',' or '=' (or NUL) and keep parsing
|
||||
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);
|
||||
}
|
||||
|
||||
if (*spec == '=') {
|
||||
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
|
||||
if (spec) {
|
||||
assume(*spec == ',' || *spec == '\0');
|
||||
if (*spec == ',')
|
||||
if (*spec == ',') {
|
||||
spec += 1 + strspn(&spec[1], " \t");
|
||||
if (*spec == '\0')
|
||||
}
|
||||
if (*spec == '\0') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void reportErrors() {
|
||||
[[noreturn]]
|
||||
void reportErrors() {
|
||||
fprintf(
|
||||
stderr, "Linking failed with %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
@@ -327,32 +334,40 @@ int main(int argc, char *argv[]) {
|
||||
isDmgMode = true;
|
||||
isWRAM0Mode = true;
|
||||
break;
|
||||
case 'h':
|
||||
printUsage();
|
||||
exit(0);
|
||||
case 'l':
|
||||
if (linkerScriptName)
|
||||
if (linkerScriptName) {
|
||||
warnx("Overriding linker script %s", linkerScriptName);
|
||||
}
|
||||
linkerScriptName = musl_optarg;
|
||||
break;
|
||||
case 'M':
|
||||
noSymInMap = true;
|
||||
break;
|
||||
case 'm':
|
||||
if (mapFileName)
|
||||
if (mapFileName) {
|
||||
warnx("Overriding map file %s", mapFileName);
|
||||
}
|
||||
mapFileName = musl_optarg;
|
||||
break;
|
||||
case 'n':
|
||||
if (symFileName)
|
||||
if (symFileName) {
|
||||
warnx("Overriding sym file %s", symFileName);
|
||||
}
|
||||
symFileName = musl_optarg;
|
||||
break;
|
||||
case 'O':
|
||||
if (overlayFileName)
|
||||
if (overlayFileName) {
|
||||
warnx("Overriding overlay file %s", overlayFileName);
|
||||
}
|
||||
overlayFileName = musl_optarg;
|
||||
break;
|
||||
case 'o':
|
||||
if (outputFileName)
|
||||
if (outputFileName) {
|
||||
warnx("Overriding output file %s", outputFileName);
|
||||
}
|
||||
outputFileName = musl_optarg;
|
||||
break;
|
||||
case 'p': {
|
||||
@@ -409,18 +424,22 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// Patch the size array depending on command-line options
|
||||
if (!is32kMode)
|
||||
if (!is32kMode) {
|
||||
sectionTypeInfo[SECTTYPE_ROM0].size = 0x4000;
|
||||
if (!isWRAM0Mode)
|
||||
}
|
||||
if (!isWRAM0Mode) {
|
||||
sectionTypeInfo[SECTTYPE_WRAM0].size = 0x1000;
|
||||
}
|
||||
|
||||
// Patch the bank ranges array depending on command-line options
|
||||
if (isDmgMode)
|
||||
if (isDmgMode) {
|
||||
sectionTypeInfo[SECTTYPE_VRAM].lastBank = 0;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// apply the linker script's modifications,
|
||||
if (linkerScriptName) {
|
||||
@@ -429,20 +448,23 @@ int main(int argc, char *argv[]) {
|
||||
script_ProcessScript(linkerScriptName);
|
||||
|
||||
// If the linker script produced any errors, some sections may be in an invalid state
|
||||
if (nbErrors != 0)
|
||||
if (nbErrors != 0) {
|
||||
reportErrors();
|
||||
}
|
||||
}
|
||||
|
||||
// then process them,
|
||||
sect_DoSanityChecks();
|
||||
if (nbErrors != 0)
|
||||
if (nbErrors != 0) {
|
||||
reportErrors();
|
||||
}
|
||||
assign_AssignSections();
|
||||
patch_CheckAssertions();
|
||||
|
||||
// and finally output the result.
|
||||
patch_ApplyPatches();
|
||||
if (nbErrors != 0)
|
||||
if (nbErrors != 0) {
|
||||
reportErrors();
|
||||
}
|
||||
out_WriteFiles();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "link/object.hpp"
|
||||
|
||||
@@ -41,11 +41,7 @@ static std::vector<std::vector<FileStackNode>> nodes;
|
||||
var = static_cast<vartype>(tmpVal); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
// Reads an unsigned long (32-bit) value from a file, or `INT64_MAX` on failure.
|
||||
static int64_t readLong(FILE *file) {
|
||||
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) {
|
||||
int byte = getc(file);
|
||||
|
||||
if (byte == EOF)
|
||||
if (byte == EOF) {
|
||||
return INT64_MAX;
|
||||
}
|
||||
// This must be casted to `unsigned`, not `uint8_t`. Rationale:
|
||||
// the type of the shift is the type of `byte` after undergoing
|
||||
// integer promotion, which would be `int` if this was casted to
|
||||
@@ -66,37 +63,14 @@ static int64_t readLong(FILE *file) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
// Helper macro to read a long from a file to a var, or error out if it fails to.
|
||||
#define tryReadLong(var, file, ...) \
|
||||
tryRead(readLong, int64_t, INT64_MAX, long, var, file, __VA_ARGS__)
|
||||
|
||||
// There is no `readbyte`, just use `fgetc` or `getc`.
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
// Helper macro to read a byte from a file to a var, or error out if it fails to.
|
||||
#define tryGetc(type, var, file, ...) tryRead(getc, int, EOF, type, var, file, __VA_ARGS__)
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
// Helper macro to read a '\0'-terminated string from a file, or error out if it fails to.
|
||||
#define tryReadString(var, file, ...) \
|
||||
do { \
|
||||
FILE *tmpFile = file; \
|
||||
@@ -107,28 +81,24 @@ static int64_t readLong(FILE *file) {
|
||||
} else { \
|
||||
tmpVal.push_back(tmpByte); \
|
||||
} \
|
||||
}; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Functions to parse object files
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
// Reads a file stack node from a file.
|
||||
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;
|
||||
|
||||
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;
|
||||
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(
|
||||
FileStackNodeType,
|
||||
@@ -136,47 +106,46 @@ static void readFileStackNode(
|
||||
file,
|
||||
"%s: Cannot read node #%" PRIu32 "'s type: %s",
|
||||
fileName,
|
||||
i
|
||||
nodeID
|
||||
);
|
||||
switch (node.type) {
|
||||
case NODE_FILE:
|
||||
case NODE_MACRO:
|
||||
node.data = "";
|
||||
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;
|
||||
|
||||
uint32_t depth;
|
||||
case NODE_REPT:
|
||||
tryReadLong(depth, file, "%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, i);
|
||||
node.data = std::vector<uint32_t>(depth);
|
||||
for (uint32_t k = 0; k < depth; k++)
|
||||
tryReadLong(
|
||||
node.iters()[k],
|
||||
depth, file, "%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, nodeID
|
||||
);
|
||||
node.data = std::vector<uint32_t>(depth);
|
||||
for (uint32_t i = 0; i < depth; i++) {
|
||||
tryReadLong(
|
||||
node.iters()[i],
|
||||
file,
|
||||
"%s: Cannot read node #%" PRIu32 "'s iter #%" PRIu32 ": %s",
|
||||
fileName,
|
||||
i,
|
||||
k
|
||||
nodeID,
|
||||
i
|
||||
);
|
||||
if (!node.parent)
|
||||
}
|
||||
if (!node.parent) {
|
||||
fatal(
|
||||
nullptr,
|
||||
0,
|
||||
"%s is not a valid object file: root node (#%" PRIu32 ") may not be REPT",
|
||||
fileName,
|
||||
i
|
||||
nodeID
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
// Reads a symbol from a file.
|
||||
static void readSymbol(
|
||||
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.
|
||||
* @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
|
||||
*/
|
||||
// Reads a patch from a file.
|
||||
static void readPatch(
|
||||
FILE *file,
|
||||
Patch &patch,
|
||||
char const *fileName,
|
||||
std::string const §Name,
|
||||
uint32_t i,
|
||||
uint32_t patchID,
|
||||
std::vector<FileStackNode> const &fileNodes
|
||||
) {
|
||||
uint32_t nodeID, rpnSize;
|
||||
@@ -250,92 +213,85 @@ static void readPatch(
|
||||
tryReadLong(
|
||||
nodeID,
|
||||
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,
|
||||
sectName.c_str(),
|
||||
i
|
||||
patchID
|
||||
);
|
||||
patch.src = &fileNodes[nodeID];
|
||||
tryReadLong(
|
||||
patch.lineNo,
|
||||
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,
|
||||
sectName.c_str(),
|
||||
i
|
||||
patchID
|
||||
);
|
||||
tryReadLong(
|
||||
patch.offset,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s offset: %s",
|
||||
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s offset: %s",
|
||||
fileName,
|
||||
sectName.c_str(),
|
||||
i
|
||||
patchID
|
||||
);
|
||||
tryReadLong(
|
||||
patch.pcSectionID,
|
||||
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,
|
||||
sectName.c_str(),
|
||||
i
|
||||
patchID
|
||||
);
|
||||
tryReadLong(
|
||||
patch.pcOffset,
|
||||
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,
|
||||
sectName.c_str(),
|
||||
i
|
||||
patchID
|
||||
);
|
||||
tryGetc(
|
||||
PatchType,
|
||||
type,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s",
|
||||
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s type: %s",
|
||||
fileName,
|
||||
sectName.c_str(),
|
||||
i
|
||||
patchID
|
||||
);
|
||||
patch.type = type;
|
||||
tryReadLong(
|
||||
rpnSize,
|
||||
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,
|
||||
sectName.c_str(),
|
||||
i
|
||||
patchID
|
||||
);
|
||||
|
||||
patch.rpnExpression.resize(rpnSize);
|
||||
size_t nbElementsRead = fread(patch.rpnExpression.data(), 1, rpnSize, file);
|
||||
|
||||
if (nbElementsRead != rpnSize)
|
||||
if (nbElementsRead != rpnSize) {
|
||||
errx(
|
||||
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
|
||||
fileName,
|
||||
sectName.c_str(),
|
||||
i,
|
||||
patchID,
|
||||
feof(file) ? "Unexpected end of file" : strerror(errno)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets a patch's pcSection from its pcSectionID.
|
||||
* @param patch The patch to fix
|
||||
*/
|
||||
// Sets a patch's `pcSection` from its `pcSectionID`.
|
||||
static void
|
||||
linkPatchToPCSect(Patch &patch, std::vector<std::unique_ptr<Section>> const &fileSections) {
|
||||
patch.pcSection =
|
||||
patch.pcSectionID != UINT32_MAX ? fileSections[patch.pcSectionID].get() : nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
// Reads a section from a file.
|
||||
static void readSection(
|
||||
FILE *file, Section §ion, char const *fileName, std::vector<FileStackNode> const &fileNodes
|
||||
) {
|
||||
@@ -356,8 +312,9 @@ static void readSection(
|
||||
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);
|
||||
}
|
||||
section.size = tmp;
|
||||
section.offset = 0;
|
||||
tryGetc(
|
||||
@@ -368,12 +325,13 @@ static void readSection(
|
||||
} else {
|
||||
section.type = SectionType(type);
|
||||
}
|
||||
if (byte >> 7)
|
||||
if (byte >> 7) {
|
||||
section.modifier = SECTION_UNION;
|
||||
else if (byte >> 6)
|
||||
} else if (byte >> 6) {
|
||||
section.modifier = SECTION_FRAGMENT;
|
||||
else
|
||||
} else {
|
||||
section.modifier = SECTION_NORMAL;
|
||||
}
|
||||
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str());
|
||||
section.isAddressFixed = tmp >= 0;
|
||||
if (tmp > UINT16_MAX) {
|
||||
@@ -392,8 +350,9 @@ static void readSection(
|
||||
fileName,
|
||||
section.name.c_str()
|
||||
);
|
||||
if (byte > 16)
|
||||
if (byte > 16) {
|
||||
byte = 16;
|
||||
}
|
||||
section.isAlignFixed = byte != 0;
|
||||
section.alignMask = (1 << byte) - 1;
|
||||
tryReadLong(
|
||||
@@ -415,7 +374,7 @@ static void readSection(
|
||||
if (section.size) {
|
||||
section.data.resize(section.size);
|
||||
if (size_t nbRead = fread(section.data.data(), 1, section.size, file);
|
||||
nbRead != section.size)
|
||||
nbRead != section.size) {
|
||||
errx(
|
||||
"%s: Cannot read \"%s\"'s data: %s",
|
||||
fileName,
|
||||
@@ -423,6 +382,7 @@ static void readSection(
|
||||
feof(file) ? "Unexpected end of file" : strerror(errno)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t nbPatches;
|
||||
|
||||
@@ -435,16 +395,13 @@ static void readSection(
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
// Links a symbol to a section, keeping the section's symbol list sorted.
|
||||
static void linkSymToSect(Symbol &symbol, Section §ion) {
|
||||
uint32_t a = 0, b = section.symbols.size();
|
||||
int32_t symbolOffset = symbol.label().offset;
|
||||
@@ -453,31 +410,27 @@ static void linkSymToSect(Symbol &symbol, Section §ion) {
|
||||
uint32_t c = (a + b) / 2;
|
||||
int32_t otherOffset = section.symbols[c]->label().offset;
|
||||
|
||||
if (otherOffset > symbolOffset)
|
||||
if (otherOffset > symbolOffset) {
|
||||
b = c;
|
||||
else
|
||||
} else {
|
||||
a = c + 1;
|
||||
}
|
||||
}
|
||||
|
||||
section.symbols.insert(section.symbols.begin() + a, &symbol);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
// Reads an assertion from a file.
|
||||
static void readAssertion(
|
||||
FILE *file,
|
||||
Assertion &assert,
|
||||
char const *fileName,
|
||||
uint32_t i,
|
||||
uint32_t assertID,
|
||||
std::vector<FileStackNode> const &fileNodes
|
||||
) {
|
||||
std::string assertName("Assertion #");
|
||||
|
||||
assertName += std::to_string(i);
|
||||
assertName += std::to_string(assertID);
|
||||
readPatch(file, assert.patch, fileName, assertName, 0, fileNodes);
|
||||
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);
|
||||
file = stdin;
|
||||
}
|
||||
if (!file)
|
||||
if (!file) {
|
||||
err("Failed to open file \"%s\"", fileName);
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
// 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
verbosePrint("Reading object file %s\n", fileName);
|
||||
|
||||
uint32_t revNum;
|
||||
|
||||
tryReadLong(revNum, file, "%s: Cannot read revision number: %s", fileName);
|
||||
if (revNum != RGBDS_OBJECT_REV)
|
||||
if (revNum != RGBDS_OBJECT_REV) {
|
||||
errx(
|
||||
"%s: Unsupported object file for rgblink %s; try rebuilding \"%s\"%s"
|
||||
" (expected revision %d, got %d)",
|
||||
@@ -547,6 +502,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
RGBDS_OBJECT_REV,
|
||||
revNum
|
||||
);
|
||||
}
|
||||
|
||||
uint32_t nbNodes;
|
||||
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);
|
||||
nodes[fileID].resize(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);
|
||||
}
|
||||
|
||||
// This file's symbols, kept to link sections to them
|
||||
std::vector<Symbol> &fileSymbols = symbolLists.emplace_front(nbSymbols);
|
||||
@@ -575,9 +532,10 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
readSymbol(file, symbol, fileName, nodes[fileID]);
|
||||
|
||||
sym_AddSymbol(symbol);
|
||||
if (symbol.data.holds<Label>())
|
||||
if (symbol.data.holds<Label>()) {
|
||||
nbSymPerSect[symbol.data.get<Label>().sectionID]++;
|
||||
}
|
||||
}
|
||||
|
||||
// This file's sections, stored in a table to link symbols to them
|
||||
std::vector<std::unique_ptr<Section>> fileSections(nbSections);
|
||||
@@ -607,10 +565,11 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
// Give patches' PC section pointers to their sections
|
||||
for (uint32_t i = 0; i < nbSections; i++) {
|
||||
if (sect_HasData(fileSections[i]->type)) {
|
||||
for (Patch &patch : fileSections[i]->patches)
|
||||
for (Patch &patch : fileSections[i]->patches) {
|
||||
linkPatchToPCSect(patch, fileSections);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Give symbols' section pointers to their sections
|
||||
for (uint32_t i = 0; i < nbSymbols; i++) {
|
||||
@@ -623,8 +582,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
}
|
||||
|
||||
// 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]));
|
||||
}
|
||||
|
||||
// Fix symbols' section pointers to component sections
|
||||
// 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