mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Compare commits
3 Commits
v0.6.0-rc2
...
v0.6.0-wel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bffe7eb4de | ||
|
|
cd454d2e9a | ||
|
|
c814a616d6 |
@@ -93,7 +93,7 @@ SpacesInConditionalStatement: false
|
|||||||
SpacesInContainerLiterals: false
|
SpacesInContainerLiterals: false
|
||||||
SpacesInParentheses: false
|
SpacesInParentheses: false
|
||||||
SpacesInSquareBrackets: false
|
SpacesInSquareBrackets: false
|
||||||
Standard: c++17
|
Standard: c++20
|
||||||
TabWidth: 4
|
TabWidth: 4
|
||||||
UseCRLF: false
|
UseCRLF: false
|
||||||
UseTab: ForIndentation
|
UseTab: AlignWithSpaces
|
||||||
|
|||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1 +1,3 @@
|
|||||||
|
github: avivace
|
||||||
|
patreon: gbdev01
|
||||||
open_collective: gbdev
|
open_collective: gbdev
|
||||||
|
|||||||
56
.github/actions/doc_postproc.awk
vendored
Executable file
56
.github/actions/doc_postproc.awk
vendored
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/awk -f
|
||||||
|
|
||||||
|
/^\s+<td><b class="Sy">.+<\/b><\/td>$/ {
|
||||||
|
# Assuming that all cells whose contents are bold are heading cells,
|
||||||
|
# use the HTML tag for those
|
||||||
|
sub(/td><b class="Sy"/, "th");
|
||||||
|
sub(/b><\/td/, "th");
|
||||||
|
}
|
||||||
|
|
||||||
|
# The whole page is being generated, so it's not meant to contain any Liquid
|
||||||
|
BEGIN {
|
||||||
|
print "{% raw %}"
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
print "{% endraw %}"
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
in_synopsis = 0
|
||||||
|
}
|
||||||
|
/<table class="Nm">/ {
|
||||||
|
in_synopsis = 1
|
||||||
|
}
|
||||||
|
/<\/table>/ {
|
||||||
|
# Resets synopsis state even when already reset, but whatever
|
||||||
|
in_synopsis = 0
|
||||||
|
}
|
||||||
|
/<code class="Fl">-[a-zA-Z]/ {
|
||||||
|
# Add links to arg descr in synopsis section
|
||||||
|
if (in_synopsis) {
|
||||||
|
while (match($0, /<code class="Fl">-[a-zA-Z]+/)) {
|
||||||
|
# 123456789012345678 -> 18 chars
|
||||||
|
optchars = substr($0, RSTART + 18, RLENGTH - 18)
|
||||||
|
i = length(optchars)
|
||||||
|
while (i) {
|
||||||
|
end = RSTART + 18 + i
|
||||||
|
i -= 1
|
||||||
|
len = i ? 1 : 2
|
||||||
|
$0 = sprintf("%s<a href=\"#%s\">%s</a>%s",
|
||||||
|
substr($0, 0, end - len - 1),
|
||||||
|
substr($0, end - 1, 1),
|
||||||
|
substr($0, end - len, len),
|
||||||
|
substr($0, end))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
# Make long opts (defined using `Fl Fl`) into a single tag
|
||||||
|
gsub(/<code class="Fl">-<\/code>\s*<code class="Fl">/, "<code class=\"Fl\">-")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
print
|
||||||
|
}
|
||||||
113
.github/actions/get-pages.sh
vendored
Executable file
113
.github/actions/get-pages.sh
vendored
Executable file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $0 [-h] [-r] <rgbds-www> <version>
|
||||||
|
Copy renders from RGBDS repository to rgbds-www documentation
|
||||||
|
Execute from the root folder of the RGBDS repo, checked out at the desired tag
|
||||||
|
<rgbds-www> : Path to the rgbds-www repository
|
||||||
|
<version> : Version to be copied, such as 'v0.4.1' or 'master'
|
||||||
|
|
||||||
|
-h Display this help message
|
||||||
|
-r Update "latest stable" redirection pages and add a new entry to the index
|
||||||
|
(use for releases, not master)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
is_release=0
|
||||||
|
bad_usage=0
|
||||||
|
while getopts ":hr" opt; do
|
||||||
|
case $opt in
|
||||||
|
r)
|
||||||
|
is_release=1
|
||||||
|
;;
|
||||||
|
h)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
\?)
|
||||||
|
echo "Unknown option '$OPTARG'"
|
||||||
|
bad_usage=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if [ $bad_usage -ne 0 ]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shift $(($OPTIND - 1))
|
||||||
|
|
||||||
|
|
||||||
|
declare -A PAGES
|
||||||
|
PAGES=(
|
||||||
|
[rgbasm.1.html]=src/asm/rgbasm.1
|
||||||
|
[rgbasm.5.html]=src/asm/rgbasm.5
|
||||||
|
[rgblink.1.html]=src/link/rgblink.1
|
||||||
|
[rgblink.5.html]=src/link/rgblink.5
|
||||||
|
[rgbfix.1.html]=src/fix/rgbfix.1
|
||||||
|
[rgbgfx.1.html]=src/gfx/rgbgfx.1
|
||||||
|
[rgbds.5.html]=src/rgbds.5
|
||||||
|
[rgbds.7.html]=src/rgbds.7
|
||||||
|
[gbz80.7.html]=src/gbz80.7
|
||||||
|
)
|
||||||
|
WWWPATH="/docs"
|
||||||
|
mkdir -p "$1/_documentation/$2"
|
||||||
|
|
||||||
|
# `mandoc` uses a different format for referring to man pages present in the **current** directory.
|
||||||
|
# We want that format for RGBDS man pages, and the other one for the rest;
|
||||||
|
# we thus need to copy all pages to a temporary directory, and process them there.
|
||||||
|
|
||||||
|
# Copy all pages to current dir
|
||||||
|
cp "${PAGES[@]}" .
|
||||||
|
|
||||||
|
for page in "${!PAGES[@]}"; do
|
||||||
|
stem="${page%.html}"
|
||||||
|
manpage="${stem%.?}(${stem#*.})"
|
||||||
|
descr="$(awk -v 'FS=.Nd ' '/.Nd/ { print $2; }' "${PAGES[$page]}")"
|
||||||
|
|
||||||
|
cat >"$1/_documentation/$2/$page" <<EOF
|
||||||
|
---
|
||||||
|
layout: doc
|
||||||
|
title: $manpage [$2]
|
||||||
|
description: RGBDS $2 — $descr
|
||||||
|
---
|
||||||
|
EOF
|
||||||
|
options=fragment,man='%N.%S;https://linux.die.net/man/%S/%N'
|
||||||
|
if [ $stem = rgbasm.5 ]; then
|
||||||
|
options+=,toc
|
||||||
|
fi
|
||||||
|
mandoc -Thtml -I os=Linux -O$options "${PAGES[$page]##*/}" | .github/actions/doc_postproc.awk >> "$1/_documentation/$2/$page"
|
||||||
|
groff -Tpdf -mdoc -wall "${PAGES[$page]##*/}" >"$1/_documentation/$2/$stem.pdf"
|
||||||
|
if [ $is_release -ne 0 ]; then
|
||||||
|
cat - >"$1/_documentation/$page" <<EOF
|
||||||
|
---
|
||||||
|
redirect_to: $WWWPATH/$2/${page%.html}
|
||||||
|
permalink: $WWWPATH/${page%.html}/
|
||||||
|
title: $manpage [latest stable]
|
||||||
|
description: RGBDS latest stable — $descr
|
||||||
|
---
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
cat - >"$1/_documentation/$2/index.html" <<EOF
|
||||||
|
---
|
||||||
|
layout: doc_index
|
||||||
|
permalink: /docs/$2/
|
||||||
|
title: RGBDS online manual [$2]
|
||||||
|
description: RGBDS $2 - Online manual
|
||||||
|
---
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
# If making a release, add a new entry right after `master`
|
||||||
|
if [ $is_release -ne 0 ]; then
|
||||||
|
awk '{ print }
|
||||||
|
/"name": "master"/ { print "\t\t{\"name\": \"'$2'\", \"text\": \"'$2'\" }," }
|
||||||
|
' "$1/_data/doc.json" >"$1/_data/doc.json.tmp"
|
||||||
|
mv "$1/_data/doc.json"{.tmp,}
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm "${PAGES[@]##*/}"
|
||||||
10
.github/workflows/create-release-docs.yml
vendored
10
.github/workflows/create-release-docs.yml
vendored
@@ -23,16 +23,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get -qq update
|
sudo apt-get -qq update
|
||||||
sudo apt-get install -yq groff zlib1g-dev
|
sudo apt-get install -yq groff zlib1g-dev
|
||||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
|
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
|
||||||
tar xf mandoc-1.14.6.tar.gz
|
tar xf mandoc-1.14.5.tar.gz
|
||||||
cd mandoc-1.14.6
|
cd mandoc-1.14.5
|
||||||
./configure
|
./configure
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
- name: Update pages
|
- name: Update pages
|
||||||
working-directory: rgbds/man
|
working-directory: rgbds
|
||||||
run: | # The ref appears to be in the format "refs/tags/<version>", so strip that
|
run: | # The ref appears to be in the format "refs/tags/<version>", so strip that
|
||||||
../../rgbds-www/.github/actions/get-pages.sh ${GITHUB_REF##*/} *
|
./.github/actions/get-pages.sh -r ../rgbds-www ${GITHUB_REF##*/}
|
||||||
- name: Push new pages
|
- name: Push new pages
|
||||||
working-directory: rgbds-www
|
working-directory: rgbds-www
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
64
.github/workflows/testing.yml
vendored
64
.github/workflows/testing.yml
vendored
@@ -16,11 +16,6 @@ jobs:
|
|||||||
cc: gcc
|
cc: gcc
|
||||||
- os: macos-11.0
|
- os: macos-11.0
|
||||||
cc: gcc
|
cc: gcc
|
||||||
include:
|
|
||||||
- cc: gcc
|
|
||||||
cxx: g++
|
|
||||||
- cc: clang
|
|
||||||
cxx: clang++
|
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
@@ -33,25 +28,25 @@ jobs:
|
|||||||
# Apple's base version is severely outdated, not even supporting -Wall,
|
# Apple's base version is severely outdated, not even supporting -Wall,
|
||||||
# but it overrides Homebrew's version nonetheless...
|
# but it overrides Homebrew's version nonetheless...
|
||||||
- name: Build & install using Make
|
- name: Build & install using Make
|
||||||
if: matrix.buildsys == 'make'
|
|
||||||
run: |
|
run: |
|
||||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||||
make develop -j Q= CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
|
make develop -j Q= CC=${{ matrix.cc }}
|
||||||
sudo make install -j Q=
|
sudo make install -j Q=
|
||||||
|
if: matrix.buildsys == 'make'
|
||||||
- name: Build & install using CMake
|
- name: Build & install using CMake
|
||||||
if: matrix.buildsys == 'cmake'
|
|
||||||
run: |
|
run: |
|
||||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
|
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${{ matrix.cc }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
|
||||||
cmake --build build -j --verbose
|
cmake --build build -j
|
||||||
cp build/src/rgb{asm,link,fix,gfx} .
|
cp build/src/rgb{asm,link,fix,gfx} .
|
||||||
sudo cmake --install build --verbose
|
sudo cmake --install build
|
||||||
|
if: matrix.buildsys == 'cmake'
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
run: |
|
run: |
|
||||||
mkdir bins
|
mkdir bins
|
||||||
cp rgb{asm,link,fix,gfx} bins
|
cp rgb{asm,link,fix,gfx} bins
|
||||||
- name: Upload binaries
|
- name: Upload binaries
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: rgbds-canary-${{ matrix.os }}-${{ matrix.cc }}-${{ matrix.buildsys }}
|
name: rgbds-canary-${{ matrix.os }}-${{ matrix.cc }}-${{ matrix.buildsys }}
|
||||||
path: bins
|
path: bins
|
||||||
@@ -76,7 +71,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Get zlib, libpng and bison
|
- name: Get zlib, libpng and bison
|
||||||
run: | # TODO: use an array; remember to update the versions being downloaded, *and* the paths being extracted! (`Move-Item`)
|
run: | # TODO: use an array
|
||||||
$wc = New-Object System.Net.WebClient
|
$wc = New-Object System.Net.WebClient
|
||||||
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
|
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
|
||||||
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
|
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
|
||||||
@@ -100,41 +95,28 @@ jobs:
|
|||||||
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
|
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
|
||||||
Move-Item zlib-1.2.12 zlib
|
Move-Item zlib-1.2.12 zlib
|
||||||
Move-Item lpng1637 libpng
|
Move-Item lpng1637 libpng
|
||||||
- uses: actions/cache@v3
|
|
||||||
id: cache
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
zbuild
|
|
||||||
pngbuild
|
|
||||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
|
||||||
- name: Build zlib
|
- name: Build zlib
|
||||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||||
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||||
cmake --build zbuild --config Release -j
|
cmake --build zbuild --config Release -j
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
- name: Install zlib
|
|
||||||
run: |
|
|
||||||
cmake --install zbuild
|
cmake --install zbuild
|
||||||
- name: Build libpng
|
- name: Build libpng
|
||||||
run: |
|
run: |
|
||||||
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
|
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
|
||||||
cmake --build pngbuild --config Release -j
|
cmake --build pngbuild --config Release -j
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
- name: Install libpng
|
|
||||||
run: |
|
|
||||||
cmake --install pngbuild
|
cmake --install pngbuild
|
||||||
- name: Build Windows binaries
|
- name: Build Windows binaries
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
||||||
cmake --build build --config Release -j --verbose
|
cmake --build build --config Release -j
|
||||||
cmake --install build --verbose --prefix install_dir
|
cmake --install build
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
mkdir bins
|
mkdir bins
|
||||||
cp install_dir/bin/{rgbasm.exe,rgblink.exe,rgbfix.exe,rgbgfx.exe,zlib1.dll,libpng16.dll} bins
|
cp install_dir/bin/{rgbasm.exe,rgblink.exe,rgbfix.exe,rgbgfx.exe,zlib1.dll,libpng16.dll} bins
|
||||||
- name: Upload Windows binaries
|
- name: Upload Windows binaries
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: rgbds-canary-win${{ matrix.bits }}
|
name: rgbds-canary-win${{ matrix.bits }}
|
||||||
path: bins
|
path: bins
|
||||||
@@ -142,7 +124,6 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cp bins/* .
|
cp bins/* .
|
||||||
cp bins/*.dll test/gfx
|
|
||||||
test/run-tests.sh
|
test/run-tests.sh
|
||||||
|
|
||||||
windows-xbuild:
|
windows-xbuild:
|
||||||
@@ -169,7 +150,7 @@ jobs:
|
|||||||
./.github/actions/install_deps.sh ${{ matrix.os }}
|
./.github/actions/install_deps.sh ${{ matrix.os }}
|
||||||
- name: Install MinGW
|
- name: Install MinGW
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install {gcc,g++}-mingw-w64-${{ matrix.arch }} mingw-w64-tools libz-mingw-w64-dev
|
sudo apt-get install gcc-mingw-w64-${{ matrix.arch }} mingw-w64-tools libz-mingw-w64-dev
|
||||||
- name: Install libpng dev headers for MinGW
|
- name: Install libpng dev headers for MinGW
|
||||||
run: |
|
run: |
|
||||||
sudo ./.github/actions/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
|
sudo ./.github/actions/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
|
||||||
@@ -185,21 +166,12 @@ jobs:
|
|||||||
mv rgbgfx bins/rgbgfx.exe
|
mv rgbgfx bins/rgbgfx.exe
|
||||||
cp /usr/${{ matrix.triplet }}/lib/zlib1.dll bins
|
cp /usr/${{ matrix.triplet }}/lib/zlib1.dll bins
|
||||||
cp /usr/${{ matrix.triplet }}/bin/libpng16-16.dll bins
|
cp /usr/${{ matrix.triplet }}/bin/libpng16-16.dll bins
|
||||||
if [ ${{ matrix.bits }} -eq 32 ]; then cp /usr/lib/gcc/${{ matrix.triplet }}/7.3-win32/lib{gcc_s_sjlj-1,stdc++-6}.dll bins; fi
|
if [ ${{ matrix.bits }} -eq 32 ]; then cp /usr/lib/gcc/${{ matrix.triplet }}/7.3-win32/libgcc_s_sjlj-1.dll bins; fi
|
||||||
mv test/gfx/randtilegen{,.exe}
|
|
||||||
mv test/gfx/rgbgfx_test{,.exe}
|
|
||||||
- name: Upload Windows binaries
|
- name: Upload Windows binaries
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
||||||
path: bins
|
path: bins
|
||||||
- name: Upload Windows test binaries
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: testing-programs-mingw-win${{ matrix.bits }}
|
|
||||||
path: |
|
|
||||||
test/gfx/randtilegen.exe
|
|
||||||
test/gfx/rgbgfx_test.exe
|
|
||||||
|
|
||||||
windows-xtesting:
|
windows-xtesting:
|
||||||
needs: windows-xbuild
|
needs: windows-xbuild
|
||||||
@@ -211,20 +183,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Retrieve binaries
|
- name: Retrieve binaries
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
||||||
path: bins
|
path: bins
|
||||||
- name: Retrieve test binaries
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: testing-programs-mingw-win${{ matrix.bits }}
|
|
||||||
path: test/gfx
|
|
||||||
- name: Extract binaries
|
- name: Extract binaries
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cp bins/* .
|
cp bins/* .
|
||||||
cp bins/*.dll test/gfx
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
31
.github/workflows/update-master-docs.yml
vendored
31
.github/workflows/update-master-docs.yml
vendored
@@ -4,15 +4,16 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
paths:
|
paths:
|
||||||
- man/gbz80.7
|
- .github/actions/get-pages.sh
|
||||||
- man/rgbds.5
|
- src/gbz80.7
|
||||||
- man/rgbds.7
|
- src/rgbds.5
|
||||||
- man/rgbasm.1
|
- src/rgbds.7
|
||||||
- man/rgbasm.5
|
- src/asm/rgbasm.1
|
||||||
- man/rgblink.1
|
- src/asm/rgbasm.5
|
||||||
- man/rgblink.5
|
- src/link/rgblink.1
|
||||||
- man/rgbfix.1
|
- src/link/rgblink.5
|
||||||
- man/rgbgfx.1
|
- src/fix/rgbfix.1
|
||||||
|
- src/gfx/rgbgfx.1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -35,16 +36,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get -qq update
|
sudo apt-get -qq update
|
||||||
sudo apt-get install -yq groff zlib1g-dev
|
sudo apt-get install -yq groff zlib1g-dev
|
||||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
|
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
|
||||||
tar xf mandoc-1.14.6.tar.gz
|
tar xf mandoc-1.14.5.tar.gz
|
||||||
cd mandoc-1.14.6
|
cd mandoc-1.14.5
|
||||||
./configure
|
./configure
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
- name: Update pages
|
- name: Update pages
|
||||||
working-directory: rgbds/man
|
working-directory: rgbds
|
||||||
run: |
|
run: |
|
||||||
../../rgbds-www/maintainer/man_to_html.sh master *
|
./.github/actions/get-pages.sh ../rgbds-www master
|
||||||
- name: Push new pages
|
- name: Push new pages
|
||||||
working-directory: rgbds-www
|
working-directory: rgbds-www
|
||||||
run: |
|
run: |
|
||||||
@@ -55,7 +56,7 @@ jobs:
|
|||||||
ssh-add ~/.ssh/id_ed25519
|
ssh-add ~/.ssh/id_ed25519
|
||||||
git config --global user.name "GitHub Action"
|
git config --global user.name "GitHub Action"
|
||||||
git config --global user.email "community@gbdev.io"
|
git config --global user.email "community@gbdev.io"
|
||||||
git add -A
|
git add .
|
||||||
git commit -m "Update RGBDS master documentation"
|
git commit -m "Update RGBDS master documentation"
|
||||||
if git remote | grep -q origin; then
|
if git remote | grep -q origin; then
|
||||||
git remote set-url origin git@github.com:gbdev/rgbds-www.git
|
git remote set-url origin git@github.com:gbdev/rgbds-www.git
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
||||||
|
|
||||||
project(rgbds
|
project(rgbds
|
||||||
LANGUAGES C CXX)
|
LANGUAGES C)
|
||||||
|
|
||||||
# get real path of source and binary directories
|
# get real path of source and binary directories
|
||||||
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
|
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
|
||||||
@@ -29,18 +29,8 @@ option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC
|
|||||||
if(MSVC)
|
if(MSVC)
|
||||||
# MSVC's standard library triggers warning C5105,
|
# MSVC's standard library triggers warning C5105,
|
||||||
# "macro expansion producing 'defined' has undefined behavior"
|
# "macro expansion producing 'defined' has undefined behavior"
|
||||||
add_compile_options(/MP /wd5105)
|
add_compile_options(/std:c11 /W1 /MP /wd5105)
|
||||||
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
|
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
|
||||||
# Also, CMake appears not to pass the C11-enabling flag, so we must add it manually... but only for C!
|
|
||||||
if(NOT CMAKE_C_FLAGS MATCHES "std:c11") # The flag may already have been injected by an earlier CMake invocation, so don't add it twice
|
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std:c11" CACHE STRING "Flags used by the C compiler during all build types." FORCE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(SANITIZERS)
|
|
||||||
set(SAN_FLAGS /fsanitize=address)
|
|
||||||
add_compile_options(${SAN_FLAGS})
|
|
||||||
add_link_options(${SAN_FLAGS})
|
|
||||||
endif()
|
|
||||||
else()
|
else()
|
||||||
add_compile_options(-Wall -pedantic)
|
add_compile_options(-Wall -pedantic)
|
||||||
add_definitions(-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE)
|
add_definitions(-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE)
|
||||||
@@ -51,18 +41,18 @@ else()
|
|||||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
|
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
|
||||||
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
||||||
add_compile_options(${SAN_FLAGS})
|
add_compile_options(${SAN_FLAGS})
|
||||||
add_link_options(${SAN_FLAGS})
|
link_libraries(${SAN_FLAGS})
|
||||||
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
||||||
# TODO: this overrides anything previously set... that's a bit sloppy!
|
# TODO: this overrides anything previously set... that's a bit sloppy!
|
||||||
set(CMAKE_C_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
set(CMAKE_C_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(MORE_WARNINGS)
|
if(MORE_WARNINGS)
|
||||||
add_compile_options(-Werror -Wextra
|
add_compile_options(-Werror -Wextra
|
||||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2
|
-Wfloat-equal -Winline -Wlogical-op -Wnested-externs -Wnull-dereference
|
||||||
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused
|
-Wold-style-definition -Wshift-overflow=2 -Wstrict-overflow=5
|
||||||
|
-Wstrict-prototypes -Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
||||||
-Wshadow # TODO: -Wshadow=compatible-local ?
|
-Wshadow # TODO: -Wshadow=compatible-local ?
|
||||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||||
-Wno-format-nonliteral # We have a couple of "dynamic" prints
|
-Wno-format-nonliteral # We have a couple of "dynamic" prints
|
||||||
@@ -87,23 +77,12 @@ else(GIT)
|
|||||||
message(STATUS "Cannot determine RGBDS version (Git not installed), falling back")
|
message(STATUS "Cannot determine RGBDS version (Git not installed), falling back")
|
||||||
endif(GIT)
|
endif(GIT)
|
||||||
|
|
||||||
find_package(PkgConfig)
|
|
||||||
if(MSVC OR NOT PKG_CONFIG_FOUND)
|
|
||||||
# fallback to find_package
|
|
||||||
find_package(PNG REQUIRED)
|
|
||||||
else()
|
|
||||||
pkg_check_modules(LIBPNG REQUIRED libpng)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include_directories("${PROJECT_SOURCE_DIR}/include")
|
include_directories("${PROJECT_SOURCE_DIR}/include")
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED True)
|
set(CMAKE_C_STANDARD_REQUIRED True)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
add_subdirectory(test)
|
|
||||||
|
|
||||||
# By default, build in Release mode; Debug mode must be explicitly requested
|
# By default, build in Release mode; Debug mode must be explicitly requested
|
||||||
# (You may want to augment it with the options above)
|
# (You may want to augment it with the options above)
|
||||||
@@ -118,19 +97,3 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
|||||||
message(CHECK_FAIL "no")
|
message(CHECK_FAIL "no")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(MANDIR "share/man")
|
|
||||||
set(man1 "man/rgbasm.1"
|
|
||||||
"man/rgbfix.1"
|
|
||||||
"man/rgbgfx.1"
|
|
||||||
"man/rgblink.1")
|
|
||||||
set(man5 "man/rgbasm.5"
|
|
||||||
"man/rgblink.5"
|
|
||||||
"man/rgbds.5")
|
|
||||||
set(man7 "man/gbz80.7"
|
|
||||||
"man/rgbds.7")
|
|
||||||
|
|
||||||
foreach(SECTION "man1" "man5" "man7")
|
|
||||||
set(DEST "${MANDIR}/${SECTION}")
|
|
||||||
install(FILES ${${SECTION}} DESTINATION ${DEST})
|
|
||||||
endforeach()
|
|
||||||
|
|||||||
66
Makefile
66
Makefile
@@ -7,7 +7,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
.SUFFIXES:
|
.SUFFIXES:
|
||||||
.SUFFIXES: .h .y .c .cpp .o
|
.SUFFIXES: .h .y .c .o
|
||||||
|
|
||||||
# User-defined variables
|
# User-defined variables
|
||||||
|
|
||||||
@@ -34,13 +34,10 @@ WARNFLAGS := -Wall -pedantic
|
|||||||
|
|
||||||
# Overridable CFLAGS
|
# Overridable CFLAGS
|
||||||
CFLAGS ?= -O3 -flto -DNDEBUG
|
CFLAGS ?= -O3 -flto -DNDEBUG
|
||||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
|
||||||
# Non-overridable CFLAGS
|
# Non-overridable CFLAGS
|
||||||
# _ISOC11_SOURCE is required on certain platforms to get C11 on top of the C99-based POSIX 2008
|
# _ISOC11_SOURCE is required on certain platforms to get C11 on top of the C99-based POSIX 2008
|
||||||
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -I include \
|
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -I include \
|
||||||
-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE
|
-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE
|
||||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++17 -I include \
|
|
||||||
-D_POSIX_C_SOURCE=200809L -fno-exceptions -fno-rtti
|
|
||||||
# Overridable LDFLAGS
|
# Overridable LDFLAGS
|
||||||
LDFLAGS ?=
|
LDFLAGS ?=
|
||||||
# Non-overridable LDFLAGS
|
# Non-overridable LDFLAGS
|
||||||
@@ -91,7 +88,6 @@ rgblink_obj := \
|
|||||||
src/link/output.o \
|
src/link/output.o \
|
||||||
src/link/patch.o \
|
src/link/patch.o \
|
||||||
src/link/script.o \
|
src/link/script.o \
|
||||||
src/link/sdas_obj.o \
|
|
||||||
src/link/section.o \
|
src/link/section.o \
|
||||||
src/link/symbol.o \
|
src/link/symbol.o \
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
@@ -106,14 +102,9 @@ rgbfix_obj := \
|
|||||||
src/error.o
|
src/error.o
|
||||||
|
|
||||||
rgbgfx_obj := \
|
rgbgfx_obj := \
|
||||||
|
src/gfx/gb.o \
|
||||||
src/gfx/main.o \
|
src/gfx/main.o \
|
||||||
src/gfx/pal_packing.o \
|
src/gfx/makepng.o \
|
||||||
src/gfx/pal_sorting.o \
|
|
||||||
src/gfx/pal_spec.o \
|
|
||||||
src/gfx/process.o \
|
|
||||||
src/gfx/proto_palette.o \
|
|
||||||
src/gfx/reverse.o \
|
|
||||||
src/gfx/rgba.o \
|
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
src/error.o
|
src/error.o
|
||||||
|
|
||||||
@@ -127,13 +118,7 @@ rgbfix: ${rgbfix_obj}
|
|||||||
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCFLAGS} src/version.c
|
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCFLAGS} src/version.c
|
||||||
|
|
||||||
rgbgfx: ${rgbgfx_obj}
|
rgbgfx: ${rgbgfx_obj}
|
||||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} -x c++ src/version.c ${PNGLDLIBS}
|
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCFLAGS} src/version.c ${PNGLDLIBS}
|
||||||
|
|
||||||
test/gfx/randtilegen: test/gfx/randtilegen.c
|
|
||||||
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
|
|
||||||
|
|
||||||
test/gfx/rgbgfx_test: test/gfx/rgbgfx_test.cpp
|
|
||||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGLDLIBS}
|
|
||||||
|
|
||||||
# Rules to process files
|
# Rules to process files
|
||||||
|
|
||||||
@@ -160,10 +145,7 @@ src/asm/parser.c: src/asm/parser.y
|
|||||||
${BISON} $$DEFS -d ${YFLAGS} -o $@ $<
|
${BISON} $$DEFS -d ${YFLAGS} -o $@ $<
|
||||||
|
|
||||||
.c.o:
|
.c.o:
|
||||||
$Q${CC} ${REALCFLAGS} -c -o $@ $<
|
$Q${CC} ${REALCFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
|
|
||||||
.cpp.o:
|
|
||||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
|
||||||
|
|
||||||
# Target used to remove all files generated by other Makefile targets
|
# Target used to remove all files generated by other Makefile targets
|
||||||
|
|
||||||
@@ -175,7 +157,6 @@ clean:
|
|||||||
$Qfind src/ -name "*.o" -exec rm {} \;
|
$Qfind src/ -name "*.o" -exec rm {} \;
|
||||||
$Q${RM} rgbshim.sh
|
$Q${RM} rgbshim.sh
|
||||||
$Q${RM} src/asm/parser.c src/asm/parser.h
|
$Q${RM} src/asm/parser.c src/asm/parser.h
|
||||||
$Q${RM} test/gfx/randtilegen test/gfx/rgbgfx_test
|
|
||||||
|
|
||||||
# Target used to install the binaries and man pages.
|
# Target used to install the binaries and man pages.
|
||||||
|
|
||||||
@@ -186,15 +167,15 @@ install: all
|
|||||||
$Qinstall ${STRIP} -m ${BINMODE} rgblink ${DESTDIR}${bindir}/rgblink
|
$Qinstall ${STRIP} -m ${BINMODE} rgblink ${DESTDIR}${bindir}/rgblink
|
||||||
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx
|
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx
|
||||||
$Qmkdir -p ${DESTDIR}${mandir}/man1 ${DESTDIR}${mandir}/man5 ${DESTDIR}${mandir}/man7
|
$Qmkdir -p ${DESTDIR}${mandir}/man1 ${DESTDIR}${mandir}/man5 ${DESTDIR}${mandir}/man7
|
||||||
$Qinstall -m ${MANMODE} man/rgbds.7 ${DESTDIR}${mandir}/man7/rgbds.7
|
$Qinstall -m ${MANMODE} src/rgbds.7 ${DESTDIR}${mandir}/man7/rgbds.7
|
||||||
$Qinstall -m ${MANMODE} man/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
|
$Qinstall -m ${MANMODE} src/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
|
||||||
$Qinstall -m ${MANMODE} man/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
|
$Qinstall -m ${MANMODE} src/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
|
||||||
$Qinstall -m ${MANMODE} man/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
|
$Qinstall -m ${MANMODE} src/asm/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
|
||||||
$Qinstall -m ${MANMODE} man/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
|
$Qinstall -m ${MANMODE} src/asm/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
|
||||||
$Qinstall -m ${MANMODE} man/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
|
$Qinstall -m ${MANMODE} src/fix/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
|
||||||
$Qinstall -m ${MANMODE} man/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
|
$Qinstall -m ${MANMODE} src/link/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
|
||||||
$Qinstall -m ${MANMODE} man/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
|
$Qinstall -m ${MANMODE} src/link/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
|
||||||
$Qinstall -m ${MANMODE} man/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
|
$Qinstall -m ${MANMODE} src/gfx/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
|
||||||
|
|
||||||
# Target used to check the coding style of the whole codebase.
|
# Target used to check the coding style of the whole codebase.
|
||||||
# `extern/` is excluded, as it contains external code that should not be patched
|
# `extern/` is excluded, as it contains external code that should not be patched
|
||||||
@@ -233,9 +214,11 @@ checkdiff:
|
|||||||
develop:
|
develop:
|
||||||
$Qenv ${MAKE} WARNFLAGS="-Werror -Wextra \
|
$Qenv ${MAKE} WARNFLAGS="-Werror -Wextra \
|
||||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2 \
|
-Wfloat-equal -Winline -Wlogical-op -Wnested-externs -Wold-style-definition \
|
||||||
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused \
|
-Wshift-overflow=2 \
|
||||||
|
-Wstrict-overflow=5 -Wstrict-prototypes -Wundef -Wuninitialized -Wunused \
|
||||||
-Wshadow \
|
-Wshadow \
|
||||||
|
-Wnull-dereference -Wstringop-overflow=4 \
|
||||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||||
-Wno-format-nonliteral \
|
-Wno-format-nonliteral \
|
||||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare \
|
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare \
|
||||||
@@ -246,8 +229,7 @@ develop:
|
|||||||
-fsanitize=signed-integer-overflow -fsanitize=bounds \
|
-fsanitize=signed-integer-overflow -fsanitize=bounds \
|
||||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
|
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
|
||||||
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
|
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
|
||||||
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
|
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
|
||||||
|
|
||||||
# Targets for the project maintainer to easily create Windows exes.
|
# Targets for the project maintainer to easily create Windows exes.
|
||||||
# This is not for Windows users!
|
# This is not for Windows users!
|
||||||
@@ -255,14 +237,12 @@ develop:
|
|||||||
# install instructions instead.
|
# install instructions instead.
|
||||||
|
|
||||||
mingw32:
|
mingw32:
|
||||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
$Q${MAKE} CC=i686-w64-mingw32-gcc BISON=bison \
|
||||||
CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ \
|
PKG_CONFIG=i686-w64-mingw32-pkg-config -j
|
||||||
BISON=bison PKG_CONFIG=i686-w64-mingw32-pkg-config -j
|
|
||||||
|
|
||||||
mingw64:
|
mingw64:
|
||||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
$Q${MAKE} CC=x86_64-w64-mingw32-gcc BISON=bison \
|
||||||
CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ \
|
PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
|
||||||
BISON=bison PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
|
|
||||||
|
|
||||||
wine-shim:
|
wine-shim:
|
||||||
$Qecho '#!/bin/bash' > rgbshim.sh
|
$Qecho '#!/bin/bash' > rgbshim.sh
|
||||||
|
|||||||
23
README.rst
23
README.rst
@@ -12,10 +12,11 @@ for the Game Boy and Game Boy Color. It consists of:
|
|||||||
This is a fork of the original RGBDS which aims to make the programs more like
|
This is a fork of the original RGBDS which aims to make the programs more like
|
||||||
other UNIX tools.
|
other UNIX tools.
|
||||||
|
|
||||||
This toolchain is maintained `on GitHub <https://github.com/gbdev/rgbds>`__.
|
This toolchain is maintained on `GitHub <https://github.com/rednex/rgbds>`__.
|
||||||
|
|
||||||
The documentation of this toolchain can be viewed online `here <https://rgbds.gbdev.io/docs/>`__, it is generated from the man pages found in this repository.
|
The documentation of this toolchain can be viewed online
|
||||||
The source code of the website itself is on GitHub as well under the repo `rgbds-www <https://github.com/gbdev/rgbds-www>`__.
|
`here <https://rgbds.gbdev.io/docs/>`__, it is generated from the man pages
|
||||||
|
found in this repository.
|
||||||
|
|
||||||
If you want to contribute or maintain RGBDS, and have questions regarding the code, its organisation, etc. you can find me `on GBDev <https://gbdev.io/chat>`__ or via mail at ``rgbds at eldred dot fr``.
|
If you want to contribute or maintain RGBDS, and have questions regarding the code, its organisation, etc. you can find me `on GBDev <https://gbdev.io/chat>`__ or via mail at ``rgbds at eldred dot fr``.
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ If you want to contribute or maintain RGBDS, and have questions regarding the co
|
|||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
The `installation procedure <https://rgbds.gbdev.io/install>`__ is available
|
The `installation procedure <https://rgbds.gbdev.io/install>`__ is available
|
||||||
online for various platforms. `Building from source <https://rgbds.gbdev.io/install/#building-from-source>`__
|
online for various platforms. `Building from source <https://rgbds.gbdev.io/install/source>`__
|
||||||
is possible using ``make`` or ``cmake``; follow the link for more detailed instructions.
|
is possible using ``make`` or ``cmake``; follow the link for more detailed instructions.
|
||||||
|
|
||||||
.. code:: sh
|
.. code:: sh
|
||||||
@@ -56,8 +57,6 @@ The RGBDS source code file structure somewhat resembles the following:
|
|||||||
│ └── ...
|
│ └── ...
|
||||||
├── include/
|
├── include/
|
||||||
│ └── ...
|
│ └── ...
|
||||||
├── man/
|
|
||||||
│ └── ...
|
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── asm/
|
│ ├── asm/
|
||||||
│ │ └── ...
|
│ │ └── ...
|
||||||
@@ -97,9 +96,7 @@ The RGBDS source code file structure somewhat resembles the following:
|
|||||||
|
|
||||||
- ``include/`` - header files for each respective C files in `src`.
|
- ``include/`` - header files for each respective C files in `src`.
|
||||||
|
|
||||||
- ``man/`` - manual pages.
|
- ``src/`` - source code and manual pages for RGBDS.
|
||||||
|
|
||||||
- ``src/`` - source code of RGBDS.
|
|
||||||
|
|
||||||
* Note that the code unique to each RGBDS tool is stored in its respective subdirectory
|
* Note that the code unique to each RGBDS tool is stored in its respective subdirectory
|
||||||
(rgbasm -> ``src/asm/``, for example). ``src/extern/`` contains code imported from external sources.
|
(rgbasm -> ``src/asm/``, for example). ``src/extern/`` contains code imported from external sources.
|
||||||
@@ -129,11 +126,3 @@ The RGBDS source code file structure somewhat resembles the following:
|
|||||||
- 2018, codebase relicensed under the MIT license.
|
- 2018, codebase relicensed under the MIT license.
|
||||||
|
|
||||||
- 2020, repository is moved to the `gbdev <https://github.com/gbdev>`__ organisation. The `rgbds.gbdev.io <https://rgbds.gbdev.io>`__ website serving documentation and downloads is created.
|
- 2020, repository is moved to the `gbdev <https://github.com/gbdev>`__ organisation. The `rgbds.gbdev.io <https://rgbds.gbdev.io>`__ website serving documentation and downloads is created.
|
||||||
|
|
||||||
4. Acknowledgements
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
RGBGFX generates palettes using algorithms found in the paper
|
|
||||||
`"Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items" <http://arxiv.org/abs/1605.00558>`__
|
|
||||||
(`GitHub <https://github.com/pagination-problem/pagination>`__, MIT license),
|
|
||||||
by Aristide Grange, Imed Kacem, and Sébastien Martin.
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#/usr/bin/env bash
|
||||||
|
|
||||||
# Known bugs:
|
# Known bugs:
|
||||||
# - Newlines in file/directory names break this script
|
# - Newlines in file/directory names break this script
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
# - Directories are not completed as such in "coalesced" short-opt arguments. For example,
|
# - Directories are not completed as such in "coalesced" short-opt arguments. For example,
|
||||||
# `rgbasm -M d<tab>` can autocomplete to `rgbasm -M dir/` (no space), but
|
# `rgbasm -M d<tab>` can autocomplete to `rgbasm -M dir/` (no space), but
|
||||||
# `rgbasm -Md<tab>` would autocomplete to `rgbasm -Mdir ` (trailing space) instead.
|
# `rgbasm -Md<tab>` would autocomplete to `rgbasm -Mdir ` (trailing space) instead.
|
||||||
# This is because directory handling is performed by Readline, whom we can't tell about the short
|
# This is because dircetory handling is performed by Readline, whom we can't tell about the short
|
||||||
# opt kerfuffle. The user can work around by separating the argument, as shown above.
|
# opt kerfuffle. The user can work around by separating the argument, as shown above.
|
||||||
# (Also, there might be more possible bugs if `-Mdir` is actually a directory. Ugh.)
|
# (Also, there might be more possible bugs if `-Mdir` is actually a directory. Ugh.)
|
||||||
|
|
||||||
@@ -20,16 +20,16 @@
|
|||||||
# Thus, we don't need to do much to handle that form of argument passing: skip '=' after long opts.
|
# Thus, we don't need to do much to handle that form of argument passing: skip '=' after long opts.
|
||||||
|
|
||||||
_rgbasm_completions() {
|
_rgbasm_completions() {
|
||||||
|
COMPREPLY=()
|
||||||
|
|
||||||
# Format: "long_opt:state_after"
|
# Format: "long_opt:state_after"
|
||||||
# Empty long opt = it doesn't exit
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
[E]="export-all:normal"
|
[E]="export-all:normal"
|
||||||
[H]="nop-after-halt:normal"
|
|
||||||
[h]="halt-without-nop:normal"
|
[h]="halt-without-nop:normal"
|
||||||
[L]="preserve-ld:normal"
|
[L]="preserve-ld:normal"
|
||||||
[l]="auto-ldh:normal"
|
|
||||||
[v]="verbose:normal"
|
[v]="verbose:normal"
|
||||||
[w]=":normal"
|
[w]=":normal"
|
||||||
[b]="binary-digits:unk"
|
[b]="binary-digits:unk"
|
||||||
@@ -39,7 +39,6 @@ _rgbasm_completions() {
|
|||||||
[M]="dependfile:glob-*.mk *.d"
|
[M]="dependfile:glob-*.mk *.d"
|
||||||
[o]="output:glob-*.o"
|
[o]="output:glob-*.o"
|
||||||
[p]="pad-value:unk"
|
[p]="pad-value:unk"
|
||||||
[Q]="q-precision:unk"
|
|
||||||
[r]="recursion-depth:unk"
|
[r]="recursion-depth:unk"
|
||||||
[W]="warning:warning"
|
[W]="warning:warning"
|
||||||
)
|
)
|
||||||
@@ -59,18 +58,6 @@ _rgbasm_completions() {
|
|||||||
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
|
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
|
||||||
# part of the argument.
|
# part of the argument.
|
||||||
parse_short_opt() {
|
parse_short_opt() {
|
||||||
# These options act like a long option (= takes up the entire word), but only use a single dash
|
|
||||||
# So, they need some special handling
|
|
||||||
if [[ "$1" = "-M"[GP] ]]; then
|
|
||||||
state=normal
|
|
||||||
optlen=${#1}
|
|
||||||
return;
|
|
||||||
elif [[ "$1" = "-M"[QT] ]]; then
|
|
||||||
state='glob-*.d *.mk *.o'
|
|
||||||
optlen=${#1}
|
|
||||||
return;
|
|
||||||
fi
|
|
||||||
|
|
||||||
for (( i = 1; i < "${#1}"; i++ )); do
|
for (( i = 1; i < "${#1}"; i++ )); do
|
||||||
# If the option is not known, assume it doesn't take an argument
|
# If the option is not known, assume it doesn't take an argument
|
||||||
local opt="${opts["${1:$i:1}"]:-":normal"}"
|
local opt="${opts["${1:$i:1}"]:-":normal"}"
|
||||||
@@ -84,7 +71,7 @@ _rgbasm_completions() {
|
|||||||
optlen=0
|
optlen=0
|
||||||
}
|
}
|
||||||
|
|
||||||
for (( i = 1; i < COMP_CWORD; i++ )); do
|
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
||||||
local word="${COMP_WORDS[$i]}"
|
local word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# If currently processing an argument, skip this word
|
# If currently processing an argument, skip this word
|
||||||
@@ -100,7 +87,7 @@ _rgbasm_completions() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if it's a long option
|
# Check if it's a long option
|
||||||
if [[ "$word" = '--'* ]]; then
|
if [[ "${word:0:2}" = '--' ]]; then
|
||||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||||
for long_opt in "${opts[@]}"; do
|
for long_opt in "${opts[@]}"; do
|
||||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||||
@@ -116,16 +103,25 @@ _rgbasm_completions() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Check if it's a short option
|
# Check if it's a short option
|
||||||
elif [[ "$word" = '-'* ]]; then
|
elif [[ "${word:0:1}" = '-' ]]; then
|
||||||
parse_short_opt "$word"
|
# The `-M?` ones are a mix of short and long, augh
|
||||||
# The last option takes an argument...
|
# They must match the *full* word, but only take a single dash
|
||||||
if [[ "$state" != 'normal' ]]; then
|
# So, handle them here
|
||||||
if [[ "$optlen" -ne "${#word}" ]]; then
|
if [[ "$1" = "-M"[GP] ]]; then
|
||||||
# If it's contained within the word, we won't complete it, revert to "normal"
|
state=normal
|
||||||
state=normal
|
elif [[ "$1" = "-M"[TQ] ]]; then
|
||||||
else
|
state='glob-*.d *.mk *.o'
|
||||||
# Otherwise, complete it, but start at the beginning of *that* word
|
else
|
||||||
optlen=0
|
parse_short_opt "$word"
|
||||||
|
# The last option takes an argument...
|
||||||
|
if [[ "$state" != 'normal' ]]; then
|
||||||
|
if [[ "$optlen" -ne "${#word}" ]]; then
|
||||||
|
# If it's contained within the word, we won't complete it, revert to "normal"
|
||||||
|
state=normal
|
||||||
|
else
|
||||||
|
# Otherwise, complete it, but start at the beginning of *that* word
|
||||||
|
optlen=0
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -133,48 +129,48 @@ _rgbasm_completions() {
|
|||||||
|
|
||||||
# Parse current word
|
# Parse current word
|
||||||
# Careful that it might look like an option, so use `--` aggressively!
|
# Careful that it might look like an option, so use `--` aggressively!
|
||||||
local cur_word="${COMP_WORDS[$i]}"
|
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
||||||
|
|
||||||
# Process options, as short ones may change the state
|
# Process options, as short ones may change the state
|
||||||
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
|
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
||||||
# We might want to complete to an option or an arg to that option
|
# We might want to complete to an option or an arg to that option
|
||||||
# Parse the option word to check
|
# Parse the option word to check
|
||||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||||
|
|
||||||
# Is this a long option?
|
# Is this a long option?
|
||||||
if [[ "$cur_word" = '--'* ]]; then
|
if [[ "${cur_word:1:1}" = '-' ]]; then
|
||||||
# It is, try to complete one
|
# It is, try to complete one
|
||||||
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
|
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
||||||
return 0
|
|
||||||
elif [[ "$cur_word" = '-M'[GPQT] ]]; then
|
|
||||||
# These options act like long opts with no arguments, so return them and exactly them
|
|
||||||
COMPREPLY=( "$cur_word" )
|
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
# Short options may be grouped, parse them to determine what to complete
|
# Short options may be grouped, parse them to determine what to complete
|
||||||
parse_short_opt "$cur_word"
|
# The `-M?` ones may not be followed by anything
|
||||||
|
if [[ "$1" != "-M"[GPTQ] ]]; then
|
||||||
|
parse_short_opt "$cur_word"
|
||||||
|
# We got some short options that behave like long ones
|
||||||
|
COMPREPLY+=( $(compgen -W '-MG -MP -MT -MQ' -- "$cur_word") )
|
||||||
|
|
||||||
if [[ "$state" = 'normal' ]]; then
|
if [[ "$state" = 'normal' ]]; then
|
||||||
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" ''; compgen -W '-MG -MP -MQ -MT' "$cur_word")
|
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||||
# This short option group only awaits its argument!
|
# This short option group only awaits its argument!
|
||||||
# Post the option group as-is as a reply so that Readline inserts a space,
|
# Post the option group as-is as a reply so that Readline inserts a space,
|
||||||
# so that the next completion request switches to the argument
|
# so that the next completion request switches to the argument
|
||||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||||
# `-W`, and it doesn't break anything.
|
# `-W`, and it doesn't break anything.
|
||||||
COMPREPLY=( "$cur_word" )
|
COMPREPLY+=( "$cur_word" )
|
||||||
return 0
|
return 0
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMPREPLY=()
|
|
||||||
case "$state" in
|
case "$state" in
|
||||||
unk) # Return with no replies: no idea what to complete!
|
unk) # Return with no replies: no idea what to complete!
|
||||||
;;
|
;;
|
||||||
warning)
|
warning)
|
||||||
mapfile -t COMPREPLY < <(compgen -W "
|
COMPREPLY+=( $(compgen -W "
|
||||||
assert
|
assert
|
||||||
backwards-for
|
backwards-for
|
||||||
builtin-args
|
builtin-args
|
||||||
@@ -192,12 +188,11 @@ _rgbasm_completions() {
|
|||||||
shift
|
shift
|
||||||
shift-amount
|
shift-amount
|
||||||
truncation
|
truncation
|
||||||
unmapped-char
|
|
||||||
user
|
user
|
||||||
all
|
all
|
||||||
extra
|
extra
|
||||||
everything
|
everything
|
||||||
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}")
|
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
|
||||||
;;
|
;;
|
||||||
normal) # Acts like a glob...
|
normal) # Acts like a glob...
|
||||||
state="glob-*.asm *.inc *.sm83"
|
state="glob-*.asm *.inc *.sm83"
|
||||||
@@ -214,10 +209,6 @@ _rgbasm_completions() {
|
|||||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||||
compopt -o filenames
|
compopt -o filenames
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#/usr/bin/env bash
|
||||||
|
|
||||||
# Same notes as RGBASM
|
# Same notes as RGBASM
|
||||||
|
|
||||||
_rgbfix_completions() {
|
_rgbfix_completions() {
|
||||||
|
COMPREPLY=()
|
||||||
|
|
||||||
# Format: "long_opt:state_after"
|
# Format: "long_opt:state_after"
|
||||||
# Empty long opt = it doesn't exit
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
@@ -52,7 +54,7 @@ _rgbfix_completions() {
|
|||||||
optlen=0
|
optlen=0
|
||||||
}
|
}
|
||||||
|
|
||||||
for (( i = 1; i < COMP_CWORD; i++ )); do
|
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
||||||
local word="${COMP_WORDS[$i]}"
|
local word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# If currently processing an argument, skip this word
|
# If currently processing an argument, skip this word
|
||||||
@@ -68,7 +70,7 @@ _rgbfix_completions() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if it's a long option
|
# Check if it's a long option
|
||||||
if [[ "$word" = '--'* ]]; then
|
if [[ "${word:0:2}" = '--' ]]; then
|
||||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||||
for long_opt in "${opts[@]}"; do
|
for long_opt in "${opts[@]}"; do
|
||||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||||
@@ -84,7 +86,7 @@ _rgbfix_completions() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Check if it's a short option
|
# Check if it's a short option
|
||||||
elif [[ "$word" = '-'* ]]; then
|
elif [[ "${word:0:1}" = '-' ]]; then
|
||||||
parse_short_opt "$word"
|
parse_short_opt "$word"
|
||||||
# The last option takes an argument...
|
# The last option takes an argument...
|
||||||
if [[ "$state" != 'normal' ]]; then
|
if [[ "$state" != 'normal' ]]; then
|
||||||
@@ -101,25 +103,25 @@ _rgbfix_completions() {
|
|||||||
|
|
||||||
# Parse current word
|
# Parse current word
|
||||||
# Careful that it might look like an option, so use `--` aggressively!
|
# Careful that it might look like an option, so use `--` aggressively!
|
||||||
local cur_word="${COMP_WORDS[$i]}"
|
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
||||||
|
|
||||||
# Process options, as short ones may change the state
|
# Process options, as short ones may change the state
|
||||||
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
|
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
||||||
# We might want to complete to an option or an arg to that option
|
# We might want to complete to an option or an arg to that option
|
||||||
# Parse the option word to check
|
# Parse the option word to check
|
||||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||||
|
|
||||||
# Is this a long option?
|
# Is this a long option?
|
||||||
if [[ "$cur_word" = '--'* ]]; then
|
if [[ "${cur_word:1:1}" = '-' ]]; then
|
||||||
# It is, try to complete one
|
# It is, try to complete one
|
||||||
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
|
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
# Short options may be grouped, parse them to determine what to complete
|
# Short options may be grouped, parse them to determine what to complete
|
||||||
parse_short_opt "$cur_word"
|
parse_short_opt "$cur_word"
|
||||||
|
|
||||||
if [[ "$state" = 'normal' ]]; then
|
if [[ "$state" = 'normal' ]]; then
|
||||||
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
|
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||||
# This short option group only awaits its argument!
|
# This short option group only awaits its argument!
|
||||||
@@ -127,25 +129,22 @@ _rgbfix_completions() {
|
|||||||
# so that the next completion request switches to the argument
|
# so that the next completion request switches to the argument
|
||||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||||
# `-W`, and it doesn't break anything.
|
# `-W`, and it doesn't break anything.
|
||||||
COMPREPLY=( "$cur_word" )
|
COMPREPLY+=( "$cur_word" )
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMPREPLY=()
|
|
||||||
case "$state" in
|
case "$state" in
|
||||||
unk) # Return with no replies: no idea what to complete!
|
unk) # Return with no replies: no idea what to complete!
|
||||||
;;
|
;;
|
||||||
fix-spec)
|
fix-spec)
|
||||||
COMPREPLY=( "${cur_word}"{l,h,g,L,H,G} )
|
COMPREPLY+=( "${cur_word}"{l,h,g,L,H,G} )
|
||||||
;;
|
;;
|
||||||
mbc)
|
mbc)
|
||||||
local cur_arg="${cur_word:$optlen}"
|
local cur_arg="${cur_word:$optlen}"
|
||||||
cur_arg="${cur_arg@U}"
|
cur_arg="${cur_arg@U}"
|
||||||
compopt -o nosort # Keep `help` first in the list, mainly
|
COMPREPLY=( $(compgen -W "
|
||||||
mapfile -t COMPREPLY < <(compgen -W "help" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}")
|
|
||||||
mapfile -t COMPREPLY -O ${#COMPREPLY} < <(compgen -W "
|
|
||||||
ROM_ONLY
|
ROM_ONLY
|
||||||
MBC1{,+RAM,+RAM+BATTERY}
|
MBC1{,+RAM,+RAM+BATTERY}
|
||||||
MBC2{,+BATTERY}
|
MBC2{,+BATTERY}
|
||||||
@@ -158,7 +157,8 @@ _rgbfix_completions() {
|
|||||||
BANDAI_TAMA5
|
BANDAI_TAMA5
|
||||||
HUC3
|
HUC3
|
||||||
HUC1+RAM+BATTERY
|
HUC1+RAM+BATTERY
|
||||||
TPP1_1.0{,+BATTERY}{,+RTC}{,+RUMBLE,+MULTIRUMBLE}" -P "${cur_word:0:$optlen}" -- "${cur_word/ /_}")
|
TPP1_1.0{,+BATTERY}{,+RTC}{,+RUMBLE,+MULTIRUMBLE}" -P "${cur_word:0:$optlen}" -- "`tr 'a-z ' 'A-Z_' <<<"${cur_word/ /_}"`") )
|
||||||
|
COMPREPLY+=( $(compgen -W "help" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
|
||||||
;;
|
;;
|
||||||
normal) # Acts like a glob...
|
normal) # Acts like a glob...
|
||||||
state="glob-*.gb *.gbc *.sgb"
|
state="glob-*.gb *.gbc *.sgb"
|
||||||
@@ -175,10 +175,6 @@ _rgbfix_completions() {
|
|||||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||||
compopt -o filenames
|
compopt -o filenames
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,30 @@
|
|||||||
#!/usr/bin/env bash
|
#/usr/bin/env bash
|
||||||
|
|
||||||
# Same notes as RGBASM
|
# Same notes as RGBASM
|
||||||
|
|
||||||
_rgbgfx_completions() {
|
_rgbgfx_completions() {
|
||||||
|
COMPREPLY=()
|
||||||
|
|
||||||
# Format: "long_opt:state_after"
|
# Format: "long_opt:state_after"
|
||||||
# Empty long opt = it doesn't exit
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
declare -A opts=(
|
declare -A opts=(
|
||||||
[V]="version:normal"
|
[V]="version:normal"
|
||||||
[C]="color-curve:normal"
|
[C]="color-curve:normal"
|
||||||
|
[D]="debug:normal"
|
||||||
|
[h]="horizontal:normal"
|
||||||
[m]="mirror-tiles:normal"
|
[m]="mirror-tiles:normal"
|
||||||
[u]="unique-tiles:normal"
|
[u]="unique-tiles:normal"
|
||||||
[v]="verbose:normal"
|
[v]="verbose:normal"
|
||||||
[Z]="columns:normal"
|
[f]="fix:normal"
|
||||||
[a]="attr-map:glob-*.attrmap"
|
[F]="fix-and-save:normal"
|
||||||
|
[a]="attr-map:*.attrmap"
|
||||||
[A]="output-attr-map:normal"
|
[A]="output-attr-map:normal"
|
||||||
[b]="base-tiles:unk"
|
|
||||||
[d]="depth:unk"
|
[d]="depth:unk"
|
||||||
[L]="slice:unk"
|
[o]="output:glob *.2bpp"
|
||||||
[N]="nb-tiles:unk"
|
[p]="palette:glob *.pal"
|
||||||
[n]="nb-palettes:unk"
|
|
||||||
[o]="output:glob-*.2bpp"
|
|
||||||
[p]="palette:glob-*.pal"
|
|
||||||
[P]="output-palette:normal"
|
[P]="output-palette:normal"
|
||||||
[q]="palette-map:glob-*.palmap"
|
[t]="tilemap:glob *.tilemap"
|
||||||
[Q]="output-palette-map:normal"
|
|
||||||
[r]="reverse:unk"
|
|
||||||
[s]="palette-size:unk"
|
|
||||||
[t]="tilemap:glob-*.tilemap"
|
|
||||||
[T]="output-tilemap:normal"
|
[T]="output-tilemap:normal"
|
||||||
[x]="trim-end:unk"
|
[x]="trim-end:unk"
|
||||||
)
|
)
|
||||||
@@ -60,7 +57,7 @@ _rgbgfx_completions() {
|
|||||||
optlen=0
|
optlen=0
|
||||||
}
|
}
|
||||||
|
|
||||||
for (( i = 1; i < COMP_CWORD; i++ )); do
|
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
||||||
local word="${COMP_WORDS[$i]}"
|
local word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# If currently processing an argument, skip this word
|
# If currently processing an argument, skip this word
|
||||||
@@ -76,7 +73,7 @@ _rgbgfx_completions() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if it's a long option
|
# Check if it's a long option
|
||||||
if [[ "$word" = '--'* ]]; then
|
if [[ "${word:0:2}" = '--' ]]; then
|
||||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||||
for long_opt in "${opts[@]}"; do
|
for long_opt in "${opts[@]}"; do
|
||||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||||
@@ -92,7 +89,7 @@ _rgbgfx_completions() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Check if it's a short option
|
# Check if it's a short option
|
||||||
elif [[ "$word" = '-'* ]]; then
|
elif [[ "${word:0:1}" = '-' ]]; then
|
||||||
parse_short_opt "$word"
|
parse_short_opt "$word"
|
||||||
# The last option takes an argument...
|
# The last option takes an argument...
|
||||||
if [[ "$state" != 'normal' ]]; then
|
if [[ "$state" != 'normal' ]]; then
|
||||||
@@ -109,25 +106,25 @@ _rgbgfx_completions() {
|
|||||||
|
|
||||||
# Parse current word
|
# Parse current word
|
||||||
# Careful that it might look like an option, so use `--` aggressively!
|
# Careful that it might look like an option, so use `--` aggressively!
|
||||||
local cur_word="${COMP_WORDS[$i]}"
|
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
||||||
|
|
||||||
# Process options, as short ones may change the state
|
# Process options, as short ones may change the state
|
||||||
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
|
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
||||||
# We might want to complete to an option or an arg to that option
|
# We might want to complete to an option or an arg to that option
|
||||||
# Parse the option word to check
|
# Parse the option word to check
|
||||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||||
|
|
||||||
# Is this a long option?
|
# Is this a long option?
|
||||||
if [[ "$cur_word" = '--'* ]]; then
|
if [[ "${cur_word:1:1}" = '-' ]]; then
|
||||||
# It is, try to complete one
|
# It is, try to complete one
|
||||||
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
|
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
# Short options may be grouped, parse them to determine what to complete
|
# Short options may be grouped, parse them to determine what to complete
|
||||||
parse_short_opt "$cur_word"
|
parse_short_opt "$cur_word"
|
||||||
|
|
||||||
if [[ "$state" = 'normal' ]]; then
|
if [[ "$state" = 'normal' ]]; then
|
||||||
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
|
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||||
# This short option group only awaits its argument!
|
# This short option group only awaits its argument!
|
||||||
@@ -135,13 +132,12 @@ _rgbgfx_completions() {
|
|||||||
# so that the next completion request switches to the argument
|
# so that the next completion request switches to the argument
|
||||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||||
# `-W`, and it doesn't break anything.
|
# `-W`, and it doesn't break anything.
|
||||||
COMPREPLY=( "$cur_word" )
|
COMPREPLY+=( "$cur_word" )
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMPREPLY=()
|
|
||||||
case "$state" in
|
case "$state" in
|
||||||
unk) # Return with no replies: no idea what to complete!
|
unk) # Return with no replies: no idea what to complete!
|
||||||
;;
|
;;
|
||||||
@@ -160,10 +156,6 @@ _rgbgfx_completions() {
|
|||||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||||
compopt -o filenames
|
compopt -o filenames
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#/usr/bin/env bash
|
||||||
|
|
||||||
# Same notes as RGBASM
|
# Same notes as RGBASM
|
||||||
|
|
||||||
_rgblink_completions() {
|
_rgblink_completions() {
|
||||||
|
COMPREPLY=()
|
||||||
|
|
||||||
# Format: "long_opt:state_after"
|
# Format: "long_opt:state_after"
|
||||||
# Empty long opt = it doesn't exit
|
# Empty long opt = it doesn't exit
|
||||||
# See the `state` variable below for info about `state_after`
|
# See the `state` variable below for info about `state_after`
|
||||||
@@ -14,7 +16,6 @@ _rgblink_completions() {
|
|||||||
[w]="wramx:normal"
|
[w]="wramx:normal"
|
||||||
[x]="nopad:normal"
|
[x]="nopad:normal"
|
||||||
[l]="linkerscript:glob-*"
|
[l]="linkerscript:glob-*"
|
||||||
[M]="no-sym-in-map:normal"
|
|
||||||
[m]="map:glob-*.map"
|
[m]="map:glob-*.map"
|
||||||
[n]="sym:glob-*.sym"
|
[n]="sym:glob-*.sym"
|
||||||
[O]="overlay:glob-*.gb *.gbc *.sgb"
|
[O]="overlay:glob-*.gb *.gbc *.sgb"
|
||||||
@@ -51,7 +52,7 @@ _rgblink_completions() {
|
|||||||
optlen=0
|
optlen=0
|
||||||
}
|
}
|
||||||
|
|
||||||
for (( i = 1; i < COMP_CWORD; i++ )); do
|
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
||||||
local word="${COMP_WORDS[$i]}"
|
local word="${COMP_WORDS[$i]}"
|
||||||
|
|
||||||
# If currently processing an argument, skip this word
|
# If currently processing an argument, skip this word
|
||||||
@@ -67,7 +68,7 @@ _rgblink_completions() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if it's a long option
|
# Check if it's a long option
|
||||||
if [[ "$word" = '--'* ]]; then
|
if [[ "${word:0:2}" = '--' ]]; then
|
||||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||||
for long_opt in "${opts[@]}"; do
|
for long_opt in "${opts[@]}"; do
|
||||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||||
@@ -83,7 +84,7 @@ _rgblink_completions() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Check if it's a short option
|
# Check if it's a short option
|
||||||
elif [[ "$word" = '-'* ]]; then
|
elif [[ "${word:0:1}" = '-' ]]; then
|
||||||
parse_short_opt "$word"
|
parse_short_opt "$word"
|
||||||
# The last option takes an argument...
|
# The last option takes an argument...
|
||||||
if [[ "$state" != 'normal' ]]; then
|
if [[ "$state" != 'normal' ]]; then
|
||||||
@@ -100,25 +101,25 @@ _rgblink_completions() {
|
|||||||
|
|
||||||
# Parse current word
|
# Parse current word
|
||||||
# Careful that it might look like an option, so use `--` aggressively!
|
# Careful that it might look like an option, so use `--` aggressively!
|
||||||
local cur_word="${COMP_WORDS[$i]}"
|
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
||||||
|
|
||||||
# Process options, as short ones may change the state
|
# Process options, as short ones may change the state
|
||||||
if $opt_ena && [[ "$state" = 'normal' && "$cur_word" = '-'* ]]; then
|
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
||||||
# We might want to complete to an option or an arg to that option
|
# We might want to complete to an option or an arg to that option
|
||||||
# Parse the option word to check
|
# Parse the option word to check
|
||||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||||
|
|
||||||
# Is this a long option?
|
# Is this a long option?
|
||||||
if [[ "$cur_word" = '--'* ]]; then
|
if [[ "${cur_word:1:1}" = '-' ]]; then
|
||||||
# It is, try to complete one
|
# It is, try to complete one
|
||||||
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
|
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
# Short options may be grouped, parse them to determine what to complete
|
# Short options may be grouped, parse them to determine what to complete
|
||||||
parse_short_opt "$cur_word"
|
parse_short_opt "$cur_word"
|
||||||
|
|
||||||
if [[ "$state" = 'normal' ]]; then
|
if [[ "$state" = 'normal' ]]; then
|
||||||
mapfile -t COMPREPLY < <(compgen -W "${!opts[*]}" -P "$cur_word" '')
|
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||||
# This short option group only awaits its argument!
|
# This short option group only awaits its argument!
|
||||||
@@ -126,13 +127,12 @@ _rgblink_completions() {
|
|||||||
# so that the next completion request switches to the argument
|
# so that the next completion request switches to the argument
|
||||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||||
# `-W`, and it doesn't break anything.
|
# `-W`, and it doesn't break anything.
|
||||||
COMPREPLY=( "$cur_word" )
|
COMPREPLY+=( "$cur_word" )
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMPREPLY=()
|
|
||||||
case "$state" in
|
case "$state" in
|
||||||
unk) # Return with no replies: no idea what to complete!
|
unk) # Return with no replies: no idea what to complete!
|
||||||
;;
|
;;
|
||||||
@@ -151,10 +151,6 @@ _rgblink_completions() {
|
|||||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||||
compopt -o filenames
|
compopt -o filenames
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
echo >&2 "Internal completion error: invalid state \"$state\", please report this bug"
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
declare -A FILES
|
declare -A FILES
|
||||||
while read -r -d '' file; do
|
while read -r -d '' file; do
|
||||||
FILES["$file"]="true"
|
FILES["$file"]="true"
|
||||||
done < <(git diff --name-only -z "$1" HEAD)
|
done < <(git diff --name-only -z $1 HEAD)
|
||||||
|
|
||||||
edited () {
|
edited () {
|
||||||
${FILES["$1"]:-"false"}
|
${FILES["$1"]:-"false"}
|
||||||
@@ -40,13 +40,13 @@ dependency () {
|
|||||||
# Pull requests that edit the first file without the second may be correct,
|
# Pull requests that edit the first file without the second may be correct,
|
||||||
# but are suspicious enough to require review.
|
# but are suspicious enough to require review.
|
||||||
|
|
||||||
dependency include/linkdefs.h man/rgbds.5 \
|
dependency include/linkdefs.h src/rgbds.5 \
|
||||||
"Was the object file format changed?"
|
"Was the object file format changed?"
|
||||||
|
|
||||||
dependency src/asm/parser.y man/rgbasm.5 \
|
dependency src/asm/parser.y src/asm/rgbasm.5 \
|
||||||
"Was the rgbasm grammar changed?"
|
"Was the rgbasm grammar changed?"
|
||||||
|
|
||||||
dependency include/asm/warning.h man/rgbasm.1 \
|
dependency include/asm/warning.h src/asm/rgbasm.1 \
|
||||||
"Were the rgbasm warnings changed?"
|
"Were the rgbasm warnings changed?"
|
||||||
|
|
||||||
dependency src/asm/object.c include/linkdefs.h \
|
dependency src/asm/object.c include/linkdefs.h \
|
||||||
@@ -59,27 +59,27 @@ dependency Makefile CMakeLists.txt \
|
|||||||
dependency Makefile src/CMakeLists.txt \
|
dependency Makefile src/CMakeLists.txt \
|
||||||
"Did the build process change?"
|
"Did the build process change?"
|
||||||
|
|
||||||
dependency src/asm/main.c man/rgbasm.1 \
|
dependency src/asm/main.c src/asm/rgbasm.1 \
|
||||||
"Did the rgbasm CLI change?"
|
"Did the rgbasm CLI change?"
|
||||||
dependency src/asm/main.c contrib/zsh_compl/_rgbasm \
|
dependency src/asm/main.c contrib/zsh_compl/_rgbasm \
|
||||||
"Did the rgbasm CLI change?"
|
"Did the rgbasm CLI change?"
|
||||||
dependency src/asm/main.c contrib/bash_compl/_rgbasm.bash \
|
dependency src/asm/main.c contrib/bash_compl/_rgbasm.bash \
|
||||||
"Did the rgbasm CLI change?"
|
"Did the rgbasm CLI change?"
|
||||||
dependency src/link/main.c man/rgblink.1 \
|
dependency src/link/main.c src/link/rgblink.1 \
|
||||||
"Did the rgblink CLI change?"
|
"Did the rgblink CLI change?"
|
||||||
dependency src/link/main.c contrib/zsh_compl/_rgblink \
|
dependency src/link/main.c contrib/zsh_compl/_rgblink \
|
||||||
"Did the rgblink CLI change?"
|
"Did the rgblink CLI change?"
|
||||||
dependency src/link/main.c contrib/bash_compl/_rgblink.bash \
|
dependency src/link/main.c contrib/bash_compl/_rgblink.bash \
|
||||||
"Did the rgblink CLI change?"
|
"Did the rgblink CLI change?"
|
||||||
dependency src/fix/main.c man/rgbfix.1 \
|
dependency src/fix/main.c src/fix/rgbfix.1 \
|
||||||
"Did the rgbfix CLI change?"
|
"Did the rgbfix CLI change?"
|
||||||
dependency src/fix/main.c contrib/zsh_compl/_rgbfix \
|
dependency src/fix/main.c contrib/zsh_compl/_rgbfix \
|
||||||
"Did the rgbfix CLI change?"
|
"Did the rgbfix CLI change?"
|
||||||
dependency src/fix/main.c contrib/bash_compl/_rgbfix.bash \
|
dependency src/fix/main.c contrib/bash_compl/_rgbfix.bash \
|
||||||
"Did the rgbfix CLI change?"
|
"Did the rgbfix CLI change?"
|
||||||
dependency src/gfx/main.cpp man/rgbgfx.1 \
|
dependency src/gfx/main.c src/gfx/rgbgfx.1 \
|
||||||
"Did the rgbgfx CLI change?"
|
"Did the rgbgfx CLI change?"
|
||||||
dependency src/gfx/main.cpp contrib/zsh_compl/_rgbgfx \
|
dependency src/gfx/main.c contrib/zsh_compl/_rgbgfx \
|
||||||
"Did the rgbgfx CLI change?"
|
"Did the rgbgfx CLI change?"
|
||||||
dependency src/gfx/main.cpp contrib/bash_compl/_rgbgfx.bash \
|
dependency src/gfx/main.c contrib/bash_compl/_rgbgfx.bash \
|
||||||
"Did the rgbgfx CLI change?"
|
"Did the rgbgfx CLI change?"
|
||||||
|
|||||||
@@ -23,51 +23,51 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
STATE=0
|
STATE=0
|
||||||
diff <(xxd "$1") <(xxd "$2") | while read -r LINE; do
|
diff <(xxd $1) <(xxd $2) | while read -r LINE; do
|
||||||
if [[ $STATE -eq 0 ]]; then
|
if [ $STATE -eq 0 ]; then
|
||||||
# Discard first line (line info)
|
# Discard first line (line info)
|
||||||
STATE=1
|
STATE=1
|
||||||
elif [[ "$LINE" = '---' ]]; then
|
elif [ "$LINE" = '---' ]; then
|
||||||
# Separator between files switches states
|
# Separator between files switches states
|
||||||
echo "$LINE"
|
echo $LINE
|
||||||
STATE=3
|
STATE=3
|
||||||
elif grep -Eq '^[0-9]+(,[0-9]+)?[cd][0-9]+(,[0-9]+)?' <<< "$LINE"; then
|
elif grep -Eq '^[0-9]+(,[0-9]+)?[cd][0-9]+(,[0-9]+)?' <<< "$LINE"; then
|
||||||
# Line info resets the whole thing
|
# Line info resets the whole thing
|
||||||
STATE=1
|
STATE=1
|
||||||
elif [[ $STATE -eq 1 || $STATE -eq 3 ]]; then
|
elif [ $STATE -eq 1 -o $STATE -eq 3 ]; then
|
||||||
# Compute the GB address from the ROM offset
|
# Compute the GB address from the ROM offset
|
||||||
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
||||||
BANK=$((0x$OFS / 0x4000))
|
BANK=$((0x$OFS / 0x4000))
|
||||||
ADDR=$((0x$OFS % 0x4000 + (BANK != 0) * 0x4000))
|
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
|
||||||
# Try finding the preceding symbol closest to the diff
|
# Try finding the preceding symbol closest to the diff
|
||||||
if [[ $STATE -eq 1 ]]; then
|
if [ $STATE -eq 1 ]; then
|
||||||
STATE=2
|
STATE=2
|
||||||
SYMFILE=${1%.*}.sym
|
SYMFILE=${1%.*}.sym
|
||||||
else
|
else
|
||||||
STATE=4
|
STATE=4
|
||||||
SYMFILE=${2%.*}.sym
|
SYMFILE=${2%.*}.sym
|
||||||
fi
|
fi
|
||||||
EXTRA=$(if [[ -f "$SYMFILE" ]]; then
|
EXTRA=$(if [ -f "$SYMFILE" ]; then
|
||||||
# Read the sym file for such a symbol
|
# Read the sym file for such a symbol
|
||||||
# Ignore comment lines, only pick matching bank
|
# Ignore comment lines, only pick matching bank
|
||||||
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
||||||
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
|
grep -Ei $(printf "^%02x:" $BANK) "$SYMFILE" |
|
||||||
cut -d ';' -f 1 |
|
cut -d ';' -f 1 |
|
||||||
tr -d "\r" |
|
tr -d "\r" |
|
||||||
while read -r SYMADDR SYM; do
|
while read -r SYMADDR SYM; do
|
||||||
SYMADDR=$((0x${SYMADDR#*:}))
|
SYMADDR=$((0x${SYMADDR#*:}))
|
||||||
if [[ $SYMADDR -le $ADDR ]]; then
|
if [ $SYMADDR -le $ADDR ]; then
|
||||||
printf " (%s+%#x)\n" "$SYM" $((ADDR - SYMADDR))
|
printf " (%s+%#x)\n" "$SYM" $(($ADDR - $SYMADDR))
|
||||||
fi
|
fi
|
||||||
# TODO: assumes sorted sym files
|
# TODO: assumes sorted sym files
|
||||||
done | tail -n 1
|
done | tail -n 1
|
||||||
fi)
|
fi)
|
||||||
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
printf "%02x:%04x %s\n" $BANK $ADDR $EXTRA
|
||||||
fi
|
fi
|
||||||
if [[ $STATE -eq 2 || $STATE -eq 4 ]]; then
|
if [ $STATE -eq 2 -o $STATE -eq 4 ]; then
|
||||||
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
||||||
BANK=$((0x$OFS / 0x4000))
|
BANK=$((0x$OFS / 0x4000))
|
||||||
ADDR=$((0x$OFS % 0x4000 + (BANK != 0) * 0x4000))
|
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
|
||||||
printf "%s %02x:%04x: %s\n" "${LINE:0:1}" $BANK $ADDR "${LINE#*: }"
|
printf "%s %02x:%04x: %s\n" "${LINE:0:1}" $BANK $ADDR "${LINE#*: }"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ _rgbasm_warnings() {
|
|||||||
'shift:Warn when shifting negative values'
|
'shift:Warn when shifting negative values'
|
||||||
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
|
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
|
||||||
'truncation:Warn when implicit truncation loses bits'
|
'truncation:Warn when implicit truncation loses bits'
|
||||||
'unmapped-char:Warn on unmapped character'
|
|
||||||
'user:Warn when executing the WARN built-in'
|
'user:Warn when executing the WARN built-in'
|
||||||
)
|
)
|
||||||
# TODO: handle `no-` and `error=` somehow?
|
# TODO: handle `no-` and `error=` somehow?
|
||||||
@@ -38,10 +37,8 @@ local args=(
|
|||||||
'(- : * options)'{-V,--version}'[Print version number]'
|
'(- : * options)'{-V,--version}'[Print version number]'
|
||||||
|
|
||||||
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
|
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
|
||||||
'(-H --nop-after-halt)'{-H,--nop-after-halt}'[Output a `nop` after `halt`]'
|
'(-h --halt-without-nop)'{-h,--halt-without-nop}'[Avoid outputting a `nop` after `halt`]'
|
||||||
'(-h --halt-without-nop)'{-h,--halt-without-nop}'[Prevent outputting a `nop` after `halt`]'
|
'(-L ---preserve-ld)'{-L,--preserve-ld}'[Prevent auto-optimizing `ld` into `ldh`]'
|
||||||
'(-L --preserve-ld)'{-L,--preserve-ld}'[Prevent optimizing `ld` into `ldh`]'
|
|
||||||
'(-l --auto-ldh)'{-l,--auto-ldh}'[Optimize `ld` into `ldh`]'
|
|
||||||
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
||||||
-w'[Disable all warnings]'
|
-w'[Disable all warnings]'
|
||||||
|
|
||||||
@@ -56,7 +53,6 @@ local args=(
|
|||||||
'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
|
'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
|
||||||
'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
|
'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
|
||||||
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
|
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
|
||||||
'(-Q --q-precision)'{-Q,--q-precision}'+[Set fixed-point precision]:precision:'
|
|
||||||
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
|
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
|
||||||
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'
|
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'
|
||||||
|
|
||||||
|
|||||||
@@ -15,25 +15,20 @@ local args=(
|
|||||||
|
|
||||||
'(-a --attr-map -A --output-attr-map)'{-A,--output-attr-map}'[Shortcut for -a <file>.attrmap]'
|
'(-a --attr-map -A --output-attr-map)'{-A,--output-attr-map}'[Shortcut for -a <file>.attrmap]'
|
||||||
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
|
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
|
||||||
|
'(-D --debug)'{-D,--debug}'[Enable debug features]'
|
||||||
|
'(-f --fix -F --fix-and-save)'{-f,--fix}'[Fix input PNG into an indexed image]'
|
||||||
|
'(-f --fix -F --fix-and-save)'{-F,--fix-and-save}'[Like -f but also save CLI params within the PNG]'
|
||||||
|
'(-h --horizontal)'{-h,--horizontal}'[Lay out tiles horizontally instead of vertically]'
|
||||||
'(-m --mirror-tiles)'{-m,--mirror-tiles}'[Eliminate mirrored tiles from output]'
|
'(-m --mirror-tiles)'{-m,--mirror-tiles}'[Eliminate mirrored tiles from output]'
|
||||||
'(-p --palette -P --output-palette)'{-P,--output-palette}'[Shortcut for -p <file>.pal]'
|
'(-p --palette -P --output-palette)'{-P,--output-palette}'[Shortcut for -p <file>.pal]'
|
||||||
'(-q --palette-map -Q --output-palette-map)'{-Q,--output-palette-map}'[Shortcut for -p <file>.palmap]'
|
|
||||||
'(-t --tilemap -T --output-tilemap)'{-T,--output-tilemap}'[Shortcut for -t <file>.tilemap]'
|
'(-t --tilemap -T --output-tilemap)'{-T,--output-tilemap}'[Shortcut for -t <file>.tilemap]'
|
||||||
'(-u --unique-tiles)'{-u,--unique-tiles}'[Eliminate redundant tiles]'
|
'(-u --unique-tiles)'{-u,--unique-tiles}'[Eliminate redundant tiles]'
|
||||||
{-v,--verbose}'[Enable verbose output]'
|
'(-v --verbose)'{-v,--verbose}'[Enable verbose output]'
|
||||||
'(-h --horizontal -Z --columns)'{-Z,--columns}'[Read the image in column-major order]'
|
|
||||||
|
|
||||||
'(-a --attr-map -A --output-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
|
'(-a --attr-map -A --output-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
|
||||||
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
|
|
||||||
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
||||||
'(-L --slice)'{-L,--slice}'+[Only process a portion of the image]:input slice:'
|
|
||||||
'(-N --nb-tiles)'{-n,--nb-tiles}'+[Limit number of tiles]:tile count:'
|
|
||||||
'(-n --nb-palettes)'{-n,--nb-palettes}'+[Limit number of palettes]:palette count:'
|
|
||||||
'(-o --output)'{-o,--output}'+[Set output file]:output file:_files'
|
'(-o --output)'{-o,--output}'+[Set output file]:output file:_files'
|
||||||
'(-p --palette -P --output-palette)'{-p,--palette}"+[Output the image's palette in little-endian native RGB555 format]:palette file:_files"
|
'(-p --palette -P --output-palette)'{-p,--palette}"+[Output the image's palette in little-endian native RGB555 format]:palette file:_files"
|
||||||
'(-q --palette-map -Q --output-palette-map)'{-p,--palette-map}"+[Output the image's palette map]:palette map file:_files"
|
|
||||||
'(-r --reverse)'{-r,--reverse}'+[Yield an image from binary data]:image width (in tiles):'
|
|
||||||
'(-s --palette-size)'{-s,--palette-size}'+[Limit palette size]:palette size:'
|
|
||||||
'(-t --tilemap -T --output-tilemap)'{-t,--tilemap}'+[Generate a map of tile indices]:tilemap file:_files'
|
'(-t --tilemap -T --output-tilemap)'{-t,--tilemap}'+[Generate a map of tile indices]:tilemap file:_files'
|
||||||
'(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:'
|
'(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:'
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ local args=(
|
|||||||
'(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]'
|
'(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]'
|
||||||
|
|
||||||
'(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'"
|
'(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'"
|
||||||
'(-M --no-sym-in-map)'{-M,--no-sym-in-map}'[Do not output symbol names in map file]'
|
|
||||||
'(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'"
|
'(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'"
|
||||||
'(-n --sym)'(-n,--sym)"+[Produce a symbol file]:sym file:_files -g '*.sym'"
|
'(-n --sym)'(-n,--sym)"+[Produce a symbol file]:sym file:_files -g '*.sym'"
|
||||||
'(-O --overlay)'{-O,--overlay}'+[Overlay sections over on top of bin file]:base overlay:_files'
|
'(-O --overlay)'{-O,--overlay}'+[Overlay sections over on top of bin file]:base overlay:_files'
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ void charmap_Add(char *mapping, uint8_t value);
|
|||||||
size_t charmap_Convert(char const *input, uint8_t *output);
|
size_t charmap_Convert(char const *input, uint8_t *output);
|
||||||
size_t charmap_ConvertNext(char const **input, uint8_t **output);
|
size_t charmap_ConvertNext(char const **input, uint8_t **output);
|
||||||
|
|
||||||
#endif // RGBDS_ASM_CHARMAP_H
|
#endif /* RGBDS_ASM_CHARMAP_H */
|
||||||
|
|||||||
@@ -11,9 +11,7 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
extern uint8_t fixPrecision;
|
int32_t fix_Callback_PI(void);
|
||||||
|
|
||||||
double fix_PrecisionFactor(void);
|
|
||||||
void fix_Print(int32_t i);
|
void fix_Print(int32_t i);
|
||||||
int32_t fix_Sin(int32_t i);
|
int32_t fix_Sin(int32_t i);
|
||||||
int32_t fix_Cos(int32_t i);
|
int32_t fix_Cos(int32_t i);
|
||||||
@@ -23,7 +21,6 @@ int32_t fix_ACos(int32_t i);
|
|||||||
int32_t fix_ATan(int32_t i);
|
int32_t fix_ATan(int32_t i);
|
||||||
int32_t fix_ATan2(int32_t i, int32_t j);
|
int32_t fix_ATan2(int32_t i, int32_t j);
|
||||||
int32_t fix_Mul(int32_t i, int32_t j);
|
int32_t fix_Mul(int32_t i, int32_t j);
|
||||||
int32_t fix_Mod(int32_t i, int32_t j);
|
|
||||||
int32_t fix_Div(int32_t i, int32_t j);
|
int32_t fix_Div(int32_t i, int32_t j);
|
||||||
int32_t fix_Pow(int32_t i, int32_t j);
|
int32_t fix_Pow(int32_t i, int32_t j);
|
||||||
int32_t fix_Log(int32_t i, int32_t j);
|
int32_t fix_Log(int32_t i, int32_t j);
|
||||||
@@ -31,4 +28,4 @@ int32_t fix_Round(int32_t i);
|
|||||||
int32_t fix_Ceil(int32_t i);
|
int32_t fix_Ceil(int32_t i);
|
||||||
int32_t fix_Floor(int32_t i);
|
int32_t fix_Floor(int32_t i);
|
||||||
|
|
||||||
#endif // RGBDS_ASM_FIXPOINT_H
|
#endif /* RGBDS_ASM_FIXPOINT_H */
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ void fmt_FinishCharacters(struct FormatSpec *fmt);
|
|||||||
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value);
|
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value);
|
||||||
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value);
|
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value);
|
||||||
|
|
||||||
#endif // RGBDS_FORMAT_SPEC_H
|
#endif /* RGBDS_FORMAT_SPEC_H */
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Contains some assembler-wide defines and externs
|
/*
|
||||||
|
* Contains some assembler-wide defines and externs
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_FSTACK_H
|
#ifndef RGBDS_ASM_FSTACK_H
|
||||||
#define RGBDS_ASM_FSTACK_H
|
#define RGBDS_ASM_FSTACK_H
|
||||||
@@ -19,13 +21,13 @@
|
|||||||
|
|
||||||
|
|
||||||
struct FileStackNode {
|
struct FileStackNode {
|
||||||
struct FileStackNode *parent; // Pointer to parent node, for error reporting
|
struct FileStackNode *parent; /* Pointer to parent node, for error reporting */
|
||||||
// Line at which the parent context was exited; meaningless for the root level
|
/* Line at which the parent context was exited; meaningless for the root level */
|
||||||
uint32_t lineNo;
|
uint32_t lineNo;
|
||||||
|
|
||||||
struct FileStackNode *next; // Next node in the output linked list
|
struct FileStackNode *next; /* Next node in the output linked list */
|
||||||
bool referenced; // If referenced, don't free!
|
bool referenced; /* If referenced, don't free! */
|
||||||
uint32_t ID; // Set only if referenced: ID within the object file, -1 if not output yet
|
uint32_t ID; /* Set only if referenced: ID within the object file, -1 if not output yet */
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
NODE_REPT,
|
NODE_REPT,
|
||||||
@@ -34,19 +36,18 @@ struct FileStackNode {
|
|||||||
} type;
|
} type;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FileStackReptNode { // NODE_REPT
|
struct FileStackReptNode { /* NODE_REPT */
|
||||||
struct FileStackNode node;
|
struct FileStackNode node;
|
||||||
uint32_t reptDepth;
|
uint32_t reptDepth;
|
||||||
// WARNING: if changing this type, change overflow check in `fstk_Init`
|
/* WARNING: if changing this type, change overflow check in `fstk_Init` */
|
||||||
uint32_t iters[]; // REPT iteration counts since last named node, in reverse depth order
|
uint32_t iters[]; /* REPT iteration counts since last named node, in reverse depth order */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FileStackNamedNode { // NODE_FILE, NODE_MACRO
|
struct FileStackNamedNode { /* NODE_FILE, NODE_MACRO */
|
||||||
struct FileStackNode node;
|
struct FileStackNode node;
|
||||||
char name[]; // File name for files, file::macro name for macros
|
char name[]; /* File name for files, file::macro name for macros */
|
||||||
};
|
};
|
||||||
|
|
||||||
#define DEFAULT_MAX_DEPTH 64
|
|
||||||
extern size_t maxRecursionDepth;
|
extern size_t maxRecursionDepth;
|
||||||
|
|
||||||
struct MacroArgs;
|
struct MacroArgs;
|
||||||
@@ -54,11 +55,11 @@ struct MacroArgs;
|
|||||||
void fstk_Dump(struct FileStackNode const *node, uint32_t lineNo);
|
void fstk_Dump(struct FileStackNode const *node, uint32_t lineNo);
|
||||||
void fstk_DumpCurrent(void);
|
void fstk_DumpCurrent(void);
|
||||||
struct FileStackNode *fstk_GetFileStack(void);
|
struct FileStackNode *fstk_GetFileStack(void);
|
||||||
// The lifetime of the returned chars is until reaching the end of that file
|
/* The lifetime of the returned chars is until reaching the end of that file */
|
||||||
char const *fstk_GetFileName(void);
|
char const *fstk_GetFileName(void);
|
||||||
|
|
||||||
void fstk_AddIncludePath(char const *s);
|
void fstk_AddIncludePath(char const *s);
|
||||||
/*
|
/**
|
||||||
* @param path The user-provided file name
|
* @param path The user-provided file name
|
||||||
* @param fullPath The address of a pointer, which will be made to point at the full path
|
* @param fullPath The address of a pointer, which will be made to point at the full path
|
||||||
* The pointer's value must be a valid argument to `realloc`, including NULL
|
* The pointer's value must be a valid argument to `realloc`, including NULL
|
||||||
@@ -79,4 +80,4 @@ bool fstk_Break(void);
|
|||||||
void fstk_NewRecursionDepth(size_t newDepth);
|
void fstk_NewRecursionDepth(size_t newDepth);
|
||||||
void fstk_Init(char const *mainPath, size_t maxDepth);
|
void fstk_Init(char const *mainPath, size_t maxDepth);
|
||||||
|
|
||||||
#endif // RGBDS_ASM_FSTACK_H
|
#endif /* RGBDS_ASM_FSTACK_H */
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define MAXSTRLEN 255
|
#define MAXSTRLEN 255
|
||||||
|
|
||||||
struct LexerState;
|
struct LexerState;
|
||||||
extern struct LexerState *lexerState;
|
extern struct LexerState *lexerState;
|
||||||
@@ -49,7 +49,9 @@ static inline void lexer_SetGfxDigits(char const digits[4])
|
|||||||
gfxDigits[3] = digits[3];
|
gfxDigits[3] = digits[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
// `path` is referenced, but not held onto..!
|
/*
|
||||||
|
* `path` is referenced, but not held onto..!
|
||||||
|
*/
|
||||||
struct LexerState *lexer_OpenFile(char const *path);
|
struct LexerState *lexer_OpenFile(char const *path);
|
||||||
struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size, uint32_t lineNo);
|
struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size, uint32_t lineNo);
|
||||||
void lexer_RestartRept(uint32_t lineNo);
|
void lexer_RestartRept(uint32_t lineNo);
|
||||||
@@ -97,4 +99,4 @@ struct DsArgList {
|
|||||||
struct Expression *args;
|
struct Expression *args;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RGBDS_ASM_LEXER_H
|
#endif /* RGBDS_ASM_LEXER_H */
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ uint32_t macro_GetUniqueID(void);
|
|||||||
char const *macro_GetUniqueIDStr(void);
|
char const *macro_GetUniqueIDStr(void);
|
||||||
void macro_SetUniqueID(uint32_t id);
|
void macro_SetUniqueID(uint32_t id);
|
||||||
uint32_t macro_UseNewUniqueID(void);
|
uint32_t macro_UseNewUniqueID(void);
|
||||||
uint32_t macro_UndefUniqueID(void);
|
|
||||||
void macro_ShiftCurrentArgs(int32_t count);
|
void macro_ShiftCurrentArgs(int32_t count);
|
||||||
uint32_t macro_NbArgs(void);
|
uint32_t macro_NbArgs(void);
|
||||||
|
|
||||||
#endif // RGBDS_MACRO_H
|
#endif
|
||||||
|
|||||||
@@ -16,11 +16,9 @@
|
|||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
|
|
||||||
extern bool haltnop;
|
extern bool haltnop;
|
||||||
extern bool warnOnHaltNop;
|
|
||||||
extern bool optimizeLoads;
|
extern bool optimizeLoads;
|
||||||
extern bool warnOnLdOpt;
|
|
||||||
extern bool verbose;
|
extern bool verbose;
|
||||||
extern bool warnings; // True to enable warnings, false to disable them.
|
extern bool warnings; /* True to enable warnings, false to disable them. */
|
||||||
|
|
||||||
extern FILE *dependfile;
|
extern FILE *dependfile;
|
||||||
extern char *targetFileName;
|
extern char *targetFileName;
|
||||||
@@ -28,4 +26,4 @@ extern bool generatedMissingIncludes;
|
|||||||
extern bool failedOnMissingInclude;
|
extern bool failedOnMissingInclude;
|
||||||
extern bool generatePhonyDeps;
|
extern bool generatePhonyDeps;
|
||||||
|
|
||||||
#endif // RGBDS_MAIN_H
|
#endif /* RGBDS_MAIN_H */
|
||||||
|
|||||||
@@ -14,8 +14,7 @@
|
|||||||
|
|
||||||
void opt_B(char const chars[2]);
|
void opt_B(char const chars[2]);
|
||||||
void opt_G(char const chars[4]);
|
void opt_G(char const chars[4]);
|
||||||
void opt_P(uint8_t padByte);
|
void opt_P(uint8_t fill);
|
||||||
void opt_Q(uint8_t precision);
|
|
||||||
void opt_L(bool optimize);
|
void opt_L(bool optimize);
|
||||||
void opt_W(char const *flag);
|
void opt_W(char const *flag);
|
||||||
void opt_Parse(char const *option);
|
void opt_Parse(char const *option);
|
||||||
@@ -23,4 +22,4 @@ void opt_Parse(char const *option);
|
|||||||
void opt_Push(void);
|
void opt_Push(void);
|
||||||
void opt_Pop(void);
|
void opt_Pop(void);
|
||||||
|
|
||||||
#endif // RGBDS_OPT_H
|
#endif
|
||||||
|
|||||||
@@ -27,4 +27,4 @@ bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
|
|||||||
char const *message, uint32_t ofs);
|
char const *message, uint32_t ofs);
|
||||||
void out_WriteObject(void);
|
void out_WriteObject(void);
|
||||||
|
|
||||||
#endif // RGBDS_ASM_OUTPUT_H
|
#endif /* RGBDS_ASM_OUTPUT_H */
|
||||||
|
|||||||
@@ -27,13 +27,17 @@ struct Expression {
|
|||||||
uint32_t rpnPatchSize; // Size the expression will take in the object file
|
uint32_t rpnPatchSize; // Size the expression will take in the object file
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determines if an expression is known at assembly time
|
/*
|
||||||
|
* Determines if an expression is known at assembly time
|
||||||
|
*/
|
||||||
static inline bool rpn_isKnown(struct Expression const *expr)
|
static inline bool rpn_isKnown(struct Expression const *expr)
|
||||||
{
|
{
|
||||||
return expr->isKnown;
|
return expr->isKnown;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determines if an expression is a symbol suitable for const diffing
|
/*
|
||||||
|
* Determines if an expression is a symbol suitable for const diffing
|
||||||
|
*/
|
||||||
static inline bool rpn_isSymbol(const struct Expression *expr)
|
static inline bool rpn_isSymbol(const struct Expression *expr)
|
||||||
{
|
{
|
||||||
return expr->isSymbol;
|
return expr->isSymbol;
|
||||||
@@ -63,4 +67,4 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src);
|
|||||||
void rpn_CheckNBit(struct Expression const *expr, uint8_t n);
|
void rpn_CheckNBit(struct Expression const *expr, uint8_t n);
|
||||||
int32_t rpn_GetConstVal(struct Expression const *expr);
|
int32_t rpn_GetConstVal(struct Expression const *expr);
|
||||||
|
|
||||||
#endif // RGBDS_ASM_RPN_H
|
#endif /* RGBDS_ASM_RPN_H */
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ struct Section {
|
|||||||
char *name;
|
char *name;
|
||||||
enum SectionType type;
|
enum SectionType type;
|
||||||
enum SectionModifier modifier;
|
enum SectionModifier modifier;
|
||||||
struct FileStackNode *src; // Where the section was defined
|
struct FileStackNode *src; /* Where the section was defined */
|
||||||
uint32_t fileLine; // Line where the section was defined
|
uint32_t fileLine; /* Line where the section was defined */
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
uint32_t org;
|
uint32_t org;
|
||||||
uint32_t bank;
|
uint32_t bank;
|
||||||
@@ -79,4 +79,4 @@ void sect_PopSection(void);
|
|||||||
|
|
||||||
bool sect_IsSizeKnown(struct Section const NONNULL(name));
|
bool sect_IsSizeKnown(struct Section const NONNULL(name));
|
||||||
|
|
||||||
#endif // RGBDS_SECTION_H
|
#endif
|
||||||
|
|||||||
@@ -32,28 +32,28 @@ enum SymbolType {
|
|||||||
struct Symbol {
|
struct Symbol {
|
||||||
char name[MAXSYMLEN + 1];
|
char name[MAXSYMLEN + 1];
|
||||||
enum SymbolType type;
|
enum SymbolType type;
|
||||||
bool isExported; // Whether the symbol is to be exported
|
bool isExported; /* Whether the symbol is to be exported */
|
||||||
bool isBuiltin; // Whether the symbol is a built-in
|
bool isBuiltin; /* Whether the symbol is a built-in */
|
||||||
struct Section *section;
|
struct Section *section;
|
||||||
struct FileStackNode *src; // Where the symbol was defined
|
struct FileStackNode *src; /* Where the symbol was defined */
|
||||||
uint32_t fileLine; // Line where the symbol was defined
|
uint32_t fileLine; /* Line where the symbol was defined */
|
||||||
|
|
||||||
bool hasCallback;
|
bool hasCallback;
|
||||||
union {
|
union {
|
||||||
// If sym_IsNumeric
|
/* If sym_IsNumeric */
|
||||||
int32_t value;
|
int32_t value;
|
||||||
int32_t (*numCallback)(void);
|
int32_t (*numCallback)(void);
|
||||||
// For SYM_MACRO and SYM_EQUS; TODO: have separate fields
|
/* For SYM_MACRO and SYM_EQUS; TODO: have separate fields */
|
||||||
struct {
|
struct {
|
||||||
size_t macroSize;
|
size_t macroSize;
|
||||||
char *macro;
|
char *macro;
|
||||||
};
|
};
|
||||||
// For SYM_EQUS
|
/* For SYM_EQUS */
|
||||||
char const *(*strCallback)(void);
|
char const *(*strCallback)(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t ID; // ID of the symbol in the object file (-1 if none)
|
uint32_t ID; /* ID of the symbol in the object file (-1 if none) */
|
||||||
struct Symbol *next; // Next object to output in the object file
|
struct Symbol *next; /* Next object to output in the object file */
|
||||||
};
|
};
|
||||||
|
|
||||||
bool sym_IsPC(struct Symbol const *sym);
|
bool sym_IsPC(struct Symbol const *sym);
|
||||||
@@ -98,7 +98,9 @@ static inline bool sym_IsExported(struct Symbol const *sym)
|
|||||||
return sym->isExported;
|
return sym->isExported;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a string equate's value
|
/*
|
||||||
|
* Get a string equate's value
|
||||||
|
*/
|
||||||
static inline char const *sym_GetStringValue(struct Symbol const *sym)
|
static inline char const *sym_GetStringValue(struct Symbol const *sym)
|
||||||
{
|
{
|
||||||
if (sym->hasCallback)
|
if (sym->hasCallback)
|
||||||
@@ -121,11 +123,17 @@ struct Symbol *sym_AddVar(char const *symName, int32_t value);
|
|||||||
uint32_t sym_GetPCValue(void);
|
uint32_t sym_GetPCValue(void);
|
||||||
uint32_t sym_GetConstantSymValue(struct Symbol const *sym);
|
uint32_t sym_GetConstantSymValue(struct Symbol const *sym);
|
||||||
uint32_t sym_GetConstantValue(char const *symName);
|
uint32_t sym_GetConstantValue(char const *symName);
|
||||||
// Find a symbol by exact name, bypassing expansion checks
|
/*
|
||||||
|
* Find a symbol by exact name, bypassing expansion checks
|
||||||
|
*/
|
||||||
struct Symbol *sym_FindExactSymbol(char const *symName);
|
struct Symbol *sym_FindExactSymbol(char const *symName);
|
||||||
// Find a symbol by exact name; may not be scoped, produces an error if it is
|
/*
|
||||||
|
* Find a symbol by exact name; may not be scoped, produces an error if it is
|
||||||
|
*/
|
||||||
struct Symbol *sym_FindUnscopedSymbol(char const *symName);
|
struct Symbol *sym_FindUnscopedSymbol(char const *symName);
|
||||||
// Find a symbol, possibly scoped, by name
|
/*
|
||||||
|
* Find a symbol, possibly scoped, by name
|
||||||
|
*/
|
||||||
struct Symbol *sym_FindScopedSymbol(char const *symName);
|
struct Symbol *sym_FindScopedSymbol(char const *symName);
|
||||||
struct Symbol const *sym_GetPC(void);
|
struct Symbol const *sym_GetPC(void);
|
||||||
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
|
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
|
||||||
@@ -135,8 +143,8 @@ struct Symbol *sym_RedefString(char const *symName, char const *value);
|
|||||||
void sym_Purge(char const *symName);
|
void sym_Purge(char const *symName);
|
||||||
void sym_Init(time_t now);
|
void sym_Init(time_t now);
|
||||||
|
|
||||||
// Functions to save and restore the current symbol scope.
|
/* Functions to save and restore the current symbol scope. */
|
||||||
char const *sym_GetCurrentSymbolScope(void);
|
char const *sym_GetCurrentSymbolScope(void);
|
||||||
void sym_SetCurrentSymbolScope(char const *newScope);
|
void sym_SetCurrentSymbolScope(char const *newScope);
|
||||||
|
|
||||||
#endif // RGBDS_SYMBOL_H
|
#endif /* RGBDS_SYMBOL_H */
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ char const *printChar(int c);
|
|||||||
*/
|
*/
|
||||||
size_t readUTF8Char(uint8_t *dest, char const *src);
|
size_t readUTF8Char(uint8_t *dest, char const *src);
|
||||||
|
|
||||||
#endif // RGBDS_UTIL_H
|
#endif /* RGBDS_UTIL_H */
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ enum WarningID {
|
|||||||
WARNING_ASSERT, // Assertions
|
WARNING_ASSERT, // Assertions
|
||||||
WARNING_BACKWARDS_FOR, // `for` loop with backwards range
|
WARNING_BACKWARDS_FOR, // `for` loop with backwards range
|
||||||
WARNING_BUILTIN_ARG, // Invalid args to builtins
|
WARNING_BUILTIN_ARG, // Invalid args to builtins
|
||||||
WARNING_CHARMAP_REDEF, // Charmap entry re-definition
|
WARNING_CHARMAP_REDEF, // Charmap entry re-definition
|
||||||
WARNING_DIV, // Division undefined behavior
|
WARNING_DIV, // Division undefined behavior
|
||||||
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
|
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
|
||||||
WARNING_EMPTY_MACRO_ARG, // Empty macro argument
|
WARNING_EMPTY_MACRO_ARG, // Empty macro argument
|
||||||
@@ -36,7 +36,6 @@ enum WarningID {
|
|||||||
WARNING_OBSOLETE, // Obsolete things
|
WARNING_OBSOLETE, // Obsolete things
|
||||||
WARNING_SHIFT, // Shifting undefined behavior
|
WARNING_SHIFT, // Shifting undefined behavior
|
||||||
WARNING_SHIFT_AMOUNT, // Strange shift amount
|
WARNING_SHIFT_AMOUNT, // Strange shift amount
|
||||||
WARNING_UNMAPPED_CHAR, // Character without charmap entry
|
|
||||||
WARNING_USER, // User warnings
|
WARNING_USER, // User warnings
|
||||||
|
|
||||||
NB_PLAIN_WARNINGS,
|
NB_PLAIN_WARNINGS,
|
||||||
@@ -91,4 +90,4 @@ _Noreturn void fatalerror(char const *fmt, ...) format_(printf, 1, 2);
|
|||||||
*/
|
*/
|
||||||
void error(char const *fmt, ...) format_(printf, 1, 2);
|
void error(char const *fmt, ...) format_(printf, 1, 2);
|
||||||
|
|
||||||
#endif // WARNING_H
|
#endif
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* Allocator adaptor that interposes construct() calls to convert value-initialization
|
|
||||||
* (which is what you get with e.g. `vector::resize`) into default-initialization (which does not
|
|
||||||
* zero out non-class types).
|
|
||||||
* From
|
|
||||||
* https://stackoverflow.com/questions/21028299/is-this-behavior-of-vectorresizesize-type-n-under-c11-and-boost-container/21028912#21028912
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef DEFAULT_INIT_ALLOC_H
|
|
||||||
#define DEFAULT_INIT_ALLOC_H
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
template<typename T, typename A = std::allocator<T>>
|
|
||||||
class default_init_allocator : public A {
|
|
||||||
using a_t = std::allocator_traits<A>;
|
|
||||||
public:
|
|
||||||
template<typename U>
|
|
||||||
struct rebind {
|
|
||||||
using other = default_init_allocator<U, typename a_t::template rebind_alloc<U>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
using A::A; // Inherit the allocator's constructors
|
|
||||||
|
|
||||||
template<typename U>
|
|
||||||
void construct(U *ptr) noexcept(std::is_nothrow_default_constructible_v<U>) {
|
|
||||||
::new (static_cast<void *>(ptr)) U;
|
|
||||||
}
|
|
||||||
template<typename U, typename... Args>
|
|
||||||
void construct(U *ptr, Args &&...args) {
|
|
||||||
a_t::construct(static_cast<A &>(*this), ptr, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
using DefaultInitVec = std::vector<T, default_init_allocator<T>>;
|
|
||||||
|
|
||||||
#endif // DEFAULT_INIT_ALLOC_H
|
|
||||||
@@ -12,18 +12,10 @@
|
|||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void warn(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
void warn(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
||||||
void warnx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
void warnx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
||||||
|
|
||||||
_Noreturn void err(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
_Noreturn void err(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
||||||
_Noreturn void errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
_Noreturn void errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#endif /* RGBDS_ERROR_H */
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // RGBDS_ERROR_H
|
|
||||||
|
|||||||
8
include/extern/getopt.h
vendored
8
include/extern/getopt.h
vendored
@@ -26,10 +26,6 @@
|
|||||||
#ifndef RGBDS_EXTERN_GETOPT_H
|
#ifndef RGBDS_EXTERN_GETOPT_H
|
||||||
#define RGBDS_EXTERN_GETOPT_H
|
#define RGBDS_EXTERN_GETOPT_H
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern char *musl_optarg;
|
extern char *musl_optarg;
|
||||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
||||||
|
|
||||||
@@ -47,8 +43,4 @@ int musl_getopt_long_only(int argc, char **argv, char const *optstring,
|
|||||||
#define required_argument 1
|
#define required_argument 1
|
||||||
#define optional_argument 2
|
#define optional_argument 2
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} // extern "C"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
2
include/extern/utf8decoder.h
vendored
2
include/extern/utf8decoder.h
vendored
@@ -11,4 +11,4 @@
|
|||||||
|
|
||||||
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte);
|
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte);
|
||||||
|
|
||||||
#endif // EXTERN_UTF8DECODER_H
|
#endif /* EXTERN_UTF8DECODER_H */
|
||||||
|
|||||||
36
include/gfx/gb.h
Normal file
36
include/gfx/gb.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_GFX_GB_H
|
||||||
|
#define RGBDS_GFX_GB_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "gfx/main.h"
|
||||||
|
|
||||||
|
#define XFLIP 0x40
|
||||||
|
#define YFLIP 0x20
|
||||||
|
|
||||||
|
void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb);
|
||||||
|
void output_file(const struct Options *opts, const struct GBImage *gb);
|
||||||
|
int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
|
||||||
|
int tile_size);
|
||||||
|
uint8_t reverse_bits(uint8_t b);
|
||||||
|
void xflip(uint8_t *tile, uint8_t *tile_xflip, int tile_size);
|
||||||
|
void yflip(uint8_t *tile, uint8_t *tile_yflip, int tile_size);
|
||||||
|
int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
|
||||||
|
int tile_size, int *flags);
|
||||||
|
void create_mapfiles(const struct Options *opts, struct GBImage *gb,
|
||||||
|
struct Mapfile *tilemap, struct Mapfile *attrmap);
|
||||||
|
void output_tilemap_file(const struct Options *opts,
|
||||||
|
const struct Mapfile *tilemap);
|
||||||
|
void output_attrmap_file(const struct Options *opts,
|
||||||
|
const struct Mapfile *attrmap);
|
||||||
|
void output_palette_file(const struct Options *opts,
|
||||||
|
const struct RawIndexedImage *raw_image);
|
||||||
|
|
||||||
|
#endif
|
||||||
91
include/gfx/main.h
Normal file
91
include/gfx/main.h
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_GFX_MAIN_H
|
||||||
|
#define RGBDS_GFX_MAIN_H
|
||||||
|
|
||||||
|
#include <png.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "error.h"
|
||||||
|
|
||||||
|
struct Options {
|
||||||
|
bool debug;
|
||||||
|
bool verbose;
|
||||||
|
bool hardfix;
|
||||||
|
bool fix;
|
||||||
|
bool horizontal;
|
||||||
|
bool mirror;
|
||||||
|
bool unique;
|
||||||
|
bool colorcurve;
|
||||||
|
unsigned int trim;
|
||||||
|
char *tilemapfile;
|
||||||
|
bool tilemapout;
|
||||||
|
char *attrmapfile;
|
||||||
|
bool attrmapout;
|
||||||
|
char *palfile;
|
||||||
|
bool palout;
|
||||||
|
char *outfile;
|
||||||
|
char *infile;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RGBColor {
|
||||||
|
uint8_t red;
|
||||||
|
uint8_t green;
|
||||||
|
uint8_t blue;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ImageOptions {
|
||||||
|
bool horizontal;
|
||||||
|
unsigned int trim;
|
||||||
|
char *tilemapfile;
|
||||||
|
bool tilemapout;
|
||||||
|
char *attrmapfile;
|
||||||
|
bool attrmapout;
|
||||||
|
char *palfile;
|
||||||
|
bool palout;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PNGImage {
|
||||||
|
png_struct *png;
|
||||||
|
png_info *info;
|
||||||
|
|
||||||
|
png_byte **data;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
png_byte depth;
|
||||||
|
png_byte type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RawIndexedImage {
|
||||||
|
uint8_t **data;
|
||||||
|
struct RGBColor *palette;
|
||||||
|
int num_colors;
|
||||||
|
unsigned int width;
|
||||||
|
unsigned int height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBImage {
|
||||||
|
uint8_t *data;
|
||||||
|
int size;
|
||||||
|
bool horizontal;
|
||||||
|
int trim;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mapfile {
|
||||||
|
uint8_t *data;
|
||||||
|
int size;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern int depth, colors;
|
||||||
|
|
||||||
|
#include "gfx/makepng.h"
|
||||||
|
#include "gfx/gb.h"
|
||||||
|
|
||||||
|
#endif /* RGBDS_GFX_MAIN_H */
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_MAIN_HPP
|
|
||||||
#define RGBDS_GFX_MAIN_HPP
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "helpers.h"
|
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
|
||||||
|
|
||||||
struct Options {
|
|
||||||
uint16_t reversedWidth = 0; // -r, in tiles
|
|
||||||
bool reverse() const { return reversedWidth != 0; }
|
|
||||||
|
|
||||||
bool useColorCurve = false; // -C
|
|
||||||
bool allowMirroring = false; // -m
|
|
||||||
bool allowDedup = false; // -u
|
|
||||||
bool columnMajor = false; // -Z, previously -h
|
|
||||||
uint8_t verbosity = 0; // -v
|
|
||||||
|
|
||||||
std::string attrmap{}; // -a, -A
|
|
||||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
|
||||||
enum {
|
|
||||||
NO_SPEC,
|
|
||||||
EXPLICIT,
|
|
||||||
EMBEDDED,
|
|
||||||
} palSpecType = NO_SPEC; // -c
|
|
||||||
std::vector<std::array<Rgba, 4>> palSpec{};
|
|
||||||
uint8_t bitDepth = 2; // -d
|
|
||||||
struct {
|
|
||||||
uint16_t left;
|
|
||||||
uint16_t top;
|
|
||||||
uint16_t width;
|
|
||||||
uint16_t height;
|
|
||||||
} inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS)
|
|
||||||
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
|
|
||||||
uint8_t nbPalettes = 8; // -n
|
|
||||||
std::string output{}; // -o
|
|
||||||
std::string palettes{}; // -p, -P
|
|
||||||
std::string palmap{}; // -q, -Q
|
|
||||||
uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth;
|
|
||||||
std::string tilemap{}; // -t, -T
|
|
||||||
uint64_t trim = 0; // -x
|
|
||||||
|
|
||||||
std::string input{}; // positional arg
|
|
||||||
|
|
||||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
|
||||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
|
||||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
|
||||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
|
||||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
|
||||||
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far
|
|
||||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
|
||||||
format_(printf, 3, 4) void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
|
||||||
|
|
||||||
mutable bool hasTransparentPixels = false;
|
|
||||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Options options;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Prints the error count, and exits with failure
|
|
||||||
*/
|
|
||||||
[[noreturn]] void giveUp();
|
|
||||||
/*
|
|
||||||
* Prints a warning, and does not change the error count
|
|
||||||
*/
|
|
||||||
void warning(char const *fmt, ...);
|
|
||||||
/*
|
|
||||||
* Prints an error, and increments the error count
|
|
||||||
*/
|
|
||||||
void error(char const *fmt, ...);
|
|
||||||
/*
|
|
||||||
* Prints a fatal error, increments the error count, and gives up
|
|
||||||
*/
|
|
||||||
[[noreturn]] void fatal(char const *fmt, ...);
|
|
||||||
|
|
||||||
struct Palette {
|
|
||||||
// An array of 4 GBC-native (RGB555) colors
|
|
||||||
std::array<uint16_t, 4> colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
|
||||||
|
|
||||||
void addColor(uint16_t color);
|
|
||||||
uint8_t indexOf(uint16_t color) const;
|
|
||||||
uint16_t &operator[](size_t index) { return colors[index]; }
|
|
||||||
uint16_t const &operator[](size_t index) const { return colors[index]; }
|
|
||||||
|
|
||||||
decltype(colors)::iterator begin();
|
|
||||||
decltype(colors)::iterator end();
|
|
||||||
decltype(colors)::const_iterator begin() const;
|
|
||||||
decltype(colors)::const_iterator end() const;
|
|
||||||
|
|
||||||
uint8_t size() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
template<typename T, T... i>
|
|
||||||
static constexpr auto flipTable(std::integer_sequence<T, i...>) {
|
|
||||||
return std::array{[](uint8_t byte) {
|
|
||||||
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
|
|
||||||
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
|
|
||||||
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
|
|
||||||
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
|
|
||||||
return byte;
|
|
||||||
}(i)...};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
|
||||||
static constexpr auto flipTable = detail::flipTable(std::make_integer_sequence<uint16_t, 256>());
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_MAIN_HPP
|
|
||||||
21
include/gfx/makepng.h
Normal file
21
include/gfx/makepng.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_GFX_PNG_H
|
||||||
|
#define RGBDS_GFX_PNG_H
|
||||||
|
|
||||||
|
#include "gfx/main.h"
|
||||||
|
|
||||||
|
struct RawIndexedImage *input_png_file(const struct Options *opts,
|
||||||
|
struct ImageOptions *png_options);
|
||||||
|
void output_png_file(const struct Options *opts,
|
||||||
|
const struct ImageOptions *png_options,
|
||||||
|
const struct RawIndexedImage *raw_image);
|
||||||
|
void destroy_raw_image(struct RawIndexedImage **raw_image_ptr_ptr);
|
||||||
|
|
||||||
|
#endif /* RGBDS_GFX_PNG_H */
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_PACKING_HPP
|
|
||||||
#define RGBDS_GFX_PAL_PACKING_HPP
|
|
||||||
|
|
||||||
#include <tuple>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "defaultinitalloc.hpp"
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
|
|
||||||
struct Palette;
|
|
||||||
class ProtoPalette;
|
|
||||||
|
|
||||||
namespace packing {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns which palette each proto-palette maps to, and how many palettes are necessary
|
|
||||||
*/
|
|
||||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
|
||||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_PAL_PACKING_HPP
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_SORTING_HPP
|
|
||||||
#define RGBDS_GFX_PAL_SORTING_HPP
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <optional>
|
|
||||||
#include <png.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
|
||||||
|
|
||||||
struct Palette;
|
|
||||||
|
|
||||||
namespace sorting {
|
|
||||||
|
|
||||||
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
|
|
||||||
png_byte *palAlpha);
|
|
||||||
void grayscale(std::vector<Palette> &palettes,
|
|
||||||
std::array<std::optional<Rgba>, 0x8001> const &colors);
|
|
||||||
void rgb(std::vector<Palette> &palettes);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_PAL_SORTING_HPP
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
|
||||||
#define RGBDS_GFX_PAL_SPEC_HPP
|
|
||||||
|
|
||||||
void parseInlinePalSpec(char const * const arg);
|
|
||||||
void parseExternalPalSpec(char const *arg);
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_PAL_SPEC_HPP
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_CONVERT_HPP
|
|
||||||
#define RGBDS_GFX_CONVERT_HPP
|
|
||||||
|
|
||||||
void process();
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_CONVERT_HPP
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_PROTO_PALETTE_HPP
|
|
||||||
#define RGBDS_GFX_PROTO_PALETTE_HPP
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
class ProtoPalette {
|
|
||||||
// Up to 4 colors, sorted, and where SIZE_MAX means the slot is empty
|
|
||||||
// (OK because it's not a valid color index)
|
|
||||||
// Sorting is done on the raw numerical values to lessen `compare`'s complexity
|
|
||||||
std::array<uint16_t, 4> _colorIndices{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
|
||||||
|
|
||||||
public:
|
|
||||||
/*
|
|
||||||
* Adds the specified color to the set
|
|
||||||
* Returns false if the set is full
|
|
||||||
*/
|
|
||||||
bool add(uint16_t color);
|
|
||||||
|
|
||||||
enum ComparisonResult {
|
|
||||||
NEITHER,
|
|
||||||
WE_BIGGER,
|
|
||||||
THEY_BIGGER = -1,
|
|
||||||
};
|
|
||||||
ComparisonResult compare(ProtoPalette const &other) const;
|
|
||||||
|
|
||||||
size_t size() const;
|
|
||||||
bool empty() const;
|
|
||||||
|
|
||||||
decltype(_colorIndices)::const_iterator begin() const;
|
|
||||||
decltype(_colorIndices)::const_iterator end() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_PROTO_PALETTE_HPP
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_REVERSE_HPP
|
|
||||||
#define RGBDS_GFX_REVERSE_HPP
|
|
||||||
|
|
||||||
void reverse();
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_REVERSE_HPP
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_GFX_RGBA_HPP
|
|
||||||
#define RGBDS_GFX_RGBA_HPP
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
struct Rgba {
|
|
||||||
uint8_t red;
|
|
||||||
uint8_t green;
|
|
||||||
uint8_t blue;
|
|
||||||
uint8_t alpha;
|
|
||||||
|
|
||||||
constexpr Rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
|
||||||
: red(r), green(g), blue(b), alpha(a) {}
|
|
||||||
/*
|
|
||||||
* Constructs the color from a "packed" RGBA representation (0xRRGGBBAA)
|
|
||||||
*/
|
|
||||||
explicit constexpr Rgba(uint32_t rgba = 0)
|
|
||||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
|
||||||
|
|
||||||
static constexpr Rgba fromCGBColor(uint16_t cgbColor) {
|
|
||||||
constexpr auto _5to8 = [](uint8_t fiveBpp) -> uint8_t {
|
|
||||||
fiveBpp &= 0b11111; // For caller's convenience
|
|
||||||
return fiveBpp << 3 | fiveBpp >> 2;
|
|
||||||
};
|
|
||||||
return {_5to8(cgbColor), _5to8(cgbColor >> 5), _5to8(cgbColor >> 10),
|
|
||||||
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF)};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns this RGBA as a 32-bit number that can be printed in hex (`%08x`) to yield its CSS
|
|
||||||
* representation
|
|
||||||
*/
|
|
||||||
uint32_t toCSS() const {
|
|
||||||
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
|
|
||||||
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
|
||||||
}
|
|
||||||
friend bool operator!=(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() != rhs.toCSS(); }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
|
||||||
* Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
|
||||||
*/
|
|
||||||
static constexpr uint16_t transparent = 0b1'00000'00000'00000;
|
|
||||||
|
|
||||||
static constexpr uint8_t transparency_threshold = 0x10;
|
|
||||||
bool isTransparent() const { return alpha < transparency_threshold; }
|
|
||||||
static constexpr uint8_t opacity_threshold = 0xF0;
|
|
||||||
bool isOpaque() const { return alpha >= opacity_threshold; }
|
|
||||||
/*
|
|
||||||
* Computes the equivalent CGB color, respects the color curve depending on options
|
|
||||||
*/
|
|
||||||
uint16_t cgbColor() const;
|
|
||||||
|
|
||||||
bool isGray() const { return red == green && green == blue; }
|
|
||||||
uint8_t grayIndex() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_RGBA_HPP
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Generic hashmap implementation (C++ templates are calling...)
|
/* Generic hashmap implementation (C++ templates are calling...) */
|
||||||
#ifndef RGBDS_LINK_HASHMAP_H
|
#ifndef RGBDS_LINK_HASHMAP_H
|
||||||
#define RGBDS_LINK_HASHMAP_H
|
#define RGBDS_LINK_HASHMAP_H
|
||||||
|
|
||||||
@@ -18,10 +18,10 @@
|
|||||||
static_assert(HALF_HASH_NB_BITS * 2 == HASH_NB_BITS, "");
|
static_assert(HALF_HASH_NB_BITS * 2 == HASH_NB_BITS, "");
|
||||||
#define HASHMAP_NB_BUCKETS (1 << HALF_HASH_NB_BITS)
|
#define HASHMAP_NB_BUCKETS (1 << HALF_HASH_NB_BITS)
|
||||||
|
|
||||||
// HashMapEntry is internal, please do not attempt to use it
|
/* HashMapEntry is internal, please do not attempt to use it */
|
||||||
typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
|
typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Adds an element to a hashmap.
|
* Adds an element to a hashmap.
|
||||||
* @warning Adding a new element with an already-present key will not cause an
|
* @warning Adding a new element with an already-present key will not cause an
|
||||||
* error, this must be handled externally.
|
* error, this must be handled externally.
|
||||||
@@ -33,7 +33,7 @@ typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
|
|||||||
*/
|
*/
|
||||||
void **hash_AddElement(HashMap map, char const *key, void *element);
|
void **hash_AddElement(HashMap map, char const *key, void *element);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Removes an element from a hashmap.
|
* Removes an element from a hashmap.
|
||||||
* @param map The HashMap to remove the element from
|
* @param map The HashMap to remove the element from
|
||||||
* @param key The key to search the element with
|
* @param key The key to search the element with
|
||||||
@@ -41,7 +41,7 @@ void **hash_AddElement(HashMap map, char const *key, void *element);
|
|||||||
*/
|
*/
|
||||||
bool hash_RemoveElement(HashMap map, char const *key);
|
bool hash_RemoveElement(HashMap map, char const *key);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Finds an element in a hashmap, and returns a pointer to its value field.
|
* Finds an element in a hashmap, and returns a pointer to its value field.
|
||||||
* @param map The map to consider the elements of
|
* @param map The map to consider the elements of
|
||||||
* @param key The key to search an element for
|
* @param key The key to search an element for
|
||||||
@@ -49,7 +49,7 @@ bool hash_RemoveElement(HashMap map, char const *key);
|
|||||||
*/
|
*/
|
||||||
void **hash_GetNode(HashMap const map, char const *key);
|
void **hash_GetNode(HashMap const map, char const *key);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Finds an element in a hashmap.
|
* Finds an element in a hashmap.
|
||||||
* @param map The map to consider the elements of
|
* @param map The map to consider the elements of
|
||||||
* @param key The key to search an element for
|
* @param key The key to search an element for
|
||||||
@@ -58,7 +58,7 @@ void **hash_GetNode(HashMap const map, char const *key);
|
|||||||
*/
|
*/
|
||||||
void *hash_GetElement(HashMap const map, char const *key);
|
void *hash_GetElement(HashMap const map, char const *key);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Executes a function on each element in a hashmap.
|
* Executes a function on each element in a hashmap.
|
||||||
* @param map The map to consider the elements of
|
* @param map The map to consider the elements of
|
||||||
* @param func The function to run. The first argument will be the element,
|
* @param func The function to run. The first argument will be the element,
|
||||||
@@ -67,11 +67,11 @@ void *hash_GetElement(HashMap const map, char const *key);
|
|||||||
*/
|
*/
|
||||||
void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg);
|
void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Cleanly empties a hashmap from its contents.
|
* Cleanly empties a hashmap from its contents.
|
||||||
* This does not `free` the data structure itself!
|
* This does not `free` the data structure itself!
|
||||||
* @param map The map to empty
|
* @param map The map to empty
|
||||||
*/
|
*/
|
||||||
void hash_EmptyMap(HashMap map);
|
void hash_EmptyMap(HashMap map);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_HASHMAP_H
|
#endif /* RGBDS_LINK_HASHMAP_H */
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
#define attr_(...)
|
#define attr_(...)
|
||||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||||
// Note that executing this is undefined behavior (declared _Noreturn, but does return)
|
// Note that executing this is undefined behavior (declared _Noreturn, but does return)
|
||||||
static inline _Noreturn void unreachable_(void) {}
|
static inline _Noreturn unreachable_(void) {}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Use builtins whenever possible, and shim them otherwise
|
// Use builtins whenever possible, and shim them otherwise
|
||||||
@@ -93,4 +93,4 @@
|
|||||||
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
|
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
|
||||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
|
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
|
||||||
|
|
||||||
#endif // HELPERS_H
|
#endif /* HELPERS_H */
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_ITERTOOLS_HPP
|
|
||||||
#define RGBDS_ITERTOOLS_HPP
|
|
||||||
|
|
||||||
#include <tuple>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
template<typename... Ts>
|
|
||||||
static inline void report() {
|
|
||||||
puts(__PRETTY_FUNCTION__);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is not a fully generic implementation; its current use cases only require for-loop behavior.
|
|
||||||
// We also assume that all iterators have the same length.
|
|
||||||
template<typename... Iters>
|
|
||||||
class Zip {
|
|
||||||
std::tuple<Iters...> _iters;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Zip(std::tuple<Iters...> &&iters) : _iters(iters) {}
|
|
||||||
|
|
||||||
Zip &operator++() {
|
|
||||||
std::apply([](auto &&...it) { (++it, ...); }, _iters);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto operator*() const {
|
|
||||||
return std::apply([](auto &&...it) { return std::tuple<decltype(*it)...>(*it...); },
|
|
||||||
_iters);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend auto operator==(Zip const &lhs, Zip const &rhs) {
|
|
||||||
return std::get<0>(lhs._iters) == std::get<0>(rhs._iters);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend auto operator!=(Zip const &lhs, Zip const &rhs) {
|
|
||||||
return std::get<0>(lhs._iters) != std::get<0>(rhs._iters);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
template<typename... Containers>
|
|
||||||
class ZipContainer {
|
|
||||||
std::tuple<Containers...> _containers;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ZipContainer(Containers &&...containers)
|
|
||||||
: _containers(std::forward<Containers>(containers)...) {}
|
|
||||||
|
|
||||||
auto begin() {
|
|
||||||
return Zip(std::apply(
|
|
||||||
[](auto &&...containers) {
|
|
||||||
using std::begin;
|
|
||||||
return std::make_tuple(begin(containers)...);
|
|
||||||
},
|
|
||||||
_containers));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end() {
|
|
||||||
return Zip(std::apply(
|
|
||||||
[](auto &&...containers) {
|
|
||||||
using std::end;
|
|
||||||
return std::make_tuple(end(containers)...);
|
|
||||||
},
|
|
||||||
_containers));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Take ownership of objects and rvalue refs passed to us, but not lvalue refs
|
|
||||||
template<typename T>
|
|
||||||
using Holder = std::conditional_t<std::is_lvalue_reference_v<T>, T,
|
|
||||||
std::remove_cv_t<std::remove_reference_t<T>>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Does the same number of iterations as the first container's iterator!
|
|
||||||
template<typename... Containers>
|
|
||||||
static constexpr auto zip(Containers &&...cs) {
|
|
||||||
return detail::ZipContainer<detail::Holder<Containers>...>(std::forward<Containers>(cs)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // RGBDS_ITERTOOLS_HPP
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Assigning all sections a place
|
/* Assigning all sections a place */
|
||||||
#ifndef RGBDS_LINK_ASSIGN_H
|
#ifndef RGBDS_LINK_ASSIGN_H
|
||||||
#define RGBDS_LINK_ASSIGN_H
|
#define RGBDS_LINK_ASSIGN_H
|
||||||
|
|
||||||
@@ -14,10 +14,14 @@
|
|||||||
|
|
||||||
extern uint64_t nbSectionsToAssign;
|
extern uint64_t nbSectionsToAssign;
|
||||||
|
|
||||||
// Assigns all sections a slice of the address space
|
/**
|
||||||
|
* Assigns all sections a slice of the address space
|
||||||
|
*/
|
||||||
void assign_AssignSections(void);
|
void assign_AssignSections(void);
|
||||||
|
|
||||||
// `free`s all assignment memory that was allocated
|
/**
|
||||||
|
* `free`s all assignment memory that was allocated.
|
||||||
|
*/
|
||||||
void assign_Cleanup(void);
|
void assign_Cleanup(void);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_ASSIGN_H
|
#endif /* RGBDS_LINK_ASSIGN_H */
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Declarations that all modules use, as well as `main` and related
|
/* Declarations that all modules use, as well as `main` and related */
|
||||||
#ifndef RGBDS_LINK_MAIN_H
|
#ifndef RGBDS_LINK_MAIN_H
|
||||||
#define RGBDS_LINK_MAIN_H
|
#define RGBDS_LINK_MAIN_H
|
||||||
|
|
||||||
@@ -16,11 +16,10 @@
|
|||||||
|
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
|
|
||||||
// Variables related to CLI options
|
/* Variables related to CLI options */
|
||||||
extern bool isDmgMode;
|
extern bool isDmgMode;
|
||||||
extern char *linkerScriptName;
|
extern char *linkerScriptName;
|
||||||
extern char const *mapFileName;
|
extern char const *mapFileName;
|
||||||
extern bool noSymInMap;
|
|
||||||
extern char const *symFileName;
|
extern char const *symFileName;
|
||||||
extern char const *overlayFileName;
|
extern char const *overlayFileName;
|
||||||
extern char const *outputFileName;
|
extern char const *outputFileName;
|
||||||
@@ -35,7 +34,7 @@ extern bool disablePadding;
|
|||||||
|
|
||||||
struct FileStackNode {
|
struct FileStackNode {
|
||||||
struct FileStackNode *parent;
|
struct FileStackNode *parent;
|
||||||
// Line at which the parent context was exited; meaningless for the root level
|
/* Line at which the parent context was exited; meaningless for the root level */
|
||||||
uint32_t lineNo;
|
uint32_t lineNo;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@@ -44,21 +43,21 @@ struct FileStackNode {
|
|||||||
NODE_MACRO,
|
NODE_MACRO,
|
||||||
} type;
|
} type;
|
||||||
union {
|
union {
|
||||||
char *name; // NODE_FILE, NODE_MACRO
|
char *name; /* NODE_FILE, NODE_MACRO */
|
||||||
struct { // NODE_REPT
|
struct { /* NODE_REPT */
|
||||||
uint32_t reptDepth;
|
uint32_t reptDepth;
|
||||||
uint32_t *iters;
|
uint32_t *iters;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper macro for printing verbose-mode messages
|
/* Helper macro for printing verbose-mode messages */
|
||||||
#define verbosePrint(...) do { \
|
#define verbosePrint(...) do { \
|
||||||
if (beVerbose) \
|
if (beVerbose) \
|
||||||
fprintf(stderr, __VA_ARGS__); \
|
fprintf(stderr, __VA_ARGS__); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Dump a file stack to stderr
|
* Dump a file stack to stderr
|
||||||
* @param node The leaf node to dump the context of
|
* @param node The leaf node to dump the context of
|
||||||
*/
|
*/
|
||||||
@@ -73,7 +72,7 @@ void error(struct FileStackNode const *where, uint32_t lineNo,
|
|||||||
_Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo,
|
_Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo,
|
||||||
char const *fmt, ...) format_(printf, 3, 4);
|
char const *fmt, ...) format_(printf, 3, 4);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Opens a file if specified, and aborts on error.
|
* Opens a file if specified, and aborts on error.
|
||||||
* @param fileName The name of the file to open; if NULL, no file will be opened
|
* @param fileName The name of the file to open; if NULL, no file will be opened
|
||||||
* @param mode The mode to open the file with
|
* @param mode The mode to open the file with
|
||||||
@@ -87,4 +86,4 @@ FILE *openFile(char const *fileName, char const *mode);
|
|||||||
fclose(tmp); \
|
fclose(tmp); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#endif // RGBDS_LINK_MAIN_H
|
#endif /* RGBDS_LINK_MAIN_H */
|
||||||
|
|||||||
@@ -6,37 +6,37 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Declarations related to processing of object (.o) files
|
/* Declarations related to processing of object (.o) files */
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_OBJECT_H
|
#ifndef RGBDS_LINK_OBJECT_H
|
||||||
#define RGBDS_LINK_OBJECT_H
|
#define RGBDS_LINK_OBJECT_H
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Read an object (.o) file, and add its info to the data structures.
|
* Read an object (.o) file, and add its info to the data structures.
|
||||||
* @param fileName A path to the object file to be read
|
* @param fileName A path to the object file to be read
|
||||||
* @param i The ID of the file
|
* @param i The ID of the file
|
||||||
*/
|
*/
|
||||||
void obj_ReadFile(char const *fileName, unsigned int i);
|
void obj_ReadFile(char const *fileName, unsigned int i);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Perform validation on the object files' contents
|
* Perform validation on the object files' contents
|
||||||
*/
|
*/
|
||||||
void obj_DoSanityChecks(void);
|
void obj_DoSanityChecks(void);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Evaluate all assertions
|
* Evaluate all assertions
|
||||||
*/
|
*/
|
||||||
void obj_CheckAssertions(void);
|
void obj_CheckAssertions(void);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Sets up object file reading
|
* Sets up object file reading
|
||||||
* @param nbFiles The number of object files that will be read
|
* @param nbFiles The number of object files that will be read
|
||||||
*/
|
*/
|
||||||
void obj_Setup(unsigned int nbFiles);
|
void obj_Setup(unsigned int nbFiles);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* `free`s all object memory that was allocated.
|
* `free`s all object memory that was allocated.
|
||||||
*/
|
*/
|
||||||
void obj_Cleanup(void);
|
void obj_Cleanup(void);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_OBJECT_H
|
#endif /* RGBDS_LINK_OBJECT_H */
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Outputting the result of linking
|
/* Outputting the result of linking */
|
||||||
#ifndef RGBDS_LINK_OUTPUT_H
|
#ifndef RGBDS_LINK_OUTPUT_H
|
||||||
#define RGBDS_LINK_OUTPUT_H
|
#define RGBDS_LINK_OUTPUT_H
|
||||||
|
|
||||||
@@ -14,22 +14,22 @@
|
|||||||
|
|
||||||
#include "link/section.h"
|
#include "link/section.h"
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Registers a section for output.
|
* Registers a section for output.
|
||||||
* @param section The section to add
|
* @param section The section to add
|
||||||
*/
|
*/
|
||||||
void out_AddSection(struct Section const *section);
|
void out_AddSection(struct Section const *section);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Finds an assigned section overlapping another one.
|
* Finds an assigned section overlapping another one.
|
||||||
* @param section The section that is being overlapped
|
* @param section The section that is being overlapped
|
||||||
* @return A section overlapping it
|
* @return A section overlapping it
|
||||||
*/
|
*/
|
||||||
struct Section const *out_OverlappingSection(struct Section const *section);
|
struct Section const *out_OverlappingSection(struct Section const *section);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Writes all output (bin, sym, map) files.
|
* Writes all output (bin, sym, map) files.
|
||||||
*/
|
*/
|
||||||
void out_WriteFiles(void);
|
void out_WriteFiles(void);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_OUTPUT_H
|
#endif /* RGBDS_LINK_OUTPUT_H */
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Applying patches to SECTIONs
|
/* Applying patches to SECTIONs */
|
||||||
#ifndef RGBDS_LINK_PATCH_H
|
#ifndef RGBDS_LINK_PATCH_H
|
||||||
#define RGBDS_LINK_PATCH_H
|
#define RGBDS_LINK_PATCH_H
|
||||||
|
|
||||||
@@ -27,15 +27,15 @@ struct Assertion {
|
|||||||
struct Assertion *next;
|
struct Assertion *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Checks all assertions
|
* Checks all assertions
|
||||||
* @return true if assertion failed
|
* @return true if assertion failed
|
||||||
*/
|
*/
|
||||||
void patch_CheckAssertions(struct Assertion *assertion);
|
void patch_CheckAssertions(struct Assertion *assertion);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Applies all SECTIONs' patches to them
|
* Applies all SECTIONs' patches to them
|
||||||
*/
|
*/
|
||||||
void patch_ApplyPatches(void);
|
void patch_ApplyPatches(void);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_PATCH_H
|
#endif /* RGBDS_LINK_PATCH_H */
|
||||||
|
|||||||
@@ -6,33 +6,31 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Parsing a linker script
|
/* Parsing a linker script */
|
||||||
#ifndef RGBDS_LINK_SCRIPT_H
|
#ifndef RGBDS_LINK_SCRIPT_H
|
||||||
#define RGBDS_LINK_SCRIPT_H
|
#define RGBDS_LINK_SCRIPT_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "linkdefs.h"
|
|
||||||
|
|
||||||
extern FILE * linkerScript;
|
extern FILE * linkerScript;
|
||||||
|
|
||||||
struct SectionPlacement {
|
struct SectionPlacement {
|
||||||
struct Section *section;
|
struct Section *section;
|
||||||
enum SectionType type;
|
|
||||||
uint16_t org;
|
uint16_t org;
|
||||||
uint32_t bank;
|
uint32_t bank;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern uint64_t script_lineNo;
|
extern uint64_t script_lineNo;
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Parses the linker script to return the next section constraint
|
* Parses the linker script to return the next section constraint
|
||||||
* @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed
|
* @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed
|
||||||
*/
|
*/
|
||||||
struct SectionPlacement *script_NextSection(void);
|
struct SectionPlacement *script_NextSection(void);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* `free`s all assignment memory that was allocated.
|
* `free`s all assignment memory that was allocated.
|
||||||
*/
|
*/
|
||||||
void script_Cleanup(void);
|
void script_Cleanup(void);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_SCRIPT_H
|
#endif /* RGBDS_LINK_SCRIPT_H */
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Assigning all sections a place
|
|
||||||
#ifndef RGBDS_LINK_SDAS_OBJ_H
|
|
||||||
#define RGBDS_LINK_SDAS_OBJ_H
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
struct FileStackNode;
|
|
||||||
|
|
||||||
void sdobj_ReadFile(struct FileStackNode const *fileName, FILE *file);
|
|
||||||
|
|
||||||
#endif // RGBDS_LINK_SDAS_OBJ_H
|
|
||||||
@@ -6,11 +6,11 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Declarations manipulating symbols
|
/* Declarations manipulating symbols */
|
||||||
#ifndef RGBDS_LINK_SECTION_H
|
#ifndef RGBDS_LINK_SECTION_H
|
||||||
#define RGBDS_LINK_SECTION_H
|
#define RGBDS_LINK_SECTION_H
|
||||||
|
|
||||||
// GUIDELINE: external code MUST NOT BE AWARE of the data structure used!!
|
/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@@ -41,29 +41,27 @@ struct Patch {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Section {
|
struct Section {
|
||||||
// Info contained in the object files
|
/* Info contained in the object files */
|
||||||
char *name;
|
char *name;
|
||||||
uint16_t size;
|
uint16_t size;
|
||||||
uint16_t offset;
|
uint16_t offset;
|
||||||
enum SectionType type;
|
enum SectionType type;
|
||||||
enum SectionModifier modifier;
|
enum SectionModifier modifier;
|
||||||
bool isAddressFixed;
|
bool isAddressFixed;
|
||||||
// This `struct`'s address in ROM.
|
|
||||||
// Importantly for fragments, this does not include `offset`!
|
|
||||||
uint16_t org;
|
uint16_t org;
|
||||||
bool isBankFixed;
|
bool isBankFixed;
|
||||||
uint32_t bank;
|
uint32_t bank;
|
||||||
bool isAlignFixed;
|
bool isAlignFixed;
|
||||||
uint16_t alignMask;
|
uint16_t alignMask;
|
||||||
uint16_t alignOfs;
|
uint16_t alignOfs;
|
||||||
uint8_t *data; // Array of size `size`
|
uint8_t *data; /* Array of size `size`*/
|
||||||
uint32_t nbPatches;
|
uint32_t nbPatches;
|
||||||
struct Patch *patches;
|
struct Patch *patches;
|
||||||
// Extra info computed during linking
|
/* Extra info computed during linking */
|
||||||
struct Symbol **fileSymbols;
|
struct Symbol **fileSymbols;
|
||||||
uint32_t nbSymbols;
|
uint32_t nbSymbols;
|
||||||
struct Symbol **symbols;
|
struct Symbol const **symbols;
|
||||||
struct Section *nextu; // The next "component" of this unionized sect
|
struct Section *nextu; /* The next "component" of this unionized sect */
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -76,27 +74,27 @@ struct Section {
|
|||||||
*/
|
*/
|
||||||
void sect_ForEach(void (*callback)(struct Section *, void *), void *arg);
|
void sect_ForEach(void (*callback)(struct Section *, void *), void *arg);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Registers a section to be processed.
|
* Registers a section to be processed.
|
||||||
* @param section The section to register.
|
* @param section The section to register.
|
||||||
*/
|
*/
|
||||||
void sect_AddSection(struct Section *section);
|
void sect_AddSection(struct Section *section);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Finds a section by its name.
|
* Finds a section by its name.
|
||||||
* @param name The name of the section to look for
|
* @param name The name of the section to look for
|
||||||
* @return A pointer to the section, or NULL if it wasn't found
|
* @return A pointer to the section, or NULL if it wasn't found
|
||||||
*/
|
*/
|
||||||
struct Section *sect_GetSection(char const *name);
|
struct Section *sect_GetSection(char const *name);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* `free`s all section memory that was allocated.
|
* `free`s all section memory that was allocated.
|
||||||
*/
|
*/
|
||||||
void sect_CleanupSections(void);
|
void sect_CleanupSections(void);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Checks if all sections meet reasonable criteria, such as max size
|
* Checks if all sections meet reasonable criteria, such as max size
|
||||||
*/
|
*/
|
||||||
void sect_DoSanityChecks(void);
|
void sect_DoSanityChecks(void);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_SECTION_H
|
#endif /* RGBDS_LINK_SECTION_H */
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Declarations manipulating symbols
|
/* Declarations manipulating symbols */
|
||||||
#ifndef RGBDS_LINK_SYMBOL_H
|
#ifndef RGBDS_LINK_SYMBOL_H
|
||||||
#define RGBDS_LINK_SYMBOL_H
|
#define RGBDS_LINK_SYMBOL_H
|
||||||
|
|
||||||
// GUIDELINE: external code MUST NOT BE AWARE of the data structure used!!
|
/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
struct FileStackNode;
|
struct FileStackNode;
|
||||||
|
|
||||||
struct Symbol {
|
struct Symbol {
|
||||||
// Info contained in the object files
|
/* Info contained in the object files */
|
||||||
char *name;
|
char *name;
|
||||||
enum ExportLevel type;
|
enum ExportLevel type;
|
||||||
char const *objFileName;
|
char const *objFileName;
|
||||||
@@ -27,11 +27,10 @@ struct Symbol {
|
|||||||
int32_t lineNo;
|
int32_t lineNo;
|
||||||
int32_t sectionID;
|
int32_t sectionID;
|
||||||
union {
|
union {
|
||||||
// Both types must be identical
|
|
||||||
int32_t offset;
|
int32_t offset;
|
||||||
int32_t value;
|
int32_t value;
|
||||||
};
|
};
|
||||||
// Extra info computed during linking
|
/* Extra info computed during linking */
|
||||||
struct Section *section;
|
struct Section *section;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -47,16 +46,16 @@ void sym_ForEach(void (*callback)(struct Symbol *, void *), void *arg);
|
|||||||
|
|
||||||
void sym_AddSymbol(struct Symbol *symbol);
|
void sym_AddSymbol(struct Symbol *symbol);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Finds a symbol in all the defined symbols.
|
* Finds a symbol in all the defined symbols.
|
||||||
* @param name The name of the symbol to look for
|
* @param name The name of the symbol to look for
|
||||||
* @return A pointer to the symbol, or NULL if not found.
|
* @return A pointer to the symbol, or NULL if not found.
|
||||||
*/
|
*/
|
||||||
struct Symbol *sym_GetSymbol(char const *name);
|
struct Symbol *sym_GetSymbol(char const *name);
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* `free`s all symbol memory that was allocated.
|
* `free`s all symbol memory that was allocated.
|
||||||
*/
|
*/
|
||||||
void sym_CleanupSymbols(void);
|
void sym_CleanupSymbols(void);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_SYMBOL_H
|
#endif /* RGBDS_LINK_SYMBOL_H */
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
#ifndef RGBDS_LINKDEFS_H
|
#ifndef RGBDS_LINKDEFS_H
|
||||||
#define RGBDS_LINKDEFS_H
|
#define RGBDS_LINKDEFS_H
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
|
||||||
|
#define RGBDS_OBJECT_VERSION_NUMBER 9U
|
||||||
#define RGBDS_OBJECT_REV 9U
|
#define RGBDS_OBJECT_REV 9U
|
||||||
|
|
||||||
enum AssertionType {
|
enum AssertionType {
|
||||||
@@ -74,50 +74,9 @@ enum SectionType {
|
|||||||
SECTTYPE_SRAM,
|
SECTTYPE_SRAM,
|
||||||
SECTTYPE_OAM,
|
SECTTYPE_OAM,
|
||||||
|
|
||||||
// In RGBLINK, this is used for "indeterminate" sections; this is primarily for SDCC
|
|
||||||
// areas, which do not carry any section type info and must be told from the linker script
|
|
||||||
SECTTYPE_INVALID
|
SECTTYPE_INVALID
|
||||||
};
|
};
|
||||||
|
|
||||||
// Nont-`const` members may be patched in RGBLINK depending on CLI flags
|
|
||||||
extern struct SectionTypeInfo {
|
|
||||||
char const *const name;
|
|
||||||
uint16_t const startAddr;
|
|
||||||
uint16_t size;
|
|
||||||
uint32_t const firstBank;
|
|
||||||
uint32_t lastBank;
|
|
||||||
} sectionTypeInfo[SECTTYPE_INVALID];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tells whether a section has data in its object file definition,
|
|
||||||
* depending on type.
|
|
||||||
* @param type The section's type
|
|
||||||
* @return `true` if the section's definition includes data
|
|
||||||
*/
|
|
||||||
static inline bool sect_HasData(enum SectionType type)
|
|
||||||
{
|
|
||||||
assert(type != SECTTYPE_INVALID);
|
|
||||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Computes a memory region's end address (last byte), eg. 0x7FFF
|
|
||||||
* @return The address of the last byte in that memory region
|
|
||||||
*/
|
|
||||||
static inline uint16_t endaddr(enum SectionType type)
|
|
||||||
{
|
|
||||||
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Computes a memory region's number of banks
|
|
||||||
* @return The number of banks, 1 for regions without banking
|
|
||||||
*/
|
|
||||||
static inline uint32_t nbbanks(enum SectionType type)
|
|
||||||
{
|
|
||||||
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SectionModifier {
|
enum SectionModifier {
|
||||||
SECTION_NORMAL,
|
SECTION_NORMAL,
|
||||||
SECTION_UNION,
|
SECTION_UNION,
|
||||||
@@ -126,6 +85,17 @@ enum SectionModifier {
|
|||||||
|
|
||||||
extern char const * const sectionModNames[];
|
extern char const * const sectionModNames[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether a section has data in its object file definition,
|
||||||
|
* depending on type.
|
||||||
|
* @param type The section's type
|
||||||
|
* @return `true` if the section's definition includes data
|
||||||
|
*/
|
||||||
|
static inline bool sect_HasData(enum SectionType type)
|
||||||
|
{
|
||||||
|
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||||
|
}
|
||||||
|
|
||||||
enum ExportLevel {
|
enum ExportLevel {
|
||||||
SYMTYPE_LOCAL,
|
SYMTYPE_LOCAL,
|
||||||
SYMTYPE_IMPORT,
|
SYMTYPE_IMPORT,
|
||||||
@@ -141,4 +111,45 @@ enum PatchType {
|
|||||||
PATCHTYPE_INVALID
|
PATCHTYPE_INVALID
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RGBDS_LINKDEFS_H
|
#define BANK_MIN_ROM0 0
|
||||||
|
#define BANK_MAX_ROM0 0
|
||||||
|
#define BANK_MIN_ROMX 1
|
||||||
|
#define BANK_MAX_ROMX 511
|
||||||
|
#define BANK_MIN_VRAM 0
|
||||||
|
#define BANK_MAX_VRAM 1
|
||||||
|
#define BANK_MIN_SRAM 0
|
||||||
|
#define BANK_MAX_SRAM 15
|
||||||
|
#define BANK_MIN_WRAM0 0
|
||||||
|
#define BANK_MAX_WRAM0 0
|
||||||
|
#define BANK_MIN_WRAMX 1
|
||||||
|
#define BANK_MAX_WRAMX 7
|
||||||
|
#define BANK_MIN_OAM 0
|
||||||
|
#define BANK_MAX_OAM 0
|
||||||
|
#define BANK_MIN_HRAM 0
|
||||||
|
#define BANK_MAX_HRAM 0
|
||||||
|
|
||||||
|
extern uint16_t startaddr[];
|
||||||
|
extern uint16_t maxsize[];
|
||||||
|
extern uint32_t bankranges[][2];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes a memory region's end address (last byte), eg. 0x7FFF
|
||||||
|
* @return The address of the last byte in that memory region
|
||||||
|
*/
|
||||||
|
static inline uint16_t endaddr(enum SectionType type)
|
||||||
|
{
|
||||||
|
return startaddr[type] + maxsize[type] - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes a memory region's number of banks
|
||||||
|
* @return The number of banks, 1 for regions without banking
|
||||||
|
*/
|
||||||
|
static inline uint32_t nbbanks(enum SectionType type)
|
||||||
|
{
|
||||||
|
return bankranges[type][1] - bankranges[type][0] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern char const * const typeNames[SECTTYPE_INVALID];
|
||||||
|
|
||||||
|
#endif /* RGBDS_LINKDEFS_H */
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ int32_t op_shift_left(int32_t value, int32_t amount);
|
|||||||
int32_t op_shift_right(int32_t value, int32_t amount);
|
int32_t op_shift_right(int32_t value, int32_t amount);
|
||||||
int32_t op_shift_right_unsigned(int32_t value, int32_t amount);
|
int32_t op_shift_right_unsigned(int32_t value, int32_t amount);
|
||||||
|
|
||||||
#endif // RGBDS_OP_MATH_H
|
#endif /* RGBDS_OP_MATH_H */
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// platform-specific hacks
|
/* platform-specific hacks */
|
||||||
|
|
||||||
#ifndef RGBDS_PLATFORM_H
|
#ifndef RGBDS_PLATFORM_H
|
||||||
#define RGBDS_PLATFORM_H
|
#define RGBDS_PLATFORM_H
|
||||||
@@ -20,20 +20,20 @@
|
|||||||
# include <strings.h>
|
# include <strings.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// MSVC has deprecated strdup in favor of _strdup
|
/* MSVC has deprecated strdup in favor of _strdup */
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# define strdup _strdup
|
# define strdup _strdup
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// MSVC prefixes the names of S_* macros with underscores,
|
/* MSVC prefixes the names of S_* macros with underscores,
|
||||||
// and doesn't define any S_IS* macros; define them ourselves
|
and doesn't define any S_IS* macros. Define them ourselves */
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# define S_IFMT _S_IFMT
|
# define S_IFMT _S_IFMT
|
||||||
# define S_IFDIR _S_IFDIR
|
# define S_IFDIR _S_IFDIR
|
||||||
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
|
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// MSVC doesn't use POSIX types or defines for `read`
|
/* MSVC doesn't use POSIX types or defines for `read` */
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# include <io.h>
|
# include <io.h>
|
||||||
# define STDIN_FILENO 0
|
# define STDIN_FILENO 0
|
||||||
@@ -46,14 +46,12 @@
|
|||||||
# include <unistd.h>
|
# include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// MSVC doesn't support `[static N]` for array arguments from C99 or C11
|
/* MSVC doesn't support `[static N]` for array arguments from C99 */
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
# define MIN_NB_ELMS(N)
|
# define MIN_NB_ELMS(N)
|
||||||
# define ARR_QUALS(...)
|
|
||||||
# define NONNULL(ptr) *ptr
|
# define NONNULL(ptr) *ptr
|
||||||
#else
|
#else
|
||||||
# define MIN_NB_ELMS(N) static (N)
|
# define MIN_NB_ELMS(N) static (N)
|
||||||
# define ARR_QUALS(...) __VA_ARGS__
|
|
||||||
# define NONNULL(ptr) ptr[static 1]
|
# define NONNULL(ptr) ptr[static 1]
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -75,4 +73,4 @@
|
|||||||
# define setmode(fd, mode) ((void)0)
|
# define setmode(fd, mode) ((void)0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // RGBDS_PLATFORM_H
|
#endif /* RGBDS_PLATFORM_H */
|
||||||
|
|||||||
@@ -9,19 +9,10 @@
|
|||||||
#ifndef EXTERN_VERSION_H
|
#ifndef EXTERN_VERSION_H
|
||||||
#define EXTERN_VERSION_H
|
#define EXTERN_VERSION_H
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define PACKAGE_VERSION_MAJOR 0
|
#define PACKAGE_VERSION_MAJOR 0
|
||||||
#define PACKAGE_VERSION_MINOR 6
|
#define PACKAGE_VERSION_MINOR 5
|
||||||
#define PACKAGE_VERSION_PATCH 0
|
#define PACKAGE_VERSION_PATCH 2
|
||||||
#define PACKAGE_VERSION_RC 2
|
|
||||||
|
|
||||||
char const *get_package_version_string(void);
|
char const *get_package_version_string(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#endif /* EXTERN_VERSION_H */
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // EXTERN_VERSION_H
|
|
||||||
|
|||||||
405
man/rgbds.5
405
man/rgbds.5
@@ -1,405 +0,0 @@
|
|||||||
.\"
|
|
||||||
.\" This file is part of RGBDS.
|
|
||||||
.\"
|
|
||||||
.\" Copyright (c) 2017-2021, Antonio Nino Diaz and RGBDS contributors.
|
|
||||||
.\"
|
|
||||||
.\" SPDX-License-Identifier: MIT
|
|
||||||
.\"
|
|
||||||
.Dd March 28, 2021
|
|
||||||
.Dt RGBDS 5
|
|
||||||
.Os
|
|
||||||
.Sh NAME
|
|
||||||
.Nm rgbds
|
|
||||||
.Nd object file format documentation
|
|
||||||
.Sh DESCRIPTION
|
|
||||||
This is the description of the object files used by
|
|
||||||
.Xr rgbasm 1
|
|
||||||
and
|
|
||||||
.Xr rgblink 1 .
|
|
||||||
.Em Please note that the specification is not stable yet.
|
|
||||||
RGBDS is still in active development, and some new features require adding more information to the object file, or modifying some fields, both of which break compatibility with older versions.
|
|
||||||
.Sh FILE STRUCTURE
|
|
||||||
The following types are used:
|
|
||||||
.Pp
|
|
||||||
.Cm LONG
|
|
||||||
is a 32-bit integer stored in little-endian format.
|
|
||||||
.Cm BYTE
|
|
||||||
is an 8-bit integer.
|
|
||||||
.Cm STRING
|
|
||||||
is a 0-terminated string of
|
|
||||||
.Cm BYTE .
|
|
||||||
Brackets after a type
|
|
||||||
.Pq e.g. Cm LONG Ns Bq Ar n
|
|
||||||
indicate
|
|
||||||
.Ar n
|
|
||||||
consecutive elements
|
|
||||||
.Pq here, Cm LONG Ns s .
|
|
||||||
All items are contiguous, with no padding anywhere\(emthis also means that they may not be aligned in the file!
|
|
||||||
.Pp
|
|
||||||
.Cm REPT Ar n
|
|
||||||
indicates that the fields between the
|
|
||||||
.Cm REPT
|
|
||||||
and corresponding
|
|
||||||
.Cm ENDR
|
|
||||||
are repeated
|
|
||||||
.Ar n
|
|
||||||
times.
|
|
||||||
.Pp
|
|
||||||
All IDs refer to objects within the file; for example, symbol ID $0001 refers to the second symbol defined in
|
|
||||||
.Em this
|
|
||||||
object file's
|
|
||||||
.Sx Symbols
|
|
||||||
array.
|
|
||||||
The only exception is the
|
|
||||||
.Sx Source file info
|
|
||||||
nodes, whose IDs are backwards, i.e. source node ID $0000 refers to the
|
|
||||||
.Em last
|
|
||||||
node in the array, not the first one.
|
|
||||||
References to other object files are made by imports (symbols), by name (sections), etc.\(embut never by ID.
|
|
||||||
.Ss Header
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm BYTE Ar Magic[4]
|
|
||||||
"RGB9"
|
|
||||||
.It Cm LONG Ar RevisionNumber
|
|
||||||
The format's revision number this file uses.
|
|
||||||
.Pq This is always in the same place in all revisions.
|
|
||||||
.It Cm LONG Ar NumberOfSymbols
|
|
||||||
How many symbols are defined in this object file.
|
|
||||||
.It Cm LONG Ar NumberOfSections
|
|
||||||
How many sections are defined in this object file.
|
|
||||||
.El
|
|
||||||
.Ss Source file info
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm LONG Ar NumberOfNodes
|
|
||||||
The number of source context nodes contained in this file.
|
|
||||||
.It Cm REPT Ar NumberOfNodes
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm LONG Ar ParentID
|
|
||||||
ID of the parent node, -1 meaning that this is the root node.
|
|
||||||
.Pp
|
|
||||||
.Sy Important :
|
|
||||||
the nodes are actually written in
|
|
||||||
.Sy reverse
|
|
||||||
order, meaning the node with ID 0 is the last one in the list!
|
|
||||||
.It Cm LONG Ar ParentLineNo
|
|
||||||
Line at which the parent node's context was exited; meaningless for the root node.
|
|
||||||
.It Cm BYTE Ar Type
|
|
||||||
.Bl -column "Value" -compact
|
|
||||||
.It Sy Value Ta Sy Meaning
|
|
||||||
.It 0 Ta REPT node
|
|
||||||
.It 1 Ta File node
|
|
||||||
.It 2 Ta Macro node
|
|
||||||
.El
|
|
||||||
.It Cm IF Ar Type No \(!= 0
|
|
||||||
If the node is not a REPT node...
|
|
||||||
.Pp
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm STRING Ar Name
|
|
||||||
The node's name: either a file name, or the macro's name prefixes by its definition's file name
|
|
||||||
.Pq e.g. Ql src/includes/defines.asm::error .
|
|
||||||
.El
|
|
||||||
.It Cm ELSE
|
|
||||||
If the node is a REPT, it also contains the iteration counter of all parent REPTs.
|
|
||||||
.Pp
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm LONG Ar Depth
|
|
||||||
.It Cm LONG Ar Iter Ns Bq Ar Depth
|
|
||||||
The number of REPT iterations, by increasing depth.
|
|
||||||
.El
|
|
||||||
.It Cm ENDC
|
|
||||||
.El
|
|
||||||
.It Cm ENDR
|
|
||||||
.El
|
|
||||||
.Ss Symbols
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm REPT Ar NumberOfSymbols
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm STRING Ar Name
|
|
||||||
This symbol's name.
|
|
||||||
Local symbols are stored as their full name
|
|
||||||
.Pq Ql Scope.symbol .
|
|
||||||
.It Cm BYTE Ar Type
|
|
||||||
.Bl -column "Value" -compact
|
|
||||||
.It Sy Value Ta Sy Meaning
|
|
||||||
.It 0 Ta Sy Local No symbol only used in this file.
|
|
||||||
.It 1 Ta Sy Import No of an exported symbol (by name) from another object file.
|
|
||||||
.It 2 Ta Sy Exported No symbol visible from other object files.
|
|
||||||
.El
|
|
||||||
.It Cm IF Ar Type No \(!= 1
|
|
||||||
If the symbol is defined in this object file...
|
|
||||||
.Pp
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm LONG Ar NodeID
|
|
||||||
Context in which the symbol was defined.
|
|
||||||
.It Cm LONG Ar LineNo
|
|
||||||
Line number in the context at which the symbol was defined.
|
|
||||||
.It Cm LONG Ar SectionID
|
|
||||||
The ID of the section in which the symbol is defined.
|
|
||||||
If the symbol doesn't belong to any specific section (i.e. it's a constant), this field contains -1.
|
|
||||||
.It Cm LONG Ar Value
|
|
||||||
The symbol's value.
|
|
||||||
If the symbol belongs to a section, this is the offset within that symbol's section.
|
|
||||||
.El
|
|
||||||
.It Cm ENDC
|
|
||||||
.El
|
|
||||||
.It Cm ENDR
|
|
||||||
.El
|
|
||||||
.Ss Sections
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm REPT Ar NumberOfSections
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm STRING Ar Name
|
|
||||||
The section's name.
|
|
||||||
.It Cm LONG Ar Size
|
|
||||||
The section's size, in bytes.
|
|
||||||
.It Cm BYTE Ar Type
|
|
||||||
Bits 0\(en2 indicate the section's type:
|
|
||||||
.Bl -column "Value" -compact
|
|
||||||
.It Sy Value Ta Sy Meaning
|
|
||||||
.It 0 Ta WRAM0
|
|
||||||
.It 1 Ta VRAM
|
|
||||||
.It 2 Ta ROMX
|
|
||||||
.It 3 Ta ROM0
|
|
||||||
.It 4 Ta HRAM
|
|
||||||
.It 5 Ta WRAMX
|
|
||||||
.It 6 Ta SRAM
|
|
||||||
.It 7 Ta OAM
|
|
||||||
.El
|
|
||||||
.Pp
|
|
||||||
Bit\ 7 being set means that the section is a "union"
|
|
||||||
.Pq see Do Unionized sections Dc in Xr rgbasm 5 .
|
|
||||||
Bit\ 6 being set means that the section is a "fragment"
|
|
||||||
.Pq see Do Section fragments Dc in Xr rgbasm 5 .
|
|
||||||
These two bits are mutually exclusive.
|
|
||||||
.It Cm LONG Ar Address
|
|
||||||
Address this section must be placed at.
|
|
||||||
This must either be valid for the section's
|
|
||||||
.Ar Type
|
|
||||||
(as affected by flags like
|
|
||||||
.Fl t
|
|
||||||
or
|
|
||||||
.Fl d
|
|
||||||
in
|
|
||||||
.Xr rgblink 1 ) ,
|
|
||||||
or -1 to indicate that the linker should automatically decide
|
|
||||||
.Pq the section is Dq floating .
|
|
||||||
.It Cm LONG Ar Bank
|
|
||||||
ID of the bank this section must be placed in.
|
|
||||||
This must either be valid for the section's
|
|
||||||
.Ar Type
|
|
||||||
(with the same caveats as for the
|
|
||||||
.Ar Address ) ,
|
|
||||||
or -1 to indicate that the linker should automatically decide.
|
|
||||||
.It Cm BYTE Ar Alignment
|
|
||||||
How many bits of the section's address should be equal to
|
|
||||||
.Ar AlignOfs ,
|
|
||||||
starting from the least-significant bit.
|
|
||||||
.It Cm LONG Ar AlignOfs
|
|
||||||
Alignment offset.
|
|
||||||
Must be strictly less than
|
|
||||||
.Ql 1 << Ar Alignment .
|
|
||||||
.It Cm IF Ar Type No \(eq 2 || Ar Type No \(eq 3
|
|
||||||
If the section has ROM type, it contains data.
|
|
||||||
.Pp
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm BYTE Ar Data Ns Bq Size
|
|
||||||
The section's raw data.
|
|
||||||
Bytes that will be patched over must be present, even though their contents will be overwritten.
|
|
||||||
.It Cm LONG Ar NumberOfPatches
|
|
||||||
How many patches must be applied to this section's
|
|
||||||
.Ar Data .
|
|
||||||
.It Cm REPT Ar NumberOfPatches
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm LONG Ar NodeID
|
|
||||||
Context in which the patch was defined.
|
|
||||||
.It Cm LONG Ar LineNo
|
|
||||||
Line number in the context at which the patch was defined.
|
|
||||||
.It Cm LONG Ar Offset
|
|
||||||
Offset within the section's
|
|
||||||
.Ar Data
|
|
||||||
at which the patch should be applied.
|
|
||||||
Must not be greater than the section's
|
|
||||||
.Ar Size
|
|
||||||
minus the patch's size
|
|
||||||
.Pq see Ar Type No below .
|
|
||||||
.It Cm LONG Ar PCSectionID
|
|
||||||
ID of the section in which PC is located.
|
|
||||||
(This is usually the same section within which the patch is applied, except for e.g.\&
|
|
||||||
.Ql LOAD
|
|
||||||
blocks, see
|
|
||||||
.Do RAM code Dc in Xr rgbasm 5 . )
|
|
||||||
.It Cm LONG Ar PCOffset
|
|
||||||
Offset of the PC symbol within the section designated by
|
|
||||||
.Ar PCSectionID .
|
|
||||||
It is expected that PC points to the instruction's first byte for instruction operands (i.e.\&
|
|
||||||
.Ql jp @
|
|
||||||
must be an infinite loop), and to the patch's first byte otherwise
|
|
||||||
.Ql ( db ,
|
|
||||||
.Ql dw ,
|
|
||||||
.Ql dl ) .
|
|
||||||
.It Cm BYTE Ar Type
|
|
||||||
.Bl -column "Value" -compact
|
|
||||||
.It Sy Value Ta Sy Meaning
|
|
||||||
.It 0 Ta Single-byte patch
|
|
||||||
.It 1 Ta Little-endian two-byte patch
|
|
||||||
.It 2 Ta Little-endian four-byte patch
|
|
||||||
.It 3 Ta Single-byte Ql jr
|
|
||||||
patch; the patch's value will be subtracted to PC + 2 (i.e.\&
|
|
||||||
.Ql jr @
|
|
||||||
must be the infinite loop
|
|
||||||
.Ql 18 FE ) .
|
|
||||||
.El
|
|
||||||
.It Cm LONG Ar RPNSize
|
|
||||||
Size of the
|
|
||||||
.Ar RPNExpr
|
|
||||||
below.
|
|
||||||
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
|
||||||
The patch's value, encoded as a RPN expression
|
|
||||||
.Pq see Sx RPN EXPRESSIONS .
|
|
||||||
.El
|
|
||||||
.It Cm ENDR
|
|
||||||
.El
|
|
||||||
.It Cm ENDC
|
|
||||||
.El
|
|
||||||
.El
|
|
||||||
.Ss Assertions
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm LONG Ar NumberOfAssertions
|
|
||||||
How many assertions this object file contains.
|
|
||||||
.It Cm REPT Ar NumberOfAssertions
|
|
||||||
Assertions are essentially patches with a message.
|
|
||||||
.Pp
|
|
||||||
.Bl -tag -width Ds -compact
|
|
||||||
.It Cm LONG Ar NodeID
|
|
||||||
Context in which the assertions was defined.
|
|
||||||
.It Cm LONG Ar LineNo
|
|
||||||
Line number in the context at which the assertion was defined.
|
|
||||||
.It Cm LONG Ar Offset
|
|
||||||
Unused leftover from the patch structure.
|
|
||||||
.It Cm LONG Ar PCSectionID
|
|
||||||
ID of the section in which PC is located.
|
|
||||||
.It Cm LONG Ar PCOffset
|
|
||||||
Offset of the PC symbol within the section designated by
|
|
||||||
.Ar PCSectionID .
|
|
||||||
.It Cm BYTE Ar Type
|
|
||||||
Describes what should happen if the expression evaluates to a non-zero value.
|
|
||||||
.Bl -column "Value" -compact
|
|
||||||
.It Sy Value Ta Sy Meaning
|
|
||||||
.It 0 Ta Print a warning message, and continue linking normally.
|
|
||||||
.It 1 Ta Print an error message, so linking will fail, but allow other assertions to be evaluated.
|
|
||||||
.It 2 Ta Print a fatal error message, and abort immediately.
|
|
||||||
.El
|
|
||||||
.It Cm LONG Ar RPNSize
|
|
||||||
Size of the
|
|
||||||
.Ar RPNExpr
|
|
||||||
below.
|
|
||||||
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
|
||||||
The patch's value, encoded as a RPN expression
|
|
||||||
.Pq see Sx RPN EXPRESSIONS .
|
|
||||||
.It Cm STRING Ar Message
|
|
||||||
The message displayed if the expression evaluates to a non-zero value.
|
|
||||||
If empty, a generic message is displayed instead.
|
|
||||||
.El
|
|
||||||
.It Cm ENDR
|
|
||||||
.El
|
|
||||||
.Ss RPN EXPRESSIONS
|
|
||||||
Expressions in the object file are stored as RPN, or
|
|
||||||
.Dq Reverse Polish Notation ,
|
|
||||||
which is a notation that allows computing arbitrary expressions with just a simple stack.
|
|
||||||
For example, the expression
|
|
||||||
.Ql 2 5 -
|
|
||||||
will first push the value
|
|
||||||
.Dq 2
|
|
||||||
to the stack, then
|
|
||||||
.Dq 5 .
|
|
||||||
The
|
|
||||||
.Ql -
|
|
||||||
operator pops two arguments from the stack, subtracts them, and then pushes back the result
|
|
||||||
.Pq Dq 3
|
|
||||||
on the stack.
|
|
||||||
A well-formed RPN expression never tries to pop from an empty stack, and leaves exactly one value in it at the end.
|
|
||||||
.Pp
|
|
||||||
RGBDS encodes RPN expressions as an array of
|
|
||||||
.Cm BYTE Ns s .
|
|
||||||
The first byte encodes either an operator, or a literal, which consumes more
|
|
||||||
.Cm BYTE Ns s
|
|
||||||
after it.
|
|
||||||
.Bl -column -offset Ds "Value"
|
|
||||||
.It Sy Value Ta Sy Meaning
|
|
||||||
.It Li $00 Ta Addition operator Pq Ql +
|
|
||||||
.It Li $01 Ta Subtraction operator Pq Ql -
|
|
||||||
.It Li $02 Ta Multiplication operator Pq Ql *
|
|
||||||
.It Li $03 Ta Division operator Pq Ql /
|
|
||||||
.It Li $04 Ta Modulo operator Pq Ql %
|
|
||||||
.It Li $05 Ta Negation Pq unary Ql -
|
|
||||||
.It Li $06 Ta Exponent operator Pq Ql **
|
|
||||||
.It Li $10 Ta Bitwise OR operator Pq Ql \&|
|
|
||||||
.It Li $11 Ta Bitwise AND operator Pq Ql &
|
|
||||||
.It Li $12 Ta Bitwise XOR operator Pq Ql ^
|
|
||||||
.It Li $13 Ta Bitwise complement operator Pq unary Ql ~
|
|
||||||
.It Li $21 Ta Logical AND operator Pq Ql &&
|
|
||||||
.It Li $22 Ta Logical OR operator Pq Ql ||
|
|
||||||
.It Li $23 Ta Logical complement operator Pq unary Ql \&!
|
|
||||||
.It Li $30 Ta Equality operator Pq Ql ==
|
|
||||||
.It Li $31 Ta Non-equality operator Pq Ql !=
|
|
||||||
.It Li $32 Ta Greater-than operator Pq Ql >
|
|
||||||
.It Li $33 Ta Less-than operator Pq Ql <
|
|
||||||
.It Li $34 Ta Greater-than-or-equal operator Pq Ql >=
|
|
||||||
.It Li $35 Ta Less-than-or-equal operator Pq Ql <=
|
|
||||||
.It Li $40 Ta Left shift operator Pq Ql <<
|
|
||||||
.It Li $41 Ta Arithmetic/signed right shift operator Pq Ql >>
|
|
||||||
.It Li $42 Ta Logical/unsigned right shift operator Pq Ql >>>
|
|
||||||
.It Li $50 Ta Fn BANK symbol ,
|
|
||||||
followed by the
|
|
||||||
.Ar symbol Ap s Cm LONG
|
|
||||||
ID.
|
|
||||||
.It Li $51 Ta Fn BANK section ,
|
|
||||||
followed by the
|
|
||||||
.Ar section Ap s Cm STRING
|
|
||||||
name.
|
|
||||||
.It Li $52 Ta PC's Fn BANK Pq i.e. Ql BANK(@) .
|
|
||||||
.It Li $53 Ta Fn SIZEOF section ,
|
|
||||||
followed by the
|
|
||||||
.Ar section Ap s Cm STRING
|
|
||||||
name.
|
|
||||||
.It Li $54 Ta Fn STARTOF section ,
|
|
||||||
followed by the
|
|
||||||
.Ar section Ap s Cm STRING
|
|
||||||
name.
|
|
||||||
.It Li $60 Ta Ql ldh
|
|
||||||
check.
|
|
||||||
Checks if the value is a valid
|
|
||||||
.Ql ldh
|
|
||||||
operand
|
|
||||||
.Pq see Do Load Instructions Dc in Xr gbz80 7 ,
|
|
||||||
i.e. that it is between either $00 and $FF, or $FF00 and $FFFF, both inclusive.
|
|
||||||
The value is then ANDed with $00FF
|
|
||||||
.Pq Ql & $FF .
|
|
||||||
.It Li $61 Ta Ql rst
|
|
||||||
check.
|
|
||||||
Checks if the value is a valid
|
|
||||||
.Ql rst
|
|
||||||
.Pq see Do RST vec Dc in Xr gbz80 7
|
|
||||||
vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
|
||||||
The value is then ORed with $C7
|
|
||||||
.Pq Ql \&| $C7 .
|
|
||||||
.It Li $80 Ta Integer literal.
|
|
||||||
Followed by the
|
|
||||||
.Cm LONG
|
|
||||||
integer.
|
|
||||||
.It Li $81 Ta A symbol's value.
|
|
||||||
Followed by the symbol's
|
|
||||||
.Cm LONG
|
|
||||||
ID.
|
|
||||||
.El
|
|
||||||
.Sh SEE ALSO
|
|
||||||
.Xr rgbasm 1 ,
|
|
||||||
.Xr rgblink 1 ,
|
|
||||||
.Xr rgbds 7 ,
|
|
||||||
.Xr gbz80 7
|
|
||||||
.Sh HISTORY
|
|
||||||
.Nm
|
|
||||||
was originally written by Carsten S\(/orensen as part of the ASMotor package,
|
|
||||||
and was later packaged in RGBDS by Justin Lloyd.
|
|
||||||
It is now maintained by a number of contributors at
|
|
||||||
.Lk https://github.com/gbdev/rgbds .
|
|
||||||
591
man/rgbgfx.1
591
man/rgbgfx.1
@@ -1,591 +0,0 @@
|
|||||||
'\" e
|
|
||||||
.\"
|
|
||||||
.\" This file is part of RGBDS.
|
|
||||||
.\"
|
|
||||||
.\" Copyright (c) 2013-2021, stag019 and RGBDS contributors.
|
|
||||||
.\"
|
|
||||||
.\" SPDX-License-Identifier: MIT
|
|
||||||
.\"
|
|
||||||
.Dd March 28, 2021
|
|
||||||
.Dt RGBGFX 1
|
|
||||||
.Os
|
|
||||||
.Sh NAME
|
|
||||||
.Nm rgbgfx
|
|
||||||
.Nd Game Boy graphics converter
|
|
||||||
.Sh SYNOPSIS
|
|
||||||
.Nm
|
|
||||||
.Op Fl r Ar stride
|
|
||||||
.Op Fl CmuVZ
|
|
||||||
.Op Fl v Op Fl v No ...
|
|
||||||
.Op Fl a Ar attrmap | Fl A
|
|
||||||
.Op Fl b Ar base_ids
|
|
||||||
.Op Fl c Ar color_spec
|
|
||||||
.Op Fl d Ar depth
|
|
||||||
.Op Fl L Ar slice
|
|
||||||
.Op Fl N Ar nb_tiles
|
|
||||||
.Op Fl n Ar nb_pals
|
|
||||||
.Op Fl o Ar out_file
|
|
||||||
.Op Fl p Ar pal_file | Fl P
|
|
||||||
.Op Fl q Ar pal_map | Fl Q
|
|
||||||
.Op Fl s Ar nb_colors
|
|
||||||
.Op Fl t Ar tilemap | Fl T
|
|
||||||
.Op Fl x Ar quantity
|
|
||||||
.Ar file
|
|
||||||
.Sh DESCRIPTION
|
|
||||||
The
|
|
||||||
.Nm
|
|
||||||
program converts PNG images into data suitable for display on the Game Boy and Game Boy Color, or vice-versa.
|
|
||||||
.Pp
|
|
||||||
The main function of
|
|
||||||
.Nm
|
|
||||||
is to divide the input PNG into 8\[tmu]8 pixel
|
|
||||||
.Em squares ,
|
|
||||||
convert each of those squares into 1bpp or 2bpp tile data, and save all of the tile data in a file.
|
|
||||||
It also has options to generate a tile map, attribute map, and/or palette set as well; more on that and how the conversion process can be tweaked below.
|
|
||||||
.Sh ARGUMENTS
|
|
||||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
|
||||||
.Fl Fl verb
|
|
||||||
is
|
|
||||||
.Fl Fl verbose ,
|
|
||||||
but
|
|
||||||
.Fl Fl ver
|
|
||||||
is invalid because it could also be
|
|
||||||
.Fl Fl version .
|
|
||||||
.Pp
|
|
||||||
.Nm
|
|
||||||
accepts decimal, binary, and hexadecimal numbers in option arguments.
|
|
||||||
Decimal numbers are written as usual; binary numbers must be prefixed with either
|
|
||||||
.Ql %
|
|
||||||
or
|
|
||||||
.Ql 0b ,
|
|
||||||
and hexadecimal numbers must be prefixed with either
|
|
||||||
.Ql $
|
|
||||||
(which will likely need escaping or quoting to avoid being interpreted by the shell), or
|
|
||||||
.Ql 0x .
|
|
||||||
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
|
|
||||||
All of these are equivalent:
|
|
||||||
.Ql 42 ,
|
|
||||||
.Ql 042 ,
|
|
||||||
.Ql 0b00101010 ,
|
|
||||||
.Ql 0B101010 ,
|
|
||||||
.Ql 0x2A ,
|
|
||||||
.Ql 0X2A ,
|
|
||||||
.Ql 0x2a .
|
|
||||||
.Pp
|
|
||||||
The following options are accepted:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap
|
|
||||||
Generate an attribute map, which is a file containing tile
|
|
||||||
.Dq attributes .
|
|
||||||
For each square of the input image, its corresponding attribute map byte contains the mirroring bits (if
|
|
||||||
.Fl m
|
|
||||||
was specified), the bank bit
|
|
||||||
.Pq see Fl N ,
|
|
||||||
and the palette index.
|
|
||||||
See
|
|
||||||
.Lk https://gbdev.io/pandocs/Tile_Maps#bg-map-attributes-cgb-mode-only Pan Docs
|
|
||||||
for the individual bytes' format.
|
|
||||||
The output is written just like the tile map (see
|
|
||||||
.Fl t ) ,
|
|
||||||
follows the same order
|
|
||||||
.Pq Fl Z ,
|
|
||||||
and has the same size.
|
|
||||||
.It Fl A , Fl Fl output-attr-map
|
|
||||||
Same as
|
|
||||||
.Fl a Ar path ,
|
|
||||||
where
|
|
||||||
.Ar path
|
|
||||||
is the input image's path with the extension set to
|
|
||||||
.Pa .attrmap .
|
|
||||||
.It Fl b Ar base_ids , Fl Fl base-tiles Ar base_ids
|
|
||||||
Set the base IDs for tile map output.
|
|
||||||
.Ar base_ids
|
|
||||||
should be one or two numbers between 0 and 255, separated by a comma; they are for bank 0 and bank 1 respectively.
|
|
||||||
Both default to 0.
|
|
||||||
.It Fl C , Fl Fl color-curve
|
|
||||||
When generating palettes, use a color curve mimicking the Game Boy Color's screen.
|
|
||||||
The resulting colors may look closer to the input image's
|
|
||||||
.Sy on hardware and accurate emulators .
|
|
||||||
.It Fl c Ar color_spec , Fl Fl colors Ar color_spec
|
|
||||||
Use the specified color palettes instead of having
|
|
||||||
.Nm
|
|
||||||
automatically determine some.
|
|
||||||
.Ar color_spec
|
|
||||||
can be one of the following:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Sy inline palette spec
|
|
||||||
If
|
|
||||||
.Ar color_spec
|
|
||||||
begins with a hash character
|
|
||||||
.Ql # ,
|
|
||||||
it is treated as an inline palette specification.
|
|
||||||
It should contain a comma-separated list of hexadecimal colors, each beginning with a hash.
|
|
||||||
Colors in are accepted either as
|
|
||||||
.Ql #rgb
|
|
||||||
or
|
|
||||||
.Ql #rrggbb
|
|
||||||
format.
|
|
||||||
Palettes must be separated by a colon or semicolon (the latter may require quoting to avoid special handling by the shell), and spaces are allowed around colons, semicolons and commas; trailing commas and semicolons are allowed.
|
|
||||||
See
|
|
||||||
.Sx EXAMPLES
|
|
||||||
for an example of an inline palette specification.
|
|
||||||
.It Sy embedded palette spec
|
|
||||||
If
|
|
||||||
.Ar color_spec
|
|
||||||
is the case-insensitive word
|
|
||||||
.Cm embedded ,
|
|
||||||
then the first four colors of the input PNG's embedded palette are used.
|
|
||||||
It is an error if the PNG is not indexed, or if colors other than these 4 are used.
|
|
||||||
.Pq This is different from the default behavior of indexed PNGs, as then unused entries in the embedded palette are ignored, whereas they are not with Fl c Cm embedded .
|
|
||||||
.It Sy external palette spec
|
|
||||||
Otherwise,
|
|
||||||
.Ar color_spec
|
|
||||||
is assumed to be an external palette specification.
|
|
||||||
The expected format is
|
|
||||||
.Ql format:path ,
|
|
||||||
where
|
|
||||||
.Ar path
|
|
||||||
is a path to a file, which will be processed according to the
|
|
||||||
.Ar format .
|
|
||||||
See
|
|
||||||
.Sx PALETTE SPECIFICATION FORMATS
|
|
||||||
for a list of formats and their descriptions.
|
|
||||||
.El
|
|
||||||
.It Fl d Ar depth , Fl Fl depth Ar depth
|
|
||||||
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
|
|
||||||
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
|
|
||||||
.It Fl L Ar slice , Fl Fl slice Ar slice
|
|
||||||
Only process a given rectangle of the image.
|
|
||||||
This is useful for example if the input image is a sheet of some sort, and you want to convert each cel individually.
|
|
||||||
The default is to process the whole image as-is.
|
|
||||||
.Pp
|
|
||||||
.Ar slice
|
|
||||||
must be two number pairs, separated by a colon.
|
|
||||||
The numbers must be separated by commas; space is allowed around all punctuation.
|
|
||||||
The first number pair specifies the X and Y coordinates of the top-left pixel that will be processed (anything above it or to its left will be ignored).
|
|
||||||
The second number pair specifies how many tiles to process horizontally and vertically, respectively.
|
|
||||||
.Pp
|
|
||||||
.Sy Fl L Sy is ignored in reverse mode , No no padding is inserted .
|
|
||||||
.It Fl m , Fl Fl mirror-tiles
|
|
||||||
Deduplicate tiles that are mirrors of each other.
|
|
||||||
Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring.
|
|
||||||
Useful with a tile map and attribute map together to keep track of the duplicated tiles and the dimension(s) mirrored.
|
|
||||||
Implies
|
|
||||||
.Fl u .
|
|
||||||
.It Fl N Ar nb_tiles , Fl Fl nb-tiles Ar nb_tiles
|
|
||||||
Set a maximum number of tiles that can be placed in each VRAM bank.
|
|
||||||
.Ar nb_tiles
|
|
||||||
should be one or two numbers between 0 and 256, separated by a comma; if the latter is omitted, it defaults to 0.
|
|
||||||
Setting either number to 0 prevents any tiles from being output in that bank.
|
|
||||||
.Pp
|
|
||||||
If more tiles are generated than can fit in the two banks combined,
|
|
||||||
.Nm
|
|
||||||
will abort.
|
|
||||||
If
|
|
||||||
.Fl N
|
|
||||||
is not specified, no limit will be set on the amount of tiles placed in bank 0, and tiles will not be placed in bank 1.
|
|
||||||
.It Fl n Ar nb_pals , Fl Fl nb-palettes Ar nb_pals
|
|
||||||
Abort if more than
|
|
||||||
.Ar nb_pals
|
|
||||||
palettes are generated.
|
|
||||||
This may not be more than 256.
|
|
||||||
.Pp
|
|
||||||
Note that attribute map output only has 3 bits for the palette ID, so a limit higher than 8 may yield incomplete data unless relying on a palette map
|
|
||||||
.Pq see Fl q .
|
|
||||||
.It Fl o Ar out_file , Fl Fl output Ar out_file
|
|
||||||
Output the tile data in native 2bpp format or in 1bpp
|
|
||||||
.Pq depending on Fl d
|
|
||||||
to this file.
|
|
||||||
.It Fl p Ar pal_file , Fl Fl palette Ar pal_file
|
|
||||||
Output the image's palette set to this file.
|
|
||||||
.It Fl P , Fl Fl output-palette
|
|
||||||
Same as
|
|
||||||
.Fl p Ar path ,
|
|
||||||
where
|
|
||||||
.Ar path
|
|
||||||
is the input image's path with the extension set to
|
|
||||||
.Pa .pal .
|
|
||||||
.It Fl q Ar pal_file , Fl Fl palette-map Ar pal_file
|
|
||||||
Output the image's palette map to this file.
|
|
||||||
This is useful if the input image contains more than 8 palettes, as the attribute map only contains the lower 3 bits of the palette indices.
|
|
||||||
.It Fl Q , Fl Fl output-palette-map
|
|
||||||
Same as
|
|
||||||
.Fl q Ar path ,
|
|
||||||
where
|
|
||||||
.Ar path
|
|
||||||
is the input image's path with the extension set to
|
|
||||||
.Pa .palmap .
|
|
||||||
.It Fl r Ar width , Fl Fl reverse Ar width
|
|
||||||
Switches
|
|
||||||
.Nm
|
|
||||||
into
|
|
||||||
.Dq Sy reverse
|
|
||||||
mode.
|
|
||||||
In this mode, instead of converting a PNG image into Game Boy data,
|
|
||||||
.Nm
|
|
||||||
will attempt to reverse the process, and render Game Boy data into an image.
|
|
||||||
See
|
|
||||||
.Sx REVERSE MODE
|
|
||||||
below for details.
|
|
||||||
.Pp
|
|
||||||
.Ar width
|
|
||||||
is the image's width, in tiles
|
|
||||||
.Pq including any margins specified by Fl L .
|
|
||||||
.It Fl s Ar nb_colors , Fl Fl palette-size Ar nb_colors
|
|
||||||
Specify how many colors each palette contains, including the transparent one if any.
|
|
||||||
.Ar nb_colors
|
|
||||||
cannot be more than
|
|
||||||
.Ql 1 << Ar depth
|
|
||||||
.Pq see Fl d .
|
|
||||||
.It Fl t Ar tilemap , Fl Fl tilemap Ar tilemap
|
|
||||||
Generate a file of tile indices.
|
|
||||||
For each square of the input image, its corresponding tile map byte contains the index of the associated tile in the tile data file.
|
|
||||||
The IDs wrap around from 255 back to 0, and do not include the bank bit; use
|
|
||||||
.Fl a
|
|
||||||
for that.
|
|
||||||
Useful in combination with
|
|
||||||
.Fl u
|
|
||||||
and/or
|
|
||||||
.Fl m
|
|
||||||
to keep track of duplicate tiles.
|
|
||||||
.It Fl T , Fl Fl output-tilemap
|
|
||||||
Same as
|
|
||||||
.Fl t Ar path ,
|
|
||||||
where
|
|
||||||
.Ar path
|
|
||||||
is the input image's path with the extension set to
|
|
||||||
.Pa .tilemap .
|
|
||||||
.It Fl u , Fl Fl unique-tiles
|
|
||||||
Deduplicate identical tiles, and omit the duplicates from the tile data file.
|
|
||||||
Useful with a tile map
|
|
||||||
.Pq see Fl t
|
|
||||||
to keep track of the duplicated tiles.
|
|
||||||
.Pp
|
|
||||||
Note that if this option is enabled, no guarantee is made on the order in which tiles are output; while it
|
|
||||||
.Em should
|
|
||||||
be consistent across identical runs of a given
|
|
||||||
.Nm
|
|
||||||
release, the same is not true for different releases.
|
|
||||||
.It Fl V , Fl Fl version
|
|
||||||
Print the version of the program and exit.
|
|
||||||
.It Fl v , Fl Fl verbose
|
|
||||||
Be verbose.
|
|
||||||
The verbosity level is increased by one each time the flag is specified, with each level including the previous:
|
|
||||||
.Bl -enum -width 2n -compact
|
|
||||||
.It
|
|
||||||
.Nm
|
|
||||||
prints out its configuration before doing anything.
|
|
||||||
.It
|
|
||||||
A generic message is printed before doing most actions.
|
|
||||||
.It
|
|
||||||
Some of the actions' intermediate results are printed.
|
|
||||||
.It
|
|
||||||
Some internal debug printing is enabled.
|
|
||||||
.El
|
|
||||||
The verbosity level does not go past 6.
|
|
||||||
.Pp
|
|
||||||
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
|
|
||||||
.It Fl x Ar quantity , Fl Fl trim-end Ar quantity
|
|
||||||
Do not output the last
|
|
||||||
.Ar quantity
|
|
||||||
tiles to the tile data file; no other output is affected.
|
|
||||||
This is useful for trimming
|
|
||||||
.Dq filler
|
|
||||||
/ blank squares at the end of an image.
|
|
||||||
If fewer than
|
|
||||||
.Ar quantity
|
|
||||||
tiles would have been emitted, the file will be empty.
|
|
||||||
.Pp
|
|
||||||
Note that this is done
|
|
||||||
.Em after
|
|
||||||
deduplication if
|
|
||||||
.Fl u
|
|
||||||
was enabled, so you probably don't want to use this option in combination with
|
|
||||||
.Fl u .
|
|
||||||
Note also that the tiles that don't get output will not count towards
|
|
||||||
.Fl N Ap s
|
|
||||||
limit.
|
|
||||||
.It Fl Z , Fl Fl columns
|
|
||||||
Read squares from the PNG in column-major order (column by column), instead of the default row-major order (line by line).
|
|
||||||
This primarily affects tile map and attribute map output, although it may also change generated tile data and palettes.
|
|
||||||
.El
|
|
||||||
.Ss At-files
|
|
||||||
In a given project, many images are to be converted with different flags.
|
|
||||||
The traditional way of solving this problem has been to specify the different flags for each image in the Makefile / build script; this can be inconvenient, as it centralizes all those flags away from the images they concern.
|
|
||||||
.Pp
|
|
||||||
To avoid these drawbacks,
|
|
||||||
.Nm
|
|
||||||
supports
|
|
||||||
.Dq at-files :
|
|
||||||
any command-line argument that begins with an at sign
|
|
||||||
.Pq Ql @
|
|
||||||
is interpreted as one.
|
|
||||||
The rest of the argument (without the @, that is) is interpreted as the path to a file, whose contents are interpreted as if given on the command line.
|
|
||||||
At-files can be stored right next to the corresponding image, for example.
|
|
||||||
.Pp
|
|
||||||
Since the contents of at-files are interpreted by
|
|
||||||
.Nm ,
|
|
||||||
.Sy no shell processing is performed ;
|
|
||||||
for example, shell variables are not expanded
|
|
||||||
.Ql ( $PWD ,
|
|
||||||
.Ql %WINDIR% ,
|
|
||||||
etc.).
|
|
||||||
In at-files, lines that are empty or contain only whitespace are ignored; lines that begin with a hash sign
|
|
||||||
.Pq Ql # ,
|
|
||||||
optionally preceded by whitespace, are considered comments and also ignored.
|
|
||||||
Each line can contain any number of arguments, which are separated by whitespace.
|
|
||||||
.Pq \&No quoting feature to prevent this is provided.
|
|
||||||
.Pp
|
|
||||||
Note that this special meaning given to arguments has less precedence than option arguments, and that the standard
|
|
||||||
.Ql --
|
|
||||||
to stop option processing also disables at-file processing.
|
|
||||||
For example, the following command line processes
|
|
||||||
.Ql @tilesets/town.png ,
|
|
||||||
outputs tile data to
|
|
||||||
.Ql @tilesets/town.2bpp ,
|
|
||||||
and reads command-line options from
|
|
||||||
.Ql tilesets/town.flags
|
|
||||||
then
|
|
||||||
.Ql tilesets.flags :
|
|
||||||
.Pp
|
|
||||||
.Dl $ rgbgfx -o @tilesets/town.2bpp @tilesets/town.flags @tilesets.flags -- @tilesets/town.png
|
|
||||||
.Pp
|
|
||||||
At-files can also specify the input image directly, and call for more at-files, both using the regular syntax.
|
|
||||||
Note that while
|
|
||||||
.Ql --
|
|
||||||
can be used in an at-file (with identical semantics), it is only effective inside of it\(emnormal option processing continues in the parent scope.
|
|
||||||
.Sh PALETTE SPECIFICATION FORMATS
|
|
||||||
The following formats are supported:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Sy act
|
|
||||||
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626 Adobe Photoshop color table .
|
|
||||||
.It Sy aco
|
|
||||||
.Lk https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819 Adobe Photoshop color swatch .
|
|
||||||
.It Sy psp
|
|
||||||
.Lk https://www.selapa.net/swatches/colors/fileformats.php#psp_pal Paint Shop Pro palette .
|
|
||||||
.El
|
|
||||||
.Pp
|
|
||||||
If you wish for another format to be supported, please open an issue (see
|
|
||||||
.Sx BUGS
|
|
||||||
below) or contact us, and supply a few sample files.
|
|
||||||
.Sh PALETTE GENERATION
|
|
||||||
.Nm
|
|
||||||
must generate palettes from the colors in the input image, unless
|
|
||||||
.Fl c
|
|
||||||
was used; in that case, the provided palettes will be used.
|
|
||||||
.Sy If the order of colors in the palettes is important to you ,
|
|
||||||
for example because you want to use palette swaps, please use
|
|
||||||
.Fl c
|
|
||||||
to specify the palette explicitly.
|
|
||||||
.Pp
|
|
||||||
First, if the image contains
|
|
||||||
.Em any
|
|
||||||
transparent pixel, color #0 of
|
|
||||||
.Em all
|
|
||||||
palettes will be allocated to it.
|
|
||||||
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
|
|
||||||
After generating palettes,
|
|
||||||
.Nm
|
|
||||||
sorts colors within those palettes using the following rules:
|
|
||||||
.EQ
|
|
||||||
delim $$
|
|
||||||
.EN
|
|
||||||
.Bl -bullet -offset indent
|
|
||||||
.It
|
|
||||||
If the PNG file internally contains a palette (often dubbed an
|
|
||||||
.Dq indexed
|
|
||||||
PNG), then colors in each output palette will be sorted according to their order in the PNG's palette.
|
|
||||||
Any unused entries will be ignored, and only the first entry is considered if there are any duplicates.
|
|
||||||
.Po If you want a given color to appear more than once, or an unused color to appear at all, you should specify the palettes explicitly instead using Fl c ;
|
|
||||||
.Fl c Cm embedded
|
|
||||||
may be appropriate.
|
|
||||||
.Pc
|
|
||||||
.It
|
|
||||||
Otherwise, if the PNG only contains shades of gray, they will be categorized into as many
|
|
||||||
.Dq bins
|
|
||||||
as there are colors per palette, and the palette is set to these bins.
|
|
||||||
The darkest gray will end up in bin #0, and so on; note that this is the opposite of the RGB method below.
|
|
||||||
If two distinct grays end up in the same bin, the RGB method is used instead.
|
|
||||||
.Pp
|
|
||||||
Be careful that
|
|
||||||
.Nm
|
|
||||||
is picky about what it considers
|
|
||||||
.Dq grays :
|
|
||||||
the red, green, and blue components of each color must
|
|
||||||
.Em all
|
|
||||||
be
|
|
||||||
.Em exactly
|
|
||||||
the same.
|
|
||||||
.It
|
|
||||||
If none of the above apply, colors are sorted from lightest (first) to darkest (last).
|
|
||||||
The definition of luminance that
|
|
||||||
.Nm
|
|
||||||
uses is
|
|
||||||
.Do
|
|
||||||
$2126 times red + 7152 times green + 722 times blue$
|
|
||||||
.Dc .
|
|
||||||
.El
|
|
||||||
.EQ
|
|
||||||
delim off
|
|
||||||
.EN
|
|
||||||
.Pp
|
|
||||||
Note that the
|
|
||||||
.Dq indexed
|
|
||||||
behavior depends on an internal detail of how the PNG is saved, specifically its
|
|
||||||
.Ql PLTE
|
|
||||||
chunk.
|
|
||||||
Since few image editors (such as GIMP) expose that detail, this behavior is only kept for compatibility and should be considered deprecated.
|
|
||||||
.Sh OUTPUT FILES
|
|
||||||
All files output by
|
|
||||||
.Nm
|
|
||||||
are binary files, and designed to follow the Game Boy and Game Boy Color's native formats.
|
|
||||||
What follows is succinct descriptions of those formats, including
|
|
||||||
.Nm Ns -specific
|
|
||||||
details.
|
|
||||||
For more complete, beginner-friendly descriptions of the native formats with illustrations, please check out
|
|
||||||
.Lk https://gbdev.io/pandocs/Rendering Pan Docs .
|
|
||||||
.Ss Tile data
|
|
||||||
Tile data is output like a binary dump of VRAM, with no padding between tiles.
|
|
||||||
Each tile is 16 bytes, 2 per row of 8 pixels; the bits of color IDs are split into each byte
|
|
||||||
.Pq or Dq bitplane .
|
|
||||||
The leftmost pixel's color ID is stored in the two bytes' most significant bits, and the rightmost pixel's color ID in their least significant bits.
|
|
||||||
.Pp
|
|
||||||
When the bit depth
|
|
||||||
.Pq Fl d
|
|
||||||
is set to 1, the most significant bitplane (second byte) of each row, being all zeros, is simply not output.
|
|
||||||
.Ss Palette data
|
|
||||||
Palette data is output like a dump of palette memory.
|
|
||||||
Each color is written as GBC-native little-endian RGB555, with the unused bit 15 set to 0.
|
|
||||||
There is no padding between colors, nor between palettes; however, empty colors in the palettes are output as 0xFFFF.
|
|
||||||
.EQ
|
|
||||||
delim $$
|
|
||||||
.EN
|
|
||||||
For example, if 5 palettes are generated with
|
|
||||||
.Fl s Cm 4 ,
|
|
||||||
the palette data file will be $2 times 4 times 5 = 40$ bytes long, even if some palettes contain less than 3 colors.
|
|
||||||
.EQ
|
|
||||||
delim off
|
|
||||||
.EN
|
|
||||||
Note that
|
|
||||||
.Fl n
|
|
||||||
only caps how many palettes are generated (and thus this file's size), but fewer may be generated still.
|
|
||||||
.Ss Tile map data
|
|
||||||
A tile map is an array of tile IDs, with one byte per tile ID.
|
|
||||||
The first byte always corresponds to the ID of the tile in top-left corner of the input image; the second byte is either the ID of the tile to its right (by default), or below it
|
|
||||||
.Pq with Fl Z ;
|
|
||||||
and so on, continuing in the same direction.
|
|
||||||
Rows / columns (respectively) are stored consecutively, with no padding.
|
|
||||||
.Ss Attribute map data
|
|
||||||
Attribute maps mirror the format of tile maps, like on the GBC, especially the order in which bytes are output.
|
|
||||||
The contents of individual bytes follows the GBC's native format:
|
|
||||||
.Bl -column "Bit 2\(en0" "Background Palette number"
|
|
||||||
.It Bit 7 Ta BG-to-OAM Priority Ta Set to 0
|
|
||||||
.It Bit 6 Ta Vertical Flip Ta 0=Normal, 1=Mirror vertically
|
|
||||||
.It Bit 5 Ta Horizontal Flip Ta 0=Normal, 1=Mirror horizontally
|
|
||||||
.It Bit 4 Ta Not used Ta Set to 0
|
|
||||||
.It Bit 3 Ta Tile VRAM Bank number Ta 0=Bank 0, 1=Bank 1
|
|
||||||
.It Bit 2\(en0 Ta Background Palette number Ta BGP0-7
|
|
||||||
.El
|
|
||||||
.Pp
|
|
||||||
Note that if more than 8 palettes are used, only the lowest 3 bits of the palette ID are output.
|
|
||||||
.Sh REVERSE MODE
|
|
||||||
.Nm
|
|
||||||
can produce a PNG image from valid data.
|
|
||||||
This may be useful for ripping graphics, recovering lost source images, etc.
|
|
||||||
An important caveat on that last one, though: the conversion process is
|
|
||||||
.Sy lossy
|
|
||||||
both ways, so the
|
|
||||||
.Do reversed Dc image won't be perfectly identical to the original\(embut it should be close to a Game Boy's output .
|
|
||||||
.Pq Keep in mind that many of consoles output different colors, so there is no true reference rendering.
|
|
||||||
.Pp
|
|
||||||
When using reverse mode, make sure to pass the same flags that were given when generating the data, especially
|
|
||||||
.Fl C , d , N , s , x ,
|
|
||||||
and
|
|
||||||
.Fl Z .
|
|
||||||
.Do Sx At-files Dc may help with this .
|
|
||||||
.Nm
|
|
||||||
will warn about any inconsistencies it detects.
|
|
||||||
.Pp
|
|
||||||
Files that are normally outputs
|
|
||||||
.Pq Fl a , p , t
|
|
||||||
become inputs, and
|
|
||||||
.Ar file
|
|
||||||
will be written to instead of read from, and thus needs not exist beforehand.
|
|
||||||
Any of these inputs not passed is assumed to be some default:
|
|
||||||
.Bl -column "attribute map"
|
|
||||||
.It palettes Ta Unspecified palette data makes
|
|
||||||
.Nm
|
|
||||||
assume DMG (monochrome Game Boy) mode: a single palette of 4 grays.
|
|
||||||
It is possible to pass palettes using
|
|
||||||
.Fl c
|
|
||||||
instead of
|
|
||||||
.Fl p .
|
|
||||||
.It tile data Ta Tile data must be provided, as there is no reasonable assumption to fall back on.
|
|
||||||
.It tile map Ta A missing tile map makes
|
|
||||||
.Nm
|
|
||||||
assume that tiles were not deduplicated, and should be laid out in the order they are stored.
|
|
||||||
.It attribute map Ta Without an attribute map,
|
|
||||||
.Nm
|
|
||||||
assumes that no tiles were mirrored.
|
|
||||||
.El
|
|
||||||
.Sh NOTES
|
|
||||||
Some flags have had their functionality removed.
|
|
||||||
.Fl D , f ,
|
|
||||||
and
|
|
||||||
.Fl F
|
|
||||||
are now ignored, and
|
|
||||||
.Fl h
|
|
||||||
is an alias for the new (and less confusingly named)
|
|
||||||
.Fl Z .
|
|
||||||
These will be removed and/or repurposed in future versions of
|
|
||||||
.Nm ,
|
|
||||||
so relying on them is not recommended.
|
|
||||||
The same applies to the corresponding long options.
|
|
||||||
.Pp
|
|
||||||
If you are curious, you may find out that palette generation is an NP-complete problem, so
|
|
||||||
.Nm
|
|
||||||
does not attempt to find the optimal solution, but instead to find a good one in a reasonable amount of time.
|
|
||||||
It is possible to compute the optimal solution externally (using a solver, for example), and then provide it to
|
|
||||||
.Nm
|
|
||||||
via
|
|
||||||
.Fl c .
|
|
||||||
.Sh EXAMPLES
|
|
||||||
The following will only validate the PNG (check its size, that all tiles have a suitable amount of colors, etc.), but output nothing:
|
|
||||||
.Pp
|
|
||||||
.Dl $ rgbgfx src/res/maps/overworld/tileset.png
|
|
||||||
.Pp
|
|
||||||
The following will convert the image using the two given palettes (and only those), and store the generated 2bpp tile data in
|
|
||||||
.Ql tileset.2bpp ,
|
|
||||||
and the attribute map in
|
|
||||||
.Ql tileset.attrmap .
|
|
||||||
.Pp
|
|
||||||
.Dl $ rgbgfx -c '#ffffff,#8d05de, #dc7905,#000000 ; #fff,#8d05de, #7e0000 \&, #000' -A -o tileset.2bpp tileset.png
|
|
||||||
.Pp
|
|
||||||
TODO: more examples.
|
|
||||||
.Sh BUGS
|
|
||||||
Please report bugs and mistakes in this man page on
|
|
||||||
.Lk https://github.com/gbdev/rgbds/issues GitHub .
|
|
||||||
Bug reports and feature requests about RGBDS are also welcome!
|
|
||||||
.Sh SEE ALSO
|
|
||||||
.Xr rgbds 7 ,
|
|
||||||
.Xr rgbasm 1 ,
|
|
||||||
.Xr rgblink 1 ,
|
|
||||||
.Xr rgbfix 1 ,
|
|
||||||
.Xr gbz80 7
|
|
||||||
.Pp
|
|
||||||
The Game Boy hardware reference
|
|
||||||
.Lk https://gbdev.io/pandocs/Rendering.html Pan Docs ,
|
|
||||||
particularly the section about graphics.
|
|
||||||
.Sh HISTORY
|
|
||||||
.Nm
|
|
||||||
was originally created by
|
|
||||||
.An stag019
|
|
||||||
to be included in RGBDS.
|
|
||||||
It was later rewritten by
|
|
||||||
.An ISSOtm ,
|
|
||||||
and is now maintained by a number of contributors at
|
|
||||||
.Lk https://github.com/gbdev/rgbds .
|
|
||||||
@@ -14,6 +14,14 @@ set(common_src
|
|||||||
"_version.c"
|
"_version.c"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
find_package(PkgConfig)
|
||||||
|
if(MSVC OR NOT PKG_CONFIG_FOUND)
|
||||||
|
# fallback to find_package
|
||||||
|
find_package(PNG REQUIRED)
|
||||||
|
else()
|
||||||
|
pkg_check_modules(LIBPNG REQUIRED libpng)
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(BISON REQUIRED)
|
find_package(BISON REQUIRED)
|
||||||
set(BISON_FLAGS "-Wall")
|
set(BISON_FLAGS "-Wall")
|
||||||
# Set sompe optimization flags on versions that support them
|
# Set sompe optimization flags on versions that support them
|
||||||
@@ -62,16 +70,9 @@ set(rgbfix_src
|
|||||||
)
|
)
|
||||||
|
|
||||||
set(rgbgfx_src
|
set(rgbgfx_src
|
||||||
"gfx/main.cpp"
|
"gfx/gb.c"
|
||||||
"gfx/pal_packing.cpp"
|
"gfx/main.c"
|
||||||
"gfx/pal_sorting.cpp"
|
"gfx/makepng.c"
|
||||||
"gfx/pal_spec.cpp"
|
|
||||||
"gfx/process.cpp"
|
|
||||||
"gfx/proto_palette.cpp"
|
|
||||||
"gfx/reverse.cpp"
|
|
||||||
"gfx/rgba.cpp"
|
|
||||||
"extern/getopt.c"
|
|
||||||
"error.c"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(rgblink_src
|
set(rgblink_src
|
||||||
@@ -81,7 +82,6 @@ set(rgblink_src
|
|||||||
"link/output.c"
|
"link/output.c"
|
||||||
"link/patch.c"
|
"link/patch.c"
|
||||||
"link/script.c"
|
"link/script.c"
|
||||||
"link/sdas_obj.c"
|
|
||||||
"link/section.c"
|
"link/section.c"
|
||||||
"link/symbol.c"
|
"link/symbol.c"
|
||||||
"hashmap.c"
|
"hashmap.c"
|
||||||
@@ -97,6 +97,22 @@ foreach(PROG "asm" "fix" "gfx" "link")
|
|||||||
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin)
|
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
set(MANDIR "share/man")
|
||||||
|
set(man1 "asm/rgbasm.1"
|
||||||
|
"fix/rgbfix.1"
|
||||||
|
"gfx/rgbgfx.1"
|
||||||
|
"link/rgblink.1")
|
||||||
|
set(man5 "asm/rgbasm.5"
|
||||||
|
"link/rgblink.5"
|
||||||
|
"rgbds.5")
|
||||||
|
set(man7 "gbz80.7"
|
||||||
|
"rgbds.7")
|
||||||
|
|
||||||
|
foreach(SECTION "man1" "man5" "man7")
|
||||||
|
set(DEST "${MANDIR}/${SECTION}")
|
||||||
|
install(FILES ${${SECTION}} DESTINATION ${DEST})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
if(LIBPNG_FOUND) # pkg-config
|
if(LIBPNG_FOUND) # pkg-config
|
||||||
target_include_directories(rgbgfx PRIVATE ${LIBPNG_INCLUDE_DIRS})
|
target_include_directories(rgbgfx PRIVATE ${LIBPNG_INCLUDE_DIRS})
|
||||||
target_link_directories(rgbgfx PRIVATE ${LIBPNG_LIBRARY_DIRS})
|
target_link_directories(rgbgfx PRIVATE ${LIBPNG_LIBRARY_DIRS})
|
||||||
|
|||||||
@@ -21,29 +21,33 @@
|
|||||||
|
|
||||||
#include "hashmap.h"
|
#include "hashmap.h"
|
||||||
|
|
||||||
// Charmaps are stored using a structure known as "trie".
|
/*
|
||||||
// Essentially a tree, where each nodes stores a single character's worth of info:
|
* Charmaps are stored using a structure known as "trie".
|
||||||
// whether there exists a mapping that ends at the current character,
|
* Essentially a tree, where each nodes stores a single character's worth of info:
|
||||||
|
* whether there exists a mapping that ends at the current character,
|
||||||
|
*/
|
||||||
struct Charnode {
|
struct Charnode {
|
||||||
bool isTerminal; // Whether there exists a mapping that ends here
|
bool isTerminal; /* Whether there exists a mapping that ends here */
|
||||||
uint8_t value; // If the above is true, its corresponding value
|
uint8_t value; /* If the above is true, its corresponding value */
|
||||||
// This MUST be indexes and not pointers, because pointers get invalidated by `realloc`!!
|
/* This MUST be indexes and not pointers, because pointers get invalidated by `realloc`!! */
|
||||||
size_t next[255]; // Indexes of where to go next, 0 = nowhere
|
size_t next[255]; /* Indexes of where to go next, 0 = nowhere */
|
||||||
};
|
};
|
||||||
|
|
||||||
#define INITIAL_CAPACITY 32
|
#define INITIAL_CAPACITY 32
|
||||||
|
|
||||||
struct Charmap {
|
struct Charmap {
|
||||||
char *name;
|
char *name;
|
||||||
size_t usedNodes; // How many nodes are being used
|
size_t usedNodes; /* How many nodes are being used */
|
||||||
size_t capacity; // How many nodes have been allocated
|
size_t capacity; /* How many nodes have been allocated */
|
||||||
struct Charnode nodes[]; // first node is reserved for the root node
|
struct Charnode nodes[]; /* first node is reserved for the root node */
|
||||||
};
|
};
|
||||||
|
|
||||||
static HashMap charmaps;
|
static HashMap charmaps;
|
||||||
|
|
||||||
// Store pointers to hashmap nodes, so that there is only one pointer to the memory block
|
/*
|
||||||
// that gets reallocated.
|
* Store pointers to hashmap nodes, so that there is only one pointer to the memory block
|
||||||
|
* that gets reallocated.
|
||||||
|
*/
|
||||||
static struct Charmap **currentCharmap;
|
static struct Charmap **currentCharmap;
|
||||||
|
|
||||||
struct CharmapStackEntry {
|
struct CharmapStackEntry {
|
||||||
@@ -92,7 +96,7 @@ struct Charmap *charmap_New(char const *name, char const *baseName)
|
|||||||
return charmap;
|
return charmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init the new charmap's fields
|
/* Init the new charmap's fields */
|
||||||
if (base) {
|
if (base) {
|
||||||
resizeCharmap(&charmap, base->capacity);
|
resizeCharmap(&charmap, base->capacity);
|
||||||
charmap->usedNodes = base->usedNodes;
|
charmap->usedNodes = base->usedNodes;
|
||||||
@@ -101,7 +105,7 @@ struct Charmap *charmap_New(char const *name, char const *baseName)
|
|||||||
} else {
|
} else {
|
||||||
resizeCharmap(&charmap, INITIAL_CAPACITY);
|
resizeCharmap(&charmap, INITIAL_CAPACITY);
|
||||||
charmap->usedNodes = 1;
|
charmap->usedNodes = 1;
|
||||||
initNode(&charmap->nodes[0]); // Init the root node
|
initNode(&charmap->nodes[0]); /* Init the root node */
|
||||||
}
|
}
|
||||||
charmap->name = strdup(name);
|
charmap->name = strdup(name);
|
||||||
|
|
||||||
@@ -165,16 +169,16 @@ void charmap_Add(char *mapping, uint8_t value)
|
|||||||
if (node->next[c]) {
|
if (node->next[c]) {
|
||||||
node = &charmap->nodes[node->next[c]];
|
node = &charmap->nodes[node->next[c]];
|
||||||
} else {
|
} else {
|
||||||
// Register next available node
|
/* Register next available node */
|
||||||
node->next[c] = charmap->usedNodes;
|
node->next[c] = charmap->usedNodes;
|
||||||
// If no more nodes are available, get new ones
|
/* If no more nodes are available, get new ones */
|
||||||
if (charmap->usedNodes == charmap->capacity) {
|
if (charmap->usedNodes == charmap->capacity) {
|
||||||
charmap->capacity *= 2;
|
charmap->capacity *= 2;
|
||||||
resizeCharmap(currentCharmap, charmap->capacity);
|
resizeCharmap(currentCharmap, charmap->capacity);
|
||||||
charmap = *currentCharmap;
|
charmap = *currentCharmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch to and init new node
|
/* Switch to and init new node */
|
||||||
node = &charmap->nodes[charmap->usedNodes++];
|
node = &charmap->nodes[charmap->usedNodes++];
|
||||||
initNode(node);
|
initNode(node);
|
||||||
}
|
}
|
||||||
@@ -199,10 +203,12 @@ size_t charmap_Convert(char const *input, uint8_t *output)
|
|||||||
|
|
||||||
size_t charmap_ConvertNext(char const **input, uint8_t **output)
|
size_t charmap_ConvertNext(char const **input, uint8_t **output)
|
||||||
{
|
{
|
||||||
// The goal is to match the longest mapping possible.
|
/*
|
||||||
// For that, advance through the trie with each character read.
|
* The goal is to match the longest mapping possible.
|
||||||
// If that would lead to a dead end, rewind characters until the last match, and output.
|
* For that, advance through the trie with each character read.
|
||||||
// If no match, read a UTF-8 codepoint and output that.
|
* If that would lead to a dead end, rewind characters until the last match, and output.
|
||||||
|
* If no match, read a UTF-8 codepoint and output that.
|
||||||
|
*/
|
||||||
struct Charmap const *charmap = *currentCharmap;
|
struct Charmap const *charmap = *currentCharmap;
|
||||||
struct Charnode const *node = &charmap->nodes[0];
|
struct Charnode const *node = &charmap->nodes[0];
|
||||||
struct Charnode const *match = NULL;
|
struct Charnode const *match = NULL;
|
||||||
@@ -236,7 +242,6 @@ size_t charmap_ConvertNext(char const **input, uint8_t **output)
|
|||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
} else if (**input) { // No match found, but there is some input left
|
} else if (**input) { // No match found, but there is some input left
|
||||||
int firstChar = **input;
|
|
||||||
// This will write the codepoint's value to `output`, little-endian
|
// This will write the codepoint's value to `output`, little-endian
|
||||||
size_t codepointLen = readUTF8Char(output ? *output : NULL,
|
size_t codepointLen = readUTF8Char(output ? *output : NULL,
|
||||||
*input);
|
*input);
|
||||||
@@ -249,12 +254,6 @@ size_t charmap_ConvertNext(char const **input, uint8_t **output)
|
|||||||
if (output)
|
if (output)
|
||||||
*output += codepointLen;
|
*output += codepointLen;
|
||||||
|
|
||||||
// Check if the character map is not the default "main" one, or if
|
|
||||||
// it has any mappings defined
|
|
||||||
if (strcmp(charmap->name, "main") || charmap->usedNodes > 1)
|
|
||||||
warning(WARNING_UNMAPPED_CHAR,
|
|
||||||
"Unmapped character %s\n", printChar(firstChar));
|
|
||||||
|
|
||||||
return codepointLen;
|
return codepointLen;
|
||||||
|
|
||||||
} else { // End of input
|
} else { // End of input
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Fixed-point math routines
|
/*
|
||||||
|
* Fixed-point math routines
|
||||||
|
*/
|
||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
@@ -17,24 +19,30 @@
|
|||||||
#include "asm/symbol.h"
|
#include "asm/symbol.h"
|
||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
|
|
||||||
|
#define fix2double(i) ((double)((i) / 65536.0))
|
||||||
|
#define double2fix(d) ((int32_t)round((d) * 65536.0))
|
||||||
|
|
||||||
|
// pi radians == 32768 fixed-point "degrees"
|
||||||
|
#define fdeg2rad(f) ((f) * (M_PI / 32768.0))
|
||||||
|
#define rad2fdeg(r) ((r) * (32768.0 / M_PI))
|
||||||
|
|
||||||
#ifndef M_PI
|
#ifndef M_PI
|
||||||
#define M_PI 3.14159265358979323846
|
#define M_PI 3.14159265358979323846
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define fix2double(i) ((double)((i) / fix_PrecisionFactor()))
|
/*
|
||||||
#define double2fix(d) ((int32_t)round((d) * fix_PrecisionFactor()))
|
* Return the _PI symbol value
|
||||||
|
*/
|
||||||
// pi*2 radians == 2**fixPrecision fixed-point "degrees"
|
int32_t fix_Callback_PI(void)
|
||||||
#define fdeg2rad(f) ((f) * (M_PI * 2) / fix_PrecisionFactor())
|
|
||||||
#define rad2fdeg(r) ((r) * fix_PrecisionFactor() / (M_PI * 2))
|
|
||||||
|
|
||||||
uint8_t fixPrecision;
|
|
||||||
|
|
||||||
double fix_PrecisionFactor(void)
|
|
||||||
{
|
{
|
||||||
return pow(2.0, fixPrecision);
|
warning(WARNING_OBSOLETE, "`_PI` is deprecated; use 3.14159\n");
|
||||||
|
|
||||||
|
return double2fix(M_PI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a fixed point value
|
||||||
|
*/
|
||||||
void fix_Print(int32_t i)
|
void fix_Print(int32_t i)
|
||||||
{
|
{
|
||||||
uint32_t u = i;
|
uint32_t u = i;
|
||||||
@@ -45,80 +53,117 @@ void fix_Print(int32_t i)
|
|||||||
sign = "-";
|
sign = "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> fixPrecision,
|
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> 16,
|
||||||
((uint32_t)(fix2double(u) * 100000 + 0.5)) % 100000);
|
((uint32_t)(fix2double(u) * 100000 + 0.5)) % 100000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate sine
|
||||||
|
*/
|
||||||
int32_t fix_Sin(int32_t i)
|
int32_t fix_Sin(int32_t i)
|
||||||
{
|
{
|
||||||
return double2fix(sin(fdeg2rad(fix2double(i))));
|
return double2fix(sin(fdeg2rad(fix2double(i))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate cosine
|
||||||
|
*/
|
||||||
int32_t fix_Cos(int32_t i)
|
int32_t fix_Cos(int32_t i)
|
||||||
{
|
{
|
||||||
return double2fix(cos(fdeg2rad(fix2double(i))));
|
return double2fix(cos(fdeg2rad(fix2double(i))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate tangent
|
||||||
|
*/
|
||||||
int32_t fix_Tan(int32_t i)
|
int32_t fix_Tan(int32_t i)
|
||||||
{
|
{
|
||||||
return double2fix(tan(fdeg2rad(fix2double(i))));
|
return double2fix(tan(fdeg2rad(fix2double(i))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate arcsine
|
||||||
|
*/
|
||||||
int32_t fix_ASin(int32_t i)
|
int32_t fix_ASin(int32_t i)
|
||||||
{
|
{
|
||||||
return double2fix(rad2fdeg(asin(fix2double(i))));
|
return double2fix(rad2fdeg(asin(fix2double(i))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate arccosine
|
||||||
|
*/
|
||||||
int32_t fix_ACos(int32_t i)
|
int32_t fix_ACos(int32_t i)
|
||||||
{
|
{
|
||||||
return double2fix(rad2fdeg(acos(fix2double(i))));
|
return double2fix(rad2fdeg(acos(fix2double(i))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate arctangent
|
||||||
|
*/
|
||||||
int32_t fix_ATan(int32_t i)
|
int32_t fix_ATan(int32_t i)
|
||||||
{
|
{
|
||||||
return double2fix(rad2fdeg(atan(fix2double(i))));
|
return double2fix(rad2fdeg(atan(fix2double(i))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate atan2
|
||||||
|
*/
|
||||||
int32_t fix_ATan2(int32_t i, int32_t j)
|
int32_t fix_ATan2(int32_t i, int32_t j)
|
||||||
{
|
{
|
||||||
return double2fix(rad2fdeg(atan2(fix2double(i), fix2double(j))));
|
return double2fix(rad2fdeg(atan2(fix2double(i), fix2double(j))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Multiplication
|
||||||
|
*/
|
||||||
int32_t fix_Mul(int32_t i, int32_t j)
|
int32_t fix_Mul(int32_t i, int32_t j)
|
||||||
{
|
{
|
||||||
return double2fix(fix2double(i) * fix2double(j));
|
return double2fix(fix2double(i) * fix2double(j));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Division
|
||||||
|
*/
|
||||||
int32_t fix_Div(int32_t i, int32_t j)
|
int32_t fix_Div(int32_t i, int32_t j)
|
||||||
{
|
{
|
||||||
return double2fix(fix2double(i) / fix2double(j));
|
return double2fix(fix2double(i) / fix2double(j));
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t fix_Mod(int32_t i, int32_t j)
|
/*
|
||||||
{
|
* Power
|
||||||
return double2fix(fmod(fix2double(i), fix2double(j)));
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
int32_t fix_Pow(int32_t i, int32_t j)
|
int32_t fix_Pow(int32_t i, int32_t j)
|
||||||
{
|
{
|
||||||
return double2fix(pow(fix2double(i), fix2double(j)));
|
return double2fix(pow(fix2double(i), fix2double(j)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Logarithm
|
||||||
|
*/
|
||||||
int32_t fix_Log(int32_t i, int32_t j)
|
int32_t fix_Log(int32_t i, int32_t j)
|
||||||
{
|
{
|
||||||
return double2fix(log(fix2double(i)) / log(fix2double(j)));
|
return double2fix(log(fix2double(i)) / log(fix2double(j)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Round
|
||||||
|
*/
|
||||||
int32_t fix_Round(int32_t i)
|
int32_t fix_Round(int32_t i)
|
||||||
{
|
{
|
||||||
return double2fix(round(fix2double(i)));
|
return double2fix(round(fix2double(i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ceil
|
||||||
|
*/
|
||||||
int32_t fix_Ceil(int32_t i)
|
int32_t fix_Ceil(int32_t i)
|
||||||
{
|
{
|
||||||
return double2fix(ceil(fix2double(i)));
|
return double2fix(ceil(fix2double(i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Floor
|
||||||
|
*/
|
||||||
int32_t fix_Floor(int32_t i)
|
int32_t fix_Floor(int32_t i)
|
||||||
{
|
{
|
||||||
return double2fix(floor(fix2double(i)));
|
return double2fix(floor(fix2double(i)));
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "asm/fixpoint.h"
|
|
||||||
#include "asm/format.h"
|
#include "asm/format.h"
|
||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
// sign
|
/* sign */
|
||||||
case ' ':
|
case ' ':
|
||||||
case '+':
|
case '+':
|
||||||
if (fmt->state > FORMAT_SIGN)
|
if (fmt->state > FORMAT_SIGN)
|
||||||
@@ -56,7 +55,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
|||||||
fmt->sign = c;
|
fmt->sign = c;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// prefix
|
/* prefix */
|
||||||
case '#':
|
case '#':
|
||||||
if (fmt->state > FORMAT_PREFIX)
|
if (fmt->state > FORMAT_PREFIX)
|
||||||
goto invalid;
|
goto invalid;
|
||||||
@@ -64,7 +63,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
|||||||
fmt->prefix = true;
|
fmt->prefix = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// align
|
/* align */
|
||||||
case '-':
|
case '-':
|
||||||
if (fmt->state > FORMAT_ALIGN)
|
if (fmt->state > FORMAT_ALIGN)
|
||||||
goto invalid;
|
goto invalid;
|
||||||
@@ -72,11 +71,11 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
|||||||
fmt->alignLeft = true;
|
fmt->alignLeft = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// pad and width
|
/* pad and width */
|
||||||
case '0':
|
case '0':
|
||||||
if (fmt->state < FORMAT_WIDTH)
|
if (fmt->state < FORMAT_WIDTH)
|
||||||
fmt->padZero = true;
|
fmt->padZero = true;
|
||||||
// fallthrough
|
/* fallthrough */
|
||||||
case '1':
|
case '1':
|
||||||
case '2':
|
case '2':
|
||||||
case '3':
|
case '3':
|
||||||
@@ -105,7 +104,7 @@ void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
|||||||
fmt->hasFrac = true;
|
fmt->hasFrac = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// type
|
/* type */
|
||||||
case 'd':
|
case 'd':
|
||||||
case 'u':
|
case 'u':
|
||||||
case 'X':
|
case 'X':
|
||||||
@@ -150,7 +149,7 @@ void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, cha
|
|||||||
size_t len = strlen(value);
|
size_t len = strlen(value);
|
||||||
size_t totalLen = fmt->width > len ? fmt->width : len;
|
size_t totalLen = fmt->width > len ? fmt->width : len;
|
||||||
|
|
||||||
if (totalLen > bufLen - 1) { // bufLen includes terminator
|
if (totalLen > bufLen - 1) { /* bufLen includes terminator */
|
||||||
error("Formatted string value too long\n");
|
error("Formatted string value too long\n");
|
||||||
totalLen = bufLen - 1;
|
totalLen = bufLen - 1;
|
||||||
if (len > totalLen)
|
if (len > totalLen)
|
||||||
@@ -183,7 +182,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
if (fmt->type == 's')
|
if (fmt->type == 's')
|
||||||
error("Formatting number as type 's'\n");
|
error("Formatting number as type 's'\n");
|
||||||
|
|
||||||
char sign = fmt->sign; // 0 or ' ' or '+'
|
char sign = fmt->sign; /* 0 or ' ' or '+' */
|
||||||
|
|
||||||
if (fmt->type == 'd' || fmt->type == 'f') {
|
if (fmt->type == 'd' || fmt->type == 'f') {
|
||||||
int32_t v = value;
|
int32_t v = value;
|
||||||
@@ -201,10 +200,10 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
: fmt->type == 'o' ? '&'
|
: fmt->type == 'o' ? '&'
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
char valueBuf[262]; // Max 5 digits + decimal + 255 fraction digits + terminator
|
char valueBuf[262]; /* Max 5 digits + decimal + 255 fraction digits + terminator */
|
||||||
|
|
||||||
if (fmt->type == 'b') {
|
if (fmt->type == 'b') {
|
||||||
// Special case for binary
|
/* Special case for binary */
|
||||||
char *ptr = valueBuf;
|
char *ptr = valueBuf;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@@ -214,7 +213,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
|
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
|
|
||||||
// Reverse the digits
|
/* Reverse the digits */
|
||||||
size_t valueLen = ptr - valueBuf;
|
size_t valueLen = ptr - valueBuf;
|
||||||
|
|
||||||
for (size_t i = 0, j = valueLen - 1; i < j; i++, j--) {
|
for (size_t i = 0, j = valueLen - 1; i < j; i++, j--) {
|
||||||
@@ -224,9 +223,9 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
valueBuf[j] = c;
|
valueBuf[j] = c;
|
||||||
}
|
}
|
||||||
} else if (fmt->type == 'f') {
|
} else if (fmt->type == 'f') {
|
||||||
// Special case for fixed-point
|
/* Special case for fixed-point */
|
||||||
|
|
||||||
// Default fractional width (C's is 6 for "%f"; here 5 is enough for Q16.16)
|
/* Default fractional width (C's is 6 for "%f"; here 5 is enough) */
|
||||||
size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
|
size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
|
||||||
|
|
||||||
if (fracWidth > 255) {
|
if (fracWidth > 255) {
|
||||||
@@ -235,8 +234,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
fracWidth = 255;
|
fracWidth = 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth,
|
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth, value / 65536.0);
|
||||||
value / fix_PrecisionFactor());
|
|
||||||
} else {
|
} else {
|
||||||
char const *spec = fmt->type == 'd' ? "%" PRId32
|
char const *spec = fmt->type == 'd' ? "%" PRId32
|
||||||
: fmt->type == 'u' ? "%" PRIu32
|
: fmt->type == 'u' ? "%" PRIu32
|
||||||
@@ -252,7 +250,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
size_t numLen = !!sign + !!prefix + len;
|
size_t numLen = !!sign + !!prefix + len;
|
||||||
size_t totalLen = fmt->width > numLen ? fmt->width : numLen;
|
size_t totalLen = fmt->width > numLen ? fmt->width : numLen;
|
||||||
|
|
||||||
if (totalLen > bufLen - 1) { // bufLen includes terminator
|
if (totalLen > bufLen - 1) { /* bufLen includes terminator */
|
||||||
error("Formatted numeric value too long\n");
|
error("Formatted numeric value too long\n");
|
||||||
totalLen = bufLen - 1;
|
totalLen = bufLen - 1;
|
||||||
if (numLen > totalLen) {
|
if (numLen > totalLen) {
|
||||||
@@ -275,7 +273,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
buf[i] = ' ';
|
buf[i] = ' ';
|
||||||
} else {
|
} else {
|
||||||
if (fmt->padZero) {
|
if (fmt->padZero) {
|
||||||
// sign, then prefix, then zero padding
|
/* sign, then prefix, then zero padding */
|
||||||
if (sign)
|
if (sign)
|
||||||
buf[pos++] = sign;
|
buf[pos++] = sign;
|
||||||
if (prefix)
|
if (prefix)
|
||||||
@@ -283,7 +281,7 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
|||||||
for (size_t i = 0; i < padLen; i++)
|
for (size_t i = 0; i < padLen; i++)
|
||||||
buf[pos++] = '0';
|
buf[pos++] = '0';
|
||||||
} else {
|
} else {
|
||||||
// space padding, then sign, then prefix
|
/* space padding, then sign, then prefix */
|
||||||
for (size_t i = 0; i < padLen; i++)
|
for (size_t i = 0; i < padLen; i++)
|
||||||
buf[pos++] = ' ';
|
buf[pos++] = ' ';
|
||||||
if (sign)
|
if (sign)
|
||||||
|
|||||||
100
src/asm/fstack.c
100
src/asm/fstack.c
@@ -19,7 +19,7 @@
|
|||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
#include "asm/symbol.h"
|
#include "asm/symbol.h"
|
||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
#include "platform.h" // S_ISDIR (stat macro)
|
#include "platform.h" /* S_ISDIR (stat macro) */
|
||||||
|
|
||||||
#define MAXINCPATHS 128
|
#define MAXINCPATHS 128
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ struct Context {
|
|||||||
struct FileStackNode *fileInfo;
|
struct FileStackNode *fileInfo;
|
||||||
struct LexerState *lexerState;
|
struct LexerState *lexerState;
|
||||||
uint32_t uniqueID;
|
uint32_t uniqueID;
|
||||||
struct MacroArgs *macroArgs; // Macro args are *saved* here
|
struct MacroArgs *macroArgs; /* Macro args are *saved* here */
|
||||||
uint32_t nbReptIters;
|
uint32_t nbReptIters;
|
||||||
int32_t forValue;
|
int32_t forValue;
|
||||||
int32_t forStep;
|
int32_t forStep;
|
||||||
@@ -37,6 +37,7 @@ struct Context {
|
|||||||
|
|
||||||
static struct Context *contextStack;
|
static struct Context *contextStack;
|
||||||
static size_t contextDepth = 0;
|
static size_t contextDepth = 0;
|
||||||
|
#define DEFAULT_MAX_DEPTH 64
|
||||||
size_t maxRecursionDepth;
|
size_t maxRecursionDepth;
|
||||||
|
|
||||||
static unsigned int nbIncPaths = 0;
|
static unsigned int nbIncPaths = 0;
|
||||||
@@ -47,7 +48,7 @@ static const char *dumpNodeAndParents(struct FileStackNode const *node)
|
|||||||
char const *name;
|
char const *name;
|
||||||
|
|
||||||
if (node->type == NODE_REPT) {
|
if (node->type == NODE_REPT) {
|
||||||
assert(node->parent); // REPT nodes should always have a parent
|
assert(node->parent); /* REPT nodes should always have a parent */
|
||||||
struct FileStackReptNode const *reptInfo = (struct FileStackReptNode const *)node;
|
struct FileStackReptNode const *reptInfo = (struct FileStackReptNode const *)node;
|
||||||
|
|
||||||
name = dumpNodeAndParents(node->parent);
|
name = dumpNodeAndParents(node->parent);
|
||||||
@@ -88,7 +89,7 @@ struct FileStackNode *fstk_GetFileStack(void)
|
|||||||
|
|
||||||
struct FileStackNode *node = contextStack->fileInfo;
|
struct FileStackNode *node = contextStack->fileInfo;
|
||||||
|
|
||||||
// Mark node and all of its parents as referenced if not already so they don't get freed
|
/* Mark node and all of its parents as referenced if not already so they don't get freed */
|
||||||
while (node && !node->referenced) {
|
while (node && !node->referenced) {
|
||||||
node->ID = -1;
|
node->ID = -1;
|
||||||
node->referenced = true;
|
node->referenced = true;
|
||||||
@@ -99,7 +100,7 @@ struct FileStackNode *fstk_GetFileStack(void)
|
|||||||
|
|
||||||
char const *fstk_GetFileName(void)
|
char const *fstk_GetFileName(void)
|
||||||
{
|
{
|
||||||
// Iterating via the nodes themselves skips nested REPTs
|
/* Iterating via the nodes themselves skips nested REPTs */
|
||||||
struct FileStackNode const *node = contextStack->fileInfo;
|
struct FileStackNode const *node = contextStack->fileInfo;
|
||||||
|
|
||||||
while (node->type != NODE_FILE)
|
while (node->type != NODE_FILE)
|
||||||
@@ -120,7 +121,7 @@ void fstk_AddIncludePath(char const *path)
|
|||||||
char *str = malloc(allocSize);
|
char *str = malloc(allocSize);
|
||||||
|
|
||||||
if (!str) {
|
if (!str) {
|
||||||
// Attempt to continue without that path
|
/* Attempt to continue without that path */
|
||||||
error("Failed to allocate new include path: %s\n", strerror(errno));
|
error("Failed to allocate new include path: %s\n", strerror(errno));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -149,14 +150,14 @@ static bool isPathValid(char const *path)
|
|||||||
if (stat(path, &statbuf) != 0)
|
if (stat(path, &statbuf) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Reject directories
|
/* Reject directories */
|
||||||
return !S_ISDIR(statbuf.st_mode);
|
return !S_ISDIR(statbuf.st_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
||||||
{
|
{
|
||||||
if (!*size) {
|
if (!*size) {
|
||||||
*size = 64; // This is arbitrary, really
|
*size = 64; /* This is arbitrary, really */
|
||||||
*fullPath = realloc(*fullPath, *size);
|
*fullPath = realloc(*fullPath, *size);
|
||||||
if (!*fullPath)
|
if (!*fullPath)
|
||||||
error("realloc error during include path search: %s\n",
|
error("realloc error during include path search: %s\n",
|
||||||
@@ -174,8 +175,8 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Oh how I wish `asnprintf` was standard...
|
/* Oh how I wish `asnprintf` was standard... */
|
||||||
if ((size_t)len >= *size) { // `size` includes the terminator, `len` doesn't
|
if ((size_t)len >= *size) { /* `len` doesn't include the terminator, `size` does */
|
||||||
*size = len + 1;
|
*size = len + 1;
|
||||||
*fullPath = realloc(*fullPath, *size);
|
*fullPath = realloc(*fullPath, *size);
|
||||||
if (!*fullPath) {
|
if (!*fullPath) {
|
||||||
@@ -212,18 +213,17 @@ bool yywrap(void)
|
|||||||
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||||
ifDepth, ifDepth == 1 ? "" : "s");
|
ifDepth, ifDepth == 1 ? "" : "s");
|
||||||
|
|
||||||
if (contextStack->fileInfo->type == NODE_REPT) {
|
if (contextStack->fileInfo->type == NODE_REPT) { /* The context is a REPT block, which may loop */
|
||||||
// The context is a REPT or FOR block, which may loop
|
|
||||||
struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo;
|
struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo;
|
||||||
|
|
||||||
// If the node is referenced, we can't edit it; duplicate it
|
/* If the node is referenced, we can't edit it; duplicate it */
|
||||||
if (contextStack->fileInfo->referenced) {
|
if (contextStack->fileInfo->referenced) {
|
||||||
size_t size = sizeof(*fileInfo) + sizeof(fileInfo->iters[0]) * fileInfo->reptDepth;
|
size_t size = sizeof(*fileInfo) + sizeof(fileInfo->iters[0]) * fileInfo->reptDepth;
|
||||||
struct FileStackReptNode *copy = malloc(size);
|
struct FileStackReptNode *copy = malloc(size);
|
||||||
|
|
||||||
if (!copy)
|
if (!copy)
|
||||||
fatalerror("Failed to duplicate REPT file node: %s\n", strerror(errno));
|
fatalerror("Failed to duplicate REPT file node: %s\n", strerror(errno));
|
||||||
// Copy all info but the referencing
|
/* Copy all info but the referencing */
|
||||||
memcpy(copy, fileInfo, size);
|
memcpy(copy, fileInfo, size);
|
||||||
copy->node.next = NULL;
|
copy->node.next = NULL;
|
||||||
copy->node.referenced = false;
|
copy->node.referenced = false;
|
||||||
@@ -232,19 +232,19 @@ bool yywrap(void)
|
|||||||
contextStack->fileInfo = (struct FileStackNode *)fileInfo;
|
contextStack->fileInfo = (struct FileStackNode *)fileInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is a FOR, update the symbol value
|
/* If this is a FOR, update the symbol value */
|
||||||
if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
|
if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
|
||||||
contextStack->forValue += contextStack->forStep;
|
contextStack->forValue += contextStack->forStep;
|
||||||
struct Symbol *sym = sym_AddVar(contextStack->forName,
|
struct Symbol *sym = sym_AddVar(contextStack->forName,
|
||||||
contextStack->forValue);
|
contextStack->forValue);
|
||||||
|
|
||||||
// This error message will refer to the current iteration
|
/* This error message will refer to the current iteration */
|
||||||
if (sym->type != SYM_VAR)
|
if (sym->type != SYM_VAR)
|
||||||
fatalerror("Failed to update FOR symbol value\n");
|
fatalerror("Failed to update FOR symbol value\n");
|
||||||
}
|
}
|
||||||
// Advance to the next iteration
|
/* Advance to the next iteration */
|
||||||
fileInfo->iters[0]++;
|
fileInfo->iters[0]++;
|
||||||
// If this wasn't the last iteration, wrap instead of popping
|
/* If this wasn't the last iteration, wrap instead of popping */
|
||||||
if (fileInfo->iters[0] <= contextStack->nbReptIters) {
|
if (fileInfo->iters[0] <= contextStack->nbReptIters) {
|
||||||
lexer_RestartRept(contextStack->fileInfo->lineNo);
|
lexer_RestartRept(contextStack->fileInfo->lineNo);
|
||||||
contextStack->uniqueID = macro_UseNewUniqueID();
|
contextStack->uniqueID = macro_UseNewUniqueID();
|
||||||
@@ -261,15 +261,15 @@ bool yywrap(void)
|
|||||||
contextDepth--;
|
contextDepth--;
|
||||||
|
|
||||||
lexer_DeleteState(context->lexerState);
|
lexer_DeleteState(context->lexerState);
|
||||||
// Restore args if a macro (not REPT) saved them
|
/* Restore args if a macro (not REPT) saved them */
|
||||||
if (context->fileInfo->type == NODE_MACRO)
|
if (context->fileInfo->type == NODE_MACRO)
|
||||||
macro_UseNewArgs(contextStack->macroArgs);
|
macro_UseNewArgs(contextStack->macroArgs);
|
||||||
// Free the file stack node
|
/* Free the file stack node */
|
||||||
if (!context->fileInfo->referenced)
|
if (!context->fileInfo->referenced)
|
||||||
free(context->fileInfo);
|
free(context->fileInfo);
|
||||||
// Free the FOR symbol name
|
/* Free the FOR symbol name */
|
||||||
free(context->forName);
|
free(context->forName);
|
||||||
// Free the entry and make its parent the current entry
|
/* Free the entry and make its parent the current entry */
|
||||||
free(context);
|
free(context);
|
||||||
|
|
||||||
lexer_SetState(contextStack->lexerState);
|
lexer_SetState(contextStack->lexerState);
|
||||||
@@ -277,30 +277,30 @@ bool yywrap(void)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure not to switch the lexer state before calling this, so the saved line no is correct.
|
/*
|
||||||
// BE CAREFUL!! This modifies the file stack directly, you should have set up the file info first.
|
* Make sure not to switch the lexer state before calling this, so the saved line no is correct
|
||||||
// Callers should set contextStack->lexerState after this so it is not NULL.
|
* BE CAREFUL!! This modifies the file stack directly, you should have set up the file info first
|
||||||
|
* Callers should set contextStack->lexerState after this so it is not NULL
|
||||||
|
*/
|
||||||
static void newContext(struct FileStackNode *fileInfo)
|
static void newContext(struct FileStackNode *fileInfo)
|
||||||
{
|
{
|
||||||
++contextDepth;
|
++contextDepth;
|
||||||
fstk_NewRecursionDepth(maxRecursionDepth); // Only checks if the max depth was exceeded
|
fstk_NewRecursionDepth(maxRecursionDepth); // Only checks if the max depth was exceeded
|
||||||
|
|
||||||
// Save the current `\@` value, to be restored when this context ends
|
|
||||||
contextStack->uniqueID = macro_GetUniqueID();
|
|
||||||
|
|
||||||
struct Context *context = malloc(sizeof(*context));
|
struct Context *context = malloc(sizeof(*context));
|
||||||
|
|
||||||
if (!context)
|
if (!context)
|
||||||
fatalerror("Failed to allocate memory for new context: %s\n", strerror(errno));
|
fatalerror("Failed to allocate memory for new context: %s\n", strerror(errno));
|
||||||
fileInfo->parent = contextStack->fileInfo;
|
fileInfo->parent = contextStack->fileInfo;
|
||||||
fileInfo->lineNo = 0; // Init to a default value, see struct definition for info
|
fileInfo->lineNo = 0; /* Init to a default value, see struct definition for info */
|
||||||
fileInfo->referenced = false;
|
fileInfo->referenced = false;
|
||||||
fileInfo->lineNo = lexer_GetLineNo();
|
fileInfo->lineNo = lexer_GetLineNo();
|
||||||
context->fileInfo = fileInfo;
|
context->fileInfo = fileInfo;
|
||||||
context->forName = NULL;
|
context->forName = NULL;
|
||||||
|
/*
|
||||||
// Link new entry to its parent so it's reachable later
|
* Link new entry to its parent so it's reachable later
|
||||||
// ERRORS SHOULD NOT OCCUR AFTER THIS!!
|
* ERRORS SHOULD NOT OCCUR AFTER THIS!!
|
||||||
|
*/
|
||||||
context->parent = contextStack;
|
context->parent = contextStack;
|
||||||
contextStack = context;
|
contextStack = context;
|
||||||
}
|
}
|
||||||
@@ -338,8 +338,9 @@ void fstk_RunInclude(char const *path)
|
|||||||
if (!contextStack->lexerState)
|
if (!contextStack->lexerState)
|
||||||
fatalerror("Failed to set up lexer for file include\n");
|
fatalerror("Failed to set up lexer for file include\n");
|
||||||
lexer_SetStateAtEOL(contextStack->lexerState);
|
lexer_SetStateAtEOL(contextStack->lexerState);
|
||||||
// We're back at top-level, so most things are reset
|
/* We're back at top-level, so most things are reset */
|
||||||
contextStack->uniqueID = macro_UndefUniqueID();
|
contextStack->uniqueID = 0;
|
||||||
|
macro_SetUniqueID(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
||||||
@@ -356,16 +357,16 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
|||||||
}
|
}
|
||||||
contextStack->macroArgs = macro_GetCurrentArgs();
|
contextStack->macroArgs = macro_GetCurrentArgs();
|
||||||
|
|
||||||
// Compute total length of this node's name: <base name>::<macro>
|
/* Compute total length of this node's name: <base name>::<macro> */
|
||||||
size_t reptNameLen = 0;
|
size_t reptNameLen = 0;
|
||||||
struct FileStackNode const *node = macro->src;
|
struct FileStackNode const *node = macro->src;
|
||||||
|
|
||||||
if (node->type == NODE_REPT) {
|
if (node->type == NODE_REPT) {
|
||||||
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
|
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
|
||||||
|
|
||||||
// 4294967295 = 2^32 - 1, aka UINT32_MAX
|
/* 4294967295 = 2^32 - 1, aka UINT32_MAX */
|
||||||
reptNameLen += reptNode->reptDepth * strlen("::REPT~4294967295");
|
reptNameLen += reptNode->reptDepth * strlen("::REPT~4294967295");
|
||||||
// Look for next named node
|
/* Look for next named node */
|
||||||
do {
|
do {
|
||||||
node = node->parent;
|
node = node->parent;
|
||||||
} while (node->type == NODE_REPT);
|
} while (node->type == NODE_REPT);
|
||||||
@@ -381,7 +382,7 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileInfo->node.type = NODE_MACRO;
|
fileInfo->node.type = NODE_MACRO;
|
||||||
// Print the name...
|
/* Print the name... */
|
||||||
char *dest = fileInfo->name;
|
char *dest = fileInfo->name;
|
||||||
|
|
||||||
memcpy(dest, baseNode->name, baseLen);
|
memcpy(dest, baseNode->name, baseLen);
|
||||||
@@ -428,13 +429,13 @@ static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
|
|||||||
fileInfo->reptDepth = reptDepth + 1;
|
fileInfo->reptDepth = reptDepth + 1;
|
||||||
fileInfo->iters[0] = 1;
|
fileInfo->iters[0] = 1;
|
||||||
if (reptDepth)
|
if (reptDepth)
|
||||||
// Copy all parent iter counts
|
/* Copy all parent iter counts */
|
||||||
memcpy(&fileInfo->iters[1],
|
memcpy(&fileInfo->iters[1],
|
||||||
((struct FileStackReptNode *)contextStack->fileInfo)->iters,
|
((struct FileStackReptNode *)contextStack->fileInfo)->iters,
|
||||||
reptDepth * sizeof(fileInfo->iters[0]));
|
reptDepth * sizeof(fileInfo->iters[0]));
|
||||||
|
|
||||||
newContext((struct FileStackNode *)fileInfo);
|
newContext((struct FileStackNode *)fileInfo);
|
||||||
// Correct our line number, which currently points to the `ENDR` line
|
/* Correct our line number, which currently points to the `ENDR` line */
|
||||||
contextStack->fileInfo->lineNo = reptLineNo;
|
contextStack->fileInfo->lineNo = reptLineNo;
|
||||||
|
|
||||||
contextStack->lexerState = lexer_OpenFileView("REPT", body, size, reptLineNo);
|
contextStack->lexerState = lexer_OpenFileView("REPT", body, size, reptLineNo);
|
||||||
@@ -492,7 +493,7 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
|||||||
|
|
||||||
void fstk_StopRept(void)
|
void fstk_StopRept(void)
|
||||||
{
|
{
|
||||||
// Prevent more iterations
|
/* Prevent more iterations */
|
||||||
contextStack->nbReptIters = 0;
|
contextStack->nbReptIters = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,7 +510,7 @@ bool fstk_Break(void)
|
|||||||
|
|
||||||
void fstk_NewRecursionDepth(size_t newDepth)
|
void fstk_NewRecursionDepth(size_t newDepth)
|
||||||
{
|
{
|
||||||
if (contextDepth > newDepth)
|
if (contextDepth >= newDepth)
|
||||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||||
maxRecursionDepth = newDepth;
|
maxRecursionDepth = newDepth;
|
||||||
}
|
}
|
||||||
@@ -532,7 +533,7 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
|
|||||||
fatalerror("Failed to allocate memory for main file info: %s\n", strerror(errno));
|
fatalerror("Failed to allocate memory for main file info: %s\n", strerror(errno));
|
||||||
|
|
||||||
context->fileInfo = (struct FileStackNode *)fileInfo;
|
context->fileInfo = (struct FileStackNode *)fileInfo;
|
||||||
// lineNo and reptIter are unused on the top-level context
|
/* lineNo and reptIter are unused on the top-level context */
|
||||||
context->fileInfo->parent = NULL;
|
context->fileInfo->parent = NULL;
|
||||||
context->fileInfo->lineNo = 0; // This still gets written to the object file, so init it
|
context->fileInfo->lineNo = 0; // This still gets written to the object file, so init it
|
||||||
context->fileInfo->referenced = false;
|
context->fileInfo->referenced = false;
|
||||||
@@ -541,17 +542,20 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
|
|||||||
|
|
||||||
context->parent = NULL;
|
context->parent = NULL;
|
||||||
context->lexerState = state;
|
context->lexerState = state;
|
||||||
context->uniqueID = macro_UndefUniqueID();
|
context->uniqueID = 0;
|
||||||
|
macro_SetUniqueID(0);
|
||||||
context->nbReptIters = 0;
|
context->nbReptIters = 0;
|
||||||
context->forValue = 0;
|
context->forValue = 0;
|
||||||
context->forStep = 0;
|
context->forStep = 0;
|
||||||
context->forName = NULL;
|
context->forName = NULL;
|
||||||
|
|
||||||
// Now that it's set up properly, register the context
|
/* Now that it's set up properly, register the context */
|
||||||
contextStack = context;
|
contextStack = context;
|
||||||
|
|
||||||
// Check that max recursion depth won't allow overflowing node `malloc`s
|
/*
|
||||||
// This assumes that the rept node is larger
|
* Check that max recursion depth won't allow overflowing node `malloc`s
|
||||||
|
* This assumes that the rept node is larger
|
||||||
|
*/
|
||||||
#define DEPTH_LIMIT ((SIZE_MAX - sizeof(struct FileStackReptNode)) / sizeof(uint32_t))
|
#define DEPTH_LIMIT ((SIZE_MAX - sizeof(struct FileStackReptNode)) / sizeof(uint32_t))
|
||||||
if (maxDepth > DEPTH_LIMIT) {
|
if (maxDepth > DEPTH_LIMIT) {
|
||||||
error("Recursion depth may not be higher than %zu, defaulting to "
|
error("Recursion depth may not be higher than %zu, defaulting to "
|
||||||
@@ -560,7 +564,7 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
|
|||||||
} else {
|
} else {
|
||||||
maxRecursionDepth = maxDepth;
|
maxRecursionDepth = maxDepth;
|
||||||
}
|
}
|
||||||
// Make sure that the default of 64 is OK, though
|
/* Make sure that the default of 64 is OK, though */
|
||||||
assert(DEPTH_LIMIT >= DEFAULT_MAX_DEPTH);
|
assert(DEPTH_LIMIT >= DEFAULT_MAX_DEPTH);
|
||||||
#undef DEPTH_LIMIT
|
#undef DEPTH_LIMIT
|
||||||
}
|
}
|
||||||
|
|||||||
698
src/asm/lexer.c
698
src/asm/lexer.c
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,5 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@@ -18,11 +12,13 @@
|
|||||||
|
|
||||||
#define MAXMACROARGS 99999
|
#define MAXMACROARGS 99999
|
||||||
|
|
||||||
// Your average macro invocation does not go past the tens, but some go further
|
/*
|
||||||
// This ensures that sane and slightly insane invocations suffer no penalties,
|
* Your average macro invocation does not go past the tens, but some go further
|
||||||
// and the rest is insane and thus will assume responsibility.
|
* This ensures that sane and slightly insane invocations suffer no penalties,
|
||||||
// Additionally, ~300 bytes (on x64) of memory per level of nesting has been
|
* and the rest is insane and thus will assume responsibility.
|
||||||
// deemed reasonable. (Halve that on x86.)
|
* Additionally, ~300 bytes (on x64) of memory per level of nesting has been
|
||||||
|
* deemed reasonable. (Halve that on x86.)
|
||||||
|
*/
|
||||||
#define INITIAL_ARG_SIZE 32
|
#define INITIAL_ARG_SIZE 32
|
||||||
struct MacroArgs {
|
struct MacroArgs {
|
||||||
unsigned int nbArgs;
|
unsigned int nbArgs;
|
||||||
@@ -37,9 +33,11 @@ struct MacroArgs {
|
|||||||
static struct MacroArgs *macroArgs = NULL;
|
static struct MacroArgs *macroArgs = NULL;
|
||||||
static uint32_t uniqueID = 0;
|
static uint32_t uniqueID = 0;
|
||||||
static uint32_t maxUniqueID = 0;
|
static uint32_t maxUniqueID = 0;
|
||||||
// The initialization is somewhat harmful, since it is never used, but it
|
/*
|
||||||
// guarantees the size of the buffer will be correct. I was unable to find a
|
* The initialization is somewhat harmful, since it is never used, but it
|
||||||
// better solution, but if you have one, please feel free!
|
* guarantees the size of the buffer will be correct. I was unable to find a
|
||||||
|
* better solution, but if you have one, please feel free!
|
||||||
|
*/
|
||||||
static char uniqueIDBuf[] = "_u4294967295"; // UINT32_MAX
|
static char uniqueIDBuf[] = "_u4294967295"; // UINT32_MAX
|
||||||
static char *uniqueIDPtr = NULL;
|
static char *uniqueIDPtr = NULL;
|
||||||
|
|
||||||
@@ -70,7 +68,7 @@ void macro_AppendArg(struct MacroArgs **argPtr, char *s)
|
|||||||
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
|
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
|
||||||
if (macArgs->nbArgs >= macArgs->capacity) {
|
if (macArgs->nbArgs >= macArgs->capacity) {
|
||||||
macArgs->capacity *= 2;
|
macArgs->capacity *= 2;
|
||||||
// Check that overflow didn't roll us back
|
/* Check that overflow didn't roll us back */
|
||||||
if (macArgs->capacity <= macArgs->nbArgs)
|
if (macArgs->capacity <= macArgs->nbArgs)
|
||||||
fatalerror("Failed to add new macro argument: capacity overflow\n");
|
fatalerror("Failed to add new macro argument: capacity overflow\n");
|
||||||
macArgs = realloc(macArgs, SIZEOF_ARGS(macArgs->capacity));
|
macArgs = realloc(macArgs, SIZEOF_ARGS(macArgs->capacity));
|
||||||
@@ -114,9 +112,9 @@ char const *macro_GetAllArgs(void)
|
|||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
|
|
||||||
for (uint32_t i = macroArgs->shift; i < macroArgs->nbArgs; i++)
|
for (uint32_t i = macroArgs->shift; i < macroArgs->nbArgs; i++)
|
||||||
len += strlen(macroArgs->args[i]) + 1; // 1 for comma
|
len += strlen(macroArgs->args[i]) + 1; /* 1 for comma */
|
||||||
|
|
||||||
char *str = malloc(len + 1); // 1 for '\0'
|
char *str = malloc(len + 1); /* 1 for '\0' */
|
||||||
char *ptr = str;
|
char *ptr = str;
|
||||||
|
|
||||||
if (!str)
|
if (!str)
|
||||||
@@ -128,9 +126,9 @@ char const *macro_GetAllArgs(void)
|
|||||||
memcpy(ptr, macroArgs->args[i], n);
|
memcpy(ptr, macroArgs->args[i], n);
|
||||||
ptr += n;
|
ptr += n;
|
||||||
|
|
||||||
// Commas go between args and after a last empty arg
|
/* Commas go between args and after a last empty arg */
|
||||||
if (i < macroArgs->nbArgs - 1 || n == 0)
|
if (i < macroArgs->nbArgs - 1 || n == 0)
|
||||||
*ptr++ = ','; // no space after comma
|
*ptr++ = ','; /* no space after comma */
|
||||||
}
|
}
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
|
|
||||||
@@ -144,21 +142,19 @@ uint32_t macro_GetUniqueID(void)
|
|||||||
|
|
||||||
char const *macro_GetUniqueIDStr(void)
|
char const *macro_GetUniqueIDStr(void)
|
||||||
{
|
{
|
||||||
// Generate a new unique ID on the first use of `\@`
|
|
||||||
if (uniqueID == 0)
|
|
||||||
macro_SetUniqueID(++maxUniqueID);
|
|
||||||
|
|
||||||
return uniqueIDPtr;
|
return uniqueIDPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void macro_SetUniqueID(uint32_t id)
|
void macro_SetUniqueID(uint32_t id)
|
||||||
{
|
{
|
||||||
uniqueID = id;
|
uniqueID = id;
|
||||||
if (id == 0 || id == (uint32_t)-1) {
|
if (id == 0) {
|
||||||
uniqueIDPtr = NULL;
|
uniqueIDPtr = NULL;
|
||||||
} else {
|
} else {
|
||||||
// The buffer is guaranteed to be the correct size
|
if (uniqueID > maxUniqueID)
|
||||||
// This is a valid label fragment, but not a valid numeric
|
maxUniqueID = uniqueID;
|
||||||
|
/* The buffer is guaranteed to be the correct size */
|
||||||
|
/* This is a valid label fragment, but not a valid numeric */
|
||||||
sprintf(uniqueIDBuf, "_u%" PRIu32, id);
|
sprintf(uniqueIDBuf, "_u%" PRIu32, id);
|
||||||
uniqueIDPtr = uniqueIDBuf;
|
uniqueIDPtr = uniqueIDBuf;
|
||||||
}
|
}
|
||||||
@@ -166,16 +162,8 @@ void macro_SetUniqueID(uint32_t id)
|
|||||||
|
|
||||||
uint32_t macro_UseNewUniqueID(void)
|
uint32_t macro_UseNewUniqueID(void)
|
||||||
{
|
{
|
||||||
// A new ID will be generated on the first use of `\@`
|
macro_SetUniqueID(++maxUniqueID);
|
||||||
macro_SetUniqueID(0);
|
return maxUniqueID;
|
||||||
return uniqueID;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t macro_UndefUniqueID(void)
|
|
||||||
{
|
|
||||||
// No ID will be generated; use of `\@` is an error
|
|
||||||
macro_SetUniqueID((uint32_t)-1);
|
|
||||||
return uniqueID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void macro_ShiftCurrentArgs(int32_t count)
|
void macro_ShiftCurrentArgs(int32_t count)
|
||||||
|
|||||||
113
src/asm/main.c
113
src/asm/main.c
@@ -19,7 +19,6 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "asm/charmap.h"
|
#include "asm/charmap.h"
|
||||||
#include "asm/fixpoint.h"
|
|
||||||
#include "asm/format.h"
|
#include "asm/format.h"
|
||||||
#include "asm/fstack.h"
|
#include "asm/fstack.h"
|
||||||
#include "asm/lexer.h"
|
#include "asm/lexer.h"
|
||||||
@@ -40,8 +39,8 @@
|
|||||||
#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 // __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
|
#endif /* __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) */
|
||||||
#endif // __clang__
|
#endif /* __clang__ */
|
||||||
|
|
||||||
#ifdef __SANITIZE_ADDRESS__
|
#ifdef __SANITIZE_ADDRESS__
|
||||||
// There are known, non-trivial to fix leaks. We would still like to have `make develop'
|
// There are known, non-trivial to fix leaks. We would still like to have `make develop'
|
||||||
@@ -60,13 +59,11 @@ bool generatePhonyDeps;
|
|||||||
char *targetFileName;
|
char *targetFileName;
|
||||||
|
|
||||||
bool haltnop;
|
bool haltnop;
|
||||||
bool warnOnHaltNop;
|
|
||||||
bool optimizeLoads;
|
bool optimizeLoads;
|
||||||
bool warnOnLdOpt;
|
|
||||||
bool verbose;
|
bool verbose;
|
||||||
bool warnings; // True to enable warnings, false to disable them.
|
bool warnings; /* True to enable warnings, false to disable them. */
|
||||||
|
|
||||||
// Escapes Make-special chars from a string
|
/* Escapes Make-special chars from a string */
|
||||||
static char *make_escape(char const *str)
|
static char *make_escape(char const *str)
|
||||||
{
|
{
|
||||||
char * const escaped_str = malloc(strlen(str) * 2 + 1);
|
char * const escaped_str = malloc(strlen(str) * 2 + 1);
|
||||||
@@ -76,7 +73,7 @@ static char *make_escape(char const *str)
|
|||||||
err("%s: Failed to allocate memory", __func__);
|
err("%s: Failed to allocate memory", __func__);
|
||||||
|
|
||||||
while (*str) {
|
while (*str) {
|
||||||
// All dollars needs to be doubled
|
/* All dollars needs to be doubled */
|
||||||
if (*str == '$')
|
if (*str == '$')
|
||||||
*dest++ = '$';
|
*dest++ = '$';
|
||||||
*dest++ = *str++;
|
*dest++ = *str++;
|
||||||
@@ -86,30 +83,30 @@ static char *make_escape(char const *str)
|
|||||||
return escaped_str;
|
return escaped_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short options
|
/* Short options */
|
||||||
static const char *optstring = "b:D:Eg:Hhi:LlM:o:p:Q:r:VvW:w";
|
static const char *optstring = "b:D:Eg:hi:LM:o:p:r:VvW:w";
|
||||||
|
|
||||||
// Variables for the long-only options
|
/* Variables for the long-only options */
|
||||||
static int depType; // Variants of `-M`
|
static int depType; /* Variants of `-M` */
|
||||||
|
|
||||||
// Equivalent long options
|
/*
|
||||||
// Please keep in the same order as short opts
|
* Equivalent long options
|
||||||
//
|
* Please keep in the same order as short opts
|
||||||
// Also, make sure long opts don't create ambiguity:
|
*
|
||||||
// A long opt's name should start with the same letter as its short opt,
|
* Also, make sure long opts don't create ambiguity:
|
||||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
* A long opt's name should start with the same letter as its short opt,
|
||||||
// This is because long opt matching, even to a single char, is prioritized
|
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||||
// over short opt matching
|
* This is because long opt matching, even to a single char, is prioritized
|
||||||
|
* over short opt matching
|
||||||
|
*/
|
||||||
static struct option const longopts[] = {
|
static struct option const longopts[] = {
|
||||||
{ "binary-digits", required_argument, NULL, 'b' },
|
{ "binary-digits", required_argument, NULL, 'b' },
|
||||||
{ "define", required_argument, NULL, 'D' },
|
{ "define", required_argument, NULL, 'D' },
|
||||||
{ "export-all", no_argument, NULL, 'E' },
|
{ "export-all", no_argument, NULL, 'E' },
|
||||||
{ "gfx-chars", required_argument, NULL, 'g' },
|
{ "gfx-chars", required_argument, NULL, 'g' },
|
||||||
{ "nop-after-halt", no_argument, NULL, 'H' },
|
|
||||||
{ "halt-without-nop", no_argument, NULL, 'h' },
|
{ "halt-without-nop", no_argument, NULL, 'h' },
|
||||||
{ "include", required_argument, NULL, 'i' },
|
{ "include", required_argument, NULL, 'i' },
|
||||||
{ "preserve-ld", no_argument, NULL, 'L' },
|
{ "preserve-ld", no_argument, NULL, 'L' },
|
||||||
{ "auto-ldh", no_argument, NULL, 'l' },
|
|
||||||
{ "dependfile", required_argument, NULL, 'M' },
|
{ "dependfile", required_argument, NULL, 'M' },
|
||||||
{ "MG", no_argument, &depType, 'G' },
|
{ "MG", no_argument, &depType, 'G' },
|
||||||
{ "MP", no_argument, &depType, 'P' },
|
{ "MP", no_argument, &depType, 'P' },
|
||||||
@@ -117,7 +114,6 @@ static struct option const longopts[] = {
|
|||||||
{ "MQ", required_argument, &depType, 'Q' },
|
{ "MQ", required_argument, &depType, 'Q' },
|
||||||
{ "output", required_argument, NULL, 'o' },
|
{ "output", required_argument, NULL, 'o' },
|
||||||
{ "pad-value", required_argument, NULL, 'p' },
|
{ "pad-value", required_argument, NULL, 'p' },
|
||||||
{ "q-precision", required_argument, NULL, 'Q' },
|
|
||||||
{ "recursion-depth", required_argument, NULL, 'r' },
|
{ "recursion-depth", required_argument, NULL, 'r' },
|
||||||
{ "version", no_argument, NULL, 'V' },
|
{ "version", no_argument, NULL, 'V' },
|
||||||
{ "verbose", no_argument, NULL, 'v' },
|
{ "verbose", no_argument, NULL, 'v' },
|
||||||
@@ -128,10 +124,9 @@ static struct option const longopts[] = {
|
|||||||
static void print_usage(void)
|
static void print_usage(void)
|
||||||
{
|
{
|
||||||
fputs(
|
fputs(
|
||||||
"Usage: rgbasm [-EHhLlVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n"
|
"Usage: rgbasm [-EhLVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n"
|
||||||
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||||
" [-o out_file] [-p pad_value] [-Q precision] [-r depth]\n"
|
" [-o out_file] [-p pad_value] [-r depth] [-W warning] <file>\n"
|
||||||
" [-W warning] <file>\n"
|
|
||||||
"Useful options:\n"
|
"Useful options:\n"
|
||||||
" -E, --export-all export all labels\n"
|
" -E, --export-all export all labels\n"
|
||||||
" -M, --dependfile <path> set the output dependency file\n"
|
" -M, --dependfile <path> set the output dependency file\n"
|
||||||
@@ -147,14 +142,19 @@ static void print_usage(void)
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
#if YYDEBUG
|
||||||
|
yydebug = 1;
|
||||||
|
#endif
|
||||||
int ch;
|
int ch;
|
||||||
char *ep;
|
char *ep;
|
||||||
|
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH");
|
char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH");
|
||||||
|
|
||||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
/*
|
||||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
* Support SOURCE_DATE_EPOCH for reproducible builds
|
||||||
|
* https://reproducible-builds.org/docs/source-date-epoch/
|
||||||
|
*/
|
||||||
if (sourceDateEpoch)
|
if (sourceDateEpoch)
|
||||||
now = (time_t)strtoul(sourceDateEpoch, NULL, 0);
|
now = (time_t)strtoul(sourceDateEpoch, NULL, 0);
|
||||||
|
|
||||||
@@ -173,15 +173,12 @@ int main(int argc, char *argv[])
|
|||||||
opt_B("01");
|
opt_B("01");
|
||||||
opt_G("0123");
|
opt_G("0123");
|
||||||
opt_P(0);
|
opt_P(0);
|
||||||
opt_Q(16);
|
|
||||||
haltnop = true;
|
|
||||||
warnOnHaltNop = true;
|
|
||||||
optimizeLoads = true;
|
optimizeLoads = true;
|
||||||
warnOnLdOpt = true;
|
haltnop = true;
|
||||||
verbose = false;
|
verbose = false;
|
||||||
warnings = true;
|
warnings = true;
|
||||||
sym_SetExportAll(false);
|
sym_SetExportAll(false);
|
||||||
uint32_t maxDepth = DEFAULT_MAX_DEPTH;
|
uint32_t maxDepth = 64;
|
||||||
size_t targetFileNameLen = 0;
|
size_t targetFileNameLen = 0;
|
||||||
|
|
||||||
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
|
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
|
||||||
@@ -215,14 +212,7 @@ int main(int argc, char *argv[])
|
|||||||
errx("Must specify exactly 4 characters for option 'g'");
|
errx("Must specify exactly 4 characters for option 'g'");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'H':
|
|
||||||
if (!haltnop)
|
|
||||||
errx("`-H` and `-h` don't make sense together");
|
|
||||||
warnOnHaltNop = false;
|
|
||||||
break;
|
|
||||||
case 'h':
|
case 'h':
|
||||||
if (!warnOnHaltNop)
|
|
||||||
errx("`-H` and `-h` don't make sense together");
|
|
||||||
haltnop = false;
|
haltnop = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -231,15 +221,8 @@ int main(int argc, char *argv[])
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'L':
|
case 'L':
|
||||||
if (!warnOnLdOpt)
|
|
||||||
errx("`-L` and `-l` don't make sense together");
|
|
||||||
optimizeLoads = false;
|
optimizeLoads = false;
|
||||||
break;
|
break;
|
||||||
case 'l':
|
|
||||||
if (!optimizeLoads)
|
|
||||||
errx("`-L` and `-l` don't make sense together");
|
|
||||||
warnOnLdOpt = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
if (!strcmp("-", musl_optarg))
|
if (!strcmp("-", musl_optarg))
|
||||||
@@ -254,34 +237,17 @@ int main(int argc, char *argv[])
|
|||||||
out_SetFileName(musl_optarg);
|
out_SetFileName(musl_optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
unsigned long padByte;
|
unsigned long fill;
|
||||||
case 'p':
|
case 'p':
|
||||||
padByte = strtoul(musl_optarg, &ep, 0);
|
fill = strtoul(musl_optarg, &ep, 0);
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *ep != '\0')
|
if (musl_optarg[0] == '\0' || *ep != '\0')
|
||||||
errx("Invalid argument for option 'p'");
|
errx("Invalid argument for option 'p'");
|
||||||
|
|
||||||
if (padByte > 0xFF)
|
if (fill > 0xFF)
|
||||||
errx("Argument for option 'p' must be between 0 and 0xFF");
|
errx("Argument for option 'p' must be between 0 and 0xFF");
|
||||||
|
|
||||||
opt_P(padByte);
|
opt_P(fill);
|
||||||
break;
|
|
||||||
|
|
||||||
unsigned long precision;
|
|
||||||
const char *precisionArg;
|
|
||||||
case 'Q':
|
|
||||||
precisionArg = musl_optarg;
|
|
||||||
if (precisionArg[0] == '.')
|
|
||||||
precisionArg++;
|
|
||||||
precision = strtoul(precisionArg, &ep, 0);
|
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *ep != '\0')
|
|
||||||
errx("Invalid argument for option 'Q'");
|
|
||||||
|
|
||||||
if (precision < 1 || precision > 31)
|
|
||||||
errx("Argument for option 'Q' must be between 1 and 31");
|
|
||||||
|
|
||||||
opt_Q(precision);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'r':
|
case 'r':
|
||||||
@@ -294,7 +260,6 @@ int main(int argc, char *argv[])
|
|||||||
case 'V':
|
case 'V':
|
||||||
printf("rgbasm %s\n", get_package_version_string());
|
printf("rgbasm %s\n", get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
verbose = true;
|
verbose = true;
|
||||||
break;
|
break;
|
||||||
@@ -307,7 +272,7 @@ int main(int argc, char *argv[])
|
|||||||
warnings = false;
|
warnings = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Long-only options
|
/* Long-only options */
|
||||||
case 0:
|
case 0:
|
||||||
switch (depType) {
|
switch (depType) {
|
||||||
case 'G':
|
case 'G':
|
||||||
@@ -339,10 +304,10 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Unrecognized options
|
/* Unrecognized options */
|
||||||
default:
|
default:
|
||||||
print_usage();
|
print_usage();
|
||||||
// NOTREACHED
|
/* NOTREACHED */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +338,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
charmap_New("main", NULL);
|
charmap_New("main", NULL);
|
||||||
|
|
||||||
// Init lexer and file stack, providing file info
|
// Init lexer and file stack, prodiving file info
|
||||||
lexer_Init();
|
lexer_Init();
|
||||||
fstk_Init(mainFileName, maxDepth);
|
fstk_Init(mainFileName, maxDepth);
|
||||||
|
|
||||||
@@ -394,7 +359,7 @@ int main(int argc, char *argv[])
|
|||||||
if (failedOnMissingInclude)
|
if (failedOnMissingInclude)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// If no path specified, don't write file
|
/* If no path specified, don't write file */
|
||||||
if (objectName != NULL)
|
if (objectName != NULL)
|
||||||
out_WriteObject();
|
out_WriteObject();
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@@ -14,7 +6,6 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "asm/fixpoint.h"
|
|
||||||
#include "asm/fstack.h"
|
#include "asm/fstack.h"
|
||||||
#include "asm/lexer.h"
|
#include "asm/lexer.h"
|
||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
@@ -24,12 +15,9 @@
|
|||||||
struct OptStackEntry {
|
struct OptStackEntry {
|
||||||
char binary[2];
|
char binary[2];
|
||||||
char gbgfx[4];
|
char gbgfx[4];
|
||||||
uint8_t fixPrecision;
|
int32_t fillByte;
|
||||||
uint8_t fillByte;
|
|
||||||
bool haltnop;
|
bool haltnop;
|
||||||
bool warnOnHaltNop;
|
|
||||||
bool optimizeLoads;
|
bool optimizeLoads;
|
||||||
bool warnOnLdOpt;
|
|
||||||
bool warningsAreErrors;
|
bool warningsAreErrors;
|
||||||
size_t maxRecursionDepth;
|
size_t maxRecursionDepth;
|
||||||
// Don't be confused: we use the size of the **global variable** `warningStates`!
|
// Don't be confused: we use the size of the **global variable** `warningStates`!
|
||||||
@@ -49,14 +37,9 @@ void opt_G(char const chars[4])
|
|||||||
lexer_SetGfxDigits(chars);
|
lexer_SetGfxDigits(chars);
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_P(uint8_t padByte)
|
void opt_P(uint8_t fill)
|
||||||
{
|
{
|
||||||
fillByte = padByte;
|
fillByte = fill;
|
||||||
}
|
|
||||||
|
|
||||||
void opt_Q(uint8_t precision)
|
|
||||||
{
|
|
||||||
fixPrecision = precision;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_R(size_t newDepth)
|
void opt_R(size_t newDepth)
|
||||||
@@ -65,11 +48,6 @@ void opt_R(size_t newDepth)
|
|||||||
lexer_CheckRecursionDepth();
|
lexer_CheckRecursionDepth();
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_H(bool warn)
|
|
||||||
{
|
|
||||||
warnOnHaltNop = warn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void opt_h(bool halt)
|
void opt_h(bool halt)
|
||||||
{
|
{
|
||||||
haltnop = halt;
|
haltnop = halt;
|
||||||
@@ -80,11 +58,6 @@ void opt_L(bool optimize)
|
|||||||
optimizeLoads = optimize;
|
optimizeLoads = optimize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_l(bool warn)
|
|
||||||
{
|
|
||||||
warnOnLdOpt = warn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void opt_W(char *flag)
|
void opt_W(char *flag)
|
||||||
{
|
{
|
||||||
processWarningFlag(flag);
|
processWarningFlag(flag);
|
||||||
@@ -110,41 +83,18 @@ void opt_Parse(char *s)
|
|||||||
case 'p':
|
case 'p':
|
||||||
if (strlen(&s[1]) <= 2) {
|
if (strlen(&s[1]) <= 2) {
|
||||||
int result;
|
int result;
|
||||||
unsigned int padByte;
|
unsigned int fillchar;
|
||||||
|
|
||||||
result = sscanf(&s[1], "%x", &padByte);
|
result = sscanf(&s[1], "%x", &fillchar);
|
||||||
if (result != 1)
|
if (result != EOF && result != 1)
|
||||||
error("Invalid argument for option 'p'\n");
|
error("Invalid argument for option 'p'\n");
|
||||||
else if (padByte > 0xFF)
|
|
||||||
error("Argument for option 'p' must be between 0 and 0xFF\n");
|
|
||||||
else
|
else
|
||||||
opt_P(padByte);
|
opt_P(fillchar);
|
||||||
} else {
|
} else {
|
||||||
error("Invalid argument for option 'p'\n");
|
error("Invalid argument for option 'p'\n");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
const char *precisionArg;
|
|
||||||
case 'Q':
|
|
||||||
precisionArg = &s[1];
|
|
||||||
if (precisionArg[0] == '.')
|
|
||||||
precisionArg++;
|
|
||||||
if (strlen(precisionArg) <= 2) {
|
|
||||||
int result;
|
|
||||||
unsigned int precision;
|
|
||||||
|
|
||||||
result = sscanf(precisionArg, "%u", &precision);
|
|
||||||
if (result != 1)
|
|
||||||
error("Invalid argument for option 'Q'\n");
|
|
||||||
else if (precision < 1 || precision > 31)
|
|
||||||
error("Argument for option 'Q' must be between 1 and 31\n");
|
|
||||||
else
|
|
||||||
opt_Q(precision);
|
|
||||||
} else {
|
|
||||||
error("Invalid argument for option 'Q'\n");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'r': {
|
case 'r': {
|
||||||
++s; // Skip 'r'
|
++s; // Skip 'r'
|
||||||
while (isblank(*s))
|
while (isblank(*s))
|
||||||
@@ -168,13 +118,6 @@ void opt_Parse(char *s)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'H':
|
|
||||||
if (s[1] == '\0')
|
|
||||||
opt_H(false);
|
|
||||||
else
|
|
||||||
error("Option 'H' does not take an argument\n");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'h':
|
case 'h':
|
||||||
if (s[1] == '\0')
|
if (s[1] == '\0')
|
||||||
opt_h(false);
|
opt_h(false);
|
||||||
@@ -189,13 +132,6 @@ void opt_Parse(char *s)
|
|||||||
error("Option 'L' does not take an argument\n");
|
error("Option 'L' does not take an argument\n");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'l':
|
|
||||||
if (s[1] == '\0')
|
|
||||||
opt_l(false);
|
|
||||||
else
|
|
||||||
error("Option 'l' does not take an argument\n");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
if (strlen(&s[1]) > 0)
|
if (strlen(&s[1]) > 0)
|
||||||
opt_W(&s[1]);
|
opt_W(&s[1]);
|
||||||
@@ -205,13 +141,6 @@ void opt_Parse(char *s)
|
|||||||
|
|
||||||
case '!': // negates flag options that do not take an argument
|
case '!': // negates flag options that do not take an argument
|
||||||
switch (s[1]) {
|
switch (s[1]) {
|
||||||
case 'H':
|
|
||||||
if (s[2] == '\0')
|
|
||||||
opt_H(true);
|
|
||||||
else
|
|
||||||
error("Option '!H' does not take an argument\n");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'h':
|
case 'h':
|
||||||
if (s[2] == '\0')
|
if (s[2] == '\0')
|
||||||
opt_h(true);
|
opt_h(true);
|
||||||
@@ -226,13 +155,6 @@ void opt_Parse(char *s)
|
|||||||
error("Option '!L' does not take an argument\n");
|
error("Option '!L' does not take an argument\n");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'l':
|
|
||||||
if (s[2] == '\0')
|
|
||||||
opt_l(true);
|
|
||||||
else
|
|
||||||
error("Option '!l' does not take an argument\n");
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
error("Unknown option '!%c'\n", s[1]);
|
error("Unknown option '!%c'\n", s[1]);
|
||||||
break;
|
break;
|
||||||
@@ -261,15 +183,11 @@ void opt_Push(void)
|
|||||||
entry->gbgfx[2] = gfxDigits[2];
|
entry->gbgfx[2] = gfxDigits[2];
|
||||||
entry->gbgfx[3] = gfxDigits[3];
|
entry->gbgfx[3] = gfxDigits[3];
|
||||||
|
|
||||||
entry->fixPrecision = fixPrecision; // Pulled from fixpoint.h
|
|
||||||
|
|
||||||
entry->fillByte = fillByte; // Pulled from section.h
|
entry->fillByte = fillByte; // Pulled from section.h
|
||||||
|
|
||||||
entry->haltnop = haltnop; // Pulled from main.h
|
entry->haltnop = haltnop; // Pulled from main.h
|
||||||
entry->warnOnHaltNop = warnOnHaltNop;
|
|
||||||
|
|
||||||
entry->optimizeLoads = optimizeLoads; // Pulled from main.h
|
entry->optimizeLoads = optimizeLoads; // Pulled from main.h
|
||||||
entry->warnOnLdOpt = warnOnLdOpt;
|
|
||||||
|
|
||||||
// Both of these pulled from warning.h
|
// Both of these pulled from warning.h
|
||||||
entry->warningsAreErrors = warningsAreErrors;
|
entry->warningsAreErrors = warningsAreErrors;
|
||||||
@@ -291,11 +209,8 @@ void opt_Pop(void)
|
|||||||
opt_B(entry->binary);
|
opt_B(entry->binary);
|
||||||
opt_G(entry->gbgfx);
|
opt_G(entry->gbgfx);
|
||||||
opt_P(entry->fillByte);
|
opt_P(entry->fillByte);
|
||||||
opt_Q(entry->fixPrecision);
|
|
||||||
opt_H(entry->warnOnHaltNop);
|
|
||||||
opt_h(entry->haltnop);
|
opt_h(entry->haltnop);
|
||||||
opt_L(entry->optimizeLoads);
|
opt_L(entry->optimizeLoads);
|
||||||
opt_l(entry->warnOnLdOpt);
|
|
||||||
|
|
||||||
// opt_W does not apply a whole warning state; it processes one flag string
|
// opt_W does not apply a whole warning state; it processes one flag string
|
||||||
warningsAreErrors = entry->warningsAreErrors;
|
warningsAreErrors = entry->warningsAreErrors;
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Outputs an objectfile
|
/*
|
||||||
|
* Outputs an objectfile
|
||||||
|
*/
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -52,16 +54,18 @@ char *objectName;
|
|||||||
|
|
||||||
struct Section *sectionList;
|
struct Section *sectionList;
|
||||||
|
|
||||||
// Linked list of symbols to put in the object file
|
/* Linked list of symbols to put in the object file */
|
||||||
static struct Symbol *objectSymbols = NULL;
|
static struct Symbol *objectSymbols = NULL;
|
||||||
static struct Symbol **objectSymbolsTail = &objectSymbols;
|
static struct Symbol **objectSymbolsTail = &objectSymbols;
|
||||||
static uint32_t nbSymbols = 0; // Length of the above list
|
static uint32_t nbSymbols = 0; /* Length of the above list */
|
||||||
|
|
||||||
static struct Assertion *assertions = NULL;
|
static struct Assertion *assertions = NULL;
|
||||||
|
|
||||||
static struct FileStackNode *fileStackNodes = NULL;
|
static struct FileStackNode *fileStackNodes = NULL;
|
||||||
|
|
||||||
// Count the number of sections used in this object
|
/*
|
||||||
|
* Count the number of sections used in this object
|
||||||
|
*/
|
||||||
static uint32_t countSections(void)
|
static uint32_t countSections(void)
|
||||||
{
|
{
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
@@ -72,7 +76,9 @@ static uint32_t countSections(void)
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count the number of patches used in this object
|
/*
|
||||||
|
* Count the number of patches used in this object
|
||||||
|
*/
|
||||||
static uint32_t countPatches(struct Section const *sect)
|
static uint32_t countPatches(struct Section const *sect)
|
||||||
{
|
{
|
||||||
uint32_t r = 0;
|
uint32_t r = 0;
|
||||||
@@ -84,7 +90,9 @@ static uint32_t countPatches(struct Section const *sect)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count the number of assertions used in this object
|
/**
|
||||||
|
* Count the number of assertions used in this object
|
||||||
|
*/
|
||||||
static uint32_t countAsserts(void)
|
static uint32_t countAsserts(void)
|
||||||
{
|
{
|
||||||
struct Assertion *assert = assertions;
|
struct Assertion *assert = assertions;
|
||||||
@@ -97,7 +105,9 @@ static uint32_t countAsserts(void)
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write a long to a file (little-endian)
|
/*
|
||||||
|
* Write a long to a file (little-endian)
|
||||||
|
*/
|
||||||
static void putlong(uint32_t i, FILE *f)
|
static void putlong(uint32_t i, FILE *f)
|
||||||
{
|
{
|
||||||
putc(i, f);
|
putc(i, f);
|
||||||
@@ -106,7 +116,9 @@ static void putlong(uint32_t i, FILE *f)
|
|||||||
putc(i >> 24, f);
|
putc(i >> 24, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write a NULL-terminated string to a file
|
/*
|
||||||
|
* Write a NULL-terminated string to a file
|
||||||
|
*/
|
||||||
static void putstring(char const *s, FILE *f)
|
static void putstring(char const *s, FILE *f)
|
||||||
{
|
{
|
||||||
while (*s)
|
while (*s)
|
||||||
@@ -121,7 +133,7 @@ static uint32_t getNbFileStackNodes(void)
|
|||||||
|
|
||||||
void out_RegisterNode(struct FileStackNode *node)
|
void out_RegisterNode(struct FileStackNode *node)
|
||||||
{
|
{
|
||||||
// If node is not already registered, register it (and parents), and give it a unique ID
|
/* If node is not already registered, register it (and parents), and give it a unique ID */
|
||||||
while (node->ID == (uint32_t)-1) {
|
while (node->ID == (uint32_t)-1) {
|
||||||
node->ID = getNbFileStackNodes();
|
node->ID = getNbFileStackNodes();
|
||||||
if (node->ID == (uint32_t)-1)
|
if (node->ID == (uint32_t)-1)
|
||||||
@@ -129,7 +141,7 @@ void out_RegisterNode(struct FileStackNode *node)
|
|||||||
node->next = fileStackNodes;
|
node->next = fileStackNodes;
|
||||||
fileStackNodes = node;
|
fileStackNodes = node;
|
||||||
|
|
||||||
// Also register the node's parents
|
/* Also register the node's parents */
|
||||||
node = node->parent;
|
node = node->parent;
|
||||||
if (!node)
|
if (!node)
|
||||||
break;
|
break;
|
||||||
@@ -144,21 +156,25 @@ This is code intended to replace a node, which is pretty useless until ref count
|
|||||||
|
|
||||||
struct FileStackNode **ptr = &fileStackNodes;
|
struct FileStackNode **ptr = &fileStackNodes;
|
||||||
|
|
||||||
// The linked list is supposed to have decrementing IDs, so iterate with less memory reads,
|
/*
|
||||||
// to hopefully hit the cache less. A debug check is added after, in case a change is made
|
* The linked list is supposed to have decrementing IDs, so iterate with less memory reads,
|
||||||
// that breaks this assumption.
|
* to hopefully hit the cache less. A debug check is added after, in case a change is made
|
||||||
|
* that breaks this assumption.
|
||||||
|
*/
|
||||||
for (uint32_t i = fileStackNodes->ID; i != node->ID; i--)
|
for (uint32_t i = fileStackNodes->ID; i != node->ID; i--)
|
||||||
ptr = &(*ptr)->next;
|
ptr = &(*ptr)->next;
|
||||||
assert((*ptr)->ID == node->ID);
|
assert((*ptr)->ID == node->ID);
|
||||||
|
|
||||||
node->next = (*ptr)->next;
|
node->next = (*ptr)->next;
|
||||||
assert(!node->next || node->next->ID == node->ID - 1); // Catch inconsistencies early
|
assert(!node->next || node->next->ID == node->ID - 1); /* Catch inconsistencies early */
|
||||||
// TODO: unreference the node
|
/* TODO: unreference the node */
|
||||||
*ptr = node;
|
*ptr = node;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a section's ID
|
/*
|
||||||
|
* Return a section's ID
|
||||||
|
*/
|
||||||
static uint32_t getsectid(struct Section const *sect)
|
static uint32_t getsectid(struct Section const *sect)
|
||||||
{
|
{
|
||||||
struct Section const *sec = sectionList;
|
struct Section const *sec = sectionList;
|
||||||
@@ -179,7 +195,9 @@ static uint32_t getSectIDIfAny(struct Section const *sect)
|
|||||||
return sect ? getsectid(sect) : (uint32_t)-1;
|
return sect ? getsectid(sect) : (uint32_t)-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write a patch to a file
|
/*
|
||||||
|
* Write a patch to a file
|
||||||
|
*/
|
||||||
static void writepatch(struct Patch const *patch, FILE *f)
|
static void writepatch(struct Patch const *patch, FILE *f)
|
||||||
{
|
{
|
||||||
assert(patch->src->ID != (uint32_t)-1);
|
assert(patch->src->ID != (uint32_t)-1);
|
||||||
@@ -193,7 +211,9 @@ static void writepatch(struct Patch const *patch, FILE *f)
|
|||||||
fwrite(patch->rpn, 1, patch->rpnSize, f);
|
fwrite(patch->rpn, 1, patch->rpnSize, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write a section to a file
|
/*
|
||||||
|
* Write a section to a file
|
||||||
|
*/
|
||||||
static void writesection(struct Section const *sect, FILE *f)
|
static void writesection(struct Section const *sect, FILE *f)
|
||||||
{
|
{
|
||||||
putstring(sect->name, f);
|
putstring(sect->name, f);
|
||||||
@@ -235,7 +255,9 @@ static void freesection(struct Section const *sect)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write a symbol to a file
|
/*
|
||||||
|
* Write a symbol to a file
|
||||||
|
*/
|
||||||
static void writesymbol(struct Symbol const *sym, FILE *f)
|
static void writesymbol(struct Symbol const *sym, FILE *f)
|
||||||
{
|
{
|
||||||
putstring(sym->name, f);
|
putstring(sym->name, f);
|
||||||
@@ -263,8 +285,10 @@ static void registerSymbol(struct Symbol *sym)
|
|||||||
sym->ID = nbSymbols++;
|
sym->ID = nbSymbols++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a symbol's ID within the object file
|
/*
|
||||||
// If the symbol does not have one, one is assigned by registering the symbol
|
* Returns a symbol's ID within the object file
|
||||||
|
* If the symbol does not have one, one is assigned by registering the symbol
|
||||||
|
*/
|
||||||
static uint32_t getSymbolID(struct Symbol *sym)
|
static uint32_t getSymbolID(struct Symbol *sym)
|
||||||
{
|
{
|
||||||
if (sym->ID == (uint32_t)-1 && !sym_IsPC(sym))
|
if (sym->ID == (uint32_t)-1 && !sym_IsPC(sym))
|
||||||
@@ -368,8 +392,10 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate a new patch structure and link it into the list
|
/*
|
||||||
// WARNING: all patches are assumed to eventually be written, so the file stack node is registered
|
* Allocate a new patch structure and link it into the list
|
||||||
|
* WARNING: all patches are assumed to eventually be written, so the file stack node is registered
|
||||||
|
*/
|
||||||
static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
|
static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
|
||||||
{
|
{
|
||||||
struct Patch *patch = malloc(sizeof(struct Patch));
|
struct Patch *patch = malloc(sizeof(struct Patch));
|
||||||
@@ -391,10 +417,10 @@ static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, ui
|
|||||||
patch->pcSection = sect_GetSymbolSection();
|
patch->pcSection = sect_GetSymbolSection();
|
||||||
patch->pcOffset = sect_GetSymbolOffset();
|
patch->pcOffset = sect_GetSymbolOffset();
|
||||||
|
|
||||||
// If the rpnSize's value is known, output a constant RPN rpnSize directly
|
/* If the rpnSize's value is known, output a constant RPN rpnSize directly */
|
||||||
if (expr->isKnown) {
|
if (expr->isKnown) {
|
||||||
patch->rpnSize = rpnSize;
|
patch->rpnSize = rpnSize;
|
||||||
// Make sure to update `rpnSize` above if modifying this!
|
/* Make sure to update `rpnSize` above if modifying this! */
|
||||||
patch->rpn[0] = RPN_CONST;
|
patch->rpn[0] = RPN_CONST;
|
||||||
patch->rpn[1] = (uint32_t)(expr->val) & 0xFF;
|
patch->rpn[1] = (uint32_t)(expr->val) & 0xFF;
|
||||||
patch->rpn[2] = (uint32_t)(expr->val) >> 8;
|
patch->rpn[2] = (uint32_t)(expr->val) >> 8;
|
||||||
@@ -409,7 +435,9 @@ static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, ui
|
|||||||
return patch;
|
return patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new patch (includes the rpn expr)
|
/*
|
||||||
|
* Create a new patch (includes the rpn expr)
|
||||||
|
*/
|
||||||
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift)
|
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
struct Patch *patch = allocpatch(type, expr, ofs);
|
struct Patch *patch = allocpatch(type, expr, ofs);
|
||||||
@@ -423,7 +451,9 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs,
|
|||||||
currentSection->patches = patch;
|
currentSection->patches = patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates an assert that will be written to the object file
|
/**
|
||||||
|
* Creates an assert that will be written to the object file
|
||||||
|
*/
|
||||||
bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
|
bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
|
||||||
char const *message, uint32_t ofs)
|
char const *message, uint32_t ofs)
|
||||||
{
|
{
|
||||||
@@ -469,7 +499,7 @@ static void writeFileStackNode(struct FileStackNode const *node, FILE *f)
|
|||||||
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
|
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
|
||||||
|
|
||||||
putlong(reptNode->reptDepth, f);
|
putlong(reptNode->reptDepth, f);
|
||||||
// Iters are stored by decreasing depth, so reverse the order for output
|
/* Iters are stored by decreasing depth, so reverse the order for output */
|
||||||
for (uint32_t i = reptNode->reptDepth; i--; )
|
for (uint32_t i = reptNode->reptDepth; i--; )
|
||||||
putlong(reptNode->iters[i], f);
|
putlong(reptNode->iters[i], f);
|
||||||
}
|
}
|
||||||
@@ -485,7 +515,9 @@ static void registerUnregisteredSymbol(struct Symbol *symbol, void *arg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write an objectfile
|
/*
|
||||||
|
* Write an objectfile
|
||||||
|
*/
|
||||||
void out_WriteObject(void)
|
void out_WriteObject(void)
|
||||||
{
|
{
|
||||||
FILE *f;
|
FILE *f;
|
||||||
@@ -497,10 +529,10 @@ void out_WriteObject(void)
|
|||||||
if (!f)
|
if (!f)
|
||||||
err("Couldn't write file '%s'", objectName);
|
err("Couldn't write file '%s'", objectName);
|
||||||
|
|
||||||
// Also write symbols that weren't written above
|
/* Also write symbols that weren't written above */
|
||||||
sym_ForEach(registerUnregisteredSymbol, NULL);
|
sym_ForEach(registerUnregisteredSymbol, NULL);
|
||||||
|
|
||||||
fprintf(f, RGBDS_OBJECT_VERSION_STRING);
|
fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER);
|
||||||
putlong(RGBDS_OBJECT_REV, f);
|
putlong(RGBDS_OBJECT_REV, f);
|
||||||
|
|
||||||
putlong(nbSymbols, f);
|
putlong(nbSymbols, f);
|
||||||
@@ -537,7 +569,9 @@ void out_WriteObject(void)
|
|||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the objectfilename
|
/*
|
||||||
|
* Set the objectfilename
|
||||||
|
*/
|
||||||
void out_SetFileName(char *s)
|
void out_SetFileName(char *s)
|
||||||
{
|
{
|
||||||
objectName = s;
|
objectName = s;
|
||||||
|
|||||||
179
src/asm/parser.y
179
src/asm/parser.y
@@ -36,7 +36,7 @@
|
|||||||
#include "linkdefs.h"
|
#include "linkdefs.h"
|
||||||
#include "platform.h" // strncasecmp, strdup
|
#include "platform.h" // strncasecmp, strdup
|
||||||
|
|
||||||
static struct CaptureBody captureBody; // Captures a REPT/FOR or MACRO
|
static struct CaptureBody captureBody; /* Captures a REPT/FOR or MACRO */
|
||||||
|
|
||||||
static void upperstring(char *dest, char const *src)
|
static void upperstring(char *dest, char const *src)
|
||||||
{
|
{
|
||||||
@@ -104,14 +104,14 @@ static size_t strlenUTF8(char const *s)
|
|||||||
case 1:
|
case 1:
|
||||||
errorInvalidUTF8Byte(byte, "STRLEN");
|
errorInvalidUTF8Byte(byte, "STRLEN");
|
||||||
state = 0;
|
state = 0;
|
||||||
// fallthrough
|
/* fallthrough */
|
||||||
case 0:
|
case 0:
|
||||||
len++;
|
len++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for partial code point.
|
/* Check for partial code point. */
|
||||||
if (state != 0)
|
if (state != 0)
|
||||||
error("STRLEN: Incomplete UTF-8 character\n");
|
error("STRLEN: Incomplete UTF-8 character\n");
|
||||||
|
|
||||||
@@ -127,13 +127,13 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
|
|||||||
uint32_t curLen = 0;
|
uint32_t curLen = 0;
|
||||||
uint32_t curPos = 1;
|
uint32_t curPos = 1;
|
||||||
|
|
||||||
// Advance to starting position in source string.
|
/* Advance to starting position in source string. */
|
||||||
while (src[srcIndex] && curPos < pos) {
|
while (src[srcIndex] && curPos < pos) {
|
||||||
switch (decode(&state, &codep, src[srcIndex])) {
|
switch (decode(&state, &codep, src[srcIndex])) {
|
||||||
case 1:
|
case 1:
|
||||||
errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
|
errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
|
||||||
state = 0;
|
state = 0;
|
||||||
// fallthrough
|
/* fallthrough */
|
||||||
case 0:
|
case 0:
|
||||||
curPos++;
|
curPos++;
|
||||||
break;
|
break;
|
||||||
@@ -141,19 +141,21 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
|
|||||||
srcIndex++;
|
srcIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A position 1 past the end of the string is allowed, but will trigger the
|
/*
|
||||||
// "Length too big" warning below if the length is nonzero.
|
* A position 1 past the end of the string is allowed, but will trigger the
|
||||||
|
* "Length too big" warning below if the length is nonzero.
|
||||||
|
*/
|
||||||
if (!src[srcIndex] && pos > curPos)
|
if (!src[srcIndex] && pos > curPos)
|
||||||
warning(WARNING_BUILTIN_ARG,
|
warning(WARNING_BUILTIN_ARG,
|
||||||
"STRSUB: Position %" PRIu32 " is past the end of the string\n", pos);
|
"STRSUB: Position %" PRIu32 " is past the end of the string\n", pos);
|
||||||
|
|
||||||
// Copy from source to destination.
|
/* Copy from source to destination. */
|
||||||
while (src[srcIndex] && destIndex < destLen - 1 && curLen < len) {
|
while (src[srcIndex] && destIndex < destLen - 1 && curLen < len) {
|
||||||
switch (decode(&state, &codep, src[srcIndex])) {
|
switch (decode(&state, &codep, src[srcIndex])) {
|
||||||
case 1:
|
case 1:
|
||||||
errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
|
errorInvalidUTF8Byte(src[srcIndex], "STRSUB");
|
||||||
state = 0;
|
state = 0;
|
||||||
// fallthrough
|
/* fallthrough */
|
||||||
case 0:
|
case 0:
|
||||||
curLen++;
|
curLen++;
|
||||||
break;
|
break;
|
||||||
@@ -164,7 +166,7 @@ static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos
|
|||||||
if (curLen < len)
|
if (curLen < len)
|
||||||
warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %" PRIu32 "\n", len);
|
warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %" PRIu32 "\n", len);
|
||||||
|
|
||||||
// Check for partial code point.
|
/* Check for partial code point. */
|
||||||
if (state != 0)
|
if (state != 0)
|
||||||
error("STRSUB: Incomplete UTF-8 character\n");
|
error("STRSUB: Incomplete UTF-8 character\n");
|
||||||
|
|
||||||
@@ -185,7 +187,7 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
|
|||||||
{
|
{
|
||||||
size_t charLen = 1;
|
size_t charLen = 1;
|
||||||
|
|
||||||
// Advance to starting position in source string.
|
/* Advance to starting position in source string. */
|
||||||
for (uint32_t curPos = 1; charLen && curPos < pos; curPos++)
|
for (uint32_t curPos = 1; charLen && curPos < pos; curPos++)
|
||||||
charLen = charmap_ConvertNext(&src, NULL);
|
charLen = charmap_ConvertNext(&src, NULL);
|
||||||
|
|
||||||
@@ -195,7 +197,7 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
|
|||||||
warning(WARNING_BUILTIN_ARG,
|
warning(WARNING_BUILTIN_ARG,
|
||||||
"CHARSUB: Position %" PRIu32 " is past the end of the string\n", pos);
|
"CHARSUB: Position %" PRIu32 " is past the end of the string\n", pos);
|
||||||
|
|
||||||
// Copy from source to destination.
|
/* Copy from source to destination. */
|
||||||
memcpy(dest, start, src - start);
|
memcpy(dest, start, src - start);
|
||||||
|
|
||||||
dest[src - start] = '\0';
|
dest[src - start] = '\0';
|
||||||
@@ -203,8 +205,10 @@ static void charsubUTF8(char *dest, char const *src, uint32_t pos)
|
|||||||
|
|
||||||
static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionName)
|
static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionName)
|
||||||
{
|
{
|
||||||
// STRSUB and CHARSUB adjust negative `pos` arguments the same way,
|
/*
|
||||||
// such that position -1 is the last character of a string.
|
* STRSUB and CHARSUB adjust negative `pos` arguments the same way,
|
||||||
|
* such that position -1 is the last character of a string.
|
||||||
|
*/
|
||||||
if (pos < 0)
|
if (pos < 0)
|
||||||
pos += len + 1;
|
pos += len + 1;
|
||||||
if (pos < 1) {
|
if (pos < 1) {
|
||||||
@@ -541,7 +545,7 @@ enum {
|
|||||||
%left T_OP_SHL T_OP_SHR T_OP_USHR
|
%left T_OP_SHL T_OP_SHR T_OP_USHR
|
||||||
%left T_OP_MUL T_OP_DIV T_OP_MOD
|
%left T_OP_MUL T_OP_DIV T_OP_MOD
|
||||||
|
|
||||||
%precedence NEG // negation -- unary minus
|
%precedence NEG /* negation -- unary minus */
|
||||||
|
|
||||||
%token T_OP_EXP "**"
|
%token T_OP_EXP "**"
|
||||||
%left T_OP_EXP
|
%left T_OP_EXP
|
||||||
@@ -554,7 +558,6 @@ enum {
|
|||||||
%token T_OP_ASIN "ASIN" T_OP_ACOS "ACOS" T_OP_ATAN "ATAN" T_OP_ATAN2 "ATAN2"
|
%token T_OP_ASIN "ASIN" T_OP_ACOS "ACOS" T_OP_ATAN "ATAN" T_OP_ATAN2 "ATAN2"
|
||||||
%token T_OP_FDIV "FDIV"
|
%token T_OP_FDIV "FDIV"
|
||||||
%token T_OP_FMUL "FMUL"
|
%token T_OP_FMUL "FMUL"
|
||||||
%token T_OP_FMOD "FMOD"
|
|
||||||
%token T_OP_POW "POW"
|
%token T_OP_POW "POW"
|
||||||
%token T_OP_LOG "LOG"
|
%token T_OP_LOG "LOG"
|
||||||
%token T_OP_ROUND "ROUND"
|
%token T_OP_ROUND "ROUND"
|
||||||
@@ -584,6 +587,7 @@ enum {
|
|||||||
%type <symName> scoped_id
|
%type <symName> scoped_id
|
||||||
%type <symName> scoped_anon_id
|
%type <symName> scoped_anon_id
|
||||||
%token T_POP_EQU "EQU"
|
%token T_POP_EQU "EQU"
|
||||||
|
%token T_POP_SET "SET"
|
||||||
%token T_POP_EQUAL "="
|
%token T_POP_EQUAL "="
|
||||||
%token T_POP_EQUS "EQUS"
|
%token T_POP_EQUS "EQUS"
|
||||||
|
|
||||||
@@ -595,6 +599,7 @@ enum {
|
|||||||
|
|
||||||
%token T_POP_INCLUDE "INCLUDE"
|
%token T_POP_INCLUDE "INCLUDE"
|
||||||
%token T_POP_PRINT "PRINT" T_POP_PRINTLN "PRINTLN"
|
%token T_POP_PRINT "PRINT" T_POP_PRINTLN "PRINTLN"
|
||||||
|
%token T_POP_PRINTF "PRINTF" T_POP_PRINTT "PRINTT" T_POP_PRINTV "PRINTV" T_POP_PRINTI "PRINTI"
|
||||||
%token T_POP_IF "IF" T_POP_ELIF "ELIF" T_POP_ELSE "ELSE" T_POP_ENDC "ENDC"
|
%token T_POP_IF "IF" T_POP_ELIF "ELIF" T_POP_ELSE "ELSE" T_POP_ENDC "ENDC"
|
||||||
%token T_POP_EXPORT "EXPORT"
|
%token T_POP_EXPORT "EXPORT"
|
||||||
%token T_POP_DB "DB" T_POP_DS "DS" T_POP_DW "DW" T_POP_DL "DL"
|
%token T_POP_DB "DB" T_POP_DS "DS" T_POP_DW "DW" T_POP_DL "DL"
|
||||||
@@ -637,35 +642,36 @@ enum {
|
|||||||
%type <forArgs> for_args
|
%type <forArgs> for_args
|
||||||
|
|
||||||
%token T_Z80_ADC "adc" T_Z80_ADD "add" T_Z80_AND "and"
|
%token T_Z80_ADC "adc" T_Z80_ADD "add" T_Z80_AND "and"
|
||||||
%token T_Z80_BIT "bit"
|
%token T_Z80_BIT "bit" // There is no T_Z80_SET, only T_POP_SET
|
||||||
%token T_Z80_CALL "call" T_Z80_CCF "ccf" T_Z80_CP "cp" T_Z80_CPL "cpl"
|
%token T_Z80_CALL "call" T_Z80_CCF "ccf" T_Z80_CP "cp" T_Z80_CPL "cpl"
|
||||||
%token T_Z80_DAA "daa" T_Z80_DEC "dec" T_Z80_DI "di"
|
%token T_Z80_DAA "daa" T_Z80_DEC "dec" T_Z80_DI "di"
|
||||||
%token T_Z80_EI "ei"
|
%token T_Z80_EI "ei"
|
||||||
%token T_Z80_HALT "halt"
|
%token T_Z80_HALT "halt✋"
|
||||||
%token T_Z80_INC "inc"
|
%token T_Z80_INC "inc"
|
||||||
%token T_Z80_JP "jp" T_Z80_JR "jr"
|
%token T_Z80_JP "jp" T_Z80_JR "jr"
|
||||||
%token T_Z80_LD "ld"
|
%token T_Z80_LD "ld"
|
||||||
%token T_Z80_LDI "ldi"
|
%token T_Z80_LDI "ldi"
|
||||||
%token T_Z80_LDD "ldd"
|
%token T_Z80_LDD "ldd"
|
||||||
%token T_Z80_LDH "ldh"
|
%token T_Z80_LDH "ldh"
|
||||||
%token T_Z80_NOP "nop"
|
%token T_Z80_NOP "nope"
|
||||||
%token T_Z80_OR "or"
|
%token T_Z80_OR "or"
|
||||||
|
%token T_OWO "owo"
|
||||||
%token T_Z80_POP "pop" T_Z80_PUSH "push"
|
%token T_Z80_POP "pop" T_Z80_PUSH "push"
|
||||||
%token T_Z80_RES "res" T_Z80_RET "ret" T_Z80_RETI "reti" T_Z80_RST "rst"
|
%token T_Z80_RES "res" T_Z80_RET "ret" T_Z80_RETI "reti" T_Z80_RST "rst"
|
||||||
%token T_Z80_RL "rl" T_Z80_RLA "rla" T_Z80_RLC "rlc" T_Z80_RLCA "rlca"
|
%token T_Z80_RL "rl" T_Z80_RLA "rla" T_Z80_RLC "rlc" T_Z80_RLCA "rlca"
|
||||||
%token T_Z80_RR "rr" T_Z80_RRA "rra" T_Z80_RRC "rrc" T_Z80_RRCA "rrca"
|
%token T_Z80_RR "rr" T_Z80_RRA "rra" T_Z80_RRC "rrc" T_Z80_RRCA "rrca"
|
||||||
%token T_Z80_SBC "sbc" T_Z80_SCF "scf" T_Z80_SET "set" T_Z80_STOP "stop"
|
%token T_Z80_SBC "sbc" T_Z80_SCF "scf" T_Z80_STOP "stop!!🛑"
|
||||||
%token T_Z80_SLA "sla" T_Z80_SRA "sra" T_Z80_SRL "srl" T_Z80_SUB "sub"
|
%token T_Z80_SLA "sla" T_Z80_SRA "sra" T_Z80_SRL "srl" T_Z80_SUB "sub"
|
||||||
%token T_Z80_SWAP "swap"
|
%token T_Z80_SWAP "swap"
|
||||||
%token T_Z80_XOR "xor"
|
%token T_Z80_XOR "xor"
|
||||||
|
|
||||||
%token T_TOKEN_A "a"
|
%token T_TOKEN_A "( •̀A•́)" T_TOKEN_F "𝓕𝓾𝓬𝓴"
|
||||||
%token T_TOKEN_B "b" T_TOKEN_C "c"
|
%token T_TOKEN_B "=B" T_TOKEN_C "♥(˘⌣˘ C)"
|
||||||
%token T_TOKEN_D "d" T_TOKEN_E "e"
|
%token T_TOKEN_D ";D" T_TOKEN_E "(´ε` )♡" T_TOKEN_E_HEART "(´ε` )♡"
|
||||||
%token T_TOKEN_H "h" T_TOKEN_L "l"
|
%token T_TOKEN_H "н" T_TOKEN_L_ARM "∠( ᐛ 」∠)_" T_TOKEN_L_FACE "∠( ᐛ 」∠)_" T_TOKEN_L_BODY "∠( ᐛ 」∠)_" T_TOKEN_L_LEG "∠( ᐛ 」∠)_"
|
||||||
%token T_MODE_AF "af" T_MODE_BC "bc" T_MODE_DE "de" T_MODE_SP "sp"
|
%token T_MODE_AF "af" /* T_MODE_BC "bc" T_MODE_DE "de" */ T_MODE_SP "sp"
|
||||||
%token T_MODE_HL "hl" T_MODE_HL_DEC "hld/hl-" T_MODE_HL_INC "hli/hl+"
|
%token T_MODE_HL_START "н∠( ᐛ 」∠)_" T_MODE_HL_DEC "hld/hl-" T_MODE_HL_INC "hli/hl+"
|
||||||
%token T_CC_NZ "nz" T_CC_Z "z" T_CC_NC "nc" // There is no T_CC_C, only T_TOKEN_C
|
%token T_CC_NZ "nz" T_CC_Z "z" T_CC_NC "nc" T_CC_C "c"
|
||||||
|
|
||||||
%type <constValue> reg_r
|
%type <constValue> reg_r
|
||||||
%type <constValue> reg_ss
|
%type <constValue> reg_ss
|
||||||
@@ -702,8 +708,8 @@ plain_directive : label
|
|||||||
;
|
;
|
||||||
|
|
||||||
line : plain_directive endofline
|
line : plain_directive endofline
|
||||||
| line_directive // Directives that manage newlines themselves
|
| line_directive /* Directives that manage newlines themselves */
|
||||||
// Continue parsing the next line on a syntax error
|
/* Continue parsing the next line on a syntax error */
|
||||||
| error {
|
| error {
|
||||||
lexer_SetMode(LEXER_NORMAL);
|
lexer_SetMode(LEXER_NORMAL);
|
||||||
lexer_ToggleStringExpansion(true);
|
lexer_ToggleStringExpansion(true);
|
||||||
@@ -711,7 +717,7 @@ line : plain_directive endofline
|
|||||||
fstk_StopRept();
|
fstk_StopRept();
|
||||||
yyerrok;
|
yyerrok;
|
||||||
}
|
}
|
||||||
// Hint about unindented macros parsed as labels
|
/* Hint about unindented macros parsed as labels */
|
||||||
| T_LABEL error {
|
| T_LABEL error {
|
||||||
lexer_SetMode(LEXER_NORMAL);
|
lexer_SetMode(LEXER_NORMAL);
|
||||||
lexer_ToggleStringExpansion(true);
|
lexer_ToggleStringExpansion(true);
|
||||||
@@ -726,17 +732,19 @@ line : plain_directive endofline
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
// For "logistical" reasons, these directives must manage newlines themselves.
|
/*
|
||||||
// This is because we need to switch the lexer's mode *after* the newline has been read,
|
* For "logistical" reasons, these directives must manage newlines themselves.
|
||||||
// and to avoid causing some grammar conflicts (token reducing is finicky).
|
* This is because we need to switch the lexer's mode *after* the newline has been read,
|
||||||
// This is DEFINITELY one of the more FRAGILE parts of the codebase, handle with care.
|
* and to avoid causing some grammar conflicts (token reducing is finicky).
|
||||||
|
* This is DEFINITELY one of the more FRAGILE parts of the codebase, handle with care.
|
||||||
|
*/
|
||||||
line_directive : macrodef
|
line_directive : macrodef
|
||||||
| rept
|
| rept
|
||||||
| for
|
| for
|
||||||
| break
|
| break
|
||||||
| include
|
| include
|
||||||
| if
|
| if
|
||||||
// It's important that all of these require being at line start for `skipIfBlock`
|
/* It's important that all of these require being at line start for `skipIfBlock` */
|
||||||
| elif
|
| elif
|
||||||
| else
|
| else
|
||||||
;
|
;
|
||||||
@@ -847,9 +855,9 @@ macroargs : %empty {
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
// These commands start with a T_LABEL.
|
/* These commands start with a T_LABEL. */
|
||||||
assignment_directive : equ
|
assignment_directive : equ
|
||||||
| assignment
|
| set
|
||||||
| rb
|
| rb
|
||||||
| rw
|
| rw
|
||||||
| rl
|
| rl
|
||||||
@@ -859,6 +867,10 @@ assignment_directive : equ
|
|||||||
directive : endc
|
directive : endc
|
||||||
| print
|
| print
|
||||||
| println
|
| println
|
||||||
|
| printf
|
||||||
|
| printt
|
||||||
|
| printv
|
||||||
|
| printi
|
||||||
| export
|
| export
|
||||||
| db
|
| db
|
||||||
| dw
|
| dw
|
||||||
@@ -916,8 +928,12 @@ compoundeq : T_POP_ADDEQ { $$ = RPN_ADD; }
|
|||||||
equ : T_LABEL T_POP_EQU const { sym_AddEqu($1, $3); }
|
equ : T_LABEL T_POP_EQU const { sym_AddEqu($1, $3); }
|
||||||
;
|
;
|
||||||
|
|
||||||
assignment : T_LABEL T_POP_EQUAL const { sym_AddVar($1, $3); }
|
set : T_LABEL T_POP_EQUAL const { sym_AddVar($1, $3); }
|
||||||
| T_LABEL compoundeq const { compoundAssignment($1, $2, $3); }
|
| T_LABEL compoundeq const { compoundAssignment($1, $2, $3); }
|
||||||
|
| T_LABEL T_POP_SET const {
|
||||||
|
warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
|
||||||
|
sym_AddVar($1, $3);
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
equs : T_LABEL T_POP_EQUS string { sym_AddString($1, $3); }
|
equs : T_LABEL T_POP_EQUS string { sym_AddString($1, $3); }
|
||||||
@@ -1093,7 +1109,6 @@ macrodef : T_POP_MACRO {
|
|||||||
captureBody.size);
|
captureBody.size);
|
||||||
}
|
}
|
||||||
| T_LABEL T_COLON T_POP_MACRO T_NEWLINE {
|
| T_LABEL T_COLON T_POP_MACRO T_NEWLINE {
|
||||||
warning(WARNING_OBSOLETE, "`%s: MACRO` is deprecated; use `MACRO %s`\n", $1, $1);
|
|
||||||
$<captureTerminated>$ = lexer_CaptureMacroBody(&captureBody);
|
$<captureTerminated>$ = lexer_CaptureMacroBody(&captureBody);
|
||||||
} endofline {
|
} endofline {
|
||||||
if ($<captureTerminated>5)
|
if ($<captureTerminated>5)
|
||||||
@@ -1160,6 +1175,14 @@ def_set : def_id T_POP_EQUAL const { sym_AddVar($1, $3); }
|
|||||||
| redef_id T_POP_EQUAL const { sym_AddVar($1, $3); }
|
| redef_id T_POP_EQUAL const { sym_AddVar($1, $3); }
|
||||||
| def_id compoundeq const { compoundAssignment($1, $2, $3); }
|
| def_id compoundeq const { compoundAssignment($1, $2, $3); }
|
||||||
| redef_id compoundeq const { compoundAssignment($1, $2, $3); }
|
| redef_id compoundeq const { compoundAssignment($1, $2, $3); }
|
||||||
|
| def_id T_POP_SET const {
|
||||||
|
warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
|
||||||
|
sym_AddVar($1, $3);
|
||||||
|
}
|
||||||
|
| redef_id T_POP_SET const {
|
||||||
|
warning(WARNING_OBSOLETE, "`SET` for variables is deprecated; use `=`\n");
|
||||||
|
sym_AddVar($1, $3);
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
def_rb : def_id T_POP_RB rs_uconst {
|
def_rb : def_id T_POP_RB rs_uconst {
|
||||||
@@ -1273,6 +1296,30 @@ print_expr : const_no_str { printf("$%" PRIX32, $1); }
|
|||||||
| string { printf("%s", $1); }
|
| string { printf("%s", $1); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
printt : T_POP_PRINTT string {
|
||||||
|
warning(WARNING_OBSOLETE, "`PRINTT` is deprecated; use `PRINT`\n");
|
||||||
|
printf("%s", $2);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
printv : T_POP_PRINTV const {
|
||||||
|
warning(WARNING_OBSOLETE, "`PRINTV` is deprecated; use `PRINT`\n");
|
||||||
|
printf("$%" PRIX32, $2);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
printi : T_POP_PRINTI const {
|
||||||
|
warning(WARNING_OBSOLETE, "`PRINTI` is deprecated; use `PRINT` with `STRFMT` \"%%d\"\n");
|
||||||
|
printf("%" PRId32, $2);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
printf : T_POP_PRINTF const {
|
||||||
|
warning(WARNING_OBSOLETE, "`PRINTF` is deprecated; use `PRINT` with `STRFMT` \"%%f\"\n");
|
||||||
|
fix_Print($2);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
const_3bit : const {
|
const_3bit : const {
|
||||||
int32_t value = $1;
|
int32_t value = $1;
|
||||||
|
|
||||||
@@ -1293,7 +1340,7 @@ constlist_8bit_entry : reloc_8bit_no_str {
|
|||||||
sect_RelByte(&$1, 0);
|
sect_RelByte(&$1, 0);
|
||||||
}
|
}
|
||||||
| string {
|
| string {
|
||||||
uint8_t *output = malloc(strlen($1)); // Cannot be larger than that
|
uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */
|
||||||
size_t length = charmap_Convert($1, output);
|
size_t length = charmap_Convert($1, output);
|
||||||
|
|
||||||
sect_AbsByteGroup(output, length);
|
sect_AbsByteGroup(output, length);
|
||||||
@@ -1309,7 +1356,7 @@ constlist_16bit_entry : reloc_16bit_no_str {
|
|||||||
sect_RelWord(&$1, 0);
|
sect_RelWord(&$1, 0);
|
||||||
}
|
}
|
||||||
| string {
|
| string {
|
||||||
uint8_t *output = malloc(strlen($1)); // Cannot be larger than that
|
uint8_t *output = malloc(strlen($1)); /* Cannot be larger than that */
|
||||||
size_t length = charmap_Convert($1, output);
|
size_t length = charmap_Convert($1, output);
|
||||||
|
|
||||||
sect_AbsWordGroup(output, length);
|
sect_AbsWordGroup(output, length);
|
||||||
@@ -1483,9 +1530,6 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); }
|
|||||||
| T_OP_FMUL T_LPAREN const T_COMMA const T_RPAREN {
|
| T_OP_FMUL T_LPAREN const T_COMMA const T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Mul($3, $5));
|
rpn_Number(&$$, fix_Mul($3, $5));
|
||||||
}
|
}
|
||||||
| T_OP_FMOD T_LPAREN const T_COMMA const T_RPAREN {
|
|
||||||
rpn_Number(&$$, fix_Mod($3, $5));
|
|
||||||
}
|
|
||||||
| T_OP_POW T_LPAREN const T_COMMA const T_RPAREN {
|
| T_OP_POW T_LPAREN const T_COMMA const T_RPAREN {
|
||||||
rpn_Number(&$$, fix_Pow($3, $5));
|
rpn_Number(&$$, fix_Pow($3, $5));
|
||||||
}
|
}
|
||||||
@@ -1674,7 +1718,7 @@ sectattrs : %empty {
|
|||||||
$$.alignOfs = $7;
|
$$.alignOfs = $7;
|
||||||
}
|
}
|
||||||
| sectattrs T_COMMA T_OP_BANK T_LBRACK uconst T_RBRACK {
|
| sectattrs T_COMMA T_OP_BANK T_LBRACK uconst T_RBRACK {
|
||||||
// We cannot check the validity of this now
|
/* We cannot check the validity of this now */
|
||||||
$$.bank = $5;
|
$$.bank = $5;
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
@@ -1726,6 +1770,7 @@ cpu_command : z80_adc
|
|||||||
| z80_sub
|
| z80_sub
|
||||||
| z80_swap
|
| z80_swap
|
||||||
| z80_xor
|
| z80_xor
|
||||||
|
| T_OWO { fatalerror("*BONK* go to horny jail\n"); }
|
||||||
;
|
;
|
||||||
|
|
||||||
z80_adc : T_Z80_ADC op_a_n {
|
z80_adc : T_Z80_ADC op_a_n {
|
||||||
@@ -1799,13 +1844,8 @@ z80_ei : T_Z80_EI { sect_AbsByte(0xFB); }
|
|||||||
|
|
||||||
z80_halt : T_Z80_HALT {
|
z80_halt : T_Z80_HALT {
|
||||||
sect_AbsByte(0x76);
|
sect_AbsByte(0x76);
|
||||||
if (haltnop) {
|
if (haltnop)
|
||||||
if (warnOnHaltNop) {
|
|
||||||
warnOnHaltNop = false;
|
|
||||||
warning(WARNING_OBSOLETE, "`nop` after `halt` will stop being the default; pass `-H` to opt into it\n");
|
|
||||||
}
|
|
||||||
sect_AbsByte(0x00);
|
sect_AbsByte(0x00);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -1913,10 +1953,6 @@ z80_ld_mem : T_Z80_LD op_mem_ind T_COMMA T_MODE_SP {
|
|||||||
| T_Z80_LD op_mem_ind T_COMMA T_MODE_A {
|
| T_Z80_LD op_mem_ind T_COMMA T_MODE_A {
|
||||||
if (optimizeLoads && rpn_isKnown(&$2)
|
if (optimizeLoads && rpn_isKnown(&$2)
|
||||||
&& $2.val >= 0xFF00) {
|
&& $2.val >= 0xFF00) {
|
||||||
if (warnOnLdOpt) {
|
|
||||||
warnOnLdOpt = false;
|
|
||||||
warning(WARNING_OBSOLETE, "ld optimization will stop being the default; pass `-l` to opt into it\n");
|
|
||||||
}
|
|
||||||
sect_AbsByte(0xE0);
|
sect_AbsByte(0xE0);
|
||||||
sect_AbsByte($2.val & 0xFF);
|
sect_AbsByte($2.val & 0xFF);
|
||||||
rpn_Free(&$2);
|
rpn_Free(&$2);
|
||||||
@@ -1965,10 +2001,6 @@ z80_ld_a : T_Z80_LD reg_r T_COMMA c_ind {
|
|||||||
if ($2 == REG_A) {
|
if ($2 == REG_A) {
|
||||||
if (optimizeLoads && rpn_isKnown(&$4)
|
if (optimizeLoads && rpn_isKnown(&$4)
|
||||||
&& $4.val >= 0xFF00) {
|
&& $4.val >= 0xFF00) {
|
||||||
if (warnOnLdOpt) {
|
|
||||||
warnOnLdOpt = false;
|
|
||||||
warning(WARNING_OBSOLETE, "ld optimization will stop being the default; pass `-l` to opt into it\n");
|
|
||||||
}
|
|
||||||
sect_AbsByte(0xF0);
|
sect_AbsByte(0xF0);
|
||||||
sect_AbsByte($4.val & 0xFF);
|
sect_AbsByte($4.val & 0xFF);
|
||||||
rpn_Free(&$4);
|
rpn_Free(&$4);
|
||||||
@@ -1991,8 +2023,10 @@ z80_ld_ss : T_Z80_LD T_MODE_BC T_COMMA reloc_16bit {
|
|||||||
sect_AbsByte(0x01 | (REG_DE << 4));
|
sect_AbsByte(0x01 | (REG_DE << 4));
|
||||||
sect_RelWord(&$4, 1);
|
sect_RelWord(&$4, 1);
|
||||||
}
|
}
|
||||||
// HL is taken care of in z80_ld_hl
|
/*
|
||||||
// SP is taken care of in z80_ld_sp
|
* HL is taken care of in z80_ld_hl
|
||||||
|
* SP is taken care of in z80_ld_sp
|
||||||
|
*/
|
||||||
;
|
;
|
||||||
|
|
||||||
z80_nop : T_Z80_NOP { sect_AbsByte(0x00); }
|
z80_nop : T_Z80_NOP { sect_AbsByte(0x00); }
|
||||||
@@ -2080,7 +2114,7 @@ z80_sbc : T_Z80_SBC op_a_n {
|
|||||||
z80_scf : T_Z80_SCF { sect_AbsByte(0x37); }
|
z80_scf : T_Z80_SCF { sect_AbsByte(0x37); }
|
||||||
;
|
;
|
||||||
|
|
||||||
z80_set : T_Z80_SET const_3bit T_COMMA reg_r {
|
z80_set : T_POP_SET const_3bit T_COMMA reg_r {
|
||||||
sect_AbsByte(0xCB);
|
sect_AbsByte(0xCB);
|
||||||
sect_AbsByte(0xC0 | ($2 << 3) | $4);
|
sect_AbsByte(0xC0 | ($2 << 3) | $4);
|
||||||
}
|
}
|
||||||
@@ -2145,7 +2179,7 @@ op_a_n : reloc_8bit
|
|||||||
| T_MODE_A T_COMMA reloc_8bit { $$ = $3; }
|
| T_MODE_A T_COMMA reloc_8bit { $$ = $3; }
|
||||||
;
|
;
|
||||||
|
|
||||||
T_MODE_A : T_TOKEN_A
|
T_MODE_A : T_LPAREN T_TOKEN_A T_RPAREN
|
||||||
| T_OP_HIGH T_LPAREN T_MODE_AF T_RPAREN
|
| T_OP_HIGH T_LPAREN T_MODE_AF T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -2153,7 +2187,7 @@ T_MODE_B : T_TOKEN_B
|
|||||||
| T_OP_HIGH T_LPAREN T_MODE_BC T_RPAREN
|
| T_OP_HIGH T_LPAREN T_MODE_BC T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
T_MODE_C : T_TOKEN_C
|
T_MODE_C : T_TOKEN_C T_CC_C T_RPAREN
|
||||||
| T_OP_LOW T_LPAREN T_MODE_BC T_RPAREN
|
| T_OP_LOW T_LPAREN T_MODE_BC T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -2161,7 +2195,7 @@ T_MODE_D : T_TOKEN_D
|
|||||||
| T_OP_HIGH T_LPAREN T_MODE_DE T_RPAREN
|
| T_OP_HIGH T_LPAREN T_MODE_DE T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
T_MODE_E : T_TOKEN_E
|
T_MODE_E : T_TOKEN_E T_RPAREN T_TOKEN_E_HEART
|
||||||
| T_OP_LOW T_LPAREN T_MODE_DE T_RPAREN
|
| T_OP_LOW T_LPAREN T_MODE_DE T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -2169,10 +2203,19 @@ T_MODE_H : T_TOKEN_H
|
|||||||
| T_OP_HIGH T_LPAREN T_MODE_HL T_RPAREN
|
| T_OP_HIGH T_LPAREN T_MODE_HL T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
T_MODE_L : T_TOKEN_L
|
T_MODE_L : T_TOKEN_L_ARM T_TOKEN_L_FACE T_TOKEN_L_BODY T_RPAREN T_TOKEN_L_LEG
|
||||||
| T_OP_LOW T_LPAREN T_MODE_HL T_RPAREN
|
| T_OP_LOW T_LPAREN T_MODE_HL T_RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
|
T_MODE_BC : T_TOKEN_B T_TOKEN_C T_CC_C T_RPAREN
|
||||||
|
;
|
||||||
|
|
||||||
|
T_MODE_DE : T_TOKEN_D T_TOKEN_E T_RPAREN T_TOKEN_E_HEART
|
||||||
|
;
|
||||||
|
|
||||||
|
T_MODE_HL : T_MODE_HL_START T_TOKEN_L_FACE T_TOKEN_L_BODY T_RPAREN T_TOKEN_L_LEG
|
||||||
|
;
|
||||||
|
|
||||||
ccode_expr : ccode
|
ccode_expr : ccode
|
||||||
| T_OP_LOGICNOT ccode_expr {
|
| T_OP_LOGICNOT ccode_expr {
|
||||||
$$ = $2 ^ 1;
|
$$ = $2 ^ 1;
|
||||||
@@ -2182,7 +2225,7 @@ ccode_expr : ccode
|
|||||||
ccode : T_CC_NZ { $$ = CC_NZ; }
|
ccode : T_CC_NZ { $$ = CC_NZ; }
|
||||||
| T_CC_Z { $$ = CC_Z; }
|
| T_CC_Z { $$ = CC_Z; }
|
||||||
| T_CC_NC { $$ = CC_NC; }
|
| T_CC_NC { $$ = CC_NC; }
|
||||||
| T_TOKEN_C { $$ = CC_C; }
|
| T_CC_C { $$ = CC_C; }
|
||||||
;
|
;
|
||||||
|
|
||||||
reg_r : T_MODE_B { $$ = REG_B; }
|
reg_r : T_MODE_B { $$ = REG_B; }
|
||||||
@@ -2198,7 +2241,7 @@ reg_r : T_MODE_B { $$ = REG_B; }
|
|||||||
reg_tt : T_MODE_BC { $$ = REG_BC; }
|
reg_tt : T_MODE_BC { $$ = REG_BC; }
|
||||||
| T_MODE_DE { $$ = REG_DE; }
|
| T_MODE_DE { $$ = REG_DE; }
|
||||||
| T_MODE_HL { $$ = REG_HL; }
|
| T_MODE_HL { $$ = REG_HL; }
|
||||||
| T_MODE_AF { $$ = REG_AF; }
|
| T_LPAREN T_TOKEN_A T_RPAREN T_TOKEN_F { $$ = REG_AF; }
|
||||||
;
|
;
|
||||||
|
|
||||||
reg_ss : T_MODE_BC { $$ = REG_BC; }
|
reg_ss : T_MODE_BC { $$ = REG_BC; }
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
.Nd Game Boy assembler
|
.Nd Game Boy assembler
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl EHhLlVvw
|
.Op Fl EhLVvw
|
||||||
.Op Fl b Ar chars
|
.Op Fl b Ar chars
|
||||||
.Op Fl D Ar name Ns Op = Ns Ar value
|
.Op Fl D Ar name Ns Op = Ns Ar value
|
||||||
.Op Fl g Ar chars
|
.Op Fl g Ar chars
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
.Op Fl MQ Ar target_file
|
.Op Fl MQ Ar target_file
|
||||||
.Op Fl o Ar out_file
|
.Op Fl o Ar out_file
|
||||||
.Op Fl p Ar pad_value
|
.Op Fl p Ar pad_value
|
||||||
.Op Fl Q Ar fix_precision
|
|
||||||
.Op Fl r Ar recursion_depth
|
.Op Fl r Ar recursion_depth
|
||||||
.Op Fl W Ar warning
|
.Op Fl W Ar warning
|
||||||
.Ar
|
.Ar
|
||||||
@@ -56,9 +55,9 @@ The defaults are 01.
|
|||||||
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl Fl define Ar name Ns Oo = Ns Ar value Oc
|
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl Fl define Ar name Ns Oo = Ns Ar value Oc
|
||||||
Add a string symbol to the compiled source code.
|
Add a string symbol to the compiled source code.
|
||||||
This is equivalent to
|
This is equivalent to
|
||||||
.Ql Ar name Ic EQUS No \(dq Ns Ar value Ns \(dq
|
.Ql Ar name Ic EQUS \(dq Ns Ar value Ns \(dq
|
||||||
in code, or
|
in code, or
|
||||||
.Ql Ar name Ic EQUS No \(dq1\(dq
|
.Ql Ar name Ic EQUS \(dq1\(dq
|
||||||
if
|
if
|
||||||
.Ar value
|
.Ar value
|
||||||
is not specified.
|
is not specified.
|
||||||
@@ -67,43 +66,25 @@ Export all labels, including unreferenced and local labels.
|
|||||||
.It Fl g Ar chars , Fl Fl gfx-chars Ar chars
|
.It Fl g Ar chars , Fl Fl gfx-chars Ar chars
|
||||||
Change the four characters used for gfx constants.
|
Change the four characters used for gfx constants.
|
||||||
The defaults are 0123.
|
The defaults are 0123.
|
||||||
.It Fl H , Fl Fl nop-after-halt
|
.It Fl h , Fl Fl halt-without-nop
|
||||||
By default,
|
By default,
|
||||||
.Nm
|
.Nm
|
||||||
inserts a
|
inserts a
|
||||||
.Ic nop
|
.Ic nop
|
||||||
instruction immediately after any
|
instruction immediately after any
|
||||||
.Ic halt
|
.Ic halt
|
||||||
instruction,
|
|
||||||
but this has been deprecated and prints a warning message the first time it occurs.
|
|
||||||
The
|
|
||||||
.Fl H
|
|
||||||
option opts into this insertion,
|
|
||||||
so no warning will be printed.
|
|
||||||
.It Fl h , Fl Fl halt-without-nop
|
|
||||||
Disables inserting a
|
|
||||||
.Ic nop
|
|
||||||
instruction immediately after any
|
|
||||||
.Ic halt
|
|
||||||
instruction.
|
instruction.
|
||||||
|
The
|
||||||
|
.Fl h
|
||||||
|
option disables this behavior.
|
||||||
.It Fl i Ar path , Fl Fl include Ar path
|
.It Fl i Ar path , Fl Fl include Ar path
|
||||||
Add an include path.
|
Add an include path.
|
||||||
.It Fl L , Fl Fl preserve-ld
|
.It Fl L , Fl Fl preserve-ld
|
||||||
By default,
|
Disable the optimization that turns loads of the form
|
||||||
.Nm
|
|
||||||
optimizes loads of the form
|
|
||||||
.Ic LD [$FF00+n8],A
|
.Ic LD [$FF00+n8],A
|
||||||
into the opcode
|
into the opcode
|
||||||
.Ic LDH [$FF00+n8],A ,
|
.Ic LDH [$FF00+n8],A
|
||||||
but this has been deprecated and prints a warning message the first time it occurs.
|
in order to have full control of the result in the final ROM.
|
||||||
The
|
|
||||||
.Fl L
|
|
||||||
option disables this optimization.
|
|
||||||
.It Fl l , Fl Fl auto-ldh
|
|
||||||
Optimize loads of the form
|
|
||||||
.Ic LD [$FF00+n8],A
|
|
||||||
into the opcode
|
|
||||||
.Ic LDH [$FF00+n8],A .
|
|
||||||
.It Fl M Ar depend_file , Fl Fl dependfile Ar depend_file
|
.It Fl M Ar depend_file , Fl Fl dependfile Ar depend_file
|
||||||
Print
|
Print
|
||||||
.Xr make 1
|
.Xr make 1
|
||||||
@@ -149,16 +130,8 @@ Write an object file to the given filename.
|
|||||||
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
|
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
|
||||||
When padding an image, pad with this value.
|
When padding an image, pad with this value.
|
||||||
The default is 0x00.
|
The default is 0x00.
|
||||||
.It Fl Q Ar fix_precision , Fl Fl q-precision Ar fix_precision
|
|
||||||
Use this as the precision of fixed-point numbers after the decimal point, unless they specify their own precision.
|
|
||||||
The default is 16, so fixed-point numbers are Q16.16 (since they are 32-bit integers).
|
|
||||||
The argument may start with a
|
|
||||||
.Ql \&.
|
|
||||||
to match the Q notation, for example,
|
|
||||||
.Ql Fl Q Ar .16 .
|
|
||||||
.It Fl r Ar recursion_depth , Fl Fl recursion-depth Ar recursion_depth
|
.It Fl r Ar recursion_depth , Fl Fl recursion-depth Ar recursion_depth
|
||||||
Specifies the recursion depth past which RGBASM will assume being in an infinite loop.
|
Specifies the recursion depth at which RGBASM will assume being in an infinite loop.
|
||||||
The default is 64.
|
|
||||||
.It Fl V , Fl Fl version
|
.It Fl V , Fl Fl version
|
||||||
Print the version of the program and exit.
|
Print the version of the program and exit.
|
||||||
.It Fl v , Fl Fl verbose
|
.It Fl v , Fl Fl verbose
|
||||||
@@ -301,12 +274,6 @@ warns when an N-bit value's absolute value is 2**N or greater.
|
|||||||
or just
|
or just
|
||||||
.Fl Wtruncation
|
.Fl Wtruncation
|
||||||
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
|
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
|
||||||
.It Fl Wunmapped-char
|
|
||||||
Warn when a character goes through charmap conversion but has no defined mapping.
|
|
||||||
This warning is always disabled if the active charmap is empty, and/or is the default charmap
|
|
||||||
.Sq main .
|
|
||||||
This warning is enabled by
|
|
||||||
.Fl Wall .
|
|
||||||
.It Fl Wno-user
|
.It Fl Wno-user
|
||||||
Warn when the
|
Warn when the
|
||||||
.Ic WARN
|
.Ic WARN
|
||||||
@@ -208,14 +208,13 @@ section.
|
|||||||
The instructions in the macro-language generally require constant expressions.
|
The instructions in the macro-language generally require constant expressions.
|
||||||
.Ss Numeric formats
|
.Ss Numeric formats
|
||||||
There are a number of numeric formats.
|
There are a number of numeric formats.
|
||||||
.Bl -column -offset indent "Precise fixed-point" "Prefix"
|
.Bl -column -offset indent "Fixed point (Q16.16)" "Prefix"
|
||||||
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
|
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
|
||||||
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
|
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
|
||||||
.It Decimal Ta none Ta 0123456789
|
.It Decimal Ta none Ta 0123456789
|
||||||
.It Octal Ta & Ta 01234567
|
.It Octal Ta & Ta 01234567
|
||||||
.It Binary Ta % Ta 01
|
.It Binary Ta % Ta 01
|
||||||
.It Fixed-point Ta none Ta 01234.56789
|
.It Fixed point (Q16.16) Ta none Ta 01234.56789
|
||||||
.It Precise fixed-point Ta none Ta 12.34q8
|
|
||||||
.It Character constant Ta none Ta \(dqABYZ\(dq
|
.It Character constant Ta none Ta \(dqABYZ\(dq
|
||||||
.It Gameboy graphics Ta \` Ta 0123
|
.It Gameboy graphics Ta \` Ta 0123
|
||||||
.El
|
.El
|
||||||
@@ -302,19 +301,9 @@ and
|
|||||||
.Ic \&!
|
.Ic \&!
|
||||||
returns 1 if the operand was 0, and 0 otherwise.
|
returns 1 if the operand was 0, and 0 otherwise.
|
||||||
.Ss Fixed-point expressions
|
.Ss Fixed-point expressions
|
||||||
Fixed-point numbers are basically normal (32-bit) integers, which count fractions instead of whole numbers.
|
Fixed-point numbers are basically normal (32-bit) integers, which count 65536ths instead of entire units, offering better precision than integers but limiting the range of values.
|
||||||
They offer better precision than integers but limit the range of values.
|
The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
|
||||||
By default, the upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
|
Since they are still akin to integers, you can use them in normal integer expressions, and some integer operators like
|
||||||
The default number of fractional bits can be changed with the
|
|
||||||
.Fl Q
|
|
||||||
command-line option.
|
|
||||||
You can also specify a precise fixed-point value by appending a
|
|
||||||
.Dq q
|
|
||||||
to it followed by the number of fractional bits, such as
|
|
||||||
.Ql 12.34q8 .
|
|
||||||
.Pp
|
|
||||||
Since fixed-point values are still just integers, you can use them in normal integer expressions.
|
|
||||||
Some integer operators like
|
|
||||||
.Sq +
|
.Sq +
|
||||||
and
|
and
|
||||||
.Sq -
|
.Sq -
|
||||||
@@ -328,9 +317,8 @@ delim $$
|
|||||||
.EN
|
.EN
|
||||||
.Bl -column -offset indent "ATAN2(x, y)"
|
.Bl -column -offset indent "ATAN2(x, y)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn DIV x y Ta Fixed-point division $( x \[di] y ) \[mu] ( 2 ^ precision )$
|
.It Fn DIV x y Ta $x \[di] y$
|
||||||
.It Fn MUL x y Ta Fixed-point multiplication $( x \[mu] y ) \[di] ( 2 ^ precision )$
|
.It Fn MUL x y Ta $x \[mu] y$
|
||||||
.It Fn FMOD x y Ta Fixed-point modulo $( x % y ) \[di] ( 2 ^ precision )$
|
|
||||||
.It Fn POW x y Ta $x$ to the $y$ power
|
.It Fn POW x y Ta $x$ to the $y$ power
|
||||||
.It Fn LOG x y Ta Logarithm of $x$ to the base $y$
|
.It Fn LOG x y Ta Logarithm of $x$ to the base $y$
|
||||||
.It Fn ROUND x Ta Round $x$ to the nearest integer
|
.It Fn ROUND x Ta Round $x$ to the nearest integer
|
||||||
@@ -957,9 +945,9 @@ assuming the section ends up at
|
|||||||
.Ad $80C0 :
|
.Ad $80C0 :
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
SECTION "Player tiles", VRAM
|
SECTION "Player tiles", VRAM
|
||||||
vPlayerTiles:
|
PlayerTiles:
|
||||||
ds 6 * 16
|
ds 6 * 16
|
||||||
\&.end
|
.end
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants.
|
A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants.
|
||||||
@@ -1225,16 +1213,14 @@ The example above defines
|
|||||||
.Ql MyMacro
|
.Ql MyMacro
|
||||||
as a new macro.
|
as a new macro.
|
||||||
String constants are not expanded within the name of the macro.
|
String constants are not expanded within the name of the macro.
|
||||||
.Pp
|
You may use the older syntax
|
||||||
(Using the
|
|
||||||
.Em deprecated
|
|
||||||
older syntax
|
|
||||||
.Ql MyMacro: MACRO
|
.Ql MyMacro: MACRO
|
||||||
instead of
|
instead of
|
||||||
.Ql MACRO MyMacro ,
|
.Ql MACRO MyMacro ,
|
||||||
with a single colon
|
with a single colon
|
||||||
.Ql \&:
|
.Ql \&:
|
||||||
following the macro's name, string constants may be expanded for the name.)
|
following the macro's name.
|
||||||
|
With the older syntax, string constants may be expanded for the name.
|
||||||
.Pp
|
.Pp
|
||||||
Macros can't be exported or imported.
|
Macros can't be exported or imported.
|
||||||
.Pp
|
.Pp
|
||||||
@@ -1338,7 +1324,7 @@ DEF AOLer EQUS "Me too"
|
|||||||
String constants are not expanded within the symbol names.
|
String constants are not expanded within the symbol names.
|
||||||
.Ss Predeclared symbols
|
.Ss Predeclared symbols
|
||||||
The following symbols are defined by the assembler:
|
The following symbols are defined by the assembler:
|
||||||
.Bl -column -offset indent "__ISO_8601_LOCAL__" "EQUS"
|
.Bl -column -offset indent "EQUS" "__ISO_8601_LOCAL__"
|
||||||
.It Sy Name Ta Sy Type Ta Sy Contents
|
.It Sy Name Ta Sy Type Ta Sy Contents
|
||||||
.It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
|
.It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
|
||||||
.It Dv _RS Ta Ic = Ta _RS Counter
|
.It Dv _RS Ta Ic = Ta _RS Counter
|
||||||
@@ -1818,29 +1804,11 @@ The
|
|||||||
value will be updated by
|
value will be updated by
|
||||||
.Ar step
|
.Ar step
|
||||||
until it reaches or exceeds
|
until it reaches or exceeds
|
||||||
.Ar stop ,
|
.Ar stop .
|
||||||
i.e. it covers the half-open range from
|
|
||||||
.Ar start
|
|
||||||
(inclusive) to
|
|
||||||
.Ar stop
|
|
||||||
(exclusive).
|
|
||||||
The variable
|
|
||||||
.Ar V
|
|
||||||
will be assigned this value at the beginning of each new iteration; any changes made to it within the
|
|
||||||
.Ic FOR
|
|
||||||
loop's body will be overwritten.
|
|
||||||
So the symbol
|
|
||||||
.Ar V
|
|
||||||
need not be already defined before any iterations of the
|
|
||||||
.Ic FOR
|
|
||||||
loop, but it must be a variable
|
|
||||||
.Pq Sx Variables
|
|
||||||
if so.
|
|
||||||
For example:
|
For example:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
FOR V, 4, 25, 5
|
FOR V, 4, 25, 5
|
||||||
PRINT "{d:V} "
|
PRINT "{d:V} "
|
||||||
DEF V *= 2
|
|
||||||
ENDR
|
ENDR
|
||||||
PRINTLN "done {d:V}"
|
PRINTLN "done {d:V}"
|
||||||
.Ed
|
.Ed
|
||||||
@@ -2045,15 +2013,17 @@ POPO
|
|||||||
The options that
|
The options that
|
||||||
.Ic OPT
|
.Ic OPT
|
||||||
can modify are currently:
|
can modify are currently:
|
||||||
.Cm b , g , p , Q , r , h , L ,
|
.Cm b , g , p , r , h , L ,
|
||||||
and
|
and
|
||||||
.Cm W .
|
.Cm W .
|
||||||
The Boolean flag options
|
The Boolean flag options
|
||||||
.Cm H , h , L ,
|
.Cm h
|
||||||
and
|
and
|
||||||
.Cm l
|
.Cm L
|
||||||
can be negated like
|
can be negated as
|
||||||
.Ql OPT !H
|
.Ql OPT !h
|
||||||
|
and
|
||||||
|
.Ql OPT !L
|
||||||
to act like omitting them from the command-line.
|
to act like omitting them from the command-line.
|
||||||
.Pp
|
.Pp
|
||||||
.Ic POPO
|
.Ic POPO
|
||||||
@@ -6,7 +6,9 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Controls RPN expressions for objectfiles
|
/*
|
||||||
|
* Controls RPN expressions for objectfiles
|
||||||
|
*/
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -26,7 +28,7 @@
|
|||||||
|
|
||||||
#include "opmath.h"
|
#include "opmath.h"
|
||||||
|
|
||||||
// Makes an expression "not known", also setting its error message
|
/* Makes an expression "not known", also setting its error message */
|
||||||
#define makeUnknown(expr_, ...) do { \
|
#define makeUnknown(expr_, ...) do { \
|
||||||
struct Expression *_expr = expr_; \
|
struct Expression *_expr = expr_; \
|
||||||
_expr->isKnown = false; \
|
_expr->isKnown = false; \
|
||||||
@@ -37,23 +39,23 @@
|
|||||||
int size = snprintf(_expr->reason, 128, __VA_ARGS__); \
|
int size = snprintf(_expr->reason, 128, __VA_ARGS__); \
|
||||||
if (size >= 128) { /* If this wasn't enough, try again */ \
|
if (size >= 128) { /* If this wasn't enough, try again */ \
|
||||||
_expr->reason = realloc(_expr->reason, size + 1); \
|
_expr->reason = realloc(_expr->reason, size + 1); \
|
||||||
if (!_expr->reason) \
|
|
||||||
fatalerror("Can't allocate err string: %s\n", strerror(errno)); \
|
|
||||||
sprintf(_expr->reason, __VA_ARGS__); \
|
sprintf(_expr->reason, __VA_ARGS__); \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
|
static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
|
||||||
{
|
{
|
||||||
// This assumes the RPN length is always less than the capacity
|
/* This assumes the RPN length is always less than the capacity */
|
||||||
if (expr->rpnCapacity - expr->rpnLength < size) {
|
if (expr->rpnCapacity - expr->rpnLength < size) {
|
||||||
// If there isn't enough room to reserve the space, realloc
|
/* If there isn't enough room to reserve the space, realloc */
|
||||||
if (!expr->rpn)
|
if (!expr->rpn)
|
||||||
expr->rpnCapacity = 256; // Initial size
|
expr->rpnCapacity = 256; /* Initial size */
|
||||||
while (expr->rpnCapacity - expr->rpnLength < size) {
|
while (expr->rpnCapacity - expr->rpnLength < size) {
|
||||||
if (expr->rpnCapacity >= MAXRPNLEN)
|
if (expr->rpnCapacity >= MAXRPNLEN)
|
||||||
// To avoid generating humongous object files, cap the
|
/*
|
||||||
// size of RPN expressions
|
* To avoid generating humongous object files, cap the
|
||||||
|
* size of RPN expressions
|
||||||
|
*/
|
||||||
fatalerror("RPN expression cannot grow larger than "
|
fatalerror("RPN expression cannot grow larger than "
|
||||||
EXPAND_AND_STR(MAXRPNLEN) " bytes\n");
|
EXPAND_AND_STR(MAXRPNLEN) " bytes\n");
|
||||||
else if (expr->rpnCapacity > MAXRPNLEN / 2)
|
else if (expr->rpnCapacity > MAXRPNLEN / 2)
|
||||||
@@ -73,7 +75,9 @@ static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init a RPN expression
|
/*
|
||||||
|
* Init a RPN expression
|
||||||
|
*/
|
||||||
static void rpn_Init(struct Expression *expr)
|
static void rpn_Init(struct Expression *expr)
|
||||||
{
|
{
|
||||||
expr->reason = NULL;
|
expr->reason = NULL;
|
||||||
@@ -85,7 +89,9 @@ static void rpn_Init(struct Expression *expr)
|
|||||||
expr->rpnPatchSize = 0;
|
expr->rpnPatchSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free the RPN expression
|
/*
|
||||||
|
* Free the RPN expression
|
||||||
|
*/
|
||||||
void rpn_Free(struct Expression *expr)
|
void rpn_Free(struct Expression *expr)
|
||||||
{
|
{
|
||||||
free(expr->rpn);
|
free(expr->rpn);
|
||||||
@@ -93,7 +99,9 @@ void rpn_Free(struct Expression *expr)
|
|||||||
rpn_Init(expr);
|
rpn_Init(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add symbols, constants and operators to expression
|
/*
|
||||||
|
* Add symbols, constants and operators to expression
|
||||||
|
*/
|
||||||
void rpn_Number(struct Expression *expr, uint32_t i)
|
void rpn_Number(struct Expression *expr, uint32_t i)
|
||||||
{
|
{
|
||||||
rpn_Init(expr);
|
rpn_Init(expr);
|
||||||
@@ -114,9 +122,9 @@ void rpn_Symbol(struct Expression *expr, char const *symName)
|
|||||||
makeUnknown(expr, sym_IsPC(sym) ? "PC is not constant at assembly time"
|
makeUnknown(expr, sym_IsPC(sym) ? "PC is not constant at assembly time"
|
||||||
: "'%s' is not constant at assembly time", symName);
|
: "'%s' is not constant at assembly time", symName);
|
||||||
sym = sym_Ref(symName);
|
sym = sym_Ref(symName);
|
||||||
expr->rpnPatchSize += 5; // 1-byte opcode + 4-byte symbol ID
|
expr->rpnPatchSize += 5; /* 1-byte opcode + 4-byte symbol ID */
|
||||||
|
|
||||||
size_t nameLen = strlen(sym->name) + 1; // Don't forget NUL!
|
size_t nameLen = strlen(sym->name) + 1; /* Don't forget NUL! */
|
||||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||||
*ptr++ = RPN_SYM;
|
*ptr++ = RPN_SYM;
|
||||||
memcpy(ptr, sym->name, nameLen);
|
memcpy(ptr, sym->name, nameLen);
|
||||||
@@ -145,7 +153,7 @@ void rpn_BankSymbol(struct Expression *expr, char const *symName)
|
|||||||
{
|
{
|
||||||
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
||||||
|
|
||||||
// The @ symbol is treated differently.
|
/* The @ symbol is treated differently. */
|
||||||
if (sym_IsPC(sym)) {
|
if (sym_IsPC(sym)) {
|
||||||
rpn_BankSelf(expr);
|
rpn_BankSelf(expr);
|
||||||
return;
|
return;
|
||||||
@@ -159,13 +167,13 @@ void rpn_BankSymbol(struct Expression *expr, char const *symName)
|
|||||||
assert(sym); // If the symbol didn't exist, it should have been created
|
assert(sym); // If the symbol didn't exist, it should have been created
|
||||||
|
|
||||||
if (sym_GetSection(sym) && sym_GetSection(sym)->bank != (uint32_t)-1) {
|
if (sym_GetSection(sym) && sym_GetSection(sym)->bank != (uint32_t)-1) {
|
||||||
// Symbol's section is known and bank is fixed
|
/* Symbol's section is known and bank is fixed */
|
||||||
expr->val = sym_GetSection(sym)->bank;
|
expr->val = sym_GetSection(sym)->bank;
|
||||||
} else {
|
} else {
|
||||||
makeUnknown(expr, "\"%s\"'s bank is not known", symName);
|
makeUnknown(expr, "\"%s\"'s bank is not known", symName);
|
||||||
expr->rpnPatchSize += 5; // opcode + 4-byte sect ID
|
expr->rpnPatchSize += 5; /* opcode + 4-byte sect ID */
|
||||||
|
|
||||||
size_t nameLen = strlen(sym->name) + 1; // Room for NUL!
|
size_t nameLen = strlen(sym->name) + 1; /* Room for NUL! */
|
||||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||||
*ptr++ = RPN_BANK_SYM;
|
*ptr++ = RPN_BANK_SYM;
|
||||||
memcpy(ptr, sym->name, nameLen);
|
memcpy(ptr, sym->name, nameLen);
|
||||||
@@ -184,7 +192,7 @@ void rpn_BankSection(struct Expression *expr, char const *sectionName)
|
|||||||
} else {
|
} else {
|
||||||
makeUnknown(expr, "Section \"%s\"'s bank is not known", sectionName);
|
makeUnknown(expr, "Section \"%s\"'s bank is not known", sectionName);
|
||||||
|
|
||||||
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
|
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
|
||||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||||
|
|
||||||
expr->rpnPatchSize += nameLen + 1;
|
expr->rpnPatchSize += nameLen + 1;
|
||||||
@@ -204,7 +212,7 @@ void rpn_SizeOfSection(struct Expression *expr, char const *sectionName)
|
|||||||
} else {
|
} else {
|
||||||
makeUnknown(expr, "Section \"%s\"'s size is not known", sectionName);
|
makeUnknown(expr, "Section \"%s\"'s size is not known", sectionName);
|
||||||
|
|
||||||
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
|
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
|
||||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||||
|
|
||||||
expr->rpnPatchSize += nameLen + 1;
|
expr->rpnPatchSize += nameLen + 1;
|
||||||
@@ -224,7 +232,7 @@ void rpn_StartOfSection(struct Expression *expr, char const *sectionName)
|
|||||||
} else {
|
} else {
|
||||||
makeUnknown(expr, "Section \"%s\"'s start is not known", sectionName);
|
makeUnknown(expr, "Section \"%s\"'s start is not known", sectionName);
|
||||||
|
|
||||||
size_t nameLen = strlen(sectionName) + 1; // Room for NUL!
|
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
|
||||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||||
|
|
||||||
expr->rpnPatchSize += nameLen + 1;
|
expr->rpnPatchSize += nameLen + 1;
|
||||||
@@ -242,7 +250,7 @@ void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src)
|
|||||||
expr->rpnPatchSize++;
|
expr->rpnPatchSize++;
|
||||||
*reserveSpace(expr, 1) = RPN_HRAM;
|
*reserveSpace(expr, 1) = RPN_HRAM;
|
||||||
} else if (expr->val >= 0xFF00 && expr->val <= 0xFFFF) {
|
} else if (expr->val >= 0xFF00 && expr->val <= 0xFFFF) {
|
||||||
// That range is valid, but only keep the lower byte
|
/* That range is valid, but only keep the lower byte */
|
||||||
expr->val &= 0xFF;
|
expr->val &= 0xFF;
|
||||||
} else if (expr->val < 0 || expr->val > 0xFF) {
|
} else if (expr->val < 0 || expr->val > 0xFF) {
|
||||||
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->val);
|
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->val);
|
||||||
@@ -254,10 +262,10 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src)
|
|||||||
*expr = *src;
|
*expr = *src;
|
||||||
|
|
||||||
if (rpn_isKnown(expr)) {
|
if (rpn_isKnown(expr)) {
|
||||||
// A valid RST address must be masked with 0x38
|
/* A valid RST address must be masked with 0x38 */
|
||||||
if (expr->val & ~0x38)
|
if (expr->val & ~0x38)
|
||||||
error("Invalid address $%" PRIx32 " for RST\n", expr->val);
|
error("Invalid address $%" PRIx32 " for RST\n", expr->val);
|
||||||
// The target is in the "0x38" bits, all other bits are set
|
/* The target is in the "0x38" bits, all other bits are set */
|
||||||
expr->val |= 0xC7;
|
expr->val |= 0xC7;
|
||||||
} else {
|
} else {
|
||||||
expr->rpnPatchSize++;
|
expr->rpnPatchSize++;
|
||||||
@@ -265,7 +273,9 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
/*
|
||||||
|
* Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||||
|
*/
|
||||||
void rpn_CheckNBit(struct Expression const *expr, uint8_t n)
|
void rpn_CheckNBit(struct Expression const *expr, uint8_t n)
|
||||||
{
|
{
|
||||||
assert(n != 0); // That doesn't make sense
|
assert(n != 0); // That doesn't make sense
|
||||||
@@ -312,7 +322,7 @@ struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
|
|||||||
|
|
||||||
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym)
|
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym)
|
||||||
{
|
{
|
||||||
// Check if both expressions only refer to a single symbol
|
/* Check if both expressions only refer to a single symbol */
|
||||||
struct Symbol const *sym1 = rpn_SymbolOf(src);
|
struct Symbol const *sym1 = rpn_SymbolOf(src);
|
||||||
|
|
||||||
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
|
if (!sym1 || !sym || sym1->type != SYM_LABEL || sym->type != SYM_LABEL)
|
||||||
@@ -329,7 +339,7 @@ static bool isDiffConstant(struct Expression const *src1,
|
|||||||
return rpn_IsDiffConstant(src1, rpn_SymbolOf(src2));
|
return rpn_IsDiffConstant(src1, rpn_SymbolOf(src2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Attempts to compute a constant binary AND from non-constant operands
|
* Attempts to compute a constant binary AND from non-constant operands
|
||||||
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
* This is possible if one operand is a symbol belonging to an `ALIGN[N]` section, and the other is
|
||||||
* a constant that only keeps (some of) the lower N bits.
|
* a constant that only keeps (some of) the lower N bits.
|
||||||
@@ -375,12 +385,12 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
expr->isSymbol = false;
|
expr->isSymbol = false;
|
||||||
int32_t constMaskVal;
|
int32_t constMaskVal;
|
||||||
|
|
||||||
// First, check if the expression is known
|
/* First, check if the expression is known */
|
||||||
expr->isKnown = src1->isKnown && src2->isKnown;
|
expr->isKnown = src1->isKnown && src2->isKnown;
|
||||||
if (expr->isKnown) {
|
if (expr->isKnown) {
|
||||||
rpn_Init(expr); // Init the expression to something sane
|
rpn_Init(expr); /* Init the expression to something sane */
|
||||||
|
|
||||||
// If both expressions are known, just compute the value
|
/* If both expressions are known, just compute the value */
|
||||||
uint32_t uleft = src1->val, uright = src2->val;
|
uint32_t uleft = src1->val, uright = src2->val;
|
||||||
|
|
||||||
switch (op) {
|
switch (op) {
|
||||||
@@ -525,9 +535,9 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
expr->val = constMaskVal;
|
expr->val = constMaskVal;
|
||||||
expr->isKnown = true;
|
expr->isKnown = true;
|
||||||
} else {
|
} else {
|
||||||
// If it's not known, start computing the RPN expression
|
/* If it's not known, start computing the RPN expression */
|
||||||
|
|
||||||
// Convert the left-hand expression if it's constant
|
/* Convert the left-hand expression if it's constant */
|
||||||
if (src1->isKnown) {
|
if (src1->isKnown) {
|
||||||
uint32_t lval = src1->val;
|
uint32_t lval = src1->val;
|
||||||
uint8_t bytes[] = {RPN_CONST, lval, lval >> 8,
|
uint8_t bytes[] = {RPN_CONST, lval, lval >> 8,
|
||||||
@@ -539,11 +549,11 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
memcpy(reserveSpace(expr, sizeof(bytes)), bytes,
|
memcpy(reserveSpace(expr, sizeof(bytes)), bytes,
|
||||||
sizeof(bytes));
|
sizeof(bytes));
|
||||||
|
|
||||||
// Use the other expression's un-const reason
|
/* Use the other expression's un-const reason */
|
||||||
expr->reason = src2->reason;
|
expr->reason = src2->reason;
|
||||||
free(src1->reason);
|
free(src1->reason);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise just reuse its RPN buffer
|
/* Otherwise just reuse its RPN buffer */
|
||||||
expr->rpnPatchSize = src1->rpnPatchSize;
|
expr->rpnPatchSize = src1->rpnPatchSize;
|
||||||
expr->rpn = src1->rpn;
|
expr->rpn = src1->rpn;
|
||||||
expr->rpnCapacity = src1->rpnCapacity;
|
expr->rpnCapacity = src1->rpnCapacity;
|
||||||
@@ -552,12 +562,12 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
free(src2->reason);
|
free(src2->reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, merge the right expression into the left one
|
/* Now, merge the right expression into the left one */
|
||||||
uint8_t *ptr = src2->rpn; // Pointer to the right RPN
|
uint8_t *ptr = src2->rpn; /* Pointer to the right RPN */
|
||||||
uint32_t len = src2->rpnLength; // Size of the right RPN
|
uint32_t len = src2->rpnLength; /* Size of the right RPN */
|
||||||
uint32_t patchSize = src2->rpnPatchSize;
|
uint32_t patchSize = src2->rpnPatchSize;
|
||||||
|
|
||||||
// If the right expression is constant, merge a shim instead
|
/* If the right expression is constant, merge a shim instead */
|
||||||
uint32_t rval = src2->val;
|
uint32_t rval = src2->val;
|
||||||
uint8_t bytes[] = {RPN_CONST, rval, rval >> 8, rval >> 16,
|
uint8_t bytes[] = {RPN_CONST, rval, rval >> 8, rval >> 16,
|
||||||
rval >> 24};
|
rval >> 24};
|
||||||
@@ -566,13 +576,13 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
len = sizeof(bytes);
|
len = sizeof(bytes);
|
||||||
patchSize = sizeof(bytes);
|
patchSize = sizeof(bytes);
|
||||||
}
|
}
|
||||||
// Copy the right RPN and append the operator
|
/* Copy the right RPN and append the operator */
|
||||||
uint8_t *buf = reserveSpace(expr, len + 1);
|
uint8_t *buf = reserveSpace(expr, len + 1);
|
||||||
|
|
||||||
memcpy(buf, ptr, len);
|
memcpy(buf, ptr, len);
|
||||||
buf[len] = op;
|
buf[len] = op;
|
||||||
|
|
||||||
free(src2->rpn); // If there was none, this is `free(NULL)`
|
free(src2->rpn); /* If there was none, this is `free(NULL)` */
|
||||||
expr->rpnPatchSize += patchSize + 1;
|
expr->rpnPatchSize += patchSize + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -23,7 +16,6 @@
|
|||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "linkdefs.h"
|
|
||||||
#include "platform.h" // strdup
|
#include "platform.h" // strdup
|
||||||
|
|
||||||
uint8_t fillByte;
|
uint8_t fillByte;
|
||||||
@@ -37,7 +29,7 @@ struct UnionStackEntry {
|
|||||||
struct SectionStackEntry {
|
struct SectionStackEntry {
|
||||||
struct Section *section;
|
struct Section *section;
|
||||||
struct Section *loadSection;
|
struct Section *loadSection;
|
||||||
char const *scope; // Section's symbol scope
|
char const *scope; /* Section's symbol scope */
|
||||||
uint32_t offset;
|
uint32_t offset;
|
||||||
int32_t loadOffset;
|
int32_t loadOffset;
|
||||||
struct UnionStackEntry *unionStack;
|
struct UnionStackEntry *unionStack;
|
||||||
@@ -45,12 +37,14 @@ struct SectionStackEntry {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct SectionStackEntry *sectionStack;
|
struct SectionStackEntry *sectionStack;
|
||||||
uint32_t curOffset; // Offset into the current section (see sect_GetSymbolOffset)
|
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */
|
||||||
struct Section *currentSection = NULL;
|
struct Section *currentSection = NULL;
|
||||||
static struct Section *currentLoadSection = NULL;
|
static struct Section *currentLoadSection = NULL;
|
||||||
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
|
int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */
|
||||||
|
|
||||||
// A quick check to see if we have an initialized section
|
/*
|
||||||
|
* A quick check to see if we have an initialized section
|
||||||
|
*/
|
||||||
attr_(warn_unused_result) static bool checksection(void)
|
attr_(warn_unused_result) static bool checksection(void)
|
||||||
{
|
{
|
||||||
if (currentSection)
|
if (currentSection)
|
||||||
@@ -60,8 +54,10 @@ attr_(warn_unused_result) static bool checksection(void)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A quick check to see if we have an initialized section that can contain
|
/*
|
||||||
// this much initialized data
|
* A quick check to see if we have an initialized section that can contain
|
||||||
|
* this much initialized data
|
||||||
|
*/
|
||||||
attr_(warn_unused_result) static bool checkcodesection(void)
|
attr_(warn_unused_result) static bool checkcodesection(void)
|
||||||
{
|
{
|
||||||
if (!checksection())
|
if (!checksection())
|
||||||
@@ -77,7 +73,7 @@ attr_(warn_unused_result) static bool checkcodesection(void)
|
|||||||
|
|
||||||
attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sect, uint32_t size)
|
attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sect, uint32_t size)
|
||||||
{
|
{
|
||||||
uint32_t maxSize = sectionTypeInfo[sect->type].size;
|
uint32_t maxSize = maxsize[sect->type];
|
||||||
|
|
||||||
// If the new size is reasonable, keep going
|
// If the new size is reasonable, keep going
|
||||||
if (size <= maxSize)
|
if (size <= maxSize)
|
||||||
@@ -88,13 +84,17 @@ attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sec
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the section has grown too much.
|
/*
|
||||||
|
* Check if the section has grown too much.
|
||||||
|
*/
|
||||||
attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
|
attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
|
||||||
{
|
{
|
||||||
// This check is here to trap broken code that generates sections that are too big and to
|
/*
|
||||||
// prevent the assembler from generating huge object files or trying to allocate too much
|
* This check is here to trap broken code that generates sections that are too big and to
|
||||||
// memory.
|
* prevent the assembler from generating huge object files or trying to allocate too much
|
||||||
// A check at the linking stage is still necessary.
|
* memory.
|
||||||
|
* A check at the linking stage is still necessary.
|
||||||
|
*/
|
||||||
|
|
||||||
// If the section has already overflowed, skip the check to avoid erroring out ad nauseam
|
// If the section has already overflowed, skip the check to avoid erroring out ad nauseam
|
||||||
if (currentSection->size != UINT32_MAX
|
if (currentSection->size != UINT32_MAX
|
||||||
@@ -132,13 +132,15 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
|
|||||||
assert(alignment < 16); // Should be ensured by the caller
|
assert(alignment < 16); // Should be ensured by the caller
|
||||||
unsigned int nbSectErrors = 0;
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
/*
|
||||||
// combination of both.
|
* Unionized sections only need "compatible" constraints, and they end up with the strictest
|
||||||
|
* combination of both.
|
||||||
|
*/
|
||||||
if (sect_HasData(type))
|
if (sect_HasData(type))
|
||||||
fail("Cannot declare ROM sections as UNION\n");
|
fail("Cannot declare ROM sections as UNION\n");
|
||||||
|
|
||||||
if (org != (uint32_t)-1) {
|
if (org != (uint32_t)-1) {
|
||||||
// If both are fixed, they must be the same
|
/* If both are fixed, they must be the same */
|
||||||
if (sect->org != (uint32_t)-1 && sect->org != org)
|
if (sect->org != (uint32_t)-1 && sect->org != org)
|
||||||
fail("Section already declared as fixed at different address $%04"
|
fail("Section already declared as fixed at different address $%04"
|
||||||
PRIx32 "\n", sect->org);
|
PRIx32 "\n", sect->org);
|
||||||
@@ -146,16 +148,16 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
|
|||||||
fail("Section already declared as aligned to %u bytes (offset %"
|
fail("Section already declared as aligned to %u bytes (offset %"
|
||||||
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
|
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
|
||||||
else
|
else
|
||||||
// Otherwise, just override
|
/* Otherwise, just override */
|
||||||
sect->org = org;
|
sect->org = org;
|
||||||
|
|
||||||
} else if (alignment != 0) {
|
} else if (alignment != 0) {
|
||||||
// Make sure any fixed address given is compatible
|
/* Make sure any fixed address given is compatible */
|
||||||
if (sect->org != (uint32_t)-1) {
|
if (sect->org != (uint32_t)-1) {
|
||||||
if ((sect->org - alignOffset) & mask(alignment))
|
if ((sect->org - alignOffset) & mask(alignment))
|
||||||
fail("Section already declared as fixed at incompatible address $%04"
|
fail("Section already declared as fixed at incompatible address $%04"
|
||||||
PRIx32 "\n", sect->org);
|
PRIx32 "\n", sect->org);
|
||||||
// Check if alignment offsets are compatible
|
/* Check if alignment offsets are compatible */
|
||||||
} else if ((alignOffset & mask(sect->align))
|
} else if ((alignOffset & mask(sect->align))
|
||||||
!= (sect->alignOfs & mask(alignment))) {
|
!= (sect->alignOfs & mask(alignment))) {
|
||||||
fail("Section already declared with incompatible %u"
|
fail("Section already declared with incompatible %u"
|
||||||
@@ -178,13 +180,15 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
|
|||||||
assert(alignment < 16); // Should be ensured by the caller
|
assert(alignment < 16); // Should be ensured by the caller
|
||||||
unsigned int nbSectErrors = 0;
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
// Fragments only need "compatible" constraints, and they end up with the strictest
|
/*
|
||||||
// combination of both.
|
* Fragments only need "compatible" constraints, and they end up with the strictest
|
||||||
// The merging is however performed at the *end* of the original section!
|
* combination of both.
|
||||||
|
* The merging is however performed at the *end* of the original section!
|
||||||
|
*/
|
||||||
if (org != (uint32_t)-1) {
|
if (org != (uint32_t)-1) {
|
||||||
uint16_t curOrg = org - sect->size;
|
uint16_t curOrg = org - sect->size;
|
||||||
|
|
||||||
// If both are fixed, they must be the same
|
/* If both are fixed, they must be the same */
|
||||||
if (sect->org != (uint32_t)-1 && sect->org != curOrg)
|
if (sect->org != (uint32_t)-1 && sect->org != curOrg)
|
||||||
fail("Section already declared as fixed at incompatible address $%04"
|
fail("Section already declared as fixed at incompatible address $%04"
|
||||||
PRIx32 " (cur addr = %04" PRIx32 ")\n",
|
PRIx32 " (cur addr = %04" PRIx32 ")\n",
|
||||||
@@ -193,7 +197,7 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
|
|||||||
fail("Section already declared as aligned to %u bytes (offset %"
|
fail("Section already declared as aligned to %u bytes (offset %"
|
||||||
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
|
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
|
||||||
else
|
else
|
||||||
// Otherwise, just override
|
/* Otherwise, just override */
|
||||||
sect->org = curOrg;
|
sect->org = curOrg;
|
||||||
|
|
||||||
} else if (alignment != 0) {
|
} else if (alignment != 0) {
|
||||||
@@ -202,12 +206,12 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
|
|||||||
if (curOfs < 0)
|
if (curOfs < 0)
|
||||||
curOfs += 1U << alignment;
|
curOfs += 1U << alignment;
|
||||||
|
|
||||||
// Make sure any fixed address given is compatible
|
/* Make sure any fixed address given is compatible */
|
||||||
if (sect->org != (uint32_t)-1) {
|
if (sect->org != (uint32_t)-1) {
|
||||||
if ((sect->org - curOfs) & mask(alignment))
|
if ((sect->org - curOfs) & mask(alignment))
|
||||||
fail("Section already declared as fixed at incompatible address $%04"
|
fail("Section already declared as fixed at incompatible address $%04"
|
||||||
PRIx32 "\n", sect->org);
|
PRIx32 "\n", sect->org);
|
||||||
// Check if alignment offsets are compatible
|
/* Check if alignment offsets are compatible */
|
||||||
} else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) {
|
} else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) {
|
||||||
fail("Section already declared with incompatible %u"
|
fail("Section already declared with incompatible %u"
|
||||||
"-byte alignment (offset %" PRIu16 ")\n",
|
"-byte alignment (offset %" PRIu16 ")\n",
|
||||||
@@ -228,7 +232,7 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
|
|||||||
unsigned int nbSectErrors = 0;
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
if (type != sect->type)
|
if (type != sect->type)
|
||||||
fail("Section already exists but with type %s\n", sectionTypeInfo[sect->type].name);
|
fail("Section already exists but with type %s\n", typeNames[sect->type]);
|
||||||
|
|
||||||
if (sect->modifier != mod) {
|
if (sect->modifier != mod) {
|
||||||
fail("Section already declared as %s section\n", sectionModNames[sect->modifier]);
|
fail("Section already declared as %s section\n", sectionModNames[sect->modifier]);
|
||||||
@@ -241,10 +245,10 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
|
|||||||
|
|
||||||
// Common checks
|
// Common checks
|
||||||
|
|
||||||
// If the section's bank is unspecified, override it
|
/* If the section's bank is unspecified, override it */
|
||||||
if (sect->bank == (uint32_t)-1)
|
if (sect->bank == (uint32_t)-1)
|
||||||
sect->bank = bank;
|
sect->bank = bank;
|
||||||
// If both specify a bank, it must be the same one
|
/* If both specify a bank, it must be the same one */
|
||||||
else if (bank != (uint32_t)-1 && sect->bank != bank)
|
else if (bank != (uint32_t)-1 && sect->bank != bank)
|
||||||
fail("Section already declared with different bank %" PRIu32 "\n",
|
fail("Section already declared with different bank %" PRIu32 "\n",
|
||||||
sect->bank);
|
sect->bank);
|
||||||
@@ -265,7 +269,9 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
|
|||||||
|
|
||||||
#undef fail
|
#undef fail
|
||||||
|
|
||||||
// Create a new section, not yet in the list.
|
/*
|
||||||
|
* Create a new section, not yet in the list.
|
||||||
|
*/
|
||||||
static struct Section *createSection(char const *name, enum SectionType type,
|
static struct Section *createSection(char const *name, enum SectionType type,
|
||||||
uint32_t org, uint32_t bank, uint8_t alignment,
|
uint32_t org, uint32_t bank, uint8_t alignment,
|
||||||
uint16_t alignOffset, enum SectionModifier mod)
|
uint16_t alignOffset, enum SectionModifier mod)
|
||||||
@@ -291,9 +297,9 @@ static struct Section *createSection(char const *name, enum SectionType type,
|
|||||||
sect->next = NULL;
|
sect->next = NULL;
|
||||||
sect->patches = NULL;
|
sect->patches = NULL;
|
||||||
|
|
||||||
// It is only needed to allocate memory for ROM sections.
|
/* It is only needed to allocate memory for ROM sections. */
|
||||||
if (sect_HasData(type)) {
|
if (sect_HasData(type)) {
|
||||||
sect->data = malloc(sectionTypeInfo[type].size);
|
sect->data = malloc(maxsize[type]);
|
||||||
if (sect->data == NULL)
|
if (sect->data == NULL)
|
||||||
fatalerror("Not enough memory for section: %s\n", strerror(errno));
|
fatalerror("Not enough memory for section: %s\n", strerror(errno));
|
||||||
} else {
|
} else {
|
||||||
@@ -303,7 +309,9 @@ static struct Section *createSection(char const *name, enum SectionType type,
|
|||||||
return sect;
|
return sect;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a section by name and type. If it doesn't exist, create it.
|
/*
|
||||||
|
* Find a section by name and type. If it doesn't exist, create it.
|
||||||
|
*/
|
||||||
static struct Section *getSection(char const *name, enum SectionType type, uint32_t org,
|
static struct Section *getSection(char const *name, enum SectionType type, uint32_t org,
|
||||||
struct SectionSpec const *attrs, enum SectionModifier mod)
|
struct SectionSpec const *attrs, enum SectionModifier mod)
|
||||||
{
|
{
|
||||||
@@ -317,13 +325,14 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
|||||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
|
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
|
||||||
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
|
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
|
||||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
||||||
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
|
else if (bank < bankranges[type][0]
|
||||||
|
|| bank > bankranges[type][1])
|
||||||
error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
|
error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
|
||||||
PRIx32 ")\n", sectionTypeInfo[type].name, bank,
|
PRIx32 ")\n", typeNames[type], bank,
|
||||||
sectionTypeInfo[type].firstBank, sectionTypeInfo[type].lastBank);
|
bankranges[type][0], bankranges[type][1]);
|
||||||
} else if (nbbanks(type) == 1) {
|
} else if (nbbanks(type) == 1) {
|
||||||
// If the section type only has a single bank, implicitly force it
|
// If the section type only has a single bank, implicitly force it
|
||||||
bank = sectionTypeInfo[type].firstBank;
|
bank = bankranges[type][0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alignOffset >= 1 << alignment) {
|
if (alignOffset >= 1 << alignment) {
|
||||||
@@ -333,10 +342,10 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (org != (uint32_t)-1) {
|
if (org != (uint32_t)-1) {
|
||||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
|
if (org < startaddr[type] || org > endaddr(type))
|
||||||
error("Section \"%s\"'s fixed address %#" PRIx32
|
error("Section \"%s\"'s fixed address %#" PRIx32
|
||||||
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
|
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
|
||||||
name, org, sectionTypeInfo[type].startAddr, endaddr(type));
|
name, org, startaddr[type], endaddr(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alignment != 0) {
|
if (alignment != 0) {
|
||||||
@@ -344,18 +353,18 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
|||||||
error("Alignment must be between 0 and 16, not %u\n", alignment);
|
error("Alignment must be between 0 and 16, not %u\n", alignment);
|
||||||
alignment = 16;
|
alignment = 16;
|
||||||
}
|
}
|
||||||
// It doesn't make sense to have both alignment and org set
|
/* It doesn't make sense to have both alignment and org set */
|
||||||
uint32_t mask = mask(alignment);
|
uint32_t mask = mask(alignment);
|
||||||
|
|
||||||
if (org != (uint32_t)-1) {
|
if (org != (uint32_t)-1) {
|
||||||
if ((org - alignOffset) & mask)
|
if ((org - alignOffset) & mask)
|
||||||
error("Section \"%s\"'s fixed address doesn't match its alignment\n",
|
error("Section \"%s\"'s fixed address doesn't match its alignment\n",
|
||||||
name);
|
name);
|
||||||
alignment = 0; // Ignore it if it's satisfied
|
alignment = 0; /* Ignore it if it's satisfied */
|
||||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
} else if (startaddr[type] & mask) {
|
||||||
error("Section \"%s\"'s alignment cannot be attained in %s\n",
|
error("Section \"%s\"'s alignment cannot be attained in %s\n",
|
||||||
name, sectionTypeInfo[type].name);
|
name, typeNames[type]);
|
||||||
alignment = 0; // Ignore it if it's unattainable
|
alignment = 0; /* Ignore it if it's unattainable */
|
||||||
org = 0;
|
org = 0;
|
||||||
} else if (alignment == 16) {
|
} else if (alignment == 16) {
|
||||||
// Treat an alignment of 16 as being fixed at address 0
|
// Treat an alignment of 16 as being fixed at address 0
|
||||||
@@ -381,7 +390,9 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
|||||||
return sect;
|
return sect;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current section
|
/*
|
||||||
|
* Set the current section
|
||||||
|
*/
|
||||||
static void changeSection(void)
|
static void changeSection(void)
|
||||||
{
|
{
|
||||||
if (unionStack)
|
if (unionStack)
|
||||||
@@ -390,7 +401,9 @@ static void changeSection(void)
|
|||||||
sym_SetCurrentSymbolScope(NULL);
|
sym_SetCurrentSymbolScope(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current section by name and type
|
/*
|
||||||
|
* Set the current section by name and type
|
||||||
|
*/
|
||||||
void sect_NewSection(char const *name, uint32_t type, uint32_t org,
|
void sect_NewSection(char const *name, uint32_t type, uint32_t org,
|
||||||
struct SectionSpec const *attribs, enum SectionModifier mod)
|
struct SectionSpec const *attribs, enum SectionModifier mod)
|
||||||
{
|
{
|
||||||
@@ -410,7 +423,9 @@ void sect_NewSection(char const *name, uint32_t type, uint32_t org,
|
|||||||
currentSection = sect;
|
currentSection = sect;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current section by name and type
|
/*
|
||||||
|
* Set the current section by name and type
|
||||||
|
*/
|
||||||
void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org,
|
void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org,
|
||||||
struct SectionSpec const *attribs, enum SectionModifier mod)
|
struct SectionSpec const *attribs, enum SectionModifier mod)
|
||||||
{
|
{
|
||||||
@@ -463,7 +478,9 @@ struct Section *sect_GetSymbolSection(void)
|
|||||||
return currentLoadSection ? currentLoadSection : currentSection;
|
return currentLoadSection ? currentLoadSection : currentSection;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The offset into the section above
|
/*
|
||||||
|
* The offset into the section above
|
||||||
|
*/
|
||||||
uint32_t sect_GetSymbolOffset(void)
|
uint32_t sect_GetSymbolOffset(void)
|
||||||
{
|
{
|
||||||
return curOffset;
|
return curOffset;
|
||||||
@@ -598,7 +615,9 @@ void sect_CheckUnionClosed(void)
|
|||||||
error("Unterminated UNION construct!\n");
|
error("Unterminated UNION construct!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output an absolute byte
|
/*
|
||||||
|
* Output an absolute byte
|
||||||
|
*/
|
||||||
void sect_AbsByte(uint8_t b)
|
void sect_AbsByte(uint8_t b)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -642,7 +661,9 @@ void sect_AbsLongGroup(uint8_t const *s, size_t length)
|
|||||||
writelong(*s++);
|
writelong(*s++);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip this many bytes
|
/*
|
||||||
|
* Skip this many bytes
|
||||||
|
*/
|
||||||
void sect_Skip(uint32_t skip, bool ds)
|
void sect_Skip(uint32_t skip, bool ds)
|
||||||
{
|
{
|
||||||
if (!checksection())
|
if (!checksection())
|
||||||
@@ -662,7 +683,9 @@ void sect_Skip(uint32_t skip, bool ds)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a NULL terminated string (excluding the NULL-character)
|
/*
|
||||||
|
* Output a NULL terminated string (excluding the NULL-character)
|
||||||
|
*/
|
||||||
void sect_String(char const *s)
|
void sect_String(char const *s)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -674,8 +697,10 @@ void sect_String(char const *s)
|
|||||||
writebyte(*s++);
|
writebyte(*s++);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a relocatable byte. Checking will be done to see if it
|
/*
|
||||||
// is an absolute value in disguise.
|
* Output a relocatable byte. Checking will be done to see if it
|
||||||
|
* is an absolute value in disguise.
|
||||||
|
*/
|
||||||
void sect_RelByte(struct Expression *expr, uint32_t pcShift)
|
void sect_RelByte(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -692,8 +717,10 @@ void sect_RelByte(struct Expression *expr, uint32_t pcShift)
|
|||||||
rpn_Free(expr);
|
rpn_Free(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output several copies of a relocatable byte. Checking will be done to see if
|
/*
|
||||||
// it is an absolute value in disguise.
|
* Output several copies of a relocatable byte. Checking will be done to see if
|
||||||
|
* it is an absolute value in disguise.
|
||||||
|
*/
|
||||||
void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
|
void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -716,8 +743,10 @@ void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
|
|||||||
rpn_Free(&exprs[i]);
|
rpn_Free(&exprs[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a relocatable word. Checking will be done to see if
|
/*
|
||||||
// it's an absolute value in disguise.
|
* Output a relocatable word. Checking will be done to see if
|
||||||
|
* it's an absolute value in disguise.
|
||||||
|
*/
|
||||||
void sect_RelWord(struct Expression *expr, uint32_t pcShift)
|
void sect_RelWord(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -734,8 +763,10 @@ void sect_RelWord(struct Expression *expr, uint32_t pcShift)
|
|||||||
rpn_Free(expr);
|
rpn_Free(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a relocatable longword. Checking will be done to see if
|
/*
|
||||||
// is an absolute value in disguise.
|
* Output a relocatable longword. Checking will be done to see if
|
||||||
|
* is an absolute value in disguise.
|
||||||
|
*/
|
||||||
void sect_RelLong(struct Expression *expr, uint32_t pcShift)
|
void sect_RelLong(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -752,8 +783,10 @@ void sect_RelLong(struct Expression *expr, uint32_t pcShift)
|
|||||||
rpn_Free(expr);
|
rpn_Free(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a PC-relative relocatable byte. Checking will be done to see if it
|
/*
|
||||||
// is an absolute value in disguise.
|
* Output a PC-relative relocatable byte. Checking will be done to see if it
|
||||||
|
* is an absolute value in disguise.
|
||||||
|
*/
|
||||||
void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
|
void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
@@ -767,12 +800,12 @@ void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
|
|||||||
writebyte(0);
|
writebyte(0);
|
||||||
} else {
|
} else {
|
||||||
struct Symbol const *sym = rpn_SymbolOf(expr);
|
struct Symbol const *sym = rpn_SymbolOf(expr);
|
||||||
// The offset wraps (jump from ROM to HRAM, for example)
|
/* The offset wraps (jump from ROM to HRAM, for example) */
|
||||||
int16_t offset;
|
int16_t offset;
|
||||||
|
|
||||||
// Offset is relative to the byte *after* the operand
|
/* Offset is relative to the byte *after* the operand */
|
||||||
if (sym == pc)
|
if (sym == pc)
|
||||||
offset = -2; // PC as operand to `jr` is lower than reference PC by 2
|
offset = -2; /* PC as operand to `jr` is lower than reference PC by 2 */
|
||||||
else
|
else
|
||||||
offset = sym_GetValue(sym) - (sym_GetValue(pc) + 1);
|
offset = sym_GetValue(sym) - (sym_GetValue(pc) + 1);
|
||||||
|
|
||||||
@@ -787,7 +820,9 @@ void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
|
|||||||
rpn_Free(expr);
|
rpn_Free(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output a binary file
|
/*
|
||||||
|
* Output a binary file
|
||||||
|
*/
|
||||||
void sect_BinaryFile(char const *s, int32_t startPos)
|
void sect_BinaryFile(char const *s, int32_t startPos)
|
||||||
{
|
{
|
||||||
if (startPos < 0) {
|
if (startPos < 0) {
|
||||||
@@ -834,7 +869,7 @@ void sect_BinaryFile(char const *s, int32_t startPos)
|
|||||||
if (errno != ESPIPE)
|
if (errno != ESPIPE)
|
||||||
error("Error determining size of INCBIN file '%s': %s\n",
|
error("Error determining size of INCBIN file '%s': %s\n",
|
||||||
s, strerror(errno));
|
s, strerror(errno));
|
||||||
// The file isn't seekable, so we'll just skip bytes
|
/* The file isn't seekable, so we'll just skip bytes */
|
||||||
while (startPos--)
|
while (startPos--)
|
||||||
(void)fgetc(f);
|
(void)fgetc(f);
|
||||||
}
|
}
|
||||||
@@ -866,7 +901,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
|||||||
|
|
||||||
if (!checkcodesection())
|
if (!checkcodesection())
|
||||||
return;
|
return;
|
||||||
if (length == 0) // Don't even bother with 0-byte slices
|
if (length == 0) /* Don't even bother with 0-byte slices */
|
||||||
return;
|
return;
|
||||||
if (!reserveSpace(length))
|
if (!reserveSpace(length))
|
||||||
return;
|
return;
|
||||||
@@ -911,7 +946,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
|||||||
if (errno != ESPIPE)
|
if (errno != ESPIPE)
|
||||||
error("Error determining size of INCBIN file '%s': %s\n",
|
error("Error determining size of INCBIN file '%s': %s\n",
|
||||||
s, strerror(errno));
|
s, strerror(errno));
|
||||||
// The file isn't seekable, so we'll just skip bytes
|
/* The file isn't seekable, so we'll just skip bytes */
|
||||||
while (start_pos--)
|
while (start_pos--)
|
||||||
(void)fgetc(f);
|
(void)fgetc(f);
|
||||||
}
|
}
|
||||||
@@ -933,7 +968,9 @@ cleanup:
|
|||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section stack routines
|
/*
|
||||||
|
* Section stack routines
|
||||||
|
*/
|
||||||
void sect_PushSection(void)
|
void sect_PushSection(void)
|
||||||
{
|
{
|
||||||
struct SectionStackEntry *entry = malloc(sizeof(*entry));
|
struct SectionStackEntry *entry = malloc(sizeof(*entry));
|
||||||
|
|||||||
186
src/asm/symbol.c
186
src/asm/symbol.c
@@ -6,7 +6,9 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Symboltable and macroargs stuff
|
/*
|
||||||
|
* Symboltable and macroargs stuff
|
||||||
|
*/
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -34,7 +36,7 @@
|
|||||||
|
|
||||||
HashMap symbols;
|
HashMap symbols;
|
||||||
|
|
||||||
static const char *labelScope; // Current section's label scope
|
static char const *labelScope; /* Current section's label scope */
|
||||||
static struct Symbol *PCSymbol;
|
static struct Symbol *PCSymbol;
|
||||||
static char savedTIME[256];
|
static char savedTIME[256];
|
||||||
static char savedDATE[256];
|
static char savedDATE[256];
|
||||||
@@ -83,34 +85,36 @@ static int32_t Callback__LINE__(void)
|
|||||||
|
|
||||||
static char const *Callback__FILE__(void)
|
static char const *Callback__FILE__(void)
|
||||||
{
|
{
|
||||||
// FIXME: this is dangerous, and here's why this is CURRENTLY okay. It's still bad, fix it.
|
/*
|
||||||
// There are only two call sites for this; one copies the contents directly, the other is
|
* FIXME: this is dangerous, and here's why this is CURRENTLY okay. It's still bad, fix it.
|
||||||
// EQUS expansions, which cannot straddle file boundaries. So this should be fine.
|
* There are only two call sites for this; one copies the contents directly, the other is
|
||||||
|
* EQUS expansions, which cannot straddle file boundaries. So this should be fine.
|
||||||
|
*/
|
||||||
static char *buf = NULL;
|
static char *buf = NULL;
|
||||||
static size_t bufsize = 0;
|
static size_t bufsize = 0;
|
||||||
char const *fileName = fstk_GetFileName();
|
char const *fileName = fstk_GetFileName();
|
||||||
size_t j = 1;
|
size_t j = 1;
|
||||||
|
|
||||||
assert(fileName[0]);
|
assert(fileName[0]);
|
||||||
// The assertion above ensures the loop runs at least once
|
/* The assertion above ensures the loop runs at least once */
|
||||||
for (size_t i = 0; fileName[i]; i++, j++) {
|
for (size_t i = 0; fileName[i]; i++, j++) {
|
||||||
// Account for the extra backslash inserted below
|
/* Account for the extra backslash inserted below */
|
||||||
if (fileName[i] == '"')
|
if (fileName[i] == '"')
|
||||||
j++;
|
j++;
|
||||||
// Ensure there will be enough room; DO NOT PRINT ANYTHING ABOVE THIS!!
|
/* Ensure there will be enough room; DO NOT PRINT ANYTHING ABOVE THIS!! */
|
||||||
if (j + 2 >= bufsize) { // Always keep room for 2 tail chars
|
if (j + 2 >= bufsize) { /* Always keep room for 2 tail chars */
|
||||||
bufsize = bufsize ? bufsize * 2 : 64;
|
bufsize = bufsize ? bufsize * 2 : 64;
|
||||||
buf = realloc(buf, bufsize);
|
buf = realloc(buf, bufsize);
|
||||||
if (!buf)
|
if (!buf)
|
||||||
fatalerror("Failed to grow buffer for file name: %s\n",
|
fatalerror("Failed to grow buffer for file name: %s\n",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
}
|
}
|
||||||
// Escape quotes, since we're returning a string
|
/* Escape quotes, since we're returning a string */
|
||||||
if (fileName[i] == '"')
|
if (fileName[i] == '"')
|
||||||
buf[j - 1] = '\\';
|
buf[j - 1] = '\\';
|
||||||
buf[j] = fileName[i];
|
buf[j] = fileName[i];
|
||||||
}
|
}
|
||||||
// Write everything after the loop, to ensure the buffer has been allocated
|
/* Write everything after the loop, to ensure the buffer has been allocated */
|
||||||
buf[0] = '"';
|
buf[0] = '"';
|
||||||
buf[j++] = '"';
|
buf[j++] = '"';
|
||||||
buf[j] = '\0';
|
buf[j] = '\0';
|
||||||
@@ -124,14 +128,16 @@ static int32_t CallbackPC(void)
|
|||||||
return section ? section->org + sect_GetSymbolOffset() : 0;
|
return section ? section->org + sect_GetSymbolOffset() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the value field of a symbol
|
/*
|
||||||
|
* Get the value field of a symbol
|
||||||
|
*/
|
||||||
int32_t sym_GetValue(struct Symbol const *sym)
|
int32_t sym_GetValue(struct Symbol const *sym)
|
||||||
{
|
{
|
||||||
if (sym_IsNumeric(sym) && sym->hasCallback)
|
if (sym_IsNumeric(sym) && sym->hasCallback)
|
||||||
return sym->numCallback();
|
return sym->numCallback();
|
||||||
|
|
||||||
if (sym->type == SYM_LABEL)
|
if (sym->type == SYM_LABEL)
|
||||||
// TODO: do not use section's org directly
|
/* TODO: do not use section's org directly */
|
||||||
return sym->value + sym_GetSection(sym)->org;
|
return sym->value + sym_GetSection(sym)->org;
|
||||||
|
|
||||||
return sym->value;
|
return sym->value;
|
||||||
@@ -147,26 +153,32 @@ static void dumpFilename(struct Symbol const *sym)
|
|||||||
fputs("<builtin>", stderr);
|
fputs("<builtin>", stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a symbol's definition filename and line
|
/*
|
||||||
|
* Set a symbol's definition filename and line
|
||||||
|
*/
|
||||||
static void setSymbolFilename(struct Symbol *sym)
|
static void setSymbolFilename(struct Symbol *sym)
|
||||||
{
|
{
|
||||||
sym->src = fstk_GetFileStack();
|
sym->src = fstk_GetFileStack();
|
||||||
sym->fileLine = sym->src ? lexer_GetLineNo() : 0; // This is (NULL, 1) for built-ins
|
sym->fileLine = sym->src ? lexer_GetLineNo() : 0; // This is (NULL, 1) for built-ins
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update a symbol's definition filename and line
|
/*
|
||||||
|
* Update a symbol's definition filename and line
|
||||||
|
*/
|
||||||
static void updateSymbolFilename(struct Symbol *sym)
|
static void updateSymbolFilename(struct Symbol *sym)
|
||||||
{
|
{
|
||||||
struct FileStackNode *oldSrc = sym->src;
|
struct FileStackNode *oldSrc = sym->src;
|
||||||
|
|
||||||
setSymbolFilename(sym);
|
setSymbolFilename(sym);
|
||||||
// If the old node was referenced, ensure the new one is
|
/* If the old node was referenced, ensure the new one is */
|
||||||
if (oldSrc && oldSrc->referenced && oldSrc->ID != (uint32_t)-1)
|
if (oldSrc && oldSrc->referenced && oldSrc->ID != (uint32_t)-1)
|
||||||
out_RegisterNode(sym->src);
|
out_RegisterNode(sym->src);
|
||||||
// TODO: unref the old node, and use `out_ReplaceNode` instead of deleting it
|
/* TODO: unref the old node, and use `out_ReplaceNode` instead of deleting it */
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new symbol by name
|
/*
|
||||||
|
* Create a new symbol by name
|
||||||
|
*/
|
||||||
static struct Symbol *createsymbol(char const *symName)
|
static struct Symbol *createsymbol(char const *symName)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = malloc(sizeof(*sym));
|
struct Symbol *sym = malloc(sizeof(*sym));
|
||||||
@@ -189,8 +201,10 @@ static struct Symbol *createsymbol(char const *symName)
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates the full name of a local symbol in a given scope, by prepending
|
/*
|
||||||
// the name with the parent symbol's name.
|
* Creates the full name of a local symbol in a given scope, by prepending
|
||||||
|
* the name with the parent symbol's name.
|
||||||
|
*/
|
||||||
static void fullSymbolName(char *output, size_t outputSize,
|
static void fullSymbolName(char *output, size_t outputSize,
|
||||||
char const *localName, char const *scopeName)
|
char const *localName, char const *scopeName)
|
||||||
{
|
{
|
||||||
@@ -236,8 +250,8 @@ struct Symbol *sym_FindScopedSymbol(char const *symName)
|
|||||||
if (strchr(localName + 1, '.'))
|
if (strchr(localName + 1, '.'))
|
||||||
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n",
|
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n",
|
||||||
symName);
|
symName);
|
||||||
// If auto-scoped local label, expand the name
|
/* If auto-scoped local label, expand the name */
|
||||||
if (localName == symName) { // Meaning, the name begins with the dot
|
if (localName == symName) { /* Meaning, the name begins with the dot */
|
||||||
char fullName[MAXSYMLEN + 1];
|
char fullName[MAXSYMLEN + 1];
|
||||||
|
|
||||||
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
|
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
|
||||||
@@ -257,7 +271,9 @@ static bool isReferenced(struct Symbol const *sym)
|
|||||||
return sym->ID != (uint32_t)-1;
|
return sym->ID != (uint32_t)-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge a symbol
|
/*
|
||||||
|
* Purge a symbol
|
||||||
|
*/
|
||||||
void sym_Purge(char const *symName)
|
void sym_Purge(char const *symName)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
||||||
@@ -269,14 +285,16 @@ void sym_Purge(char const *symName)
|
|||||||
} else if (isReferenced(sym)) {
|
} else if (isReferenced(sym)) {
|
||||||
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName);
|
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName);
|
||||||
} else {
|
} else {
|
||||||
// Do not keep a reference to the label's name after purging it
|
/* Do not keep a reference to the label's name after purging it */
|
||||||
if (sym->name == labelScope)
|
if (sym->name == labelScope)
|
||||||
sym_SetCurrentSymbolScope(NULL);
|
sym_SetCurrentSymbolScope(NULL);
|
||||||
|
|
||||||
// FIXME: this leaks sym->macro for SYM_EQUS and SYM_MACRO, but this can't
|
/*
|
||||||
// free(sym->macro) because the expansion may be purging itself.
|
* FIXME: this leaks sym->macro for SYM_EQUS and SYM_MACRO, but this can't
|
||||||
|
* free(sym->macro) because the expansion may be purging itself.
|
||||||
|
*/
|
||||||
hash_RemoveElement(symbols, sym->name);
|
hash_RemoveElement(symbols, sym->name);
|
||||||
// TODO: ideally, also unref the file stack nodes
|
/* TODO: ideally, also unref the file stack nodes */
|
||||||
free(sym);
|
free(sym);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,7 +312,9 @@ uint32_t sym_GetPCValue(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a constant symbol's value, assuming it's defined
|
/*
|
||||||
|
* Return a constant symbol's value, assuming it's defined
|
||||||
|
*/
|
||||||
uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
|
uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
|
||||||
{
|
{
|
||||||
if (sym == PCSymbol)
|
if (sym == PCSymbol)
|
||||||
@@ -307,7 +327,9 @@ uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a constant symbol's value
|
/*
|
||||||
|
* Return a constant symbol's value
|
||||||
|
*/
|
||||||
uint32_t sym_GetConstantValue(char const *symName)
|
uint32_t sym_GetConstantValue(char const *symName)
|
||||||
{
|
{
|
||||||
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
||||||
@@ -359,7 +381,9 @@ static struct Symbol *createNonrelocSymbol(char const *symName, bool numeric)
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an equated symbol
|
/*
|
||||||
|
* Add an equated symbol
|
||||||
|
*/
|
||||||
struct Symbol *sym_AddEqu(char const *symName, int32_t value)
|
struct Symbol *sym_AddEqu(char const *symName, int32_t value)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = createNonrelocSymbol(symName, true);
|
struct Symbol *sym = createNonrelocSymbol(symName, true);
|
||||||
@@ -441,14 +465,18 @@ struct Symbol *sym_RedefString(char const *symName, char const *value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateSymbolFilename(sym);
|
updateSymbolFilename(sym);
|
||||||
// FIXME: this leaks the previous sym->macro value, but this can't
|
/*
|
||||||
// free(sym->macro) because the expansion may be redefining itself.
|
* FIXME: this leaks the previous sym->macro value, but this can't
|
||||||
|
* free(sym->macro) because the expansion may be redefining itself.
|
||||||
|
*/
|
||||||
assignStringSymbol(sym, value);
|
assignStringSymbol(sym, value);
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alter a mutable symbol's value
|
/*
|
||||||
|
* Alter a mutable symbol's value
|
||||||
|
*/
|
||||||
struct Symbol *sym_AddVar(char const *symName, int32_t value)
|
struct Symbol *sym_AddVar(char const *symName, int32_t value)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = sym_FindExactSymbol(symName);
|
struct Symbol *sym = sym_FindExactSymbol(symName);
|
||||||
@@ -478,7 +506,7 @@ struct Symbol *sym_AddVar(char const *symName, int32_t value)
|
|||||||
*/
|
*/
|
||||||
static struct Symbol *addLabel(char const *symName)
|
static struct Symbol *addLabel(char const *symName)
|
||||||
{
|
{
|
||||||
assert(symName[0] != '.'); // The symbol name must have been expanded prior
|
assert(symName[0] != '.'); /* The symbol name must have been expanded prior */
|
||||||
struct Symbol *sym = sym_FindExactSymbol(symName);
|
struct Symbol *sym = sym_FindExactSymbol(symName);
|
||||||
|
|
||||||
if (!sym) {
|
if (!sym) {
|
||||||
@@ -491,7 +519,7 @@ static struct Symbol *addLabel(char const *symName)
|
|||||||
} else {
|
} else {
|
||||||
updateSymbolFilename(sym);
|
updateSymbolFilename(sym);
|
||||||
}
|
}
|
||||||
// If the symbol already exists as a ref, just "take over" it
|
/* If the symbol already exists as a ref, just "take over" it */
|
||||||
sym->type = SYM_LABEL;
|
sym->type = SYM_LABEL;
|
||||||
sym->value = sect_GetSymbolOffset();
|
sym->value = sect_GetSymbolOffset();
|
||||||
if (exportall)
|
if (exportall)
|
||||||
@@ -503,41 +531,43 @@ static struct Symbol *addLabel(char const *symName)
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a local (`.name` or `Parent.name`) relocatable symbol
|
/*
|
||||||
|
* Add a local (`.name` or `Parent.name`) relocatable symbol
|
||||||
|
*/
|
||||||
struct Symbol *sym_AddLocalLabel(char const *symName)
|
struct Symbol *sym_AddLocalLabel(char const *symName)
|
||||||
{
|
{
|
||||||
if (!labelScope) {
|
if (!labelScope) {
|
||||||
error("Local label '%s' in main scope\n", symName);
|
error("Local label '%s' in main scope\n", symName);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
assert(!strchr(labelScope, '.')); // Assuming no dots in `labelScope`
|
assert(!strchr(labelScope, '.')); /* Assuming no dots in `labelScope` */
|
||||||
|
|
||||||
char fullName[MAXSYMLEN + 1];
|
char fullName[MAXSYMLEN + 1];
|
||||||
char const *localName = strchr(symName, '.');
|
char const *localName = strchr(symName, '.');
|
||||||
|
|
||||||
assert(localName); // There should be at least one dot in `symName`
|
assert(localName); /* There should be at least one dot in `symName` */
|
||||||
// Check for something after the dot in `localName`
|
/* Check for something after the dot in `localName` */
|
||||||
if (localName[1] == '\0') {
|
if (localName[1] == '\0') {
|
||||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n",
|
fatalerror("'%s' is a nonsensical reference to an empty local label\n",
|
||||||
symName);
|
symName);
|
||||||
}
|
}
|
||||||
// Check for more than one dot in `localName`
|
/* Check for more than one dot in `localName` */
|
||||||
if (strchr(localName + 1, '.'))
|
if (strchr(localName + 1, '.'))
|
||||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
|
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
|
||||||
symName);
|
symName);
|
||||||
|
|
||||||
if (localName == symName) {
|
if (localName == symName) {
|
||||||
// Expand `symName` to the full `labelScope.symName` name
|
/* Expand `symName` to the full `labelScope.symName` name */
|
||||||
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
|
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
|
||||||
symName = fullName;
|
symName = fullName;
|
||||||
} else {
|
} else {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
// Find where `labelScope` and `symName` first differ
|
/* Find where `labelScope` and `symName` first differ */
|
||||||
while (labelScope[i] && symName[i] == labelScope[i])
|
while (labelScope[i] && symName[i] == labelScope[i])
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
// Check that `symName` starts with `labelScope` and then a '.'
|
/* Check that `symName` starts with `labelScope` and then a '.' */
|
||||||
if (labelScope[i] != '\0' || symName[i] != '.') {
|
if (labelScope[i] != '\0' || symName[i] != '.') {
|
||||||
size_t parentLen = localName - symName;
|
size_t parentLen = localName - symName;
|
||||||
|
|
||||||
@@ -549,12 +579,14 @@ struct Symbol *sym_AddLocalLabel(char const *symName)
|
|||||||
return addLabel(symName);
|
return addLabel(symName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a relocatable symbol
|
/*
|
||||||
|
* Add a relocatable symbol
|
||||||
|
*/
|
||||||
struct Symbol *sym_AddLabel(char const *symName)
|
struct Symbol *sym_AddLabel(char const *symName)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = addLabel(symName);
|
struct Symbol *sym = addLabel(symName);
|
||||||
|
|
||||||
// Set the symbol as the new scope
|
/* Set the symbol as the new scope */
|
||||||
if (sym)
|
if (sym)
|
||||||
sym_SetCurrentSymbolScope(sym->name);
|
sym_SetCurrentSymbolScope(sym->name);
|
||||||
return sym;
|
return sym;
|
||||||
@@ -562,7 +594,9 @@ struct Symbol *sym_AddLabel(char const *symName)
|
|||||||
|
|
||||||
static uint32_t anonLabelID;
|
static uint32_t anonLabelID;
|
||||||
|
|
||||||
// Add an anonymous label
|
/*
|
||||||
|
* Add an anonymous label
|
||||||
|
*/
|
||||||
struct Symbol *sym_AddAnonLabel(void)
|
struct Symbol *sym_AddAnonLabel(void)
|
||||||
{
|
{
|
||||||
if (anonLabelID == UINT32_MAX) {
|
if (anonLabelID == UINT32_MAX) {
|
||||||
@@ -576,7 +610,9 @@ struct Symbol *sym_AddAnonLabel(void)
|
|||||||
return addLabel(name);
|
return addLabel(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write an anonymous label's name to a buffer
|
/*
|
||||||
|
* Write an anonymous label's name to a buffer
|
||||||
|
*/
|
||||||
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg)
|
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg)
|
||||||
{
|
{
|
||||||
uint32_t id = 0;
|
uint32_t id = 0;
|
||||||
@@ -600,7 +636,9 @@ void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs,
|
|||||||
sprintf(buf, "!%u", id);
|
sprintf(buf, "!%u", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export a symbol
|
/*
|
||||||
|
* Export a symbol
|
||||||
|
*/
|
||||||
void sym_Export(char const *symName)
|
void sym_Export(char const *symName)
|
||||||
{
|
{
|
||||||
if (symName[0] == '!') {
|
if (symName[0] == '!') {
|
||||||
@@ -610,13 +648,15 @@ void sym_Export(char const *symName)
|
|||||||
|
|
||||||
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
||||||
|
|
||||||
// If the symbol doesn't exist, create a ref that can be purged
|
/* If the symbol doesn't exist, create a ref that can be purged */
|
||||||
if (!sym)
|
if (!sym)
|
||||||
sym = sym_Ref(symName);
|
sym = sym_Ref(symName);
|
||||||
sym->isExported = true;
|
sym->isExported = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a macro definition
|
/*
|
||||||
|
* Add a macro definition
|
||||||
|
*/
|
||||||
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size)
|
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = createNonrelocSymbol(symName, false);
|
struct Symbol *sym = createNonrelocSymbol(symName, false);
|
||||||
@@ -627,16 +667,20 @@ struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body,
|
|||||||
sym->type = SYM_MACRO;
|
sym->type = SYM_MACRO;
|
||||||
sym->macroSize = size;
|
sym->macroSize = size;
|
||||||
sym->macro = body;
|
sym->macro = body;
|
||||||
setSymbolFilename(sym); // TODO: is this really necessary?
|
setSymbolFilename(sym); /* TODO: is this really necessary? */
|
||||||
// The symbol is created at the line after the `endm`,
|
/*
|
||||||
// override this with the actual definition line
|
* The symbol is created at the line after the `endm`,
|
||||||
|
* override this with the actual definition line
|
||||||
|
*/
|
||||||
sym->fileLine = defLineNo;
|
sym->fileLine = defLineNo;
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flag that a symbol is referenced in an RPN expression
|
/*
|
||||||
// and create it if it doesn't exist yet
|
* Flag that a symbol is referenced in an RPN expression
|
||||||
|
* and create it if it doesn't exist yet
|
||||||
|
*/
|
||||||
struct Symbol *sym_Ref(char const *symName)
|
struct Symbol *sym_Ref(char const *symName)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
||||||
@@ -658,7 +702,9 @@ struct Symbol *sym_Ref(char const *symName)
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set whether to export all relocatable symbols by default
|
/*
|
||||||
|
* Set whether to export all relocatable symbols by default
|
||||||
|
*/
|
||||||
void sym_SetExportAll(bool set)
|
void sym_SetExportAll(bool set)
|
||||||
{
|
{
|
||||||
exportall = set;
|
exportall = set;
|
||||||
@@ -675,7 +721,9 @@ static struct Symbol *createBuiltinSymbol(char const *symName)
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the symboltable
|
/*
|
||||||
|
* Initialize the symboltable
|
||||||
|
*/
|
||||||
void sym_Init(time_t now)
|
void sym_Init(time_t now)
|
||||||
{
|
{
|
||||||
PCSymbol = createBuiltinSymbol("@");
|
PCSymbol = createBuiltinSymbol("@");
|
||||||
@@ -695,13 +743,8 @@ void sym_Init(time_t now)
|
|||||||
|
|
||||||
sym_AddVar("_RS", 0)->isBuiltin = true;
|
sym_AddVar("_RS", 0)->isBuiltin = true;
|
||||||
|
|
||||||
#define addSym(fn, name, val) do { \
|
#define addNumber(name, val) sym_AddEqu(name, val)->isBuiltin = true
|
||||||
struct Symbol *sym = fn(name, val); \
|
#define addString(name, val) sym_AddString(name, val)->isBuiltin = true
|
||||||
assert(sym); \
|
|
||||||
sym->isBuiltin = true; \
|
|
||||||
} while (0)
|
|
||||||
#define addNumber(name, val) addSym(sym_AddEqu, name, val)
|
|
||||||
#define addString(name, val) addSym(sym_AddString, name, val)
|
|
||||||
|
|
||||||
addString("__RGBDS_VERSION__", get_package_version_string());
|
addString("__RGBDS_VERSION__", get_package_version_string());
|
||||||
addNumber("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR);
|
addNumber("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR);
|
||||||
@@ -713,7 +756,7 @@ void sym_Init(time_t now)
|
|||||||
|
|
||||||
if (now == (time_t)-1) {
|
if (now == (time_t)-1) {
|
||||||
warn("Couldn't determine current time");
|
warn("Couldn't determine current time");
|
||||||
// Fall back by pretending we are at the Epoch
|
/* Fall back by pretending we are at the Epoch */
|
||||||
now = 0;
|
now = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,7 +788,16 @@ void sym_Init(time_t now)
|
|||||||
|
|
||||||
#undef addNumber
|
#undef addNumber
|
||||||
#undef addString
|
#undef addString
|
||||||
#undef addSym
|
|
||||||
|
|
||||||
sym_SetCurrentSymbolScope(NULL);
|
sym_SetCurrentSymbolScope(NULL);
|
||||||
anonLabelID = 0;}
|
anonLabelID = 0;
|
||||||
|
|
||||||
|
/* _PI is deprecated */
|
||||||
|
struct Symbol *_PISymbol = createBuiltinSymbol("_PI");
|
||||||
|
|
||||||
|
_PISymbol->type = SYM_EQU;
|
||||||
|
_PISymbol->src = NULL;
|
||||||
|
_PISymbol->fileLine = 0;
|
||||||
|
_PISymbol->hasCallback = true;
|
||||||
|
_PISymbol->numCallback = fix_Callback_PI;
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ char const *printChar(int c)
|
|||||||
buf[2] = 't';
|
buf[2] = 't';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // Print as hex
|
default: /* Print as hex */
|
||||||
buf[0] = '0';
|
buf[0] = '0';
|
||||||
buf[1] = 'x';
|
buf[1] = 'x';
|
||||||
snprintf(&buf[2], 3, "%02hhX", (uint8_t)c); // includes the '\0'
|
snprintf(&buf[2], 3, "%02hhX", (uint8_t)c); // includes the '\0'
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
|
|||||||
[WARNING_OBSOLETE] = WARNING_ENABLED,
|
[WARNING_OBSOLETE] = WARNING_ENABLED,
|
||||||
[WARNING_SHIFT] = WARNING_DISABLED,
|
[WARNING_SHIFT] = WARNING_DISABLED,
|
||||||
[WARNING_SHIFT_AMOUNT] = WARNING_DISABLED,
|
[WARNING_SHIFT_AMOUNT] = WARNING_DISABLED,
|
||||||
[WARNING_UNMAPPED_CHAR] = WARNING_ENABLED,
|
|
||||||
[WARNING_USER] = WARNING_ENABLED,
|
[WARNING_USER] = WARNING_ENABLED,
|
||||||
|
|
||||||
[WARNING_NUMERIC_STRING_1] = WARNING_ENABLED,
|
[WARNING_NUMERIC_STRING_1] = WARNING_ENABLED,
|
||||||
@@ -49,19 +48,19 @@ static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
|
|||||||
|
|
||||||
enum WarningState warningStates[ARRAY_SIZE(warningStates)];
|
enum WarningState warningStates[ARRAY_SIZE(warningStates)];
|
||||||
|
|
||||||
bool warningsAreErrors; // Set if `-Werror` was specified
|
bool warningsAreErrors; /* Set if `-Werror` was specified */
|
||||||
|
|
||||||
static enum WarningState warningState(enum WarningID id)
|
static enum WarningState warningState(enum WarningID id)
|
||||||
{
|
{
|
||||||
// Check if warnings are globally disabled
|
/* Check if warnings are globally disabled */
|
||||||
if (!warnings)
|
if (!warnings)
|
||||||
return WARNING_DISABLED;
|
return WARNING_DISABLED;
|
||||||
|
|
||||||
// Get the actual state
|
/* Get the actual state */
|
||||||
enum WarningState state = warningStates[id];
|
enum WarningState state = warningStates[id];
|
||||||
|
|
||||||
if (state == WARNING_DEFAULT)
|
if (state == WARNING_DEFAULT)
|
||||||
// The state isn't set, grab its default state
|
/* The state isn't set, grab its default state */
|
||||||
state = defaultWarnings[id];
|
state = defaultWarnings[id];
|
||||||
|
|
||||||
if (warningsAreErrors && state == WARNING_ENABLED)
|
if (warningsAreErrors && state == WARNING_ENABLED)
|
||||||
@@ -86,7 +85,6 @@ static const char * const warningFlags[NB_WARNINGS] = {
|
|||||||
"obsolete",
|
"obsolete",
|
||||||
"shift",
|
"shift",
|
||||||
"shift-amount",
|
"shift-amount",
|
||||||
"unmapped-char",
|
|
||||||
"user",
|
"user",
|
||||||
|
|
||||||
// Parametric warnings
|
// Parametric warnings
|
||||||
@@ -95,10 +93,10 @@ static const char * const warningFlags[NB_WARNINGS] = {
|
|||||||
"truncation",
|
"truncation",
|
||||||
"truncation",
|
"truncation",
|
||||||
|
|
||||||
// Meta warnings
|
/* Meta warnings */
|
||||||
"all",
|
"all",
|
||||||
"extra",
|
"extra",
|
||||||
"everything", // Especially useful for testing
|
"everything", /* Especially useful for testing */
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct {
|
static const struct {
|
||||||
@@ -151,7 +149,7 @@ enum MetaWarningCommand {
|
|||||||
META_WARNING_DONE = NB_WARNINGS
|
META_WARNING_DONE = NB_WARNINGS
|
||||||
};
|
};
|
||||||
|
|
||||||
// Warnings that probably indicate an error
|
/* Warnings that probably indicate an error */
|
||||||
static uint8_t const _wallCommands[] = {
|
static uint8_t const _wallCommands[] = {
|
||||||
WARNING_BACKWARDS_FOR,
|
WARNING_BACKWARDS_FOR,
|
||||||
WARNING_BUILTIN_ARG,
|
WARNING_BUILTIN_ARG,
|
||||||
@@ -162,12 +160,11 @@ static uint8_t const _wallCommands[] = {
|
|||||||
WARNING_LONG_STR,
|
WARNING_LONG_STR,
|
||||||
WARNING_NESTED_COMMENT,
|
WARNING_NESTED_COMMENT,
|
||||||
WARNING_OBSOLETE,
|
WARNING_OBSOLETE,
|
||||||
WARNING_UNMAPPED_CHAR,
|
|
||||||
WARNING_NUMERIC_STRING_1,
|
WARNING_NUMERIC_STRING_1,
|
||||||
META_WARNING_DONE
|
META_WARNING_DONE
|
||||||
};
|
};
|
||||||
|
|
||||||
// Warnings that are less likely to indicate an error
|
/* Warnings that are less likely to indicate an error */
|
||||||
static uint8_t const _wextraCommands[] = {
|
static uint8_t const _wextraCommands[] = {
|
||||||
WARNING_EMPTY_MACRO_ARG,
|
WARNING_EMPTY_MACRO_ARG,
|
||||||
WARNING_MACRO_SHIFT,
|
WARNING_MACRO_SHIFT,
|
||||||
@@ -179,7 +176,7 @@ static uint8_t const _wextraCommands[] = {
|
|||||||
META_WARNING_DONE
|
META_WARNING_DONE
|
||||||
};
|
};
|
||||||
|
|
||||||
// Literally everything. Notably useful for testing
|
/* Literally everything. Notably useful for testing */
|
||||||
static uint8_t const _weverythingCommands[] = {
|
static uint8_t const _weverythingCommands[] = {
|
||||||
WARNING_BACKWARDS_FOR,
|
WARNING_BACKWARDS_FOR,
|
||||||
WARNING_BUILTIN_ARG,
|
WARNING_BUILTIN_ARG,
|
||||||
@@ -194,12 +191,11 @@ static uint8_t const _weverythingCommands[] = {
|
|||||||
WARNING_OBSOLETE,
|
WARNING_OBSOLETE,
|
||||||
WARNING_SHIFT,
|
WARNING_SHIFT,
|
||||||
WARNING_SHIFT_AMOUNT,
|
WARNING_SHIFT_AMOUNT,
|
||||||
WARNING_UNMAPPED_CHAR,
|
|
||||||
WARNING_NUMERIC_STRING_1,
|
WARNING_NUMERIC_STRING_1,
|
||||||
WARNING_NUMERIC_STRING_2,
|
WARNING_NUMERIC_STRING_2,
|
||||||
WARNING_TRUNCATION_1,
|
WARNING_TRUNCATION_1,
|
||||||
WARNING_TRUNCATION_2,
|
WARNING_TRUNCATION_2,
|
||||||
// WARNING_USER,
|
/* WARNING_USER, */
|
||||||
META_WARNING_DONE
|
META_WARNING_DONE
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -213,18 +209,18 @@ void processWarningFlag(char *flag)
|
|||||||
{
|
{
|
||||||
static bool setError = false;
|
static bool setError = false;
|
||||||
|
|
||||||
// First, try to match against a "meta" warning
|
/* First, try to match against a "meta" warning */
|
||||||
for (enum WarningID id = META_WARNINGS_START; id < NB_WARNINGS; id++) {
|
for (enum WarningID id = META_WARNINGS_START; id < NB_WARNINGS; id++) {
|
||||||
// TODO: improve the matching performance?
|
/* TODO: improve the matching performance? */
|
||||||
if (!strcmp(flag, warningFlags[id])) {
|
if (!strcmp(flag, warningFlags[id])) {
|
||||||
// We got a match!
|
/* We got a match! */
|
||||||
if (setError)
|
if (setError)
|
||||||
errx("Cannot make meta warning \"%s\" into an error",
|
errx("Cannot make meta warning \"%s\" into an error",
|
||||||
flag);
|
flag);
|
||||||
|
|
||||||
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
|
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
|
||||||
*ptr != META_WARNING_DONE; ptr++) {
|
*ptr != META_WARNING_DONE; ptr++) {
|
||||||
// Warning flag, set without override
|
/* Warning flag, set without override */
|
||||||
if (warningStates[*ptr] == WARNING_DEFAULT)
|
if (warningStates[*ptr] == WARNING_DEFAULT)
|
||||||
warningStates[*ptr] = WARNING_ENABLED;
|
warningStates[*ptr] = WARNING_ENABLED;
|
||||||
}
|
}
|
||||||
@@ -233,31 +229,31 @@ void processWarningFlag(char *flag)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's not a meta warning, specially check against `-Werror`
|
/* If it's not a meta warning, specially check against `-Werror` */
|
||||||
if (!strncmp(flag, "error", strlen("error"))) {
|
if (!strncmp(flag, "error", strlen("error"))) {
|
||||||
char *errorFlag = flag + strlen("error");
|
char *errorFlag = flag + strlen("error");
|
||||||
|
|
||||||
switch (*errorFlag) {
|
switch (*errorFlag) {
|
||||||
case '\0':
|
case '\0':
|
||||||
// `-Werror`
|
/* `-Werror` */
|
||||||
warningsAreErrors = true;
|
warningsAreErrors = true;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case '=':
|
case '=':
|
||||||
// `-Werror=XXX`
|
/* `-Werror=XXX` */
|
||||||
setError = true;
|
setError = true;
|
||||||
processWarningFlag(errorFlag + 1); // Skip the `=`
|
processWarningFlag(errorFlag + 1); /* Skip the `=` */
|
||||||
setError = false;
|
setError = false;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Otherwise, allow parsing as another flag
|
/* Otherwise, allow parsing as another flag */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Well, it's either a normal warning or a mistake
|
/* Well, it's either a normal warning or a mistake */
|
||||||
|
|
||||||
enum WarningState state = setError ? WARNING_ERROR :
|
enum WarningState state = setError ? WARNING_ERROR :
|
||||||
// Not an error, then check if this is a negation
|
/* Not an error, then check if this is a negation */
|
||||||
strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
|
strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
|
||||||
: WARNING_DISABLED;
|
: WARNING_DISABLED;
|
||||||
char const *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
|
char const *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
|
||||||
@@ -308,10 +304,10 @@ void processWarningFlag(char *flag)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to match the flag against a "normal" flag
|
/* Try to match the flag against a "normal" flag */
|
||||||
for (enum WarningID id = 0; id < NB_PLAIN_WARNINGS; id++) {
|
for (enum WarningID id = 0; id < NB_PLAIN_WARNINGS; id++) {
|
||||||
if (!strcmp(rootFlag, warningFlags[id])) {
|
if (!strcmp(rootFlag, warningFlags[id])) {
|
||||||
// We got a match!
|
/* We got a match! */
|
||||||
warningStates[id] = state;
|
warningStates[id] = state;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -374,7 +370,7 @@ void warning(enum WarningID id, char const *fmt, ...)
|
|||||||
|
|
||||||
case WARNING_DEFAULT:
|
case WARNING_DEFAULT:
|
||||||
unreachable_();
|
unreachable_();
|
||||||
// Not reached
|
/* Not reached */
|
||||||
|
|
||||||
case WARNING_ENABLED:
|
case WARNING_ENABLED:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
bison -V | awk -v major="$1" -v minor="$2" '
|
bison -V | awk -v major="$1" -v minor="$2" '
|
||||||
/^bison.*[0-9]+(\.[0-9]+)(\.[0-9]+)?$/ {
|
/^bison.*[0-9]+(\.[0-9]+)(\.[0-9]+)?$/ {
|
||||||
match($0, /[0-9]+(\.[0-9]+)(\.[0-9]+)?$/);
|
match($0, /[0-9]+(\.[0-9]+)(\.[0-9]+)?$/);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
#define BANK_SIZE 0x4000
|
#define BANK_SIZE 0x4000
|
||||||
|
|
||||||
// Short options
|
/* Short options */
|
||||||
static const char *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv";
|
static const char *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -191,7 +191,7 @@ static void printAcceptedMBCNames(void)
|
|||||||
|
|
||||||
static uint8_t tpp1Rev[2];
|
static uint8_t tpp1Rev[2];
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* @return False on failure
|
* @return False on failure
|
||||||
*/
|
*/
|
||||||
static bool readMBCSlice(char const **name, char const *expected)
|
static bool readMBCSlice(char const **name, char const *expected)
|
||||||
@@ -837,7 +837,7 @@ static ssize_t writeBytes(int fd, void *buf, size_t len)
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* @param rom0 A pointer to rom0
|
* @param rom0 A pointer to rom0
|
||||||
* @param addr What address to check
|
* @param addr What address to check
|
||||||
* @param fixedByte The fixed byte at the address
|
* @param fixedByte The fixed byte at the address
|
||||||
@@ -853,7 +853,7 @@ static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char
|
|||||||
rom0[addr] = fixedByte;
|
rom0[addr] = fixedByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* @param rom0 A pointer to rom0
|
* @param rom0 A pointer to rom0
|
||||||
* @param startAddr What address to begin checking from
|
* @param startAddr What address to begin checking from
|
||||||
* @param fixed The fixed bytes at the address
|
* @param fixed The fixed bytes at the address
|
||||||
@@ -878,7 +878,7 @@ static void overwriteBytes(uint8_t *rom0, uint16_t startAddr, uint8_t const *fix
|
|||||||
memcpy(&rom0[startAddr], fixed, size);
|
memcpy(&rom0[startAddr], fixed, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* @param input File descriptor to be used for reading
|
* @param input File descriptor to be used for reading
|
||||||
* @param output File descriptor to be used for writing, may be equal to `input`
|
* @param output File descriptor to be used for writing, may be equal to `input`
|
||||||
* @param name The file's name, to be displayed for error output
|
* @param name The file's name, to be displayed for error output
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ SurvivalKids.gbc
|
|||||||
.Sh TPP1
|
.Sh TPP1
|
||||||
TPP1 is a homebrew mapper designed as a functional superset of the common traditional MBCs, allowing larger ROM and RAM sizes combined with other hardware features.
|
TPP1 is a homebrew mapper designed as a functional superset of the common traditional MBCs, allowing larger ROM and RAM sizes combined with other hardware features.
|
||||||
Its specification, as well as more resources, can be found online at
|
Its specification, as well as more resources, can be found online at
|
||||||
.Lk https://github.com/aaaaaa123456789/tpp1 .
|
.Lk https://github.com/TwitchPlaysPokemon/tpp1 .
|
||||||
.Ss MBC name
|
.Ss MBC name
|
||||||
The MBC name for TPP1 is more complex than standard mappers.
|
The MBC name for TPP1 is more complex than standard mappers.
|
||||||
It must be followed with the revision number, of the form
|
It must be followed with the revision number, of the form
|
||||||
File diff suppressed because it is too large
Load Diff
385
src/gfx/gb.c
Normal file
385
src/gfx/gb.c
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "gfx/gb.h"
|
||||||
|
|
||||||
|
void transpose_tiles(struct GBImage *gb, int width)
|
||||||
|
{
|
||||||
|
uint8_t *newdata;
|
||||||
|
int i;
|
||||||
|
int newbyte;
|
||||||
|
|
||||||
|
newdata = calloc(gb->size, 1);
|
||||||
|
if (!newdata)
|
||||||
|
err("%s: Failed to allocate memory for new data", __func__);
|
||||||
|
|
||||||
|
for (i = 0; i < gb->size; i++) {
|
||||||
|
newbyte = i / (8 * depth) * width * 8 * depth;
|
||||||
|
newbyte = newbyte % gb->size
|
||||||
|
+ 8 * depth * (newbyte / gb->size)
|
||||||
|
+ i % (8 * depth);
|
||||||
|
newdata[newbyte] = gb->data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
free(gb->data);
|
||||||
|
|
||||||
|
gb->data = newdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb)
|
||||||
|
{
|
||||||
|
uint8_t index;
|
||||||
|
|
||||||
|
for (unsigned int y = 0; y < raw_image->height; y++) {
|
||||||
|
for (unsigned int x = 0; x < raw_image->width; x++) {
|
||||||
|
index = raw_image->data[y][x];
|
||||||
|
index &= (1 << depth) - 1;
|
||||||
|
|
||||||
|
unsigned int byte = y * depth
|
||||||
|
+ x / 8 * raw_image->height / 8 * 8 * depth;
|
||||||
|
gb->data[byte] |= (index & 1) << (7 - x % 8);
|
||||||
|
if (depth == 2) {
|
||||||
|
gb->data[byte + 1] |=
|
||||||
|
(index >> 1) << (7 - x % 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gb->horizontal)
|
||||||
|
transpose_tiles(gb, raw_image->width / 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void output_file(const struct Options *opts, const struct GBImage *gb)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = fopen(opts->outfile, "wb");
|
||||||
|
if (!f)
|
||||||
|
err("%s: Opening output file '%s' failed", __func__,
|
||||||
|
opts->outfile);
|
||||||
|
|
||||||
|
fwrite(gb->data, 1, gb->size - gb->trim * 8 * depth, f);
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles, int tile_size)
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
for (i = 0; i < num_tiles; i++) {
|
||||||
|
for (j = 0; j < tile_size; j++) {
|
||||||
|
if (tile[j] != tiles[i][j])
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j >= tile_size)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t reverse_bits(uint8_t b)
|
||||||
|
{
|
||||||
|
uint8_t rev = 0;
|
||||||
|
|
||||||
|
rev |= (b & 0x80) >> 7;
|
||||||
|
rev |= (b & 0x40) >> 5;
|
||||||
|
rev |= (b & 0x20) >> 3;
|
||||||
|
rev |= (b & 0x10) >> 1;
|
||||||
|
rev |= (b & 0x08) << 1;
|
||||||
|
rev |= (b & 0x04) << 3;
|
||||||
|
rev |= (b & 0x02) << 5;
|
||||||
|
rev |= (b & 0x01) << 7;
|
||||||
|
return rev;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xflip(uint8_t *tile, uint8_t *tile_xflip, int tile_size)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < tile_size; i++)
|
||||||
|
tile_xflip[i] = reverse_bits(tile[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void yflip(uint8_t *tile, uint8_t *tile_yflip, int tile_size)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < tile_size; i++)
|
||||||
|
tile_yflip[i] = tile[(tile_size - i - 1) ^ (depth - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_mirrored_tile_index looks for `tile` in tile array `tiles`, also
|
||||||
|
* checking x-, y-, and xy-mirrored versions of `tile`. If one is found,
|
||||||
|
* `*flags` is set according to the type of mirroring and the index of the
|
||||||
|
* matched tile is returned. If no match is found, -1 is returned.
|
||||||
|
*/
|
||||||
|
int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
|
||||||
|
int tile_size, int *flags)
|
||||||
|
{
|
||||||
|
int index;
|
||||||
|
uint8_t *tile_xflip;
|
||||||
|
uint8_t *tile_yflip;
|
||||||
|
|
||||||
|
index = get_tile_index(tile, tiles, num_tiles, tile_size);
|
||||||
|
if (index >= 0) {
|
||||||
|
*flags = 0;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
tile_yflip = malloc(tile_size);
|
||||||
|
if (!tile_yflip)
|
||||||
|
err("%s: Failed to allocate memory for Y flip of tile",
|
||||||
|
__func__);
|
||||||
|
yflip(tile, tile_yflip, tile_size);
|
||||||
|
index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
|
||||||
|
if (index >= 0) {
|
||||||
|
*flags = YFLIP;
|
||||||
|
free(tile_yflip);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
tile_xflip = malloc(tile_size);
|
||||||
|
if (!tile_xflip)
|
||||||
|
err("%s: Failed to allocate memory for X flip of tile",
|
||||||
|
__func__);
|
||||||
|
xflip(tile, tile_xflip, tile_size);
|
||||||
|
index = get_tile_index(tile_xflip, tiles, num_tiles, tile_size);
|
||||||
|
if (index >= 0) {
|
||||||
|
*flags = XFLIP;
|
||||||
|
free(tile_yflip);
|
||||||
|
free(tile_xflip);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
yflip(tile_xflip, tile_yflip, tile_size);
|
||||||
|
index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
|
||||||
|
if (index >= 0)
|
||||||
|
*flags = XFLIP | YFLIP;
|
||||||
|
|
||||||
|
free(tile_yflip);
|
||||||
|
free(tile_xflip);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void create_mapfiles(const struct Options *opts, struct GBImage *gb,
|
||||||
|
struct Mapfile *tilemap, struct Mapfile *attrmap)
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
int gb_i;
|
||||||
|
int tile_size;
|
||||||
|
int max_tiles;
|
||||||
|
int num_tiles;
|
||||||
|
int index;
|
||||||
|
int flags;
|
||||||
|
int gb_size;
|
||||||
|
uint8_t *tile;
|
||||||
|
uint8_t **tiles;
|
||||||
|
|
||||||
|
tile_size = sizeof(*tile) * depth * 8;
|
||||||
|
gb_size = gb->size - (gb->trim * tile_size);
|
||||||
|
max_tiles = gb_size / tile_size;
|
||||||
|
|
||||||
|
/* If the input image doesn't fill the last tile, increase the count. */
|
||||||
|
if (gb_size > max_tiles * tile_size)
|
||||||
|
max_tiles++;
|
||||||
|
|
||||||
|
tiles = calloc(max_tiles, sizeof(*tiles));
|
||||||
|
if (!tiles)
|
||||||
|
err("%s: Failed to allocate memory for tiles", __func__);
|
||||||
|
num_tiles = 0;
|
||||||
|
|
||||||
|
if (*opts->tilemapfile) {
|
||||||
|
tilemap->data = calloc(max_tiles, sizeof(*tilemap->data));
|
||||||
|
if (!tilemap->data)
|
||||||
|
err("%s: Failed to allocate memory for tilemap data",
|
||||||
|
__func__);
|
||||||
|
tilemap->size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*opts->attrmapfile) {
|
||||||
|
attrmap->data = calloc(max_tiles, sizeof(*attrmap->data));
|
||||||
|
if (!attrmap->data)
|
||||||
|
err("%s: Failed to allocate memory for attrmap data",
|
||||||
|
__func__);
|
||||||
|
attrmap->size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gb_i = 0;
|
||||||
|
while (gb_i < gb_size) {
|
||||||
|
flags = 0;
|
||||||
|
tile = malloc(tile_size);
|
||||||
|
if (!tile)
|
||||||
|
err("%s: Failed to allocate memory for tile",
|
||||||
|
__func__);
|
||||||
|
/*
|
||||||
|
* If the input image doesn't fill the last tile,
|
||||||
|
* `gb_i` will reach `gb_size`.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < tile_size && gb_i < gb_size; i++) {
|
||||||
|
tile[i] = gb->data[gb_i];
|
||||||
|
gb_i++;
|
||||||
|
}
|
||||||
|
if (opts->unique) {
|
||||||
|
if (opts->mirror) {
|
||||||
|
index = get_mirrored_tile_index(tile, tiles,
|
||||||
|
num_tiles,
|
||||||
|
tile_size,
|
||||||
|
&flags);
|
||||||
|
} else {
|
||||||
|
index = get_tile_index(tile, tiles, num_tiles,
|
||||||
|
tile_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
index = num_tiles;
|
||||||
|
tiles[num_tiles] = tile;
|
||||||
|
num_tiles++;
|
||||||
|
} else {
|
||||||
|
free(tile);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
index = num_tiles;
|
||||||
|
tiles[num_tiles] = tile;
|
||||||
|
num_tiles++;
|
||||||
|
}
|
||||||
|
if (*opts->tilemapfile) {
|
||||||
|
tilemap->data[tilemap->size] = index;
|
||||||
|
tilemap->size++;
|
||||||
|
}
|
||||||
|
if (*opts->attrmapfile) {
|
||||||
|
attrmap->data[attrmap->size] = flags;
|
||||||
|
attrmap->size++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts->unique) {
|
||||||
|
free(gb->data);
|
||||||
|
gb->data = malloc(tile_size * num_tiles);
|
||||||
|
if (!gb->data)
|
||||||
|
err("%s: Failed to allocate memory for tile data",
|
||||||
|
__func__);
|
||||||
|
for (i = 0; i < num_tiles; i++) {
|
||||||
|
tile = tiles[i];
|
||||||
|
for (j = 0; j < tile_size; j++)
|
||||||
|
gb->data[i * tile_size + j] = tile[j];
|
||||||
|
}
|
||||||
|
gb->size = i * tile_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num_tiles; i++)
|
||||||
|
free(tiles[i]);
|
||||||
|
|
||||||
|
free(tiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
void output_tilemap_file(const struct Options *opts,
|
||||||
|
const struct Mapfile *tilemap)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = fopen(opts->tilemapfile, "wb");
|
||||||
|
if (!f)
|
||||||
|
err("%s: Opening tilemap file '%s' failed", __func__,
|
||||||
|
opts->tilemapfile);
|
||||||
|
|
||||||
|
fwrite(tilemap->data, 1, tilemap->size, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (opts->tilemapout)
|
||||||
|
free(opts->tilemapfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void output_attrmap_file(const struct Options *opts,
|
||||||
|
const struct Mapfile *attrmap)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = fopen(opts->attrmapfile, "wb");
|
||||||
|
if (!f)
|
||||||
|
err("%s: Opening attrmap file '%s' failed", __func__,
|
||||||
|
opts->attrmapfile);
|
||||||
|
|
||||||
|
fwrite(attrmap->data, 1, attrmap->size, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (opts->attrmapout)
|
||||||
|
free(opts->attrmapfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* based on the Gaussian-like curve used by SameBoy since commit
|
||||||
|
* 65dd02cc52f531dbbd3a7e6014e99d5b24e71a4c (Oct 2017)
|
||||||
|
* with ties resolved by comparing the difference of the squares.
|
||||||
|
*/
|
||||||
|
static int reverse_curve[] = {
|
||||||
|
0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4,
|
||||||
|
5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7,
|
||||||
|
7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8,
|
||||||
|
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10,
|
||||||
|
10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
|
||||||
|
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13,
|
||||||
|
13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14,
|
||||||
|
14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17,
|
||||||
|
17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18,
|
||||||
|
18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
|
||||||
|
19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21,
|
||||||
|
21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22,
|
||||||
|
22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24,
|
||||||
|
24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26,
|
||||||
|
26, 27, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 30, 30, 31,
|
||||||
|
};
|
||||||
|
|
||||||
|
void output_palette_file(const struct Options *opts,
|
||||||
|
const struct RawIndexedImage *raw_image)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
int i, color;
|
||||||
|
uint8_t cur_bytes[2];
|
||||||
|
|
||||||
|
f = fopen(opts->palfile, "wb");
|
||||||
|
if (!f)
|
||||||
|
err("%s: Opening palette file '%s' failed", __func__,
|
||||||
|
opts->palfile);
|
||||||
|
|
||||||
|
for (i = 0; i < raw_image->num_colors; i++) {
|
||||||
|
int r = raw_image->palette[i].red;
|
||||||
|
int g = raw_image->palette[i].green;
|
||||||
|
int b = raw_image->palette[i].blue;
|
||||||
|
|
||||||
|
if (opts->colorcurve) {
|
||||||
|
g = (g * 4 - b) / 3;
|
||||||
|
if (g < 0)
|
||||||
|
g = 0;
|
||||||
|
|
||||||
|
r = reverse_curve[r];
|
||||||
|
g = reverse_curve[g];
|
||||||
|
b = reverse_curve[b];
|
||||||
|
} else {
|
||||||
|
r >>= 3;
|
||||||
|
g >>= 3;
|
||||||
|
b >>= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
color = b << 10 | g << 5 | r;
|
||||||
|
cur_bytes[0] = color & 0xFF;
|
||||||
|
cur_bytes[1] = color >> 8;
|
||||||
|
fwrite(cur_bytes, 2, 1, f);
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (opts->palout)
|
||||||
|
free(opts->palfile);
|
||||||
|
}
|
||||||
358
src/gfx/main.c
Normal file
358
src/gfx/main.c
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <png.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "gfx/main.h"
|
||||||
|
|
||||||
|
#include "extern/getopt.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
int depth, colors;
|
||||||
|
|
||||||
|
/* Short options */
|
||||||
|
static char const *optstring = "Aa:CDd:Ffhmo:Pp:Tt:uVvx:";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Equivalent long options
|
||||||
|
* Please keep in the same order as short opts
|
||||||
|
*
|
||||||
|
* Also, make sure long opts don't create ambiguity:
|
||||||
|
* A long opt's name should start with the same letter as its short opt,
|
||||||
|
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||||
|
* This is because long opt matching, even to a single char, is prioritized
|
||||||
|
* over short opt matching
|
||||||
|
*/
|
||||||
|
static struct option const longopts[] = {
|
||||||
|
{ "output-attr-map", no_argument, NULL, 'A' },
|
||||||
|
{ "attr-map", required_argument, NULL, 'a' },
|
||||||
|
{ "color-curve", no_argument, NULL, 'C' },
|
||||||
|
{ "debug", no_argument, NULL, 'D' },
|
||||||
|
{ "depth", required_argument, NULL, 'd' },
|
||||||
|
{ "fix", no_argument, NULL, 'f' },
|
||||||
|
{ "fix-and-save", no_argument, NULL, 'F' },
|
||||||
|
{ "horizontal", no_argument, NULL, 'h' },
|
||||||
|
{ "mirror-tiles", no_argument, NULL, 'm' },
|
||||||
|
{ "output", required_argument, NULL, 'o' },
|
||||||
|
{ "output-palette", no_argument, NULL, 'P' },
|
||||||
|
{ "palette", required_argument, NULL, 'p' },
|
||||||
|
{ "output-tilemap", no_argument, NULL, 'T' },
|
||||||
|
{ "tilemap", required_argument, NULL, 't' },
|
||||||
|
{ "unique-tiles", no_argument, NULL, 'u' },
|
||||||
|
{ "version", no_argument, NULL, 'V' },
|
||||||
|
{ "verbose", no_argument, NULL, 'v' },
|
||||||
|
{ "trim-end", required_argument, NULL, 'x' },
|
||||||
|
{ NULL, no_argument, NULL, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
static void print_usage(void)
|
||||||
|
{
|
||||||
|
fputs(
|
||||||
|
"Usage: rgbgfx [-CDhmuVv] [-f | -F] [-a <attr_map> | -A] [-d <depth>]\n"
|
||||||
|
" [-o <out_file>] [-p <pal_file> | -P] [-t <tile_map> | -T]\n"
|
||||||
|
" [-x <tiles>] <file>\n"
|
||||||
|
"Useful options:\n"
|
||||||
|
" -f, --fix make the input image an indexed PNG\n"
|
||||||
|
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
||||||
|
" -o, --output <path> set the output binary file\n"
|
||||||
|
" -t, --tilemap <path> set the output tilemap file\n"
|
||||||
|
" -u, --unique-tiles optimize out identical tiles\n"
|
||||||
|
" -V, --version print RGBGFX version and exit\n"
|
||||||
|
"\n"
|
||||||
|
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
|
||||||
|
stderr);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int ch, size;
|
||||||
|
struct Options opts = {0};
|
||||||
|
struct ImageOptions png_options = {0};
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
struct GBImage gb = {0};
|
||||||
|
struct Mapfile tilemap = {0};
|
||||||
|
struct Mapfile attrmap = {0};
|
||||||
|
char *ext;
|
||||||
|
|
||||||
|
opts.tilemapfile = "";
|
||||||
|
opts.attrmapfile = "";
|
||||||
|
opts.palfile = "";
|
||||||
|
opts.outfile = "";
|
||||||
|
|
||||||
|
depth = 2;
|
||||||
|
|
||||||
|
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts,
|
||||||
|
NULL)) != -1) {
|
||||||
|
switch (ch) {
|
||||||
|
case 'A':
|
||||||
|
opts.attrmapout = true;
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
opts.attrmapfile = musl_optarg;
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
opts.colorcurve = true;
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
opts.debug = true;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
depth = strtoul(musl_optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
|
opts.hardfix = true;
|
||||||
|
/* fallthrough */
|
||||||
|
case 'f':
|
||||||
|
opts.fix = true;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
opts.horizontal = true;
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
opts.mirror = true;
|
||||||
|
opts.unique = true;
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
opts.outfile = musl_optarg;
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
opts.palout = true;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
opts.palfile = musl_optarg;
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
opts.tilemapout = true;
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
opts.tilemapfile = musl_optarg;
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
opts.unique = true;
|
||||||
|
break;
|
||||||
|
case 'V':
|
||||||
|
printf("rgbgfx %s\n", get_package_version_string());
|
||||||
|
exit(0);
|
||||||
|
case 'v':
|
||||||
|
opts.verbose = true;
|
||||||
|
break;
|
||||||
|
case 'x':
|
||||||
|
opts.trim = strtoul(musl_optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print_usage();
|
||||||
|
/* NOTREACHED */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argc -= musl_optind;
|
||||||
|
argv += musl_optind;
|
||||||
|
|
||||||
|
if (argc == 0) {
|
||||||
|
fputs("FATAL: no input files\n", stderr);
|
||||||
|
print_usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
#define WARN_MISMATCH(property) \
|
||||||
|
warnx("The PNG's " property \
|
||||||
|
" setting doesn't match the one defined on the command line")
|
||||||
|
|
||||||
|
opts.infile = argv[argc - 1];
|
||||||
|
|
||||||
|
if (depth != 1 && depth != 2)
|
||||||
|
errx("Depth option must be either 1 or 2.");
|
||||||
|
|
||||||
|
colors = 1 << depth;
|
||||||
|
|
||||||
|
raw_image = input_png_file(&opts, &png_options);
|
||||||
|
|
||||||
|
png_options.tilemapfile = "";
|
||||||
|
png_options.attrmapfile = "";
|
||||||
|
png_options.palfile = "";
|
||||||
|
|
||||||
|
if (png_options.horizontal != opts.horizontal) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("horizontal");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.horizontal = opts.horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (png_options.horizontal)
|
||||||
|
opts.horizontal = png_options.horizontal;
|
||||||
|
|
||||||
|
if (png_options.trim != opts.trim) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("trim");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.trim = opts.trim;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (png_options.trim)
|
||||||
|
opts.trim = png_options.trim;
|
||||||
|
|
||||||
|
if (raw_image->width % 8) {
|
||||||
|
errx("Input PNG file %s not sized correctly. The image's width must be a multiple of 8.",
|
||||||
|
opts.infile);
|
||||||
|
}
|
||||||
|
if (raw_image->width / 8 > 1 && raw_image->height % 8) {
|
||||||
|
errx("Input PNG file %s not sized correctly. If the image is more than 1 tile wide, its height must be a multiple of 8.",
|
||||||
|
opts.infile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.trim &&
|
||||||
|
opts.trim > (raw_image->width / 8) * (raw_image->height / 8) - 1) {
|
||||||
|
errx("Trim (%d) for input raw_image file '%s' too large (max: %u)",
|
||||||
|
opts.trim, opts.infile,
|
||||||
|
(raw_image->width / 8) * (raw_image->height / 8) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(png_options.tilemapfile, opts.tilemapfile) != 0) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("tilemap file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.tilemapfile = opts.tilemapfile;
|
||||||
|
}
|
||||||
|
if (!*opts.tilemapfile)
|
||||||
|
opts.tilemapfile = png_options.tilemapfile;
|
||||||
|
|
||||||
|
if (png_options.tilemapout != opts.tilemapout) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("tilemap file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.tilemapout = opts.tilemapout;
|
||||||
|
}
|
||||||
|
if (png_options.tilemapout)
|
||||||
|
opts.tilemapout = png_options.tilemapout;
|
||||||
|
|
||||||
|
if (strcmp(png_options.attrmapfile, opts.attrmapfile) != 0) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("attrmap file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.attrmapfile = opts.attrmapfile;
|
||||||
|
}
|
||||||
|
if (!*opts.attrmapfile)
|
||||||
|
opts.attrmapfile = png_options.attrmapfile;
|
||||||
|
|
||||||
|
if (png_options.attrmapout != opts.attrmapout) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("attrmap file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.attrmapout = opts.attrmapout;
|
||||||
|
}
|
||||||
|
if (png_options.attrmapout)
|
||||||
|
opts.attrmapout = png_options.attrmapout;
|
||||||
|
|
||||||
|
if (strcmp(png_options.palfile, opts.palfile) != 0) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("palette file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.palfile = opts.palfile;
|
||||||
|
}
|
||||||
|
if (!*opts.palfile)
|
||||||
|
opts.palfile = png_options.palfile;
|
||||||
|
|
||||||
|
if (png_options.palout != opts.palout) {
|
||||||
|
if (opts.verbose)
|
||||||
|
WARN_MISMATCH("palette file");
|
||||||
|
|
||||||
|
if (opts.hardfix)
|
||||||
|
png_options.palout = opts.palout;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef WARN_MISMATCH
|
||||||
|
|
||||||
|
if (png_options.palout)
|
||||||
|
opts.palout = png_options.palout;
|
||||||
|
|
||||||
|
if (!*opts.tilemapfile && opts.tilemapout) {
|
||||||
|
ext = strrchr(opts.infile, '.');
|
||||||
|
|
||||||
|
if (ext != NULL) {
|
||||||
|
size = ext - opts.infile + 9;
|
||||||
|
opts.tilemapfile = malloc(size);
|
||||||
|
strncpy(opts.tilemapfile, opts.infile, size);
|
||||||
|
*strrchr(opts.tilemapfile, '.') = '\0';
|
||||||
|
strcat(opts.tilemapfile, ".tilemap");
|
||||||
|
} else {
|
||||||
|
opts.tilemapfile = malloc(strlen(opts.infile) + 9);
|
||||||
|
strcpy(opts.tilemapfile, opts.infile);
|
||||||
|
strcat(opts.tilemapfile, ".tilemap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*opts.attrmapfile && opts.attrmapout) {
|
||||||
|
ext = strrchr(opts.infile, '.');
|
||||||
|
|
||||||
|
if (ext != NULL) {
|
||||||
|
size = ext - opts.infile + 9;
|
||||||
|
opts.attrmapfile = malloc(size);
|
||||||
|
strncpy(opts.attrmapfile, opts.infile, size);
|
||||||
|
*strrchr(opts.attrmapfile, '.') = '\0';
|
||||||
|
strcat(opts.attrmapfile, ".attrmap");
|
||||||
|
} else {
|
||||||
|
opts.attrmapfile = malloc(strlen(opts.infile) + 9);
|
||||||
|
strcpy(opts.attrmapfile, opts.infile);
|
||||||
|
strcat(opts.attrmapfile, ".attrmap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*opts.palfile && opts.palout) {
|
||||||
|
ext = strrchr(opts.infile, '.');
|
||||||
|
|
||||||
|
if (ext != NULL) {
|
||||||
|
size = ext - opts.infile + 5;
|
||||||
|
opts.palfile = malloc(size);
|
||||||
|
strncpy(opts.palfile, opts.infile, size);
|
||||||
|
*strrchr(opts.palfile, '.') = '\0';
|
||||||
|
strcat(opts.palfile, ".pal");
|
||||||
|
} else {
|
||||||
|
opts.palfile = malloc(strlen(opts.infile) + 5);
|
||||||
|
strcpy(opts.palfile, opts.infile);
|
||||||
|
strcat(opts.palfile, ".pal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gb.size = raw_image->width * raw_image->height * depth / 8;
|
||||||
|
gb.data = calloc(gb.size, 1);
|
||||||
|
gb.trim = opts.trim;
|
||||||
|
gb.horizontal = opts.horizontal;
|
||||||
|
|
||||||
|
if (*opts.outfile || *opts.tilemapfile || *opts.attrmapfile) {
|
||||||
|
raw_to_gb(raw_image, &gb);
|
||||||
|
create_mapfiles(&opts, &gb, &tilemap, &attrmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*opts.outfile)
|
||||||
|
output_file(&opts, &gb);
|
||||||
|
|
||||||
|
if (*opts.tilemapfile)
|
||||||
|
output_tilemap_file(&opts, &tilemap);
|
||||||
|
|
||||||
|
if (*opts.attrmapfile)
|
||||||
|
output_attrmap_file(&opts, &attrmap);
|
||||||
|
|
||||||
|
if (*opts.palfile)
|
||||||
|
output_palette_file(&opts, raw_image);
|
||||||
|
|
||||||
|
if (opts.fix || opts.debug)
|
||||||
|
output_png_file(&opts, &png_options, raw_image);
|
||||||
|
|
||||||
|
destroy_raw_image(&raw_image);
|
||||||
|
free(gb.data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
813
src/gfx/main.cpp
813
src/gfx/main.cpp
@@ -1,813 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <ios>
|
|
||||||
#include <limits>
|
|
||||||
#include <numeric>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "extern/getopt.h"
|
|
||||||
#include "platform.h"
|
|
||||||
#include "version.h"
|
|
||||||
|
|
||||||
#include "gfx/pal_spec.hpp"
|
|
||||||
#include "gfx/process.hpp"
|
|
||||||
#include "gfx/reverse.hpp"
|
|
||||||
|
|
||||||
using namespace std::literals::string_view_literals;
|
|
||||||
|
|
||||||
Options options;
|
|
||||||
char const *externalPalSpec = nullptr;
|
|
||||||
static uintmax_t nbErrors;
|
|
||||||
|
|
||||||
[[noreturn]] void giveUp() {
|
|
||||||
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void warning(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fputs("warning: ", stderr);
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void error(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fputs("error: ", stderr);
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
putc('\n', stderr);
|
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
|
||||||
nbErrors++;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]] void fatal(char const *fmt, ...) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
fputs("FATAL: ", stderr);
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
putc('\n', stderr);
|
|
||||||
|
|
||||||
if (nbErrors != std::numeric_limits<decltype(nbErrors)>::max())
|
|
||||||
nbErrors++;
|
|
||||||
|
|
||||||
giveUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
|
||||||
if (verbosity >= level) {
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Short options
|
|
||||||
static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:o:Pp:Qq:r:s:Tt:U:uVvx:Z";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Equivalent long options
|
|
||||||
* Please keep in the same order as short opts
|
|
||||||
*
|
|
||||||
* Also, make sure long opts don't create ambiguity:
|
|
||||||
* A long opt's name should start with the same letter as its short opt,
|
|
||||||
* except if it doesn't create any ambiguity (`verbose` versus `version`).
|
|
||||||
* This is because long opt matching, even to a single char, is prioritized
|
|
||||||
* over short opt matching
|
|
||||||
*/
|
|
||||||
static struct option const longopts[] = {
|
|
||||||
{"output-attr-map", no_argument, NULL, 'A'},
|
|
||||||
{"attr-map", required_argument, NULL, 'a'},
|
|
||||||
{"base-tiles", required_argument, NULL, 'b'},
|
|
||||||
{"color-curve", no_argument, NULL, 'C'},
|
|
||||||
{"colors", required_argument, NULL, 'c'},
|
|
||||||
{"debug", no_argument, NULL, 'D'}, // Ignored
|
|
||||||
{"depth", required_argument, NULL, 'd'},
|
|
||||||
{"fix", no_argument, NULL, 'f'},
|
|
||||||
{"fix-and-save", no_argument, NULL, 'F'}, // Deprecated
|
|
||||||
{"horizontal", no_argument, NULL, 'h'}, // Deprecated
|
|
||||||
{"slice", required_argument, NULL, 'L'},
|
|
||||||
{"mirror-tiles", no_argument, NULL, 'm'},
|
|
||||||
{"nb-tiles", required_argument, NULL, 'N'},
|
|
||||||
{"nb-palettes", required_argument, NULL, 'n'},
|
|
||||||
{"output", required_argument, NULL, 'o'},
|
|
||||||
{"output-palette", no_argument, NULL, 'P'},
|
|
||||||
{"palette", required_argument, NULL, 'p'},
|
|
||||||
{"output-palette-map", no_argument, NULL, 'Q'},
|
|
||||||
{"palette-map", required_argument, NULL, 'q'},
|
|
||||||
{"reverse", required_argument, NULL, 'r'},
|
|
||||||
{"output-tilemap", no_argument, NULL, 'T'},
|
|
||||||
{"tilemap", required_argument, NULL, 't'},
|
|
||||||
{"unit-size", required_argument, NULL, 'U'},
|
|
||||||
{"unique-tiles", no_argument, NULL, 'u'},
|
|
||||||
{"version", no_argument, NULL, 'V'},
|
|
||||||
{"verbose", no_argument, NULL, 'v'},
|
|
||||||
{"trim-end", required_argument, NULL, 'x'},
|
|
||||||
{"columns", no_argument, NULL, 'Z'},
|
|
||||||
{NULL, no_argument, NULL, 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
static void printUsage(void) {
|
|
||||||
fputs("Usage: rgbgfx [-r stride] [-CmuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
|
||||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
|
|
||||||
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
|
|
||||||
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
|
|
||||||
"Useful options:\n"
|
|
||||||
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
|
||||||
" -o, --output <path> output the tile data to this path\n"
|
|
||||||
" -t, --tilemap <path> output the tile map to this path\n"
|
|
||||||
" -u, --unique-tiles optimize out identical tiles\n"
|
|
||||||
" -V, --version print RGBGFX version and exit\n"
|
|
||||||
"\n"
|
|
||||||
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
|
|
||||||
stderr);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Parses a number at the beginning of a string, moving the pointer to skip the parsed characters
|
|
||||||
* Returns the provided errVal on error
|
|
||||||
*/
|
|
||||||
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
|
|
||||||
uint8_t base = 10;
|
|
||||||
if (*string == '\0') {
|
|
||||||
error("%s: expected number, but found nothing", errPrefix);
|
|
||||||
return errVal;
|
|
||||||
} else if (*string == '$') {
|
|
||||||
base = 16;
|
|
||||||
++string;
|
|
||||||
} else if (*string == '%') {
|
|
||||||
base = 2;
|
|
||||||
++string;
|
|
||||||
} else if (*string == '0' && string[1] != '\0') {
|
|
||||||
// Check if we have a "0x" or "0b" here
|
|
||||||
if (string[1] == 'x' || string[1] == 'X') {
|
|
||||||
base = 16;
|
|
||||||
string += 2;
|
|
||||||
} else if (string[1] == 'b' || string[1] == 'B') {
|
|
||||||
base = 2;
|
|
||||||
string += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Turns a digit into its numeric value in the current base, if it has one.
|
|
||||||
* Maximum is inclusive. The string_view is modified to "consume" all digits.
|
|
||||||
* Returns 255 on parse failure (including wrong char for base), in which case
|
|
||||||
* the string_view may be pointing on garbage.
|
|
||||||
*/
|
|
||||||
auto charIndex = [&base](unsigned char c) -> uint8_t {
|
|
||||||
unsigned char index = c - '0'; // Use wrapping semantics
|
|
||||||
if (base == 2 && index >= 2) {
|
|
||||||
return 255;
|
|
||||||
} else if (index < 10) {
|
|
||||||
return index;
|
|
||||||
} else if (base != 16) {
|
|
||||||
return 255; // Letters are only valid in hex
|
|
||||||
}
|
|
||||||
index = tolower(c) - 'a'; // OK because we pass an `unsigned char`
|
|
||||||
if (index < 6) {
|
|
||||||
return index + 10;
|
|
||||||
}
|
|
||||||
return 255;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (charIndex(*string) == 255) {
|
|
||||||
error("%s: expected digit%s, but found nothing", errPrefix,
|
|
||||||
base != 10 ? " after base" : "");
|
|
||||||
return errVal;
|
|
||||||
}
|
|
||||||
uint16_t number = 0;
|
|
||||||
do {
|
|
||||||
// Read a character, and check if it's valid in the given base
|
|
||||||
uint8_t index = charIndex(*string);
|
|
||||||
if (index == 255) {
|
|
||||||
break; // Found an invalid character, end
|
|
||||||
}
|
|
||||||
++string;
|
|
||||||
|
|
||||||
number *= base;
|
|
||||||
number += index;
|
|
||||||
// The lax check covers the addition on top of the multiplication
|
|
||||||
if (number >= UINT16_MAX / base) {
|
|
||||||
error("%s: the number is too large!", errPrefix);
|
|
||||||
return errVal;
|
|
||||||
}
|
|
||||||
} while (*string != '\0'); // No more characters?
|
|
||||||
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void skipWhitespace(char *&arg) {
|
|
||||||
arg += strspn(arg, " \t");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void registerInput(char const *arg) {
|
|
||||||
if (!options.input.empty()) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"FATAL: input image specified more than once! (first \"%s\", then "
|
|
||||||
"\"%s\")\n",
|
|
||||||
options.input.c_str(), arg);
|
|
||||||
printUsage();
|
|
||||||
exit(1);
|
|
||||||
} else if (arg[0] == '\0') { // Empty input path
|
|
||||||
fprintf(stderr, "FATAL: input image path cannot be empty!\n");
|
|
||||||
printUsage();
|
|
||||||
exit(1);
|
|
||||||
} else {
|
|
||||||
options.input = arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Turn an "at-file"'s contents into an argv that `getopt` can handle
|
|
||||||
* @param argPool Argument characters will be appended to this vector, for storage purposes.
|
|
||||||
*/
|
|
||||||
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
|
||||||
std::filebuf file;
|
|
||||||
file.open(path, std::ios_base::in);
|
|
||||||
|
|
||||||
static_assert(decltype(file)::traits_type::eof() == EOF,
|
|
||||||
"isblank(char_traits<...>::eof()) is UB!");
|
|
||||||
std::vector<size_t> argvOfs;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
int c;
|
|
||||||
|
|
||||||
// First, discard any leading whitespace
|
|
||||||
do {
|
|
||||||
c = file.sbumpc();
|
|
||||||
if (c == EOF) {
|
|
||||||
return argvOfs;
|
|
||||||
}
|
|
||||||
} while (isblank(c));
|
|
||||||
|
|
||||||
switch (c) {
|
|
||||||
case '#': // If it's a comment, discard everything until EOL
|
|
||||||
while ((c = file.sbumpc()) != '\n') {
|
|
||||||
if (c == EOF) {
|
|
||||||
return argvOfs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue; // Start processing the next line
|
|
||||||
// If it's an empty line, ignore it
|
|
||||||
case '\r': // Assuming CRLF here
|
|
||||||
file.sbumpc(); // Discard the upcoming '\n'
|
|
||||||
[[fallthrough]];
|
|
||||||
case '\n':
|
|
||||||
continue; // Start processing the next line
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alright, now we can parse the line
|
|
||||||
do {
|
|
||||||
// Read one argument (until the next whitespace char).
|
|
||||||
// We know there is one because we already have its first character in `c`.
|
|
||||||
argvOfs.push_back(argPool.size());
|
|
||||||
// Reading and appending characters one at a time may be inefficient, but I'm counting
|
|
||||||
// on `vector` and `sbumpc` to do the right thing here.
|
|
||||||
argPool.push_back(c); // Push the character we've already read
|
|
||||||
for (;;) {
|
|
||||||
c = file.sbumpc();
|
|
||||||
if (isblank(c) || c == '\n' || c == EOF) {
|
|
||||||
break;
|
|
||||||
} else if (c == '\r') {
|
|
||||||
file.sbumpc(); // Discard the '\n'
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
argPool.push_back(c);
|
|
||||||
}
|
|
||||||
argPool.push_back('\0');
|
|
||||||
|
|
||||||
// Discard whitespace until the next argument (candidate)
|
|
||||||
while (isblank(c)) {
|
|
||||||
c = file.sbumpc();
|
|
||||||
}
|
|
||||||
if (c == '\r') {
|
|
||||||
c = file.sbumpc(); // Skip the '\n'
|
|
||||||
}
|
|
||||||
} while (c != '\n' && c != EOF); // End if we reached EOL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Parses an arg vector, modifying `options` as options are read.
|
|
||||||
* The three booleans are for the "auto path" flags, since their processing must be deferred to the
|
|
||||||
* end of option parsing.
|
|
||||||
*
|
|
||||||
* Returns NULL if the vector was fully parsed, or a pointer (which is part of the arg vector) to an
|
|
||||||
* "at-file" path if one is encountered.
|
|
||||||
*/
|
|
||||||
static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilemap,
|
|
||||||
bool &autoPalettes, bool &autoPalmap) {
|
|
||||||
int opt;
|
|
||||||
|
|
||||||
while ((opt = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1) {
|
|
||||||
char *arg = musl_optarg; // Make a copy for scanning
|
|
||||||
switch (opt) {
|
|
||||||
case 'A':
|
|
||||||
autoAttrmap = true;
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
autoAttrmap = false;
|
|
||||||
options.attrmap = musl_optarg;
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
options.baseTileIDs[0] = parseNumber(arg, "Bank 0 base tile ID", 0);
|
|
||||||
if (options.baseTileIDs[0] >= 256) {
|
|
||||||
error("Bank 0 base tile ID must be below 256");
|
|
||||||
}
|
|
||||||
if (*arg == '\0') {
|
|
||||||
options.baseTileIDs[1] = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
skipWhitespace(arg);
|
|
||||||
if (*arg != ',') {
|
|
||||||
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg; // Skip comma
|
|
||||||
skipWhitespace(arg);
|
|
||||||
options.baseTileIDs[1] = parseNumber(arg, "Bank 1 base tile ID", 0);
|
|
||||||
if (options.baseTileIDs[1] >= 256) {
|
|
||||||
error("Bank 1 base tile ID must be below 256");
|
|
||||||
}
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'C':
|
|
||||||
options.useColorCurve = true;
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
if (musl_optarg[0] == '#') {
|
|
||||||
options.palSpecType = Options::EXPLICIT;
|
|
||||||
parseInlinePalSpec(musl_optarg);
|
|
||||||
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
|
|
||||||
// Use PLTE, error out if missing
|
|
||||||
options.palSpecType = Options::EMBEDDED;
|
|
||||||
} else {
|
|
||||||
options.palSpecType = Options::EXPLICIT;
|
|
||||||
// Can't parse the file yet, as "flat" color collections need to know the palette
|
|
||||||
// size to be split; thus, we defer that
|
|
||||||
// TODO: this does not validate the `fmt` part of any external spec but the last
|
|
||||||
// one, but I guess that's okay
|
|
||||||
externalPalSpec = musl_optarg;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
options.bitDepth = parseNumber(arg, "Bit depth", 2);
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Bit depth (-b) argument must be a valid number, not \"%s\"", musl_optarg);
|
|
||||||
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
|
|
||||||
error("Bit depth must be 1 or 2, not %" PRIu8);
|
|
||||||
options.bitDepth = 2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'L':
|
|
||||||
options.inputSlice.left = parseNumber(arg, "Input slice left coordinate");
|
|
||||||
if (options.inputSlice.left > INT16_MAX) {
|
|
||||||
error("Input slice left coordinate is out of range!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
skipWhitespace(arg);
|
|
||||||
if (*arg != ',') {
|
|
||||||
error("Missing comma after left coordinate in \"%s\"", musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg;
|
|
||||||
skipWhitespace(arg);
|
|
||||||
options.inputSlice.top = parseNumber(arg, "Input slice upper coordinate");
|
|
||||||
skipWhitespace(arg);
|
|
||||||
if (*arg != ':') {
|
|
||||||
error("Missing colon after upper coordinate in \"%s\"", musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg;
|
|
||||||
skipWhitespace(arg);
|
|
||||||
options.inputSlice.width = parseNumber(arg, "Input slice width");
|
|
||||||
skipWhitespace(arg);
|
|
||||||
if (options.inputSlice.width == 0) {
|
|
||||||
error("Input slice width may not be 0!");
|
|
||||||
}
|
|
||||||
if (*arg != ',') {
|
|
||||||
error("Missing comma after width in \"%s\"", musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg;
|
|
||||||
skipWhitespace(arg);
|
|
||||||
options.inputSlice.height = parseNumber(arg, "Input slice height");
|
|
||||||
if (options.inputSlice.height == 0) {
|
|
||||||
error("Input slice height may not be 0!");
|
|
||||||
}
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Unexpected extra characters after slice spec in \"%s\"", musl_optarg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'm':
|
|
||||||
options.allowMirroring = true;
|
|
||||||
[[fallthrough]]; // Imply `-u`
|
|
||||||
case 'u':
|
|
||||||
options.allowDedup = true;
|
|
||||||
break;
|
|
||||||
case 'N':
|
|
||||||
options.maxNbTiles[0] = parseNumber(arg, "Number of tiles in bank 0", 256);
|
|
||||||
if (options.maxNbTiles[0] > 256) {
|
|
||||||
error("Bank 0 cannot contain more than 256 tiles");
|
|
||||||
}
|
|
||||||
if (*arg == '\0') {
|
|
||||||
options.maxNbTiles[1] = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
skipWhitespace(arg);
|
|
||||||
if (*arg != ',') {
|
|
||||||
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++arg; // Skip comma
|
|
||||||
skipWhitespace(arg);
|
|
||||||
options.maxNbTiles[1] = parseNumber(arg, "Number of tiles in bank 1", 256);
|
|
||||||
if (options.maxNbTiles[1] > 256) {
|
|
||||||
error("Bank 1 cannot contain more than 256 tiles");
|
|
||||||
}
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
options.nbPalettes = parseNumber(arg, "Number of palettes", 256);
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Number of palettes (-n) must be a valid number, not \"%s\"", musl_optarg);
|
|
||||||
}
|
|
||||||
if (options.nbPalettes > 256) {
|
|
||||||
error("Number of palettes (-n) must not exceed 256!");
|
|
||||||
} else if (options.nbPalettes == 0) {
|
|
||||||
error("Number of palettes (-n) may not be 0!");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'o':
|
|
||||||
options.output = musl_optarg;
|
|
||||||
break;
|
|
||||||
case 'P':
|
|
||||||
autoPalettes = true;
|
|
||||||
break;
|
|
||||||
case 'p':
|
|
||||||
autoPalettes = false;
|
|
||||||
options.palettes = musl_optarg;
|
|
||||||
break;
|
|
||||||
case 'Q':
|
|
||||||
autoPalmap = true;
|
|
||||||
break;
|
|
||||||
case 'q':
|
|
||||||
autoPalmap = false;
|
|
||||||
options.palmap = musl_optarg;
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
options.reversedWidth = parseNumber(arg, "Reversed image stride");
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Reversed image stride (-r) must be a valid number, not \"%s\"", musl_optarg);
|
|
||||||
}
|
|
||||||
if (options.reversedWidth == 0) {
|
|
||||||
error("Reversed image stride (-r) may not be 0!");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
options.nbColorsPerPal = parseNumber(arg, "Number of colors per palette", 4);
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Palette size (-s) must be a valid number, not \"%s\"", musl_optarg);
|
|
||||||
}
|
|
||||||
if (options.nbColorsPerPal > 4) {
|
|
||||||
error("Palette size (-s) must not exceed 4!");
|
|
||||||
} else if (options.nbColorsPerPal == 0) {
|
|
||||||
error("Palette size (-s) may not be 0!");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'T':
|
|
||||||
autoTilemap = true;
|
|
||||||
break;
|
|
||||||
case 't':
|
|
||||||
autoTilemap = false;
|
|
||||||
options.tilemap = musl_optarg;
|
|
||||||
break;
|
|
||||||
case 'V':
|
|
||||||
printf("rgbgfx %s\n", get_package_version_string());
|
|
||||||
exit(0);
|
|
||||||
case 'v':
|
|
||||||
if (options.verbosity < Options::VERB_VVVVVV) {
|
|
||||||
++options.verbosity;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'x':
|
|
||||||
options.trim = parseNumber(arg, "Number of tiles to trim", 0);
|
|
||||||
if (*arg != '\0') {
|
|
||||||
error("Tile trim (-x) argument must be a valid number, not \"%s\"", musl_optarg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
warning("`-h` is deprecated, use `-Z` instead");
|
|
||||||
[[fallthrough]];
|
|
||||||
case 'Z':
|
|
||||||
options.columnMajor = true;
|
|
||||||
break;
|
|
||||||
case 1: // Positional argument, requested by leading `-` in opt string
|
|
||||||
if (musl_optarg[0] == '@') {
|
|
||||||
// Instruct the caller to process that at-file
|
|
||||||
return &musl_optarg[1];
|
|
||||||
} else {
|
|
||||||
registerInput(musl_optarg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'D':
|
|
||||||
case 'F':
|
|
||||||
case 'f':
|
|
||||||
warning("Ignoring retired option `-%c`", opt);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
printUsage();
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr; // Done processing this argv
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
bool autoAttrmap = false, autoTilemap = false, autoPalettes = false, autoPalmap = false;
|
|
||||||
|
|
||||||
struct AtFileStackEntry {
|
|
||||||
int parentInd; // Saved offset into parent argv
|
|
||||||
std::vector<char *> argv; // This context's arg pointer vec
|
|
||||||
std::vector<char> argPool;
|
|
||||||
|
|
||||||
AtFileStackEntry(int parentInd_, std::vector<char *> argv_)
|
|
||||||
: parentInd(parentInd_), argv(argv_) {}
|
|
||||||
};
|
|
||||||
std::vector<AtFileStackEntry> atFileStack;
|
|
||||||
|
|
||||||
int curArgc = argc;
|
|
||||||
char **curArgv = argv;
|
|
||||||
for (;;) {
|
|
||||||
char *atFileName =
|
|
||||||
parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes, autoPalmap);
|
|
||||||
if (atFileName) {
|
|
||||||
// Copy `argv[0]` for error reporting, and because option parsing skips it
|
|
||||||
AtFileStackEntry &stackEntry =
|
|
||||||
atFileStack.emplace_back(musl_optind, std::vector{atFileName});
|
|
||||||
// It would be nice to compute the char pointers on the fly, but reallocs don't allow
|
|
||||||
// that; so we must compute the offsets after the pool is fixed
|
|
||||||
auto offsets = readAtFile(&musl_optarg[1], stackEntry.argPool);
|
|
||||||
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
|
|
||||||
for (size_t ofs : offsets) {
|
|
||||||
stackEntry.argv.push_back(&stackEntry.argPool.data()[ofs]);
|
|
||||||
}
|
|
||||||
stackEntry.argv.push_back(nullptr); // Don't forget the arg vector terminator!
|
|
||||||
|
|
||||||
curArgc = stackEntry.argv.size() - 1;
|
|
||||||
curArgv = stackEntry.argv.data();
|
|
||||||
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
|
|
||||||
continue; // Begin scanning that arg vector
|
|
||||||
}
|
|
||||||
|
|
||||||
if (musl_optind != curArgc) {
|
|
||||||
// This happens if `--` is passed, process the remaining arg(s) as positional
|
|
||||||
assert(musl_optind < curArgc);
|
|
||||||
for (int i = musl_optind; i < curArgc; ++i) {
|
|
||||||
registerInput(argv[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop off the top stack entry, or end parsing if none
|
|
||||||
if (atFileStack.empty()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// OK to restore `optind` directly, because `optpos` must be 0 right now.
|
|
||||||
// (Providing 0 would be a "proper" reset, but we want to resume parsing)
|
|
||||||
musl_optind = atFileStack.back().parentInd;
|
|
||||||
atFileStack.pop_back();
|
|
||||||
if (atFileStack.empty()) {
|
|
||||||
curArgc = argc;
|
|
||||||
curArgv = argv;
|
|
||||||
} else {
|
|
||||||
auto &vec = atFileStack.back().argv;
|
|
||||||
curArgc = vec.size();
|
|
||||||
curArgv = vec.data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.nbColorsPerPal == 0) {
|
|
||||||
options.nbColorsPerPal = 1u << options.bitDepth;
|
|
||||||
} else if (options.nbColorsPerPal > 1u << options.bitDepth) {
|
|
||||||
error("%" PRIu8 "bpp palettes can only contain %u colors, not %" PRIu8, options.bitDepth,
|
|
||||||
1u << options.bitDepth, options.nbColorsPerPal);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) {
|
|
||||||
if (autoOptEnabled) {
|
|
||||||
constexpr std::string_view chars =
|
|
||||||
// Both must start with a dot!
|
|
||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
|
||||||
"./\\"sv;
|
|
||||||
#else
|
|
||||||
"./"sv;
|
|
||||||
#endif
|
|
||||||
size_t len = options.input.npos;
|
|
||||||
size_t i = options.input.find_last_of(chars);
|
|
||||||
if (i != options.input.npos && options.input[i] == '.') {
|
|
||||||
// We found the last dot, but check if it's part of a stem
|
|
||||||
// (There must be a non-path separator character before it)
|
|
||||||
if (i != 0 && chars.find(options.input[i - 1], 1) == chars.npos) {
|
|
||||||
// We can replace the extension
|
|
||||||
len = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
path.assign(options.input, 0, len);
|
|
||||||
path.append(extension);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
autoOutPath(autoAttrmap, options.attrmap, ".attrmap");
|
|
||||||
autoOutPath(autoTilemap, options.tilemap, ".tilemap");
|
|
||||||
autoOutPath(autoPalettes, options.palettes, ".pal");
|
|
||||||
autoOutPath(autoPalmap, options.palmap, ".palmap");
|
|
||||||
|
|
||||||
// Execute deferred external pal spec parsing, now that all other params are known
|
|
||||||
if (externalPalSpec) {
|
|
||||||
parseExternalPalSpec(externalPalSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.verbosity >= Options::VERB_CFG) {
|
|
||||||
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
|
|
||||||
|
|
||||||
if (options.verbosity >= Options::VERB_VVVVVV) {
|
|
||||||
fputc('\n', stderr);
|
|
||||||
static std::array<uint16_t, 21> gfx{
|
|
||||||
0x1FE, 0x3FF, 0x399, 0x399, 0x3FF, 0x3FF, 0x381, 0x3C3, 0x1FE, 0x078, 0x1FE,
|
|
||||||
0x3FF, 0x3FF, 0x3FF, 0x37B, 0x37B, 0x0FC, 0x0CC, 0x1CE, 0x1CE, 0x1CE,
|
|
||||||
};
|
|
||||||
static std::array<char const *, 3> textbox{
|
|
||||||
" ,----------------------------------------.",
|
|
||||||
" | Augh, dimensional interference again?! |",
|
|
||||||
" `----------------------------------------'"};
|
|
||||||
for (size_t i = 0; i < gfx.size(); ++i) {
|
|
||||||
uint16_t row = gfx[i];
|
|
||||||
for (uint8_t _ = 0; _ < 10; ++_) {
|
|
||||||
unsigned char c = row & 1 ? '0' : ' ';
|
|
||||||
fputc(c, stderr);
|
|
||||||
// Double the pixel horizontally, otherwise the aspect ratio looks wrong
|
|
||||||
fputc(c, stderr);
|
|
||||||
row >>= 1;
|
|
||||||
}
|
|
||||||
if (i < textbox.size()) {
|
|
||||||
fputs(textbox[i], stderr);
|
|
||||||
}
|
|
||||||
fputc('\n', stderr);
|
|
||||||
}
|
|
||||||
fputc('\n', stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fputs("Options:\n", stderr);
|
|
||||||
if (options.columnMajor)
|
|
||||||
fputs("\tVisit image in column-major order\n", stderr);
|
|
||||||
if (options.allowMirroring)
|
|
||||||
fputs("\tAllow mirroring tiles\n", stderr);
|
|
||||||
if (options.allowDedup)
|
|
||||||
fputs("\tAllow deduplicating tiles\n", stderr);
|
|
||||||
if (options.useColorCurve)
|
|
||||||
fputs("\tUse color curve\n", stderr);
|
|
||||||
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
|
|
||||||
if (options.trim != 0)
|
|
||||||
fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
|
|
||||||
fprintf(stderr, "\tMaximum %" PRIu8 " palettes\n", options.nbPalettes);
|
|
||||||
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
|
|
||||||
fprintf(stderr, "\t%s palette spec\n", []() {
|
|
||||||
switch (options.palSpecType) {
|
|
||||||
case Options::NO_SPEC:
|
|
||||||
return "No";
|
|
||||||
case Options::EXPLICIT:
|
|
||||||
return "Explicit";
|
|
||||||
case Options::EMBEDDED:
|
|
||||||
return "Embedded";
|
|
||||||
}
|
|
||||||
return "???";
|
|
||||||
}());
|
|
||||||
if (options.palSpecType == Options::EXPLICIT) {
|
|
||||||
fputs("\t[\n", stderr);
|
|
||||||
for (std::array<Rgba, 4> const &pal : options.palSpec) {
|
|
||||||
fprintf(stderr, "\t\t#%06x, #%06x, #%06x, #%06x,\n", pal[0].toCSS() >> 8,
|
|
||||||
pal[1].toCSS() >> 8, pal[2].toCSS() >> 8, pal[3].toCSS() >> 8);
|
|
||||||
}
|
|
||||||
fputs("\t]\n", stderr);
|
|
||||||
}
|
|
||||||
fprintf(stderr,
|
|
||||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32
|
|
||||||
", %" PRIi32 ")\n",
|
|
||||||
options.inputSlice.width, options.inputSlice.height, options.inputSlice.left,
|
|
||||||
options.inputSlice.top);
|
|
||||||
fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0],
|
|
||||||
options.baseTileIDs[1]);
|
|
||||||
fprintf(stderr, "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
|
|
||||||
options.maxNbTiles[0], options.maxNbTiles[1]);
|
|
||||||
auto printPath = [](char const *name, std::string const &path) {
|
|
||||||
if (!path.empty()) {
|
|
||||||
fprintf(stderr, "\t%s: %s\n", name, path.c_str());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
printPath("Input image", options.input);
|
|
||||||
printPath("Output tile data", options.output);
|
|
||||||
printPath("Output tilemap", options.tilemap);
|
|
||||||
printPath("Output attrmap", options.attrmap);
|
|
||||||
printPath("Output palettes", options.palettes);
|
|
||||||
fputs("Ready.\n", stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.input.empty()) {
|
|
||||||
fatal("No input image specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not do anything if option parsing went wrong
|
|
||||||
if (nbErrors) {
|
|
||||||
giveUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.reverse()) {
|
|
||||||
reverse();
|
|
||||||
} else {
|
|
||||||
process();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbErrors) {
|
|
||||||
giveUp();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Palette::addColor(uint16_t color) {
|
|
||||||
for (size_t i = 0; true; ++i) {
|
|
||||||
assert(i < colors.size()); // The packing should guarantee this
|
|
||||||
if (colors[i] == color) { // The color is already present
|
|
||||||
break;
|
|
||||||
} else if (colors[i] == UINT16_MAX) { // Empty slot
|
|
||||||
colors[i] = color;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns the ID of the color in the palette, or `size()` if the color is not in
|
|
||||||
*/
|
|
||||||
uint8_t Palette::indexOf(uint16_t color) const {
|
|
||||||
return std::find(colors.begin(), colors.end(), color) - colors.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Palette::begin() -> decltype(colors)::iterator {
|
|
||||||
// Skip the first slot if reserved for transparency
|
|
||||||
return colors.begin() + options.hasTransparentPixels;
|
|
||||||
}
|
|
||||||
auto Palette::end() -> decltype(colors)::iterator {
|
|
||||||
return std::find(begin(), colors.end(), UINT16_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Palette::begin() const -> decltype(colors)::const_iterator {
|
|
||||||
// Skip the first slot if reserved for transparency
|
|
||||||
return colors.begin() + options.hasTransparentPixels;
|
|
||||||
}
|
|
||||||
auto Palette::end() const -> decltype(colors)::const_iterator {
|
|
||||||
return std::find(begin(), colors.end(), UINT16_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t Palette::size() const {
|
|
||||||
return indexOf(UINT16_MAX);
|
|
||||||
}
|
|
||||||
806
src/gfx/makepng.c
Normal file
806
src/gfx/makepng.c
Normal file
@@ -0,0 +1,806 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2018, stag019 and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <png.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "gfx/makepng.h"
|
||||||
|
|
||||||
|
static void initialize_png(struct PNGImage *img, FILE * f);
|
||||||
|
static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img);
|
||||||
|
static struct RawIndexedImage *grayscale_png_to_raw(struct PNGImage *img);
|
||||||
|
static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img);
|
||||||
|
static void get_text(const struct PNGImage *img,
|
||||||
|
struct ImageOptions *png_options);
|
||||||
|
static void set_text(const struct PNGImage *img,
|
||||||
|
const struct ImageOptions *png_options);
|
||||||
|
static void free_png_data(const struct PNGImage *png);
|
||||||
|
|
||||||
|
struct RawIndexedImage *input_png_file(const struct Options *opts,
|
||||||
|
struct ImageOptions *png_options)
|
||||||
|
{
|
||||||
|
struct PNGImage img;
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = fopen(opts->infile, "rb");
|
||||||
|
if (!f)
|
||||||
|
err("Opening input png file '%s' failed", opts->infile);
|
||||||
|
|
||||||
|
initialize_png(&img, f);
|
||||||
|
|
||||||
|
if (img.depth != depth) {
|
||||||
|
if (opts->verbose) {
|
||||||
|
warnx("Image bit depth is not %d (is %d).",
|
||||||
|
depth, img.depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (img.type) {
|
||||||
|
case PNG_COLOR_TYPE_PALETTE:
|
||||||
|
raw_image = indexed_png_to_raw(&img); break;
|
||||||
|
case PNG_COLOR_TYPE_GRAY:
|
||||||
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||||
|
raw_image = grayscale_png_to_raw(&img); break;
|
||||||
|
case PNG_COLOR_TYPE_RGB:
|
||||||
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
||||||
|
raw_image = truecolor_png_to_raw(&img); break;
|
||||||
|
default:
|
||||||
|
/* Shouldn't happen, but might as well handle just in case. */
|
||||||
|
errx("Input PNG file is of invalid color type.");
|
||||||
|
}
|
||||||
|
|
||||||
|
get_text(&img, png_options);
|
||||||
|
|
||||||
|
png_destroy_read_struct(&img.png, &img.info, NULL);
|
||||||
|
fclose(f);
|
||||||
|
free_png_data(&img);
|
||||||
|
|
||||||
|
return raw_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
void output_png_file(const struct Options *opts,
|
||||||
|
const struct ImageOptions *png_options,
|
||||||
|
const struct RawIndexedImage *raw_image)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
char *outfile;
|
||||||
|
struct PNGImage img;
|
||||||
|
png_color *png_palette;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Variable outfile is for debugging purposes. Eventually,
|
||||||
|
* opts.infile will be used directly.
|
||||||
|
*/
|
||||||
|
if (opts->debug) {
|
||||||
|
outfile = malloc(strlen(opts->infile) + 5);
|
||||||
|
if (!outfile)
|
||||||
|
err("%s: Failed to allocate memory for outfile",
|
||||||
|
__func__);
|
||||||
|
strcpy(outfile, opts->infile);
|
||||||
|
strcat(outfile, ".out");
|
||||||
|
} else {
|
||||||
|
outfile = opts->infile;
|
||||||
|
}
|
||||||
|
|
||||||
|
f = fopen(outfile, "wb");
|
||||||
|
if (!f)
|
||||||
|
err("Opening output png file '%s' failed", outfile);
|
||||||
|
|
||||||
|
if (opts->debug)
|
||||||
|
free(outfile);
|
||||||
|
|
||||||
|
img.png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
||||||
|
NULL, NULL, NULL);
|
||||||
|
if (!img.png)
|
||||||
|
errx("Creating png structure failed");
|
||||||
|
|
||||||
|
img.info = png_create_info_struct(img.png);
|
||||||
|
if (!img.info)
|
||||||
|
errx("Creating png info structure failed");
|
||||||
|
|
||||||
|
if (setjmp(png_jmpbuf(img.png)))
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
png_init_io(img.png, f);
|
||||||
|
|
||||||
|
png_set_IHDR(img.png, img.info, raw_image->width, raw_image->height,
|
||||||
|
8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
|
||||||
|
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||||
|
|
||||||
|
png_palette = malloc(sizeof(*png_palette) * raw_image->num_colors);
|
||||||
|
if (!png_palette)
|
||||||
|
err("%s: Failed to allocate memory for PNG palette",
|
||||||
|
__func__);
|
||||||
|
for (i = 0; i < raw_image->num_colors; i++) {
|
||||||
|
png_palette[i].red = raw_image->palette[i].red;
|
||||||
|
png_palette[i].green = raw_image->palette[i].green;
|
||||||
|
png_palette[i].blue = raw_image->palette[i].blue;
|
||||||
|
}
|
||||||
|
png_set_PLTE(img.png, img.info, png_palette, raw_image->num_colors);
|
||||||
|
free(png_palette);
|
||||||
|
|
||||||
|
if (opts->fix)
|
||||||
|
set_text(&img, png_options);
|
||||||
|
|
||||||
|
png_write_info(img.png, img.info);
|
||||||
|
|
||||||
|
png_write_image(img.png, (png_byte **) raw_image->data);
|
||||||
|
png_write_end(img.png, NULL);
|
||||||
|
|
||||||
|
png_destroy_write_struct(&img.png, &img.info);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_raw_image(struct RawIndexedImage **raw_image_ptr_ptr)
|
||||||
|
{
|
||||||
|
struct RawIndexedImage *raw_image = *raw_image_ptr_ptr;
|
||||||
|
|
||||||
|
for (unsigned int y = 0; y < raw_image->height; y++)
|
||||||
|
free(raw_image->data[y]);
|
||||||
|
|
||||||
|
free(raw_image->data);
|
||||||
|
free(raw_image->palette);
|
||||||
|
free(raw_image);
|
||||||
|
*raw_image_ptr_ptr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initialize_png(struct PNGImage *img, FILE *f)
|
||||||
|
{
|
||||||
|
img->png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
||||||
|
NULL, NULL, NULL);
|
||||||
|
if (!img->png)
|
||||||
|
errx("Creating png structure failed");
|
||||||
|
|
||||||
|
img->info = png_create_info_struct(img->png);
|
||||||
|
if (!img->info)
|
||||||
|
errx("Creating png info structure failed");
|
||||||
|
|
||||||
|
if (setjmp(png_jmpbuf(img->png)))
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
png_init_io(img->png, f);
|
||||||
|
|
||||||
|
png_read_info(img->png, img->info);
|
||||||
|
|
||||||
|
img->width = png_get_image_width(img->png, img->info);
|
||||||
|
img->height = png_get_image_height(img->png, img->info);
|
||||||
|
img->depth = png_get_bit_depth(img->png, img->info);
|
||||||
|
img->type = png_get_color_type(img->png, img->info);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void read_png(struct PNGImage *img);
|
||||||
|
static struct RawIndexedImage *create_raw_image(int width, int height,
|
||||||
|
int num_colors);
|
||||||
|
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
|
||||||
|
png_color const *palette, int num_colors);
|
||||||
|
|
||||||
|
static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img)
|
||||||
|
{
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
png_color *palette;
|
||||||
|
int colors_in_PLTE;
|
||||||
|
int colors_in_new_palette;
|
||||||
|
png_byte *trans_alpha;
|
||||||
|
int num_trans;
|
||||||
|
png_color_16 *trans_color;
|
||||||
|
png_color *original_palette;
|
||||||
|
uint8_t *old_to_new_palette;
|
||||||
|
int i, x, y;
|
||||||
|
|
||||||
|
if (img->depth < 8)
|
||||||
|
png_set_packing(img->png);
|
||||||
|
|
||||||
|
png_get_PLTE(img->png, img->info, &palette, &colors_in_PLTE);
|
||||||
|
|
||||||
|
raw_image = create_raw_image(img->width, img->height, colors);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transparent palette entries are removed, and the palette is
|
||||||
|
* collapsed. Transparent pixels are then replaced with palette index 0.
|
||||||
|
* This way, an indexed PNG can contain transparent pixels in *addition*
|
||||||
|
* to 4 normal colors.
|
||||||
|
*/
|
||||||
|
if (png_get_tRNS(img->png, img->info, &trans_alpha, &num_trans,
|
||||||
|
&trans_color)) {
|
||||||
|
original_palette = palette;
|
||||||
|
palette = malloc(sizeof(*palette) * colors_in_PLTE);
|
||||||
|
if (!palette)
|
||||||
|
err("%s: Failed to allocate memory for palette",
|
||||||
|
__func__);
|
||||||
|
colors_in_new_palette = 0;
|
||||||
|
old_to_new_palette = malloc(sizeof(*old_to_new_palette)
|
||||||
|
* colors_in_PLTE);
|
||||||
|
if (!old_to_new_palette)
|
||||||
|
err("%s: Failed to allocate memory for new palette",
|
||||||
|
__func__);
|
||||||
|
|
||||||
|
for (i = 0; i < num_trans; i++) {
|
||||||
|
if (trans_alpha[i] == 0) {
|
||||||
|
old_to_new_palette[i] = 0;
|
||||||
|
} else {
|
||||||
|
old_to_new_palette[i] = colors_in_new_palette;
|
||||||
|
palette[colors_in_new_palette++] =
|
||||||
|
original_palette[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = num_trans; i < colors_in_PLTE; i++) {
|
||||||
|
old_to_new_palette[i] = colors_in_new_palette;
|
||||||
|
palette[colors_in_new_palette++] = original_palette[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colors_in_new_palette != colors_in_PLTE) {
|
||||||
|
palette = realloc(palette,
|
||||||
|
sizeof(*palette) *
|
||||||
|
colors_in_new_palette);
|
||||||
|
if (!palette)
|
||||||
|
err("%s: Failed to allocate memory for palette",
|
||||||
|
__func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setting and validating palette before reading
|
||||||
|
* allows us to error out *before* doing the data
|
||||||
|
* transformation if the palette is too long.
|
||||||
|
*/
|
||||||
|
set_raw_image_palette(raw_image, palette,
|
||||||
|
colors_in_new_palette);
|
||||||
|
read_png(img);
|
||||||
|
|
||||||
|
for (y = 0; y < img->height; y++) {
|
||||||
|
for (x = 0; x < img->width; x++) {
|
||||||
|
raw_image->data[y][x] =
|
||||||
|
old_to_new_palette[img->data[y][x]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(palette);
|
||||||
|
free(old_to_new_palette);
|
||||||
|
} else {
|
||||||
|
set_raw_image_palette(raw_image, palette, colors_in_PLTE);
|
||||||
|
read_png(img);
|
||||||
|
|
||||||
|
for (y = 0; y < img->height; y++) {
|
||||||
|
for (x = 0; x < img->width; x++)
|
||||||
|
raw_image->data[y][x] = img->data[y][x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct RawIndexedImage *grayscale_png_to_raw(struct PNGImage *img)
|
||||||
|
{
|
||||||
|
if (img->depth < 8)
|
||||||
|
png_set_expand_gray_1_2_4_to_8(img->png);
|
||||||
|
|
||||||
|
png_set_gray_to_rgb(img->png);
|
||||||
|
return truecolor_png_to_raw(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rgba_png_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors);
|
||||||
|
static struct RawIndexedImage
|
||||||
|
*processed_rgba_png_to_raw(const struct PNGImage *img,
|
||||||
|
png_color const *palette,
|
||||||
|
int colors_in_palette);
|
||||||
|
|
||||||
|
static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img)
|
||||||
|
{
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
png_color *palette;
|
||||||
|
int colors_in_palette;
|
||||||
|
|
||||||
|
if (img->depth == 16) {
|
||||||
|
#if PNG_LIBPNG_VER >= 10504
|
||||||
|
png_set_scale_16(img->png);
|
||||||
|
#else
|
||||||
|
png_set_strip_16(img->png);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(img->type & PNG_COLOR_MASK_ALPHA)) {
|
||||||
|
if (png_get_valid(img->png, img->info, PNG_INFO_tRNS))
|
||||||
|
png_set_tRNS_to_alpha(img->png);
|
||||||
|
else
|
||||||
|
png_set_add_alpha(img->png, 0xFF, PNG_FILLER_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
read_png(img);
|
||||||
|
|
||||||
|
rgba_png_palette(img, &palette, &colors_in_palette);
|
||||||
|
raw_image = processed_rgba_png_to_raw(img, palette, colors_in_palette);
|
||||||
|
|
||||||
|
free(palette);
|
||||||
|
|
||||||
|
return raw_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rgba_PLTE_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors);
|
||||||
|
static void rgba_build_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors);
|
||||||
|
|
||||||
|
static void rgba_png_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors)
|
||||||
|
{
|
||||||
|
if (png_get_valid(img->png, img->info, PNG_INFO_PLTE))
|
||||||
|
rgba_PLTE_palette(img, palette_ptr_ptr, num_colors);
|
||||||
|
else
|
||||||
|
rgba_build_palette(img, palette_ptr_ptr, num_colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rgba_PLTE_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors)
|
||||||
|
{
|
||||||
|
png_get_PLTE(img->png, img->info, palette_ptr_ptr, num_colors);
|
||||||
|
/*
|
||||||
|
* Lets us free the palette manually instead of leaving it to libpng,
|
||||||
|
* which lets us handle a PLTE and a built palette the same way.
|
||||||
|
*/
|
||||||
|
png_data_freer(img->png, img->info,
|
||||||
|
PNG_USER_WILL_FREE_DATA, PNG_FREE_PLTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_built_palette(png_color *palette,
|
||||||
|
png_color const *pixel_color, png_byte alpha,
|
||||||
|
int *num_colors, bool *only_grayscale);
|
||||||
|
static int fit_grayscale_palette(png_color *palette, int *num_colors);
|
||||||
|
static void order_color_palette(png_color *palette, int num_colors);
|
||||||
|
|
||||||
|
static void rgba_build_palette(struct PNGImage *img,
|
||||||
|
png_color **palette_ptr_ptr, int *num_colors)
|
||||||
|
{
|
||||||
|
png_color *palette;
|
||||||
|
int y, value_index;
|
||||||
|
png_color cur_pixel_color;
|
||||||
|
png_byte cur_alpha;
|
||||||
|
bool only_grayscale = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By filling the palette up with black by default, if the image
|
||||||
|
* doesn't have enough colors, the palette gets padded with black.
|
||||||
|
*/
|
||||||
|
*palette_ptr_ptr = calloc(colors, sizeof(**palette_ptr_ptr));
|
||||||
|
if (!*palette_ptr_ptr)
|
||||||
|
err("%s: Failed to allocate memory for palette", __func__);
|
||||||
|
palette = *palette_ptr_ptr;
|
||||||
|
*num_colors = 0;
|
||||||
|
|
||||||
|
for (y = 0; y < img->height; y++) {
|
||||||
|
value_index = 0;
|
||||||
|
while (value_index < img->width * 4) {
|
||||||
|
cur_pixel_color.red = img->data[y][value_index++];
|
||||||
|
cur_pixel_color.green = img->data[y][value_index++];
|
||||||
|
cur_pixel_color.blue = img->data[y][value_index++];
|
||||||
|
cur_alpha = img->data[y][value_index++];
|
||||||
|
|
||||||
|
update_built_palette(palette, &cur_pixel_color,
|
||||||
|
cur_alpha,
|
||||||
|
num_colors, &only_grayscale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In order not to count 100% transparent images as grayscale. */
|
||||||
|
only_grayscale = *num_colors ? only_grayscale : false;
|
||||||
|
|
||||||
|
if (!only_grayscale || !fit_grayscale_palette(palette, num_colors))
|
||||||
|
order_color_palette(palette, *num_colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_built_palette(png_color *palette,
|
||||||
|
png_color const *pixel_color, png_byte alpha,
|
||||||
|
int *num_colors, bool *only_grayscale)
|
||||||
|
{
|
||||||
|
bool color_exists;
|
||||||
|
png_color cur_palette_color;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transparent pixels don't count toward the palette,
|
||||||
|
* as they'll be replaced with color #0 later.
|
||||||
|
*/
|
||||||
|
if (alpha == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (*only_grayscale && !(pixel_color->red == pixel_color->green &&
|
||||||
|
pixel_color->red == pixel_color->blue)) {
|
||||||
|
*only_grayscale = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
color_exists = false;
|
||||||
|
for (i = 0; i < *num_colors; i++) {
|
||||||
|
cur_palette_color = palette[i];
|
||||||
|
if (pixel_color->red == cur_palette_color.red &&
|
||||||
|
pixel_color->green == cur_palette_color.green &&
|
||||||
|
pixel_color->blue == cur_palette_color.blue) {
|
||||||
|
color_exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!color_exists) {
|
||||||
|
if (*num_colors == colors) {
|
||||||
|
errx("Too many colors in input PNG file to fit into a %d-bit palette (max %d).",
|
||||||
|
depth, colors);
|
||||||
|
}
|
||||||
|
palette[*num_colors] = *pixel_color;
|
||||||
|
(*num_colors)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fit_grayscale_palette(png_color *palette, int *num_colors)
|
||||||
|
{
|
||||||
|
int interval = 256 / colors;
|
||||||
|
png_color *fitted_palette = malloc(sizeof(*fitted_palette) * colors);
|
||||||
|
bool *set_indices = calloc(colors, sizeof(*set_indices));
|
||||||
|
int i, shade_index;
|
||||||
|
|
||||||
|
if (!fitted_palette)
|
||||||
|
err("%s: Failed to allocate memory for palette", __func__);
|
||||||
|
if (!set_indices)
|
||||||
|
err("%s: Failed to allocate memory for indices", __func__);
|
||||||
|
|
||||||
|
fitted_palette[0].red = 0xFF;
|
||||||
|
fitted_palette[0].green = 0xFF;
|
||||||
|
fitted_palette[0].blue = 0xFF;
|
||||||
|
fitted_palette[colors - 1].red = 0;
|
||||||
|
fitted_palette[colors - 1].green = 0;
|
||||||
|
fitted_palette[colors - 1].blue = 0;
|
||||||
|
if (colors == 4) {
|
||||||
|
fitted_palette[1].red = 0xA9;
|
||||||
|
fitted_palette[1].green = 0xA9;
|
||||||
|
fitted_palette[1].blue = 0xA9;
|
||||||
|
fitted_palette[2].red = 0x55;
|
||||||
|
fitted_palette[2].green = 0x55;
|
||||||
|
fitted_palette[2].blue = 0x55;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < *num_colors; i++) {
|
||||||
|
shade_index = colors - 1 - palette[i].red / interval;
|
||||||
|
if (set_indices[shade_index]) {
|
||||||
|
free(fitted_palette);
|
||||||
|
free(set_indices);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fitted_palette[shade_index] = palette[i];
|
||||||
|
set_indices[shade_index] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < colors; i++)
|
||||||
|
palette[i] = fitted_palette[i];
|
||||||
|
|
||||||
|
*num_colors = colors;
|
||||||
|
|
||||||
|
free(fitted_palette);
|
||||||
|
free(set_indices);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A combined struct is needed to sort csolors in order of luminance. */
|
||||||
|
struct ColorWithLuminance {
|
||||||
|
png_color color;
|
||||||
|
int luminance;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int compare_luminance(void const *a, void const *b)
|
||||||
|
{
|
||||||
|
const struct ColorWithLuminance *x, *y;
|
||||||
|
|
||||||
|
x = (const struct ColorWithLuminance *)a;
|
||||||
|
y = (const struct ColorWithLuminance *)b;
|
||||||
|
|
||||||
|
return y->luminance - x->luminance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void order_color_palette(png_color *palette, int num_colors)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct ColorWithLuminance *palette_with_luminance =
|
||||||
|
malloc(sizeof(*palette_with_luminance) * num_colors);
|
||||||
|
|
||||||
|
if (!palette_with_luminance)
|
||||||
|
err("%s: Failed to allocate memory for palette", __func__);
|
||||||
|
|
||||||
|
for (i = 0; i < num_colors; i++) {
|
||||||
|
/*
|
||||||
|
* Normally this would be done with floats, but since it's only
|
||||||
|
* used for comparison, we might as well use integer math.
|
||||||
|
*/
|
||||||
|
palette_with_luminance[i].color = palette[i];
|
||||||
|
palette_with_luminance[i].luminance = 2126 * palette[i].red +
|
||||||
|
7152 * palette[i].green +
|
||||||
|
722 * palette[i].blue;
|
||||||
|
}
|
||||||
|
qsort(palette_with_luminance, num_colors,
|
||||||
|
sizeof(*palette_with_luminance), compare_luminance);
|
||||||
|
for (i = 0; i < num_colors; i++)
|
||||||
|
palette[i] = palette_with_luminance[i].color;
|
||||||
|
|
||||||
|
free(palette_with_luminance);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
|
||||||
|
const struct PNGImage *img,
|
||||||
|
int *value_index, int x, int y,
|
||||||
|
png_color const *palette,
|
||||||
|
int colors_in_palette);
|
||||||
|
|
||||||
|
static struct RawIndexedImage
|
||||||
|
*processed_rgba_png_to_raw(const struct PNGImage *img,
|
||||||
|
png_color const *palette,
|
||||||
|
int colors_in_palette)
|
||||||
|
{
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
int x, y, value_index;
|
||||||
|
|
||||||
|
raw_image = create_raw_image(img->width, img->height, colors);
|
||||||
|
|
||||||
|
set_raw_image_palette(raw_image, palette, colors_in_palette);
|
||||||
|
|
||||||
|
for (y = 0; y < img->height; y++) {
|
||||||
|
x = raw_image->width - 1;
|
||||||
|
value_index = img->width * 4 - 1;
|
||||||
|
|
||||||
|
while (x >= 0) {
|
||||||
|
put_raw_image_pixel(raw_image, img,
|
||||||
|
&value_index, x, y,
|
||||||
|
palette, colors_in_palette);
|
||||||
|
x--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t palette_index_of(png_color const *palette,
|
||||||
|
int num_colors, png_color const *color);
|
||||||
|
|
||||||
|
static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
|
||||||
|
const struct PNGImage *img,
|
||||||
|
int *value_index, int x, int y,
|
||||||
|
png_color const *palette,
|
||||||
|
int colors_in_palette)
|
||||||
|
{
|
||||||
|
png_color pixel_color;
|
||||||
|
png_byte alpha;
|
||||||
|
|
||||||
|
alpha = img->data[y][*value_index];
|
||||||
|
if (alpha == 0) {
|
||||||
|
raw_image->data[y][x] = 0;
|
||||||
|
*value_index -= 4;
|
||||||
|
} else {
|
||||||
|
(*value_index)--;
|
||||||
|
pixel_color.blue = img->data[y][(*value_index)--];
|
||||||
|
pixel_color.green = img->data[y][(*value_index)--];
|
||||||
|
pixel_color.red = img->data[y][(*value_index)--];
|
||||||
|
raw_image->data[y][x] = palette_index_of(palette,
|
||||||
|
colors_in_palette,
|
||||||
|
&pixel_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t palette_index_of(png_color const *palette,
|
||||||
|
int num_colors, png_color const *color)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < num_colors; i++) {
|
||||||
|
if (palette[i].red == color->red &&
|
||||||
|
palette[i].green == color->green &&
|
||||||
|
palette[i].blue == color->blue) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errx("The input PNG file contains colors that don't appear in its embedded palette.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void read_png(struct PNGImage *img)
|
||||||
|
{
|
||||||
|
int y;
|
||||||
|
|
||||||
|
png_read_update_info(img->png, img->info);
|
||||||
|
|
||||||
|
img->data = malloc(sizeof(*img->data) * img->height);
|
||||||
|
if (!img->data)
|
||||||
|
err("%s: Failed to allocate memory for image data",
|
||||||
|
__func__);
|
||||||
|
for (y = 0; y < img->height; y++) {
|
||||||
|
img->data[y] = malloc(png_get_rowbytes(img->png, img->info));
|
||||||
|
if (!img->data[y])
|
||||||
|
err("%s: Failed to allocate memory for image data",
|
||||||
|
__func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
png_read_image(img->png, img->data);
|
||||||
|
png_read_end(img->png, img->info);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct RawIndexedImage *create_raw_image(int width, int height,
|
||||||
|
int num_colors)
|
||||||
|
{
|
||||||
|
struct RawIndexedImage *raw_image;
|
||||||
|
int y;
|
||||||
|
|
||||||
|
raw_image = malloc(sizeof(*raw_image));
|
||||||
|
if (!raw_image)
|
||||||
|
err("%s: Failed to allocate memory for raw image",
|
||||||
|
__func__);
|
||||||
|
|
||||||
|
raw_image->width = width;
|
||||||
|
raw_image->height = height;
|
||||||
|
raw_image->num_colors = num_colors;
|
||||||
|
|
||||||
|
raw_image->palette = malloc(sizeof(*raw_image->palette) * num_colors);
|
||||||
|
if (!raw_image->palette)
|
||||||
|
err("%s: Failed to allocate memory for raw image palette",
|
||||||
|
__func__);
|
||||||
|
|
||||||
|
raw_image->data = malloc(sizeof(*raw_image->data) * height);
|
||||||
|
if (!raw_image->data)
|
||||||
|
err("%s: Failed to allocate memory for raw image data",
|
||||||
|
__func__);
|
||||||
|
for (y = 0; y < height; y++) {
|
||||||
|
raw_image->data[y] = malloc(sizeof(*raw_image->data[y])
|
||||||
|
* width);
|
||||||
|
if (!raw_image->data[y])
|
||||||
|
err("%s: Failed to allocate memory for raw image data",
|
||||||
|
__func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
|
||||||
|
png_color const *palette, int num_colors)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (num_colors > raw_image->num_colors) {
|
||||||
|
errx("Too many colors in input PNG file's palette to fit into a %d-bit palette (%d in input palette, max %d).",
|
||||||
|
raw_image->num_colors >> 1,
|
||||||
|
num_colors, raw_image->num_colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num_colors; i++) {
|
||||||
|
raw_image->palette[i].red = palette[i].red;
|
||||||
|
raw_image->palette[i].green = palette[i].green;
|
||||||
|
raw_image->palette[i].blue = palette[i].blue;
|
||||||
|
}
|
||||||
|
for (i = num_colors; i < raw_image->num_colors; i++) {
|
||||||
|
raw_image->palette[i].red = 0;
|
||||||
|
raw_image->palette[i].green = 0;
|
||||||
|
raw_image->palette[i].blue = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_text(const struct PNGImage *img,
|
||||||
|
struct ImageOptions *png_options)
|
||||||
|
{
|
||||||
|
png_text *text;
|
||||||
|
int i, numtxts, numremoved;
|
||||||
|
|
||||||
|
png_get_text(img->png, img->info, &text, &numtxts);
|
||||||
|
for (i = 0; i < numtxts; i++) {
|
||||||
|
if (strcmp(text[i].key, "h") == 0 && !*text[i].text) {
|
||||||
|
png_options->horizontal = true;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "x") == 0) {
|
||||||
|
png_options->trim = strtoul(text[i].text, NULL, 0);
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "t") == 0) {
|
||||||
|
png_options->tilemapfile = text[i].text;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "T") == 0 && !*text[i].text) {
|
||||||
|
png_options->tilemapout = true;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "a") == 0) {
|
||||||
|
png_options->attrmapfile = text[i].text;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "A") == 0 && !*text[i].text) {
|
||||||
|
png_options->attrmapout = true;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "p") == 0) {
|
||||||
|
png_options->palfile = text[i].text;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
} else if (strcmp(text[i].key, "P") == 0 && !*text[i].text) {
|
||||||
|
png_options->palout = true;
|
||||||
|
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Remove this and simply change the warning function not to warn
|
||||||
|
* instead.
|
||||||
|
*/
|
||||||
|
for (i = 0, numremoved = 0; i < numtxts; i++) {
|
||||||
|
if (text[i].key == NULL)
|
||||||
|
numremoved++;
|
||||||
|
|
||||||
|
text[i].key = text[i + numremoved].key;
|
||||||
|
text[i].text = text[i + numremoved].text;
|
||||||
|
text[i].compression = text[i + numremoved].compression;
|
||||||
|
}
|
||||||
|
png_set_text(img->png, img->info, text, numtxts - numremoved);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_text(const struct PNGImage *img,
|
||||||
|
const struct ImageOptions *png_options)
|
||||||
|
{
|
||||||
|
png_text *text;
|
||||||
|
char buffer[3];
|
||||||
|
|
||||||
|
text = malloc(sizeof(*text));
|
||||||
|
if (!text)
|
||||||
|
err("%s: Failed to allocate memory for PNG text",
|
||||||
|
__func__);
|
||||||
|
|
||||||
|
if (png_options->horizontal) {
|
||||||
|
text[0].key = "h";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (png_options->trim) {
|
||||||
|
text[0].key = "x";
|
||||||
|
snprintf(buffer, 3, "%d", png_options->trim);
|
||||||
|
text[0].text = buffer;
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (*png_options->tilemapfile) {
|
||||||
|
text[0].key = "t";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (png_options->tilemapout) {
|
||||||
|
text[0].key = "T";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (*png_options->attrmapfile) {
|
||||||
|
text[0].key = "a";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (png_options->attrmapout) {
|
||||||
|
text[0].key = "A";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (*png_options->palfile) {
|
||||||
|
text[0].key = "p";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
if (png_options->palout) {
|
||||||
|
text[0].key = "P";
|
||||||
|
text[0].text = "";
|
||||||
|
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
|
||||||
|
png_set_text(img->png, img->info, text, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_png_data(const struct PNGImage *img)
|
||||||
|
{
|
||||||
|
int y;
|
||||||
|
|
||||||
|
for (y = 0; y < img->height; y++)
|
||||||
|
free(img->data[y]);
|
||||||
|
|
||||||
|
free(img->data);
|
||||||
|
}
|
||||||
@@ -1,512 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/pal_packing.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <bitset>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <deque>
|
|
||||||
#include <numeric>
|
|
||||||
#include <optional>
|
|
||||||
#include <queue>
|
|
||||||
#include <tuple>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "defaultinitalloc.hpp"
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
#include "gfx/proto_palette.hpp"
|
|
||||||
|
|
||||||
using std::swap;
|
|
||||||
|
|
||||||
namespace packing {
|
|
||||||
|
|
||||||
// The solvers here are picked from the paper at http://arxiv.org/abs/1605.00558:
|
|
||||||
// "Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items"
|
|
||||||
// Their formulation of the problem consists in packing "tiles" into "pages"; here is a
|
|
||||||
// correspondence table for our application of it:
|
|
||||||
// Paper | RGBGFX
|
|
||||||
// ------+-------
|
|
||||||
// Tile | Proto-palette
|
|
||||||
// Page | Palette
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A reference to a proto-palette, and attached attributes for sorting purposes
|
|
||||||
*/
|
|
||||||
struct ProtoPalAttrs {
|
|
||||||
size_t const protoPalIndex;
|
|
||||||
/*
|
|
||||||
* Pages from which we are banned (to prevent infinite loops)
|
|
||||||
* This is dynamic because we wish not to hard-cap the amount of palettes
|
|
||||||
*/
|
|
||||||
std::vector<bool> bannedPages;
|
|
||||||
|
|
||||||
ProtoPalAttrs(size_t index) : protoPalIndex(index) {}
|
|
||||||
bool isBannedFrom(size_t index) const {
|
|
||||||
return index < bannedPages.size() && bannedPages[index];
|
|
||||||
}
|
|
||||||
void banFrom(size_t index) {
|
|
||||||
if (bannedPages.size() <= index) {
|
|
||||||
bannedPages.resize(index + 1);
|
|
||||||
}
|
|
||||||
bannedPages[index] = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A collection of proto-palettes assigned to a palette
|
|
||||||
* Does not contain the actual color indices because we need to be able to remove elements
|
|
||||||
*/
|
|
||||||
class AssignedProtos {
|
|
||||||
// We leave room for emptied slots to avoid copying the structs around on removal
|
|
||||||
std::vector<std::optional<ProtoPalAttrs>> _assigned;
|
|
||||||
// For resolving proto-palette indices
|
|
||||||
std::vector<ProtoPalette> const *_protoPals;
|
|
||||||
|
|
||||||
public:
|
|
||||||
template<typename... Ts>
|
|
||||||
AssignedProtos(std::vector<ProtoPalette> const &protoPals, Ts &&...elems)
|
|
||||||
: _assigned{std::forward<Ts>(elems)...}, _protoPals{&protoPals} {}
|
|
||||||
|
|
||||||
private:
|
|
||||||
template<typename Inner, template<typename> typename Constness>
|
|
||||||
class Iter {
|
|
||||||
public:
|
|
||||||
friend class AssignedProtos;
|
|
||||||
// For `iterator_traits`
|
|
||||||
using difference_type = typename std::iterator_traits<Inner>::difference_type;
|
|
||||||
using value_type = ProtoPalAttrs;
|
|
||||||
using pointer = Constness<value_type> *;
|
|
||||||
using reference = Constness<value_type> &;
|
|
||||||
using iterator_category = std::forward_iterator_tag;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Constness<decltype(_assigned)> *_array = nullptr;
|
|
||||||
Inner _iter{};
|
|
||||||
|
|
||||||
Iter(decltype(_array) array, decltype(_iter) &&iter) : _array(array), _iter(iter) {}
|
|
||||||
Iter &skipEmpty() {
|
|
||||||
while (_iter != _array->end() && !_iter->has_value()) {
|
|
||||||
++_iter;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
Iter() = default;
|
|
||||||
|
|
||||||
bool operator==(Iter const &other) const { return _iter == other._iter; }
|
|
||||||
bool operator!=(Iter const &other) const { return !(*this == other); }
|
|
||||||
Iter &operator++() {
|
|
||||||
++_iter;
|
|
||||||
skipEmpty();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
Iter operator++(int) {
|
|
||||||
Iter it = *this;
|
|
||||||
++(*this);
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
reference operator*() const {
|
|
||||||
assert((*_iter).has_value());
|
|
||||||
return **_iter;
|
|
||||||
}
|
|
||||||
pointer operator->() const {
|
|
||||||
return &(**this); // Invokes the operator above, not quite a no-op!
|
|
||||||
}
|
|
||||||
|
|
||||||
friend void swap(Iter &lhs, Iter &rhs) {
|
|
||||||
swap(lhs._array, rhs._array);
|
|
||||||
swap(lhs._iter, rhs._iter);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
using iterator = Iter<decltype(_assigned)::iterator, std::remove_const_t>;
|
|
||||||
iterator begin() { return iterator{&_assigned, _assigned.begin()}.skipEmpty(); }
|
|
||||||
iterator end() { return iterator{&_assigned, _assigned.end()}; }
|
|
||||||
using const_iterator = Iter<decltype(_assigned)::const_iterator, std::add_const_t>;
|
|
||||||
const_iterator begin() const {
|
|
||||||
return const_iterator{&_assigned, _assigned.begin()}.skipEmpty();
|
|
||||||
}
|
|
||||||
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Assigns a new ProtoPalAttrs in a free slot, assuming there is one
|
|
||||||
* Args are passed to the `ProtoPalAttrs`'s constructor
|
|
||||||
*/
|
|
||||||
template<typename... Ts>
|
|
||||||
void assign(Ts &&...args) {
|
|
||||||
auto freeSlot = std::find_if_not(
|
|
||||||
_assigned.begin(), _assigned.end(),
|
|
||||||
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); });
|
|
||||||
|
|
||||||
if (freeSlot == _assigned.end()) { // We are full, use a new slot
|
|
||||||
_assigned.emplace_back(std::forward<Ts>(args)...);
|
|
||||||
} else { // Reuse a free slot
|
|
||||||
freeSlot->emplace(std::forward<Ts>(args)...);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void remove(iterator const &iter) {
|
|
||||||
iter._iter->reset(); // This time, we want to access the `optional` itself
|
|
||||||
}
|
|
||||||
void clear() { _assigned.clear(); }
|
|
||||||
|
|
||||||
bool empty() const {
|
|
||||||
return std::find_if(
|
|
||||||
_assigned.begin(), _assigned.end(),
|
|
||||||
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); })
|
|
||||||
== _assigned.end();
|
|
||||||
}
|
|
||||||
size_t nbProtoPals() const { return std::distance(begin(), end()); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
template<typename Iter>
|
|
||||||
static void addUniqueColors(std::unordered_set<uint16_t> &colors, Iter iter, Iter const &end,
|
|
||||||
std::vector<ProtoPalette> const &protoPals) {
|
|
||||||
for (; iter != end; ++iter) {
|
|
||||||
ProtoPalette const &protoPal = protoPals[iter->protoPalIndex];
|
|
||||||
colors.insert(protoPal.begin(), protoPal.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This function should stay private because it returns a reference to a unique object
|
|
||||||
std::unordered_set<uint16_t> &uniqueColors() const {
|
|
||||||
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
|
||||||
// faster than "back-checking" on every element (O(n²))
|
|
||||||
//
|
|
||||||
// TODO: calc84maniac suggested another approach; try implementing it, see if it
|
|
||||||
// performs better:
|
|
||||||
// > So basically you make a priority queue that takes iterators into each of your sets
|
|
||||||
// > (paired with end iterators so you'll know where to stop), and the comparator tests the
|
|
||||||
// > values pointed to by each iterator
|
|
||||||
// > Then each iteration you pop from the queue,
|
|
||||||
// > optionally add one to your count, increment the iterator and push it back into the
|
|
||||||
// > queue if it didn't reach the end
|
|
||||||
// > And you do this until the priority queue is empty
|
|
||||||
static std::unordered_set<uint16_t> colors;
|
|
||||||
|
|
||||||
colors.clear();
|
|
||||||
addUniqueColors(colors, begin(), end(), *_protoPals);
|
|
||||||
return colors;
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
/*
|
|
||||||
* Returns the number of distinct colors
|
|
||||||
*/
|
|
||||||
size_t volume() const { return uniqueColors().size(); }
|
|
||||||
bool canFit(ProtoPalette const &protoPal) const {
|
|
||||||
auto &colors = uniqueColors();
|
|
||||||
colors.insert(protoPal.begin(), protoPal.end());
|
|
||||||
return colors.size() <= options.maxOpaqueColors();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Computes the "relative size" of a proto-palette on this palette
|
|
||||||
*/
|
|
||||||
double relSizeOf(ProtoPalette const &protoPal) const {
|
|
||||||
// NOTE: this function must not call `uniqueColors`, or one of its callers will break!
|
|
||||||
double relSize = 0.;
|
|
||||||
for (uint16_t color : protoPal) {
|
|
||||||
auto n = std::count_if(begin(), end(), [this, &color](ProtoPalAttrs const &attrs) {
|
|
||||||
ProtoPalette const &pal = (*_protoPals)[attrs.protoPalIndex];
|
|
||||||
return std::find(pal.begin(), pal.end(), color) != pal.end();
|
|
||||||
});
|
|
||||||
// NOTE: The paper and the associated code disagree on this: the code has
|
|
||||||
// this `1 +`, whereas the paper does not; its lack causes a division by 0
|
|
||||||
// if the symbol is not found anywhere, so I'm assuming the paper is wrong.
|
|
||||||
relSize += 1. / (1 + n);
|
|
||||||
}
|
|
||||||
return relSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Computes the "relative size" of a set of proto-palettes on this palette
|
|
||||||
*/
|
|
||||||
template<typename Iter>
|
|
||||||
auto combinedVolume(Iter &&begin, Iter const &end,
|
|
||||||
std::vector<ProtoPalette> const &protoPals) const {
|
|
||||||
auto &colors = uniqueColors();
|
|
||||||
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
|
|
||||||
return colors.size();
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Computes the "relative size" of a set of colors on this palette
|
|
||||||
*/
|
|
||||||
template<typename Iter>
|
|
||||||
auto combinedVolume(Iter &&begin, Iter &&end) const {
|
|
||||||
auto &colors = uniqueColors();
|
|
||||||
colors.insert(std::forward<Iter>(begin), std::forward<Iter>(end));
|
|
||||||
return colors.size();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static void decant(std::vector<AssignedProtos> &assignments,
|
|
||||||
std::vector<ProtoPalette> const &protoPalettes) {
|
|
||||||
// "Decanting" is the process of moving all *things* that can fit in a lower index there
|
|
||||||
auto decantOn = [&assignments](auto const &tryDecanting) {
|
|
||||||
// No need to attempt decanting on palette #0, as there are no palettes to decant to
|
|
||||||
for (size_t from = assignments.size(); --from;) {
|
|
||||||
// Scan all palettes before this one
|
|
||||||
for (size_t to = 0; to < from; ++to) {
|
|
||||||
tryDecanting(assignments[to], assignments[from]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the proto-palette is now empty, remove it
|
|
||||||
// Doing this now reduces the number of iterations performed by later steps
|
|
||||||
// NB: order is intentionally preserved so as not to alter the "decantation"'s
|
|
||||||
// properties
|
|
||||||
// NB: this does mean that the first step might get empty palettes as its input!
|
|
||||||
// NB: this is safe to do because we go towards the beginning of the vector, thereby not
|
|
||||||
// invalidating our iteration (thus, iterators should not be used to drivethe outer
|
|
||||||
// loop)
|
|
||||||
if (assignments[from].empty()) {
|
|
||||||
assignments.erase(assignments.begin() + from);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes before decanting\n",
|
|
||||||
assignments.size());
|
|
||||||
|
|
||||||
// Decant on palettes
|
|
||||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
|
||||||
// If the entire palettes can be merged, move all of `from`'s proto-palettes
|
|
||||||
if (to.combinedVolume(from.begin(), from.end(), protoPalettes)
|
|
||||||
<= options.maxOpaqueColors()) {
|
|
||||||
for (ProtoPalAttrs &attrs : from) {
|
|
||||||
to.assign(attrs.protoPalIndex);
|
|
||||||
}
|
|
||||||
from.clear();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n",
|
|
||||||
assignments.size());
|
|
||||||
|
|
||||||
// Decant on "components" (= proto-pals sharing colors)
|
|
||||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
|
||||||
// We need to iterate on all the "components", which are groups of proto-palettes sharing at
|
|
||||||
// least one color with another proto-palettes in the group.
|
|
||||||
// We do this by adding the first available proto-palette, and then looking for palettes
|
|
||||||
// with common colors. (As an optimization, we know we can skip palettes already scanned.)
|
|
||||||
std::vector<bool> processed(from.nbProtoPals(), false);
|
|
||||||
std::unordered_set<uint16_t> colors;
|
|
||||||
std::vector<size_t> members;
|
|
||||||
while (true) {
|
|
||||||
auto iter = std::find(processed.begin(), processed.end(), true);
|
|
||||||
if (iter == processed.end()) { // Processed everything!
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
auto attrs = from.begin();
|
|
||||||
std::advance(attrs, (iter - processed.begin()));
|
|
||||||
|
|
||||||
// Build up the "component"...
|
|
||||||
colors.clear();
|
|
||||||
members.clear();
|
|
||||||
assert(members.empty()); // Compiler optimization hint
|
|
||||||
do {
|
|
||||||
ProtoPalette const &protoPal = protoPalettes[attrs->protoPalIndex];
|
|
||||||
// If this is the first proto-pal, or if at least one color matches, add it
|
|
||||||
if (members.empty()
|
|
||||||
|| std::find_first_of(colors.begin(), colors.end(), protoPal.begin(),
|
|
||||||
protoPal.end())
|
|
||||||
!= colors.end()) {
|
|
||||||
colors.insert(protoPal.begin(), protoPal.end());
|
|
||||||
members.push_back(iter - processed.begin());
|
|
||||||
*iter = true; // Mark that proto-pal as processed
|
|
||||||
}
|
|
||||||
++iter;
|
|
||||||
++attrs;
|
|
||||||
} while (iter != processed.end());
|
|
||||||
|
|
||||||
if (to.combinedVolume(colors.begin(), colors.end()) <= options.maxOpaqueColors()) {
|
|
||||||
// Iterate through the component's proto-palettes, and transfer them
|
|
||||||
auto member = from.begin();
|
|
||||||
size_t curIndex = 0;
|
|
||||||
for (size_t index : members) {
|
|
||||||
std::advance(member, index - curIndex);
|
|
||||||
curIndex = index;
|
|
||||||
to.assign(std::move(*member));
|
|
||||||
from.remove(member); // Removing does not shift elements, so it's cheap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n",
|
|
||||||
assignments.size());
|
|
||||||
|
|
||||||
// Decant on individual proto-palettes
|
|
||||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
|
||||||
for (auto iter = from.begin(); iter != from.end(); ++iter) {
|
|
||||||
if (to.canFit(protoPalettes[iter->protoPalIndex])) {
|
|
||||||
to.assign(std::move(*iter));
|
|
||||||
from.remove(iter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n",
|
|
||||||
assignments.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
|
||||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes) {
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT,
|
|
||||||
"Paginating palettes using \"overload-and-remove\" strategy...\n");
|
|
||||||
|
|
||||||
// Sort the proto-palettes by size, which improves the packing algorithm's efficiency
|
|
||||||
DefaultInitVec<size_t> sortedProtoPalIDs(protoPalettes.size());
|
|
||||||
sortedProtoPalIDs.clear();
|
|
||||||
for (size_t i = 0; i < protoPalettes.size(); ++i) {
|
|
||||||
sortedProtoPalIDs.insert(
|
|
||||||
std::lower_bound(sortedProtoPalIDs.begin(), sortedProtoPalIDs.end(), i), i);
|
|
||||||
}
|
|
||||||
// Begin with all proto-palettes queued up for insertion
|
|
||||||
std::queue<ProtoPalAttrs> queue(
|
|
||||||
std::deque<ProtoPalAttrs>(sortedProtoPalIDs.begin(), sortedProtoPalIDs.end()));
|
|
||||||
// Begin with no pages
|
|
||||||
std::vector<AssignedProtos> assignments{};
|
|
||||||
|
|
||||||
for (; !queue.empty(); queue.pop()) {
|
|
||||||
ProtoPalAttrs const &attrs = queue.front(); // Valid until the `queue.pop()`
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "Handling proto-pal %zu\n", attrs.protoPalIndex);
|
|
||||||
|
|
||||||
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
|
|
||||||
size_t bestPalIndex = assignments.size();
|
|
||||||
// We're looking for a palette where the proto-palette's relative size is less than
|
|
||||||
// its actual size; so only overwrite the "not found" index on meeting that criterion
|
|
||||||
double bestRelSize = protoPal.size();
|
|
||||||
|
|
||||||
for (size_t i = 0; i < assignments.size(); ++i) {
|
|
||||||
// Skip the page if this one is banned from it
|
|
||||||
if (attrs.isBannedFrom(i)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG, "%zu/%zu: Rel size: %f (size = %zu)\n", i + 1,
|
|
||||||
assignments.size(), assignments[i].relSizeOf(protoPal),
|
|
||||||
protoPal.size());
|
|
||||||
if (assignments[i].relSizeOf(protoPal) < bestRelSize) {
|
|
||||||
bestPalIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestPalIndex == assignments.size()) {
|
|
||||||
// Found nowhere to put it, create a new page containing just that one
|
|
||||||
assignments.emplace_back(protoPalettes, std::move(attrs));
|
|
||||||
} else {
|
|
||||||
auto &bestPal = assignments[bestPalIndex];
|
|
||||||
// Add the color to that palette
|
|
||||||
bestPal.assign(std::move(attrs));
|
|
||||||
|
|
||||||
// If this overloads the palette, get it back to normal (if possible)
|
|
||||||
while (bestPal.volume() > options.maxOpaqueColors()) {
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG,
|
|
||||||
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
|
|
||||||
bestPalIndex, bestPal.volume(), options.maxOpaqueColors());
|
|
||||||
|
|
||||||
// Look for a proto-pal minimizing "efficiency" (size / rel_size)
|
|
||||||
auto efficiency = [&bestPal](ProtoPalette const &pal) {
|
|
||||||
return pal.size() / bestPal.relSizeOf(pal);
|
|
||||||
};
|
|
||||||
auto [minEfficiencyIter, maxEfficiencyIter] =
|
|
||||||
std::minmax_element(bestPal.begin(), bestPal.end(),
|
|
||||||
[&efficiency, &protoPalettes](ProtoPalAttrs const &lhs,
|
|
||||||
ProtoPalAttrs const &rhs) {
|
|
||||||
return efficiency(protoPalettes[lhs.protoPalIndex])
|
|
||||||
< efficiency(protoPalettes[rhs.protoPalIndex]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// All efficiencies are identical iff min equals max
|
|
||||||
// TODO: maybe not ideal to re-compute these two?
|
|
||||||
// TODO: yikes for float comparison! I *think* this threshold is OK?
|
|
||||||
if (efficiency(protoPalettes[maxEfficiencyIter->protoPalIndex])
|
|
||||||
- efficiency(protoPalettes[minEfficiencyIter->protoPalIndex])
|
|
||||||
< .001) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the proto-pal with minimal efficiency
|
|
||||||
queue.emplace(std::move(*minEfficiencyIter));
|
|
||||||
queue.back().banFrom(bestPalIndex); // Ban it from this palette
|
|
||||||
bestPal.remove(minEfficiencyIter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal with palettes still overloaded, by emptying them
|
|
||||||
for (AssignedProtos &pal : assignments) {
|
|
||||||
if (pal.volume() > options.maxOpaqueColors()) {
|
|
||||||
for (ProtoPalAttrs &attrs : pal) {
|
|
||||||
queue.emplace(std::move(attrs));
|
|
||||||
}
|
|
||||||
pal.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Place back any proto-palettes now in the queue via first-fit
|
|
||||||
while (!queue.empty()) {
|
|
||||||
ProtoPalAttrs const &attrs = queue.front();
|
|
||||||
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
|
|
||||||
auto iter =
|
|
||||||
std::find_if(assignments.begin(), assignments.end(),
|
|
||||||
[&protoPal](AssignedProtos const &pal) { return pal.canFit(protoPal); });
|
|
||||||
if (iter == assignments.end()) { // No such page, create a new one
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG,
|
|
||||||
"Adding new palette (%zu) for overflowing proto-pal %zu\n",
|
|
||||||
assignments.size(), attrs.protoPalIndex);
|
|
||||||
assignments.emplace_back(protoPalettes, std::move(attrs));
|
|
||||||
} else {
|
|
||||||
options.verbosePrint(Options::VERB_DEBUG,
|
|
||||||
"Assigning overflowing proto-pal %zu to palette %zu\n",
|
|
||||||
attrs.protoPalIndex, iter - assignments.begin());
|
|
||||||
iter->assign(std::move(attrs));
|
|
||||||
}
|
|
||||||
queue.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
|
||||||
for (auto &&assignment : assignments) {
|
|
||||||
fprintf(stderr, "{ ");
|
|
||||||
for (auto &&attrs : assignment) {
|
|
||||||
fprintf(stderr, "[%zu] ", attrs.protoPalIndex);
|
|
||||||
for (auto &&colorIndex : protoPalettes[attrs.protoPalIndex]) {
|
|
||||||
fprintf(stderr, "%04" PRIx16 ", ", colorIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Decant" the result
|
|
||||||
decant(assignments, protoPalettes);
|
|
||||||
// Note that the result does not contain any empty palettes
|
|
||||||
|
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
|
||||||
for (auto &&assignment : assignments) {
|
|
||||||
fprintf(stderr, "{ ");
|
|
||||||
for (auto &&attrs : assignment) {
|
|
||||||
fprintf(stderr, "[%zu] ", attrs.protoPalIndex);
|
|
||||||
for (auto &&colorIndex : protoPalettes[attrs.protoPalIndex]) {
|
|
||||||
fprintf(stderr, "%04" PRIx16 ", ", colorIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DefaultInitVec<size_t> mappings(protoPalettes.size());
|
|
||||||
for (size_t i = 0; i < assignments.size(); ++i) {
|
|
||||||
for (ProtoPalAttrs const &attrs : assignments[i]) {
|
|
||||||
mappings[attrs.protoPalIndex] = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {mappings, assignments.size()};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace packing
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/pal_sorting.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <png.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "helpers.h"
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
#include "gfx/process.hpp"
|
|
||||||
|
|
||||||
namespace sorting {
|
|
||||||
|
|
||||||
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
|
|
||||||
png_byte *palAlpha) {
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes using embedded palette...\n");
|
|
||||||
|
|
||||||
auto pngToRgb = [&palRGB, &palAlpha](int index) {
|
|
||||||
auto const &c = palRGB[index];
|
|
||||||
return Rgba(c.red, c.green, c.blue, palAlpha ? palAlpha[index] : 0xFF);
|
|
||||||
};
|
|
||||||
|
|
||||||
// HACK: for compatibility with old versions, add unused colors if:
|
|
||||||
// - there is only one palette, and
|
|
||||||
// - only some of the first N colors are being used
|
|
||||||
if (palettes.size() == 1) {
|
|
||||||
Palette &palette = palettes[0];
|
|
||||||
// Build our candidate array of colors
|
|
||||||
decltype(palette.colors) colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
|
||||||
for (int i = 0; i < options.maxOpaqueColors(); ++i) {
|
|
||||||
colors[i + options.hasTransparentPixels] = pngToRgb(i).cgbColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the palette only uses those colors
|
|
||||||
if (std::all_of(palette.begin(), palette.end(), [&colors](uint16_t color) {
|
|
||||||
return std::find(colors.begin(), colors.end(), color) != colors.end();
|
|
||||||
})) {
|
|
||||||
if (palette.size() != options.maxOpaqueColors()) {
|
|
||||||
warning("Unused color in PNG embedded palette was re-added; please use `-c "
|
|
||||||
"embedded` to get this in future versions");
|
|
||||||
}
|
|
||||||
// Overwrite the palette, and return with that (it's already sorted)
|
|
||||||
palette.colors = colors;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Palette &pal : palettes) {
|
|
||||||
std::sort(pal.begin(), pal.end(), [&](uint16_t lhs, uint16_t rhs) {
|
|
||||||
// Iterate through the PNG's palette, looking for either of the two
|
|
||||||
for (int i = 0; i < palSize; ++i) {
|
|
||||||
uint16_t color = pngToRgb(i).cgbColor();
|
|
||||||
if (color == Rgba::transparent) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Return whether lhs < rhs
|
|
||||||
if (color == rhs) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (color == lhs) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unreachable_(); // This should not be possible
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void grayscale(std::vector<Palette> &palettes,
|
|
||||||
std::array<std::optional<Rgba>, 0x8001> const &colors) {
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n");
|
|
||||||
|
|
||||||
// This method is only applicable if there are at most as many colors as colors per palette, so
|
|
||||||
// we should only have a single palette.
|
|
||||||
assert(palettes.size() == 1);
|
|
||||||
|
|
||||||
Palette &palette = palettes[0];
|
|
||||||
std::fill(palette.colors.begin(), palette.colors.end(), Rgba::transparent);
|
|
||||||
for (auto const &slot : colors) {
|
|
||||||
if (!slot.has_value() || slot->isTransparent()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
palette[slot->grayIndex()] = slot->cgbColor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned int legacyLuminance(uint16_t color) {
|
|
||||||
uint8_t red = color & 0b11111;
|
|
||||||
uint8_t green = color >> 5 & 0b11111;
|
|
||||||
uint8_t blue = color >> 10;
|
|
||||||
return 2126 * red + 7152 * green + 722 * blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rgb(std::vector<Palette> &palettes) {
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by \"\"\"luminance\"\"\"...\n");
|
|
||||||
|
|
||||||
for (Palette &pal : palettes) {
|
|
||||||
std::sort(pal.begin(), pal.end(), [](uint16_t lhs, uint16_t rhs) {
|
|
||||||
return legacyLuminance(lhs) > legacyLuminance(rhs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sorting
|
|
||||||
@@ -1,451 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/pal_spec.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cassert>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <climits>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
|
||||||
#include <ostream>
|
|
||||||
#include <streambuf>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <tuple>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#include "platform.h"
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
|
|
||||||
using namespace std::string_view_literals;
|
|
||||||
|
|
||||||
constexpr uint8_t nibble(char c) {
|
|
||||||
if (c >= 'a') {
|
|
||||||
assert(c <= 'f');
|
|
||||||
return c - 'a' + 10;
|
|
||||||
} else if (c >= 'A') {
|
|
||||||
assert(c <= 'F');
|
|
||||||
return c - 'A' + 10;
|
|
||||||
} else {
|
|
||||||
assert(c >= '0' && c <= '9');
|
|
||||||
return c - '0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr uint8_t toHex(char c1, char c2) {
|
|
||||||
return nibble(c1) * 16 + nibble(c2);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr uint8_t singleToHex(char c) {
|
|
||||||
return toHex(c, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Str> // Should be std::string or std::string_view
|
|
||||||
static void skipWhitespace(Str const &str, typename Str::size_type &pos) {
|
|
||||||
pos = std::min(str.find_first_not_of(" \t", pos), str.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseInlinePalSpec(char const * const rawArg) {
|
|
||||||
// List of #rrggbb/#rgb colors, comma-separated, palettes are separated by colons
|
|
||||||
|
|
||||||
std::string_view arg(rawArg);
|
|
||||||
using size_type = decltype(arg)::size_type;
|
|
||||||
|
|
||||||
auto parseError = [&rawArg, &arg](size_type ofs, size_type len, char const *fmt,
|
|
||||||
auto &&...args) {
|
|
||||||
(void)arg; // With NDEBUG, `arg` is otherwise not used
|
|
||||||
assert(ofs <= arg.length());
|
|
||||||
assert(len <= arg.length());
|
|
||||||
|
|
||||||
error(fmt, args...);
|
|
||||||
fprintf(stderr,
|
|
||||||
"In inline palette spec: %s\n"
|
|
||||||
" ",
|
|
||||||
rawArg);
|
|
||||||
for (auto i = ofs; i; --i) {
|
|
||||||
putc(' ', stderr);
|
|
||||||
}
|
|
||||||
for (auto i = len; i; --i) {
|
|
||||||
putc('^', stderr);
|
|
||||||
}
|
|
||||||
putc('\n', stderr);
|
|
||||||
};
|
|
||||||
|
|
||||||
options.palSpec.clear();
|
|
||||||
options.palSpec.emplace_back(); // Value-initialized, not default-init'd, so we get zeros
|
|
||||||
|
|
||||||
size_type n = 0; // Index into the argument
|
|
||||||
// TODO: store max `nbColors` ever reached, and compare against palette size later
|
|
||||||
size_t nbColors = 0; // Number of colors in the current palette
|
|
||||||
for (;;) {
|
|
||||||
++n; // Ignore the '#' (checked either by caller or previous loop iteration)
|
|
||||||
|
|
||||||
Rgba &color = options.palSpec.back()[nbColors];
|
|
||||||
auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
|
|
||||||
switch (pos - n) {
|
|
||||||
case 3:
|
|
||||||
color = Rgba(singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]),
|
|
||||||
0xFF);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
color = Rgba(toHex(arg[n + 0], arg[n + 1]), toHex(arg[n + 2], arg[n + 3]),
|
|
||||||
toHex(arg[n + 4], arg[n + 5]), 0xFF);
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
parseError(n - 1, 1, "Missing color after '#'");
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
parseError(n, pos - n, "Unknown color specification");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
n = pos;
|
|
||||||
|
|
||||||
// Skip whitespace, if any
|
|
||||||
skipWhitespace(arg, n);
|
|
||||||
|
|
||||||
// Skip comma/semicolon, or end
|
|
||||||
if (n == arg.length()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
switch (arg[n]) {
|
|
||||||
case ',':
|
|
||||||
++n; // Skip it
|
|
||||||
|
|
||||||
++nbColors;
|
|
||||||
|
|
||||||
// A trailing comma may be followed by a semicolon
|
|
||||||
skipWhitespace(arg, n);
|
|
||||||
if (n == arg.length()) {
|
|
||||||
break;
|
|
||||||
} else if (arg[n] != ';' && arg[n] != ':') {
|
|
||||||
if (nbColors == 4) {
|
|
||||||
parseError(n, 1, "Each palette can only contain up to 4 colors");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
[[fallthrough]];
|
|
||||||
|
|
||||||
case ':':
|
|
||||||
case ';':
|
|
||||||
++n;
|
|
||||||
skipWhitespace(arg, n);
|
|
||||||
|
|
||||||
nbColors = 0; // Start a new palette
|
|
||||||
// Avoid creating a spurious empty palette
|
|
||||||
if (n != arg.length()) {
|
|
||||||
options.palSpec.emplace_back();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
parseError(n, 1, "Unexpected character, expected ',', ';', or end of argument");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check again to allow trailing a comma/semicolon
|
|
||||||
if (n == arg.length()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (arg[n] != '#') {
|
|
||||||
parseError(n, 1, "Unexpected character, expected '#'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Tries to read some magic bytes from the provided `file`.
|
|
||||||
* Returns whether the magic was correctly read.
|
|
||||||
*/
|
|
||||||
template<size_t n>
|
|
||||||
static bool readMagic(std::filebuf &file, char const *magic) {
|
|
||||||
assert(strlen(magic) == n);
|
|
||||||
|
|
||||||
char magicBuf[n];
|
|
||||||
return file.sgetn(magicBuf, n) == n && memcmp(magicBuf, magic, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like `readMagic`, but automatically determines the size from the string literal's length.
|
|
||||||
// Don't worry if you make a mistake, an `assert`'s got your back!
|
|
||||||
#define READ_MAGIC(file, magic) \
|
|
||||||
readMagic<sizeof(magic) - 1>(file, magic) // Don't count the terminator
|
|
||||||
|
|
||||||
template<typename T, typename U>
|
|
||||||
static T readBE(U const *bytes) {
|
|
||||||
T val = 0;
|
|
||||||
for (size_t i = 0; i < sizeof(val); ++i) {
|
|
||||||
val = val << 8 | static_cast<uint8_t>(bytes[i]);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* **Appends** the first line read from `file` to the end of the provided `buffer`.
|
|
||||||
*/
|
|
||||||
static void readLine(std::filebuf &file, std::string &buffer) {
|
|
||||||
// TODO: maybe this can be optimized to bulk reads?
|
|
||||||
for (;;) {
|
|
||||||
auto c = file.sbumpc();
|
|
||||||
if (c == std::filebuf::traits_type::eof()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (c == '\n') {
|
|
||||||
// Discard a trailing CRLF
|
|
||||||
if (!buffer.empty() && buffer.back() == '\r') {
|
|
||||||
buffer.pop_back();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.push_back(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Normally we'd use `std::from_chars`, but that's not available with GCC 7
|
|
||||||
/*
|
|
||||||
* Parses the initial part of a string_view, advancing the "read index" as it does
|
|
||||||
*/
|
|
||||||
static uint16_t parseDec(std::string const &str, std::string::size_type &n) {
|
|
||||||
uint32_t value = 0; // Use a larger type to handle overflow more easily
|
|
||||||
for (auto end = std::min(str.length(), str.find_first_not_of("0123456789", n)); n < end; ++n) {
|
|
||||||
value = std::min<uint32_t>(value * 10 + (str[n] - '0'), UINT16_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void parsePSPFile(std::filebuf &file) {
|
|
||||||
// https://www.selapa.net/swatches/colors/fileformats.php#psp_pal
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
readLine(file, line);
|
|
||||||
if (line != "JASC-PAL") {
|
|
||||||
error("Palette file does not appear to be a PSP palette file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
line.clear();
|
|
||||||
readLine(file, line);
|
|
||||||
if (line != "0100") {
|
|
||||||
error("Unsupported PSP palette file version \"%s\"", line.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
line.clear();
|
|
||||||
readLine(file, line);
|
|
||||||
std::string::size_type n = 0;
|
|
||||||
uint16_t nbColors = parseDec(line, n);
|
|
||||||
if (n != line.length()) {
|
|
||||||
error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
|
|
||||||
warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
|
||||||
"; ignoring extra",
|
|
||||||
nbColors, options.nbColorsPerPal * options.nbPalettes);
|
|
||||||
nbColors = options.nbColorsPerPal * options.nbPalettes;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.palSpec.clear();
|
|
||||||
|
|
||||||
for (uint16_t i = 0; i < nbColors; ++i) {
|
|
||||||
line.clear();
|
|
||||||
readLine(file, line);
|
|
||||||
n = 0;
|
|
||||||
|
|
||||||
uint8_t r = parseDec(line, n);
|
|
||||||
skipWhitespace(line, n);
|
|
||||||
if (n == line.length()) {
|
|
||||||
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
|
|
||||||
line.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t g = parseDec(line, n);
|
|
||||||
if (n == line.length()) {
|
|
||||||
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
|
|
||||||
line.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
skipWhitespace(line, n);
|
|
||||||
uint8_t b = parseDec(line, n);
|
|
||||||
if (n != line.length()) {
|
|
||||||
error("Failed to parse color #%" PRIu16
|
|
||||||
" (\"%s\"): trailing characters after blue component",
|
|
||||||
i + 1, line.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i % options.nbColorsPerPal == 0) {
|
|
||||||
options.palSpec.emplace_back();
|
|
||||||
}
|
|
||||||
options.palSpec.back()[i % options.nbColorsPerPal] = Rgba(r, g, b, 0xFF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseACTFile(std::filebuf &file) {
|
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626
|
|
||||||
|
|
||||||
std::array<char, 772> buf;
|
|
||||||
auto len = file.sgetn(buf.data(), buf.size());
|
|
||||||
|
|
||||||
uint16_t nbColors = 256;
|
|
||||||
if (len == 772) {
|
|
||||||
nbColors = readBE<uint16_t>(&buf[768]);
|
|
||||||
// TODO: apparently there is a "transparent color index"? What?
|
|
||||||
if (nbColors > 256 || nbColors == 0) {
|
|
||||||
error("Invalid number of colors in ACT file (%" PRIu16 ")", nbColors);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (len != 768) {
|
|
||||||
error("Invalid file size for ACT file (expected 768 or 772 bytes, got %zu", len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
|
|
||||||
warning("ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
|
||||||
"; ignoring extra",
|
|
||||||
nbColors, options.nbColorsPerPal * options.nbPalettes);
|
|
||||||
nbColors = options.nbColorsPerPal * options.nbPalettes;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.palSpec.clear();
|
|
||||||
options.palSpec.emplace_back();
|
|
||||||
|
|
||||||
char const *ptr = buf.data();
|
|
||||||
size_t colorIdx = 0;
|
|
||||||
for (uint16_t i = 0; i < nbColors; ++i) {
|
|
||||||
Rgba &color = options.palSpec.back()[colorIdx];
|
|
||||||
color = Rgba(ptr[0], ptr[1], ptr[2], 0xFF);
|
|
||||||
|
|
||||||
ptr += 3;
|
|
||||||
++colorIdx;
|
|
||||||
if (colorIdx == options.nbColorsPerPal) {
|
|
||||||
options.palSpec.emplace_back();
|
|
||||||
colorIdx = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the spurious empty palette if there is one
|
|
||||||
if (colorIdx == 0) {
|
|
||||||
options.palSpec.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseACOFile(std::filebuf &file) {
|
|
||||||
// rhttps://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819
|
|
||||||
// http://www.nomodes.com/aco.html
|
|
||||||
|
|
||||||
char buf[10];
|
|
||||||
|
|
||||||
if (file.sgetn(buf, 2) != 2) {
|
|
||||||
error("Couldn't read ACO file version");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (readBE<uint16_t>(buf) != 1) {
|
|
||||||
error("Palette file does not appear to be an ACO file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.sgetn(buf, 2) != 2) {
|
|
||||||
error("Couldn't read number of colors in palette file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint16_t nbColors = readBE<uint16_t>(buf);
|
|
||||||
|
|
||||||
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
|
|
||||||
warning("ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
|
||||||
"; ignoring extra",
|
|
||||||
nbColors, options.nbColorsPerPal * options.nbPalettes);
|
|
||||||
nbColors = options.nbColorsPerPal * options.nbPalettes;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.palSpec.clear();
|
|
||||||
|
|
||||||
for (uint16_t i = 0; i < nbColors; ++i) {
|
|
||||||
if (file.sgetn(buf, 10) != 10) {
|
|
||||||
error("Failed to read color #%" PRIu16 " from palette file", i + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i % options.nbColorsPerPal == 0) {
|
|
||||||
options.palSpec.emplace_back();
|
|
||||||
}
|
|
||||||
|
|
||||||
Rgba &color = options.palSpec.back()[i % options.nbColorsPerPal];
|
|
||||||
uint16_t colorType = readBE<uint16_t>(buf);
|
|
||||||
switch (colorType) {
|
|
||||||
case 0: // RGB
|
|
||||||
color = Rgba(buf[0], buf[2], buf[4], 0xFF);
|
|
||||||
break;
|
|
||||||
case 1: // HSB
|
|
||||||
error("Unsupported color type (HSB) for ACO file");
|
|
||||||
return;
|
|
||||||
case 2: // CMYK
|
|
||||||
error("Unsupported color type (CMYK) for ACO file");
|
|
||||||
return;
|
|
||||||
case 7: // Lab
|
|
||||||
error("Unsupported color type (lab) for ACO file");
|
|
||||||
return;
|
|
||||||
case 8: // Grayscale
|
|
||||||
error("Unsupported color type (grayscale) for ACO file");
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
error("Unknown color type (%" PRIu16 ") for ACO file", colorType);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: maybe scan the v2 data instead (if present)
|
|
||||||
// `codecvt` can be used to convert from UTF-16 to UTF-8
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseExternalPalSpec(char const *arg) {
|
|
||||||
// `fmt:path`, parse the file according to the given format
|
|
||||||
|
|
||||||
// Split both parts, error out if malformed
|
|
||||||
char const *ptr = strchr(arg, ':');
|
|
||||||
if (ptr == nullptr) {
|
|
||||||
error("External palette spec must have format `fmt:path` (missing colon)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char const *path = ptr + 1;
|
|
||||||
|
|
||||||
static std::array parsers{
|
|
||||||
std::tuple{"PSP", &parsePSPFile, std::ios::in },
|
|
||||||
std::tuple{"ACT", &parseACTFile, std::ios::binary},
|
|
||||||
std::tuple{"ACO", &parseACOFile, std::ios::binary},
|
|
||||||
};
|
|
||||||
|
|
||||||
auto iter = std::find_if(parsers.begin(), parsers.end(),
|
|
||||||
[&arg, &ptr](decltype(parsers)::value_type const &parser) {
|
|
||||||
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
|
|
||||||
});
|
|
||||||
if (iter == parsers.end()) {
|
|
||||||
error("Unknown external palette format \"%.*s\"",
|
|
||||||
static_cast<int>(std::min(ptr - arg, static_cast<decltype(ptr - arg)>(INT_MAX))),
|
|
||||||
arg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filebuf file;
|
|
||||||
// Some parsers read the file in text mode, others in binary mode
|
|
||||||
if (!file.open(path, std::ios::in | std::get<2>(*iter))) {
|
|
||||||
error("Failed to open palette file \"%s\"", path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::get<1> (*iter)(file);
|
|
||||||
}
|
|
||||||
1115
src/gfx/process.cpp
1115
src/gfx/process.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/proto_palette.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <cassert>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
bool ProtoPalette::add(uint16_t color) {
|
|
||||||
size_t i = 0;
|
|
||||||
|
|
||||||
// Seek the first slot greater than our color
|
|
||||||
// (A linear search is better because we don't store the array size,
|
|
||||||
// and there are very few slots anyway)
|
|
||||||
while (_colorIndices[i] < color) {
|
|
||||||
++i;
|
|
||||||
if (i == _colorIndices.size())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If we found ourselves, great!
|
|
||||||
if (_colorIndices[i] == color)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Swap entries until the end
|
|
||||||
while (_colorIndices[i] != UINT16_MAX) {
|
|
||||||
std::swap(_colorIndices[i], color);
|
|
||||||
++i;
|
|
||||||
if (i == _colorIndices.size())
|
|
||||||
return false; // Oh well
|
|
||||||
}
|
|
||||||
// Write that last one into the new slot
|
|
||||||
_colorIndices[i] = color;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProtoPalette::ComparisonResult ProtoPalette::compare(ProtoPalette const &other) const {
|
|
||||||
// This works because the sets are sorted numerically
|
|
||||||
assert(std::is_sorted(_colorIndices.begin(), _colorIndices.end()));
|
|
||||||
assert(std::is_sorted(other._colorIndices.begin(), other._colorIndices.end()));
|
|
||||||
|
|
||||||
auto ours = _colorIndices.begin(), theirs = other._colorIndices.begin();
|
|
||||||
bool weBigger = true, theyBigger = true;
|
|
||||||
|
|
||||||
while (ours != _colorIndices.end() && theirs != other._colorIndices.end()) {
|
|
||||||
if (*ours == *theirs) {
|
|
||||||
++ours;
|
|
||||||
++theirs;
|
|
||||||
} else if (*ours < *theirs) {
|
|
||||||
++ours;
|
|
||||||
theyBigger = false;
|
|
||||||
} else { // *ours > *theirs
|
|
||||||
++theirs;
|
|
||||||
weBigger = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
weBigger &= theirs == other._colorIndices.end();
|
|
||||||
theyBigger &= ours == _colorIndices.end();
|
|
||||||
|
|
||||||
return theyBigger ? THEY_BIGGER : (weBigger ? WE_BIGGER : NEITHER);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ProtoPalette::size() const {
|
|
||||||
return std::distance(begin(), end());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProtoPalette::empty() const {
|
|
||||||
return _colorIndices[0] == UINT16_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ProtoPalette::begin() const -> decltype(_colorIndices)::const_iterator {
|
|
||||||
return _colorIndices.begin();
|
|
||||||
}
|
|
||||||
auto ProtoPalette::end() const -> decltype(_colorIndices)::const_iterator {
|
|
||||||
return std::find(_colorIndices.begin(), _colorIndices.end(), UINT16_MAX);
|
|
||||||
}
|
|
||||||
@@ -1,330 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/reverse.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <optional>
|
|
||||||
#include <png.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <tuple>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "defaultinitalloc.hpp"
|
|
||||||
#include "helpers.h"
|
|
||||||
#include "itertools.hpp"
|
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
|
||||||
|
|
||||||
static DefaultInitVec<uint8_t> readInto(std::string path) {
|
|
||||||
std::filebuf file;
|
|
||||||
if (!file.open(path, std::ios::in | std::ios::binary)) {
|
|
||||||
fatal("Failed to open \"%s\": %s", path.c_str(), strerror(errno));
|
|
||||||
}
|
|
||||||
DefaultInitVec<uint8_t> data(128 * 16); // Begin with some room pre-allocated
|
|
||||||
|
|
||||||
size_t curSize = 0;
|
|
||||||
for (;;) {
|
|
||||||
size_t oldSize = curSize;
|
|
||||||
curSize = data.size();
|
|
||||||
|
|
||||||
// Fill the new area ([oldSize; curSize[) with bytes
|
|
||||||
size_t nbRead =
|
|
||||||
file.sgetn(reinterpret_cast<char *>(&data.data()[oldSize]), curSize - oldSize);
|
|
||||||
if (nbRead != curSize - oldSize) {
|
|
||||||
// Shrink the vector to discard bytes that weren't read
|
|
||||||
data.resize(oldSize + nbRead);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// If the vector has some capacity left, use it; otherwise, double the current size
|
|
||||||
|
|
||||||
// Arbitrary, but if you got a better idea...
|
|
||||||
size_t newSize = oldSize != data.capacity() ? data.capacity() : oldSize * 2;
|
|
||||||
assert(oldSize != newSize);
|
|
||||||
data.resize(newSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]] static void pngError(png_structp png, char const *msg) {
|
|
||||||
fatal("Error writing reversed image (\"%s\"): %s",
|
|
||||||
static_cast<char const *>(png_get_error_ptr(png)), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pngWarning(png_structp png, char const *msg) {
|
|
||||||
warning("While writing reversed image (\"%s\"): %s",
|
|
||||||
static_cast<char const *>(png_get_error_ptr(png)), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void writePng(png_structp png, png_bytep data, size_t length) {
|
|
||||||
auto &pngFile = *static_cast<std::filebuf *>(png_get_io_ptr(png));
|
|
||||||
pngFile.sputn(reinterpret_cast<char *>(data), length);
|
|
||||||
}
|
|
||||||
|
|
||||||
void flushPng(png_structp png) {
|
|
||||||
auto &pngFile = *static_cast<std::filebuf *>(png_get_io_ptr(png));
|
|
||||||
pngFile.pubsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reverse() {
|
|
||||||
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
|
|
||||||
|
|
||||||
// Check for weird flag combinations
|
|
||||||
|
|
||||||
if (options.output.empty()) {
|
|
||||||
fatal("Tile data must be provided when reversing an image!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.allowDedup && options.tilemap.empty()) {
|
|
||||||
warning("Tile deduplication is enabled, but no tilemap is provided?");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.useColorCurve) {
|
|
||||||
warning("The color curve is not yet supported in reverse mode...");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.inputSlice.left != 0 || options.inputSlice.top != 0
|
|
||||||
|| options.inputSlice.height != 0) {
|
|
||||||
warning("\"Sliced-off\" pixels are ignored in reverse mode");
|
|
||||||
}
|
|
||||||
if (options.inputSlice.width != 0 && options.inputSlice.width != options.reversedWidth * 8) {
|
|
||||||
warning("Specified input slice width (%" PRIu16
|
|
||||||
") doesn't match provided reversing width (%" PRIu8 " * 8)",
|
|
||||||
options.inputSlice.width, options.reversedWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n");
|
|
||||||
auto const tiles = readInto(options.output);
|
|
||||||
uint8_t tileSize = 8 * options.bitDepth;
|
|
||||||
if (tiles.size() % tileSize != 0) {
|
|
||||||
fatal("Tile data size must be a multiple of %" PRIu8 " bytes! (Read %zu)", tileSize,
|
|
||||||
tiles.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
|
|
||||||
size_t nbTileInstances = tiles.size() / tileSize + options.trim; // Image size in tiles
|
|
||||||
options.verbosePrint(Options::VERB_INTERM, "Read %zu tiles.\n", nbTileInstances);
|
|
||||||
std::optional<DefaultInitVec<uint8_t>> tilemap;
|
|
||||||
if (!options.tilemap.empty()) {
|
|
||||||
tilemap = readInto(options.tilemap);
|
|
||||||
nbTileInstances = tilemap->size();
|
|
||||||
options.verbosePrint(Options::VERB_INTERM, "Read %zu tilemap entries.\n", nbTileInstances);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbTileInstances == 0) {
|
|
||||||
fatal("Cannot generate empty image");
|
|
||||||
}
|
|
||||||
if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
|
||||||
warning("Read %zu tiles, more than the limit of %zu + %zu", nbTileInstances,
|
|
||||||
options.maxNbTiles[0], options.maxNbTiles[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t width = options.reversedWidth, height; // In tiles
|
|
||||||
if (nbTileInstances % width != 0) {
|
|
||||||
fatal("Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
|
|
||||||
nbTileInstances, width);
|
|
||||||
}
|
|
||||||
height = nbTileInstances / width;
|
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width,
|
|
||||||
height);
|
|
||||||
|
|
||||||
// TODO: -U
|
|
||||||
|
|
||||||
std::vector<std::array<Rgba, 4>> palettes{
|
|
||||||
{Rgba(0xffffffff), Rgba(0xaaaaaaff), Rgba(0x555555ff), Rgba(0x000000ff)}
|
|
||||||
};
|
|
||||||
if (!options.palettes.empty()) {
|
|
||||||
std::filebuf file;
|
|
||||||
file.open(options.palettes, std::ios::in | std::ios::binary);
|
|
||||||
|
|
||||||
palettes.clear();
|
|
||||||
std::array<uint8_t, sizeof(uint16_t) * 4> buf; // 4 colors
|
|
||||||
size_t nbRead;
|
|
||||||
do {
|
|
||||||
nbRead = file.sgetn(reinterpret_cast<char *>(buf.data()), buf.size());
|
|
||||||
if (nbRead == buf.size()) {
|
|
||||||
// Expand the colors
|
|
||||||
auto &palette = palettes.emplace_back();
|
|
||||||
std::generate(palette.begin(), palette.begin() + options.nbColorsPerPal,
|
|
||||||
[&buf, i = 0]() mutable {
|
|
||||||
i += 2;
|
|
||||||
return Rgba::fromCGBColor(buf[i - 2] + (buf[i - 1] << 8));
|
|
||||||
});
|
|
||||||
} else if (nbRead != 0) {
|
|
||||||
fatal("Palette data size (%zu) is not a multiple of %zu bytes!\n",
|
|
||||||
palettes.size() * buf.size() + nbRead, buf.size());
|
|
||||||
}
|
|
||||||
} while (nbRead != 0);
|
|
||||||
|
|
||||||
if (palettes.size() > options.nbPalettes) {
|
|
||||||
warning("Read %zu palettes, more than the specified limit of %zu", palettes.size(),
|
|
||||||
options.nbPalettes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefaultInitVec<uint8_t>> attrmap;
|
|
||||||
if (!options.attrmap.empty()) {
|
|
||||||
attrmap = readInto(options.attrmap);
|
|
||||||
if (attrmap->size() != nbTileInstances) {
|
|
||||||
fatal("Attribute map size (%zu tiles) doesn't match image's (%zu)", attrmap->size(),
|
|
||||||
nbTileInstances);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan through the attributes for inconsistencies
|
|
||||||
// We do this now for two reasons:
|
|
||||||
// 1. Checking those during the main loop is harmful to optimization, and
|
|
||||||
// 2. It clutters the code more, and it's not in great shape to begin with
|
|
||||||
bool bad = false;
|
|
||||||
for (auto attr : *attrmap) {
|
|
||||||
if ((attr & 0b111) > palettes.size()) {
|
|
||||||
error("Referencing palette %u, but there are only %zu!");
|
|
||||||
bad = true;
|
|
||||||
}
|
|
||||||
if (attr & 0x08 && !tilemap) {
|
|
||||||
warning("Tile in bank 1 but no tilemap specified; ignoring the bank bit");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bad) {
|
|
||||||
giveUp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tilemap) {
|
|
||||||
if (attrmap) {
|
|
||||||
for (auto [id, attr] : zip(*tilemap, *attrmap)) {
|
|
||||||
bool bank = attr & 1 << 3;
|
|
||||||
if (id >= options.maxNbTiles[bank]) {
|
|
||||||
warning("Tile #%" PRIu8
|
|
||||||
" was referenced, but the limit for bank %u is %" PRIu16,
|
|
||||||
id, bank, options.maxNbTiles[bank]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (auto id : *tilemap) {
|
|
||||||
if (id >= options.maxNbTiles[0]) {
|
|
||||||
warning("Tile #%" PRIu8 " was referenced, but the limit is %" PRIu16, id,
|
|
||||||
options.maxNbTiles[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<DefaultInitVec<uint8_t>> palmap;
|
|
||||||
if (!options.palmap.empty()) {
|
|
||||||
palmap = readInto(options.palmap);
|
|
||||||
if (palmap->size() != nbTileInstances) {
|
|
||||||
fatal("Palette map size (%zu tiles) doesn't match image's (%zu)", palmap->size(),
|
|
||||||
nbTileInstances);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
|
|
||||||
std::filebuf pngFile;
|
|
||||||
pngFile.open(options.input, std::ios::out | std::ios::binary);
|
|
||||||
png_structp png = png_create_write_struct(
|
|
||||||
PNG_LIBPNG_VER_STRING,
|
|
||||||
const_cast<png_voidp>(static_cast<void const *>(options.input.c_str())), pngError,
|
|
||||||
pngWarning);
|
|
||||||
if (!png) {
|
|
||||||
fatal("Couldn't create PNG write struct: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
png_infop pngInfo = png_create_info_struct(png);
|
|
||||||
if (!pngInfo) {
|
|
||||||
fatal("Couldn't create PNG info struct: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
png_set_write_fn(png, &pngFile, writePng, flushPng);
|
|
||||||
|
|
||||||
png_set_IHDR(png, pngInfo, options.reversedWidth * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA,
|
|
||||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
|
||||||
png_write_info(png, pngInfo);
|
|
||||||
|
|
||||||
png_color_8 sbitChunk;
|
|
||||||
sbitChunk.red = 5;
|
|
||||||
sbitChunk.green = 5;
|
|
||||||
sbitChunk.blue = 5;
|
|
||||||
sbitChunk.alpha = 1;
|
|
||||||
png_set_sBIT(png, pngInfo, &sbitChunk);
|
|
||||||
|
|
||||||
constexpr uint8_t SIZEOF_PIXEL = 4; // Each pixel is 4 bytes (RGBA @ 8 bits/component)
|
|
||||||
size_t const SIZEOF_ROW = options.reversedWidth * 8 * SIZEOF_PIXEL;
|
|
||||||
std::vector<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels
|
|
||||||
uint8_t * const rowPtrs[8] = {
|
|
||||||
&tileRow.data()[0 * SIZEOF_ROW], &tileRow.data()[1 * SIZEOF_ROW],
|
|
||||||
&tileRow.data()[2 * SIZEOF_ROW], &tileRow.data()[3 * SIZEOF_ROW],
|
|
||||||
&tileRow.data()[4 * SIZEOF_ROW], &tileRow.data()[5 * SIZEOF_ROW],
|
|
||||||
&tileRow.data()[6 * SIZEOF_ROW], &tileRow.data()[7 * SIZEOF_ROW],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (size_t ty = 0; ty < height; ++ty) {
|
|
||||||
for (size_t tx = 0; tx < width; ++tx) {
|
|
||||||
size_t index = options.columnMajor ? ty + tx * width : ty * width + tx;
|
|
||||||
// By default, a tile is unflipped, in bank 0, and uses palette #0
|
|
||||||
uint8_t attribute = attrmap.has_value() ? (*attrmap)[index] : 0x00;
|
|
||||||
bool bank = attribute & 0x08;
|
|
||||||
// Get the tile ID at this location
|
|
||||||
size_t tileID = index;
|
|
||||||
if (tilemap.has_value()) {
|
|
||||||
tileID =
|
|
||||||
(*tilemap)[index] - options.baseTileIDs[bank] + bank * options.maxNbTiles[0];
|
|
||||||
}
|
|
||||||
assert(tileID < nbTileInstances); // Should have been checked earlier
|
|
||||||
size_t palID = palmap ? (*palmap)[index] : attribute & 0b111;
|
|
||||||
assert(palID < palettes.size()); // Should be ensured on data read
|
|
||||||
|
|
||||||
// We do not have data for tiles trimmed with `-x`, so assume they are "blank"
|
|
||||||
static std::array<uint8_t, 16> const trimmedTile{
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
};
|
|
||||||
uint8_t const *tileData = tileID > nbTileInstances - options.trim
|
|
||||||
? trimmedTile.data()
|
|
||||||
: &tiles[tileID * tileSize];
|
|
||||||
auto const &palette = palettes[palID];
|
|
||||||
for (uint8_t y = 0; y < 8; ++y) {
|
|
||||||
// If vertically mirrored, fetch the bytes from the other end
|
|
||||||
uint8_t realY = attribute & 0x40 ? 7 - y : y;
|
|
||||||
uint8_t bitplane0 = tileData[realY * 2], bitplane1 = tileData[realY * 2 + 1];
|
|
||||||
if (attribute & 0x20) { // Handle horizontal flip
|
|
||||||
bitplane0 = flipTable[bitplane0];
|
|
||||||
bitplane1 = flipTable[bitplane1];
|
|
||||||
}
|
|
||||||
uint8_t *ptr = &rowPtrs[y][tx * 8 * SIZEOF_PIXEL];
|
|
||||||
for (uint8_t x = 0; x < 8; ++x) {
|
|
||||||
uint8_t bit0 = bitplane0 & 0x80, bit1 = bitplane1 & 0x80;
|
|
||||||
Rgba const &pixel = palette[bit0 >> 7 | bit1 >> 6];
|
|
||||||
*ptr++ = pixel.red;
|
|
||||||
*ptr++ = pixel.green;
|
|
||||||
*ptr++ = pixel.blue;
|
|
||||||
*ptr++ = pixel.alpha;
|
|
||||||
|
|
||||||
// Shift the pixel out
|
|
||||||
bitplane0 <<= 1;
|
|
||||||
bitplane1 <<= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We never modify the pointers, and neither should libpng, despite the overly lax function
|
|
||||||
// signature.
|
|
||||||
// (AIUI, casting away const-ness is okay as long as you don't actually modify the
|
|
||||||
// pointed-to data)
|
|
||||||
png_write_rows(png, const_cast<png_bytepp>(rowPtrs), 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finalize the write
|
|
||||||
png_write_end(png, pngInfo);
|
|
||||||
|
|
||||||
png_destroy_write_struct(&png, &pngInfo);
|
|
||||||
pngFile.close();
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022, Eldred Habert and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "gfx/main.hpp" // options
|
|
||||||
|
|
||||||
/*
|
|
||||||
* based on the Gaussian-like curve used by SameBoy since commit
|
|
||||||
* 65dd02cc52f531dbbd3a7e6014e99d5b24e71a4c (Oct 2017)
|
|
||||||
* with ties resolved by comparing the difference of the squares.
|
|
||||||
*/
|
|
||||||
static std::array<uint8_t, 256> reverse_curve{
|
|
||||||
0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, // These
|
|
||||||
5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, // comments
|
|
||||||
7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, // prevent
|
|
||||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, // clang-format
|
|
||||||
10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, // from
|
|
||||||
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, // reflowing
|
|
||||||
13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, // these
|
|
||||||
14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, // sixteen
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, // 16-item
|
|
||||||
17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, // lines,
|
|
||||||
18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, // which,
|
|
||||||
19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, // in
|
|
||||||
21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, // my
|
|
||||||
22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, // opinion,
|
|
||||||
24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, // help
|
|
||||||
26, 27, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 30, 30, 31, // visualization!
|
|
||||||
};
|
|
||||||
|
|
||||||
uint16_t Rgba::cgbColor() const {
|
|
||||||
if (isTransparent()) {
|
|
||||||
return transparent;
|
|
||||||
}
|
|
||||||
assert(isOpaque());
|
|
||||||
|
|
||||||
uint8_t r = red, g = green, b = blue;
|
|
||||||
if (options.useColorCurve) {
|
|
||||||
g = g * 4 < b ? 0 : (g * 4 - b) / 3;
|
|
||||||
r = reverse_curve[r];
|
|
||||||
g = reverse_curve[g];
|
|
||||||
b = reverse_curve[b];
|
|
||||||
}
|
|
||||||
return (r >> 3) | (g >> 3) << 5 | (b >> 3) << 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t Rgba::grayIndex() const {
|
|
||||||
assert(isGray());
|
|
||||||
// Convert from [0; 256[ to [0; maxOpaqueColors[
|
|
||||||
return static_cast<uint16_t>(255 - red) * options.maxOpaqueColors() / 256;
|
|
||||||
}
|
|
||||||
160
src/gfx/rgbgfx.1
Normal file
160
src/gfx/rgbgfx.1
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
.\"
|
||||||
|
.\" This file is part of RGBDS.
|
||||||
|
.\"
|
||||||
|
.\" Copyright (c) 2013-2021, stag019 and RGBDS contributors.
|
||||||
|
.\"
|
||||||
|
.\" SPDX-License-Identifier: MIT
|
||||||
|
.\"
|
||||||
|
.Dd March 28, 2021
|
||||||
|
.Dt RGBGFX 1
|
||||||
|
.Os
|
||||||
|
.Sh NAME
|
||||||
|
.Nm rgbgfx
|
||||||
|
.Nd Game Boy graphics converter
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.Nm
|
||||||
|
.Op Fl CDhmuVv
|
||||||
|
.Op Fl f | Fl F
|
||||||
|
.Op Fl a Ar attrmap | Fl A
|
||||||
|
.Op Fl d Ar depth
|
||||||
|
.Op Fl o Ar out_file
|
||||||
|
.Op Fl p Ar pal_file | Fl P
|
||||||
|
.Op Fl t Ar tilemap | Fl T
|
||||||
|
.Op Fl x Ar tiles
|
||||||
|
.Ar file
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
The
|
||||||
|
.Nm
|
||||||
|
program converts PNG images into the Nintendo Game Boy's planar tile format.
|
||||||
|
.Pp
|
||||||
|
The resulting colors and their palette indices are determined differently depending on the input PNG file:
|
||||||
|
.Bl -dash -width Ds
|
||||||
|
.It
|
||||||
|
If the file has an embedded palette, that palette's color and order are used.
|
||||||
|
.It
|
||||||
|
If not, and the image only contains shades of gray, rgbgfx maps them to the indices appropriate for each shade.
|
||||||
|
Any undetermined indices are set to respective default shades of gray.
|
||||||
|
For example: if the bit depth is 2 and the image contains light gray and black, they become the second and fourth colors, and the first and third colors get set to default white and dark gray.
|
||||||
|
If the image has multiple shades that map to the same index, the palette is instead determined as if the image had color.
|
||||||
|
.It
|
||||||
|
If the image has color (or the grayscale method failed), the colors are sorted from lightest to darkest.
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
The input image may not contain more colors than the selected bit depth allows.
|
||||||
|
Transparent pixels are set to palette index 0.
|
||||||
|
.Sh ARGUMENTS
|
||||||
|
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
||||||
|
.Fl Fl verb
|
||||||
|
is
|
||||||
|
.Fl Fl verbose ,
|
||||||
|
but
|
||||||
|
.Fl Fl ver
|
||||||
|
is invalid because it could also be
|
||||||
|
.Fl Fl version .
|
||||||
|
The arguments are as follows:
|
||||||
|
.Bl -tag -width Ds
|
||||||
|
.It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap
|
||||||
|
Generate a file of tile mirroring attributes for OAM or (CGB-only) background tiles.
|
||||||
|
For each tile in the input file, a byte is written representing the dimensions that the associated tile in the output file should be mirrored.
|
||||||
|
Useful in combination with
|
||||||
|
.Fl m
|
||||||
|
to keep track the mirror direction of mirrored duplicate tiles.
|
||||||
|
.It Fl A , Fl Fl output-attr-map
|
||||||
|
Same as
|
||||||
|
.Fl a ,
|
||||||
|
but the attrmap file output name is made by taking the input filename, removing the file extension, and appending
|
||||||
|
.Pa .attrmap .
|
||||||
|
.It Fl C , Fl Fl color-curve
|
||||||
|
Use the color curve of the Game Boy Color when generating palettes.
|
||||||
|
.It Fl D , Fl Fl debug
|
||||||
|
Debug features are enabled.
|
||||||
|
.It Fl d Ar depth , Fl Fl depth Ar depth
|
||||||
|
The bit depth of the output image (either 1 or 2).
|
||||||
|
By default, the bit depth is 2 (two bits per pixel).
|
||||||
|
.It Fl f , Fl Fl fix
|
||||||
|
Fix the input PNG file to be a correctly indexed image.
|
||||||
|
.It Fl F , Fl Fl fix-and-save
|
||||||
|
Same as
|
||||||
|
.Fl f ,
|
||||||
|
but additionally, the supplied command line parameters are saved within the PNG and will be loaded and automatically used next time.
|
||||||
|
.It Fl h , Fl Fl horizontal
|
||||||
|
Lay out tiles in column-major order (column by column), instead of the default row-major order (line by line).
|
||||||
|
Especially useful for "8x16" OBJ mode, if the input image is 16 pixels tall.
|
||||||
|
.It Fl m , Fl Fl mirror-tiles
|
||||||
|
Truncate tiles by checking for tiles that are mirrored versions of others and omitting these from the output file.
|
||||||
|
Useful with tilemaps and attrmaps together to keep track of the duplicated tiles and the dimension mirrored.
|
||||||
|
Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring.
|
||||||
|
Implies
|
||||||
|
.Fl u .
|
||||||
|
.It Fl o Ar out_file , Fl Fl output Ar out_file
|
||||||
|
The name of the output file.
|
||||||
|
.It Fl p Ar pal_file , Fl Fl palette Ar pal_file
|
||||||
|
Output the image's palette in standard GBC palette format: bytes (8 bytes for two bits per pixel, 4 bytes for one bit per pixel) containing the RGB15 values in little-endian byte order.
|
||||||
|
If the palette contains too few colors, the remaining entries are set to black.
|
||||||
|
.It Fl P , Fl Fl output-palette
|
||||||
|
Same as
|
||||||
|
.Fl p ,
|
||||||
|
but the palette file output name is made by taking the input PNG file's filename, removing the file extension, and appending
|
||||||
|
.Pa .pal .
|
||||||
|
.It Fl t Ar tilemap , Fl Fl tilemap Ar tilemap
|
||||||
|
Generate a file of tile indices.
|
||||||
|
For each tile in the input file, a byte is written representing the index of the associated tile in the output file.
|
||||||
|
Useful in combination with
|
||||||
|
.Fl u
|
||||||
|
or
|
||||||
|
.Fl m
|
||||||
|
to keep track of duplicate tiles.
|
||||||
|
.It Fl T , Fl Fl output-tilemap
|
||||||
|
Same as
|
||||||
|
.Fl t ,
|
||||||
|
but the tilemap file output name is made by taking the input filename, removing the file extension, and appending
|
||||||
|
.Pa .tilemap .
|
||||||
|
.It Fl u , Fl Fl unique-tiles
|
||||||
|
Truncate tiles by checking for tiles that are exact duplicates of others and omitting these from the output file.
|
||||||
|
Useful with tilemaps to keep track of the duplicated tiles.
|
||||||
|
.It Fl V , Fl Fl version
|
||||||
|
Print the version of the program and exit.
|
||||||
|
.It Fl v , Fl Fl verbose
|
||||||
|
Verbose.
|
||||||
|
Print errors when the command line parameters and the parameters in the PNG file don't match.
|
||||||
|
.It Fl x Ar tiles , Fl Fl trim-end Ar tiles
|
||||||
|
Trim the end of the output file by this many tiles.
|
||||||
|
.El
|
||||||
|
.Sh EXAMPLES
|
||||||
|
The following will take a PNG file with a bit depth of 1, 2, or 8, and output planar 2bpp data:
|
||||||
|
.Pp
|
||||||
|
.D1 $ rgbgfx -o out.2bpp in.png
|
||||||
|
.Pp
|
||||||
|
The following creates a planar 2bpp file with only unique tiles, and its tilemap
|
||||||
|
.Pa out.tilemap :
|
||||||
|
.Pp
|
||||||
|
.D1 $ rgbgfx -T -u -o out.2bpp in.png
|
||||||
|
.Pp
|
||||||
|
The following creates a planar 2bpp file with only unique tiles
|
||||||
|
.Pa accounting for tile mirroring
|
||||||
|
and its associated tilemap
|
||||||
|
.Pa out.tilemap
|
||||||
|
and attrmap
|
||||||
|
.Pa out.attrmap :
|
||||||
|
.Pp
|
||||||
|
.D1 $ rgbgfx -A -T -m -o out.2bpp in.png
|
||||||
|
.Pp
|
||||||
|
The following will do nothing:
|
||||||
|
.Pp
|
||||||
|
.D1 $ rgbgfx in.png
|
||||||
|
.Sh BUGS
|
||||||
|
Please report bugs on
|
||||||
|
.Lk https://github.com/gbdev/rgbds/issues GitHub .
|
||||||
|
.Sh SEE ALSO
|
||||||
|
.Xr rgbds 7 ,
|
||||||
|
.Xr rgbasm 1 ,
|
||||||
|
.Xr rgblink 1 ,
|
||||||
|
.Xr rgbfix 1 ,
|
||||||
|
.Xr gbz80 7
|
||||||
|
.Sh HISTORY
|
||||||
|
.Nm
|
||||||
|
was created by
|
||||||
|
.An stag019
|
||||||
|
to be included in RGBDS.
|
||||||
|
It is now maintained by a number of contributors at
|
||||||
|
.Lk https://github.com/gbdev/rgbds .
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user