mirror of
https://github.com/gbdev/rgbds.git
synced 2026-09-23 22:27:06 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7b8207322 | ||
|
|
33ef57ea49 | ||
|
|
6e5d7a9823 | ||
|
|
4d5b38ebfe | ||
|
|
eca8c1f3e7 | ||
|
|
bc1ed831ec | ||
|
|
4eba73d7b2 | ||
|
|
970342f564 | ||
|
|
c43b123183 | ||
|
|
306a83a4b4 | ||
|
|
6da3c3c38d | ||
|
|
629074bc28 | ||
|
|
63a06f02c3 | ||
|
|
c8b05a6a46 | ||
|
|
2882424407 | ||
|
|
04bbe80ae0 | ||
|
|
84c1671cb3 | ||
|
|
a2dd318f1d | ||
|
|
ace8db1b73 | ||
|
|
408e861ecc | ||
|
|
e1ed304e6b | ||
|
|
bb0fc08980 | ||
|
|
b7f6c641f1 | ||
|
|
b80bbe0fe6 | ||
|
|
c151450d9f | ||
|
|
76bba2809b | ||
|
|
44328d522b | ||
|
|
142eae4558 | ||
|
|
89403ca23b | ||
|
|
0286d4119e | ||
|
|
c803a20259 | ||
|
|
aab22852d3 | ||
|
|
0e41ace1e3 | ||
|
|
ce5c8ce8ac | ||
|
|
c49828f7c9 | ||
|
|
5b9903f358 | ||
|
|
d43ef53745 | ||
|
|
ef709fade6 | ||
|
|
8685bcbfbf | ||
|
|
1ca4282e31 | ||
|
|
afe1f01d00 | ||
|
|
8bf5957c83 | ||
|
|
f234796428 | ||
|
|
0b9f8ab523 | ||
|
|
3b250d5386 | ||
|
|
b4988afc8a | ||
|
|
6f69867d04 | ||
|
|
960f82126f | ||
|
|
b9b5c9a2b4 | ||
|
|
aa657cd481 | ||
|
|
e39dbe1752 | ||
|
|
9f175f7923 | ||
|
|
b2967ff509 | ||
|
|
e8d9eb4511 | ||
|
|
589616338f | ||
|
|
6587941512 | ||
|
|
004749ea75 | ||
|
|
f4c6221eb1 | ||
|
|
d81ab2fb23 | ||
|
|
febdf63896 | ||
|
|
b0f49a36b7 | ||
|
|
ed1ac7309b | ||
|
|
d082f24a2d | ||
|
|
7746ddbf2b | ||
|
|
0fe43bf0cb | ||
|
|
c8e8a2f522 | ||
|
|
de1e1ff688 | ||
|
|
f8a1da71c1 | ||
|
|
49b3f566b1 | ||
|
|
82f58b95ac | ||
|
|
e9fc7b3135 | ||
|
|
47bbd30f2c | ||
|
|
520cc2a80d | ||
|
|
a703b8c704 | ||
|
|
e9c800b3be | ||
|
|
8a7019e1f8 | ||
|
|
439a7a1828 | ||
|
|
438b943a1a | ||
|
|
5a2927778f | ||
|
|
9e785495ff | ||
|
|
9d69a08909 | ||
|
|
970bb84450 | ||
|
|
a662cad7ce | ||
|
|
e85375f3e1 | ||
|
|
429a2325fb | ||
|
|
9c5e872f9c | ||
|
|
25240e9fc0 | ||
|
|
3112239174 | ||
|
|
8a6b1946e3 | ||
|
|
631ef003e7 | ||
|
|
fdd6cece30 | ||
|
|
db9509fba0 | ||
|
|
7d0cb000e8 | ||
|
|
a0ab1802ff | ||
|
|
bf3ab7cb18 | ||
|
|
8988cdde46 | ||
|
|
b36028d1d1 | ||
|
|
760cc4d464 | ||
|
|
cebf57630a | ||
|
|
eb2b7c1842 | ||
|
|
a2c52caca2 | ||
|
|
21a4b85a4f | ||
|
|
9fa5058add | ||
|
|
e287ee2724 | ||
|
|
0888600cb7 | ||
|
|
8bc7de35f9 | ||
|
|
ed0a2d1075 | ||
|
|
71d9e236bc | ||
|
|
d9003f633a | ||
|
|
cda77721d4 |
@@ -10,10 +10,10 @@ if [[ "$tag" = v*-rc* ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
files=(
|
files=(
|
||||||
win64/rgbds-win64.zip
|
rgbds-win64.zip
|
||||||
win32/rgbds-win32.zip
|
rgbds-win32.zip
|
||||||
macos/rgbds-macos.zip
|
rgbds-macos.zip
|
||||||
linux/rgbds-linux-x86_64.tar.xz
|
rgbds-linux-x86_64.tar.xz
|
||||||
rgbds-source.tar.gz
|
rgbds-source.tar.gz
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{github.workflow}}-${{github.ref}}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
name: Contrib script syntax checking
|
name: Contrib script syntax checking
|
||||||
on:
|
on:
|
||||||
- push
|
push:
|
||||||
- pull_request
|
paths: [ 'contrib/**', .github/scripts/check-contrib.sh ]
|
||||||
|
pull_request:
|
||||||
|
paths: [ 'contrib/**', .github/scripts/check-contrib.sh ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{github.workflow}}-${{github.event.pull_request.number || github.ref}}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
||||||
@@ -15,6 +22,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
sparse-checkout: | # Avoid checking out other files, we don't use them here.
|
||||||
|
contrib
|
||||||
|
.github/scripts
|
||||||
- name: Install zsh
|
- name: Install zsh
|
||||||
run: |
|
run: |
|
||||||
sudo apt install -Uyq zsh
|
sudo apt install -Uyq zsh
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
name: Diff completeness check
|
name: Diff completeness check
|
||||||
on: pull_request
|
on: pull_request
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{github.workflow}}-${{github.event.pull_request.number || github.ref}}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
name: Code format checking
|
name: Code format checking
|
||||||
on: pull_request
|
on: pull_request
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{github.workflow}}-${{github.event.pull_request.number || github.ref}}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ on:
|
|||||||
- push
|
- push
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{github.workflow}}-${{github.event.pull_request.number || github.ref}}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- v[0-9]*
|
- v[0-9]*
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{github.workflow}}-${{github.ref}}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
@@ -38,7 +42,9 @@ jobs:
|
|||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call .github\scripts\msvc-env.bat ${{ matrix.bits }}
|
call .github\scripts\msvc-env.bat ${{ matrix.bits }}
|
||||||
cmake -B build -G Ninja -DFETCHCONTENT_BASE_DIR="${{ env.DEPS_ROOT_DIR }}" -DCMAKE_C_FLAGS="/nologo"
|
cmake -B build -G Ninja -DFETCHCONTENT_BASE_DIR="${{ env.DEPS_ROOT_DIR }}" ^
|
||||||
|
-DCMAKE_COMPILE_WARNING_AS_ERROR=YES -DCMAKE_LINK_WARNING_AS_ERROR=YES ^
|
||||||
|
-DCMAKE_C_FLAGS="/nologo" -DCMAKE_INSTALL_BINDIR=. -DCPACK_COMPONENTS_ALL=binaries
|
||||||
cmake --build build -- -k 0
|
cmake --build build -- -k 0
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
working-directory: build
|
working-directory: build
|
||||||
@@ -47,8 +53,8 @@ jobs:
|
|||||||
- name: Upload Windows binaries
|
- name: Upload Windows binaries
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: win${{ matrix.bits }}
|
|
||||||
path: build/rgbds-win${{ matrix.bits }}.zip
|
path: build/rgbds-win${{ matrix.bits }}.zip
|
||||||
|
archive: false
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
@@ -61,7 +67,8 @@ jobs:
|
|||||||
./.github/scripts/install-deps.sh macos lld
|
./.github/scripts/install-deps.sh macos lld
|
||||||
- name: Build binaries
|
- name: Build binaries
|
||||||
run: |
|
run: |
|
||||||
cmake -B build -G Ninja --preset macos-static -DFETCHCONTENT_BASE_DIR="${{ env.DEPS_ROOT_DIR }}"
|
cmake -B build -G Ninja --preset macos-static -DFETCHCONTENT_BASE_DIR="${{ env.DEPS_ROOT_DIR }}" \
|
||||||
|
-DCMAKE_COMPILE_WARNING_AS_ERROR=YES -DCMAKE_LINK_WARNING_AS_ERROR=YES
|
||||||
cmake --build build -- -k 0
|
cmake --build build -- -k 0
|
||||||
strip rgb{asm,link,fix,gfx}
|
strip rgb{asm,link,fix,gfx}
|
||||||
env:
|
env:
|
||||||
@@ -75,8 +82,8 @@ jobs:
|
|||||||
- name: Upload macOS binaries
|
- name: Upload macOS binaries
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: macos
|
|
||||||
path: rgbds-macos.zip
|
path: rgbds-macos.zip
|
||||||
|
archive: false
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
linux:
|
linux:
|
||||||
@@ -89,7 +96,7 @@ jobs:
|
|||||||
./.github/scripts/install-deps.sh ubuntu-22.04
|
./.github/scripts/install-deps.sh ubuntu-22.04
|
||||||
- name: Build binaries
|
- name: Build binaries
|
||||||
run: |
|
run: |
|
||||||
make -k -j "$(getconf _NPROCESSORS_ONLN)" WARNFLAGS="-Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q=
|
make -k -j "$(getconf _NPROCESSORS_ONLN)" WARNFLAGS="-Werror -Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q=
|
||||||
strip rgb{asm,link,fix,gfx}
|
strip rgb{asm,link,fix,gfx}
|
||||||
- name: Create install script
|
- name: Create install script
|
||||||
run: |
|
run: |
|
||||||
@@ -100,8 +107,8 @@ jobs:
|
|||||||
- name: Upload Linux binaries
|
- name: Upload Linux binaries
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: linux
|
|
||||||
path: rgbds-linux-x86_64.tar.xz
|
path: rgbds-linux-x86_64.tar.xz
|
||||||
|
archive: false
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
release:
|
release:
|
||||||
@@ -117,6 +124,9 @@ jobs:
|
|||||||
make dist Q=
|
make dist Q=
|
||||||
- name: Download all binary packages
|
- name: Download all binary packages
|
||||||
uses: actions/download-artifact@v8
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
skip-decompress: true # Do not decompress `.zip`s, we want them as-is.
|
||||||
|
merge-multiple: true # Do not download non-`.zip`s into separate directories.
|
||||||
- name: Draft the release
|
- name: Draft the release
|
||||||
run: |
|
run: |
|
||||||
.github/scripts/draft-release.sh "${ref#refs/tags/}"
|
.github/scripts/draft-release.sh "${ref#refs/tags/}"
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ on:
|
|||||||
- push
|
- push
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{github.workflow}}-${{github.event.pull_request.number || github.ref}}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
@@ -197,7 +201,7 @@ jobs:
|
|||||||
run: | # ASan seems to be broken on Windows, so we disable it.
|
run: | # ASan seems to be broken on Windows, so we disable it.
|
||||||
call .github\scripts\msvc-env.bat ${{ matrix.bits }}
|
call .github\scripts\msvc-env.bat ${{ matrix.bits }}
|
||||||
cmake -B build -G Ninja --preset develop -DFETCHCONTENT_BASE_DIR="${{ env.DEPS_ROOT_DIR }}" ^
|
cmake -B build -G Ninja --preset develop -DFETCHCONTENT_BASE_DIR="${{ env.DEPS_ROOT_DIR }}" ^
|
||||||
-DSANITIZERS=OFF -DCMAKE_C_FLAGS="/nologo"
|
-DSANITIZERS=OFF -DCMAKE_C_FLAGS="/nologo" -DCMAKE_INSTALL_BINDIR=. -DCPACK_COMPONENTS_ALL=binaries
|
||||||
cmake --build build -- -k 0
|
cmake --build build -- -k 0
|
||||||
- name: Install
|
- name: Install
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@@ -207,12 +211,12 @@ jobs:
|
|||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: |
|
run: |
|
||||||
cpack -DCPACK_PACKAGE_FILE_NAME=rgbds-win${{ matrix.bits }} -G ZIP --verbose
|
cpack -DCPACK_PACKAGE_FILE_NAME=rgbds-canary-w${{ matrix.bits }}-${{ matrix.os }} -G ZIP --verbose
|
||||||
- name: Upload Windows binaries
|
- name: Upload Windows binaries
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: rgbds-canary-w${{ matrix.bits }}-${{ matrix.os }}
|
path: build/rgbds-canary-w${{ matrix.bits }}-${{ matrix.os }}.zip
|
||||||
path: build/rgbds-win${{ matrix.bits }}.zip
|
archive: false
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
- name: Compute test dependency cache params
|
- name: Compute test dependency cache params
|
||||||
id: test-deps-cache-params
|
id: test-deps-cache-params
|
||||||
@@ -363,8 +367,8 @@ jobs:
|
|||||||
make
|
make
|
||||||
ninja
|
ninja
|
||||||
pkg-config
|
pkg-config
|
||||||
python3
|
python312
|
||||||
python-imaging
|
python312-imaging
|
||||||
- name: Build using Make
|
- name: Build using Make
|
||||||
if: matrix.arch == 'x86'
|
if: matrix.arch == 'x86'
|
||||||
run: | # Cygwin doesn't support `make develop` sanitizers ASan or UBSan, so we don't use it.
|
run: | # Cygwin doesn't support `make develop` sanitizers ASan or UBSan, so we don't use it.
|
||||||
@@ -387,6 +391,7 @@ jobs:
|
|||||||
type rgbasm rgblink rgbfix rgbgfx
|
type rgbasm rgblink rgbfix rgbgfx
|
||||||
man -w 1 rgbasm rgblink rgbfix rgbgfx
|
man -w 1 rgbasm rgblink rgbfix rgbgfx
|
||||||
- name: Compute test dependency cache params
|
- name: Compute test dependency cache params
|
||||||
|
if: matrix.arch == 'x86_64'
|
||||||
id: test-deps-cache-params
|
id: test-deps-cache-params
|
||||||
run: |
|
run: |
|
||||||
paths=$(test/external/fetch-repos.sh --get-paths)
|
paths=$(test/external/fetch-repos.sh --get-paths)
|
||||||
@@ -394,13 +399,14 @@ jobs:
|
|||||||
tee -a <<<"paths=\"${paths//,/\\n}\"" $GITHUB_OUTPUT
|
tee -a <<<"paths=\"${paths//,/\\n}\"" $GITHUB_OUTPUT
|
||||||
tee -a <<<"hash=${hash%-}" $GITHUB_OUTPUT
|
tee -a <<<"hash=${hash%-}" $GITHUB_OUTPUT
|
||||||
- name: Check test dependency repositories cache
|
- name: Check test dependency repositories cache
|
||||||
|
if: matrix.arch == 'x86_64'
|
||||||
id: test-deps-cache
|
id: test-deps-cache
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||||
key: cygwin-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
key: cygwin-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||||
- name: Fetch test dependency repositories
|
- name: Fetch test dependency repositories
|
||||||
if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
if: matrix.arch == 'x86_64' && steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
test/external/fetch-repos.sh
|
test/external/fetch-repos.sh
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ on:
|
|||||||
- man/*
|
- man/*
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{github.workflow}}-${{github.ref}}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
# Force colored output (see https://bixense.com/clicolors/ and https://force-color.org/)
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
*.o
|
*.o
|
||||||
*.exe
|
*.exe
|
||||||
*.dll
|
*.dll
|
||||||
|
*.pdb
|
||||||
*.gcno
|
*.gcno
|
||||||
*.gcda
|
*.gcda
|
||||||
*.gcov
|
*.gcov
|
||||||
|
|||||||
+59
-38
@@ -11,21 +11,21 @@ file(STRINGS "include/version.hpp" version_defines REGEX "^[ \t]*#define[ \t]+PA
|
|||||||
foreach(line IN LISTS version_defines)
|
foreach(line IN LISTS version_defines)
|
||||||
# We want the `CMAKE_MATCH_n` variables, so we just need to run *some* regex op.
|
# We want the `CMAKE_MATCH_n` variables, so we just need to run *some* regex op.
|
||||||
string(REGEX MATCH "PACKAGE_(VERSION_[^ \t]+)[ \t]+([0-9]+)" dummy "${line}")
|
string(REGEX MATCH "PACKAGE_(VERSION_[^ \t]+)[ \t]+([0-9]+)" dummy "${line}")
|
||||||
set("${CMAKE_MATCH_1}" "${CMAKE_MATCH_2}")
|
set("RGBDS_${CMAKE_MATCH_1}" "${CMAKE_MATCH_2}")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
project(rgbds
|
project(rgbds
|
||||||
VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}"
|
VERSION "${RGBDS_VERSION_MAJOR}.${RGBDS_VERSION_MINOR}.${RGBDS_VERSION_PATCH}"
|
||||||
LANGUAGES CXX
|
LANGUAGES CXX
|
||||||
DESCRIPTION "Game Boy assembly toolchain"
|
DESCRIPTION "Game Boy assembly toolchain"
|
||||||
HOMEPAGE_URL "https://rgbds.gbdev.io")
|
HOMEPAGE_URL "https://rgbds.gbdev.io")
|
||||||
if(DEFINED VERSION_RC)
|
if(DEFINED VERSION_RC)
|
||||||
string(APPEND CMAKE_PROJECT_VERSION "-rc${VERSION_RC}")
|
string(APPEND PROJECT_VERSION "-rc${RGBDS_VERSION_RC}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Reject in-source builds, as they may conflict with the Makefile.
|
# Reject in-source builds, as they may conflict with the Makefile.
|
||||||
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
|
get_filename_component(srcdir "${CMAKE_CURRENT_SOURCE_DIR}" REALPATH)
|
||||||
get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)
|
get_filename_component(bindir "${CMAKE_CURRENT_BINARY_DIR}" REALPATH)
|
||||||
if(srcdir STREQUAL bindir)
|
if(srcdir STREQUAL bindir)
|
||||||
message(FATAL_ERROR "RGBDS should not be built in the source directory.
|
message(FATAL_ERROR "RGBDS should not be built in the source directory.
|
||||||
Instead, create a separate build directory and specify to CMake the path to the source directory.")
|
Instead, create a separate build directory and specify to CMake the path to the source directory.")
|
||||||
@@ -52,14 +52,22 @@ endif()
|
|||||||
if(SANITIZERS)
|
if(SANITIZERS)
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
message(STATUS "ASan enabled")
|
message(STATUS "ASan enabled")
|
||||||
add_compile_options(/fsanitize=address) # Note that this shouldn't be passed to the linker.
|
add_compile_options(/fsanitize=address /sdl) # Note that this shouldn't be passed to the linker.
|
||||||
|
add_definitions(/_MSVC_STL_HARDENING=1 /_MSVC_STL_DESTRUCTOR_TOMBSTONES=1 /D_ITERATOR_DEBUG_LEVEL=2)
|
||||||
else() # We assume a GNU-like compiler.
|
else() # We assume a GNU-like compiler.
|
||||||
message(STATUS "ASan and UBSan enabled")
|
message(STATUS "ASan and UBSan enabled")
|
||||||
set(SAN_FLAGS -fsanitize=address -fsanitize=undefined
|
set(SAN_FLAGS -fsanitize=address -fsanitize=undefined
|
||||||
-fsanitize=float-divide-by-zero)
|
-fsanitize=float-divide-by-zero)
|
||||||
add_compile_options(${SAN_FLAGS})
|
add_compile_options(${SAN_FLAGS})
|
||||||
add_link_options(${SAN_FLAGS})
|
add_link_options(${SAN_FLAGS})
|
||||||
add_definitions(-D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG)
|
# Consider using `_GLIBCXX_DEBUG_BACKTRACE` (and `-lstdc++exp`) if unable to debug locally.
|
||||||
|
add_definitions(-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC -D_GLIBCXX_SANITIZE_VECTOR
|
||||||
|
-D_GLIBCXX_VERBOSE_ASSERT
|
||||||
|
# This enables debug-mode checks for `std::string`s, but stdlibc++ says
|
||||||
|
# "this is unsupported and not guaranteed to work", so removing it may be fine.
|
||||||
|
# https://gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode_semantics.html
|
||||||
|
-D_GLIBCXX_EXTERN_TEMPLATE=0
|
||||||
|
-D_LIBCPP_DEBUG -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG)
|
||||||
# A non-zero optimization level is desired even in debug mode (especially for Clang),
|
# A non-zero optimization level is desired even in debug mode (especially for Clang),
|
||||||
# and the two codegen flags improve the sanitizers' backtraces, but we want the user to
|
# and the two codegen flags improve the sanitizers' backtraces, but we want the user to
|
||||||
# be able to override these easily so we put them first.
|
# be able to override these easily so we put them first.
|
||||||
@@ -82,35 +90,46 @@ endif()
|
|||||||
# Use versioning consistent with Makefile:
|
# Use versioning consistent with Makefile:
|
||||||
# the git revision is used but uses the fallback in an archive.
|
# the git revision is used but uses the fallback in an archive.
|
||||||
|
|
||||||
message(CHECK_START "Determining RGBDS version from Git history")
|
if(DEFINED RGBDS_VERSION_STRING) # Possibly stored in the cache, e.g. if specified on the CLI.
|
||||||
list(APPEND CMAKE_MESSAGE_INDENT " ")
|
if(RGBDS_VERSION_STRING STREQUAL "")
|
||||||
set(GIT_REV "") # This fallback is important!
|
message(STATUS "Will generate version string from `version.hpp`")
|
||||||
find_package(Git)
|
|
||||||
list(POP_BACK CMAKE_MESSAGE_INDENT)
|
|
||||||
if(NOT Git_FOUND)
|
|
||||||
message(CHECK_FAIL "Git not found")
|
|
||||||
else()
|
|
||||||
execute_process(COMMAND "${GIT_EXECUTABLE}" --git-dir=.git -c safe.directory='*'
|
|
||||||
describe --tags --dirty --always --match "v[0-9]*"
|
|
||||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
|
||||||
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
||||||
ERROR_VARIABLE git_err ERROR_STRIP_TRAILING_WHITESPACE
|
|
||||||
RESULT_VARIABLE result)
|
|
||||||
if(NOT result EQUAL 0)
|
|
||||||
# Note that this happens e.g. when building from a tarball, so it shouldn't fail the build!
|
|
||||||
message(CHECK_FAIL "error ${result} from Git:")
|
|
||||||
list(APPEND CMAKE_MESSAGE_INDENT " ")
|
|
||||||
message("${git_err}")
|
|
||||||
list(POP_BACK CMAKE_MESSAGE_INDENT)
|
|
||||||
else()
|
else()
|
||||||
message(CHECK_PASS "${GIT_REV}")
|
message(STATUS "Using provided version string - ${RGBDS_VERSION_STRING}")
|
||||||
if(NOT "${GIT_REV}" MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+(-rc[0-9]+)?")
|
endif()
|
||||||
# Can't find an ancestor tag! (That passes `--match`, anyway.)
|
else()
|
||||||
message(WARNING "No `v*` Git tag reachable; falling back")
|
message(CHECK_START "Determining RGBDS version from Git history")
|
||||||
elseif(NOT CMAKE_MATCH_0 STREQUAL "v${CMAKE_PROJECT_VERSION}")
|
# Note that we do NOT store this in the cache, since the Git revision is fairly volatile.
|
||||||
message(SEND_ERROR "\
|
set(RGBDS_VERSION_STRING "") # An empty value causes `version.cpp` to generate a version string.
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MESSAGE_INDENT " ")
|
||||||
|
find_package(Git)
|
||||||
|
list(POP_BACK CMAKE_MESSAGE_INDENT)
|
||||||
|
if(NOT Git_FOUND)
|
||||||
|
message(CHECK_FAIL "Git not found")
|
||||||
|
else()
|
||||||
|
execute_process(COMMAND "${GIT_EXECUTABLE}" --git-dir=.git -c safe.directory='*'
|
||||||
|
describe --tags --dirty --always --match "v[0-9]*"
|
||||||
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||||
|
OUTPUT_VARIABLE RGBDS_VERSION_STRING OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
ERROR_VARIABLE git_err ERROR_STRIP_TRAILING_WHITESPACE
|
||||||
|
RESULT_VARIABLE result)
|
||||||
|
if(NOT result EQUAL 0)
|
||||||
|
# Note that this happens e.g. when building from a tarball, so it shouldn't fail the build!
|
||||||
|
message(CHECK_FAIL "error ${result} from Git:")
|
||||||
|
list(APPEND CMAKE_MESSAGE_INDENT " ")
|
||||||
|
message("${git_err}")
|
||||||
|
list(POP_BACK CMAKE_MESSAGE_INDENT)
|
||||||
|
else()
|
||||||
|
message(CHECK_PASS "${RGBDS_VERSION_STRING}")
|
||||||
|
if(NOT "${RGBDS_VERSION_STRING}" MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+(-rc[0-9]+)?")
|
||||||
|
# Can't find an ancestor tag! (That passes `--match`, anyway.)
|
||||||
|
message(WARNING "No `v*` Git tag reachable; falling back")
|
||||||
|
elseif(NOT CMAKE_MATCH_0 STREQUAL "v${PROJECT_VERSION}")
|
||||||
|
message(SEND_ERROR "\
|
||||||
Version mismatch! Git says ${CMAKE_MATCH_0},
|
Version mismatch! Git says ${CMAKE_MATCH_0},
|
||||||
version.hpp says v${CMAKE_PROJECT_VERSION}!")
|
version.hpp says v${PROJECT_VERSION}
|
||||||
|
Did you forget to consult `RELEASE.md`?")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
@@ -185,7 +204,7 @@ else()
|
|||||||
# We rely on runtimes more modern than that, so we can ignore those warnings.
|
# We rely on runtimes more modern than that, so we can ignore those warnings.
|
||||||
# If this is a problem for you: use Microsoft's equivalents, or define `__USE_MINGW_ANSI_STDIO=1`.
|
# If this is a problem for you: use Microsoft's equivalents, or define `__USE_MINGW_ANSI_STDIO=1`.
|
||||||
# https://sourceforge.net/p/mingw-w64/wiki2/gnu%20printf/
|
# https://sourceforge.net/p/mingw-w64/wiki2/gnu%20printf/
|
||||||
add_compile_options(-Wno-error=format -Wno-error=format-extra-args)
|
add_compile_options(-Wno-format -Wno-format-extra-args)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -215,17 +234,19 @@ endforeach()
|
|||||||
# the rest is rather convention from our side, and thus more appropriate for presets or CLI flags.
|
# the rest is rather convention from our side, and thus more appropriate for presets or CLI flags.
|
||||||
## CPACK_PACKAGE_NAME: copied from `project()`
|
## CPACK_PACKAGE_NAME: copied from `project()`
|
||||||
set(CPACK_PACKAGE_VENDOR "GBDev")
|
set(CPACK_PACKAGE_VENDOR "GBDev")
|
||||||
set(CPACK_PACKAGE_VERSION "${CMAKE_PROJECT_VERSION}") # The individual components are defined implicitly.
|
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") # The individual components are defined implicitly.
|
||||||
set(CPACK_PACKAGE_DESCRIPTION "An assembly toolchain for the Nintendo Game Boy and Game Boy Color") # Same as our repo's description.
|
set(CPACK_PACKAGE_DESCRIPTION "An assembly toolchain for the Nintendo Game Boy and Game Boy Color") # Same as our repo's description.
|
||||||
## CPACK_PACKAGE_DESCRIPTION_SUMMARY: copied from `project()`
|
## CPACK_PACKAGE_DESCRIPTION_SUMMARY: copied from `project()`
|
||||||
set(CPACK_PACKAGE_HOMEPAGE_URL "https://rgbds.gbdev.io")
|
set(CPACK_PACKAGE_HOMEPAGE_URL "https://rgbds.gbdev.io")
|
||||||
## CPACK_PACKAGE_FILE_NAME: should be provided at runtime (`cpack -P`)
|
## CPACK_PACKAGE_FILE_NAME: should be provided at runtime (`cpack -P`)
|
||||||
set(CPACK_PACKAGE_CHECKSUM SHA256)
|
set(CPACK_PACKAGE_CHECKSUM SHA256)
|
||||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
|
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||||
set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md")
|
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
||||||
set(CPACK_STRIP_FILES ON) # Only applies to binary packages, not sources.
|
set(CPACK_STRIP_FILES ON) # Only applies to binary packages, not sources.
|
||||||
set(CPACK_VERBATIM_VARIABLES ON)
|
set(CPACK_VERBATIM_VARIABLES ON)
|
||||||
set(CPACK_THREADS 0) # Use all available CPU cores.
|
set(CPACK_THREADS 0) # Use all available CPU cores.
|
||||||
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)
|
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)
|
||||||
|
set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)
|
||||||
|
set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
|
||||||
|
|
||||||
include(CPack)
|
include(CPack)
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
FROM debian:13-slim
|
FROM debian:13-slim
|
||||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||||
ARG version=1.0.3
|
ARG version=1.0.4
|
||||||
WORKDIR /rgbds
|
WORKDIR /rgbds
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option \
|
|||||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||||
# Non-overridable CXXFLAGS
|
# Non-overridable CXXFLAGS
|
||||||
REQUIREDCXXFLAGS := -std=c++20 -I include -fno-exceptions -fno-rtti
|
REQUIREDCXXFLAGS := -std=c++20 -I include -fno-exceptions -fno-rtti
|
||||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} ${REQUIREDCXXFLAGS}
|
REALCXXFLAGS := ${CXXFLAGS} ${REQUIREDCXXFLAGS}
|
||||||
# Overridable LDFLAGS
|
# Overridable LDFLAGS
|
||||||
LDFLAGS ?=
|
LDFLAGS ?=
|
||||||
# Non-overridable LDFLAGS
|
# Non-overridable LDFLAGS
|
||||||
REQUIREDLDFLAGS := -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
REQUIREDLDFLAGS := -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} ${REQUIREDLDFLAGS}
|
REALLDFLAGS := ${LDFLAGS} ${REQUIREDLDFLAGS}
|
||||||
|
|
||||||
# Wrapper around bison that passes flags depending on what the version supports
|
# Wrapper around bison that passes flags depending on what the version supports
|
||||||
BISON := src/bison.sh
|
BISON := src/bison.sh
|
||||||
@@ -130,22 +130,22 @@ rgbgfx_obj := \
|
|||||||
src/verbosity.o
|
src/verbosity.o
|
||||||
|
|
||||||
rgbasm: ${rgbasm_obj}
|
rgbasm: ${rgbasm_obj}
|
||||||
$Q${CXX} ${REALLDFLAGS} -o $@ ${rgbasm_obj} ${REALCXXFLAGS} src/version.cpp
|
$Q${CXX} ${WARNFLAGS} ${REALLDFLAGS} -o $@ ${rgbasm_obj} ${REALCXXFLAGS} src/version.cpp
|
||||||
|
|
||||||
rgblink: ${rgblink_obj}
|
rgblink: ${rgblink_obj}
|
||||||
$Q${CXX} ${REALLDFLAGS} -o $@ ${rgblink_obj} ${REALCXXFLAGS} src/version.cpp
|
$Q${CXX} ${WARNFLAGS} ${REALLDFLAGS} -o $@ ${rgblink_obj} ${REALCXXFLAGS} src/version.cpp
|
||||||
|
|
||||||
rgbfix: ${rgbfix_obj}
|
rgbfix: ${rgbfix_obj}
|
||||||
$Q${CXX} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCXXFLAGS} src/version.cpp
|
$Q${CXX} ${WARNFLAGS} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCXXFLAGS} src/version.cpp
|
||||||
|
|
||||||
rgbgfx: ${rgbgfx_obj}
|
rgbgfx: ${rgbgfx_obj}
|
||||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} ${PNGLDLIBS} src/version.cpp
|
$Q${CXX} ${WARNFLAGS} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} ${PNGLDLIBS} src/version.cpp
|
||||||
|
|
||||||
test/gfx/randtilegen: test/gfx/randtilegen.cpp
|
test/gfx/randtilegen: test/gfx/randtilegen.cpp
|
||||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
|
$Q${CXX} ${WARNFLAGS} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
|
||||||
|
|
||||||
test/gfx/rgbgfx_test: test/gfx/rgbgfx_test.cpp
|
test/gfx/rgbgfx_test: test/gfx/rgbgfx_test.cpp
|
||||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
|
$Q${CXX} ${WARNFLAGS} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
|
||||||
|
|
||||||
# Rules to process files
|
# Rules to process files
|
||||||
|
|
||||||
@@ -163,29 +163,33 @@ src/link/script.hpp: src/link/script.cpp
|
|||||||
|
|
||||||
# Only RGBGFX uses libpng (POSIX make doesn't support pattern rules to cover all these)
|
# Only RGBGFX uses libpng (POSIX make doesn't support pattern rules to cover all these)
|
||||||
src/gfx/color_set.o: src/gfx/color_set.cpp
|
src/gfx/color_set.o: src/gfx/color_set.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${WARNFLAGS} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
src/gfx/main.o: src/gfx/main.cpp
|
src/gfx/main.o: src/gfx/main.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${WARNFLAGS} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
src/gfx/pal_packing.o: src/gfx/pal_packing.cpp
|
src/gfx/pal_packing.o: src/gfx/pal_packing.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${WARNFLAGS} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
src/gfx/pal_sorting.o: src/gfx/pal_sorting.cpp
|
src/gfx/pal_sorting.o: src/gfx/pal_sorting.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${WARNFLAGS} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
src/gfx/pal_spec.o: src/gfx/pal_spec.cpp
|
src/gfx/pal_spec.o: src/gfx/pal_spec.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${WARNFLAGS} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
src/gfx/png.o: src/gfx/png.cpp
|
src/gfx/png.o: src/gfx/png.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${WARNFLAGS} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
src/gfx/process.o: src/gfx/process.cpp
|
src/gfx/process.o: src/gfx/process.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${WARNFLAGS} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
src/gfx/reverse.o: src/gfx/reverse.cpp
|
src/gfx/reverse.o: src/gfx/reverse.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${WARNFLAGS} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
src/gfx/rgba.o: src/gfx/rgba.cpp
|
src/gfx/rgba.o: src/gfx/rgba.cpp
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CXX} ${WARNFLAGS} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
|
|
||||||
.cpp.o:
|
.cpp.o:
|
||||||
$Q${CXX} ${REALCXXFLAGS} -c -o $@ $<
|
$Q${CXX} ${WARNFLAGS} ${REALCXXFLAGS} -c -o $@ $<
|
||||||
|
|
||||||
|
# Relying on the Makefile itself to introspect the installation commands.
|
||||||
|
# First pretending to build the executables to exclude their build commands,
|
||||||
|
# and then emitting the install commands without actually executing them.
|
||||||
install.sh:
|
install.sh:
|
||||||
$Qecho '#!/usr/bin/env bash' > $@
|
$Qecho '#!/usr/bin/env bash' > $@
|
||||||
|
$Q${MAKE} -t all
|
||||||
$Q${MAKE} -s -n install MANSRC= >> $@
|
$Q${MAKE} -s -n install MANSRC= >> $@
|
||||||
$Qchmod +x $@
|
$Qchmod +x $@
|
||||||
|
|
||||||
@@ -225,7 +229,9 @@ develop:
|
|||||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
||||||
-D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG \
|
-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC -D_GLIBCXX_SANITIZE_VECTOR \
|
||||||
|
-D_GLIBCXX_VERBOSE_ASSERT -D_GLIBCXX_EXTERN_TEMPLATE=0 \
|
||||||
|
-D_LIBCPP_DEBUG -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG \
|
||||||
-fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero" \
|
-fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero" \
|
||||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,16 @@
|
|||||||
# triggers some poorly-tested code paths within Apple's linker, which then crashes.
|
# triggers some poorly-tested code paths within Apple's linker, which then crashes.
|
||||||
# This can be worked around by using LLVM's LLD linker and passing `-fuse-ld=lld` when linking.
|
# This can be worked around by using LLVM's LLD linker and passing `-fuse-ld=lld` when linking.
|
||||||
|
|
||||||
# The `-mmacosx-version-min=10.4` flag ensures that the binary only uses APIs available on Mac OS X 10.4 Tiger.
|
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.4 CACHE STRING "Minimum Mac OS X version to target for deployment (at runtime)")
|
||||||
# The `-arch` flags build a "fat binary" that works on both Apple architectures:
|
# This builds a "fat binary" that works on both Apple architectures:
|
||||||
# older Intel x64 Macs and newer ARM "Apple Silicon" ones.
|
# older Intel x64 Macs and newer ARM "Apple Silicon" ones.
|
||||||
set(secret_sauce -mmacosx-version-min=10.4 "SHELL:-arch x86_64" "SHELL:-arch arm64") # Avoid `-arch` being dedup'd.
|
# Due to a libpng build script limitation/bug (as of 1.6.58), the native architecture has to be first...
|
||||||
add_compile_options(${secret_sauce})
|
# and since our CI builds this executable on an ARM machine, that's what we're putting first.
|
||||||
add_link_options(${secret_sauce})
|
set(CMAKE_OSX_ARCHITECTURES arm64 x86_64 CACHE STRING "Build architectures for Mac OS X")
|
||||||
set(PNG_HARDWARE_OPTIMIZATIONS OFF) # These do not play well with a dual-arch build.
|
# This controls the SIMD optimizations, which include architecture-specific headers that get rejected
|
||||||
|
# in a dual-arch build, thus we have to disable them.
|
||||||
|
# This shouldn't be a big deal for RGBGFX, anyway?
|
||||||
|
set(PNG_HARDWARE_OPTIMIZATIONS OFF)
|
||||||
|
|
||||||
# Mac OS X has always provided zlib, so we can safely link dynamically against it.
|
# Mac OS X has always provided zlib, so we can safely link dynamically against it.
|
||||||
# However, libpng is *not* provided by default, so we link it statically, which requires downloading and building it from source.
|
# However, libpng is *not* provided by default, so we link it statically, which requires downloading and building it from source.
|
||||||
|
|||||||
@@ -22,4 +22,4 @@ for (( i = 0; i < NB_PALS; i++ )); do
|
|||||||
printf $(printf '\\x%x' $i{,,,}) >> "$TMP/tmp.palmap"
|
printf $(printf '\\x%x' $i{,,,}) >> "$TMP/tmp.palmap"
|
||||||
done
|
done
|
||||||
|
|
||||||
"${RGBGFX:-${RGBDS+$RGBDS/}rgbgfx}" -r 4 "$2" -o "$TMP/tmp.2bpp" -OTQ -p "$1" -n "$NB_PALS"
|
"${RGBGFX:-${RGBDS+$RGBDS/}rgbgfx}" -o "$TMP/tmp.2bpp" -OTQ -p "$1" -n "$NB_PALS" -r 4 -- "$2"
|
||||||
|
|||||||
+2
-1
@@ -12,7 +12,8 @@ GitHub.
|
|||||||
- [Dockerfile](/Dockerfile): update `ARG version`.
|
- [Dockerfile](/Dockerfile): update `ARG version`.
|
||||||
- [test/external/\*.cfg](/test/external): update test dependency commits
|
- [test/external/\*.cfg](/test/external): update test dependency commits
|
||||||
(preferably, use the latest available).
|
(preferably, use the latest available).
|
||||||
- [man/\*](/man/): update dates and authors.
|
- [man/\*](/man/): update dates and authors. You can use
|
||||||
|
`sed -Ei '/^\.Dd/ s/ .*/ '"$(LC_ALL=C date +'%B %d, %Y')"/ man/*`.
|
||||||
|
|
||||||
2. Create a Git tag formatted as <code>v<i><MAJOR></i>.<i><MINOR></i>.<i><PATCH></i></code>,
|
2. Create a Git tag formatted as <code>v<i><MAJOR></i>.<i><MINOR></i>.<i><PATCH></i></code>,
|
||||||
or <code>v<i><MAJOR></i>.<i><MINOR></i>.<i><PATCH></i>-rc<i><RC></i></code>
|
or <code>v<i><MAJOR></i>.<i><MINOR></i>.<i><PATCH></i>-rc<i><RC></i></code>
|
||||||
|
|||||||
@@ -57,10 +57,12 @@ struct Expression {
|
|||||||
void addCheckBitIndex(uint8_t mask);
|
void addCheckBitIndex(uint8_t mask);
|
||||||
|
|
||||||
void checkNBit(uint8_t n) const;
|
void checkNBit(uint8_t n) const;
|
||||||
|
void checkSignedNBit(uint8_t n) const;
|
||||||
|
|
||||||
void encode(std::vector<uint8_t> &buffer) const;
|
void encode(std::vector<uint8_t> &buffer) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool checkNBit(int32_t v, uint8_t n, char const *name);
|
bool checkNBit(int32_t v, uint8_t n, char const *name);
|
||||||
|
bool checkSignedNBit(int32_t v, uint8_t n, char const *name);
|
||||||
|
|
||||||
#endif // RGBDS_ASM_RPN_HPP
|
#endif // RGBDS_ASM_RPN_HPP
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ struct Section {
|
|||||||
std::deque<Patch> patches;
|
std::deque<Patch> patches;
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
|
|
||||||
|
SectionTypeInfo const &typeInfo() const { return sectionTypeInfo[type]; }
|
||||||
|
|
||||||
uint32_t getID() const; // ID of the section in the object file (`UINT32_MAX` if none)
|
uint32_t getID() const; // ID of the section in the object file (`UINT32_MAX` if none)
|
||||||
bool isSizeKnown() const;
|
bool isSizeKnown() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ struct Options {
|
|||||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
||||||
enum {
|
enum {
|
||||||
NO_SPEC,
|
NO_SPEC,
|
||||||
EXPLICIT,
|
INLINE,
|
||||||
|
EXTERNAL,
|
||||||
EMBEDDED,
|
EMBEDDED,
|
||||||
DMG,
|
DMG,
|
||||||
} palSpecType = NO_SPEC; // -c
|
} palSpecType = NO_SPEC; // -c
|
||||||
@@ -59,6 +60,8 @@ struct Options {
|
|||||||
|
|
||||||
uint16_t maxNbColors() const { return nbColorsPerPal * nbPalettes; }
|
uint16_t maxNbColors() const { return nbColorsPerPal * nbPalettes; }
|
||||||
|
|
||||||
|
bool hasExplicitPalSpec() const { return palSpecType == INLINE || palSpecType == EXTERNAL; }
|
||||||
|
|
||||||
uint8_t dmgColors[4] = {};
|
uint8_t dmgColors[4] = {};
|
||||||
uint8_t dmgValue(uint8_t i) const {
|
uint8_t dmgValue(uint8_t i) const {
|
||||||
assume(i < 4);
|
assume(i < 4);
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
void parseInlinePalSpec(char const * const rawArg);
|
void parseInlinePalSpec(char const * const rawArg);
|
||||||
void parseExternalPalSpec(char const *arg);
|
void parseExternalPalSpec(char const *arg);
|
||||||
void parseDmgPalSpec(char const * const rawArg);
|
void parseDmgPalSpec(char const * const rawArg);
|
||||||
void parseDmgPalSpec(uint8_t palSpecDmg);
|
|
||||||
|
|
||||||
void parseBackgroundPalSpec(char const *arg);
|
void parseBackgroundPalSpec(char const *arg);
|
||||||
|
|
||||||
#endif // RGBDS_GFX_PAL_SPEC_HPP
|
#endif // RGBDS_GFX_PAL_SPEC_HPP
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ struct Png {
|
|||||||
uint32_t height = 0;
|
uint32_t height = 0;
|
||||||
std::vector<Rgba> pixels{};
|
std::vector<Rgba> pixels{};
|
||||||
std::vector<Rgba> palette{};
|
std::vector<Rgba> palette{};
|
||||||
|
bool isIndexed = false;
|
||||||
|
|
||||||
Png() {}
|
Png() {}
|
||||||
Png(char const *filename, std::streambuf &file);
|
Png(char const *filename, std::streambuf &file);
|
||||||
|
|||||||
+10
-19
@@ -20,23 +20,6 @@ struct Rgba {
|
|||||||
explicit constexpr Rgba(uint32_t rgba = 0)
|
explicit constexpr Rgba(uint32_t rgba = 0)
|
||||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||||
|
|
||||||
// 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 (1 << 15) exactly.
|
|
||||||
static constexpr uint16_t transparent = 0b1'00000'00000'00000;
|
|
||||||
|
|
||||||
static constexpr Rgba fromCGBColor(uint16_t color) {
|
|
||||||
constexpr auto _5to8 = [](uint8_t channel) -> uint8_t {
|
|
||||||
channel &= 0b11111; // For caller's convenience
|
|
||||||
return channel << 3 | channel >> 2;
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
_5to8(color),
|
|
||||||
_5to8(color >> 5),
|
|
||||||
_5to8(color >> 10),
|
|
||||||
static_cast<uint8_t>(color & transparent ? 0x00 : 0xFF),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns this RGBA as a 32-bit number that can be printed in hex (`#%08x`)
|
// Returns this RGBA as a 32-bit number that can be printed in hex (`#%08x`)
|
||||||
// to yield its CSS representation (`#rrggbbaa`).
|
// to yield its CSS representation (`#rrggbbaa`).
|
||||||
uint32_t toCSS() const {
|
uint32_t toCSS() const {
|
||||||
@@ -45,14 +28,22 @@ struct Rgba {
|
|||||||
};
|
};
|
||||||
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); }
|
bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); }
|
||||||
|
|
||||||
|
// We allow some leeway to consider colors as transparent or opaque,
|
||||||
|
// but intermediate alpha values are still ambiguous.
|
||||||
static constexpr uint8_t transparency_threshold = 0x10;
|
static constexpr uint8_t transparency_threshold = 0x10;
|
||||||
bool isTransparent() const { return alpha < transparency_threshold; }
|
bool isTransparent() const { return alpha < transparency_threshold; }
|
||||||
static constexpr uint8_t opacity_threshold = 0xF0;
|
static constexpr uint8_t opacity_threshold = 0xF0;
|
||||||
bool isOpaque() const { return alpha >= opacity_threshold; }
|
bool isOpaque() const { return alpha >= opacity_threshold; }
|
||||||
// Computes the equivalent CGB color, respects the color curve depending on options
|
bool isAmbiguous() const { return isTransparent() == isOpaque(); }
|
||||||
|
|
||||||
|
// 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 (1 << 15) exactly.
|
||||||
|
static constexpr uint16_t transparent = 0b1'00000'00000'00000;
|
||||||
|
// Computes the equivalent RGB888 color; respects the color curve depending on argument
|
||||||
|
static Rgba fromCGBColor(uint16_t color, bool useColorCurve);
|
||||||
|
// Computes the equivalent RGB555 color; respects the color curve depending on options
|
||||||
uint16_t cgbColor() const;
|
uint16_t cgbColor() const;
|
||||||
|
|
||||||
bool isGray() const { return red == green && green == blue; }
|
bool isGray() const { return red == green && green == blue; }
|
||||||
|
|||||||
@@ -30,8 +30,12 @@ auto end(ReversedIterable<IterableT> r) {
|
|||||||
return std::rend(r._iterable);
|
return std::rend(r._iterable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent creation of a dangling reference to a temporary rvalue
|
||||||
template<typename IterableT>
|
template<typename IterableT>
|
||||||
ReversedIterable<IterableT> reversed(IterableT &&_iterable) {
|
ReversedIterable<IterableT> reversed(IterableT &&_iterable) = delete;
|
||||||
|
|
||||||
|
template<typename IterableT>
|
||||||
|
ReversedIterable<IterableT> reversed(IterableT &_iterable) {
|
||||||
return {_iterable};
|
return {_iterable};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ void out_AddSection(Section const §ion);
|
|||||||
// Finds an assigned section overlapping another one.
|
// Finds an assigned section overlapping another one.
|
||||||
Section const *out_OverlappingSection(Section const §ion);
|
Section const *out_OverlappingSection(Section const §ion);
|
||||||
|
|
||||||
// Writes all output (bin, sym, map) files.
|
void out_WriteROMIfAny();
|
||||||
void out_WriteFiles();
|
void out_WriteSymIfAny();
|
||||||
|
void out_WriteMapIfAny();
|
||||||
|
|
||||||
#endif // RGBDS_LINK_OUTPUT_HPP
|
#endif // RGBDS_LINK_OUTPUT_HPP
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ struct Section {
|
|||||||
std::vector<Symbol *> symbols;
|
std::vector<Symbol *> symbols;
|
||||||
std::unique_ptr<Section> nextPiece; // The next fragment or union "piece" of this section
|
std::unique_ptr<Section> nextPiece; // The next fragment or union "piece" of this section
|
||||||
|
|
||||||
|
SectionTypeInfo const &typeInfo() const { return sectionTypeInfo[type]; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Template class for both const and non-const iterators over the "pieces" of this section
|
// Template class for both const and non-const iterators over the "pieces" of this section
|
||||||
template<QualifiedEquivalent<Section> SectionT>
|
template<QualifiedEquivalent<Section> SectionT>
|
||||||
|
|||||||
+18
-19
@@ -4,7 +4,6 @@
|
|||||||
#define RGBDS_LINKDEFS_HPP
|
#define RGBDS_LINKDEFS_HPP
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "helpers.hpp" // assume
|
#include "helpers.hpp" // assume
|
||||||
|
|
||||||
@@ -82,6 +81,13 @@ static constexpr uint8_t SECTTYPE_TYPE_MASK = 0b111;
|
|||||||
static constexpr uint8_t SECTTYPE_UNION_BIT = 7;
|
static constexpr uint8_t SECTTYPE_UNION_BIT = 7;
|
||||||
static constexpr uint8_t SECTTYPE_FRAGMENT_BIT = 6;
|
static constexpr uint8_t SECTTYPE_FRAGMENT_BIT = 6;
|
||||||
|
|
||||||
|
// Tells whether a section has data in its object file definition,
|
||||||
|
// depending on type.
|
||||||
|
static inline bool sectTypeHasData(SectionType type) {
|
||||||
|
assume(type != SECTTYPE_INVALID);
|
||||||
|
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||||
|
}
|
||||||
|
|
||||||
enum FileStackNodeType {
|
enum FileStackNodeType {
|
||||||
NODE_REPT,
|
NODE_REPT,
|
||||||
NODE_FILE,
|
NODE_FILE,
|
||||||
@@ -90,31 +96,24 @@ enum FileStackNodeType {
|
|||||||
|
|
||||||
static constexpr uint8_t FSTACKNODE_QUIET_BIT = 7;
|
static constexpr uint8_t FSTACKNODE_QUIET_BIT = 7;
|
||||||
|
|
||||||
// Nont-`const` members may be patched in RGBLINK depending on CLI flags
|
// Non-`const` members may be patched in RGBLINK depending on CLI flags
|
||||||
extern struct SectionTypeInfo {
|
struct SectionTypeInfo {
|
||||||
std::string const name;
|
char const *name;
|
||||||
uint16_t const startAddr;
|
uint16_t const startAddr;
|
||||||
uint16_t size;
|
uint16_t size;
|
||||||
uint32_t const firstBank;
|
uint32_t const firstBank;
|
||||||
uint32_t lastBank;
|
uint32_t lastBank;
|
||||||
} sectionTypeInfo[SECTTYPE_INVALID];
|
|
||||||
|
|
||||||
// Tells whether a section has data in its object file definition,
|
// Returns a memory region's end address (last byte), e.g. 0x7FFF
|
||||||
// depending on type.
|
uint16_t endAddr() const { return startAddr + size - 1; }
|
||||||
static inline bool sectTypeHasData(SectionType type) {
|
|
||||||
assume(type != SECTTYPE_INVALID);
|
|
||||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a memory region's end address (last byte), e.g. 0x7FFF
|
// Returns a memory region's number of banks, or 1 for regions without banking
|
||||||
static inline uint16_t sectTypeEndAddr(SectionType type) {
|
uint32_t nbBanks() const { return lastBank - firstBank + 1; }
|
||||||
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a memory region's number of banks, or 1 for regions without banking
|
bool isBanked() const { return nbBanks() != 1; }
|
||||||
static inline uint32_t sectTypeBanks(SectionType type) {
|
};
|
||||||
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
|
|
||||||
}
|
extern SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID];
|
||||||
|
|
||||||
enum SectionModifier { SECTION_NORMAL, SECTION_UNION, SECTION_FRAGMENT };
|
enum SectionModifier { SECTION_NORMAL, SECTION_UNION, SECTION_FRAGMENT };
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
int32_t op_add(int32_t augend, int32_t addend);
|
||||||
|
int32_t op_sub(int32_t minuend, int32_t subtrahend);
|
||||||
|
int32_t op_mul(int32_t multiplicand, int32_t multiplier);
|
||||||
int32_t op_divide(int32_t dividend, int32_t divisor);
|
int32_t op_divide(int32_t dividend, int32_t divisor);
|
||||||
int32_t op_modulo(int32_t dividend, int32_t divisor);
|
int32_t op_modulo(int32_t dividend, int32_t divisor);
|
||||||
int32_t op_exponent(int32_t base, uint32_t power);
|
int32_t op_exponent(int32_t base, uint32_t power);
|
||||||
|
|||||||
@@ -66,4 +66,26 @@
|
|||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Apple has deprecated `sprintf` since Xcode 14 (for macOS 13), but we use it solely in
|
||||||
|
// contexts where both the size of the buffer *and* max size of the printed string are
|
||||||
|
// known statically, which GCC thus checks for.
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#define sprintf_to_array(array, ...) \
|
||||||
|
do { \
|
||||||
|
static_assert( \
|
||||||
|
std::is_array_v<decltype(array)>, "Only use this macro to print to an array!" \
|
||||||
|
); \
|
||||||
|
snprintf(array, sizeof(array), __VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define sprintf_to_array(array, ...) \
|
||||||
|
do { \
|
||||||
|
static_assert( \
|
||||||
|
std::is_array_v<decltype(array)>, "Only use this macro to print to an array!" \
|
||||||
|
); \
|
||||||
|
sprintf(array, __VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // RGBDS_PLATFORM_HPP
|
#endif // RGBDS_PLATFORM_HPP
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#define PACKAGE_VERSION_MAJOR 1
|
#define PACKAGE_VERSION_MAJOR 1
|
||||||
#define PACKAGE_VERSION_MINOR 0
|
#define PACKAGE_VERSION_MINOR 0
|
||||||
#define PACKAGE_VERSION_PATCH 3
|
#define PACKAGE_VERSION_PATCH 4
|
||||||
// #define PACKAGE_VERSION_RC 1
|
// #define PACKAGE_VERSION_RC 1
|
||||||
|
|
||||||
char const *get_package_version_string();
|
char const *get_package_version_string();
|
||||||
|
|||||||
+7
-7
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd August 1, 2026
|
.Dd September 22, 2026
|
||||||
.Dt GBZ80 7
|
.Dt GBZ80 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -1525,7 +1525,7 @@ Set according to result.
|
|||||||
.Ss RLC r8
|
.Ss RLC r8
|
||||||
Rotate register
|
Rotate register
|
||||||
.Ar r8
|
.Ar r8
|
||||||
left.
|
Left Circularly.
|
||||||
.Bd -literal
|
.Bd -literal
|
||||||
┏━ Flags ━┓ ┏━━━━━━━ r8 ━━━━━━┓
|
┏━ Flags ━┓ ┏━━━━━━━ r8 ━━━━━━┓
|
||||||
┃ C ←╂─┬─╂─ b7 ← ... ← b0 ←╂─┐
|
┃ C ←╂─┬─╂─ b7 ← ... ← b0 ←╂─┐
|
||||||
@@ -1551,7 +1551,7 @@ Set according to result.
|
|||||||
.Ss RLC [HL]
|
.Ss RLC [HL]
|
||||||
Rotate the byte pointed to by
|
Rotate the byte pointed to by
|
||||||
.Sy HL
|
.Sy HL
|
||||||
left.
|
Left Circularly.
|
||||||
.Bd -literal
|
.Bd -literal
|
||||||
┏━ Flags ━┓ ┏━━━━━━ [HL] ━━━━━┓
|
┏━ Flags ━┓ ┏━━━━━━ [HL] ━━━━━┓
|
||||||
┃ C ←╂─┬─╂─ b7 ← ... ← b0 ←╂─┐
|
┃ C ←╂─┬─╂─ b7 ← ... ← b0 ←╂─┐
|
||||||
@@ -1568,7 +1568,7 @@ Flags: See
|
|||||||
.Ss RLCA
|
.Ss RLCA
|
||||||
Rotate register
|
Rotate register
|
||||||
.Sy A
|
.Sy A
|
||||||
left.
|
Left Circularly.
|
||||||
.Bd -literal
|
.Bd -literal
|
||||||
┏━ Flags ━┓ ┏━━━━━━━ A ━━━━━━━┓
|
┏━ Flags ━┓ ┏━━━━━━━ A ━━━━━━━┓
|
||||||
┃ C ←╂─┬─╂─ b7 ← ... ← b0 ←╂─┐
|
┃ C ←╂─┬─╂─ b7 ← ... ← b0 ←╂─┐
|
||||||
@@ -1663,7 +1663,7 @@ Set according to result.
|
|||||||
.Ss RRC r8
|
.Ss RRC r8
|
||||||
Rotate register
|
Rotate register
|
||||||
.Ar r8
|
.Ar r8
|
||||||
right.
|
Right Circularly.
|
||||||
.Bd -literal
|
.Bd -literal
|
||||||
┏━━━━━━━ r8 ━━━━━━┓ ┏━ Flags ━┓
|
┏━━━━━━━ r8 ━━━━━━┓ ┏━ Flags ━┓
|
||||||
┌─╂→ b7 → ... → b0 ─╂─┬─╂→ C ┃
|
┌─╂→ b7 → ... → b0 ─╂─┬─╂→ C ┃
|
||||||
@@ -1689,7 +1689,7 @@ Set according to result.
|
|||||||
.Ss RRC [HL]
|
.Ss RRC [HL]
|
||||||
Rotate the byte pointed to by
|
Rotate the byte pointed to by
|
||||||
.Sy HL
|
.Sy HL
|
||||||
right.
|
Right Circularly.
|
||||||
.Bd -literal
|
.Bd -literal
|
||||||
┏━━━━━━ [HL] ━━━━━┓ ┏━ Flags ━┓
|
┏━━━━━━ [HL] ━━━━━┓ ┏━ Flags ━┓
|
||||||
┌─╂→ b7 → ... → b0 ─╂─┬─╂→ C ┃
|
┌─╂→ b7 → ... → b0 ─╂─┬─╂→ C ┃
|
||||||
@@ -1706,7 +1706,7 @@ Flags: See
|
|||||||
.Ss RRCA
|
.Ss RRCA
|
||||||
Rotate register
|
Rotate register
|
||||||
.Sy A
|
.Sy A
|
||||||
right.
|
Right Circularly.
|
||||||
.Bd -literal
|
.Bd -literal
|
||||||
┏━━━━━━━ A ━━━━━━━┓ ┏━ Flags ━┓
|
┏━━━━━━━ A ━━━━━━━┓ ┏━ Flags ━┓
|
||||||
┌─╂→ b7 → ... → b0 ─╂─┬─╂→ C ┃
|
┌─╂→ b7 → ... → b0 ─╂─┬─╂→ C ┃
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd August 1, 2026
|
.Dd September 22, 2026
|
||||||
.Dt RGBASM-OLD 5
|
.Dt RGBASM-OLD 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd August 1, 2026
|
.Dd September 22, 2026
|
||||||
.Dt RGBASM 1
|
.Dt RGBASM 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
.Nd Game Boy assembler
|
.Nd Game Boy assembler
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl EhVvw
|
.Op Fl EhVw
|
||||||
|
.Op Fl v Op Fl v No ...
|
||||||
.Op Fl B Ar param
|
.Op Fl B Ar param
|
||||||
.Op Fl b Ar chars
|
.Op Fl b Ar chars
|
||||||
.Op Fl \-color Ar when
|
.Op Fl \-color Ar when
|
||||||
|
|||||||
+16
-3
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd August 1, 2026
|
.Dd September 22, 2026
|
||||||
.Dt RGBASM 5
|
.Dt RGBASM 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -729,17 +729,21 @@ String constants are not expanded within the parentheses.
|
|||||||
.Ic IF ) ,
|
.Ic IF ) ,
|
||||||
or 0 if only RGBLINK can compute its value.
|
or 0 if only RGBLINK can compute its value.
|
||||||
.It Fn BANK arg Ta Returns a bank number.
|
.It Fn BANK arg Ta Returns a bank number.
|
||||||
|
.Pp
|
||||||
If
|
If
|
||||||
.Ar arg
|
.Ar arg
|
||||||
is the symbol
|
is the symbol
|
||||||
.Ic @ ,
|
.Ic @ ,
|
||||||
this function returns the bank of the current section.
|
this function returns the bank of the current section.
|
||||||
|
.Pp
|
||||||
If
|
If
|
||||||
.Ar arg
|
.Ar arg
|
||||||
is a string, it returns the bank of the section that has that name.
|
is a string, it returns the bank of the section that has that name.
|
||||||
|
.Pp
|
||||||
If
|
If
|
||||||
.Ar arg
|
.Ar arg
|
||||||
is a label, it returns the bank number the label is in.
|
is a label, it returns the bank number the label is in.
|
||||||
|
.Pp
|
||||||
The result may be constant if
|
The result may be constant if
|
||||||
.Nm
|
.Nm
|
||||||
is able to compute it.
|
is able to compute it.
|
||||||
@@ -757,10 +761,17 @@ this function returns the name of the current section.
|
|||||||
.Ar arg
|
.Ar arg
|
||||||
is a string, this function returns the size of the section named
|
is a string, this function returns the size of the section named
|
||||||
.Ar arg .
|
.Ar arg .
|
||||||
|
The result is a known constant if the section is already "completed" (i.e. not a
|
||||||
|
.Ic UNION
|
||||||
|
or
|
||||||
|
.Ic FRAGMENT
|
||||||
|
and no longer the current section).
|
||||||
|
.Pp
|
||||||
If
|
If
|
||||||
.Ar arg
|
.Ar arg
|
||||||
is a section type keyword, it returns the size of that section type.
|
is a section type keyword, it returns the size of that section type.
|
||||||
The result is not constant, since only RGBLINK can compute its value.
|
The result is not constant, since only RGBLINK can compute its value.
|
||||||
|
.Pp
|
||||||
If
|
If
|
||||||
.Ar arg
|
.Ar arg
|
||||||
is an 8-bit or 16-bit register, it returns the size of that register.
|
is an 8-bit or 16-bit register, it returns the size of that register.
|
||||||
@@ -768,6 +779,8 @@ is an 8-bit or 16-bit register, it returns the size of that register.
|
|||||||
.Ar arg
|
.Ar arg
|
||||||
is a string, this function returns the starting address of the section named
|
is a string, this function returns the starting address of the section named
|
||||||
.Ar arg .
|
.Ar arg .
|
||||||
|
The result is a known constant if the section has a fixed address.
|
||||||
|
.Pp
|
||||||
If
|
If
|
||||||
.Ar arg
|
.Ar arg
|
||||||
is a section type keyword, it returns the starting address of that section type.
|
is a section type keyword, it returns the starting address of that section type.
|
||||||
@@ -2052,8 +2065,8 @@ It's possible to pass arguments to macros as well!
|
|||||||
MACRO lb
|
MACRO lb
|
||||||
ld \e1, (\e2) << 8 | (\e3)
|
ld \e1, (\e2) << 8 | (\e3)
|
||||||
ENDM
|
ENDM
|
||||||
lb hl, 20, 18 ; Expands to "ld hl, ((20) << 8) | (18)"
|
lb hl, 20, 18 ; Expands to "ld hl, (20) << 8 | (18)"
|
||||||
lb de, 3 + 1, NUM**2 ; Expands to "ld de, ((3 + 1) << 8) | (NUM**2)"
|
lb de, 3 + 1, NUM**2 ; Expands to "ld de, (3 + 1) << 8 | (NUM**2)"
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
You expand the arguments inside the macro body by using the escape sequences
|
You expand the arguments inside the macro body by using the escape sequences
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd August 1, 2026
|
.Dd September 22, 2026
|
||||||
.Dt RGBDS 5
|
.Dt RGBDS 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd August 1, 2026
|
.Dd September 22, 2026
|
||||||
.Dt RGBDS 7
|
.Dt RGBDS 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd August 1, 2026
|
.Dd September 22, 2026
|
||||||
.Dt RGBFIX 1
|
.Dt RGBFIX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
+14
-10
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd August 1, 2026
|
.Dd September 22, 2026
|
||||||
.Dt RGBGFX 1
|
.Dt RGBGFX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -491,9 +491,17 @@ deduplication if
|
|||||||
.Fl u
|
.Fl u
|
||||||
was enabled, so you probably don't want to use this option in combination with
|
was enabled, so you probably don't want to use this option in combination with
|
||||||
.Fl u .
|
.Fl u .
|
||||||
Note also that the tiles that don't get output will not count towards
|
.Pp
|
||||||
|
Also note that the tiles that don't get output in the tile data
|
||||||
|
.Em do
|
||||||
|
still count towards
|
||||||
.Fl N Ap s
|
.Fl N Ap s
|
||||||
limit.
|
limit, since they may get output in the tilemap or attribute map.
|
||||||
|
If you need to omit input tiles from all output in a way that does not count towards
|
||||||
|
.Fl N ,
|
||||||
|
use
|
||||||
|
.Fl B
|
||||||
|
background tiles.
|
||||||
.It Fl Y , Fl \-mirror-y
|
.It Fl Y , Fl \-mirror-y
|
||||||
Deduplicate tiles that are vertically symmetrical mirror images of each other across the Y axis.
|
Deduplicate tiles that are vertically symmetrical mirror images of each other across the Y axis.
|
||||||
Implies
|
Implies
|
||||||
@@ -585,15 +593,11 @@ for example because you want to use palette swaps, please use
|
|||||||
.Fl c
|
.Fl c
|
||||||
to specify the palette explicitly.
|
to specify the palette explicitly.
|
||||||
.Pp
|
.Pp
|
||||||
First, if the image contains
|
Note that if the image contains
|
||||||
.Em any
|
.Em any
|
||||||
transparent pixel, color #0 of
|
transparent pixels, color #0 of
|
||||||
.Em all
|
.Em all
|
||||||
palettes will be allocated to it.
|
palettes will be transparent.
|
||||||
This is done
|
|
||||||
.Sy even if palettes were explicitly specified using Fl c ;
|
|
||||||
then the specification only covers color #1 onwards.
|
|
||||||
.Pq If you do not want this, ask your image editor to remove the alpha channel.
|
|
||||||
.Pp
|
.Pp
|
||||||
After generating palettes,
|
After generating palettes,
|
||||||
.Nm
|
.Nm
|
||||||
|
|||||||
+11
-3
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd August 1, 2026
|
.Dd September 22, 2026
|
||||||
.Dt RGBLINK 1
|
.Dt RGBLINK 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
.Nd Game Boy linker
|
.Nd Game Boy linker
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl dhMtVvwx
|
.Op Fl dhMtVwx
|
||||||
|
.Op Fl v Op Fl v No ...
|
||||||
.Op Fl B Ar param
|
.Op Fl B Ar param
|
||||||
.Op Fl \-color Ar when
|
.Op Fl \-color Ar when
|
||||||
.Op Fl l Ar linker_script
|
.Op Fl l Ar linker_script
|
||||||
@@ -284,7 +285,7 @@ Certain regions allow omitting the size, in which case it defaults to its maximu
|
|||||||
.Bl -column "Region name" "Max value" "Size optional"
|
.Bl -column "Region name" "Max value" "Size optional"
|
||||||
Region name Ta Max size Ta Size optional
|
Region name Ta Max size Ta Size optional
|
||||||
.Cm romx Ta 65535 Ta \&No
|
.Cm romx Ta 65535 Ta \&No
|
||||||
.Cm sram Ta 255 Ta \&No
|
.Cm sram Ta 256 Ta \&No
|
||||||
.Cm wramx Ta 7 Ta Yes
|
.Cm wramx Ta 7 Ta Yes
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
@@ -307,6 +308,13 @@ Later region specs override earlier ones; for example,
|
|||||||
disables scrambling for
|
disables scrambling for
|
||||||
.Cm romx .
|
.Cm romx .
|
||||||
.Pp
|
.Pp
|
||||||
|
The arguments to multiple
|
||||||
|
.Fl S
|
||||||
|
options are processed as if concatenated; for example,
|
||||||
|
.Fl S Cm romx=16 Fl S Cm wramx=8 Fl S Cm sram=4
|
||||||
|
will work the same as
|
||||||
|
.Fl S Cm romx=16,wramx=8,sram=4 .
|
||||||
|
.Pp
|
||||||
.Cm wramx
|
.Cm wramx
|
||||||
scrambling is silently ignored if
|
scrambling is silently ignored if
|
||||||
.Fl w
|
.Fl w
|
||||||
|
|||||||
+1
-4
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd August 1, 2026
|
.Dd September 22, 2026
|
||||||
.Dt RGBLINK 5
|
.Dt RGBLINK 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -73,14 +73,11 @@ and
|
|||||||
.Ic HRAM .
|
.Ic HRAM .
|
||||||
The bank number can be omitted from the types that only contain a single bank, which are:
|
The bank number can be omitted from the types that only contain a single bank, which are:
|
||||||
.Ic ROM0 ,
|
.Ic ROM0 ,
|
||||||
.Ic ROMX No if Fl t No is passed to Xr rgblink 1 ,
|
|
||||||
.Ic VRAM No if Fl d No is passed to Xr rgblink 1 ,
|
.Ic VRAM No if Fl d No is passed to Xr rgblink 1 ,
|
||||||
.Ic WRAM0 ,
|
.Ic WRAM0 ,
|
||||||
.Ic WRAMX No if Fl w No is passed to Xr rgblink 1 ,
|
|
||||||
.Ic OAM ,
|
.Ic OAM ,
|
||||||
and
|
and
|
||||||
.Ic HRAM .
|
.Ic HRAM .
|
||||||
.Pq Ic SRAM No is the only type that can never have its bank number omitted.
|
|
||||||
.Pp
|
.Pp
|
||||||
After a bank specification, the
|
After a bank specification, the
|
||||||
.Dq current address
|
.Dq current address
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ add_library(common OBJECT
|
|||||||
"util.cpp"
|
"util.cpp"
|
||||||
"version.cpp"
|
"version.cpp"
|
||||||
)
|
)
|
||||||
target_compile_definitions(common PRIVATE "BUILD_VERSION_STRING=\"${GIT_REV}\"")
|
target_compile_definitions(common PRIVATE "BUILD_VERSION_STRING=\"${RGBDS_VERSION_STRING}\"")
|
||||||
|
|
||||||
find_package(BISON 3.0.0 REQUIRED)
|
find_package(BISON 3.0.0 REQUIRED)
|
||||||
# BISON 4.0 deprecates passing this BISON_FLAGS string to `bison_target`'s `COMPILE_FLAGS`,
|
# BISON 4.0 deprecates passing this BISON_FLAGS string to `bison_target`'s `COMPILE_FLAGS`,
|
||||||
|
|||||||
+1
-1
@@ -555,7 +555,7 @@ std::string act_StringFormat(
|
|||||||
std::string str;
|
std::string str;
|
||||||
size_t argIndex = 0;
|
size_t argIndex = 0;
|
||||||
|
|
||||||
for (size_t i = 0; spec[i] != '\0';) {
|
for (size_t i = 0; i < spec.length();) {
|
||||||
if (int c = spec[i]; c != '%') {
|
if (int c = spec[i]; c != '%') {
|
||||||
str += c;
|
str += c;
|
||||||
++i;
|
++i;
|
||||||
|
|||||||
+14
-6
@@ -542,7 +542,7 @@ static uint32_t readBracketedMacroArgNum() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
uint32_t n = readNumber<10>(bumpChar(), nullptr);
|
uint32_t n = readNumber<10>(bumpChar(), nullptr);
|
||||||
if (n > INT32_MAX) {
|
if (n > INT32_MAX && !(negative && n == static_cast<uint32_t>(INT32_MAX) + 1)) {
|
||||||
error("Number in bracketed macro argument is too large");
|
error("Number in bracketed macro argument is too large");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1005,14 +1005,22 @@ static uint32_t finishReadingFixedPoint(uint32_t integer) {
|
|||||||
precision = options.fixPrecision;
|
precision = options.fixPrecision;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (integer >= (1ULL << (32 - precision))) {
|
|
||||||
warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cast to unsigned avoids undefined overflow behavior
|
// Cast to unsigned avoids undefined overflow behavior
|
||||||
uint32_t fractional =
|
uint32_t fractional =
|
||||||
static_cast<uint32_t>(round(static_cast<double>(dividend) / divisor * (1ULL << precision)));
|
static_cast<uint32_t>(round(static_cast<double>(dividend) / divisor * (1ULL << precision)));
|
||||||
|
// Carry from `fractional` to `integer` if `round` rounded up to the next integer
|
||||||
|
assume(fractional <= 1ULL << precision);
|
||||||
|
bool overflowed = false;
|
||||||
|
if (fractional == 1ULL << precision) {
|
||||||
|
overflowed = integer == UINT32_MAX;
|
||||||
|
++integer; // This may overflow from UINT32_MAX to 0
|
||||||
|
fractional = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overflowed || integer >= 1ULL << (32 - precision)) {
|
||||||
|
warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return (integer << precision) | fractional;
|
return (integer << precision) | fractional;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ void MacroArgs::shiftArgs(int32_t count) {
|
|||||||
count > 0 && (static_cast<uint32_t>(count) > nbArgs || shift > nbArgs - count)) {
|
count > 0 && (static_cast<uint32_t>(count) > nbArgs || shift > nbArgs - count)) {
|
||||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end");
|
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end");
|
||||||
shift = nbArgs;
|
shift = nbArgs;
|
||||||
} else if (count < 0 && shift < static_cast<uint32_t>(-count)) {
|
} else if (count < 0 && (count == INT32_MIN || shift < static_cast<uint32_t>(-count))) {
|
||||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their beginning");
|
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their beginning");
|
||||||
shift = 0;
|
shift = 0;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -162,6 +162,10 @@ void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32
|
|||||||
void out_CreateAssert(
|
void out_CreateAssert(
|
||||||
AssertionType type, Expression const &expr, std::string const &message, uint32_t ofs
|
AssertionType type, Expression const &expr, std::string const &message, uint32_t ofs
|
||||||
) {
|
) {
|
||||||
|
if (message.find('\0') != std::string::npos) {
|
||||||
|
fatal("Assertion messages cannot contain '\\0' characters");
|
||||||
|
}
|
||||||
|
|
||||||
Assertion &assertion = assertions.emplace_front();
|
Assertion &assertion = assertions.emplace_front();
|
||||||
|
|
||||||
initPatch(assertion.patch, type, expr, ofs);
|
initPatch(assertion.patch, type, expr, ofs);
|
||||||
|
|||||||
+11
-3
@@ -340,6 +340,7 @@
|
|||||||
%type <Expression> reloc_3bit
|
%type <Expression> reloc_3bit
|
||||||
%type <Expression> reloc_8bit
|
%type <Expression> reloc_8bit
|
||||||
%type <Expression> reloc_16bit
|
%type <Expression> reloc_16bit
|
||||||
|
%type <Expression> reloc_8bit_signed
|
||||||
|
|
||||||
// Constant numbers
|
// Constant numbers
|
||||||
%type <int32_t> iconst
|
%type <int32_t> iconst
|
||||||
@@ -1241,6 +1242,13 @@ reloc_8bit:
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
reloc_8bit_signed:
|
||||||
|
relocexpr {
|
||||||
|
$$ = std::move($1);
|
||||||
|
$$.checkSignedNBit(8);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
reloc_16bit:
|
reloc_16bit:
|
||||||
relocexpr {
|
relocexpr {
|
||||||
$$ = std::move($1);
|
$$ = std::move($1);
|
||||||
@@ -1886,7 +1894,7 @@ sm83_add:
|
|||||||
| SM83_ADD MODE_HL COMMA reg_ss {
|
| SM83_ADD MODE_HL COMMA reg_ss {
|
||||||
sect_ConstByte(0x09 | ($4 << 4));
|
sect_ConstByte(0x09 | ($4 << 4));
|
||||||
}
|
}
|
||||||
| SM83_ADD MODE_SP COMMA reloc_8bit {
|
| SM83_ADD MODE_SP COMMA reloc_8bit_signed {
|
||||||
sect_ConstByte(0xE8);
|
sect_ConstByte(0xE8);
|
||||||
sect_RelByte($4, 1);
|
sect_RelByte($4, 1);
|
||||||
}
|
}
|
||||||
@@ -2434,11 +2442,11 @@ op_a_n:
|
|||||||
op_sp_offset:
|
op_sp_offset:
|
||||||
OP_ADD relocexpr {
|
OP_ADD relocexpr {
|
||||||
$$ = std::move($2);
|
$$ = std::move($2);
|
||||||
$$.checkNBit(8);
|
$$.checkSignedNBit(8);
|
||||||
}
|
}
|
||||||
| OP_SUB relocexpr {
|
| OP_SUB relocexpr {
|
||||||
$$.makeUnaryOp(RPN_NEG, std::move($2));
|
$$.makeUnaryOp(RPN_NEG, std::move($2));
|
||||||
$$.checkNBit(8);
|
$$.checkSignedNBit(8);
|
||||||
}
|
}
|
||||||
| %empty {
|
| %empty {
|
||||||
::error("\"LD HL, SP\" is not a valid instruction; use \"LD HL, SP + 0\"");
|
::error("\"LD HL, SP\" is not a valid instruction; use \"LD HL, SP + 0\"");
|
||||||
|
|||||||
+22
-4
@@ -297,7 +297,6 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
if (src1.isKnown() && src2.isKnown()) {
|
if (src1.isKnown() && src2.isKnown()) {
|
||||||
// If both expressions are known, just compute the value
|
// If both expressions are known, just compute the value
|
||||||
int32_t lval = src1.value(), rval = src2.value();
|
int32_t lval = src1.value(), rval = src2.value();
|
||||||
uint32_t ulval = static_cast<uint32_t>(lval), urval = static_cast<uint32_t>(rval);
|
|
||||||
|
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case RPN_LOGOR:
|
case RPN_LOGOR:
|
||||||
@@ -325,10 +324,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
data = lval != rval;
|
data = lval != rval;
|
||||||
break;
|
break;
|
||||||
case RPN_ADD:
|
case RPN_ADD:
|
||||||
data = static_cast<int32_t>(ulval + urval);
|
data = op_add(lval, rval);
|
||||||
break;
|
break;
|
||||||
case RPN_SUB:
|
case RPN_SUB:
|
||||||
data = static_cast<int32_t>(ulval - urval);
|
data = op_sub(lval, rval);
|
||||||
break;
|
break;
|
||||||
case RPN_XOR:
|
case RPN_XOR:
|
||||||
data = lval ^ rval;
|
data = lval ^ rval;
|
||||||
@@ -370,7 +369,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
data = op_shift_right_unsigned(lval, rval);
|
data = op_shift_right_unsigned(lval, rval);
|
||||||
break;
|
break;
|
||||||
case RPN_MUL:
|
case RPN_MUL:
|
||||||
data = static_cast<int32_t>(ulval * urval);
|
data = op_mul(lval, rval);
|
||||||
break;
|
break;
|
||||||
case RPN_DIV:
|
case RPN_DIV:
|
||||||
if (rval == 0) {
|
if (rval == 0) {
|
||||||
@@ -512,6 +511,25 @@ bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks that an RPN expression's value fits within N bits (must be signed)
|
||||||
|
void Expression::checkSignedNBit(uint8_t n) const {
|
||||||
|
if (isKnown()) {
|
||||||
|
::checkSignedNBit(value(), n, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkSignedNBit(int32_t v, uint8_t n, char const *name) {
|
||||||
|
assume(n != 0); // That doesn't make sense
|
||||||
|
assume(n < CHAR_BIT * sizeof(int) - 1); // Otherwise `1 << n` is UB
|
||||||
|
|
||||||
|
if (v < -(1 << (n - 1)) || v >= 1 << (n - 1)) {
|
||||||
|
warning(WARNING_TRUNCATION_1, "%s must be signed %u-bit", name ? name : "Expression", n);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Expression::encode(std::vector<uint8_t> &buffer) const {
|
void Expression::encode(std::vector<uint8_t> &buffer) const {
|
||||||
assume(buffer.empty());
|
assume(buffer.empty());
|
||||||
|
|
||||||
|
|||||||
+66
-49
@@ -100,7 +100,7 @@ void sect_ForEach(void (*callback)(Section &)) {
|
|||||||
|
|
||||||
void sect_CheckSizes() {
|
void sect_CheckSizes() {
|
||||||
for (Section const § : sections) {
|
for (Section const § : sections) {
|
||||||
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize) {
|
if (uint32_t maxSize = sect.typeInfo().size; sect.size > maxSize) {
|
||||||
error(
|
error(
|
||||||
"Section \"%s\" grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
"Section \"%s\" grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
||||||
")",
|
")",
|
||||||
@@ -214,10 +214,8 @@ static void mergeFragments(Section §, uint32_t org, uint8_t alignment, uint1
|
|||||||
// combination of both.
|
// combination of both.
|
||||||
// The merging is however performed at the *end* of the original section!
|
// The merging is however performed at the *end* of the original section!
|
||||||
if (org != UINT32_MAX) {
|
if (org != UINT32_MAX) {
|
||||||
uint16_t curOrg = org - sect.size;
|
// If both are fixed, they must be compatible
|
||||||
|
if (uint16_t curOrg = org - sect.size; sect.org != UINT32_MAX && sect.org != curOrg) {
|
||||||
// If both are fixed, they must be the same
|
|
||||||
if (sect.org != UINT32_MAX && sect.org != curOrg) {
|
|
||||||
sectError(
|
sectError(
|
||||||
"Section \"%s\" already declared as fixed at incompatible address $%04" PRIx32,
|
"Section \"%s\" already declared as fixed at incompatible address $%04" PRIx32,
|
||||||
sect.name.c_str(),
|
sect.name.c_str(),
|
||||||
@@ -231,6 +229,16 @@ static void mergeFragments(Section §, uint32_t org, uint8_t alignment, uint1
|
|||||||
sectAlignSize,
|
sectAlignSize,
|
||||||
sect.alignOfs
|
sect.alignOfs
|
||||||
);
|
);
|
||||||
|
} else if (org < sect.size) {
|
||||||
|
// Check that `curOrg` did not underflow. Note that it's safe for the above checks to
|
||||||
|
// use an underflowed value, since their reported errors will still be accurate.
|
||||||
|
sectError(
|
||||||
|
"Section \"%s\" already contains %" PRIu32
|
||||||
|
" bytes, higher than this fragment's fixed address $%04" PRIx32,
|
||||||
|
sect.name.c_str(),
|
||||||
|
sect.size,
|
||||||
|
org
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, just override
|
// Otherwise, just override
|
||||||
sect.org = curOrg;
|
sect.org = curOrg;
|
||||||
@@ -274,14 +282,6 @@ static void mergeSections(
|
|||||||
) {
|
) {
|
||||||
sectErrors.clear();
|
sectErrors.clear();
|
||||||
|
|
||||||
if (type != sect.type) {
|
|
||||||
sectError(
|
|
||||||
"Section \"%s\" already exists but with type `%s`",
|
|
||||||
sect.name.c_str(),
|
|
||||||
sectionTypeInfo[sect.type].name.c_str()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sect.modifier != mod) {
|
if (sect.modifier != mod) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section \"%s\" already declared as `SECTION %s`",
|
"Section \"%s\" already declared as `SECTION %s`",
|
||||||
@@ -290,8 +290,29 @@ static void mergeSections(
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
switch (mod) {
|
switch (mod) {
|
||||||
|
case SECTION_NORMAL:
|
||||||
|
// Only union/fragment sections can end up with multiple errors queued in `sectErrors`,
|
||||||
|
// and they cannot encounter this error, so it's okay for this one to skip the queue.
|
||||||
|
// Queueing it in `sectErrors` would require a sentinel value anyway (e.g. an empty
|
||||||
|
// string) to handle the "no trace" callback.
|
||||||
|
assume(sectErrors.empty());
|
||||||
|
fatalNoTrace([§]() {
|
||||||
|
fprintf(stderr, "Section \"%s\" already defined\n", sect.name.c_str());
|
||||||
|
fstk_TraceCurrent();
|
||||||
|
fputs(" and also:\n", stderr);
|
||||||
|
sect.src->printBacktrace(sect.fileLine);
|
||||||
|
});
|
||||||
|
|
||||||
case SECTION_UNION:
|
case SECTION_UNION:
|
||||||
case SECTION_FRAGMENT: {
|
case SECTION_FRAGMENT: {
|
||||||
|
if (type != sect.type) {
|
||||||
|
sectError(
|
||||||
|
"Section \"%s\" already exists but with type `%s`",
|
||||||
|
sect.name.c_str(),
|
||||||
|
sect.typeInfo().name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void (*merge)(Section &, uint32_t, uint8_t, uint16_t) =
|
void (*merge)(Section &, uint32_t, uint8_t, uint16_t) =
|
||||||
mod == SECTION_UNION ? mergeSectUnion : mergeFragments;
|
mod == SECTION_UNION ? mergeSectUnion : mergeFragments;
|
||||||
merge(sect, org, alignment, alignOffset);
|
merge(sect, org, alignment, alignOffset);
|
||||||
@@ -310,20 +331,6 @@ static void mergeSections(
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SECTION_NORMAL:
|
|
||||||
// Only union/fragment sections can end up with multiple errors queued in `sectErrors`,
|
|
||||||
// and they cannot encounter this error, so it's okay for this one to skip the queue.
|
|
||||||
// Queueing it in `sectErrors` would require a sentinel value anyway (e.g. an empty
|
|
||||||
// string) to handle the "no trace" callback.
|
|
||||||
assume(sectErrors.empty());
|
|
||||||
fatalNoTrace([§]() {
|
|
||||||
fprintf(stderr, "Section \"%s\" already defined\n", sect.name.c_str());
|
|
||||||
fstk_TraceCurrent();
|
|
||||||
fputs(" and also:\n", stderr);
|
|
||||||
sect.src->printBacktrace(sect.fileLine);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,8 +377,8 @@ static Section *createSection(
|
|||||||
out_RegisterNode(sect.src);
|
out_RegisterNode(sect.src);
|
||||||
|
|
||||||
// It is only needed to allocate memory for ROM sections.
|
// It is only needed to allocate memory for ROM sections.
|
||||||
if (sectTypeHasData(type)) {
|
if (sectTypeHasData(sect.type)) {
|
||||||
sect.data.resize(sectionTypeInfo[type].size);
|
sect.data.resize(sect.typeInfo().size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return §
|
return §
|
||||||
@@ -396,7 +403,7 @@ static Section *createSectionFragmentLiteral(Section const &parent) {
|
|||||||
|
|
||||||
// Section fragment literals must be ROM sections.
|
// Section fragment literals must be ROM sections.
|
||||||
assume(sectTypeHasData(sect.type));
|
assume(sectTypeHasData(sect.type));
|
||||||
sect.data.resize(sectionTypeInfo[sect.type].size);
|
sect.data.resize(sect.typeInfo().size);
|
||||||
|
|
||||||
return §
|
return §
|
||||||
}
|
}
|
||||||
@@ -417,24 +424,24 @@ static Section *getSection(
|
|||||||
uint32_t alignMask = alignSize - 1;
|
uint32_t alignMask = alignSize - 1;
|
||||||
|
|
||||||
// First, validate parameters, and normalize them if applicable
|
// First, validate parameters, and normalize them if applicable
|
||||||
|
SectionTypeInfo const &typeInfo = sectionTypeInfo[type];
|
||||||
|
|
||||||
if (bank != UINT32_MAX) {
|
if (bank != UINT32_MAX) {
|
||||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
||||||
&& type != SECTTYPE_WRAMX) {
|
&& type != SECTTYPE_WRAMX) {
|
||||||
error("`BANK` only allowed for `ROMX`, `WRAMX`, `SRAM`, or `VRAM` sections");
|
error("`BANK` only allowed for `ROMX`, `WRAMX`, `SRAM`, or `VRAM` sections");
|
||||||
} else if (bank < sectionTypeInfo[type].firstBank
|
} else if (bank < typeInfo.firstBank || bank > typeInfo.lastBank) {
|
||||||
|| bank > sectionTypeInfo[type].lastBank) {
|
|
||||||
error(
|
error(
|
||||||
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")",
|
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")",
|
||||||
sectionTypeInfo[type].name.c_str(),
|
typeInfo.name,
|
||||||
bank,
|
bank,
|
||||||
sectionTypeInfo[type].firstBank,
|
typeInfo.firstBank,
|
||||||
sectionTypeInfo[type].lastBank
|
typeInfo.lastBank
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (sectTypeBanks(type) == 1) {
|
} else if (!typeInfo.isBanked()) {
|
||||||
// If the section type only has a single bank, implicitly force it
|
// If the section type only has a single bank, implicitly force it
|
||||||
bank = sectionTypeInfo[type].firstBank;
|
bank = typeInfo.firstBank;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should be redundant, as the parser guarantees that `AlignmentSpec` will be valid.
|
// This should be redundant, as the parser guarantees that `AlignmentSpec` will be valid.
|
||||||
@@ -450,14 +457,14 @@ static Section *getSection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (org != UINT32_MAX) {
|
if (org != UINT32_MAX) {
|
||||||
if (org < sectionTypeInfo[type].startAddr || org > sectTypeEndAddr(type)) {
|
if (org < typeInfo.startAddr || org > typeInfo.endAddr()) {
|
||||||
error(
|
error(
|
||||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||||
"; $%04" PRIx16 "]",
|
"; $%04" PRIx16 "]",
|
||||||
name.c_str(),
|
name.c_str(),
|
||||||
org,
|
org,
|
||||||
sectionTypeInfo[type].startAddr,
|
typeInfo.startAddr,
|
||||||
sectTypeEndAddr(type)
|
typeInfo.endAddr()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,18 +472,15 @@ static Section *getSection(
|
|||||||
if (alignment != 0) {
|
if (alignment != 0) {
|
||||||
// It doesn't make sense to have both alignment and org set
|
// It doesn't make sense to have both alignment and org set
|
||||||
if (org != UINT32_MAX) {
|
if (org != UINT32_MAX) {
|
||||||
if ((org - alignOffset) & alignMask) {
|
if ((org & alignMask) != alignOffset) {
|
||||||
error("Section \"%s\"'s fixed address does not match its alignment", name.c_str());
|
error("Section \"%s\"'s fixed address does not match its alignment", name.c_str());
|
||||||
}
|
}
|
||||||
alignment = 0; // Ignore it if it's satisfied
|
alignment = 0; // Ignore it if it's satisfied
|
||||||
} else if (sectionTypeInfo[type].startAddr & alignMask) {
|
} else if ((typeInfo.startAddr & alignMask) > alignOffset) {
|
||||||
error(
|
error(
|
||||||
"Section \"%s\"'s alignment cannot be attained in %s",
|
"Section \"%s\"'s alignment cannot be attained in %s", name.c_str(), typeInfo.name
|
||||||
name.c_str(),
|
|
||||||
sectionTypeInfo[type].name.c_str()
|
|
||||||
);
|
);
|
||||||
alignment = 0; // Ignore it if it's unattainable
|
alignment = 0; // Ignore it if it's unattainable
|
||||||
org = 0;
|
|
||||||
} else if (alignment == 16) {
|
} else if (alignment == 16) {
|
||||||
// Treat an alignment of 16 as fixing the address.
|
// Treat an alignment of 16 as fixing the address.
|
||||||
alignment = 0;
|
alignment = 0;
|
||||||
@@ -544,6 +548,10 @@ void sect_NewSection(
|
|||||||
SectionSpec const &attrs,
|
SectionSpec const &attrs,
|
||||||
SectionModifier mod
|
SectionModifier mod
|
||||||
) {
|
) {
|
||||||
|
if (name.find('\0') != std::string::npos) {
|
||||||
|
fatal("Section names cannot contain '\\0' characters");
|
||||||
|
}
|
||||||
|
|
||||||
for (SectionStackEntry &entry : sectionStack) {
|
for (SectionStackEntry &entry : sectionStack) {
|
||||||
if (entry.section && entry.section->name == name) {
|
if (entry.section && entry.section->name == name) {
|
||||||
fatal("Section \"%s\" is already on the stack", name.c_str());
|
fatal("Section \"%s\" is already on the stack", name.c_str());
|
||||||
@@ -706,8 +714,17 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
|||||||
} else if (alignment == 16) {
|
} else if (alignment == 16) {
|
||||||
// Treat an alignment large enough as fixing the address.
|
// Treat an alignment large enough as fixing the address.
|
||||||
// Note that this also ensures that a section's alignment never becomes 16 or greater.
|
// Note that this also ensures that a section's alignment never becomes 16 or greater.
|
||||||
sect->align = 0; // Reset the alignment, since we're fixing the address.
|
if (offset < curOffset) {
|
||||||
sect->org = offset - curOffset;
|
error(
|
||||||
|
"Section already contains %" PRIu32
|
||||||
|
" bytes, higher than this aligned address $%04" PRIx32,
|
||||||
|
curOffset,
|
||||||
|
offset
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sect->align = 0; // Reset the alignment, since we're fixing the address.
|
||||||
|
sect->org = offset - curOffset;
|
||||||
|
}
|
||||||
} else if (alignment > sect->align) {
|
} else if (alignment > sect->align) {
|
||||||
sect->align = alignment;
|
sect->align = alignment;
|
||||||
// We need `(sect->alignOfs + curOffset) & alignMask == offset`
|
// We need `(sect->alignOfs + curOffset) & alignMask == offset`
|
||||||
@@ -1049,7 +1066,7 @@ bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t l
|
|||||||
*fileSize
|
*fileSize
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
} else if (startPos + length > *fileSize) {
|
} else if (length > *fileSize - startPos) {
|
||||||
error(
|
error(
|
||||||
"Specified range in `INCBIN` file \"%s\" is out of bounds (%" PRIu32 " + %" PRIu32
|
"Specified range in `INCBIN` file \"%s\" is out of bounds (%" PRIu32 " + %" PRIu32
|
||||||
" > %" PRIu64 ")",
|
" > %" PRIu64 ")",
|
||||||
|
|||||||
+2
-2
@@ -251,9 +251,9 @@ static void
|
|||||||
error("\"%s\" has more than 65536 banks", name); // LCOV_EXCL_LINE
|
error("\"%s\" has more than 65536 banks", name); // LCOV_EXCL_LINE
|
||||||
};
|
};
|
||||||
static constexpr off_t NB_BANKS_LIMIT = 0x10000;
|
static constexpr off_t NB_BANKS_LIMIT = 0x10000;
|
||||||
static_assert(NB_BANKS_LIMIT * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS");
|
static_assert(NB_BANKS_LIMIT * BANK_SIZE < SSIZE_MAX, "Max input file size too large for OS");
|
||||||
if (input == output) {
|
if (input == output) {
|
||||||
if (fileSize >= NB_BANKS_LIMIT * BANK_SIZE) {
|
if (fileSize > NB_BANKS_LIMIT * BANK_SIZE) {
|
||||||
return errorTooLarge(); // LCOV_EXCL_LINE
|
return errorTooLarge(); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
// Compute number of banks and ROMX len from file size
|
// Compute number of banks and ROMX len from file size
|
||||||
|
|||||||
+1
-1
@@ -240,7 +240,7 @@ static void parseArg(int ch, char *arg) {
|
|||||||
len = maxLen;
|
len = maxLen;
|
||||||
warning(
|
warning(
|
||||||
WARNING_TRUNCATION,
|
WARNING_TRUNCATION,
|
||||||
"Truncating title \"%s\" to %u chars",
|
"Truncating title \"%s\" to %" PRIu8 " chars",
|
||||||
options.title->c_str(),
|
options.title->c_str(),
|
||||||
maxLen
|
maxLen
|
||||||
);
|
);
|
||||||
|
|||||||
+54
-37
@@ -36,13 +36,13 @@ Options options;
|
|||||||
|
|
||||||
// Flags which must be processed after the option parsing finishes
|
// Flags which must be processed after the option parsing finishes
|
||||||
static struct LocalOptions {
|
static struct LocalOptions {
|
||||||
std::optional<std::string> externalPalSpec; // -c
|
std::optional<std::string> palSpec; // -c
|
||||||
bool autoAttrmap; // -A
|
bool autoAttrmap; // -A
|
||||||
bool autoTilemap; // -T
|
bool autoTilemap; // -T
|
||||||
bool autoPalettes; // -P
|
bool autoPalettes; // -P
|
||||||
bool autoPalmap; // -Q
|
bool autoPalmap; // -Q
|
||||||
bool groupOutputs; // -O
|
bool groupOutputs; // -O
|
||||||
bool reverse; // -r
|
bool reverse; // -r
|
||||||
|
|
||||||
bool autoAny() const { return autoAttrmap || autoTilemap || autoPalettes || autoPalmap; }
|
bool autoAny() const { return autoAttrmap || autoTilemap || autoPalettes || autoPalmap; }
|
||||||
} localOptions;
|
} localOptions;
|
||||||
@@ -193,10 +193,10 @@ static void parseArg(int ch, char *arg) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'c':
|
case 'c':
|
||||||
localOptions.externalPalSpec = std::nullopt; // Allow overriding a previous pal spec
|
localOptions.palSpec = std::nullopt; // Allow overriding a previous pal spec
|
||||||
if (arg[0] == '#') {
|
if (arg[0] == '#') {
|
||||||
options.palSpecType = Options::EXPLICIT;
|
options.palSpecType = Options::INLINE;
|
||||||
parseInlinePalSpec(arg);
|
localOptions.palSpec = arg;
|
||||||
} else if (strcasecmp(arg, "embedded") == 0) {
|
} else if (strcasecmp(arg, "embedded") == 0) {
|
||||||
// Use PLTE, error out if missing
|
// Use PLTE, error out if missing
|
||||||
options.palSpecType = Options::EMBEDDED;
|
options.palSpecType = Options::EMBEDDED;
|
||||||
@@ -204,13 +204,13 @@ static void parseArg(int ch, char *arg) {
|
|||||||
options.palSpecType = Options::NO_SPEC;
|
options.palSpecType = Options::NO_SPEC;
|
||||||
} else if (strcasecmp(arg, "dmg") == 0) {
|
} else if (strcasecmp(arg, "dmg") == 0) {
|
||||||
options.palSpecType = Options::DMG;
|
options.palSpecType = Options::DMG;
|
||||||
parseDmgPalSpec(0xE4); // Same darkest-first order as `sortGrayscale`
|
localOptions.palSpec = "e4"; // Same darkest-first order as `sortGrayscale`
|
||||||
} else if (strncasecmp(arg, "dmg=", literal_strlen("dmg=")) == 0) {
|
} else if (strncasecmp(arg, "dmg=", literal_strlen("dmg=")) == 0) {
|
||||||
options.palSpecType = Options::DMG;
|
options.palSpecType = Options::DMG;
|
||||||
parseDmgPalSpec(&arg[literal_strlen("dmg=")]);
|
localOptions.palSpec = &arg[literal_strlen("dmg=")];
|
||||||
} else {
|
} else {
|
||||||
options.palSpecType = Options::EXPLICIT;
|
options.palSpecType = Options::EXTERNAL;
|
||||||
localOptions.externalPalSpec = arg;
|
localOptions.palSpec = arg;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -299,6 +299,10 @@ static void parseArg(int ch, char *arg) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'N':
|
case 'N':
|
||||||
|
// Explicit numbers for either tile bank cannot be greater than 256.
|
||||||
|
// If they were greater than 256, it would permit tile IDs to be truncated in the tilemap.
|
||||||
|
// We do warn that tile IDs may be truncated for the implicit/default unlimited number of
|
||||||
|
// tiles in bank 0.
|
||||||
options.maxNbTiles[0] = readNumber(argPtr, "Number of tiles in bank 0", 256);
|
options.maxNbTiles[0] = readNumber(argPtr, "Number of tiles in bank 0", 256);
|
||||||
if (options.maxNbTiles[0] > 256) {
|
if (options.maxNbTiles[0] > 256) {
|
||||||
error("Bank 0 cannot contain more than 256 tiles");
|
error("Bank 0 cannot contain more than 256 tiles");
|
||||||
@@ -514,28 +518,17 @@ static void verboseOutputConfig() {
|
|||||||
// -s/--palette-size
|
// -s/--palette-size
|
||||||
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
||||||
// -c/--colors
|
// -c/--colors
|
||||||
if (options.palSpecType == Options::NO_SPEC) {
|
switch (options.palSpecType) {
|
||||||
|
case Options::NO_SPEC:
|
||||||
fputs("\tAutomatic palette generation\n", stderr);
|
fputs("\tAutomatic palette generation\n", stderr);
|
||||||
} else {
|
break;
|
||||||
fprintf(stderr, "\t%s palette spec\n", [] {
|
case Options::INLINE:
|
||||||
switch (options.palSpecType) {
|
case Options::EXTERNAL:
|
||||||
case Options::EXPLICIT:
|
fputs("\tExplicit palette spec\n\t[\n", stderr);
|
||||||
return "Explicit";
|
|
||||||
case Options::EMBEDDED:
|
|
||||||
return "Embedded";
|
|
||||||
case Options::DMG:
|
|
||||||
return "DMG";
|
|
||||||
default:
|
|
||||||
return "???";
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
}
|
|
||||||
if (options.palSpecType == Options::EXPLICIT) {
|
|
||||||
fputs("\t[\n", stderr);
|
|
||||||
for (auto const &pal : options.palSpec) {
|
for (auto const &pal : options.palSpec) {
|
||||||
fputs("\t\t", stderr);
|
fputs("\t\t", stderr);
|
||||||
for (auto const &color : pal) {
|
for (auto const &color : pal) {
|
||||||
if (color) {
|
if (color.has_value()) {
|
||||||
fprintf(stderr, "#%06x, ", color->toCSS() >> 8);
|
fprintf(stderr, "#%06x, ", color->toCSS() >> 8);
|
||||||
} else {
|
} else {
|
||||||
fputs("#none, ", stderr);
|
fputs("#none, ", stderr);
|
||||||
@@ -544,6 +537,13 @@ static void verboseOutputConfig() {
|
|||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
}
|
}
|
||||||
fputs("\t]\n", stderr);
|
fputs("\t]\n", stderr);
|
||||||
|
break;
|
||||||
|
case Options::EMBEDDED:
|
||||||
|
fputs("\tEmbedded palette spec from PNG indexed PLTE chunk\n", stderr);
|
||||||
|
break;
|
||||||
|
case Options::DMG:
|
||||||
|
fprintf(stderr, "\tDMG palette spec $%02" PRIx8 "\n", options.palSpecDmg);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
// -L/--slice
|
// -L/--slice
|
||||||
if (options.inputSlice.width || options.inputSlice.height || options.inputSlice.left
|
if (options.inputSlice.width || options.inputSlice.height || options.inputSlice.left
|
||||||
@@ -671,9 +671,27 @@ int main(int argc, char *argv[]) {
|
|||||||
autoOutPath(localOptions.autoPalettes, options.palettes, ".pal");
|
autoOutPath(localOptions.autoPalettes, options.palettes, ".pal");
|
||||||
autoOutPath(localOptions.autoPalmap, options.palmap, ".palmap");
|
autoOutPath(localOptions.autoPalmap, options.palmap, ".palmap");
|
||||||
|
|
||||||
// Execute deferred external pal spec parsing, now that all other params are known
|
// Execute deferred pal spec parsing, now that all other params are known.
|
||||||
if (localOptions.externalPalSpec) {
|
// Do not parse pal specs if `options.nbColorsPerPal` is invalid.
|
||||||
parseExternalPalSpec(localOptions.externalPalSpec->c_str());
|
if (options.nbColorsPerPal > 0 && options.nbColorsPerPal <= 4) {
|
||||||
|
switch (options.palSpecType) {
|
||||||
|
case Options::NO_SPEC:
|
||||||
|
case Options::EMBEDDED:
|
||||||
|
assume(!localOptions.palSpec);
|
||||||
|
break;
|
||||||
|
case Options::INLINE:
|
||||||
|
assume(localOptions.palSpec);
|
||||||
|
parseInlinePalSpec(localOptions.palSpec->c_str());
|
||||||
|
break;
|
||||||
|
case Options::EXTERNAL:
|
||||||
|
assume(localOptions.palSpec);
|
||||||
|
parseExternalPalSpec(localOptions.palSpec->c_str());
|
||||||
|
break;
|
||||||
|
case Options::DMG:
|
||||||
|
assume(localOptions.palSpec);
|
||||||
|
parseDmgPalSpec(localOptions.palSpec->c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseDo(VERB_CONFIG, verboseOutputConfig);
|
verboseDo(VERB_CONFIG, verboseOutputConfig);
|
||||||
@@ -687,8 +705,7 @@ int main(int argc, char *argv[]) {
|
|||||||
} else {
|
} else {
|
||||||
process();
|
process();
|
||||||
}
|
}
|
||||||
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT
|
} else if (!options.palettes.empty() && options.hasExplicitPalSpec() && !localOptions.reverse) {
|
||||||
&& !localOptions.reverse) {
|
|
||||||
processPalettes();
|
processPalettes();
|
||||||
} else {
|
} else {
|
||||||
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
||||||
|
|||||||
+14
-11
@@ -29,6 +29,7 @@
|
|||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
#include "gfx/warning.hpp"
|
#include "gfx/warning.hpp"
|
||||||
|
|
||||||
|
using namespace std::literals;
|
||||||
using namespace std::string_view_literals;
|
using namespace std::string_view_literals;
|
||||||
|
|
||||||
static char const *hexDigits = "0123456789ABCDEFabcdef";
|
static char const *hexDigits = "0123456789ABCDEFabcdef";
|
||||||
@@ -131,8 +132,14 @@ void parseInlinePalSpec(char const * const rawArg) {
|
|||||||
if (n == arg.length()) {
|
if (n == arg.length()) {
|
||||||
break;
|
break;
|
||||||
} else if (arg[n] != ';' && arg[n] != ':') {
|
} else if (arg[n] != ';' && arg[n] != ':') {
|
||||||
if (nbColors == 4) {
|
if (nbColors == options.nbColorsPerPal) {
|
||||||
parseError(n, 1, "Each palette can only contain up to 4 colors");
|
// `parseError` cannot take variadic arguments, since `format_` and
|
||||||
|
// `-Wformat-security` would complain about passing a template parameter pack
|
||||||
|
// to the C-style variadic `error` function, so we format the error message
|
||||||
|
// before passing it to `parseError`.
|
||||||
|
std::string msg = "Each palette can only contain up to "s
|
||||||
|
+ std::to_string(options.nbColorsPerPal) + " colors";
|
||||||
|
parseError(n, 1, msg.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -535,10 +542,10 @@ static void parseGBCFile(char const *filename, std::filebuf &file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options.palSpec.push_back({
|
options.palSpec.push_back({
|
||||||
Rgba::fromCGBColor(toWord(buf[0], buf[1])),
|
Rgba::fromCGBColor(toWord(buf[0], buf[1]), false),
|
||||||
Rgba::fromCGBColor(toWord(buf[2], buf[3])),
|
Rgba::fromCGBColor(toWord(buf[2], buf[3]), false),
|
||||||
Rgba::fromCGBColor(toWord(buf[4], buf[5])),
|
Rgba::fromCGBColor(toWord(buf[4], buf[5]), false),
|
||||||
Rgba::fromCGBColor(toWord(buf[6], buf[7])),
|
Rgba::fromCGBColor(toWord(buf[6], buf[7]), false),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -664,11 +671,7 @@ void parseDmgPalSpec(char const * const rawArg) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseDmgPalSpec(toHex(arg[0], arg[1]));
|
options.palSpecDmg = toHex(arg[0], arg[1]);
|
||||||
}
|
|
||||||
|
|
||||||
void parseDmgPalSpec(uint8_t palSpecDmg) {
|
|
||||||
options.palSpecDmg = palSpecDmg;
|
|
||||||
|
|
||||||
// Map gray shades to their DMG color indexes for fast lookup by `Rgba::grayIndex`
|
// Map gray shades to their DMG color indexes for fast lookup by `Rgba::grayIndex`
|
||||||
for (uint8_t i = 0; i < 4; ++i) {
|
for (uint8_t i = 0; i < 4; ++i) {
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ Png::Png(char const *filename, std::streambuf &file) {
|
|||||||
break;
|
break;
|
||||||
case PNG_COLOR_TYPE_PALETTE:
|
case PNG_COLOR_TYPE_PALETTE:
|
||||||
png_set_palette_to_rgb(png);
|
png_set_palette_to_rgb(png);
|
||||||
|
isIndexed = true; // This enables sorting generated palette colors by the PLTE chunk
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+139
-73
@@ -179,9 +179,14 @@ struct Image {
|
|||||||
std::unordered_set<std::pair<uint32_t, uint32_t>, decltype(hashPair)> fusions;
|
std::unordered_set<std::pair<uint32_t, uint32_t>, decltype(hashPair)> fusions;
|
||||||
|
|
||||||
// Register colors from `png` into `colors`
|
// Register colors from `png` into `colors`
|
||||||
for (uint32_t y = 0; y < png.height; ++y) {
|
uint32_t const pxLeft = options.inputSlice.width ? options.inputSlice.left : 0;
|
||||||
for (uint32_t x = 0; x < png.width; ++x) {
|
uint32_t const pxTop = options.inputSlice.height ? options.inputSlice.top : 0;
|
||||||
if (Rgba const &color = pixel(x, y); color.isTransparent() == color.isOpaque()) {
|
uint32_t const pxRight = options.inputSlice.width ? options.inputSlice.right() : png.width;
|
||||||
|
uint32_t const pxBottom =
|
||||||
|
options.inputSlice.height ? options.inputSlice.bottom() : png.height;
|
||||||
|
for (uint32_t y = pxTop; y < pxBottom; ++y) {
|
||||||
|
for (uint32_t x = pxLeft; x < pxRight; ++x) {
|
||||||
|
if (Rgba const &color = pixel(x, y); color.isAmbiguous()) {
|
||||||
// Report ambiguously transparent or opaque colors
|
// Report ambiguously transparent or opaque colors
|
||||||
if (uint32_t css = color.toCSS(); ambiguous.find(css) == ambiguous.end()) {
|
if (uint32_t css = color.toCSS(); ambiguous.find(css) == ambiguous.end()) {
|
||||||
error(
|
error(
|
||||||
@@ -213,6 +218,13 @@ struct Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (size_t nbAmbiguousColors = ambiguous.size(); nbAmbiguousColors > 0) {
|
||||||
|
fatal(
|
||||||
|
"Image contains %zu ambiguous color%s (neither transparent nor opaque)",
|
||||||
|
nbAmbiguousColors,
|
||||||
|
nbAmbiguousColors == 1 ? "" : "s"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TilesVisitor {
|
class TilesVisitor {
|
||||||
@@ -365,8 +377,9 @@ static std::pair<std::vector<size_t>, std::vector<Palette>>
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
std::vector<Palette> palettes(nbPalettes);
|
std::vector<Palette> palettes(nbPalettes);
|
||||||
|
|
||||||
// If the image contains at least one transparent pixel, force transparency in the first slot of
|
// If the image contains at least one transparent pixel, force transparency in the first slot of
|
||||||
// all palettes
|
// all palettes.
|
||||||
if (options.hasTransparentPixels) {
|
if (options.hasTransparentPixels) {
|
||||||
for (Palette &pal : palettes) {
|
for (Palette &pal : palettes) {
|
||||||
pal.colors[0] = Rgba::transparent;
|
pal.colors[0] = Rgba::transparent;
|
||||||
@@ -384,7 +397,14 @@ static std::pair<std::vector<size_t>, std::vector<Palette>>
|
|||||||
// "Sort" colors in the generated palettes, see the man page for the flowchart
|
// "Sort" colors in the generated palettes, see the man page for the flowchart
|
||||||
if (options.palSpecType == Options::DMG) {
|
if (options.palSpecType == Options::DMG) {
|
||||||
sortGrayscale(palettes, image.colors.raw());
|
sortGrayscale(palettes, image.colors.raw());
|
||||||
} else if (!image.png.palette.empty()) {
|
} else if (image.png.isIndexed) {
|
||||||
|
// A PNG image using PNG_COLOR_TYPE_RGB (2) or PNG_COLOR_TYPE_RGBA (6) can still
|
||||||
|
// contain a PLTE chunk. From the PNG spec: "If present, it provides a suggested set of
|
||||||
|
// from 1 to 256 colors to which the truecolor image can be quantized if the viewer
|
||||||
|
// cannot display truecolor directly." We only sort palette colors by the PLTE chunk's
|
||||||
|
// color order if the image uses PNG_COLOR_TYPE_PALETTE (3), since that guarantees every
|
||||||
|
// color used will also be in the embedded palette.
|
||||||
|
assume(!image.png.palette.empty());
|
||||||
warning(
|
warning(
|
||||||
WARNING_EMBEDDED,
|
WARNING_EMBEDDED,
|
||||||
"Sorting palette colors by PNG's embedded PLTE chunk without '-c/--colors embedded'"
|
"Sorting palette colors by PNG's embedded PLTE chunk without '-c/--colors embedded'"
|
||||||
@@ -400,14 +420,43 @@ static std::pair<std::vector<size_t>, std::vector<Palette>>
|
|||||||
|
|
||||||
static std::pair<std::vector<size_t>, std::vector<Palette>>
|
static std::pair<std::vector<size_t>, std::vector<Palette>>
|
||||||
makePalsAsSpecified(std::vector<ColorSet> const &colorSets) {
|
makePalsAsSpecified(std::vector<ColorSet> const &colorSets) {
|
||||||
// Convert the palette spec to actual palettes
|
|
||||||
std::vector<Palette> palettes(options.palSpec.size());
|
std::vector<Palette> palettes(options.palSpec.size());
|
||||||
|
|
||||||
|
// If the image contains at least one transparent pixel, force transparency in the first slot of
|
||||||
|
// all palettes
|
||||||
|
if (options.hasTransparentPixels) {
|
||||||
|
for (Palette &pal : palettes) {
|
||||||
|
pal.colors[0] = Rgba::transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the palette spec to actual palettes
|
||||||
for (auto [spec, pal] : zip(options.palSpec, palettes)) {
|
for (auto [spec, pal] : zip(options.palSpec, palettes)) {
|
||||||
|
bool skipFirst = false;
|
||||||
|
// If the image contains any transparent pixels, color #0 of all palettes is transparent.
|
||||||
|
// Thus, all explicit palette specs should leave color #0 as "#none" or transparent.
|
||||||
|
// If they specify an opaque color #0, we have legacy behavior of implicitly inserting
|
||||||
|
// a transparent color #0, and expecting the spec to only cover the subsequent colors.
|
||||||
|
if (options.hasTransparentPixels && spec.front().has_value() && spec.front()->isOpaque()) {
|
||||||
|
skipFirst = true;
|
||||||
|
}
|
||||||
for (size_t i = 0; i < options.nbColorsPerPal; ++i) {
|
for (size_t i = 0; i < options.nbColorsPerPal; ++i) {
|
||||||
// If the spec has a gap, there's no need to copy anything.
|
// If the spec has a gap, there's no need to copy anything.
|
||||||
if (spec[i].has_value() && spec[i]->isOpaque()) {
|
if (!spec[i].has_value() || !spec[i]->isOpaque()) {
|
||||||
pal[i] = spec[i]->cgbColor();
|
continue;
|
||||||
}
|
}
|
||||||
|
// If we're skipping color #0 as implicitly transparent, a full spec
|
||||||
|
// plus the implicit transparent color will be too large for a palette.
|
||||||
|
if (i + skipFirst >= options.nbColorsPerPal) {
|
||||||
|
error(
|
||||||
|
"Each palette spec can only contain up to %" PRIu8
|
||||||
|
" color%s plus the implict transparent color",
|
||||||
|
options.nbColorsPerPal - 1,
|
||||||
|
options.nbColorsPerPal - 1 == 1 ? "" : "s"
|
||||||
|
);
|
||||||
|
giveUp();
|
||||||
|
}
|
||||||
|
pal[i + skipFirst] = spec[i]->cgbColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,14 +507,14 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
|||||||
});
|
});
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
if (palettes.size() > options.nbPalettes) {
|
if (size_t nbPals = palettes.size(); nbPals > options.nbPalettes) {
|
||||||
// If the palette generation is wrong, other (dependee) operations are likely to be
|
// If the palette generation is wrong, other (dependee) operations are likely to be
|
||||||
// nonsensical, so fatal-error outright
|
// nonsensical, so fatal-error outright
|
||||||
fatal(
|
fatal("Generated %zu palettes, over the maximum of %" PRIu16, nbPals, options.nbPalettes);
|
||||||
"Generated %zu palettes, over the maximum of %" PRIu16,
|
} else if (nbPals > 8 && !options.attrmap.empty() && options.palmap.empty()) {
|
||||||
palettes.size(),
|
// With `-n/--nb-palettes` greater than 8, palette IDs may be truncated in the attrmap
|
||||||
options.nbPalettes
|
// (though not in the palmap), so warn about that.
|
||||||
);
|
warnx("Generated %zu palettes, of which only 8 are representable in the attrmap", nbPals);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.palettes.empty()) {
|
if (!options.palettes.empty()) {
|
||||||
@@ -631,17 +680,15 @@ static void outputUnoptimizedTileData(
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t widthTiles = options.inputSlice.width ? options.inputSlice.width : image.png.width / 8;
|
size_t nbTiles = std::count_if(RANGE(attrmap), [](AttrmapEntry const &attr) {
|
||||||
uint64_t heightTiles =
|
return !attr.isBackgroundTile();
|
||||||
options.inputSlice.height ? options.inputSlice.height : image.png.height / 8;
|
});
|
||||||
uint64_t nbTiles = widthTiles * heightTiles;
|
size_t nbKeptTiles = nbTiles > options.trim ? nbTiles - options.trim : 0;
|
||||||
uint64_t nbKeptTiles = nbTiles > options.trim ? nbTiles - options.trim : 0;
|
size_t tileIdx = 0;
|
||||||
uint64_t tileIdx = 0;
|
|
||||||
|
|
||||||
for (auto const &[tile, attr] : zip(image.visitAsTiles(), attrmap)) {
|
for (auto const &[tile, attr] : zip(image.visitAsTiles(), attrmap)) {
|
||||||
// Do not emit fully-background tiles.
|
// Do not emit fully-background tiles.
|
||||||
if (attr.isBackgroundTile()) {
|
if (attr.isBackgroundTile()) {
|
||||||
++tileIdx;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,38 +738,49 @@ static void outputUnoptimizedMaps(
|
|||||||
autoOpenPath(options.attrmap, attrmapOutput);
|
autoOpenPath(options.attrmap, attrmapOutput);
|
||||||
autoOpenPath(options.palmap, palmapOutput);
|
autoOpenPath(options.palmap, palmapOutput);
|
||||||
|
|
||||||
|
auto const emit = [](std::optional<File> &output, uint8_t byte) {
|
||||||
|
if (output.has_value()) {
|
||||||
|
output.value()->sputc(byte);
|
||||||
|
}
|
||||||
|
};
|
||||||
uint16_t tileIdx = 0;
|
uint16_t tileIdx = 0;
|
||||||
uint8_t bank = 0;
|
uint8_t bank = 0;
|
||||||
for (AttrmapEntry const &attr : attrmap) {
|
for (AttrmapEntry const &attr : attrmap) {
|
||||||
// The update-increment logic at the end of this loop may increment `bank` from 1 to 2,
|
// A non-zero base ID may make this addition overflow, wrapping around the available
|
||||||
// if both banks 0 and 1 are full, but by then all the `attrmap` entries should have been
|
// palette IDs. Since the operands are unsigned, this won't cause undefined behavior.
|
||||||
// processed, since there cannot be more tiles than could fit in both banks.
|
// With `-n/--nb-palettes` greater than 8, palette IDs may be truncated in the attrmap
|
||||||
assume(bank < 2);
|
// (though not in the palmap), which was already warned about.
|
||||||
|
|
||||||
// The unsigned overflow for `tileID` and `palID` is intentional, since
|
|
||||||
// nonzero base IDs may overflow beyond 255 and continue with IDs from 0.
|
|
||||||
if (tilemapOutput.has_value()) {
|
|
||||||
uint8_t tileID = (attr.isBackgroundTile() ? 0 : tileIdx) + options.baseTileIDs[bank];
|
|
||||||
(*tilemapOutput)->sputc(tileID);
|
|
||||||
}
|
|
||||||
uint8_t palID = attr.getPalID(mappings) + options.basePalID;
|
uint8_t palID = attr.getPalID(mappings) + options.basePalID;
|
||||||
if (attrmapOutput.has_value()) {
|
|
||||||
(*attrmapOutput)->sputc((palID & 0b111) | bank << 3); // The other flags are all 0
|
|
||||||
}
|
|
||||||
if (palmapOutput.has_value()) {
|
|
||||||
(*palmapOutput)->sputc(palID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Background tiles were not emitted in the tile data, so their ID and bank do not update.
|
|
||||||
if (attr.isBackgroundTile()) {
|
if (attr.isBackgroundTile()) {
|
||||||
continue;
|
// The tile bank may be 2 here, which is fine since background tiles are emitted as
|
||||||
}
|
// if they used the base tile ID and bank 0.
|
||||||
|
assume(bank <= 2);
|
||||||
|
|
||||||
if (tileIdx + 1 < options.maxNbTiles[bank]) {
|
emit(tilemapOutput, options.baseTileIDs[0]);
|
||||||
++tileIdx;
|
emit(attrmapOutput, palID & 0b111); // The other flags are all zeros.
|
||||||
|
emit(palmapOutput, palID);
|
||||||
|
// Since background tiles are not in tile data, they do not increment the tile index.
|
||||||
} else {
|
} else {
|
||||||
++bank;
|
// The only valid tile banks are 0 and 1.
|
||||||
tileIdx = 0;
|
assume(bank < 2);
|
||||||
|
|
||||||
|
// A non-zero base ID may make this addition overflow, wrapping around the available
|
||||||
|
// tile IDs. Since the operands are unsigned, this won't cause undefined behavior.
|
||||||
|
// With `-N/--nb-tiles` unlimited (by default) for bank 0, tile IDs may be truncated in
|
||||||
|
// the tilemap, which was already warned about.
|
||||||
|
uint8_t tileID = tileIdx + options.baseTileIDs[bank];
|
||||||
|
emit(tilemapOutput, tileID);
|
||||||
|
emit(attrmapOutput, (palID & 0b111) | bank << 3); // The other flags are all zeros.
|
||||||
|
emit(palmapOutput, palID);
|
||||||
|
|
||||||
|
++tileIdx;
|
||||||
|
// The `bank` may increment from 1 to 2, if banks 0 and 1 are both full. By then all
|
||||||
|
// the tiles should have been emitted, since there cannot be more tiles than could fit
|
||||||
|
// in both banks, but there may still be background tiles to skip.
|
||||||
|
if (tileIdx >= options.maxNbTiles[bank]) {
|
||||||
|
tileIdx = 0;
|
||||||
|
++bank;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -816,9 +874,9 @@ static UniqueTiles dedupTiles(
|
|||||||
attr.xFlip = false;
|
attr.xFlip = false;
|
||||||
attr.yFlip = false;
|
attr.yFlip = false;
|
||||||
attr.bank = 0;
|
attr.bank = 0;
|
||||||
attr.tileID = 0;
|
attr.tileID = options.baseTileIDs[attr.bank];
|
||||||
} else {
|
} else {
|
||||||
auto [tileID, matchType] = tiles.addTile({tile, palettes[attr.getPalID(mappings)]});
|
auto [tileIdx, matchType] = tiles.addTile({tile, palettes[attr.getPalID(mappings)]});
|
||||||
|
|
||||||
if (inputWithoutOutput && matchType == TileData::NOPE) {
|
if (inputWithoutOutput && matchType == TileData::NOPE) {
|
||||||
error(
|
error(
|
||||||
@@ -831,8 +889,8 @@ static UniqueTiles dedupTiles(
|
|||||||
|
|
||||||
attr.xFlip = matchType == TileData::HFLIP || matchType == TileData::VHFLIP;
|
attr.xFlip = matchType == TileData::HFLIP || matchType == TileData::VHFLIP;
|
||||||
attr.yFlip = matchType == TileData::VFLIP || matchType == TileData::VHFLIP;
|
attr.yFlip = matchType == TileData::VFLIP || matchType == TileData::VHFLIP;
|
||||||
attr.bank = tileID >= options.maxNbTiles[0];
|
attr.bank = tileIdx >= options.maxNbTiles[0];
|
||||||
attr.tileID = (attr.bank ? tileID - options.maxNbTiles[0] : tileID)
|
attr.tileID = (attr.bank ? tileIdx - options.maxNbTiles[0] : tileIdx)
|
||||||
+ options.baseTileIDs[attr.bank];
|
+ options.baseTileIDs[attr.bank];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -849,9 +907,9 @@ static void outputTileData(UniqueTiles const &tiles) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t nbTiles = tiles.size();
|
size_t nbTiles = tiles.size();
|
||||||
uint64_t nbKeptTiles = nbTiles > options.trim ? nbTiles - options.trim : 0;
|
size_t nbKeptTiles = nbTiles > options.trim ? nbTiles - options.trim : 0;
|
||||||
uint64_t tileIdx = 0;
|
size_t tileIdx = 0;
|
||||||
|
|
||||||
for (TileData const *tile : tiles) {
|
for (TileData const *tile : tiles) {
|
||||||
assume(tile->tileID == tileIdx);
|
assume(tile->tileID == tileIdx);
|
||||||
@@ -889,6 +947,8 @@ static void outputTilemap(std::vector<AttrmapEntry> const &attrmap) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With `-N/--nb-tiles` unlimited (by default) for bank 0, tile IDs may be truncated in the
|
||||||
|
// tilemap, which was already warned about.
|
||||||
for (AttrmapEntry const &entry : attrmap) {
|
for (AttrmapEntry const &entry : attrmap) {
|
||||||
output->sputc(entry.tileID); // The tile ID has already been converted
|
output->sputc(entry.tileID); // The tile ID has already been converted
|
||||||
}
|
}
|
||||||
@@ -908,6 +968,8 @@ static void
|
|||||||
attr |= entry.bank << 3;
|
attr |= entry.bank << 3;
|
||||||
// The unsigned underflow for the palette ID is intentional, since a
|
// The unsigned underflow for the palette ID is intentional, since a
|
||||||
// nonzero base palette ID may overflow and continue with IDs from 0.
|
// nonzero base palette ID may overflow and continue with IDs from 0.
|
||||||
|
// With `-n/--nb-palettes` greater than 8, palette IDs may be truncated in the attrmap
|
||||||
|
// (though not in the palmap), which was already warned about.
|
||||||
attr |= (entry.getPalID(mappings) + options.basePalID) & 0b111;
|
attr |= (entry.getPalID(mappings) + options.basePalID) & 0b111;
|
||||||
output->sputc(attr);
|
output->sputc(attr);
|
||||||
}
|
}
|
||||||
@@ -990,12 +1052,14 @@ void process() {
|
|||||||
for (auto tile : image.visitAsTiles()) {
|
for (auto tile : image.visitAsTiles()) {
|
||||||
AttrmapEntry &attrs = attrmap.emplace_back();
|
AttrmapEntry &attrs = attrmap.emplace_back();
|
||||||
|
|
||||||
// Count the unique non-transparent colors for packing
|
// Count the unique opaque colors for packing
|
||||||
std::unordered_set<uint16_t> tileColors;
|
std::unordered_set<uint16_t> tileColors;
|
||||||
for (uint32_t y = 0; y < 8; ++y) {
|
for (uint32_t y = 0; y < 8; ++y) {
|
||||||
for (uint32_t x = 0; x < 8; ++x) {
|
for (uint32_t x = 0; x < 8; ++x) {
|
||||||
if (Rgba color = tile.pixel(x, y);
|
Rgba color = tile.pixel(x, y);
|
||||||
color.isOpaque() || !options.hasTransparentPixels) {
|
// Ambiguous colors should not be in `tileColors`
|
||||||
|
assume(color.isOpaque() != color.isTransparent());
|
||||||
|
if (color.isOpaque() || !options.hasTransparentPixels) {
|
||||||
tileColors.insert(color.cgbColor());
|
tileColors.insert(color.cgbColor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1140,20 +1204,31 @@ continue_visiting_tiles:;
|
|||||||
: makePalsAsSpecified(colorSets);
|
: makePalsAsSpecified(colorSets);
|
||||||
outputPalettes(palettes);
|
outputPalettes(palettes);
|
||||||
|
|
||||||
// If deduplication is not happening, we just need to output the tile data and/or maps as-is
|
auto checkTileCountLimit = [](size_t nbTiles) {
|
||||||
if (!options.allowDedup) {
|
if (nbTiles > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
||||||
// Check the tile count
|
|
||||||
if (size_t nbTiles = std::count_if(
|
|
||||||
RANGE(attrmap), [](AttrmapEntry const &attr) { return !attr.isBackgroundTile(); }
|
|
||||||
);
|
|
||||||
nbTiles > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
|
||||||
fatal(
|
fatal(
|
||||||
"Image contains %zu tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
|
"Image contains %zu tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
|
||||||
nbTiles,
|
nbTiles,
|
||||||
options.maxNbTiles[0],
|
options.maxNbTiles[0],
|
||||||
options.maxNbTiles[1]
|
options.maxNbTiles[1]
|
||||||
);
|
);
|
||||||
|
} else if (((nbTiles > 256 && options.maxNbTiles[0] > 256)
|
||||||
|
|| (nbTiles > options.maxNbTiles[0] + 256u && options.maxNbTiles[1] > 256))
|
||||||
|
&& !options.tilemap.empty()) {
|
||||||
|
// With `-N/--nb-tiles` unlimited (by default) for bank 0, tile IDs may be truncated in
|
||||||
|
// the tilemap, so warn about that.
|
||||||
|
warnx(
|
||||||
|
"Image contains %zu tiles, of which only 256 are representable in the tilemap",
|
||||||
|
nbTiles
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If deduplication is not happening, we just need to output the tile data and/or maps as-is
|
||||||
|
if (!options.allowDedup) {
|
||||||
|
checkTileCountLimit(std::count_if(RANGE(attrmap), [](AttrmapEntry const &attr) {
|
||||||
|
return !attr.isBackgroundTile();
|
||||||
|
}));
|
||||||
|
|
||||||
// I currently cannot figure out useful semantics for this combination of flags.
|
// I currently cannot figure out useful semantics for this combination of flags.
|
||||||
if (!options.inputTileset.empty()) {
|
if (!options.inputTileset.empty()) {
|
||||||
@@ -1176,16 +1251,7 @@ continue_visiting_tiles:;
|
|||||||
verbosePrint(VERB_NOTICE, "Deduplicating tiles...\n");
|
verbosePrint(VERB_NOTICE, "Deduplicating tiles...\n");
|
||||||
UniqueTiles tiles = dedupTiles(image, attrmap, palettes, mappings);
|
UniqueTiles tiles = dedupTiles(image, attrmap, palettes, mappings);
|
||||||
|
|
||||||
// Check the tile count
|
checkTileCountLimit(tiles.size());
|
||||||
if (size_t nbTiles = tiles.size();
|
|
||||||
nbTiles > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
|
||||||
fatal(
|
|
||||||
"Image contains %zu tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
|
|
||||||
nbTiles,
|
|
||||||
options.maxNbTiles[0],
|
|
||||||
options.maxNbTiles[1]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.output.empty()) {
|
if (!options.output.empty()) {
|
||||||
verbosePrint(VERB_NOTICE, "Generating optimized tile data...\n");
|
verbosePrint(VERB_NOTICE, "Generating optimized tile data...\n");
|
||||||
|
|||||||
+63
-36
@@ -119,18 +119,14 @@ void reverse() {
|
|||||||
warnx("Tile deduplication is enabled, but no tilemap is provided");
|
warnx("Tile deduplication is enabled, but no tilemap is provided");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.useColorCurve) {
|
|
||||||
warnx("The color curve is not yet supported in reverse mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.inputSlice.left != 0 || options.inputSlice.top != 0
|
if (options.inputSlice.left != 0 || options.inputSlice.top != 0
|
||||||
|| options.inputSlice.height != 0) {
|
|| options.inputSlice.height != 0) {
|
||||||
warnx("\"Sliced-off\" pixels are ignored in reverse mode");
|
warnx("\"Sliced-off\" pixels are ignored in reverse mode");
|
||||||
}
|
}
|
||||||
if (options.inputSlice.width != 0 && options.inputSlice.width != options.reversedWidth * 8) {
|
if (options.inputSlice.width != 0 && options.inputSlice.width != options.reversedWidth) {
|
||||||
warnx(
|
warnx(
|
||||||
"Specified input slice width (%" PRIu16
|
"Specified input slice width (%" PRIu16
|
||||||
") does not match provided reversing width (%" PRIu16 " * 8)",
|
") does not match provided reversing width (%" PRIu16 ")",
|
||||||
options.inputSlice.width,
|
options.inputSlice.width,
|
||||||
options.reversedWidth
|
options.reversedWidth
|
||||||
);
|
);
|
||||||
@@ -216,26 +212,28 @@ void reverse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
palettes.clear();
|
palettes.clear();
|
||||||
std::array<uint8_t, sizeof(uint16_t) * 4> buf; // 4 colors
|
std::array<uint8_t, sizeof(uint16_t) * 4> buf; // max 4 colors
|
||||||
|
size_t const palSize = sizeof(uint16_t) * options.nbColorsPerPal;
|
||||||
|
assume(buf.size() >= palSize);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (size_t nbRead = file->sgetn(reinterpret_cast<char *>(buf.data()), buf.size());
|
if (size_t nbRead = file->sgetn(reinterpret_cast<char *>(buf.data()), palSize);
|
||||||
nbRead == 0) {
|
nbRead == 0) {
|
||||||
break;
|
break;
|
||||||
} else if (nbRead != buf.size()) {
|
} else if (nbRead != palSize) {
|
||||||
fatal(
|
fatal(
|
||||||
"Palette data size (%zu) is not a multiple of %zu bytes\n",
|
"Palette data size (%zu) is not a multiple of %zu bytes",
|
||||||
palettes.size() * buf.size() + nbRead,
|
palettes.size() * palSize + nbRead,
|
||||||
buf.size()
|
palSize
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Expand the colors
|
// Expand the little-endian RGB555 colors to RGB888
|
||||||
auto &palette = palettes.emplace_back();
|
auto &palette = palettes.emplace_back();
|
||||||
std::generate(
|
std::generate(
|
||||||
palette.begin(),
|
palette.begin(),
|
||||||
palette.begin() + options.nbColorsPerPal,
|
palette.begin() + options.nbColorsPerPal,
|
||||||
[&buf, i = 0]() mutable {
|
[&buf, i = 0]() mutable {
|
||||||
i += 2;
|
i += 2;
|
||||||
return Rgba::fromCGBColor(buf[i - 2] | buf[i - 1] << 8); // little-endian
|
return Rgba::fromCGBColor(buf[i - 2] | buf[i - 1] << 8, options.useColorCurve);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -248,8 +246,21 @@ void reverse() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.palSpecType == Options::EXPLICIT && palettes != options.palSpec) {
|
if (options.hasExplicitPalSpec() && palettes != options.palSpec) {
|
||||||
warnx("Colors in the palette file do not match those specified with '-c'");
|
// The explicit `-c` pal spec does not match the input `-p` palette file.
|
||||||
|
// Check whether their 8-to-5-bit-reduced GB colors nevertheless match.
|
||||||
|
std::vector<std::array<std::optional<Rgba>, 4>> palSpecQuantized(options.palSpec);
|
||||||
|
for (auto &pal : palSpecQuantized) {
|
||||||
|
for (auto &color : pal) {
|
||||||
|
if (color.has_value()) {
|
||||||
|
color = Rgba::fromCGBColor(color->cgbColor(), options.useColorCurve);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
warnx(
|
||||||
|
"Colors %s the palette file do not match those specified with '-c'",
|
||||||
|
palettes == palSpecQuantized ? "reversed from" : "in"
|
||||||
|
);
|
||||||
// This spacing aligns "...versus with `-c`" above the column of `-c` palettes
|
// This spacing aligns "...versus with `-c`" above the column of `-c` palettes
|
||||||
fputs("Colors specified in the palette file: ...versus with '-c':\n", stderr);
|
fputs("Colors specified in the palette file: ...versus with '-c':\n", stderr);
|
||||||
for (size_t i = 0; i < palettes.size() || i < options.palSpec.size(); ++i) {
|
for (size_t i = 0; i < palettes.size() || i < options.palSpec.size(); ++i) {
|
||||||
@@ -272,12 +283,12 @@ void reverse() {
|
|||||||
} else if (options.palSpecType == Options::EMBEDDED) {
|
} else if (options.palSpecType == Options::EMBEDDED) {
|
||||||
warnx("An embedded palette was requested, but no palette file was specified; ignoring "
|
warnx("An embedded palette was requested, but no palette file was specified; ignoring "
|
||||||
"request");
|
"request");
|
||||||
} else if (options.palSpecType == Options::EXPLICIT) {
|
} else if (options.hasExplicitPalSpec()) {
|
||||||
palettes = std::move(options.palSpec); // We won't be using it again.
|
palettes = std::move(options.palSpec); // We won't be using it again.
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::vector<uint8_t>> attrmap;
|
std::optional<std::vector<uint8_t>> attrmap;
|
||||||
uint16_t nbTilesInBank[2] = {0, 0}; // Only used if there is an attrmap.
|
uint16_t nbTilesMappedInBank[2] = {0, 0}; // Only used if there is an attrmap.
|
||||||
if (!options.attrmap.empty()) {
|
if (!options.attrmap.empty()) {
|
||||||
attrmap = readInto(options.attrmap);
|
attrmap = readInto(options.attrmap);
|
||||||
if (attrmap->size() != mapSize) {
|
if (attrmap->size() != mapSize) {
|
||||||
@@ -327,8 +338,8 @@ void reverse() {
|
|||||||
// The unsigned underflow for `tileOfs` is intentional, since a nonzero
|
// The unsigned underflow for `tileOfs` is intentional, since a nonzero
|
||||||
// base tile ID may overflow and continue with IDs from 0.
|
// base tile ID may overflow and continue with IDs from 0.
|
||||||
if (uint8_t tileOfs = (*tilemap)[index] - options.baseTileIDs[bank];
|
if (uint8_t tileOfs = (*tilemap)[index] - options.baseTileIDs[bank];
|
||||||
tileOfs >= nbTilesInBank[bank]) {
|
tileOfs >= nbTilesMappedInBank[bank]) {
|
||||||
nbTilesInBank[bank] = tileOfs + 1;
|
nbTilesMappedInBank[bank] = tileOfs + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,29 +347,43 @@ void reverse() {
|
|||||||
verbosePrint(
|
verbosePrint(
|
||||||
VERB_INFO,
|
VERB_INFO,
|
||||||
"Number of tiles in bank {0: %" PRIu16 ", 1: %" PRIu16 "}\n",
|
"Number of tiles in bank {0: %" PRIu16 ", 1: %" PRIu16 "}\n",
|
||||||
nbTilesInBank[0],
|
nbTilesMappedInBank[0],
|
||||||
nbTilesInBank[1]
|
nbTilesMappedInBank[1]
|
||||||
);
|
);
|
||||||
|
|
||||||
for (int bank = 0; bank < 2; ++bank) {
|
for (int bank = 0; bank < 2; ++bank) {
|
||||||
if (nbTilesInBank[bank] > options.maxNbTiles[bank]) {
|
if (nbTilesMappedInBank[bank] > options.maxNbTiles[bank]) {
|
||||||
error(
|
error(
|
||||||
"Bank %d contains %" PRIu16 " tiles, but the specified limit is %" PRIu16,
|
"Bank %d contains %" PRIu16 " tiles, but the specified limit is %" PRIu16,
|
||||||
bank,
|
bank,
|
||||||
nbTilesInBank[bank],
|
nbTilesMappedInBank[bank],
|
||||||
options.maxNbTiles[bank]
|
options.maxNbTiles[bank]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nbTilesInBank[0] + nbTilesInBank[1] > nbTiles) {
|
if (uint16_t const maxTotalNbTiles =
|
||||||
fatal(
|
nbTilesMappedInBank[1] > 0
|
||||||
"The tilemap references %" PRIu16 " tiles in bank 0 and %" PRIu16
|
? std::max<uint16_t>(
|
||||||
" in bank 1, but only %zu have been read in total",
|
nbTilesMappedInBank[0], options.maxNbTiles[0] + nbTilesMappedInBank[1]
|
||||||
nbTilesInBank[0],
|
)
|
||||||
nbTilesInBank[1],
|
: nbTilesMappedInBank[0];
|
||||||
nbTiles
|
maxTotalNbTiles > nbTiles + options.trim) {
|
||||||
);
|
std::string message =
|
||||||
|
"The tilemap references " + std::to_string(nbTilesMappedInBank[0]) + " tiles";
|
||||||
|
if (nbTilesMappedInBank[1] > 0) {
|
||||||
|
if (nbTilesMappedInBank[0] != options.maxNbTiles[0]) {
|
||||||
|
message += " out of a maximum " + std::to_string(options.maxNbTiles[0]);
|
||||||
|
}
|
||||||
|
message += " in bank 0, and " + std::to_string(nbTilesMappedInBank[1])
|
||||||
|
+ " in bank 1 (total: " + std::to_string(maxTotalNbTiles) + ")";
|
||||||
|
}
|
||||||
|
message += ", but only " + std::to_string(nbTiles) + " have been read";
|
||||||
|
if (options.trim > 0) {
|
||||||
|
message += " plus " + std::to_string(options.trim)
|
||||||
|
+ " trimmed (total: " + std::to_string(nbTiles + options.trim) + ")";
|
||||||
|
}
|
||||||
|
fatal("%s", message.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
requireZeroErrors();
|
requireZeroErrors();
|
||||||
@@ -388,7 +413,9 @@ void reverse() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
size_t const limit = std::min<size_t>(nbTiles, options.maxNbTiles[0]);
|
// Tiles trimmed with `-x` were never written to the tile data file, but are
|
||||||
|
// still referenced by the tilemap.
|
||||||
|
size_t const limit = std::min<size_t>(nbTiles + options.trim, options.maxNbTiles[0]);
|
||||||
for (size_t index = 0; index < mapSize; ++index) {
|
for (size_t index = 0; index < mapSize; ++index) {
|
||||||
size_t tx = index % width, ty = index / width;
|
size_t tx = index % width, ty = index / width;
|
||||||
uint8_t tileID = (*tilemap)[index];
|
uint8_t tileID = (*tilemap)[index];
|
||||||
@@ -548,7 +575,7 @@ void reverse() {
|
|||||||
// Get the tile ID at this location
|
// Get the tile ID at this location
|
||||||
size_t tileOfs =
|
size_t tileOfs =
|
||||||
tilemap ? static_cast<uint8_t>((*tilemap)[index] - options.baseTileIDs[bank])
|
tilemap ? static_cast<uint8_t>((*tilemap)[index] - options.baseTileIDs[bank])
|
||||||
+ (bank ? nbTilesInBank[0] : 0)
|
+ (bank ? options.maxNbTiles[0] : 0)
|
||||||
: index;
|
: index;
|
||||||
// This should have been enforced by the earlier checking.
|
// This should have been enforced by the earlier checking.
|
||||||
assume(tileOfs < nbTiles + options.trim);
|
assume(tileOfs < nbTiles + options.trim);
|
||||||
@@ -567,7 +594,7 @@ void reverse() {
|
|||||||
// If vertically mirrored, fetch the bytes from the other end
|
// If vertically mirrored, fetch the bytes from the other end
|
||||||
uint8_t realY = (attribute & 0x40 ? 7 - y : y) * options.bitDepth;
|
uint8_t realY = (attribute & 0x40 ? 7 - y : y) * options.bitDepth;
|
||||||
uint8_t bitplane0 = tileData[realY];
|
uint8_t bitplane0 = tileData[realY];
|
||||||
uint8_t bitplane1 = options.bitDepth == 2 ? tileData[realY + 1] : bitplane0;
|
uint8_t bitplane1 = options.bitDepth == 2 ? tileData[realY + 1] : 0;
|
||||||
if (attribute & 0x20) { // Handle horizontal flip
|
if (attribute & 0x20) { // Handle horizontal flip
|
||||||
bitplane0 = flipTable[bitplane0];
|
bitplane0 = flipTable[bitplane0];
|
||||||
bitplane1 = flipTable[bitplane1];
|
bitplane1 = flipTable[bitplane1];
|
||||||
@@ -599,7 +626,7 @@ void reverse() {
|
|||||||
if (Rgba const &pixel = *color; pngColorType == PNG_COLOR_TYPE_GRAY) {
|
if (Rgba const &pixel = *color; pngColorType == PNG_COLOR_TYPE_GRAY) {
|
||||||
gray = gray << pngDepth | (pixel.red & ((1 << pngDepth) - 1));
|
gray = gray << pngDepth | (pixel.red & ((1 << pngDepth) - 1));
|
||||||
} else if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
|
} else if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
|
||||||
*ptr++ = palOfs * 4 + colorID;
|
*ptr++ = palOfs * options.nbColorsPerPal + colorID;
|
||||||
} else {
|
} else {
|
||||||
*ptr++ = pixel.red;
|
*ptr++ = pixel.red;
|
||||||
*ptr++ = pixel.green;
|
*ptr++ = pixel.green;
|
||||||
|
|||||||
+38
-8
@@ -24,16 +24,24 @@ std::string toCGB(uint16_t color) {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Based on inverting the "Modern - Accurate" formula used by SameBoy
|
// Copied from the "Modern - Accurate" (`GB_COLOR_CORRECTION_MODERN_ACCURATE`)
|
||||||
// since commit b5a611c5db46d6a0649d04d24d8d6339200f9ca1 (Dec 2020),
|
// formula used by SameBoy in its `scale_channel_with_curve` function since
|
||||||
// with gaps in the scale curve filled by polynomial interpolation.
|
// commit b5a611c5db46d6a0649d04d24d8d6339200f9ca1 (Dec 2020).
|
||||||
|
// clang-format off: vertically align columns of values
|
||||||
|
static std::array<uint8_t, 32> color_curve{
|
||||||
|
0, 6, 12, 20, 28, 36, 45, 56, 66, 76, 88, 100, 113, 125, 137, 149,
|
||||||
|
161, 172, 182, 192, 202, 210, 218, 225, 232, 238, 243, 247, 250, 252, 254, 255,
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// Inverted `color_curve`, with gaps filled by polynomial interpolation.
|
||||||
// clang-format off: vertically align columns of values
|
// clang-format off: vertically align columns of values
|
||||||
static std::array<uint8_t, 256> reverse_curve{
|
static std::array<uint8_t, 256> reverse_curve{
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19,
|
||||||
@@ -48,6 +56,28 @@ static std::array<uint8_t, 256> reverse_curve{
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
Rgba Rgba::fromCGBColor(uint16_t color, bool useColorCurve) {
|
||||||
|
uint8_t r = color & 0b11111, g = (color >> 5) & 0b11111, b = (color >> 10) & 0b11111;
|
||||||
|
if (useColorCurve) {
|
||||||
|
r = color_curve[r];
|
||||||
|
g = color_curve[g];
|
||||||
|
b = color_curve[b];
|
||||||
|
if (g != b) {
|
||||||
|
g = round(pow((pow(g / 255.0, 2.2) * 3 + pow(b / 255.0, 2.2)) / 4, 1 / 2.2) * 255);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r = r << 3 | r >> 2;
|
||||||
|
g = g << 3 | g >> 2;
|
||||||
|
b = b << 3 | b >> 2;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
static_cast<uint8_t>(color & transparent ? 0x00 : 0xFF),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t Rgba::cgbColor() const {
|
uint16_t Rgba::cgbColor() const {
|
||||||
if (isTransparent()) {
|
if (isTransparent()) {
|
||||||
return transparent;
|
return transparent;
|
||||||
|
|||||||
+303
-238
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "link/assign.hpp"
|
#include "link/assign.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "itertools.hpp"
|
#include "itertools.hpp"
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
|
#include "platform.hpp"
|
||||||
#include "verbosity.hpp"
|
#include "verbosity.hpp"
|
||||||
|
|
||||||
#include "link/main.hpp"
|
#include "link/main.hpp"
|
||||||
@@ -23,21 +25,255 @@
|
|||||||
#include "link/symbol.hpp"
|
#include "link/symbol.hpp"
|
||||||
#include "link/warning.hpp"
|
#include "link/warning.hpp"
|
||||||
|
|
||||||
struct MemoryLocation {
|
|
||||||
uint16_t address;
|
|
||||||
uint32_t bank;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FreeSpace {
|
struct FreeSpace {
|
||||||
uint16_t address;
|
uint16_t address;
|
||||||
uint16_t size;
|
uint16_t size; // Never zero.
|
||||||
|
|
||||||
|
uint16_t addrOnePast() const { return address + size; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Table of free space for each bank
|
// Table of free space for each bank
|
||||||
static std::vector<std::deque<FreeSpace>> memory[SECTTYPE_INVALID];
|
static std::vector<std::deque<FreeSpace>> memory[SECTTYPE_INVALID];
|
||||||
|
using FreeSpaceIter = std::deque<FreeSpace>::iterator;
|
||||||
|
|
||||||
|
static std::deque<FreeSpace> &freeSpaceOfBank(Section const §ion, uint32_t bank) {
|
||||||
|
assume(bank >= section.typeInfo().firstBank);
|
||||||
|
assume(bank <= section.typeInfo().lastBank);
|
||||||
|
return memory[section.type][bank - section.typeInfo().firstBank];
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Scrambling {
|
||||||
|
uint16_t romxOfs = 0;
|
||||||
|
uint16_t sramOfs = 0;
|
||||||
|
uint16_t wramxOfs = 0;
|
||||||
|
|
||||||
|
// Helper for the next function, to give names to its two returned values.
|
||||||
|
struct ScramblingInfo {
|
||||||
|
uint16_t &curOfs;
|
||||||
|
uint16_t maxOfs;
|
||||||
|
};
|
||||||
|
std::optional<ScramblingInfo> getInfoFor(SectionType type) {
|
||||||
|
switch (type) {
|
||||||
|
case SECTTYPE_ROMX:
|
||||||
|
return {
|
||||||
|
{romxOfs, options.scrambleROMX}
|
||||||
|
};
|
||||||
|
case SECTTYPE_SRAM:
|
||||||
|
return {
|
||||||
|
{sramOfs, options.scrambleSRAM}
|
||||||
|
};
|
||||||
|
case SECTTYPE_WRAMX:
|
||||||
|
return {
|
||||||
|
{wramxOfs, options.scrambleWRAMX}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Non-banked sections don't need scrambling support...
|
||||||
|
case SECTTYPE_ROM0:
|
||||||
|
case SECTTYPE_WRAM0:
|
||||||
|
case SECTTYPE_OAM:
|
||||||
|
case SECTTYPE_HRAM:
|
||||||
|
assume(!sectionTypeInfo[type].isBanked());
|
||||||
|
return std::nullopt;
|
||||||
|
// ...but VRAM doesn't either, regardless of whether it's banked or not.
|
||||||
|
case SECTTYPE_VRAM:
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
case SECTTYPE_INVALID:
|
||||||
|
unreachable_();
|
||||||
|
}
|
||||||
|
return std::nullopt; // Dead code, but some compilers don't recognize that.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static Scrambling scrambling;
|
||||||
|
|
||||||
|
struct MemoryLocation {
|
||||||
|
uint16_t address;
|
||||||
|
uint32_t bank;
|
||||||
|
|
||||||
|
static MemoryLocation initFor(Section const §ion) {
|
||||||
|
MemoryLocation location;
|
||||||
|
|
||||||
|
if (section.isAddressFixed) { // This will never change.
|
||||||
|
location.address = section.org;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.isBankFixed) {
|
||||||
|
location.bank = section.bank;
|
||||||
|
} else {
|
||||||
|
location.bank = section.typeInfo().firstBank;
|
||||||
|
|
||||||
|
if (auto info = scrambling.getInfoFor(section.type);
|
||||||
|
info.has_value() && info->maxOfs != 0) { // If scrambling is enabled...
|
||||||
|
// ...then we will begin the search from a different offset for each section.
|
||||||
|
// Go to the next offset (backwards), wrapping around. (Thus, no underflow!)
|
||||||
|
info->curOfs = (info->curOfs != 0 ? info->curOfs : info->maxOfs) - 1;
|
||||||
|
location.bank += info->curOfs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try again in the next bank, if one is available.
|
||||||
|
[[nodiscard("This returns whether iteration can be continued")]]
|
||||||
|
bool goToNextApplicableBankFor(Section const §ion) {
|
||||||
|
assume(bank >= section.typeInfo().firstBank);
|
||||||
|
assume(bank <= section.typeInfo().lastBank);
|
||||||
|
|
||||||
|
assume(!section.isBankFixed);
|
||||||
|
|
||||||
|
// Try scrambled banks in descending order until no bank in the scrambled range is
|
||||||
|
// available.
|
||||||
|
if (auto info = scrambling.getInfoFor(section.type);
|
||||||
|
info.has_value() && info->maxOfs != 0) {
|
||||||
|
// All floating sections within a scrambled region should be
|
||||||
|
// within the scrambled bank pool.
|
||||||
|
assume(bank < info->maxOfs + section.typeInfo().firstBank);
|
||||||
|
|
||||||
|
uint16_t ofsWithinPool = bank - section.typeInfo().firstBank;
|
||||||
|
// Go to the next bank (backwards), wrapping around. (Thus, no overflow!)
|
||||||
|
ofsWithinPool = (ofsWithinPool != 0 ? ofsWithinPool : info->maxOfs) - 1;
|
||||||
|
|
||||||
|
bank = ofsWithinPool + section.typeInfo().firstBank;
|
||||||
|
// Keep iterating unless we have wrapped back around to the start offset.
|
||||||
|
return ofsWithinPool != info->curOfs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, try in ascending order.
|
||||||
|
if (bank == section.typeInfo().lastBank) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++bank;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void makeAddressAligned(uint16_t alignMask, uint16_t alignOfs) {
|
||||||
|
// By how much the address is past the target offset within the current alignment "page".
|
||||||
|
uint16_t offset = (address - alignOfs) & alignMask;
|
||||||
|
// Move by one page *minus* that "overshoot" offset.
|
||||||
|
// If it's 0, then this would move by a whole page, but `& alignMask` resets it back to 0.
|
||||||
|
address += ((alignMask + 1) - offset) & alignMask;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static FreeSpaceIter tryPlacingInBank(Section const §ion, MemoryLocation &location) {
|
||||||
|
std::deque<FreeSpace> &bankMem = freeSpaceOfBank(section, location.bank);
|
||||||
|
|
||||||
|
if (section.isAddressFixed) {
|
||||||
|
// There is only one candidate location in this bank: the address at which the section is
|
||||||
|
// fixed.
|
||||||
|
assume(location.address == section.org);
|
||||||
|
|
||||||
|
FreeSpaceIter iter = std::find_if(RANGE(bankMem), [&location](FreeSpace const &freeSpace) {
|
||||||
|
// If they both exactly match, that means the next block will begin past the requested
|
||||||
|
// addr, so the function would fail anyway.
|
||||||
|
return freeSpace.addrOnePast() >= location.address;
|
||||||
|
});
|
||||||
|
if (iter != bankMem.end()) {
|
||||||
|
// We have the first block ending after the section's address, so all that's left is
|
||||||
|
// checking that the address does fall into the block, and then that the section fits.
|
||||||
|
if (location.address < iter->address
|
||||||
|
|| location.address + section.size > iter->addrOnePast()) {
|
||||||
|
return bankMem.end(); // Failed! Better luck next bank?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iter;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// There are many possible locations within the bank, so we are going to iterate on free
|
||||||
|
// blocks. If it is impossible to fit at the earliest (constraint-satisfying) address, then
|
||||||
|
// no other address in the block will do; thus, we make only one attempt per block.
|
||||||
|
return std::find_if(RANGE(bankMem), [&location, §ion](FreeSpace const &freeSpace) {
|
||||||
|
location.address = freeSpace.address;
|
||||||
|
if (section.isAlignFixed) {
|
||||||
|
location.makeAddressAligned(section.alignMask, section.alignOfs);
|
||||||
|
// Did it advance past the block? Or, rarely, overflowed?
|
||||||
|
if (location.address >= freeSpace.addrOnePast()
|
||||||
|
|| location.address < freeSpace.address) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since `location.address` lies within the block,
|
||||||
|
// we only need to check that its end address also does.
|
||||||
|
return location.address + section.size <= freeSpace.addrOnePast();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place section using first-fit decreasing algorithm
|
||||||
|
// <https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm>
|
||||||
|
// Returns an iterator to within `freeSpaceOfBank(section, location.bank)`
|
||||||
|
// (guaranteeing that `location.bank` remains valid) pointing at the free block that
|
||||||
|
// the section can go into (at `location.address`).
|
||||||
|
// The iterator is an end iterator if and only if no suitable location was found.
|
||||||
|
// `location` is updated accordingly.
|
||||||
|
static FreeSpaceIter tryPlacing(Section const §ion, MemoryLocation &location) {
|
||||||
|
if (section.isBankFixed) {
|
||||||
|
assume(location.bank == section.bank);
|
||||||
|
return tryPlacingInBank(section, location);
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (FreeSpaceIter iter = tryPlacingInBank(section, location);
|
||||||
|
iter != freeSpaceOfBank(section, location.bank).end()) {
|
||||||
|
return iter; // Found one!
|
||||||
|
}
|
||||||
|
} while (location.goToNextApplicableBankFor(section));
|
||||||
|
// Return a deque's end iterator to signal failure.
|
||||||
|
// The exact deque doesn't matter, but the caller will use `freeSpaceOfBank` also.
|
||||||
|
return freeSpaceOfBank(section, location.bank).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
updateFreeSpace(FreeSpaceIter iter, std::deque<FreeSpace> &bankMem, Section const §ion) {
|
||||||
|
assume(section.org + section.size <= UINT16_MAX);
|
||||||
|
uint16_t sectionEnd = section.org + section.size;
|
||||||
|
assume(section.org >= iter->address);
|
||||||
|
assume(sectionEnd <= iter->addrOnePast());
|
||||||
|
|
||||||
|
bool noLeftSpace = iter->address == section.org;
|
||||||
|
bool noRightSpace = iter->address + iter->size == sectionEnd;
|
||||||
|
if (noLeftSpace && noRightSpace) {
|
||||||
|
// The free space is entirely deleted
|
||||||
|
bankMem.erase(iter);
|
||||||
|
} else if (!noLeftSpace && !noRightSpace) {
|
||||||
|
// The free space is split in two
|
||||||
|
uint16_t size = static_cast<uint16_t>(iter->address + iter->size - sectionEnd);
|
||||||
|
// Resize the original space (address is unmodified)
|
||||||
|
iter->size = section.org - iter->address;
|
||||||
|
// Append the new space after the original one
|
||||||
|
bankMem.insert(iter + 1, {.address = sectionEnd, .size = size});
|
||||||
|
// `iter` cannot be reused from this point on, because `bankMem.insert`
|
||||||
|
// invalidates iterators to itself!
|
||||||
|
} else {
|
||||||
|
// The amount of free spaces doesn't change: resize!
|
||||||
|
iter->size -= section.size;
|
||||||
|
if (noLeftSpace) {
|
||||||
|
// The free space is moved *and* resized
|
||||||
|
iter->address += section.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Assigns a section to a given memory location
|
// Assigns a section to a given memory location
|
||||||
static void assignSection(Section §ion, MemoryLocation const &location) {
|
static void assignSection(Section §ion, MemoryLocation const &location) {
|
||||||
|
assume(location.address >= section.typeInfo().startAddr);
|
||||||
|
// Zero-sized sections can start one past the end of their region.
|
||||||
|
assume(location.address <= section.typeInfo().endAddr() + 1);
|
||||||
|
// This one is not redundant, it guards against overflow!
|
||||||
|
assume(location.address + section.size >= section.typeInfo().startAddr);
|
||||||
|
assume(location.address + section.size <= section.typeInfo().endAddr() + 1);
|
||||||
|
|
||||||
|
if (section.isAddressFixed) {
|
||||||
|
assume(location.address == section.org);
|
||||||
|
} else if (section.isAlignFixed) {
|
||||||
|
assume((location.address & section.alignMask) == section.alignOfs);
|
||||||
|
}
|
||||||
|
if (section.isBankFixed) {
|
||||||
|
assume(location.bank == section.bank);
|
||||||
|
}
|
||||||
|
|
||||||
// Propagate the assigned location to all UNIONs/FRAGMENTs
|
// Propagate the assigned location to all UNIONs/FRAGMENTs
|
||||||
// so `jr` patches in them will have the correct offset
|
// so `jr` patches in them will have the correct offset
|
||||||
for (Section &piece : section.pieces()) {
|
for (Section &piece : section.pieces()) {
|
||||||
@@ -47,267 +283,93 @@ static void assignSection(Section §ion, MemoryLocation const &location) {
|
|||||||
out_AddSection(section);
|
out_AddSection(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks whether a given location is suitable for placing a given section
|
static std::string describeConstraintsOf(Section const §ion) {
|
||||||
// This checks not only that the location has enough room for the section, but
|
std::string description = "\"" + section.name + "\" (" + section.typeInfo().name + " section) ";
|
||||||
// also that the constraints (alignment...) are respected.
|
if (section.isBankFixed && section.typeInfo().isBanked()) {
|
||||||
static bool isLocationSuitable(
|
char bank[9];
|
||||||
Section const §ion, FreeSpace const &freeSpace, MemoryLocation const &location
|
sprintf_to_array(bank, "%02" PRIx32, section.bank);
|
||||||
) {
|
|
||||||
if (section.isAddressFixed && section.org != location.address) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (section.isAlignFixed && ((location.address - section.alignOfs) & section.alignMask)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (location.address < freeSpace.address) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return location.address + section.size <= freeSpace.address + freeSpace.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MemoryLocation getStartLocation(Section const §ion) {
|
|
||||||
static uint16_t curScrambleROM = 0;
|
|
||||||
static uint16_t curScrambleWRAM = 0;
|
|
||||||
static uint16_t curScrambleSRAM = 0;
|
|
||||||
|
|
||||||
MemoryLocation location;
|
|
||||||
|
|
||||||
// Determine which bank we should start searching in
|
|
||||||
if (section.isBankFixed) {
|
|
||||||
location.bank = section.bank;
|
|
||||||
} else {
|
|
||||||
location.bank = sectionTypeInfo[section.type].firstBank;
|
|
||||||
|
|
||||||
// Scramble the bank if applicable
|
|
||||||
if (options.scrambleROMX && section.type == SECTTYPE_ROMX) {
|
|
||||||
if (curScrambleROM == 0) {
|
|
||||||
curScrambleROM = options.scrambleROMX;
|
|
||||||
}
|
|
||||||
location.bank += --curScrambleROM;
|
|
||||||
} else if (options.scrambleWRAMX && section.type == SECTTYPE_WRAMX) {
|
|
||||||
if (curScrambleWRAM == 0) {
|
|
||||||
curScrambleWRAM = options.scrambleWRAMX;
|
|
||||||
}
|
|
||||||
location.bank += --curScrambleWRAM;
|
|
||||||
} else if (options.scrambleSRAM && section.type == SECTTYPE_SRAM) {
|
|
||||||
if (curScrambleSRAM == 0) {
|
|
||||||
curScrambleSRAM = options.scrambleSRAM;
|
|
||||||
}
|
|
||||||
location.bank += --curScrambleSRAM;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a suitable free space index into `memory[section->type]` at which to place the given
|
|
||||||
// section, or `std::nullopt` if none was found.
|
|
||||||
static std::optional<size_t> getPlacement(Section const §ion, MemoryLocation &location) {
|
|
||||||
SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type];
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
if (location.bank < typeInfo.firstBank
|
|
||||||
|| location.bank >= memory[section.type].size() + typeInfo.firstBank) {
|
|
||||||
fatal(
|
|
||||||
"Invalid bank for %s section \"%s\": %" PRIu32,
|
|
||||||
sectionTypeInfo[section.type].name.c_str(),
|
|
||||||
section.name.c_str(),
|
|
||||||
location.bank
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch to the beginning of the next bank
|
|
||||||
std::deque<FreeSpace> &bankMem = memory[section.type][location.bank - typeInfo.firstBank];
|
|
||||||
size_t spaceIdx = 0;
|
|
||||||
|
|
||||||
if (spaceIdx < bankMem.size()) {
|
|
||||||
location.address = bankMem[spaceIdx].address;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process locations in that bank
|
|
||||||
while (spaceIdx < bankMem.size()) {
|
|
||||||
// If that location is OK, return it
|
|
||||||
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 and try again in the next bank.
|
|
||||||
if (location.address >= section.org) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
location.address = section.org;
|
|
||||||
} else if (section.isAlignFixed) {
|
|
||||||
// Move to next aligned location
|
|
||||||
// We have previously ensured alignment to 15 or fewer bits, so this will progress
|
|
||||||
assume(section.alignMask < (1 << 16) - 1);
|
|
||||||
// Move back to alignment boundary
|
|
||||||
location.address -= section.alignOfs;
|
|
||||||
// Ensure we're there (e.g. on first check)
|
|
||||||
location.address &= ~section.alignMask;
|
|
||||||
// Go to next align boundary and add offset
|
|
||||||
location.address += section.alignMask + 1 + section.alignOfs;
|
|
||||||
} else if (++spaceIdx < bankMem.size()) {
|
|
||||||
// Any location is fine, so, next free block
|
|
||||||
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) {
|
|
||||||
++spaceIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try again with the new location/free space combo
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try again in the next bank, if one is available.
|
|
||||||
// Try scrambled banks in descending order until no bank in the scrambled range is
|
|
||||||
// available. Otherwise, try in ascending order.
|
|
||||||
if (section.isBankFixed) {
|
|
||||||
return std::nullopt;
|
|
||||||
} else if (options.scrambleROMX && section.type == SECTTYPE_ROMX
|
|
||||||
&& location.bank <= options.scrambleROMX) {
|
|
||||||
if (location.bank > typeInfo.firstBank) {
|
|
||||||
--location.bank;
|
|
||||||
} else if (options.scrambleROMX < typeInfo.lastBank) {
|
|
||||||
location.bank = options.scrambleROMX + 1;
|
|
||||||
} else {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
} else if (options.scrambleWRAMX && section.type == SECTTYPE_WRAMX
|
|
||||||
&& location.bank <= options.scrambleWRAMX) {
|
|
||||||
if (location.bank > typeInfo.firstBank) {
|
|
||||||
--location.bank;
|
|
||||||
} else if (options.scrambleWRAMX < typeInfo.lastBank) {
|
|
||||||
location.bank = options.scrambleWRAMX + 1;
|
|
||||||
} else {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
} else if (options.scrambleSRAM && section.type == SECTTYPE_SRAM
|
|
||||||
&& location.bank <= options.scrambleSRAM) {
|
|
||||||
if (location.bank > typeInfo.firstBank) {
|
|
||||||
--location.bank;
|
|
||||||
} else if (options.scrambleSRAM < typeInfo.lastBank) {
|
|
||||||
location.bank = options.scrambleSRAM + 1;
|
|
||||||
} else {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
} else if (location.bank < typeInfo.lastBank) {
|
|
||||||
++location.bank;
|
|
||||||
} else {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try again in the next iteration.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string getSectionDescription(Section const §ion) {
|
|
||||||
std::string description =
|
|
||||||
"\"" + section.name + "\" (" + sectionTypeInfo[section.type].name + " section) ";
|
|
||||||
if (section.isBankFixed && sectTypeBanks(section.type) != 1) {
|
|
||||||
char bank[8];
|
|
||||||
snprintf(bank, sizeof(bank), "%02" PRIx32, section.bank);
|
|
||||||
if (section.isAddressFixed) {
|
if (section.isAddressFixed) {
|
||||||
char addr[8];
|
char addr[5];
|
||||||
snprintf(addr, sizeof(addr), "%04" PRIx16, section.org);
|
sprintf_to_array(addr, "%04" PRIx16, section.org);
|
||||||
description = description + "at $" + bank + ":" + addr;
|
description = description + "at $" + bank + ":" + addr;
|
||||||
} else if (section.isAlignFixed) {
|
} else if (section.isAlignFixed) {
|
||||||
char mask[8];
|
char mask[5];
|
||||||
snprintf(mask, sizeof(mask), "%" PRIx16, static_cast<uint16_t>(~section.alignMask));
|
sprintf_to_array(mask, "%" PRIx16, static_cast<uint16_t>(~section.alignMask));
|
||||||
description = description + "in bank $" + bank + " with align mask $" + mask;
|
description = description + "in bank $" + bank + " with align mask $" + mask;
|
||||||
} else {
|
} else {
|
||||||
description = description + "in bank $" + bank;
|
description = description + "in bank $" + bank;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (section.isAddressFixed) {
|
if (section.isAddressFixed) {
|
||||||
char addr[8];
|
char addr[5];
|
||||||
snprintf(addr, sizeof(addr), "%04" PRIx16, section.org);
|
sprintf_to_array(addr, "%04" PRIx16, section.org);
|
||||||
description = description + "at address $" + addr;
|
description = description + "at address $" + addr;
|
||||||
} else if (section.isAlignFixed) {
|
} else if (section.isAlignFixed) {
|
||||||
char mask[8], offset[8];
|
char mask[5], offset[5];
|
||||||
snprintf(mask, sizeof(mask), "%" PRIx16, static_cast<uint16_t>(~section.alignMask));
|
sprintf_to_array(mask, "%" PRIx16, static_cast<uint16_t>(~section.alignMask));
|
||||||
snprintf(offset, sizeof(offset), "%" PRIx16, section.alignOfs);
|
sprintf_to_array(offset, "%" PRIx16, section.alignOfs);
|
||||||
description = description + "with align mask $" + mask + " and offset $" + offset;
|
description = description + "with align mask $" + mask + " and offset $" + offset;
|
||||||
} else {
|
} else {
|
||||||
description = description + "anywhere";
|
description = description + "anywhere";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto info = scrambling.getInfoFor(section.type);
|
||||||
|
info.has_value() && info->maxOfs != 0) { // Only mention scrambling if it is enabled.
|
||||||
|
char size[6];
|
||||||
|
sprintf_to_array(size, "%" PRIu16, info->maxOfs);
|
||||||
|
description = description + " within the " + size + " scrambled banks";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Places a section in a suitable location, or error out if it fails to.
|
// Places a section in a suitable location, or error out if it fails to.
|
||||||
// Due to the implemented algorithm, this should be called with sections of decreasing size!
|
// Due to the implemented algorithm, this should be called with sections of decreasing size!
|
||||||
static void placeSection(Section §ion) {
|
static void placeSection(Section §ion) {
|
||||||
// Specially handle 0-byte SECTIONs, as they can't overlap anything
|
MemoryLocation location = MemoryLocation::initFor(section);
|
||||||
|
|
||||||
|
// Specially handle 0-byte SECTIONs, as they ignore free space entirely.
|
||||||
if (section.size == 0) {
|
if (section.size == 0) {
|
||||||
// Unless the SECTION's address was fixed, the starting address
|
if (!section.isAddressFixed) {
|
||||||
// is fine for any alignment, as checked in sect_DoSanityChecks.
|
location.address = section.typeInfo().startAddr;
|
||||||
MemoryLocation location = {
|
if (section.isAlignFixed) {
|
||||||
.address =
|
location.makeAddressAligned(section.alignMask, section.alignOfs);
|
||||||
section.isAddressFixed ? section.org : sectionTypeInfo[section.type].startAddr,
|
|
||||||
.bank = section.isBankFixed ? section.bank : sectionTypeInfo[section.type].firstBank,
|
|
||||||
};
|
|
||||||
assignSection(section, location);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place section using first-fit decreasing algorithm
|
|
||||||
// https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm
|
|
||||||
MemoryLocation location = getStartLocation(section);
|
|
||||||
if (std::optional<size_t> spaceIdx = getPlacement(section, location); spaceIdx) {
|
|
||||||
std::deque<FreeSpace> &bankMem =
|
|
||||||
memory[section.type][location.bank - sectionTypeInfo[section.type].firstBank];
|
|
||||||
FreeSpace &freeSpace = bankMem[*spaceIdx];
|
|
||||||
|
|
||||||
assignSection(section, location);
|
|
||||||
|
|
||||||
// Update the free space
|
|
||||||
assume(section.org + section.size <= UINT16_MAX);
|
|
||||||
uint16_t sectionEnd = section.org + section.size;
|
|
||||||
bool noLeftSpace = freeSpace.address == section.org;
|
|
||||||
bool noRightSpace = freeSpace.address + freeSpace.size == sectionEnd;
|
|
||||||
if (noLeftSpace && noRightSpace) {
|
|
||||||
// The free space is entirely deleted
|
|
||||||
bankMem.erase(bankMem.begin() + *spaceIdx);
|
|
||||||
} else if (!noLeftSpace && !noRightSpace) {
|
|
||||||
// The free space is split in two
|
|
||||||
// Append the new space after the original one
|
|
||||||
uint16_t size = static_cast<uint16_t>(freeSpace.address + freeSpace.size - sectionEnd);
|
|
||||||
bankMem.insert(bankMem.begin() + *spaceIdx + 1, {.address = sectionEnd, .size = size});
|
|
||||||
// **`freeSpace` cannot be reused from this point on, because `bankMem.insert`
|
|
||||||
// invalidates all references to itself!**
|
|
||||||
|
|
||||||
// Resize the original space (address is unmodified)
|
|
||||||
bankMem[*spaceIdx].size = section.org - bankMem[*spaceIdx].address;
|
|
||||||
} else {
|
|
||||||
// The amount of free spaces doesn't change: resize!
|
|
||||||
freeSpace.size -= section.size;
|
|
||||||
if (noLeftSpace) {
|
|
||||||
// The free space is moved *and* resized
|
|
||||||
freeSpace.address += section.size;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This check safely handles sections with impossible alignment and no fixed address.
|
||||||
|
// The above `location.address = section.typeInfo().startAddr` would be valid on its own,
|
||||||
|
// but `location.makeAddressAligned(...)` can increase `location.address` above the valid
|
||||||
|
// range for its `section`, which would violate an assumption in `assignSection`.
|
||||||
|
// Note that sections with fixed addresses are handled earlier by `sect_DoSanityChecks`,
|
||||||
|
// but this check would safely handle them too if they ever reached it.
|
||||||
|
if (location.address <= section.typeInfo().endAddr() + 1) {
|
||||||
|
assignSection(section, location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeSpaceIter iter = tryPlacing(section, location);
|
||||||
|
if (std::deque<FreeSpace> &bankMem = freeSpaceOfBank(section, location.bank);
|
||||||
|
iter != bankMem.end()) {
|
||||||
|
assignSection(section, location);
|
||||||
|
|
||||||
|
updateFreeSpace(iter, bankMem, section);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!section.isBankFixed || !section.isAddressFixed) {
|
if (!section.isBankFixed || !section.isAddressFixed) {
|
||||||
// If a section failed to go to several places, nothing we can report
|
// If a section failed to go to several places, nothing we can report
|
||||||
fatal("Unable to place %s", getSectionDescription(section).c_str());
|
fatal("Unable to place %s", describeConstraintsOf(section).c_str());
|
||||||
} else if (section.org + section.size > sectTypeEndAddr(section.type) + 1) {
|
} else if (uint16_t onePastEnd = section.typeInfo().endAddr() + 1;
|
||||||
|
section.org + section.size > onePastEnd) {
|
||||||
// If the section just can't fit the bank, report that
|
// If the section just can't fit the bank, report that
|
||||||
fatal(
|
fatal(
|
||||||
"Unable to place %s: section runs past end of region ($%04x > $%04x)",
|
"Unable to place %s: section runs past end of region ($%04x > $%04x)",
|
||||||
getSectionDescription(section).c_str(),
|
describeConstraintsOf(section).c_str(),
|
||||||
section.org + section.size,
|
section.org + section.size,
|
||||||
sectTypeEndAddr(section.type) + 1
|
onePastEnd
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise there is overlap with another section
|
// Otherwise there is overlap with another section
|
||||||
@@ -315,7 +377,7 @@ static void placeSection(Section §ion) {
|
|||||||
assume(overlap != nullptr);
|
assume(overlap != nullptr);
|
||||||
fatal(
|
fatal(
|
||||||
"Unable to place %s: section overlaps with \"%s\"",
|
"Unable to place %s: section overlaps with \"%s\"",
|
||||||
getSectionDescription(section).c_str(),
|
describeConstraintsOf(section).c_str(),
|
||||||
overlap->name.c_str()
|
overlap->name.c_str()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -392,8 +454,9 @@ static void checkOverlayCompat() {
|
|||||||
unfixedList += "\n- and ";
|
unfixedList += "\n- and ";
|
||||||
unfixedList += std::to_string(nbUnfixedSections - nbListed);
|
unfixedList += std::to_string(nbUnfixedSections - nbListed);
|
||||||
unfixedList += " more";
|
unfixedList += " more";
|
||||||
break;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
unfixedList += "\n- \"";
|
unfixedList += "\n- \"";
|
||||||
unfixedList += section->name;
|
unfixedList += section->name;
|
||||||
unfixedList += "\" (";
|
unfixedList += "\" (";
|
||||||
@@ -410,6 +473,7 @@ static void checkOverlayCompat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finish: // Can't `break` out of a nested loop
|
||||||
fatal(
|
fatal(
|
||||||
"All sections must be fixed when using an overlay file; %zu %s not:%s",
|
"All sections must be fixed when using an overlay file; %zu %s not:%s",
|
||||||
nbUnfixedSections,
|
nbUnfixedSections,
|
||||||
@@ -423,11 +487,12 @@ void assign_AssignSections() {
|
|||||||
|
|
||||||
// Initialize the free space-modelling structs
|
// Initialize the free space-modelling structs
|
||||||
for (SectionType type : EnumSeq(SECTTYPE_INVALID)) {
|
for (SectionType type : EnumSeq(SECTTYPE_INVALID)) {
|
||||||
memory[type].resize(sectTypeBanks(type));
|
SectionTypeInfo const &typeInfo = sectionTypeInfo[type];
|
||||||
|
memory[type].resize(typeInfo.nbBanks());
|
||||||
for (std::deque<FreeSpace> &bankMem : memory[type]) {
|
for (std::deque<FreeSpace> &bankMem : memory[type]) {
|
||||||
bankMem.push_back({
|
bankMem.push_back({
|
||||||
.address = sectionTypeInfo[type].startAddr,
|
.address = typeInfo.startAddr,
|
||||||
.size = sectionTypeInfo[type].size,
|
.size = typeInfo.size,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-20
@@ -31,7 +31,7 @@ static void setActiveTypeAndIdx(SectionType type, uint32_t idx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void layout_SetFloatingSectionType(SectionType type) {
|
void layout_SetFloatingSectionType(SectionType type) {
|
||||||
if (sectTypeBanks(type) == 1) {
|
if (!sectionTypeInfo[type].isBanked()) {
|
||||||
// There is only a single bank anyway, so just set the index to 0.
|
// There is only a single bank anyway, so just set the index to 0.
|
||||||
setActiveTypeAndIdx(type, 0);
|
setActiveTypeAndIdx(type, 0);
|
||||||
} else {
|
} else {
|
||||||
@@ -46,8 +46,8 @@ void layout_SetFloatingSectionType(SectionType type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void layout_SetSectionType(SectionType type) {
|
void layout_SetSectionType(SectionType type) {
|
||||||
if (sectTypeBanks(type) != 1) {
|
if (SectionTypeInfo const &typeInfo = sectionTypeInfo[type]; typeInfo.isBanked()) {
|
||||||
scriptError("A bank number must be specified for %s", sectionTypeInfo[type].name.c_str());
|
scriptError("A bank number must be specified for %s", typeInfo.name);
|
||||||
// Keep going with a default value for the bank index.
|
// Keep going with a default value for the bank index.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ void layout_SetSectionType(SectionType type, uint32_t bank) {
|
|||||||
if (bank < typeInfo.firstBank) {
|
if (bank < typeInfo.firstBank) {
|
||||||
scriptError(
|
scriptError(
|
||||||
"%s bank %" PRIu32 " does not exist (the minimum is %" PRIu32 ")",
|
"%s bank %" PRIu32 " does not exist (the minimum is %" PRIu32 ")",
|
||||||
typeInfo.name.c_str(),
|
typeInfo.name,
|
||||||
bank,
|
bank,
|
||||||
typeInfo.firstBank
|
typeInfo.firstBank
|
||||||
);
|
);
|
||||||
@@ -68,10 +68,11 @@ void layout_SetSectionType(SectionType type, uint32_t bank) {
|
|||||||
} else if (bank > typeInfo.lastBank) {
|
} else if (bank > typeInfo.lastBank) {
|
||||||
scriptError(
|
scriptError(
|
||||||
"%s bank %" PRIu32 " does not exist (the maximum is %" PRIu32 ")",
|
"%s bank %" PRIu32 " does not exist (the maximum is %" PRIu32 ")",
|
||||||
typeInfo.name.c_str(),
|
typeInfo.name,
|
||||||
bank,
|
bank,
|
||||||
typeInfo.lastBank
|
typeInfo.lastBank
|
||||||
);
|
);
|
||||||
|
bank = typeInfo.lastBank;
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveTypeAndIdx(type, bank - typeInfo.firstBank);
|
setActiveTypeAndIdx(type, bank - typeInfo.firstBank);
|
||||||
@@ -87,22 +88,22 @@ void layout_SetAddr(uint32_t addr) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t &pc = curAddr[activeType][activeBankIdx];
|
|
||||||
SectionTypeInfo const &typeInfo = sectionTypeInfo[activeType];
|
SectionTypeInfo const &typeInfo = sectionTypeInfo[activeType];
|
||||||
|
|
||||||
if (addr < pc) {
|
if (uint16_t &pc = curAddr[activeType][activeBankIdx]; addr < pc) {
|
||||||
scriptError("Cannot decrease the current address (from $%04x to $%04x)", pc, addr);
|
scriptError("Cannot decrease the current address (from $%04x to $%04x)", pc, addr);
|
||||||
} else if (addr > sectTypeEndAddr(activeType)) { // Allow "one past the end" sections.
|
} else if (addr > typeInfo.endAddr() + 1u) { // Allow "one past the end" zero-sized sections.
|
||||||
scriptError(
|
scriptError(
|
||||||
"Cannot set the current address to $%04" PRIx32 ": %s ends at $%04" PRIx16,
|
"Cannot set the current address to $%04" PRIx32 ": %s ends at $%04" PRIx16,
|
||||||
addr,
|
addr,
|
||||||
typeInfo.name.c_str(),
|
typeInfo.name,
|
||||||
sectTypeEndAddr(activeType)
|
typeInfo.endAddr()
|
||||||
);
|
);
|
||||||
pc = sectTypeEndAddr(activeType);
|
pc = typeInfo.endAddr();
|
||||||
} else {
|
} else {
|
||||||
pc = addr;
|
pc = addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
isPcFloating = false;
|
isPcFloating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +126,7 @@ void layout_AlignTo(uint32_t alignment, uint32_t alignOfs) {
|
|||||||
|
|
||||||
if (isPcFloating) {
|
if (isPcFloating) {
|
||||||
if (alignment >= 16) {
|
if (alignment >= 16) {
|
||||||
layout_SetAddr(floatingAlignOffset);
|
layout_SetAddr(alignOfs);
|
||||||
} else {
|
} else {
|
||||||
uint32_t alignSize = 1u << alignment;
|
uint32_t alignSize = 1u << alignment;
|
||||||
uint32_t alignMask = alignSize - 1;
|
uint32_t alignMask = alignSize - 1;
|
||||||
@@ -181,7 +182,7 @@ void layout_AlignTo(uint32_t alignment, uint32_t alignOfs) {
|
|||||||
", past $%04" PRIx16,
|
", past $%04" PRIx16,
|
||||||
pc,
|
pc,
|
||||||
static_cast<uint16_t>(pc + length),
|
static_cast<uint16_t>(pc + length),
|
||||||
static_cast<uint16_t>(sectTypeEndAddr(activeType) + 1)
|
static_cast<uint16_t>(typeInfo.endAddr() + 1)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -211,7 +212,7 @@ void layout_Pad(uint32_t length) {
|
|||||||
"Cannot increase the current address by %u bytes: only %u bytes to $%04" PRIx16,
|
"Cannot increase the current address by %u bytes: only %u bytes to $%04" PRIx16,
|
||||||
length,
|
length,
|
||||||
typeInfo.size - offset,
|
typeInfo.size - offset,
|
||||||
static_cast<uint16_t>(sectTypeEndAddr(activeType) + 1)
|
static_cast<uint16_t>(typeInfo.endAddr() + 1)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
pc += length;
|
pc += length;
|
||||||
@@ -241,7 +242,7 @@ void layout_PlaceSection(std::string const &name, bool isOptional) {
|
|||||||
scriptError(
|
scriptError(
|
||||||
"\"%s\" is specified to be a %s section, but it contains data",
|
"\"%s\" is specified to be a %s section, but it contains data",
|
||||||
name.c_str(),
|
name.c_str(),
|
||||||
typeInfo.name.c_str()
|
typeInfo.name
|
||||||
);
|
);
|
||||||
} else if (sectTypeHasData(activeType) && section->data.empty() && section->size != 0) {
|
} else if (sectTypeHasData(activeType) && section->data.empty() && section->size != 0) {
|
||||||
// A section that lacks data can only be assigned to a type that requires data
|
// A section that lacks data can only be assigned to a type that requires data
|
||||||
@@ -249,7 +250,7 @@ void layout_PlaceSection(std::string const &name, bool isOptional) {
|
|||||||
scriptError(
|
scriptError(
|
||||||
"\"%s\" is specified to be a %s section, but it does not contain data",
|
"\"%s\" is specified to be a %s section, but it does not contain data",
|
||||||
name.c_str(),
|
name.c_str(),
|
||||||
typeInfo.name.c_str()
|
typeInfo.name
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// SDCC areas don't have a type assigned yet, so the linker script gives them one.
|
// SDCC areas don't have a type assigned yet, so the linker script gives them one.
|
||||||
@@ -261,8 +262,8 @@ void layout_PlaceSection(std::string const &name, bool isOptional) {
|
|||||||
scriptError(
|
scriptError(
|
||||||
"\"%s\" is specified to be a %s section, but it is already a %s section",
|
"\"%s\" is specified to be a %s section, but it is already a %s section",
|
||||||
name.c_str(),
|
name.c_str(),
|
||||||
typeInfo.name.c_str(),
|
typeInfo.name,
|
||||||
sectionTypeInfo[section->type].name.c_str()
|
section->typeInfo().name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +276,7 @@ void layout_PlaceSection(std::string const &name, bool isOptional) {
|
|||||||
"The linker script places section \"%s\" in %s bank %" PRIu32
|
"The linker script places section \"%s\" in %s bank %" PRIu32
|
||||||
", but it was already defined in bank %" PRIu32,
|
", but it was already defined in bank %" PRIu32,
|
||||||
name.c_str(),
|
name.c_str(),
|
||||||
sectionTypeInfo[section->type].name.c_str(),
|
section->typeInfo().name,
|
||||||
bank,
|
bank,
|
||||||
section->bank
|
section->bank
|
||||||
);
|
);
|
||||||
@@ -320,7 +321,7 @@ void layout_PlaceSection(std::string const &name, bool isOptional) {
|
|||||||
", but then it would overflow %s by %" PRIu16 " byte%s",
|
", but then it would overflow %s by %" PRIu16 " byte%s",
|
||||||
name.c_str(),
|
name.c_str(),
|
||||||
org,
|
org,
|
||||||
typeInfo.name.c_str(),
|
typeInfo.name,
|
||||||
overflowSize,
|
overflowSize,
|
||||||
overflowSize == 1 ? "" : "s"
|
overflowSize == 1 ? "" : "s"
|
||||||
);
|
);
|
||||||
|
|||||||
+15
-6
@@ -101,7 +101,7 @@ static void parseScrambleSpec(char *spec) {
|
|||||||
// clang-format off: vertically align nested initializers
|
// clang-format off: vertically align nested initializers
|
||||||
static UpperMap<std::pair<uint16_t *, uint16_t>> scrambleSpecs{
|
static UpperMap<std::pair<uint16_t *, uint16_t>> scrambleSpecs{
|
||||||
{"ROMX", std::pair{&options.scrambleROMX, 65535}},
|
{"ROMX", std::pair{&options.scrambleROMX, 65535}},
|
||||||
{"SRAM", std::pair{&options.scrambleSRAM, 255 }},
|
{"SRAM", std::pair{&options.scrambleSRAM, 256 }},
|
||||||
{"WRAMX", std::pair{&options.scrambleWRAMX, 7 }},
|
{"WRAMX", std::pair{&options.scrambleWRAMX, 7 }},
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
@@ -433,14 +433,14 @@ int main(int argc, char *argv[]) {
|
|||||||
sectionTypeInfo[SECTTYPE_VRAM].lastBank = 0;
|
sectionTypeInfo[SECTTYPE_VRAM].lastBank = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read all object files first,
|
// Read all object files...
|
||||||
size_t nbFiles = localOptions.inputFileNames.size();
|
size_t nbFiles = localOptions.inputFileNames.size();
|
||||||
obj_Setup(nbFiles);
|
obj_Setup(nbFiles);
|
||||||
for (size_t i = 0; i < nbFiles; ++i) {
|
for (size_t i = 0; i < nbFiles; ++i) {
|
||||||
obj_ReadFile(localOptions.inputFileNames[i], nbFiles - i - 1);
|
obj_ReadFile(localOptions.inputFileNames[i], nbFiles - i - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply the linker script's modifications,
|
// Apply the linker script's modifications...
|
||||||
if (localOptions.linkerScriptName) {
|
if (localOptions.linkerScriptName) {
|
||||||
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
|
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
|
||||||
|
|
||||||
@@ -453,16 +453,25 @@ int main(int argc, char *argv[]) {
|
|||||||
requireZeroErrors();
|
requireZeroErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
// then process them,
|
// Attempt to generate a layout...
|
||||||
sect_DoSanityChecks();
|
sect_DoSanityChecks();
|
||||||
requireZeroErrors();
|
requireZeroErrors();
|
||||||
assign_AssignSections();
|
assign_AssignSections();
|
||||||
|
|
||||||
|
// Emit the symbol and/or map files now (if requested on the CLI),
|
||||||
|
// so that more link-time info is available to debug any failing assertions.
|
||||||
|
// This is safe to do, since we have a valid layout at this point;
|
||||||
|
// assertions only check user-level assumptions made about it.
|
||||||
|
out_WriteSymIfAny();
|
||||||
|
out_WriteMapIfAny();
|
||||||
|
|
||||||
|
// Make any last-minute verifications...
|
||||||
patch_CheckAssertions();
|
patch_CheckAssertions();
|
||||||
|
|
||||||
// and finally output the result.
|
// And, finally, make the ROM and emit it!
|
||||||
patch_ApplyPatches();
|
patch_ApplyPatches();
|
||||||
requireZeroErrors();
|
requireZeroErrors();
|
||||||
out_WriteFiles();
|
out_WriteROMIfAny();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-38
@@ -71,7 +71,7 @@ void out_AddSection(Section const §ion) {
|
|||||||
1, // SECTTYPE_OAM
|
1, // SECTTYPE_OAM
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t targetBank = section.bank - sectionTypeInfo[section.type].firstBank;
|
uint32_t targetBank = section.bank - section.typeInfo().firstBank;
|
||||||
if (targetBank >= maxNbBanks[section.type]) {
|
if (targetBank >= maxNbBanks[section.type]) {
|
||||||
fatal(
|
fatal(
|
||||||
"Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
|
"Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
|
||||||
@@ -96,7 +96,7 @@ void out_AddSection(Section const §ion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section const *out_OverlappingSection(Section const §ion) {
|
Section const *out_OverlappingSection(Section const §ion) {
|
||||||
uint32_t bank = section.bank - sectionTypeInfo[section.type].firstBank;
|
uint32_t bank = section.bank - section.typeInfo().firstBank;
|
||||||
|
|
||||||
for (Section const *ptr : sections[section.type][bank].sections) {
|
for (Section const *ptr : sections[section.type][bank].sections) {
|
||||||
if (ptr->org < section.org + section.size && section.org < ptr->org + ptr->size) {
|
if (ptr->org < section.org + section.size && section.org < ptr->org + ptr->size) {
|
||||||
@@ -204,7 +204,7 @@ static void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeROM() {
|
void out_WriteROMIfAny() {
|
||||||
if (options.outputFileName) {
|
if (options.outputFileName) {
|
||||||
char const *outputFileName = options.outputFileName->c_str();
|
char const *outputFileName = options.outputFileName->c_str();
|
||||||
if (*options.outputFileName != "-") {
|
if (*options.outputFileName != "-") {
|
||||||
@@ -267,31 +267,27 @@ static void writeROM() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void writeSymName(std::string const &name, FILE *file) {
|
static void writeSymName(std::string const &name, FILE *file) {
|
||||||
for (char const *ptr = name.c_str(); *ptr != '\0';) {
|
for (size_t i = 0; i < name.length();) {
|
||||||
// Output legal ASCII characters as-is
|
// Output legal ASCII characters as-is
|
||||||
if (char c = *ptr; continuesIdentifier(c)) {
|
if (char c = name[i]; continuesIdentifier(c)) {
|
||||||
putc(c, file);
|
putc(c, file);
|
||||||
++ptr;
|
++i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output illegal characters using Unicode escapes ('\u' or '\U')
|
// Output illegal characters using Unicode escapes ('\u' or '\U')
|
||||||
// Decode the UTF-8 codepoint; or at least attempt to
|
// Decode the UTF-8 codepoint; or at least attempt to
|
||||||
Utf8Decoder decoder;
|
Utf8Decoder decoder;
|
||||||
do {
|
while (i < name.length()) {
|
||||||
if (decoder.update(*ptr) != UTF8_REJECT) {
|
decoder.update(static_cast<uint8_t>(name[i++]));
|
||||||
++ptr;
|
if (decoder.state == UTF8_ACCEPT || decoder.state == UTF8_REJECT) {
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
// This sequence was invalid; emit a U+FFFD, and recover
|
}
|
||||||
|
if (decoder.state != UTF8_ACCEPT) {
|
||||||
|
// This sequence was invalid or incomplete; emit a U+FFFD instead
|
||||||
decoder.codepoint = 0xFFFD;
|
decoder.codepoint = 0xFFFD;
|
||||||
// Skip continuation bytes
|
}
|
||||||
// A NUL byte does not qualify, so we're good
|
|
||||||
while ((*ptr & 0xC0) == 0x80) {
|
|
||||||
++ptr;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} while (decoder.state != UTF8_ACCEPT);
|
|
||||||
fprintf(
|
fprintf(
|
||||||
file, decoder.codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32, decoder.codepoint
|
file, decoder.codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32, decoder.codepoint
|
||||||
);
|
);
|
||||||
@@ -465,18 +461,19 @@ static void writeMapBank(SortedSections const §List, SectionType type, uint3
|
|||||||
fprintf(
|
fprintf(
|
||||||
mapFile,
|
mapFile,
|
||||||
"\n%s bank #%" PRIu32 ":\n",
|
"\n%s bank #%" PRIu32 ":\n",
|
||||||
sectionTypeInfo[type].name.c_str(),
|
sectionTypeInfo[type].name,
|
||||||
bank + sectionTypeInfo[type].firstBank
|
bank + sectionTypeInfo[type].firstBank
|
||||||
);
|
);
|
||||||
|
|
||||||
uint16_t prevEndAddr = sectionTypeInfo[type].startAddr;
|
uint16_t prevEndAddr = sectionTypeInfo[type].startAddr;
|
||||||
uint16_t used = forEachSection(sectList, [&](Section const §) {
|
uint16_t used = forEachSection(sectList, [&](Section const §) {
|
||||||
assume(sect.offset == 0);
|
assume(sect.offset == 0);
|
||||||
|
|
||||||
writeEmptySpace(prevEndAddr, sect.org);
|
|
||||||
|
|
||||||
assume(sect.org + sect.size <= UINT16_MAX);
|
assume(sect.org + sect.size <= UINT16_MAX);
|
||||||
prevEndAddr = sect.org + sect.size;
|
|
||||||
|
if (uint16_t endAddr = sect.org + sect.size; endAddr > prevEndAddr) {
|
||||||
|
writeEmptySpace(prevEndAddr, sect.org);
|
||||||
|
prevEndAddr = endAddr;
|
||||||
|
}
|
||||||
|
|
||||||
fprintf(mapFile, "\tSECTION: $%04" PRIx16, sect.org);
|
fprintf(mapFile, "\tSECTION: $%04" PRIx16, sect.org);
|
||||||
if (sect.size != 0) {
|
if (sect.size != 0) {
|
||||||
@@ -495,12 +492,10 @@ static void writeMapBank(SortedSections const §List, SectionType type, uint3
|
|||||||
if (used == 0) {
|
if (used == 0) {
|
||||||
fputs("\tEMPTY\n", mapFile);
|
fputs("\tEMPTY\n", mapFile);
|
||||||
} else {
|
} else {
|
||||||
uint16_t bankEndAddr = sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size;
|
uint16_t bankEndAddr = sectionTypeInfo[type].endAddr() + 1;
|
||||||
|
|
||||||
writeEmptySpace(prevEndAddr, bankEndAddr);
|
writeEmptySpace(prevEndAddr, bankEndAddr);
|
||||||
|
|
||||||
uint16_t slack = sectionTypeInfo[type].size - used;
|
uint16_t slack = sectionTypeInfo[type].size - used;
|
||||||
|
|
||||||
fprintf(mapFile, "\tTOTAL EMPTY: $%04" PRIx16 " byte%s\n", slack, slack == 1 ? "" : "s");
|
fprintf(mapFile, "\tTOTAL EMPTY: $%04" PRIx16 " byte%s\n", slack, slack == 1 ? "" : "s");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -517,13 +512,12 @@ static void writeMapSummary() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not output unused section types
|
// Skip types which haven't been used at all.
|
||||||
if (nbBanks == 0) {
|
if (nbBanks == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t usedTotal = 0;
|
uint32_t usedTotal = 0;
|
||||||
|
|
||||||
for (uint32_t bank = 0; bank < nbBanks; ++bank) {
|
for (uint32_t bank = 0; bank < nbBanks; ++bank) {
|
||||||
usedTotal += forEachSection(sections[type][bank], [](Section const &) {});
|
usedTotal += forEachSection(sections[type][bank], [](Section const &) {});
|
||||||
}
|
}
|
||||||
@@ -531,19 +525,19 @@ static void writeMapSummary() {
|
|||||||
fprintf(
|
fprintf(
|
||||||
mapFile,
|
mapFile,
|
||||||
"\t%s: %" PRIu32 " byte%s used / %zu free",
|
"\t%s: %" PRIu32 " byte%s used / %zu free",
|
||||||
sectionTypeInfo[type].name.c_str(),
|
sectionTypeInfo[type].name,
|
||||||
usedTotal,
|
usedTotal,
|
||||||
usedTotal == 1 ? "" : "s",
|
usedTotal == 1 ? "" : "s",
|
||||||
static_cast<size_t>(nbBanks) * sectionTypeInfo[type].size - usedTotal
|
static_cast<size_t>(nbBanks) * sectionTypeInfo[type].size - usedTotal
|
||||||
);
|
);
|
||||||
if (sectionTypeInfo[type].firstBank != sectionTypeInfo[type].lastBank || nbBanks > 1) {
|
if (sectionTypeInfo[type].isBanked()) {
|
||||||
fprintf(mapFile, " in %u bank%s", nbBanks, nbBanks == 1 ? "" : "s");
|
fprintf(mapFile, " in %u bank%s", nbBanks, nbBanks == 1 ? "" : "s");
|
||||||
}
|
}
|
||||||
putc('\n', mapFile);
|
putc('\n', mapFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeSym() {
|
void out_WriteSymIfAny() {
|
||||||
if (!options.symFileName) {
|
if (!options.symFileName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -594,7 +588,7 @@ static void writeSym() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeMap() {
|
void out_WriteMapIfAny() {
|
||||||
if (!options.mapFileName) {
|
if (!options.mapFileName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -622,9 +616,3 @@ static void writeMap() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void out_WriteFiles() {
|
|
||||||
writeROM();
|
|
||||||
writeSym();
|
|
||||||
writeMap();
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-3
@@ -114,14 +114,14 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
|||||||
int32_t value;
|
int32_t value;
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case RPN_ADD:
|
case RPN_ADD:
|
||||||
value = popRPN(patch) + popRPN(patch);
|
value = op_add(popRPN(patch), popRPN(patch));
|
||||||
break;
|
break;
|
||||||
case RPN_SUB:
|
case RPN_SUB:
|
||||||
value = popRPN(patch);
|
value = popRPN(patch);
|
||||||
value = popRPN(patch) - value;
|
value = op_sub(popRPN(patch), value);
|
||||||
break;
|
break;
|
||||||
case RPN_MUL:
|
case RPN_MUL:
|
||||||
value = popRPN(patch) * popRPN(patch);
|
value = op_mul(popRPN(patch), popRPN(patch));
|
||||||
break;
|
break;
|
||||||
case RPN_DIV:
|
case RPN_DIV:
|
||||||
value = popRPN(patch);
|
value = popRPN(patch);
|
||||||
|
|||||||
@@ -855,7 +855,6 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
|
|
||||||
// RAM sections can have a size, but don't get any data (they shouldn't have any)
|
// RAM sections can have a size, but don't get any data (they shouldn't have any)
|
||||||
if (section->type != SECTTYPE_INVALID) {
|
if (section->type != SECTTYPE_INVALID) {
|
||||||
SectionTypeInfo const &typeInfo = sectionTypeInfo[section->type];
|
|
||||||
// Otherwise, how would the type already be known at this point?
|
// Otherwise, how would the type already be known at this point?
|
||||||
assume(section->isAddressFixed);
|
assume(section->isAddressFixed);
|
||||||
|
|
||||||
@@ -866,7 +865,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
"\"%s\" is implicitly defined as a %s section (being at address $%04" PRIx16
|
"\"%s\" is implicitly defined as a %s section (being at address $%04" PRIx16
|
||||||
"), but it has data! (Was a bad `__at()` value used?)",
|
"), but it has data! (Was a bad `__at()` value used?)",
|
||||||
section->name.c_str(),
|
section->name.c_str(),
|
||||||
typeInfo.name.c_str(),
|
section->typeInfo().name,
|
||||||
section->org
|
section->org
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -876,7 +875,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
"\"%s\" is implicitly defined as a %s section (being at address $%04" PRIx16
|
"\"%s\" is implicitly defined as a %s section (being at address $%04" PRIx16
|
||||||
"), but it doesn't have any data! (Was a bad `__at()` value used?)",
|
"), but it doesn't have any data! (Was a bad `__at()` value used?)",
|
||||||
section->name.c_str(),
|
section->name.c_str(),
|
||||||
typeInfo.name.c_str(),
|
section->typeInfo().name,
|
||||||
section->org
|
section->org
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-17
@@ -56,6 +56,18 @@ static void checkPieceCompat(Section &target, Section const &other, size_t delta
|
|||||||
other.org
|
other.org
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else if (other.org < delta) {
|
||||||
|
// Check that `org` did not underflow. Note that it's safe for the above checks to
|
||||||
|
// use an underflowed value, since their reported errors will still be accurate.
|
||||||
|
fatalTwoAt(
|
||||||
|
target,
|
||||||
|
other,
|
||||||
|
"Section \"%s\" contains %" PRIu16
|
||||||
|
" bytes, but is also defined with a lower fixed address $%04" PRIx16,
|
||||||
|
target.name.c_str(),
|
||||||
|
target.size,
|
||||||
|
other.org
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
target.isAddressFixed = true;
|
target.isAddressFixed = true;
|
||||||
@@ -115,8 +127,8 @@ static void mergeSections(Section &target, std::unique_ptr<Section> &&other) {
|
|||||||
*other,
|
*other,
|
||||||
"Section \"%s\" is defined with type `%s`, but also with type `%s`",
|
"Section \"%s\" is defined with type `%s`, but also with type `%s`",
|
||||||
target.name.c_str(),
|
target.name.c_str(),
|
||||||
sectionTypeInfo[target.type].name.c_str(),
|
target.typeInfo().name,
|
||||||
sectionTypeInfo[other->type].name.c_str()
|
other->typeInfo().name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +158,15 @@ static void mergeSections(Section &target, std::unique_ptr<Section> &&other) {
|
|||||||
|
|
||||||
case SECTION_FRAGMENT:
|
case SECTION_FRAGMENT:
|
||||||
checkPieceCompat(target, *other, target.size);
|
checkPieceCompat(target, *other, target.size);
|
||||||
|
// Check that `target.size += other->size` below will not overflow
|
||||||
|
if (target.size + other->size > UINT16_MAX) {
|
||||||
|
fatalTwoAt(
|
||||||
|
target,
|
||||||
|
*other,
|
||||||
|
"Section \"%s\" fragments combined are larger than the GB address space",
|
||||||
|
target.name.c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
// Append `other` to `target`
|
// Append `other` to `target`
|
||||||
other->offset = target.size;
|
other->offset = target.size;
|
||||||
target.size += other->size;
|
target.size += other->size;
|
||||||
@@ -181,7 +202,7 @@ void sect_AddSection(std::unique_ptr<Section> &§ion) {
|
|||||||
fatal(
|
fatal(
|
||||||
"Section \"%s\" is of type `%s`, which cannot be `UNION`ized",
|
"Section \"%s\" is of type `%s`, which cannot be `UNION`ized",
|
||||||
section->name.c_str(),
|
section->name.c_str(),
|
||||||
sectionTypeInfo[section->type].name.c_str()
|
section->typeInfo().name
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
sections.add(section->name, std::move(section));
|
sections.add(section->name, std::move(section));
|
||||||
@@ -214,6 +235,7 @@ static void doSanityChecks(Section §ion) {
|
|||||||
bankModeError = true;
|
bankModeError = true;
|
||||||
} else {
|
} else {
|
||||||
section.type = SECTTYPE_ROM0;
|
section.type = SECTTYPE_ROM0;
|
||||||
|
section.isBankFixed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (options.isWRAM0Mode && section.type == SECTTYPE_WRAMX) {
|
if (options.isWRAM0Mode && section.type == SECTTYPE_WRAMX) {
|
||||||
@@ -226,6 +248,7 @@ static void doSanityChecks(Section §ion) {
|
|||||||
bankModeError = true;
|
bankModeError = true;
|
||||||
} else {
|
} else {
|
||||||
section.type = SECTTYPE_WRAM0;
|
section.type = SECTTYPE_WRAM0;
|
||||||
|
section.isBankFixed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (options.isDmgMode && section.type == SECTTYPE_VRAM && section.isBankFixed
|
if (options.isDmgMode && section.type == SECTTYPE_VRAM && section.isBankFixed
|
||||||
@@ -243,18 +266,20 @@ static void doSanityChecks(Section §ion) {
|
|||||||
section.isAlignFixed = false;
|
section.isAlignFixed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The section's type is determined now, so we can get its type info
|
||||||
|
SectionTypeInfo const &typeInfo = section.typeInfo();
|
||||||
|
|
||||||
// Too large an alignment may not be satisfiable
|
// Too large an alignment may not be satisfiable
|
||||||
if (section.isAlignFixed && (section.alignMask & sectionTypeInfo[section.type].startAddr)) {
|
if (section.isAlignFixed && (section.alignMask & typeInfo.startAddr) > section.alignOfs) {
|
||||||
error(
|
error(
|
||||||
"Section \"%s\" has type `%s`, which cannot be aligned to $%04x bytes",
|
"Section \"%s\" has type `%s`, which cannot be aligned to $%04x bytes",
|
||||||
section.name.c_str(),
|
section.name.c_str(),
|
||||||
sectionTypeInfo[section.type].name.c_str(),
|
typeInfo.name,
|
||||||
section.alignMask + 1
|
section.alignMask + 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t minbank = sectionTypeInfo[section.type].firstBank,
|
uint32_t minbank = typeInfo.firstBank, maxbank = typeInfo.lastBank;
|
||||||
maxbank = sectionTypeInfo[section.type].lastBank;
|
|
||||||
|
|
||||||
if (!bankModeError && section.isBankFixed
|
if (!bankModeError && section.isBankFixed
|
||||||
&& (section.bank < minbank || section.bank > maxbank)) {
|
&& (section.bank < minbank || section.bank > maxbank)) {
|
||||||
@@ -271,12 +296,12 @@ static void doSanityChecks(Section §ion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if section has a chance to be placed
|
// Check if section has a chance to be placed
|
||||||
if (section.size > sectionTypeInfo[section.type].size) {
|
if (section.size > typeInfo.size) {
|
||||||
error(
|
error(
|
||||||
"Section \"%s\" is bigger than the max size for that type: $%" PRIx16 " > $%" PRIx16,
|
"Section \"%s\" is bigger than the max size for that type: $%" PRIx16 " > $%" PRIx16,
|
||||||
section.name.c_str(),
|
section.name.c_str(),
|
||||||
section.size,
|
section.size,
|
||||||
sectionTypeInfo[section.type].size
|
typeInfo.size
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,24 +325,21 @@ static void doSanityChecks(Section §ion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the target address is valid
|
// Ensure the target address is valid
|
||||||
if (section.org < sectionTypeInfo[section.type].startAddr
|
if (section.org < typeInfo.startAddr || section.org > typeInfo.endAddr()) {
|
||||||
|| section.org > sectTypeEndAddr(section.type)) {
|
|
||||||
error(
|
error(
|
||||||
"Section \"%s\"'s fixed address $%04" PRIx16 " is outside of range [$%04" PRIx16
|
"Section \"%s\"'s fixed address $%04" PRIx16 " is outside of range [$%04" PRIx16
|
||||||
"; $%04" PRIx16 "]",
|
"; $%04" PRIx16 "]",
|
||||||
section.name.c_str(),
|
section.name.c_str(),
|
||||||
section.org,
|
section.org,
|
||||||
sectionTypeInfo[section.type].startAddr,
|
typeInfo.startAddr,
|
||||||
sectTypeEndAddr(section.type)
|
typeInfo.endAddr()
|
||||||
);
|
);
|
||||||
}
|
} else if (section.org + section.size > typeInfo.endAddr() + 1) {
|
||||||
|
|
||||||
if (section.org + section.size > sectTypeEndAddr(section.type) + 1) {
|
|
||||||
error(
|
error(
|
||||||
"Section \"%s\"'s end address $%04x is greater than last address $%04x",
|
"Section \"%s\"'s end address $%04x is greater than last address $%04x",
|
||||||
section.name.c_str(),
|
section.name.c_str(),
|
||||||
section.org + section.size,
|
section.org + section.size,
|
||||||
sectTypeEndAddr(section.type) + 1
|
typeInfo.endAddr() + 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-12
@@ -2,65 +2,61 @@
|
|||||||
|
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
using namespace std::literals;
|
|
||||||
|
|
||||||
// The default values are the most lax, as they are used as-is by RGBASM; only RGBLINK has the full
|
// The default values are the most lax, as they are used as-is by RGBASM; only RGBLINK has the full
|
||||||
// info, so RGBASM's job is only to catch unconditional errors earlier.
|
// info, so RGBASM's job is only to catch unconditional errors earlier.
|
||||||
// clang-format off: nested initializers
|
// clang-format off: nested initializers
|
||||||
SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID] = {
|
SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID] = {
|
||||||
{
|
{
|
||||||
.name = "WRAM0"s,
|
.name = "WRAM0",
|
||||||
.startAddr = 0xC000,
|
.startAddr = 0xC000,
|
||||||
.size = 0x2000, // Patched to 0x1000 if !isWRAM0Mode
|
.size = 0x2000, // Patched to 0x1000 if !isWRAM0Mode
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
.lastBank = 0,
|
.lastBank = 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "VRAM"s,
|
.name = "VRAM",
|
||||||
.startAddr = 0x8000,
|
.startAddr = 0x8000,
|
||||||
.size = 0x2000,
|
.size = 0x2000,
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
.lastBank = 1, // Patched to 0 if isDmgMode
|
.lastBank = 1, // Patched to 0 if isDmgMode
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "ROMX"s,
|
.name = "ROMX",
|
||||||
.startAddr = 0x4000,
|
.startAddr = 0x4000,
|
||||||
.size = 0x4000,
|
.size = 0x4000,
|
||||||
.firstBank = 1,
|
.firstBank = 1,
|
||||||
.lastBank = 65535,
|
.lastBank = 65535,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "ROM0"s,
|
.name = "ROM0",
|
||||||
.startAddr = 0x0000,
|
.startAddr = 0x0000,
|
||||||
.size = 0x8000, // Patched to 0x4000 if !is32kMode
|
.size = 0x8000, // Patched to 0x4000 if !is32kMode
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
.lastBank = 0,
|
.lastBank = 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "HRAM"s,
|
.name = "HRAM",
|
||||||
.startAddr = 0xFF80,
|
.startAddr = 0xFF80,
|
||||||
.size = 0x007F,
|
.size = 0x007F,
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
.lastBank = 0,
|
.lastBank = 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "WRAMX"s,
|
.name = "WRAMX",
|
||||||
.startAddr = 0xD000,
|
.startAddr = 0xD000,
|
||||||
.size = 0x1000,
|
.size = 0x1000,
|
||||||
.firstBank = 1,
|
.firstBank = 1,
|
||||||
.lastBank = 7,
|
.lastBank = 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "SRAM"s,
|
.name = "SRAM",
|
||||||
.startAddr = 0xA000,
|
.startAddr = 0xA000,
|
||||||
.size = 0x2000,
|
.size = 0x2000,
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
.lastBank = 255,
|
.lastBank = 255,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "OAM"s,
|
.name = "OAM",
|
||||||
.startAddr = 0xFE00,
|
.startAddr = 0xFE00,
|
||||||
.size = 0x00A0,
|
.size = 0x00A0,
|
||||||
.firstBank = 0,
|
.firstBank = 0,
|
||||||
|
|||||||
@@ -8,6 +8,23 @@
|
|||||||
|
|
||||||
#include "helpers.hpp" // assume, clz, ctz
|
#include "helpers.hpp" // assume, clz, ctz
|
||||||
|
|
||||||
|
// Signed overflow is UB, so these compute with unsigned arithmetic, which wraps around.
|
||||||
|
// Casting back is OK because the types implement two's complement behavior.
|
||||||
|
|
||||||
|
int32_t op_add(int32_t augend, int32_t addend) {
|
||||||
|
return static_cast<int32_t>(static_cast<uint32_t>(augend) + static_cast<uint32_t>(addend));
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t op_sub(int32_t minuend, int32_t subtrahend) {
|
||||||
|
return static_cast<int32_t>(static_cast<uint32_t>(minuend) - static_cast<uint32_t>(subtrahend));
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t op_mul(int32_t multiplicand, int32_t multiplier) {
|
||||||
|
return static_cast<int32_t>(
|
||||||
|
static_cast<uint32_t>(multiplicand) * static_cast<uint32_t>(multiplier)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
int32_t op_divide(int32_t dividend, int32_t divisor) {
|
int32_t op_divide(int32_t dividend, int32_t divisor) {
|
||||||
assume(divisor != 0); // Division by 0 is UB
|
assume(divisor != 0); // Division by 0 is UB
|
||||||
assume(dividend != INT32_MIN || divisor != -1); // INT32_MIN / -1 is UB
|
assume(dividend != INT32_MIN || divisor != -1); // INT32_MIN / -1 is UB
|
||||||
|
|||||||
+22
-8
@@ -4,23 +4,37 @@
|
|||||||
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
|
||||||
// We do not build `make develop` with `-fsanitize=leak` because macOS clang++ does not support it.
|
|
||||||
// Instead, we enable ASan (`-fsanitize=address`) to check for memory leaks in all four programs.
|
|
||||||
#ifdef __clang__
|
#ifdef __clang__
|
||||||
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
|
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
|
||||||
#define __SANITIZE_ADDRESS__
|
#define __SANITIZE_ADDRESS__
|
||||||
#endif
|
#endif
|
||||||
|
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_UNDEFINED__)
|
||||||
|
#define __SANITIZE_UNDEFINED__
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#if !defined(NDEBUG) && defined(__SANITIZE_ADDRESS__) && !defined(__APPLE__)
|
|
||||||
|
#if !defined(NDEBUG) && defined(__SANITIZE_ADDRESS__)
|
||||||
extern "C" {
|
extern "C" {
|
||||||
char const *__asan_default_options(void) {
|
char const *__asan_default_options(void) {
|
||||||
return "detect_leaks=1"
|
return ":check_initialization_order=1"
|
||||||
":detect_stack_use_after_return=1"
|
|
||||||
":detect_invalid_pointer_pairs=2"
|
":detect_invalid_pointer_pairs=2"
|
||||||
":check_initialization_order=1"
|
// `detect_leaks` is not supported on macOS.
|
||||||
|
#ifndef __APPLE__
|
||||||
|
":detect_leaks=1"
|
||||||
|
#endif
|
||||||
|
":detect_stack_use_after_return=1"
|
||||||
|
// ":fast_unwind_on_malloc=0" // Enable this if ASan outputs bad backtraces
|
||||||
|
":print_legend=0"
|
||||||
":strict_init_order=1"
|
":strict_init_order=1"
|
||||||
":strict_string_checks=1"
|
":strict_string_checks=1";
|
||||||
":print_legend=0";
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(NDEBUG) && defined(__SANITIZE_UNDEFINED__)
|
||||||
|
extern "C" {
|
||||||
|
char const *__ubsan_default_options(void) {
|
||||||
|
return "print_stacktrace=1";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
SECTION "test", ROM0
|
||||||
|
db 1, 2, 3
|
||||||
|
align 16, 2
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
error: Section already contains 3 bytes, higher than this aligned address $0002
|
||||||
|
at align-beyond-size.asm(3)
|
||||||
|
Assembly aborted with 1 error
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
assert x, "oops \0 null"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
FATAL: Assertion messages cannot contain '\0' characters
|
||||||
|
at assert-nul.asm(1)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
opt Q8
|
||||||
|
println strfmt("%f", 1.999)
|
||||||
|
println strfmt("%f", 2.999)
|
||||||
|
println strfmt("%f", 3.999)
|
||||||
|
println strfmt("%f", 16777215.999)
|
||||||
|
println strfmt("%f", 4294967295.999)
|
||||||
|
|
||||||
|
opt Q16
|
||||||
|
println strfmt("%f", 1.999999)
|
||||||
|
println strfmt("%f", 2.999999)
|
||||||
|
println strfmt("%f", 3.999999)
|
||||||
|
println strfmt("%f", 65535.999999)
|
||||||
|
println strfmt("%f", 4294967295.999999)
|
||||||
|
|
||||||
|
opt Q24
|
||||||
|
println strfmt("%f", 1.999999999)
|
||||||
|
println strfmt("%f", 2.999999999)
|
||||||
|
println strfmt("%f", 3.999999999)
|
||||||
|
println strfmt("%f", 255.999999999)
|
||||||
|
println strfmt("%f", 4294967295.999999999)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
warning: Magnitude of fixed-point constant is too large [-Wlarge-constant]
|
||||||
|
at fixed-point-carry.asm(5)
|
||||||
|
warning: Magnitude of fixed-point constant is too large [-Wlarge-constant]
|
||||||
|
at fixed-point-carry.asm(6)
|
||||||
|
warning: Magnitude of fixed-point constant is too large [-Wlarge-constant]
|
||||||
|
at fixed-point-carry.asm(12)
|
||||||
|
warning: Magnitude of fixed-point constant is too large [-Wlarge-constant]
|
||||||
|
at fixed-point-carry.asm(13)
|
||||||
|
warning: Magnitude of fixed-point constant is too large [-Wlarge-constant]
|
||||||
|
at fixed-point-carry.asm(19)
|
||||||
|
warning: Magnitude of fixed-point constant is too large [-Wlarge-constant]
|
||||||
|
at fixed-point-carry.asm(20)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
2.00000
|
||||||
|
3.00000
|
||||||
|
4.00000
|
||||||
|
0.00000
|
||||||
|
0.00000
|
||||||
|
2.00000
|
||||||
|
3.00000
|
||||||
|
4.00000
|
||||||
|
0.00000
|
||||||
|
0.00000
|
||||||
|
2.00000
|
||||||
|
3.00000
|
||||||
|
4.00000
|
||||||
|
0.00000
|
||||||
|
0.00000
|
||||||
@@ -5,9 +5,9 @@ $18B
|
|||||||
$62B85
|
$62B85
|
||||||
$4
|
$4
|
||||||
$10000
|
$10000
|
||||||
|
$20000
|
||||||
$10000
|
$10000
|
||||||
$10000
|
$20000
|
||||||
$10000
|
|
||||||
$C570A
|
$C570A
|
||||||
$13333
|
$13333
|
||||||
$13333
|
$13333
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
SECTION FRAGMENT "test", ROM0
|
||||||
|
ds $11, 42
|
||||||
|
|
||||||
|
SECTION FRAGMENT "test", ROM0[$10]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
FATAL: Section "test" already contains 17 bytes, higher than this fragment's fixed address $0010
|
||||||
|
at fragment-org-beyond-size.asm(4)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
SECTION "test", ROM0
|
||||||
|
LOAD "test", WRAM0
|
||||||
|
ENDL
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
FATAL: Section "test" already defined
|
||||||
|
at load-already-defined.asm(2)
|
||||||
|
and also:
|
||||||
|
at load-already-defined.asm(1)
|
||||||
@@ -12,9 +12,8 @@ error: Macro argument `\<2147483647>` not defined
|
|||||||
at negative-macro-args.asm::mac(11) <- negative-macro-args.asm(21)
|
at negative-macro-args.asm::mac(11) <- negative-macro-args.asm(21)
|
||||||
error: Macro argument `\<-2147483648>` not defined
|
error: Macro argument `\<-2147483648>` not defined
|
||||||
at negative-macro-args.asm::mac(14) <- negative-macro-args.asm(21)
|
at negative-macro-args.asm::mac(14) <- negative-macro-args.asm(21)
|
||||||
error: Number in bracketed macro argument is too large
|
error: Macro argument `\<-2147483648>` not defined
|
||||||
at negative-macro-args.asm::mac(14) <- negative-macro-args.asm(21)
|
at negative-macro-args.asm::mac(14) <- negative-macro-args.asm(21)
|
||||||
while expanding symbol `i`
|
|
||||||
error: Macro argument `\<-2147483648>` not defined
|
error: Macro argument `\<-2147483648>` not defined
|
||||||
at negative-macro-args.asm::mac(15) <- negative-macro-args.asm(21)
|
at negative-macro-args.asm::mac(15) <- negative-macro-args.asm(21)
|
||||||
error: Number in bracketed macro argument is too large
|
error: Number in bracketed macro argument is too large
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
0: ==
|
0: ==
|
||||||
-8: ==
|
-8: ==
|
||||||
2147483647: ==
|
2147483647: ==
|
||||||
-2147483648: == >
|
-2147483648: ==
|
||||||
2147483648: == >
|
2147483648: == >
|
||||||
-1: G == G
|
-1: G == G
|
||||||
4294967295: G == >
|
4294967295: G == >
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ error: Macro argument `\1` not defined
|
|||||||
at negative-shifted-macro-args.asm::test(15) <- negative-shifted-macro-args.asm(22)
|
at negative-shifted-macro-args.asm::test(15) <- negative-shifted-macro-args.asm(22)
|
||||||
error: Macro argument `\<-1>` not defined
|
error: Macro argument `\<-1>` not defined
|
||||||
at negative-shifted-macro-args.asm::test(16) <- negative-shifted-macro-args.asm(22)
|
at negative-shifted-macro-args.asm::test(16) <- negative-shifted-macro-args.asm(22)
|
||||||
error: Number in bracketed macro argument is too large
|
error: Macro argument `\<-2147483648>` not defined
|
||||||
at negative-shifted-macro-args.asm::test(19) <- negative-shifted-macro-args.asm(22)
|
at negative-shifted-macro-args.asm::test(19) <- negative-shifted-macro-args.asm(22)
|
||||||
error: syntax error, unexpected >
|
Assembly aborted with 8 errors
|
||||||
at negative-shifted-macro-args.asm::test(19) <- negative-shifted-macro-args.asm(22)
|
|
||||||
Assembly aborted with 9 errors
|
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ $0
|
|||||||
|
|
||||||
|
|
||||||
$A
|
$A
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
SECTION "test", ROM0
|
||||||
|
SECTION "test", WRAM0
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
FATAL: Section "test" already defined
|
||||||
|
at section-already-defined.asm(2)
|
||||||
|
and also:
|
||||||
|
at section-already-defined.asm(1)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
SECTION "test\0 foo", ROM0
|
||||||
|
SECTION "test\0 bar", ROM0
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
FATAL: Section names cannot contain '\0' characters
|
||||||
|
at section-name-nul.asm(1)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
MACRO m
|
||||||
|
println \1
|
||||||
|
shift $8000_0000 ; INT32_MIN
|
||||||
|
println \1
|
||||||
|
ENDM
|
||||||
|
m 1, 2, 3
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
warning: Cannot shift macro arguments past their beginning [-Wmacro-shift]
|
||||||
|
at shift-int-min.asm::m(3) <- shift-int-min.asm(6)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
$1
|
||||||
|
$1
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
section "test", rom0
|
||||||
|
|
||||||
|
opt Wtruncation=1
|
||||||
|
|
||||||
|
; good
|
||||||
|
ld hl, sp + 0
|
||||||
|
ld hl, sp + 127
|
||||||
|
ld hl, sp - 128
|
||||||
|
ld hl, sp + -128
|
||||||
|
add sp, 0
|
||||||
|
add sp, 127
|
||||||
|
add sp, -128
|
||||||
|
|
||||||
|
; bad
|
||||||
|
ld hl, sp + 128
|
||||||
|
ld hl, sp - 129
|
||||||
|
ld hl, sp + 255
|
||||||
|
ld hl, sp - 256
|
||||||
|
add sp, 128
|
||||||
|
add sp, -129
|
||||||
|
add sp, 255
|
||||||
|
add sp, -256
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
warning: Expression must be signed 8-bit [-Wtruncation]
|
||||||
|
at sp-signed-truncation.asm(15)
|
||||||
|
warning: Expression must be signed 8-bit [-Wtruncation]
|
||||||
|
at sp-signed-truncation.asm(16)
|
||||||
|
warning: Expression must be signed 8-bit [-Wtruncation]
|
||||||
|
at sp-signed-truncation.asm(17)
|
||||||
|
warning: Expression must be signed 8-bit [-Wtruncation]
|
||||||
|
at sp-signed-truncation.asm(18)
|
||||||
|
warning: Expression must be signed 8-bit [-Wtruncation]
|
||||||
|
at sp-signed-truncation.asm(19)
|
||||||
|
warning: Expression must be signed 8-bit [-Wtruncation]
|
||||||
|
at sp-signed-truncation.asm(20)
|
||||||
|
warning: Expression must be signed 8-bit [-Wtruncation]
|
||||||
|
at sp-signed-truncation.asm(21)
|
||||||
|
warning: Expression must be signed 8-bit [-Wtruncation]
|
||||||
|
at sp-signed-truncation.asm(22)
|
||||||
@@ -26,3 +26,7 @@ PRINTLN STRFMT("%d eol %", 1)
|
|||||||
PRINTLN STRFMT("invalid %w spec", 42)
|
PRINTLN STRFMT("invalid %w spec", 42)
|
||||||
|
|
||||||
PRINTLN STRFMT("one=%d two=%d three=%d", 1)
|
PRINTLN STRFMT("one=%d two=%d three=%d", 1)
|
||||||
|
|
||||||
|
DEF NUL EQUS STRFMT("%s \0 %s", "goodbye", "world")
|
||||||
|
ASSERT #NUL === "goodbye \0 world"
|
||||||
|
PRINTLN #NUL
|
||||||
|
|||||||
Binary file not shown.
+7
-3
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
export LC_ALL=C
|
export LC_ALL=C
|
||||||
|
|
||||||
|
# Screen width for help/usage text (for reproducible test results)
|
||||||
|
export COLUMNS=79
|
||||||
|
shopt -u checkwinsize # Prevent subsequent commands from resetting `COLUMNS`
|
||||||
|
|
||||||
# Game Boy release date, 1989-04-21T12:34:56Z (for reproducible test results)
|
# Game Boy release date, 1989-04-21T12:34:56Z (for reproducible test results)
|
||||||
export SOURCE_DATE_EPOCH=609165296
|
export SOURCE_DATE_EPOCH=609165296
|
||||||
|
|
||||||
@@ -86,9 +90,9 @@ for i in *.asm notexist.asm; do
|
|||||||
# "make-deps.asm" refers to its output filename in its desired output.
|
# "make-deps.asm" refers to its output filename in its desired output.
|
||||||
# "notexist.asm" doesn't exist, so there's no point in trying to `cat` it.
|
# "notexist.asm" doesn't exist, so there's no point in trying to `cat` it.
|
||||||
# Skip testing with stdin input for those files.
|
# Skip testing with stdin input for those files.
|
||||||
if [[ "$i" = include-recursion.asm || "$i" = make-deps.asm || "$i" = notexist.asm ]]; then
|
case "$i" in
|
||||||
continue
|
include-recursion.asm | make-deps.asm | notexist.asm) continue;;
|
||||||
fi
|
esac
|
||||||
|
|
||||||
# shellcheck disable=SC2002 # (This use of `cat` intentionally makes the input an unseekable pipe.)
|
# shellcheck disable=SC2002 # (This use of `cat` intentionally makes the input an unseekable pipe.)
|
||||||
cat "$i" | "$RGBASM" "${RGBASMFLAGS[@]}" -o "$o" - >"$output" 2>"$errput"
|
cat "$i" | "$RGBASM" "${RGBASMFLAGS[@]}" -o "$o" - >"$output" 2>"$errput"
|
||||||
|
|||||||
Vendored
+8
-4
@@ -69,12 +69,16 @@ case "$actionname" in
|
|||||||
|
|
||||||
action() {
|
action() {
|
||||||
if [ ! -d "$EXT_TEST_REPO" ]; then
|
if [ ! -d "$EXT_TEST_REPO" ]; then
|
||||||
git clone "https://$EXT_TEST_DOMAIN/$EXT_TEST_OWNER/$EXT_TEST_REPO.git" \
|
git clone "https://$EXT_TEST_DOMAIN/$EXT_TEST_OWNER/$EXT_TEST_REPO" \
|
||||||
--revision="$EXT_TEST_COMMIT" --depth=1 --recursive --shallow-submodules \
|
--revision="$EXT_TEST_COMMIT" --depth=1 --recursive --shallow-submodules \
|
||||||
--config advice.detachedHead=false
|
--jobs="$(getconf _NPROCESSORS_ONLN)" --config advice.detachedHead=false
|
||||||
|
pushd "$EXT_TEST_REPO"
|
||||||
|
else
|
||||||
|
pushd "$EXT_TEST_REPO"
|
||||||
|
git remote set-url origin "https://$EXT_TEST_DOMAIN/$EXT_TEST_OWNER/$EXT_TEST_REPO"
|
||||||
|
git fetch --depth=1 --recurse-submodules --jobs="$(getconf _NPROCESSORS_ONLN)" origin -- "$EXT_TEST_COMMIT"
|
||||||
|
git checkout --force --detach "$EXT_TEST_COMMIT" --
|
||||||
fi
|
fi
|
||||||
pushd "$EXT_TEST_REPO"
|
|
||||||
git checkout --force --detach "$EXT_TEST_COMMIT" --
|
|
||||||
if [ -f "../patches/$EXT_TEST_REPO.patch" ]; then
|
if [ -f "../patches/$EXT_TEST_REPO.patch" ]; then
|
||||||
git apply --ignore-whitespace "../patches/$EXT_TEST_REPO.patch"
|
git apply --ignore-whitespace "../patches/$EXT_TEST_REPO.patch"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
EXT_TEST_OWNER=ISSOtm
|
EXT_TEST_OWNER=ISSOtm
|
||||||
EXT_TEST_REPO=gb-starter-kit
|
EXT_TEST_REPO=gb-starter-kit
|
||||||
EXT_TEST_DOMAIN=codeberg.org
|
EXT_TEST_DOMAIN=codeberg.org
|
||||||
EXT_TEST_COMMIT=83162620eece547d9fb95c3e0b89d7a8812bcb7c
|
EXT_TEST_COMMIT=e3e395fdd6cdbfa168cf1b814d73c93ab167c7d8
|
||||||
EXT_TEST_IS_NONFREE=false
|
EXT_TEST_IS_NONFREE=false
|
||||||
|
|
||||||
EXT_TEST_TARGET=all
|
EXT_TEST_TARGET=all
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
EXT_TEST_OWNER=zladx
|
EXT_TEST_OWNER=zladx
|
||||||
EXT_TEST_REPO=LADX-Disassembly
|
EXT_TEST_REPO=LADX-Disassembly
|
||||||
EXT_TEST_DOMAIN=github.com
|
EXT_TEST_DOMAIN=github.com
|
||||||
EXT_TEST_COMMIT=76e9128437b90708ee27dce2d6c9752ad9f22f2d
|
EXT_TEST_COMMIT=968956783bd7ca4487a5712c46e98ae894d8dc0f
|
||||||
EXT_TEST_IS_NONFREE=true
|
EXT_TEST_IS_NONFREE=true
|
||||||
|
|
||||||
EXT_TEST_TARGET=default
|
EXT_TEST_TARGET=default
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
EXT_TEST_OWNER=pret
|
EXT_TEST_OWNER=pret
|
||||||
EXT_TEST_REPO=pokecrystal
|
EXT_TEST_REPO=pokecrystal
|
||||||
EXT_TEST_DOMAIN=github.com
|
EXT_TEST_DOMAIN=github.com
|
||||||
EXT_TEST_COMMIT=3438c7003a57fa2987fcb223d14b660761b33c64
|
EXT_TEST_COMMIT=7a7881d0d62e0ddbd82dcf10e7116807487ac651
|
||||||
EXT_TEST_IS_NONFREE=true
|
EXT_TEST_IS_NONFREE=true
|
||||||
|
|
||||||
EXT_TEST_TARGET=compare
|
EXT_TEST_TARGET=compare
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
EXT_TEST_OWNER=pret
|
EXT_TEST_OWNER=pret
|
||||||
EXT_TEST_REPO=pokered
|
EXT_TEST_REPO=pokered
|
||||||
EXT_TEST_DOMAIN=github.com
|
EXT_TEST_DOMAIN=github.com
|
||||||
EXT_TEST_COMMIT=405b6246372d7e5a2cb029cbb65219b13286b8c9
|
EXT_TEST_COMMIT=a1a22aaf84d1675bcdbaeb194592379d586d838e
|
||||||
EXT_TEST_IS_NONFREE=true
|
EXT_TEST_IS_NONFREE=true
|
||||||
|
|
||||||
EXT_TEST_TARGET=compare
|
EXT_TEST_TARGET=compare
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
export LC_ALL=C
|
export LC_ALL=C
|
||||||
|
|
||||||
|
# Screen width for help/usage text (for reproducible test results)
|
||||||
|
export COLUMNS=79
|
||||||
|
shopt -u checkwinsize # Prevent subsequent commands from resetting `COLUMNS`
|
||||||
|
|
||||||
tmpdir="$(mktemp -d)"
|
tmpdir="$(mktemp -d)"
|
||||||
# shellcheck disable=SC2064 # (Immediate expansion is the desired behavior.)
|
# shellcheck disable=SC2064 # (Immediate expansion is the desired behavior.)
|
||||||
trap "cd; rm -rf ${tmpdir@Q}" EXIT
|
trap "cd; rm -rf ${tmpdir@Q}" EXIT
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
error: Color #ff800080 is neither transparent (alpha < 16) nor opaque (alpha >= 240) (first seen at (0, 8))
|
error: Color #ff800080 is neither transparent (alpha < 16) nor opaque (alpha >= 240) (first seen at (0, 8))
|
||||||
Conversion aborted after 1 error
|
FATAL: Image contains 1 ambiguous color (neither transparent nor opaque)
|
||||||
|
Conversion aborted after 2 errors
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user