mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Compare commits
22 Commits
v0.6.0-rc1
...
race-car2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76c1995559 | ||
|
|
ae84570f04 | ||
|
|
094a31ef8c | ||
|
|
291dcf3b6c | ||
|
|
a890bd072b | ||
|
|
2f6c808ccb | ||
|
|
e80907abd0 | ||
|
|
25d39155d3 | ||
|
|
77021d229b | ||
|
|
1b250b90b2 | ||
|
|
e2b4723489 | ||
|
|
2507413162 | ||
|
|
e023a84d04 | ||
|
|
34c127d9c3 | ||
|
|
9a930988c2 | ||
|
|
8c4204c542 | ||
|
|
663c1930ec | ||
|
|
30ccf43f44 | ||
|
|
fdc17adbcb | ||
|
|
cc196954f3 | ||
|
|
55b6cfff84 | ||
|
|
1fc73b04eb |
@@ -1,99 +0,0 @@
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: Left
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignConsecutiveBitFields: Consecutive
|
||||
AlignConsecutiveDeclarations: None
|
||||
AlignConsecutiveMacros: Consecutive
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: false
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros:
|
||||
- format_
|
||||
- attr_
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BitFieldColonSpacing: Both
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeConceptDeclarations: true
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: AfterComma
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 100
|
||||
CompactNamespaces: false
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
EmptyLineBeforeAccessModifier: Leave
|
||||
FixNamespaceComments: false
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<sys/'
|
||||
Priority: 0
|
||||
- Regex: '^<'
|
||||
Priority: 1
|
||||
- Regex: '^"extern/'
|
||||
Priority: 2
|
||||
- Regex: '^"(asm|link|fix|gfx)/'
|
||||
Priority: 3
|
||||
- Regex: '^"'
|
||||
Priority: 2
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: NoIndent
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: BeforeHash
|
||||
IndentRequires: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
# Only support for Javascript as of clang-format 13...
|
||||
# InsertTrailingCommas: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
LambdaBodyIndentation: Signature
|
||||
Language: Cpp
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
PPIndentWidth: -1
|
||||
PointerAlignment: Right
|
||||
ReflowComments: true
|
||||
SortIncludes: CaseSensitive
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceAroundPointerQualifiers: Both
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: c++17
|
||||
TabWidth: 4
|
||||
UseCRLF: false
|
||||
UseTab: ForIndentation
|
||||
@@ -1,9 +0,0 @@
|
||||
[*]
|
||||
root = true
|
||||
indent_style = tab
|
||||
indent_size = tab
|
||||
tab_width = 8
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,2 +0,0 @@
|
||||
# Shell scripts need Unix line endings (see https://github.com/gbdev/rgbds/issues/841)
|
||||
*.sh text eol=lf
|
||||
56
.github.bak/actions/doc_postproc.awk
Executable file
56
.github.bak/actions/doc_postproc.awk
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.bak/actions/get-pages.sh
Executable file
113
.github.bak/actions/get-pages.sh
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[@]##*/}"
|
||||
@@ -1,7 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
case "${1%-*}" in
|
||||
case `echo $1 | cut -d '-' -f 1` in
|
||||
ubuntu)
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -yq bison libpng-dev pkg-config
|
||||
@@ -6,7 +6,6 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'gbdev'
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout rgbds@release
|
||||
@@ -23,16 +22,16 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -yq groff zlib1g-dev
|
||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
|
||||
tar xf mandoc-1.14.6.tar.gz
|
||||
cd mandoc-1.14.6
|
||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
|
||||
tar xf mandoc-1.14.5.tar.gz
|
||||
cd mandoc-1.14.5
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
- 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
|
||||
../../rgbds-www/.github/actions/get-pages.sh ${GITHUB_REF##*/} *
|
||||
./.github/actions/get-pages.sh -r ../rgbds-www ${GITHUB_REF##*/}
|
||||
- name: Push new pages
|
||||
working-directory: rgbds-www
|
||||
run: |
|
||||
@@ -7,20 +7,18 @@ jobs:
|
||||
unix-testing:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-18.04, macos-11.0, macos-10.15]
|
||||
os: [ubuntu-20.04, ubuntu-18.04, ubuntu-16.04, macos-10.15]
|
||||
cc: [gcc, clang]
|
||||
buildsys: [make, cmake]
|
||||
exclude:
|
||||
# `gcc` is just an alias to `clang` on macOS, don't bother
|
||||
- os: macos-10.15
|
||||
cc: gcc
|
||||
- os: macos-11.0
|
||||
cc: gcc
|
||||
include:
|
||||
- cc: gcc
|
||||
cxx: g++
|
||||
- cc: clang
|
||||
cxx: clang++
|
||||
- os: ubuntu-18.04
|
||||
cc: gcc
|
||||
target: develop
|
||||
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||
- os: ubuntu-20.04
|
||||
cc: gcc
|
||||
target: develop
|
||||
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@@ -33,25 +31,25 @@ jobs:
|
||||
# Apple's base version is severely outdated, not even supporting -Wall,
|
||||
# but it overrides Homebrew's version nonetheless...
|
||||
- name: Build & install using Make
|
||||
if: matrix.buildsys == 'make'
|
||||
run: |
|
||||
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||
make develop -j Q= CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
|
||||
make ${{ matrix.target }} -j Q= CC=${{ matrix.cc }}
|
||||
sudo make install -j Q=
|
||||
if: matrix.buildsys == 'make'
|
||||
- name: Build & install using CMake
|
||||
if: matrix.buildsys == 'cmake'
|
||||
run: |
|
||||
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 --build build -j --verbose
|
||||
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.cc }} ${{ matrix.cmakevars }}
|
||||
cmake --build build
|
||||
cp build/src/rgb{asm,link,fix,gfx} .
|
||||
sudo cmake --install build --verbose
|
||||
sudo cmake --install build
|
||||
if: matrix.buildsys == 'cmake'
|
||||
- name: Package binaries
|
||||
run: |
|
||||
mkdir bins
|
||||
cp rgb{asm,link,fix,gfx} bins
|
||||
- name: Upload binaries
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: rgbds-canary-${{ matrix.os }}-${{ matrix.cc }}-${{ matrix.buildsys }}
|
||||
path: bins
|
||||
@@ -76,11 +74,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- 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.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
|
||||
$wc.DownloadFile('https://www.zlib.net/zlib1211.zip', 'zlib.zip')
|
||||
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
|
||||
if ($hash -ne '173e89893dcb8b4a150d7731cd72f0602f1d6b45e60e2a54efdf7f3fc3325fd7') {
|
||||
if ($hash -ne 'd7510a8ee1918b7d0cad197a089c0a2cd4d6df05fee22389f67f115e738b178d') {
|
||||
Write-Host "zlib SHA256 mismatch! ($hash)"
|
||||
exit 1
|
||||
}
|
||||
@@ -90,51 +88,38 @@ jobs:
|
||||
Write-Host "libpng SHA256 mismatch! ($hash)"
|
||||
exit 1
|
||||
}
|
||||
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.24/win_flex_bison-2.5.24.zip', 'winflexbison.zip')
|
||||
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.23/win_flex_bison-2.5.23.zip', 'winflexbison.zip')
|
||||
$hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash
|
||||
if ($hash -ne '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505') {
|
||||
if ($hash -ne '6AA5C8EA662DA1550020A5804C28BE63FFAA53486DA9F6842E24C379EC422DFC') {
|
||||
Write-Host "bison SHA256 mismatch! ($hash)"
|
||||
}
|
||||
Expand-Archive -DestinationPath . "zlib.zip"
|
||||
Expand-Archive -DestinationPath . "libpng.zip"
|
||||
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
|
||||
Move-Item zlib-1.2.12 zlib
|
||||
Move-Item zlib-1.2.11 zlib
|
||||
Move-Item lpng1637 libpng
|
||||
- uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
zbuild
|
||||
pngbuild
|
||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||
- name: Build zlib
|
||||
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 --build zbuild --config Release -j
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install zlib
|
||||
run: |
|
||||
cmake --build zbuild --config Release
|
||||
cmake --install zbuild
|
||||
- name: Build libpng
|
||||
run: |
|
||||
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
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install libpng
|
||||
run: |
|
||||
cmake --build pngbuild --config Release
|
||||
cmake --install pngbuild
|
||||
- name: Build Windows binaries
|
||||
run: |
|
||||
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --config Release -j --verbose
|
||||
cmake --install build --verbose --prefix install_dir
|
||||
cmake --build build --config Release
|
||||
cmake --install build
|
||||
- name: Package binaries
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir bins
|
||||
cp install_dir/bin/{rgbasm.exe,rgblink.exe,rgbfix.exe,rgbgfx.exe,zlib1.dll,libpng16.dll} bins
|
||||
- name: Upload Windows binaries
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: rgbds-canary-win${{ matrix.bits }}
|
||||
path: bins
|
||||
@@ -142,7 +127,6 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cp bins/* .
|
||||
cp bins/*.dll test/gfx
|
||||
test/run-tests.sh
|
||||
|
||||
windows-xbuild:
|
||||
@@ -169,7 +153,7 @@ jobs:
|
||||
./.github/actions/install_deps.sh ${{ matrix.os }}
|
||||
- name: Install MinGW
|
||||
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
|
||||
run: |
|
||||
sudo ./.github/actions/mingw-w64-libpng-dev.sh ${{ matrix.triplet }}
|
||||
@@ -185,21 +169,12 @@ jobs:
|
||||
mv rgbgfx bins/rgbgfx.exe
|
||||
cp /usr/${{ matrix.triplet }}/lib/zlib1.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
|
||||
mv test/gfx/randtilegen{,.exe}
|
||||
mv test/gfx/rgbgfx_test{,.exe}
|
||||
if [ ${{ matrix.bits }} -eq 32 ]; then cp /usr/lib/gcc/${{ matrix.triplet }}/7.3-win32/libgcc_s_sjlj-1.dll bins; fi
|
||||
- name: Upload Windows binaries
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
||||
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:
|
||||
needs: windows-xbuild
|
||||
@@ -211,20 +186,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Retrieve binaries
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: rgbds-canary-mingw-win${{ matrix.bits }}
|
||||
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
|
||||
shell: bash
|
||||
run: |
|
||||
cp bins/* .
|
||||
cp bins/*.dll test/gfx
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -4,19 +4,19 @@ on:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- man/gbz80.7
|
||||
- man/rgbds.5
|
||||
- man/rgbds.7
|
||||
- man/rgbasm.1
|
||||
- man/rgbasm.5
|
||||
- man/rgblink.1
|
||||
- man/rgblink.5
|
||||
- man/rgbfix.1
|
||||
- man/rgbgfx.1
|
||||
- .github/actions/get-pages.sh
|
||||
- src/gbz80.7
|
||||
- src/rgbds.5
|
||||
- src/rgbds.7
|
||||
- src/asm/rgbasm.1
|
||||
- src/asm/rgbasm.5
|
||||
- src/link/rgblink.1
|
||||
- src/link/rgblink.5
|
||||
- src/fix/rgbfix.1
|
||||
- src/gfx/rgbgfx.1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'gbdev'
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout rgbds@master
|
||||
@@ -35,16 +35,16 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -yq groff zlib1g-dev
|
||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.6.tar.gz'
|
||||
tar xf mandoc-1.14.6.tar.gz
|
||||
cd mandoc-1.14.6
|
||||
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
|
||||
tar xf mandoc-1.14.5.tar.gz
|
||||
cd mandoc-1.14.5
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
- name: Update pages
|
||||
working-directory: rgbds/man
|
||||
working-directory: rgbds
|
||||
run: |
|
||||
../../rgbds-www/maintainer/man_to_html.sh master *
|
||||
./.github/actions/get-pages.sh ../rgbds-www master
|
||||
- name: Push new pages
|
||||
working-directory: rgbds-www
|
||||
run: |
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
ssh-add ~/.ssh/id_ed25519
|
||||
git config --global user.name "GitHub Action"
|
||||
git config --global user.email "community@gbdev.io"
|
||||
git add -A
|
||||
git add .
|
||||
git commit -m "Update RGBDS master documentation"
|
||||
if git remote | grep -q origin; then
|
||||
git remote set-url origin git@github.com:gbdev/rgbds-www.git
|
||||
17
.github/workflows/checkdiff.yml
vendored
17
.github/workflows/checkdiff.yml
vendored
@@ -1,17 +0,0 @@
|
||||
name: "Code coverage checking"
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
checkdiff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up repo
|
||||
run: |
|
||||
git clone -b "${{ github.event.pull_request.head.ref }}" "${{ github.event.pull_request.head.repo.clone_url }}" rgbds
|
||||
cd rgbds
|
||||
git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}"
|
||||
git fetch upstream
|
||||
- name: Checkdiff
|
||||
working-directory: rgbds
|
||||
run: |
|
||||
make checkdiff "BASE_REF=${{ github.event.pull_request.base.sha }}" Q= | tee log
|
||||
10
.github/workflows/create-release-artifacts.yaml
vendored
10
.github/workflows/create-release-artifacts.yaml
vendored
@@ -17,9 +17,9 @@ jobs:
|
||||
- name: Get zlib, libpng and bison
|
||||
run: | # TODO: use an array
|
||||
$wc = New-Object System.Net.WebClient
|
||||
$wc.DownloadFile('https://www.zlib.net/zlib1212.zip', 'zlib.zip')
|
||||
$wc.DownloadFile('https://www.zlib.net/zlib1211.zip', 'zlib.zip')
|
||||
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
|
||||
if ($hash -ne '173e89893dcb8b4a150d7731cd72f0602f1d6b45e60e2a54efdf7f3fc3325fd7') {
|
||||
if ($hash -ne 'd7510a8ee1918b7d0cad197a089c0a2cd4d6df05fee22389f67f115e738b178d') {
|
||||
Write-Host "zlib SHA256 mismatch! ($hash)"
|
||||
exit 1
|
||||
}
|
||||
@@ -29,15 +29,15 @@ jobs:
|
||||
Write-Host "libpng SHA256 mismatch! ($hash)"
|
||||
exit 1
|
||||
}
|
||||
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.24/win_flex_bison-2.5.24.zip', 'winflexbison.zip')
|
||||
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.23/win_flex_bison-2.5.23.zip', 'winflexbison.zip')
|
||||
$hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash
|
||||
if ($hash -ne '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505') {
|
||||
if ($hash -ne '6AA5C8EA662DA1550020A5804C28BE63FFAA53486DA9F6842E24C379EC422DFC') {
|
||||
Write-Host "bison SHA256 mismatch! ($hash)"
|
||||
}
|
||||
Expand-Archive -DestinationPath . "zlib.zip"
|
||||
Expand-Archive -DestinationPath . "libpng.zip"
|
||||
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
|
||||
Move-Item zlib-1.2.12 zlib
|
||||
Move-Item zlib-1.2.11 zlib
|
||||
Move-Item lpng1637 libpng
|
||||
- name: Build 32-bit zlib
|
||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||
|
||||
16
.gitignore
vendored
16
.gitignore
vendored
@@ -1,12 +1,16 @@
|
||||
/rgbasm
|
||||
/rgblink
|
||||
/rgbfix
|
||||
/rgbgfx
|
||||
/rgbshim.sh
|
||||
rgbasm
|
||||
rgblink
|
||||
rgbfix
|
||||
rgbgfx
|
||||
rgbshim.sh
|
||||
*.o
|
||||
*.exe
|
||||
*.dll
|
||||
.checkpatch-camelcase.*
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
CMakeFiles
|
||||
cmake_install.cmake
|
||||
|
||||
test/pokecrystal
|
||||
test/pokered
|
||||
test/ucity
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
||||
|
||||
project(rgbds
|
||||
LANGUAGES C CXX)
|
||||
LANGUAGES C)
|
||||
|
||||
# get real path of source and binary directories
|
||||
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
|
||||
@@ -25,85 +25,54 @@ endif()
|
||||
|
||||
option(SANITIZERS "Build with sanitizers enabled" OFF) # Ignored on MSVC
|
||||
option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC
|
||||
option(TRACE_PARSER "Trace parser execution" OFF)
|
||||
option(TRACE_LEXER "Trace lexer execution" OFF)
|
||||
|
||||
if(MSVC)
|
||||
# MSVC's standard library triggers warning C5105,
|
||||
# "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)
|
||||
# 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()
|
||||
add_compile_options(-Wall -pedantic)
|
||||
add_definitions(-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE)
|
||||
if(SANITIZERS)
|
||||
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
|
||||
-fsanitize=unreachable -fsanitize=vla-bound
|
||||
-fsanitize=signed-integer-overflow -fsanitize=bounds
|
||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
|
||||
-fsanitize=alignment -fsanitize=null -fsanitize=address)
|
||||
-fsanitize=alignment -fsanitize=null)
|
||||
add_compile_options(${SAN_FLAGS})
|
||||
add_link_options(${SAN_FLAGS})
|
||||
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
||||
# TODO: this overrides anything previously set... that's a bit sloppy!
|
||||
set(CMAKE_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)
|
||||
link_libraries(${SAN_FLAGS})
|
||||
endif()
|
||||
|
||||
if(MORE_WARNINGS)
|
||||
add_compile_options(-Werror -Wextra
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2
|
||||
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused
|
||||
-Wshadow # TODO: -Wshadow=compatible-local ?
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||
-Wno-format-nonliteral # We have a couple of "dynamic" prints
|
||||
# We do some range checks that are always false on some platforms (GCC, Clang)
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare
|
||||
-Wvla # MSVC does not support VLAs
|
||||
-Wno-unknown-warning-option) # Clang shouldn't diagnose unknown warnings
|
||||
add_compile_options(-Werror -Wextra -Wno-type-limits
|
||||
-Wno-sign-compare -Wvla -Wformat -Wformat-security -Wformat-overflow=2
|
||||
-Wformat-truncation=1 -Wformat-y2k -Wswitch-enum -Wunused
|
||||
-Wuninitialized -Wunknown-pragmas -Wstrict-overflow=5
|
||||
-Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond
|
||||
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op
|
||||
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline
|
||||
-Wundef -Wstrict-prototypes -Wold-style-definition)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Use versioning consistent with Makefile
|
||||
# the git revision is used but uses the fallback in an archive
|
||||
|
||||
find_program(GIT git)
|
||||
if(GIT)
|
||||
execute_process(COMMAND ${GIT} describe --tags --dirty --always
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_REV OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET)
|
||||
message(STATUS "RGBDS version: ${GIT_REV}")
|
||||
else(GIT)
|
||||
message(STATUS "Cannot determine RGBDS version (Git not installed), falling back")
|
||||
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()
|
||||
execute_process(COMMAND git describe --tags --dirty --always
|
||||
OUTPUT_VARIABLE GIT_REV
|
||||
ERROR_QUIET)
|
||||
string(STRIP "${GIT_REV}" GIT_REV)
|
||||
|
||||
include_directories("${PROJECT_SOURCE_DIR}/include")
|
||||
|
||||
add_definitions(-DBUILD_VERSION_STRING="${GIT_REV}")
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED True)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(test)
|
||||
|
||||
# By default, build in Release mode; Debug mode must be explicitly requested
|
||||
# (You may want to augment it with the options above)
|
||||
@@ -119,18 +88,10 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
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")
|
||||
if(TRACE_PARSER)
|
||||
target_compile_definitions(rgbasm PRIVATE -DYYDEBUG)
|
||||
endif()
|
||||
|
||||
foreach(SECTION "man1" "man5" "man7")
|
||||
set(DEST "${MANDIR}/${SECTION}")
|
||||
install(FILES ${${SECTION}} DESTINATION ${DEST})
|
||||
endforeach()
|
||||
if(TRACE_LEXER)
|
||||
target_compile_definitions(rgbasm PRIVATE -DLEXER_DEBUG)
|
||||
endif()
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
Contributing
|
||||
============
|
||||
|
||||
RGBDS was created in the late '90s and has received contributions from several
|
||||
RGBDS was created in the late 90's and has received contributions from several
|
||||
developers since then. It wouldn't have been possible to get to this point
|
||||
without their work, and it is always open to the contributions of other people.
|
||||
without their work and, for that reason, it is always open to the contributions
|
||||
of other people.
|
||||
|
||||
Reporting Bugs
|
||||
--------------
|
||||
|
||||
@@ -19,10 +19,6 @@ Main contributors
|
||||
|
||||
- Antonio Niño Díaz <antonio_nd@outlook.com>
|
||||
|
||||
- Eldred "ISSOtm" Habert <eldredhabert0@gmail.com>
|
||||
|
||||
- Rangi <http://github.com/Rangi42>
|
||||
|
||||
Other contributors
|
||||
------------------
|
||||
|
||||
@@ -34,9 +30,7 @@ Other contributors
|
||||
|
||||
- David Brotz <dbrotz007@gmail.com>
|
||||
|
||||
- Jakub Kądziołka <kuba@kadziolka.net>
|
||||
|
||||
- James "JL2210" Larrowe <https://github.com/JL2210>
|
||||
- Eldred Habert <eldredhabert0@gmail.com>
|
||||
|
||||
- The Musl C library <http://www.musl-libc.org>
|
||||
|
||||
@@ -46,6 +40,8 @@ Other contributors
|
||||
|
||||
- Quint Guvernator <quint@guvernator.net>
|
||||
|
||||
- Rangi <http://github.com/Rangi42>
|
||||
|
||||
- Sanqui <gsanky@gmail.com>
|
||||
|
||||
- YamaArashi <shadow962@live.com>
|
||||
|
||||
111
Makefile
111
Makefile
@@ -7,7 +7,7 @@
|
||||
#
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .h .y .c .cpp .o
|
||||
.SUFFIXES: .h .y .c .o
|
||||
|
||||
# User-defined variables
|
||||
|
||||
@@ -30,17 +30,13 @@ PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
||||
# Note: if this comes up empty, `version.c` will automatically fall back to last release number
|
||||
VERSION_STRING := `git describe --tags --dirty --always 2>/dev/null`
|
||||
|
||||
WARNFLAGS := -Wall -pedantic
|
||||
WARNFLAGS := -Wall
|
||||
|
||||
# Overridable CFLAGS
|
||||
CFLAGS ?= -O3 -flto -DNDEBUG
|
||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||
# Non-overridable CFLAGS
|
||||
# _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 \
|
||||
-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
|
||||
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -D_POSIX_C_SOURCE=200809L \
|
||||
-Iinclude
|
||||
# Overridable LDFLAGS
|
||||
LDFLAGS ?=
|
||||
# Non-overridable LDFLAGS
|
||||
@@ -52,9 +48,6 @@ YFLAGS ?= -Wall
|
||||
BISON := bison
|
||||
RM := rm -rf
|
||||
|
||||
# Used for checking pull requests
|
||||
BASE_REF := origin/master
|
||||
|
||||
# Rules to build the RGBDS binaries
|
||||
|
||||
all: rgbasm rgblink rgbfix rgbgfx
|
||||
@@ -67,17 +60,17 @@ rgbasm_obj := \
|
||||
src/asm/lexer.o \
|
||||
src/asm/macro.o \
|
||||
src/asm/main.o \
|
||||
src/asm/parser.o \
|
||||
src/asm/opt.o \
|
||||
src/asm/output.o \
|
||||
src/asm/parser.o \
|
||||
src/asm/rpn.o \
|
||||
src/asm/section.o \
|
||||
src/asm/symbol.o \
|
||||
src/asm/util.o \
|
||||
src/asm/warning.o \
|
||||
src/extern/err.o \
|
||||
src/extern/getopt.o \
|
||||
src/extern/utf8decoder.o \
|
||||
src/error.o \
|
||||
src/hashmap.o \
|
||||
src/linkdefs.o \
|
||||
src/opmath.o
|
||||
@@ -93,28 +86,23 @@ rgblink_obj := \
|
||||
src/link/script.o \
|
||||
src/link/section.o \
|
||||
src/link/symbol.o \
|
||||
src/extern/err.o \
|
||||
src/extern/getopt.o \
|
||||
src/error.o \
|
||||
src/hashmap.o \
|
||||
src/linkdefs.o \
|
||||
src/opmath.o
|
||||
|
||||
rgbfix_obj := \
|
||||
src/fix/main.o \
|
||||
src/extern/getopt.o \
|
||||
src/error.o
|
||||
src/extern/err.o \
|
||||
src/extern/getopt.o
|
||||
|
||||
rgbgfx_obj := \
|
||||
src/gfx/gb.o \
|
||||
src/gfx/main.o \
|
||||
src/gfx/pal_packing.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/error.o
|
||||
src/gfx/makepng.o \
|
||||
src/extern/err.o \
|
||||
src/extern/getopt.o
|
||||
|
||||
rgbasm: ${rgbasm_obj}
|
||||
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbasm_obj} ${REALCFLAGS} src/version.c -lm
|
||||
@@ -126,13 +114,7 @@ rgbfix: ${rgbfix_obj}
|
||||
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbfix_obj} ${REALCFLAGS} src/version.c
|
||||
|
||||
rgbgfx: ${rgbgfx_obj}
|
||||
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} -x c++ 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}
|
||||
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCFLAGS} src/version.c ${PNGLDLIBS}
|
||||
|
||||
# Rules to process files
|
||||
|
||||
@@ -159,10 +141,7 @@ src/asm/parser.c: src/asm/parser.y
|
||||
${BISON} $$DEFS -d ${YFLAGS} -o $@ $<
|
||||
|
||||
.c.o:
|
||||
$Q${CC} ${REALCFLAGS} -c -o $@ $<
|
||||
|
||||
.cpp.o:
|
||||
$Q${CXX} ${REALCXXFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||
$Q${CC} ${REALCFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||
|
||||
# Target used to remove all files generated by other Makefile targets
|
||||
|
||||
@@ -174,7 +153,6 @@ clean:
|
||||
$Qfind src/ -name "*.o" -exec rm {} \;
|
||||
$Q${RM} rgbshim.sh
|
||||
$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.
|
||||
|
||||
@@ -185,15 +163,15 @@ install: all
|
||||
$Qinstall ${STRIP} -m ${BINMODE} rgblink ${DESTDIR}${bindir}/rgblink
|
||||
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx
|
||||
$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} man/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
|
||||
$Qinstall -m ${MANMODE} man/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
|
||||
$Qinstall -m ${MANMODE} man/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
|
||||
$Qinstall -m ${MANMODE} man/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
|
||||
$Qinstall -m ${MANMODE} man/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
|
||||
$Qinstall -m ${MANMODE} man/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
|
||||
$Qinstall -m ${MANMODE} man/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
|
||||
$Qinstall -m ${MANMODE} man/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
|
||||
$Qinstall -m ${MANMODE} src/rgbds.7 ${DESTDIR}${mandir}/man7/rgbds.7
|
||||
$Qinstall -m ${MANMODE} src/gbz80.7 ${DESTDIR}${mandir}/man7/gbz80.7
|
||||
$Qinstall -m ${MANMODE} src/rgbds.5 ${DESTDIR}${mandir}/man5/rgbds.5
|
||||
$Qinstall -m ${MANMODE} src/asm/rgbasm.1 ${DESTDIR}${mandir}/man1/rgbasm.1
|
||||
$Qinstall -m ${MANMODE} src/asm/rgbasm.5 ${DESTDIR}${mandir}/man5/rgbasm.5
|
||||
$Qinstall -m ${MANMODE} src/fix/rgbfix.1 ${DESTDIR}${mandir}/man1/rgbfix.1
|
||||
$Qinstall -m ${MANMODE} src/link/rgblink.1 ${DESTDIR}${mandir}/man1/rgblink.1
|
||||
$Qinstall -m ${MANMODE} src/link/rgblink.5 ${DESTDIR}${mandir}/man5/rgblink.5
|
||||
$Qinstall -m ${MANMODE} src/gfx/rgbgfx.1 ${DESTDIR}${mandir}/man1/rgbgfx.1
|
||||
|
||||
# Target used to check the coding style of the whole codebase.
|
||||
# `extern/` is excluded, as it contains external code that should not be patched
|
||||
@@ -210,8 +188,9 @@ checkcodebase:
|
||||
# the first common commit between the HEAD and origin/master.
|
||||
# `.y` files aren't checked, unfortunately...
|
||||
|
||||
BASE_REF:= origin/master
|
||||
checkpatch:
|
||||
$QCOMMON_COMMIT=`git merge-base HEAD ${BASE_REF}`; \
|
||||
$Qeval COMMON_COMMIT=$$(git merge-base HEAD ${BASE_REF}); \
|
||||
for commit in `git rev-list $$COMMON_COMMIT..HEAD`; do \
|
||||
echo "[*] Analyzing commit '$$commit'"; \
|
||||
git format-patch --stdout "$$commit~..$$commit" \
|
||||
@@ -219,34 +198,24 @@ checkpatch:
|
||||
| ${CHECKPATCH} - || true; \
|
||||
done
|
||||
|
||||
# Target used to check for suspiciously missing changed files.
|
||||
|
||||
checkdiff:
|
||||
$Qcontrib/checkdiff.bash `git merge-base HEAD ${BASE_REF}`
|
||||
|
||||
# This target is used during development in order to prevent adding new issues
|
||||
# to the source code. All warnings are treated as errors in order to block the
|
||||
# compilation and make the continous integration infrastructure return failure.
|
||||
# The rationale for some of the flags is documented in the CMakeLists.
|
||||
|
||||
develop:
|
||||
$Qenv ${MAKE} WARNFLAGS="-Werror -Wextra \
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2 \
|
||||
-Wstringop-overflow=4 -Wstrict-overflow=5 -Wundef -Wuninitialized -Wunused \
|
||||
-Wshadow \
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||
-Wno-format-nonliteral \
|
||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare \
|
||||
-Wvla \
|
||||
-Wno-unknown-warning-option \
|
||||
$Qenv $(MAKE) -j WARNFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-type-limits \
|
||||
-Wno-sign-compare -Wvla -Wformat -Wformat-security -Wformat-overflow=2 \
|
||||
-Wformat-truncation=1 -Wformat-y2k -Wswitch-enum -Wunused \
|
||||
-Wuninitialized -Wunknown-pragmas -Wstrict-overflow=4 \
|
||||
-Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond \
|
||||
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op \
|
||||
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline \
|
||||
-Wundef -Wstrict-prototypes -Wold-style-definition \
|
||||
-fsanitize=shift -fsanitize=integer-divide-by-zero \
|
||||
-fsanitize=unreachable -fsanitize=vla-bound \
|
||||
-fsanitize=signed-integer-overflow -fsanitize=bounds \
|
||||
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \
|
||||
-fsanitize=alignment -fsanitize=null -fsanitize=address" \
|
||||
CFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
|
||||
CXXFLAGS="-ggdb3 -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||
-fsanitize=alignment -fsanitize=null" CFLAGS="-ggdb3 -O0"
|
||||
|
||||
# Targets for the project maintainer to easily create Windows exes.
|
||||
# This is not for Windows users!
|
||||
@@ -254,14 +223,12 @@ develop:
|
||||
# install instructions instead.
|
||||
|
||||
mingw32:
|
||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
||||
CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ \
|
||||
BISON=bison PKG_CONFIG=i686-w64-mingw32-pkg-config -j
|
||||
$Qmake CC=i686-w64-mingw32-gcc BISON=bison \
|
||||
PKG_CONFIG=i686-w64-mingw32-pkg-config -j
|
||||
|
||||
mingw64:
|
||||
$Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \
|
||||
CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ \
|
||||
BISON=bison PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
|
||||
$Qmake CC=x86_64-w64-mingw32-gcc BISON=bison \
|
||||
PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
|
||||
|
||||
wine-shim:
|
||||
$Qecho '#!/bin/bash' > rgbshim.sh
|
||||
|
||||
78
README.rst
78
README.rst
@@ -12,18 +12,17 @@ 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
|
||||
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 source code of the website itself is on GitHub as well under the repo `rgbds-www <https://github.com/gbdev/rgbds-www>`__.
|
||||
|
||||
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``.
|
||||
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.
|
||||
|
||||
1. Installing RGBDS
|
||||
-------------------
|
||||
|
||||
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.
|
||||
|
||||
.. code:: sh
|
||||
@@ -46,68 +45,53 @@ The RGBDS source code file structure somewhat resembles the following:
|
||||
|
||||
.
|
||||
├── .github/
|
||||
│ ├── actions/
|
||||
│ │ └── ...
|
||||
│ └── workflows/
|
||||
│ └── ...
|
||||
│ ├── actions/
|
||||
│ │ └── ...
|
||||
│ └── workflows/
|
||||
│ └── ...
|
||||
├── contrib/
|
||||
│ ├── zsh_compl/
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
│ ├── zsh_compl/
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
├── include/
|
||||
│ └── ...
|
||||
├── man/
|
||||
│ └── ...
|
||||
│ └── ...
|
||||
├── src/
|
||||
│ ├── asm/
|
||||
│ │ └── ...
|
||||
│ ├── extern/
|
||||
│ │ └── ...
|
||||
│ ├── fix/
|
||||
│ │ └── ...
|
||||
│ ├── gfx/
|
||||
│ │ └── ...
|
||||
│ ├── link/
|
||||
│ │ └── ...
|
||||
│ ├── CMakeLists.txt
|
||||
│ └── ...
|
||||
│ ├── asm/
|
||||
│ │ └── ...
|
||||
│ ├── extern/
|
||||
│ │ └── ...
|
||||
│ ├── fix/
|
||||
│ │ └── ...
|
||||
│ ├── gfx/
|
||||
│ │ └── ...
|
||||
│ ├── link/
|
||||
│ │ └── ...
|
||||
│ ├── CMakeLists.txt
|
||||
│ └── ...
|
||||
├── test/
|
||||
│ ├── ...
|
||||
│ ├── ...
|
||||
│ └── run-tests.sh
|
||||
├── .clang-format
|
||||
├── CMakeLists.txt
|
||||
├── Makefile
|
||||
└── README.rst
|
||||
|
||||
.. |clang-format| replace:: ``clang-format``
|
||||
.. _clang-format: https://clang.llvm.org/docs/ClangFormat.html
|
||||
|
||||
- ``.github/`` - files and scripts related to the integration of the RGBDS codebase with
|
||||
GitHub.
|
||||
|
||||
* ``actions/`` - scripts used by workflow files.
|
||||
* ``workflows/`` - CI workflow description files.
|
||||
|
||||
- ``contrib/`` - scripts and other resources which may be useful to users and developers of
|
||||
RGBDS.
|
||||
|
||||
* ``zsh_compl`` contains tab completion scripts for use with zsh. Put them somewhere in your ``fpath``, and they should auto-load.
|
||||
|
||||
* ``bash_compl`` contains tab completion scripts for use with bash. Run them with ``source`` somewhere in your ``.bashrc``, and they should load every time you open a shell.
|
||||
|
||||
- ``include/`` - header files for each respective C files in `src`.
|
||||
|
||||
- ``man/`` - manual pages.
|
||||
|
||||
- ``src/`` - source code of RGBDS.
|
||||
- ``src/`` - source code and manual pages for RGBDS.
|
||||
|
||||
* 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.
|
||||
|
||||
- ``test/`` - testing framework used to verify that changes to the code don't break or modify the behavior of RGBDS.
|
||||
|
||||
- ``.clang-format`` - code style for automated formatting with |clang-format|_. The C code does not currently follow this style, but all C++ code should.
|
||||
|
||||
3. History
|
||||
----------
|
||||
|
||||
@@ -129,11 +113,3 @@ The RGBDS source code file structure somewhat resembles the following:
|
||||
- 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.
|
||||
|
||||
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.
|
||||
|
||||
67
RELEASE.rst
67
RELEASE.rst
@@ -1,67 +0,0 @@
|
||||
Releasing
|
||||
=========
|
||||
|
||||
This describes for the maintainers of RGBDS how to publish a new release on
|
||||
GitHub.
|
||||
|
||||
1. Update, commit, and push `include/version.h <include/version.h>`__ with
|
||||
values for ``PACKAGE_VERSION_MAJOR``, ``PACKAGE_VERSION_MINOR``,
|
||||
``PACKAGE_VERSION_PATCH``, and ``PACKAGE_VERSION_RC``. Only define
|
||||
``PACKAGE_VERSION_RC`` if you are publishing a release candidate! You can
|
||||
use ``git commit -m "Release <version>"`` and ``git push origin master``.
|
||||
|
||||
2. Create a Git tag formatted as ``v<MAJOR>.<MINOR>.<PATCH>``, or
|
||||
``v<MAJOR>.<MINOR>.<PATCH>-rc<RC>`` for a release candidate. ``MAJOR``,
|
||||
``MINOR``, ``PATCH``, and ``RC`` should match their values from
|
||||
`include/version.h <include/version.h>`__. You can use ``git tag <tag>``.
|
||||
|
||||
3. Push the tag to GitHub. You can use ``git push origin <tag>``.
|
||||
|
||||
GitHub Actions will run the `create-release-artifacts.yaml
|
||||
<.github/workflows/create-release-artifacts.yaml>`__ workflow to detect the
|
||||
tag starting with "``v[0-9]``" and automatically do the following:
|
||||
|
||||
1. Build 32-bit and 64-bit RGBDS binaries for Windows with ``cmake``.
|
||||
|
||||
2. Package the binaries into zip files.
|
||||
|
||||
3. Package the source code into a tar.gz file with ``make dist``.
|
||||
|
||||
4. Create a draft GitHub release for the tag, attaching the three
|
||||
packaged files. It will be a prerelease if the tag contains "``-rc``".
|
||||
|
||||
If an error occurred in the above steps, delete the tag and restart the
|
||||
procedure. You can use ``git push --delete origin <tag>`` and
|
||||
``git tag --delete <tag>``.
|
||||
|
||||
4. GitHub Actions will run the `create-release-docs.yml
|
||||
<.github/workflows/create-release-docs.yml>`__ workflow to add the release
|
||||
documentation to `rgbds-www <https://github.com/gbdev/rgbds-www>`__.
|
||||
|
||||
For a release candidate, which creates a prerelease, you will have to
|
||||
take these steps yourself.
|
||||
|
||||
1. Clone `rgbds-www <https://github.com/gbdev/rgbds-www>`__. You can use
|
||||
``git clone https://github.com/gbdev/rgbds-www.git``.
|
||||
|
||||
2. Make sure that you have installed ``groff`` and ``mandoc``. You will
|
||||
need ``mandoc`` 1.14.5 or later to support ``-O toc``.
|
||||
|
||||
3. Run ``.github/actions/get-pages.sh -r <path/to/rgbds-www> <tag>``. This
|
||||
will render the RGBDS documentation as HTML and PDF and copy it to
|
||||
``rgbds-www``.
|
||||
|
||||
If you do not have ``groff`` installed, you can change
|
||||
``groff -Tpdf -mdoc -wall`` to ``mandoc -Tpdf -I os=Linux`` in
|
||||
`.github/actions/get-pages.sh <.github/actions/get-pages.sh>`__ and it
|
||||
will suffice.
|
||||
|
||||
4. Commit and push the documentation. You can use ``git commit -m
|
||||
"Create RGBDS <tag> documentation"`` and ``git push origin master``
|
||||
(within the ``rgbds-www`` directory, not RGBDS).
|
||||
|
||||
5. Write a changelog in the GitHub draft release.
|
||||
|
||||
6. Click the "Publish release" button to publish it!
|
||||
|
||||
7. Update the `release` branch. You can use ``git push origin release``.
|
||||
@@ -1,215 +0,0 @@
|
||||
#/usr/bin/env bash
|
||||
|
||||
# Known bugs:
|
||||
# - Newlines in file/directory names break this script
|
||||
# This is because we rely on `compgen -A`, which is broken like this.
|
||||
# A fix would require implementing it ourselves, and no thanks!
|
||||
# - `rgbasm --binary-digits=a` is treated the same as `rgbasm --binary-digits=` (for example)
|
||||
# This is not our fault, Bash passes both of these identically.
|
||||
# Maybe it could be worked around, but such a fix would likely be involved.
|
||||
# The user can work around it by typing `--binary-digits ''` instead, 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 -Md<tab>` would autocomplete to `rgbasm -Mdir ` (trailing space) instead.
|
||||
# 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.
|
||||
# (Also, there might be more possible bugs if `-Mdir` is actually a directory. Ugh.)
|
||||
|
||||
# Something to note:
|
||||
# `rgbasm --binary-digits=a` gets passed to us as ('rgbasm' '--binary-digits' '=' 'a')
|
||||
# Thus, we don't need to do much to handle that form of argument passing: skip '=' after long opts.
|
||||
|
||||
_rgbasm_completions() {
|
||||
COMPREPLY=()
|
||||
|
||||
# Format: "long_opt:state_after"
|
||||
# Empty long opt = it doesn't exit
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[E]="export-all:normal"
|
||||
[h]="halt-without-nop:normal"
|
||||
[L]="preserve-ld:normal"
|
||||
[v]="verbose:normal"
|
||||
[w]=":normal"
|
||||
[b]="binary-digits:unk"
|
||||
[D]="define:unk"
|
||||
[g]="gfx-chars:unk"
|
||||
[i]="include:dir"
|
||||
[M]="dependfile:glob-*.mk *.d"
|
||||
[o]="output:glob-*.o"
|
||||
[p]="pad-value:unk"
|
||||
[r]="recursion-depth:unk"
|
||||
[W]="warning:warning"
|
||||
)
|
||||
# Parse command-line up to current word
|
||||
local opt_ena=true
|
||||
# Possible states:
|
||||
# - normal = Well, normal. Options are parsed normally.
|
||||
# - unk = An argument that can't be completed, and should just be skipped.
|
||||
# - warning = A warning flag.
|
||||
# - dir = A directory path
|
||||
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
|
||||
local state=normal
|
||||
# The length of the option, used as a return value by the function below
|
||||
local optlen=0
|
||||
# $1: a short option word
|
||||
# `state` will be set to the parsing state after the last option character in the word. If
|
||||
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
|
||||
# part of the argument.
|
||||
parse_short_opt() {
|
||||
for (( i = 1; i < "${#1}"; i++ )); do
|
||||
# If the option is not known, assume it doesn't take an argument
|
||||
local opt="${opts["${1:$i:1}"]:-":normal"}"
|
||||
state="${opt#*:}"
|
||||
# If the option takes an argument, record the length and exit
|
||||
if [[ "$state" != 'normal' ]]; then
|
||||
let optlen="$i + 1"
|
||||
return
|
||||
fi
|
||||
done
|
||||
optlen=0
|
||||
}
|
||||
|
||||
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
||||
local word="${COMP_WORDS[$i]}"
|
||||
|
||||
# If currently processing an argument, skip this word
|
||||
if [[ "$state" != 'normal' ]]; then
|
||||
state=normal
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$word" = '--' ]]; then
|
||||
# Options stop being parsed after this
|
||||
opt_ena=false
|
||||
break
|
||||
fi
|
||||
|
||||
# Check if it's a long option
|
||||
if [[ "${word:0:2}" = '--' ]]; then
|
||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||
for long_opt in "${opts[@]}"; do
|
||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||
state="${long_opt#*:}"
|
||||
# Check if the next word is just '='; if so, skip it, the argument must follow
|
||||
# (See "known bugs" at the top of this script)
|
||||
let i++
|
||||
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
|
||||
let i--
|
||||
fi
|
||||
optlen=0
|
||||
break
|
||||
fi
|
||||
done
|
||||
# Check if it's a short option
|
||||
elif [[ "${word:0:1}" = '-' ]]; then
|
||||
# The `-M?` ones are a mix of short and long, augh
|
||||
# They must match the *full* word, but only take a single dash
|
||||
# So, handle them here
|
||||
if [[ "$1" = "-M"[GP] ]]; then
|
||||
state=normal
|
||||
elif [[ "$1" = "-M"[TQ] ]]; then
|
||||
state='glob-*.d *.mk *.o'
|
||||
else
|
||||
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
|
||||
done
|
||||
|
||||
# Parse current word
|
||||
# Careful that it might look like an option, so use `--` aggressively!
|
||||
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
||||
|
||||
# Process options, as short ones may change the state
|
||||
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
||||
# We might want to complete to an option or an arg to that option
|
||||
# Parse the option word to check
|
||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||
|
||||
# Is this a long option?
|
||||
if [[ "${cur_word:1:1}" = '-' ]]; then
|
||||
# It is, try to complete one
|
||||
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
||||
return 0
|
||||
else
|
||||
# Short options may be grouped, parse them to determine what to complete
|
||||
# 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
|
||||
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
||||
return 0
|
||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||
# This short option group only awaits its argument!
|
||||
# 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
|
||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||
# `-W`, and it doesn't break anything.
|
||||
COMPREPLY+=( "$cur_word" )
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$state" in
|
||||
unk) # Return with no replies: no idea what to complete!
|
||||
;;
|
||||
warning)
|
||||
COMPREPLY+=( $(compgen -W "
|
||||
assert
|
||||
backwards-for
|
||||
builtin-args
|
||||
charmap-redef
|
||||
div
|
||||
empty-data-directive
|
||||
empty-macro-arg
|
||||
empty-strrpl
|
||||
large-constant
|
||||
long-string
|
||||
macro-shift
|
||||
nested-comment
|
||||
numeric-string
|
||||
obsolete
|
||||
shift
|
||||
shift-amount
|
||||
truncation
|
||||
user
|
||||
all
|
||||
extra
|
||||
everything
|
||||
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
|
||||
;;
|
||||
normal) # Acts like a glob...
|
||||
state="glob-*.asm *.inc *.sm83"
|
||||
;&
|
||||
glob-*)
|
||||
while read -r word; do
|
||||
COMPREPLY+=("${cur_word:0:$optlen}$word")
|
||||
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
|
||||
# Also complete directories
|
||||
;&
|
||||
dir)
|
||||
while read -r word; do
|
||||
COMPREPLY+=("${cur_word:0:$optlen}$word")
|
||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||
compopt -o filenames
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
complete -F _rgbasm_completions rgbasm
|
||||
@@ -1,181 +0,0 @@
|
||||
#/usr/bin/env bash
|
||||
|
||||
# Same notes as RGBASM
|
||||
|
||||
_rgbfix_completions() {
|
||||
COMPREPLY=()
|
||||
|
||||
# Format: "long_opt:state_after"
|
||||
# Empty long opt = it doesn't exit
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[j]="non-japanese:normal"
|
||||
[s]="sgb-compatible:normal"
|
||||
[v]="validate:normal"
|
||||
[C]="color-only:normal"
|
||||
[c]="color-compatible:normal"
|
||||
[f]="fix-spec:fix-spec"
|
||||
[i]="game-id:unk"
|
||||
[k]="new-licensee:unk"
|
||||
[l]="old-licensee:unk"
|
||||
[m]="mbc-type:mbc"
|
||||
[n]="rom-version:unk"
|
||||
[p]="pad-value:unk"
|
||||
[r]="ram-size:unk"
|
||||
[t]="title:unk"
|
||||
)
|
||||
# Parse command-line up to current word
|
||||
local opt_ena=true
|
||||
# Possible states:
|
||||
# - normal = Well, normal. Options are parsed normally.
|
||||
# - unk = An argument that can't be completed, and should just be skipped.
|
||||
# - warning = A warning flag.
|
||||
# - dir = A directory path
|
||||
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
|
||||
local state=normal
|
||||
# The length of the option, used as a return value by the function below
|
||||
local optlen=0
|
||||
# $1: a short option word
|
||||
# `state` will be set to the parsing state after the last option character in the word. If
|
||||
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
|
||||
# part of the argument.
|
||||
parse_short_opt() {
|
||||
for (( i = 1; i < "${#1}"; i++ )); do
|
||||
# If the option is not known, assume it doesn't take an argument
|
||||
local opt="${opts["${1:$i:1}"]:-":normal"}"
|
||||
state="${opt#*:}"
|
||||
# If the option takes an argument, record the length and exit
|
||||
if [[ "$state" != 'normal' ]]; then
|
||||
let optlen="$i + 1"
|
||||
return
|
||||
fi
|
||||
done
|
||||
optlen=0
|
||||
}
|
||||
|
||||
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
||||
local word="${COMP_WORDS[$i]}"
|
||||
|
||||
# If currently processing an argument, skip this word
|
||||
if [[ "$state" != 'normal' ]]; then
|
||||
state=normal
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$word" = '--' ]]; then
|
||||
# Options stop being parsed after this
|
||||
opt_ena=false
|
||||
break
|
||||
fi
|
||||
|
||||
# Check if it's a long option
|
||||
if [[ "${word:0:2}" = '--' ]]; then
|
||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||
for long_opt in "${opts[@]}"; do
|
||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||
state="${long_opt#*:}"
|
||||
# Check if the next word is just '='; if so, skip it, the argument must follow
|
||||
# (See "known bugs" at the top of this script)
|
||||
let i++
|
||||
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
|
||||
let i--
|
||||
fi
|
||||
optlen=0
|
||||
break
|
||||
fi
|
||||
done
|
||||
# Check if it's a short option
|
||||
elif [[ "${word:0:1}" = '-' ]]; then
|
||||
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
|
||||
done
|
||||
|
||||
# Parse current word
|
||||
# Careful that it might look like an option, so use `--` aggressively!
|
||||
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
||||
|
||||
# Process options, as short ones may change the state
|
||||
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
||||
# We might want to complete to an option or an arg to that option
|
||||
# Parse the option word to check
|
||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||
|
||||
# Is this a long option?
|
||||
if [[ "${cur_word:1:1}" = '-' ]]; then
|
||||
# It is, try to complete one
|
||||
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
||||
return 0
|
||||
else
|
||||
# Short options may be grouped, parse them to determine what to complete
|
||||
parse_short_opt "$cur_word"
|
||||
|
||||
if [[ "$state" = 'normal' ]]; then
|
||||
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
||||
return 0
|
||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||
# This short option group only awaits its argument!
|
||||
# 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
|
||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||
# `-W`, and it doesn't break anything.
|
||||
COMPREPLY+=( "$cur_word" )
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$state" in
|
||||
unk) # Return with no replies: no idea what to complete!
|
||||
;;
|
||||
fix-spec)
|
||||
COMPREPLY+=( "${cur_word}"{l,h,g,L,H,G} )
|
||||
;;
|
||||
mbc)
|
||||
local cur_arg="${cur_word:$optlen}"
|
||||
cur_arg="${cur_arg@U}"
|
||||
COMPREPLY=( $(compgen -W "
|
||||
ROM_ONLY
|
||||
MBC1{,+RAM,+RAM+BATTERY}
|
||||
MBC2{,+BATTERY}
|
||||
MMM01{,+RAM}
|
||||
MBC3{+TIMER+BATTERY,+TIMER+RAM+BATTERY,,+RAM,+RAM+BATTERY}
|
||||
MBC5{,+RAM,+RAM+BATTERY,+RUMBLE,+RUMBLE+RAM,+RUMBLE+RAM+BATTERY}
|
||||
MBC6
|
||||
MBC7+SENSOR+RUMBLE+RAM+BATTERY
|
||||
POCKET_CAMERA
|
||||
BANDAI_TAMA5
|
||||
HUC3
|
||||
HUC1+RAM+BATTERY
|
||||
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...
|
||||
state="glob-*.gb *.gbc *.sgb"
|
||||
;&
|
||||
glob-*)
|
||||
while read -r word; do
|
||||
COMPREPLY+=("${cur_word:0:$optlen}$word")
|
||||
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
|
||||
# Also complete directories
|
||||
;&
|
||||
dir)
|
||||
while read -r word; do
|
||||
COMPREPLY+=("${cur_word:0:$optlen}$word")
|
||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||
compopt -o filenames
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
complete -F _rgbfix_completions rgbfix
|
||||
@@ -1,167 +0,0 @@
|
||||
#/usr/bin/env bash
|
||||
|
||||
# Same notes as RGBASM
|
||||
|
||||
_rgbgfx_completions() {
|
||||
COMPREPLY=()
|
||||
|
||||
# Format: "long_opt:state_after"
|
||||
# Empty long opt = it doesn't exit
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[C]="color-curve:normal"
|
||||
[m]="mirror-tiles:normal"
|
||||
[u]="unique-tiles:normal"
|
||||
[v]="verbose:normal"
|
||||
[Z]="columns:normal"
|
||||
[a]="attr-map:*.attrmap"
|
||||
[A]="output-attr-map:normal"
|
||||
[b]="base-tiles:unk"
|
||||
[d]="depth:unk"
|
||||
[L]="slice:unk"
|
||||
[N]="nb-tiles:unk"
|
||||
[n]="nb-palettes:unk"
|
||||
[o]="output:glob *.2bpp"
|
||||
[p]="palette:glob *.pal"
|
||||
[P]="output-palette:normal"
|
||||
[q]="palette-map:glob *.palmap"
|
||||
[Q]="output-palette-map:normal"
|
||||
[r]="reverse:unk"
|
||||
[s]="palette-size:unk"
|
||||
[t]="tilemap:glob *.tilemap"
|
||||
[T]="output-tilemap:normal"
|
||||
[x]="trim-end:unk"
|
||||
)
|
||||
# Parse command-line up to current word
|
||||
local opt_ena=true
|
||||
# Possible states:
|
||||
# - normal = Well, normal. Options are parsed normally.
|
||||
# - unk = An argument that can't be completed, and should just be skipped.
|
||||
# - warning = A warning flag.
|
||||
# - dir = A directory path
|
||||
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
|
||||
local state=normal
|
||||
# The length of the option, used as a return value by the function below
|
||||
local optlen=0
|
||||
# $1: a short option word
|
||||
# `state` will be set to the parsing state after the last option character in the word. If
|
||||
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
|
||||
# part of the argument.
|
||||
parse_short_opt() {
|
||||
for (( i = 1; i < "${#1}"; i++ )); do
|
||||
# If the option is not known, assume it doesn't take an argument
|
||||
local opt="${opts["${1:$i:1}"]:-":normal"}"
|
||||
state="${opt#*:}"
|
||||
# If the option takes an argument, record the length and exit
|
||||
if [[ "$state" != 'normal' ]]; then
|
||||
let optlen="$i + 1"
|
||||
return
|
||||
fi
|
||||
done
|
||||
optlen=0
|
||||
}
|
||||
|
||||
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
||||
local word="${COMP_WORDS[$i]}"
|
||||
|
||||
# If currently processing an argument, skip this word
|
||||
if [[ "$state" != 'normal' ]]; then
|
||||
state=normal
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$word" = '--' ]]; then
|
||||
# Options stop being parsed after this
|
||||
opt_ena=false
|
||||
break
|
||||
fi
|
||||
|
||||
# Check if it's a long option
|
||||
if [[ "${word:0:2}" = '--' ]]; then
|
||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||
for long_opt in "${opts[@]}"; do
|
||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||
state="${long_opt#*:}"
|
||||
# Check if the next word is just '='; if so, skip it, the argument must follow
|
||||
# (See "known bugs" at the top of this script)
|
||||
let i++
|
||||
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
|
||||
let i--
|
||||
fi
|
||||
optlen=0
|
||||
break
|
||||
fi
|
||||
done
|
||||
# Check if it's a short option
|
||||
elif [[ "${word:0:1}" = '-' ]]; then
|
||||
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
|
||||
done
|
||||
|
||||
# Parse current word
|
||||
# Careful that it might look like an option, so use `--` aggressively!
|
||||
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
||||
|
||||
# Process options, as short ones may change the state
|
||||
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
||||
# We might want to complete to an option or an arg to that option
|
||||
# Parse the option word to check
|
||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||
|
||||
# Is this a long option?
|
||||
if [[ "${cur_word:1:1}" = '-' ]]; then
|
||||
# It is, try to complete one
|
||||
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
||||
return 0
|
||||
else
|
||||
# Short options may be grouped, parse them to determine what to complete
|
||||
parse_short_opt "$cur_word"
|
||||
|
||||
if [[ "$state" = 'normal' ]]; then
|
||||
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
||||
return 0
|
||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||
# This short option group only awaits its argument!
|
||||
# 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
|
||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||
# `-W`, and it doesn't break anything.
|
||||
COMPREPLY+=( "$cur_word" )
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$state" in
|
||||
unk) # Return with no replies: no idea what to complete!
|
||||
;;
|
||||
normal) # Acts like a glob...
|
||||
state="glob-*.png"
|
||||
;&
|
||||
glob-*)
|
||||
while read -r word; do
|
||||
COMPREPLY+=("${cur_word:0:$optlen}$word")
|
||||
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
|
||||
# Also complete directories
|
||||
;&
|
||||
dir)
|
||||
while read -r word; do
|
||||
COMPREPLY+=("${cur_word:0:$optlen}$word")
|
||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||
compopt -o filenames
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
complete -F _rgbgfx_completions rgbgfx
|
||||
@@ -1,157 +0,0 @@
|
||||
#/usr/bin/env bash
|
||||
|
||||
# Same notes as RGBASM
|
||||
|
||||
_rgblink_completions() {
|
||||
COMPREPLY=()
|
||||
|
||||
# Format: "long_opt:state_after"
|
||||
# Empty long opt = it doesn't exit
|
||||
# See the `state` variable below for info about `state_after`
|
||||
declare -A opts=(
|
||||
[V]="version:normal"
|
||||
[d]="dmg:normal"
|
||||
[t]="tiny:normal"
|
||||
[v]="verbose:normal"
|
||||
[w]="wramx:normal"
|
||||
[x]="nopad:normal"
|
||||
[l]="linkerscript:glob-*"
|
||||
[m]="map:glob-*.map"
|
||||
[n]="sym:glob-*.sym"
|
||||
[O]="overlay:glob-*.gb *.gbc *.sgb"
|
||||
[o]="output:glob-*.gb *.gbc *.sgb"
|
||||
[p]="pad:unk"
|
||||
[s]="smart:unk"
|
||||
)
|
||||
# Parse command-line up to current word
|
||||
local opt_ena=true
|
||||
# Possible states:
|
||||
# - normal = Well, normal. Options are parsed normally.
|
||||
# - unk = An argument that can't be completed, and should just be skipped.
|
||||
# - warning = A warning flag.
|
||||
# - dir = A directory path
|
||||
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
|
||||
local state=normal
|
||||
# The length of the option, used as a return value by the function below
|
||||
local optlen=0
|
||||
# $1: a short option word
|
||||
# `state` will be set to the parsing state after the last option character in the word. If
|
||||
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
|
||||
# part of the argument.
|
||||
parse_short_opt() {
|
||||
for (( i = 1; i < "${#1}"; i++ )); do
|
||||
# If the option is not known, assume it doesn't take an argument
|
||||
local opt="${opts["${1:$i:1}"]:-":normal"}"
|
||||
state="${opt#*:}"
|
||||
# If the option takes an argument, record the length and exit
|
||||
if [[ "$state" != 'normal' ]]; then
|
||||
let optlen="$i + 1"
|
||||
return
|
||||
fi
|
||||
done
|
||||
optlen=0
|
||||
}
|
||||
|
||||
for (( i = 1; i < $COMP_CWORD; i++ )); do
|
||||
local word="${COMP_WORDS[$i]}"
|
||||
|
||||
# If currently processing an argument, skip this word
|
||||
if [[ "$state" != 'normal' ]]; then
|
||||
state=normal
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$word" = '--' ]]; then
|
||||
# Options stop being parsed after this
|
||||
opt_ena=false
|
||||
break
|
||||
fi
|
||||
|
||||
# Check if it's a long option
|
||||
if [[ "${word:0:2}" = '--' ]]; then
|
||||
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
|
||||
for long_opt in "${opts[@]}"; do
|
||||
if [[ "$word" = "--${long_opt%%:*}" ]]; then
|
||||
state="${long_opt#*:}"
|
||||
# Check if the next word is just '='; if so, skip it, the argument must follow
|
||||
# (See "known bugs" at the top of this script)
|
||||
let i++
|
||||
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
|
||||
let i--
|
||||
fi
|
||||
optlen=0
|
||||
break
|
||||
fi
|
||||
done
|
||||
# Check if it's a short option
|
||||
elif [[ "${word:0:1}" = '-' ]]; then
|
||||
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
|
||||
done
|
||||
|
||||
# Parse current word
|
||||
# Careful that it might look like an option, so use `--` aggressively!
|
||||
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
|
||||
|
||||
# Process options, as short ones may change the state
|
||||
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
|
||||
# We might want to complete to an option or an arg to that option
|
||||
# Parse the option word to check
|
||||
# There's no whitespace in the option names, so we can ride a little dirty...
|
||||
|
||||
# Is this a long option?
|
||||
if [[ "${cur_word:1:1}" = '-' ]]; then
|
||||
# It is, try to complete one
|
||||
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
|
||||
return 0
|
||||
else
|
||||
# Short options may be grouped, parse them to determine what to complete
|
||||
parse_short_opt "$cur_word"
|
||||
|
||||
if [[ "$state" = 'normal' ]]; then
|
||||
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
|
||||
return 0
|
||||
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
|
||||
# This short option group only awaits its argument!
|
||||
# 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
|
||||
# An exception is made for warnings, since it's idiomatic to stick them to the
|
||||
# `-W`, and it doesn't break anything.
|
||||
COMPREPLY+=( "$cur_word" )
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$state" in
|
||||
unk) # Return with no replies: no idea what to complete!
|
||||
;;
|
||||
normal) # Acts like a glob...
|
||||
state="glob-*.o *.obj"
|
||||
;&
|
||||
glob-*)
|
||||
while read -r word; do
|
||||
COMPREPLY+=("${cur_word:0:$optlen}$word")
|
||||
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
|
||||
# Also complete directories
|
||||
;&
|
||||
dir)
|
||||
while read -r word; do
|
||||
COMPREPLY+=("${cur_word:0:$optlen}$word")
|
||||
done < <(compgen -A directory -- "${cur_word:$optlen}")
|
||||
compopt -o filenames
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
complete -F _rgblink_completions rgblink
|
||||
@@ -1,85 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# Copyright (c) 2021 Rangi
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
declare -A FILES
|
||||
while read -r -d '' file; do
|
||||
FILES["$file"]="true"
|
||||
done < <(git diff --name-only -z "$1" HEAD)
|
||||
|
||||
edited () {
|
||||
${FILES["$1"]:-"false"}
|
||||
}
|
||||
|
||||
dependency () {
|
||||
if edited "$1" && ! edited "$2"; then
|
||||
echo "'$1' was modified, but not '$2'! $3" | xargs
|
||||
fi
|
||||
}
|
||||
|
||||
# Pull requests that edit the first file without the second may be correct,
|
||||
# but are suspicious enough to require review.
|
||||
|
||||
dependency include/linkdefs.h man/rgbds.5 \
|
||||
"Was the object file format changed?"
|
||||
|
||||
dependency src/asm/parser.y man/rgbasm.5 \
|
||||
"Was the rgbasm grammar changed?"
|
||||
|
||||
dependency include/asm/warning.h man/rgbasm.1 \
|
||||
"Were the rgbasm warnings changed?"
|
||||
|
||||
dependency src/asm/object.c include/linkdefs.h \
|
||||
"Should the object file revision be bumped?"
|
||||
dependency src/link/object.c include/linkdefs.h \
|
||||
"Should the object file revision be bumped?"
|
||||
|
||||
dependency Makefile CMakeLists.txt \
|
||||
"Did the build process change?"
|
||||
dependency Makefile src/CMakeLists.txt \
|
||||
"Did the build process change?"
|
||||
|
||||
dependency src/asm/main.c man/rgbasm.1 \
|
||||
"Did the rgbasm CLI change?"
|
||||
dependency src/asm/main.c contrib/zsh_compl/_rgbasm \
|
||||
"Did the rgbasm CLI change?"
|
||||
dependency src/asm/main.c contrib/bash_compl/_rgbasm.bash \
|
||||
"Did the rgbasm CLI change?"
|
||||
dependency src/link/main.c man/rgblink.1 \
|
||||
"Did the rgblink CLI change?"
|
||||
dependency src/link/main.c contrib/zsh_compl/_rgblink \
|
||||
"Did the rgblink CLI change?"
|
||||
dependency src/link/main.c contrib/bash_compl/_rgblink.bash \
|
||||
"Did the rgblink CLI change?"
|
||||
dependency src/fix/main.c man/rgbfix.1 \
|
||||
"Did the rgbfix CLI change?"
|
||||
dependency src/fix/main.c contrib/zsh_compl/_rgbfix \
|
||||
"Did the rgbfix CLI change?"
|
||||
dependency src/fix/main.c contrib/bash_compl/_rgbfix.bash \
|
||||
"Did the rgbfix CLI change?"
|
||||
dependency src/gfx/main.cpp man/rgbgfx.1 \
|
||||
"Did the rgbgfx CLI change?"
|
||||
dependency src/gfx/main.cpp contrib/zsh_compl/_rgbgfx \
|
||||
"Did the rgbgfx CLI change?"
|
||||
dependency src/gfx/main.cpp contrib/bash_compl/_rgbgfx.bash \
|
||||
"Did the rgbgfx CLI change?"
|
||||
@@ -23,51 +23,51 @@
|
||||
# SOFTWARE.
|
||||
|
||||
STATE=0
|
||||
diff <(xxd "$1") <(xxd "$2") | while read -r LINE; do
|
||||
if [[ $STATE -eq 0 ]]; then
|
||||
diff <(xxd $1) <(xxd $2) | while read -r LINE; do
|
||||
if [ $STATE -eq 0 ]; then
|
||||
# Discard first line (line info)
|
||||
STATE=1
|
||||
elif [[ "$LINE" = '---' ]]; then
|
||||
elif [ "$LINE" = '---' ]; then
|
||||
# Separator between files switches states
|
||||
echo "$LINE"
|
||||
echo $LINE
|
||||
STATE=3
|
||||
elif grep -Eq '^[0-9]+(,[0-9]+)?[cd][0-9]+(,[0-9]+)?' <<< "$LINE"; then
|
||||
# Line info resets the whole thing
|
||||
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
|
||||
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
||||
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
|
||||
if [[ $STATE -eq 1 ]]; then
|
||||
if [ $STATE -eq 1 ]; then
|
||||
STATE=2
|
||||
SYMFILE=${1%.*}.sym
|
||||
else
|
||||
STATE=4
|
||||
SYMFILE=${2%.*}.sym
|
||||
fi
|
||||
EXTRA=$(if [[ -f "$SYMFILE" ]]; then
|
||||
EXTRA=$(if [ -f "$SYMFILE" ]; then
|
||||
# Read the sym file for such a symbol
|
||||
# Ignore comment lines, only pick matching bank
|
||||
# (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 |
|
||||
tr -d "\r" |
|
||||
while read -r SYMADDR SYM; do
|
||||
SYMADDR=$((0x${SYMADDR#*:}))
|
||||
if [[ $SYMADDR -le $ADDR ]]; then
|
||||
printf " (%s+%#x)\n" "$SYM" $((ADDR - SYMADDR))
|
||||
if [ $SYMADDR -le $ADDR ]; then
|
||||
printf " (%s+%#x)\n" "$SYM" $(($ADDR - $SYMADDR))
|
||||
fi
|
||||
# TODO: assumes sorted sym files
|
||||
done | tail -n 1
|
||||
fi)
|
||||
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
||||
printf "%02x:%04x %s\n" $BANK $ADDR $EXTRA
|
||||
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 ':')
|
||||
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#*: }"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -9,26 +9,18 @@ _rgbasm_warnings() {
|
||||
'everything:Enable literally everything'
|
||||
|
||||
'assert:Warn when WARN-type asserts fail'
|
||||
'backwards-for:Warn when start and stop are backwards relative to step'
|
||||
'builtin-args:Report incorrect args to built-in funcs'
|
||||
'charmap-redef:Warn when redefining a charmap mapping'
|
||||
'div:Warn when dividing the smallest int by -1'
|
||||
'empty-data-directive:Warn on arg-less d[bwl] in ROM'
|
||||
'empty-macro-arg:Warn on empty macro arg'
|
||||
'empty-strrpl:Warn on calling STRRPL with empty pattern'
|
||||
'empty-entry:Warn on empty entries in db, dw, dl args'
|
||||
'large-constant:Warn on constants too large for a signed 32-bit int'
|
||||
'long-string:Warn on strings too long'
|
||||
'macro-shift:Warn when shifting macro args part their limits'
|
||||
'nested-comment:Warn on "/*" inside block comments'
|
||||
'numeric-string:Warn when a multi-character string is treated as a number'
|
||||
'obsolete:Warn when using deprecated features'
|
||||
'shift:Warn when shifting negative values'
|
||||
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
|
||||
'truncation:Warn when implicit truncation loses bits'
|
||||
'truncation:Warn when implicit truncations lose bits'
|
||||
'user:Warn when executing the WARN built-in'
|
||||
)
|
||||
# TODO: handle `no-` and `error=` somehow?
|
||||
# TODO: handle `=0|1|2` levels for `numeric-string` and `truncation`?
|
||||
_describe warning warnings
|
||||
}
|
||||
|
||||
@@ -43,19 +35,15 @@ local args=(
|
||||
-w'[Disable all warnings]'
|
||||
|
||||
'(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:'
|
||||
'*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
|
||||
'(-D --define)'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
|
||||
'(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:'
|
||||
'(-i --include)'{-i,--include}'+[Add an include directory]:include path:_files -/'
|
||||
'(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}'"
|
||||
-MG'[Assume missing files should be generated]'
|
||||
-MP'[Add phony targets to all deps]'
|
||||
'*'-MT"+[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}'"
|
||||
'(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}"
|
||||
'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
|
||||
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
|
||||
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
|
||||
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'
|
||||
|
||||
":assembly sources:_files -g '*.asm'"
|
||||
'*'":assembly sources:_files -g '*.asm'"
|
||||
)
|
||||
_arguments -s -S : $args
|
||||
|
||||
@@ -1,47 +1,12 @@
|
||||
#compdef rgbfix
|
||||
|
||||
_mbc_names() {
|
||||
local mbc_names=(
|
||||
'ROM:$00'
|
||||
'MBC1:$01'
|
||||
'MBC1+RAM:$02'
|
||||
'MBC1+RAM+BATTERY:$03'
|
||||
'MBC2:$05'
|
||||
'MBC2+BATTERY:$06'
|
||||
'ROM+RAM:$08'
|
||||
'ROM+RAM+BATTERY:$09'
|
||||
'MMM01:$0B'
|
||||
'MMM01+RAM:$0C'
|
||||
'MMM01+RAM+BATTERY:$0D'
|
||||
'MBC3+TIMER+BATTERY:$0F'
|
||||
'MBC3+TIMER+RAM+BATTERY:$10'
|
||||
'MBC3:$11'
|
||||
'MBC3+RAM:$12'
|
||||
'MBC3+RAM+BATTERY:$13'
|
||||
'MBC5:$19'
|
||||
'MBC5+RAM:$1A'
|
||||
'MBC5+RAM+BATTERY:$1B'
|
||||
'MBC5+RUMBLE:$1C'
|
||||
'MBC5+RUMBLE+RAM:$1D'
|
||||
'MBC5+RUMBLE+RAM+BATTERY:$1E'
|
||||
'MBC6:$20'
|
||||
'MBC7+SENSOR+RUMBLE+RAM+BATTERY:$22'
|
||||
'POCKET_CAMERA:$FC'
|
||||
'BANDAI_TAMA5:$FD'
|
||||
'HUC3:$FE'
|
||||
'HUC1+RAM+BATTERY:$FF'
|
||||
)
|
||||
_describe "MBC name" mbc_names
|
||||
}
|
||||
|
||||
local args=(
|
||||
# Arguments are listed here in the same order as in the manual, except for the version
|
||||
'(- : * options)'{-V,--version}'[Print version number]'
|
||||
|
||||
'(-C --color-only -c --color-compatible)'{-C,--color-only}'[Mark ROM as GBC-only]'
|
||||
'(-C --color-only -c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'
|
||||
'(-C --color-only)'{-C,--color-only}'[Mark ROM as GBC-only]'
|
||||
'(-c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'
|
||||
'(-j --non-japanese)'{-j,--non-japanese}'[Set the non-Japanese region flag]'
|
||||
'(-O --overwrite)'{-O,--overwrite}'[Allow overwriting non-zero bytes]'
|
||||
'(-s --sgb-compatible)'{-s,--sgb-compatible}'[Set the SGB flag]'
|
||||
'(-f --fix-spec -v --validate)'{-v,--validate}'[Shorthand for -f lhg]'
|
||||
|
||||
@@ -49,7 +14,7 @@ local args=(
|
||||
'(-i --game-id)'{-i,--game-id}'+[Set game ID string]:4-char game ID:'
|
||||
'(-k --new-licensee)'{-k,--new-licensee}'+[Set new licensee string]:2-char licensee ID:'
|
||||
'(-l --old-licensee)'{-l,--old-licensee}'+[Set old licensee ID]:licensee number:'
|
||||
'(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names"
|
||||
'(-m --mbc-type)'{-m,--mbc-type}'+[Set MBC flags]:mbc flags byte:'
|
||||
'(-n --rom-version)'{-n,--rom-version}'+[Set ROM version]:rom version byte:'
|
||||
'(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'
|
||||
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'
|
||||
|
||||
@@ -15,28 +15,23 @@ local args=(
|
||||
|
||||
'(-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]'
|
||||
'(-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]'
|
||||
'(-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]'
|
||||
'(-u --unique-tiles)'{-u,--unique-tiles}'[Eliminate redundant tiles]'
|
||||
{-v,--verbose}'[Enable verbose output]'
|
||||
'(-h --horizontal -Z --columns)'{-Z,--columns}'[Read the image in column-major order]'
|
||||
'(-v --verbose)'{-v,--verbose}'[Enable verbose output]'
|
||||
|
||||
'(-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'
|
||||
'(-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'
|
||||
'(-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'
|
||||
'(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:'
|
||||
|
||||
":input png file:_files -g '*.png'"
|
||||
'*'":input png files:_files -g '*.png'"
|
||||
)
|
||||
_arguments -s -S : $args
|
||||
|
||||
@@ -8,7 +8,6 @@ local args=(
|
||||
'(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]'
|
||||
'(-v --verbose)'{-v,--verbose}'[Enable verbose output]'
|
||||
'(-w --wramx)'{-w,--wramx}'[Disable WRAM banking]'
|
||||
'(-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'"
|
||||
'(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'"
|
||||
@@ -16,7 +15,6 @@ local args=(
|
||||
'(-O --overlay)'{-O,--overlay}'+[Overlay sections over on top of bin file]:base overlay:_files'
|
||||
'(-o --output)'{-o,--output}"+[Write ROM image to this file]:rom file:_files -g '*.{gb,sgb,gbc}'"
|
||||
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
|
||||
'(-S --scramble)'{-s,--scramble}'+[Activate scrambling]:scramble spec'
|
||||
'(-s --smart)'{-s,--smart}'+[!BROKEN! Perform smart linking from this symbol]:symbol name:'
|
||||
|
||||
'*'":object files:_files -g '*.o'"
|
||||
|
||||
@@ -11,13 +11,12 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct Charmap *charmap_New(char const *name, char const *baseName);
|
||||
struct Charmap *charmap_New(const char *name, const char *baseName);
|
||||
void charmap_Delete(struct Charmap *charmap);
|
||||
void charmap_Set(char const *name);
|
||||
void charmap_Set(const char *name);
|
||||
void charmap_Push(void);
|
||||
void charmap_Pop(void);
|
||||
void charmap_Add(char *mapping, uint8_t value);
|
||||
size_t charmap_Convert(char const *input, uint8_t *output);
|
||||
size_t charmap_ConvertNext(char const **input, uint8_t **output);
|
||||
|
||||
#endif /* RGBDS_ASM_CHARMAP_H */
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t fix_Callback_PI(void);
|
||||
void fix_Print(int32_t i);
|
||||
int32_t fix_Sin(int32_t i);
|
||||
int32_t fix_Cos(int32_t i);
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
#ifndef RGBDS_FORMAT_SPEC_H
|
||||
#define RGBDS_FORMAT_SPEC_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum FormatState {
|
||||
FORMAT_SIGN, // expects '+' or ' ' (optional)
|
||||
@@ -28,9 +28,9 @@ struct FormatSpec {
|
||||
bool prefix;
|
||||
bool alignLeft;
|
||||
bool padZero;
|
||||
size_t width;
|
||||
uint8_t width;
|
||||
bool hasFrac;
|
||||
size_t fracWidth;
|
||||
uint8_t fracWidth;
|
||||
int type;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "asm/lexer.h"
|
||||
|
||||
#include "types.h"
|
||||
|
||||
struct FileStackNode {
|
||||
struct FileStackNode *parent; /* Pointer to parent node, for error reporting */
|
||||
@@ -48,7 +49,7 @@ struct FileStackNamedNode { /* NODE_FILE, NODE_MACRO */
|
||||
char name[]; /* File name for files, file::macro name for macros */
|
||||
};
|
||||
|
||||
extern size_t maxRecursionDepth;
|
||||
extern size_t nMaxRecursionDepth;
|
||||
|
||||
struct MacroArgs;
|
||||
|
||||
@@ -71,13 +72,12 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size);
|
||||
bool yywrap(void);
|
||||
void fstk_RunInclude(char const *path);
|
||||
void fstk_RunMacro(char const *macroName, struct MacroArgs *args);
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size);
|
||||
void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char *body, size_t size);
|
||||
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
||||
int32_t reptLineNo, char *body, size_t size);
|
||||
void fstk_StopRept(void);
|
||||
bool fstk_Break(void);
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth);
|
||||
void fstk_Init(char const *mainPath, size_t maxDepth);
|
||||
void fstk_Init(char const *mainPath, size_t maxRecursionDepth);
|
||||
|
||||
#endif /* RGBDS_ASM_FSTACK_H */
|
||||
|
||||
@@ -53,7 +53,7 @@ static inline void lexer_SetGfxDigits(char const digits[4])
|
||||
* `path` is referenced, but not held onto..!
|
||||
*/
|
||||
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 *buf, size_t size, uint32_t lineNo);
|
||||
void lexer_RestartRept(uint32_t lineNo);
|
||||
void lexer_DeleteState(struct LexerState *state);
|
||||
void lexer_Init(void);
|
||||
@@ -83,14 +83,13 @@ struct CaptureBody {
|
||||
size_t size;
|
||||
};
|
||||
|
||||
void lexer_CheckRecursionDepth(void);
|
||||
char const *lexer_GetFileName(void);
|
||||
uint32_t lexer_GetLineNo(void);
|
||||
uint32_t lexer_GetColNo(void);
|
||||
void lexer_DumpStringExpansions(void);
|
||||
int yylex(void);
|
||||
bool lexer_CaptureRept(struct CaptureBody *capture);
|
||||
bool lexer_CaptureMacroBody(struct CaptureBody *capture);
|
||||
void lexer_CaptureRept(struct CaptureBody *capture);
|
||||
void lexer_CaptureMacroBody(struct CaptureBody *capture);
|
||||
|
||||
#define INITIAL_DS_ARG_SIZE 2
|
||||
struct DsArgList {
|
||||
|
||||
@@ -25,7 +25,7 @@ void macro_AppendArg(struct MacroArgs **args, char *s);
|
||||
void macro_UseNewArgs(struct MacroArgs *args);
|
||||
void macro_FreeArgs(struct MacroArgs *args);
|
||||
char const *macro_GetArg(uint32_t i);
|
||||
char const *macro_GetAllArgs(void);
|
||||
char *macro_GetAllArgs(void);
|
||||
|
||||
uint32_t macro_GetUniqueID(void);
|
||||
char const *macro_GetUniqueIDStr(void);
|
||||
|
||||
@@ -16,16 +16,22 @@
|
||||
#include "helpers.h"
|
||||
|
||||
extern bool haltnop;
|
||||
extern bool warnOnHaltNop;
|
||||
extern bool optimizeLoads;
|
||||
extern bool warnOnLdOpt;
|
||||
extern bool optimizeloads;
|
||||
extern bool verbose;
|
||||
extern bool warnings; /* True to enable warnings, false to disable them. */
|
||||
|
||||
extern FILE *dependfile;
|
||||
extern char *targetFileName;
|
||||
extern bool generatedMissingIncludes;
|
||||
extern bool failedOnMissingInclude;
|
||||
extern bool generatePhonyDeps;
|
||||
extern char *tzTargetFileName;
|
||||
extern bool oGeneratedMissingIncludes;
|
||||
extern bool oFailedOnMissingInclude;
|
||||
extern bool oGeneratePhonyDeps;
|
||||
|
||||
/* TODO: are these really needed? */
|
||||
#define YY_FATAL_ERROR fatalerror
|
||||
|
||||
#ifdef YYLMAX
|
||||
#undef YYLMAX
|
||||
#endif
|
||||
#define YYLMAX 65536
|
||||
|
||||
#endif /* RGBDS_MAIN_H */
|
||||
|
||||
@@ -9,17 +9,14 @@
|
||||
#ifndef RGBDS_OPT_H
|
||||
#define RGBDS_OPT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void opt_B(char const chars[2]);
|
||||
void opt_G(char const chars[4]);
|
||||
void opt_B(char chars[2]);
|
||||
void opt_G(char chars[4]);
|
||||
void opt_P(uint8_t fill);
|
||||
void opt_L(bool optimize);
|
||||
void opt_W(char const *flag);
|
||||
void opt_Parse(char const *option);
|
||||
|
||||
void opt_Push(void);
|
||||
void opt_Pop(void);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
struct Expression;
|
||||
struct FileStackNode;
|
||||
|
||||
extern char *objectName;
|
||||
extern struct Section *sectionList;
|
||||
extern char *tzObjectname;
|
||||
extern struct Section *pSectionList, *pCurrentSection;
|
||||
|
||||
void out_RegisterNode(struct FileStackNode *node);
|
||||
void out_ReplaceNode(struct FileStackNode *node);
|
||||
|
||||
@@ -17,20 +17,20 @@
|
||||
#define MAXRPNLEN 1048576
|
||||
|
||||
struct Expression {
|
||||
int32_t val; // If the expression's value is known, it's here
|
||||
char *reason; // Why the expression is not known, if it isn't
|
||||
bool isKnown; // Whether the expression's value is known
|
||||
bool isSymbol; // Whether the expression represents a symbol
|
||||
uint8_t *rpn; // Array of bytes serializing the RPN expression
|
||||
uint32_t rpnCapacity; // Size of the `rpn` buffer
|
||||
uint32_t rpnLength; // Used size of the `rpn` buffer
|
||||
uint32_t rpnPatchSize; // Size the expression will take in the object file
|
||||
int32_t nVal; // If the expression's value is known, it's here
|
||||
char *reason; // Why the expression is not known, if it isn't
|
||||
bool isKnown; // Whether the expression's value is known
|
||||
bool isSymbol; // Whether the expression represents a symbol
|
||||
uint8_t *tRPN; // Array of bytes serializing the RPN expression
|
||||
uint32_t nRPNCapacity; // Size of the `tRPN` buffer
|
||||
uint32_t nRPNLength; // Used size of the `tRPN` buffer
|
||||
uint32_t nRPNPatchSize; // Size the expression will take in the obj file
|
||||
};
|
||||
|
||||
/*
|
||||
* Determines if an expression is known at assembly time
|
||||
*/
|
||||
static inline bool rpn_isKnown(struct Expression const *expr)
|
||||
static inline bool rpn_isKnown(const struct Expression *expr)
|
||||
{
|
||||
return expr->isKnown;
|
||||
}
|
||||
@@ -43,11 +43,11 @@ static inline bool rpn_isSymbol(const struct Expression *expr)
|
||||
return expr->isSymbol;
|
||||
}
|
||||
|
||||
void rpn_Symbol(struct Expression *expr, char const *symName);
|
||||
void rpn_Symbol(struct Expression *expr, char const *tzSym);
|
||||
void rpn_Number(struct Expression *expr, uint32_t i);
|
||||
void rpn_LOGNOT(struct Expression *expr, const struct Expression *src);
|
||||
struct Symbol const *rpn_SymbolOf(struct Expression const *expr);
|
||||
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *symName);
|
||||
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym);
|
||||
void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
||||
const struct Expression *src1,
|
||||
const struct Expression *src2);
|
||||
@@ -56,15 +56,11 @@ void rpn_LOW(struct Expression *expr, const struct Expression *src);
|
||||
void rpn_ISCONST(struct Expression *expr, const struct Expression *src);
|
||||
void rpn_UNNEG(struct Expression *expr, const struct Expression *src);
|
||||
void rpn_UNNOT(struct Expression *expr, const struct Expression *src);
|
||||
void rpn_BankSymbol(struct Expression *expr, char const *symName);
|
||||
void rpn_BankSection(struct Expression *expr, char const *sectionName);
|
||||
void rpn_BankSymbol(struct Expression *expr, char const *tzSym);
|
||||
void rpn_BankSection(struct Expression *expr, char const *tzSectionName);
|
||||
void rpn_BankSelf(struct Expression *expr);
|
||||
void rpn_SizeOfSection(struct Expression *expr, char const *sectionName);
|
||||
void rpn_StartOfSection(struct Expression *expr, char const *sectionName);
|
||||
void rpn_Free(struct Expression *expr);
|
||||
void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src);
|
||||
void rpn_CheckRST(struct Expression *expr, const struct Expression *src);
|
||||
void rpn_CheckNBit(struct Expression const *expr, uint8_t n);
|
||||
int32_t rpn_GetConstVal(struct Expression const *expr);
|
||||
|
||||
#endif /* RGBDS_ASM_RPN_H */
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "linkdefs.h"
|
||||
#include "platform.h" // NONNULL
|
||||
|
||||
extern uint8_t fillByte;
|
||||
|
||||
@@ -28,7 +27,7 @@ struct Section {
|
||||
uint32_t size;
|
||||
uint32_t org;
|
||||
uint32_t bank;
|
||||
uint8_t align; // Exactly as specified in `ALIGN[]`
|
||||
uint8_t align;
|
||||
uint16_t alignOfs;
|
||||
struct Section *next;
|
||||
struct Patch *patches;
|
||||
@@ -41,14 +40,14 @@ struct SectionSpec {
|
||||
uint16_t alignOfs;
|
||||
};
|
||||
|
||||
extern struct Section *currentSection;
|
||||
|
||||
struct Section *sect_FindSectionByName(char const *name);
|
||||
void sect_NewSection(char const *name, uint32_t secttype, uint32_t org,
|
||||
struct SectionSpec const *attributes, enum SectionModifier mod);
|
||||
void sect_SetLoadSection(char const *name, uint32_t secttype, uint32_t org,
|
||||
struct SectionSpec const *attributes, enum SectionModifier mod);
|
||||
void sect_EndLoadSection(void);
|
||||
struct Section *out_FindSectionByName(const char *name);
|
||||
void out_NewSection(char const *name, uint32_t secttype, uint32_t org,
|
||||
struct SectionSpec const *attributes,
|
||||
enum SectionModifier mod);
|
||||
void out_SetLoadSection(char const *name, uint32_t secttype, uint32_t org,
|
||||
struct SectionSpec const *attributes,
|
||||
enum SectionModifier mod);
|
||||
void out_EndLoadSection(void);
|
||||
|
||||
struct Section *sect_GetSymbolSection(void);
|
||||
uint32_t sect_GetSymbolOffset(void);
|
||||
@@ -60,23 +59,21 @@ void sect_NextUnionMember(void);
|
||||
void sect_EndUnion(void);
|
||||
void sect_CheckUnionClosed(void);
|
||||
|
||||
void sect_AbsByte(uint8_t b);
|
||||
void sect_AbsByteGroup(uint8_t const *s, size_t length);
|
||||
void sect_AbsWordGroup(uint8_t const *s, size_t length);
|
||||
void sect_AbsLongGroup(uint8_t const *s, size_t length);
|
||||
void sect_Skip(uint32_t skip, bool ds);
|
||||
void sect_String(char const *s);
|
||||
void sect_RelByte(struct Expression *expr, uint32_t pcShift);
|
||||
void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size);
|
||||
void sect_RelWord(struct Expression *expr, uint32_t pcShift);
|
||||
void sect_RelLong(struct Expression *expr, uint32_t pcShift);
|
||||
void sect_PCRelByte(struct Expression *expr, uint32_t pcShift);
|
||||
void sect_BinaryFile(char const *s, int32_t startPos);
|
||||
void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length);
|
||||
void out_AbsByte(uint8_t b);
|
||||
void out_AbsByteGroup(uint8_t const *s, int32_t length);
|
||||
void out_AbsWordGroup(uint8_t const *s, int32_t length);
|
||||
void out_AbsLongGroup(uint8_t const *s, int32_t length);
|
||||
void out_Skip(int32_t skip, bool ds);
|
||||
void out_String(char const *s);
|
||||
void out_RelByte(struct Expression *expr, uint32_t pcShift);
|
||||
void out_RelBytes(uint32_t n, struct Expression *exprs, size_t size);
|
||||
void out_RelWord(struct Expression *expr, uint32_t pcShift);
|
||||
void out_RelLong(struct Expression *expr, uint32_t pcShift);
|
||||
void out_PCRelByte(struct Expression *expr, uint32_t pcShift);
|
||||
void out_BinaryFile(char const *s, int32_t startPos);
|
||||
void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length);
|
||||
|
||||
void sect_PushSection(void);
|
||||
void sect_PopSection(void);
|
||||
|
||||
bool sect_IsSizeKnown(struct Section const NONNULL(name));
|
||||
void out_PushSection(void);
|
||||
void out_PopSection(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,13 +17,15 @@
|
||||
#include "asm/section.h"
|
||||
|
||||
#include "platform.h" // MIN_NB_ELMS
|
||||
#include "types.h"
|
||||
|
||||
#define MAXSYMLEN 255
|
||||
#define HASHSIZE (1 << 16)
|
||||
#define MAXSYMLEN 256
|
||||
|
||||
enum SymbolType {
|
||||
SYM_LABEL,
|
||||
SYM_EQU,
|
||||
SYM_VAR,
|
||||
SYM_SET,
|
||||
SYM_MACRO,
|
||||
SYM_EQUS,
|
||||
SYM_REF // Forward reference to a label
|
||||
@@ -43,13 +45,13 @@ struct Symbol {
|
||||
/* If sym_IsNumeric */
|
||||
int32_t value;
|
||||
int32_t (*numCallback)(void);
|
||||
/* For SYM_MACRO and SYM_EQUS; TODO: have separate fields */
|
||||
/* For SYM_MACRO */
|
||||
struct {
|
||||
size_t macroSize;
|
||||
char *macro;
|
||||
};
|
||||
/* For SYM_EQUS */
|
||||
char const *(*strCallback)(void);
|
||||
/* For SYM_EQUS, TODO: separate "base" fields from SYM_MACRO */
|
||||
char const *(*strCallback)(void); /* For SYM_EQUS */
|
||||
};
|
||||
|
||||
uint32_t ID; /* ID of the symbol in the object file (-1 if none) */
|
||||
@@ -75,12 +77,13 @@ static inline bool sym_IsConstant(struct Symbol const *sym)
|
||||
|
||||
return sect && sect->org != (uint32_t)-1;
|
||||
}
|
||||
return sym->type == SYM_EQU || sym->type == SYM_VAR;
|
||||
return sym->type == SYM_EQU || sym->type == SYM_SET;
|
||||
}
|
||||
|
||||
static inline bool sym_IsNumeric(struct Symbol const *sym)
|
||||
{
|
||||
return sym->type == SYM_LABEL || sym->type == SYM_EQU || sym->type == SYM_VAR;
|
||||
return sym->type == SYM_LABEL || sym->type == SYM_EQU
|
||||
|| sym->type == SYM_SET;
|
||||
}
|
||||
|
||||
static inline bool sym_IsLabel(struct Symbol const *sym)
|
||||
@@ -118,23 +121,22 @@ struct Symbol *sym_AddAnonLabel(void);
|
||||
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg);
|
||||
void sym_Export(char const *symName);
|
||||
struct Symbol *sym_AddEqu(char const *symName, int32_t value);
|
||||
struct Symbol *sym_RedefEqu(char const *symName, int32_t value);
|
||||
struct Symbol *sym_AddVar(char const *symName, int32_t value);
|
||||
struct Symbol *sym_AddSet(char const *symName, int32_t value);
|
||||
uint32_t sym_GetPCValue(void);
|
||||
uint32_t sym_GetConstantSymValue(struct Symbol const *sym);
|
||||
uint32_t sym_GetConstantValue(char const *symName);
|
||||
uint32_t sym_GetConstantValue(char const *s);
|
||||
/*
|
||||
* Find a symbol by exact name, bypassing expansion checks
|
||||
*/
|
||||
struct Symbol *sym_FindExactSymbol(char const *symName);
|
||||
struct Symbol *sym_FindExactSymbol(char const *name);
|
||||
/*
|
||||
* 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 *name);
|
||||
/*
|
||||
* Find a symbol, possibly scoped, by name
|
||||
*/
|
||||
struct Symbol *sym_FindScopedSymbol(char const *symName);
|
||||
struct Symbol *sym_FindScopedSymbol(char const *name);
|
||||
struct Symbol const *sym_GetPC(void);
|
||||
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
|
||||
struct Symbol *sym_Ref(char const *symName);
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
char const *printChar(int c);
|
||||
|
||||
uint32_t calchash(const char *s);
|
||||
char const *print(int c);
|
||||
/*
|
||||
* @return The number of bytes read, or 0 if invalid data was found
|
||||
*/
|
||||
|
||||
@@ -13,65 +13,42 @@
|
||||
|
||||
extern unsigned int nbErrors;
|
||||
|
||||
enum WarningState {
|
||||
WARNING_DEFAULT,
|
||||
WARNING_DISABLED,
|
||||
WARNING_ENABLED,
|
||||
WARNING_ERROR
|
||||
};
|
||||
|
||||
enum WarningID {
|
||||
WARNING_ASSERT, // Assertions
|
||||
WARNING_BACKWARDS_FOR, // `for` loop with backwards range
|
||||
WARNING_BUILTIN_ARG, // Invalid args to builtins
|
||||
WARNING_CHARMAP_REDEF, // Charmap entry re-definition
|
||||
WARNING_DIV, // Division undefined behavior
|
||||
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
|
||||
WARNING_EMPTY_MACRO_ARG, // Empty macro argument
|
||||
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
|
||||
WARNING_LARGE_CONSTANT, // Constants too large
|
||||
WARNING_LONG_STR, // String too long for internal buffers
|
||||
WARNING_MACRO_SHIFT, // Shift past available arguments in macro
|
||||
WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
|
||||
WARNING_OBSOLETE, // Obsolete things
|
||||
WARNING_SHIFT, // Shifting undefined behavior
|
||||
WARNING_SHIFT_AMOUNT, // Strange shift amount
|
||||
WARNING_USER, // User warnings
|
||||
WARNING_ASSERT, /* Assertions */
|
||||
WARNING_BUILTIN_ARG, /* Invalid args to builtins */
|
||||
WARNING_CHARMAP_REDEF, /* Charmap entry re-definition */
|
||||
WARNING_DIV, /* Division undefined behavior */
|
||||
WARNING_EMPTY_DATA_DIRECTIVE, /* `db`, `dw` or `dl` directive without data in ROM */
|
||||
WARNING_EMPTY_MACRO_ARG, /* Empty macro argument */
|
||||
WARNING_EMPTY_STRRPL, /* Empty second argument in `STRRPL` */
|
||||
WARNING_LARGE_CONSTANT, /* Constants too large */
|
||||
WARNING_LONG_STR, /* String too long for internal buffers */
|
||||
WARNING_MACRO_SHIFT, /* Shift past available arguments in macro */
|
||||
WARNING_NESTED_COMMENT, /* Comment-start delimiter in a block comment */
|
||||
WARNING_OBSOLETE, /* Obsolete things */
|
||||
WARNING_SHIFT, /* Shifting undefined behavior */
|
||||
WARNING_SHIFT_AMOUNT, /* Strange shift amount */
|
||||
WARNING_TRUNCATION, /* Implicit truncation loses some bits */
|
||||
WARNING_USER, /* User warnings */
|
||||
|
||||
NB_PLAIN_WARNINGS,
|
||||
NB_WARNINGS,
|
||||
|
||||
// Warnings past this point are "parametric" warnings, only mapping to a single flag
|
||||
#define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
|
||||
// Treating string as number may lose some bits
|
||||
WARNING_NUMERIC_STRING_1 = PARAM_WARNINGS_START,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
// Implicit truncation loses some bits
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
|
||||
NB_PLAIN_AND_PARAM_WARNINGS,
|
||||
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
|
||||
|
||||
// Warnings past this point are "meta" warnings
|
||||
#define META_WARNINGS_START NB_PLAIN_AND_PARAM_WARNINGS
|
||||
WARNING_ALL = META_WARNINGS_START,
|
||||
/* Warnings past this point are "meta" warnings */
|
||||
WARNING_ALL = NB_WARNINGS,
|
||||
WARNING_EXTRA,
|
||||
WARNING_EVERYTHING,
|
||||
|
||||
NB_WARNINGS,
|
||||
#define NB_META_WARNINGS (NB_WARNINGS - META_WARNINGS_START)
|
||||
NB_WARNINGS_ALL
|
||||
#define NB_META_WARNINGS (NB_WARNINGS_ALL - NB_WARNINGS)
|
||||
};
|
||||
|
||||
extern enum WarningState warningStates[NB_PLAIN_AND_PARAM_WARNINGS];
|
||||
extern bool warningsAreErrors;
|
||||
|
||||
void processWarningFlag(char *flag);
|
||||
void processWarningFlag(char const *flag);
|
||||
|
||||
/*
|
||||
* Used to warn the user about problems that don't prevent the generation of
|
||||
* valid code.
|
||||
*/
|
||||
void warning(enum WarningID id, char const *fmt, ...) format_(printf, 2, 3);
|
||||
void warning(enum WarningID id, const char *fmt, ...) format_(printf, 2, 3);
|
||||
|
||||
/*
|
||||
* Used for errors that compromise the whole assembly process by affecting the
|
||||
@@ -80,7 +57,7 @@ void warning(enum WarningID id, char const *fmt, ...) format_(printf, 2, 3);
|
||||
* It is also used when the assembler goes into an invalid state (for example,
|
||||
* when it fails to allocate memory).
|
||||
*/
|
||||
_Noreturn void fatalerror(char const *fmt, ...) format_(printf, 1, 2);
|
||||
_Noreturn void fatalerror(const char *fmt, ...) format_(printf, 1, 2);
|
||||
|
||||
/*
|
||||
* Used for errors that make it impossible to assemble correctly, but don't
|
||||
@@ -88,6 +65,6 @@ _Noreturn void fatalerror(char const *fmt, ...) format_(printf, 1, 2);
|
||||
* get a list of all errors at the end, making it easier to fix all of them at
|
||||
* once.
|
||||
*/
|
||||
void error(char const *fmt, ...) format_(printf, 1, 2);
|
||||
void error(const char *fmt, ...) format_(printf, 1, 2);
|
||||
|
||||
#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
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 1997-2021, RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef RGBDS_ERROR_H
|
||||
#define RGBDS_ERROR_H
|
||||
|
||||
#include "helpers.h"
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void warn(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 errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RGBDS_ERROR_H */
|
||||
44
include/extern/err.h
vendored
Normal file
44
include/extern/err.h
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 1997-2018, RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef EXTERN_ERR_H
|
||||
#define EXTERN_ERR_H
|
||||
|
||||
#ifdef ERR_IN_LIBC
|
||||
|
||||
#include <err.h>
|
||||
|
||||
#else /* ERR_IN_LIBC */
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
#define warn rgbds_warn
|
||||
#define vwarn rgbds_vwarn
|
||||
#define warnx rgbds_warnx
|
||||
#define vwarnx rgbds_vwarnx
|
||||
|
||||
#define err rgbds_err
|
||||
#define verr rgbds_verr
|
||||
#define errx rgbds_errx
|
||||
#define verrx rgbds_verrx
|
||||
|
||||
void warn(const char *fmt, ...) format_(printf, 1, 2);
|
||||
void vwarn(const char *fmt, va_list ap) format_(printf, 1, 0);
|
||||
void warnx(const char *fmt, ...) format_(printf, 1, 2);
|
||||
void vwarnx(const char *fmt, va_list ap) format_(printf, 1, 0);
|
||||
|
||||
_Noreturn void err(int status, const char *fmt, ...) format_(printf, 2, 3);
|
||||
_Noreturn void verr(int status, const char *fmt, va_list ap) format_(printf, 2, 0);
|
||||
_Noreturn void errx(int status, const char *fmt, ...) format_(printf, 2, 3);
|
||||
_Noreturn void verrx(int status, const char *fmt, va_list ap) format_(printf, 2, 0);
|
||||
|
||||
#endif /* ERR_IN_LIBC */
|
||||
|
||||
#endif /* EXTERN_ERR_H */
|
||||
13
include/extern/getopt.h
vendored
13
include/extern/getopt.h
vendored
@@ -26,29 +26,20 @@
|
||||
#ifndef RGBDS_EXTERN_GETOPT_H
|
||||
#define RGBDS_EXTERN_GETOPT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern char *musl_optarg;
|
||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
||||
|
||||
struct option {
|
||||
char const *name;
|
||||
const char *name;
|
||||
int has_arg;
|
||||
int *flag;
|
||||
int val;
|
||||
};
|
||||
|
||||
int musl_getopt_long_only(int argc, char **argv, char const *optstring,
|
||||
const struct option *longopts, int *idx);
|
||||
int musl_getopt_long_only(int, char **, const char *, const struct option *, int *);
|
||||
|
||||
#define no_argument 0
|
||||
#define required_argument 1
|
||||
#define optional_argument 2
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
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 "extern/err.h"
|
||||
|
||||
struct Options {
|
||||
bool debug;
|
||||
bool verbose;
|
||||
bool hardfix;
|
||||
bool fix;
|
||||
bool horizontal;
|
||||
bool mirror;
|
||||
bool unique;
|
||||
bool colorcurve;
|
||||
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;
|
||||
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 {
|
||||
uint8_t reversedWidth = 0; // -r, in pixels
|
||||
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 */
|
||||
@@ -29,9 +29,19 @@ typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
|
||||
* @param map The HashMap to add the element to
|
||||
* @param key The key with which the element will be stored and retrieved
|
||||
* @param element The element to add
|
||||
* @return A pointer to the pointer to the element.
|
||||
* @return True if a collision occurred (for statistics)
|
||||
*/
|
||||
void **hash_AddElement(HashMap map, char const *key, void *element);
|
||||
bool hash_AddElement(HashMap map, char const *key, void *element);
|
||||
|
||||
/**
|
||||
* Replaces an element with an already-present key in a hashmap.
|
||||
* @warning Inserting a NULL will make `hash_GetElement`'s return ambiguous!
|
||||
* @param map The HashMap to replace the element in
|
||||
* @param key The key with which the element will be stored and retrieved
|
||||
* @param element The element to replace
|
||||
* @return True if the element was found and replaced
|
||||
*/
|
||||
bool hash_ReplaceElement(HashMap const map, char const *key, void *element);
|
||||
|
||||
/**
|
||||
* Removes an element from a hashmap.
|
||||
@@ -41,14 +51,6 @@ void **hash_AddElement(HashMap map, char const *key, void *element);
|
||||
*/
|
||||
bool hash_RemoveElement(HashMap map, char const *key);
|
||||
|
||||
/**
|
||||
* Finds an element in a hashmap, and returns a pointer to its value field.
|
||||
* @param map The map to consider the elements of
|
||||
* @param key The key to search an element for
|
||||
* @return A pointer to the pointer to the element, or NULL if not found.
|
||||
*/
|
||||
void **hash_GetNode(HashMap const map, char const *key);
|
||||
|
||||
/**
|
||||
* Finds an element in a hashmap.
|
||||
* @param map The map to consider the elements of
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#ifdef __GNUC__ // GCC or compatible
|
||||
#define format_(archetype, str_index, first_arg) \
|
||||
__attribute__ ((format (archetype, str_index, first_arg)))
|
||||
#define attr_(...) __attribute__ ((__VA_ARGS__))
|
||||
// In release builds, define "unreachable" as such, but trap in debug builds
|
||||
#ifdef NDEBUG
|
||||
#define unreachable_ __builtin_unreachable
|
||||
@@ -28,10 +27,9 @@
|
||||
#else
|
||||
// Unsupported, but no need to throw a fit
|
||||
#define format_(archetype, str_index, first_arg)
|
||||
#define attr_(...)
|
||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||
// Note that executing this is undefined behavior (declared _Noreturn, but does return)
|
||||
static inline _Noreturn void unreachable_(void) {}
|
||||
static inline _Noreturn unreachable_(void) {}
|
||||
#endif
|
||||
|
||||
// Use builtins whenever possible, and shim them otherwise
|
||||
@@ -61,6 +59,7 @@
|
||||
}
|
||||
|
||||
#else
|
||||
// FIXME: these are rarely used, and need testing...
|
||||
#include <limits.h>
|
||||
static inline int ctz(unsigned int x)
|
||||
{
|
||||
@@ -89,8 +88,4 @@
|
||||
#define STR(x) #x
|
||||
#define EXPAND_AND_STR(x) STR(x)
|
||||
|
||||
// Obtaining the size of an array; `arr` must be an expression, not a type!
|
||||
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
|
||||
|
||||
#endif /* HELPERS_H */
|
||||
|
||||
@@ -1,90 +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 */
|
||||
@@ -24,9 +24,6 @@ extern char const *symFileName;
|
||||
extern char const *overlayFileName;
|
||||
extern char const *outputFileName;
|
||||
extern uint8_t padValue;
|
||||
extern uint16_t scrambleROMX;
|
||||
extern uint8_t scrambleWRAMX;
|
||||
extern uint8_t scrambleSRAM;
|
||||
extern bool is32kMode;
|
||||
extern bool beVerbose;
|
||||
extern bool isWRA0Mode;
|
||||
|
||||
@@ -21,7 +21,10 @@ struct Assertion {
|
||||
struct Patch patch;
|
||||
// enum AssertionType type; The `patch`'s field is instead re-used
|
||||
char *message;
|
||||
// This would be redundant with `.section->fileSymbols`... but `section` is sometimes NULL!
|
||||
/*
|
||||
* This would be redundant with `.section->fileSymbols`... but
|
||||
* `section` is sometimes NULL!
|
||||
*/
|
||||
struct Symbol **fileSymbols;
|
||||
|
||||
struct Assertion *next;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
|
||||
#define RGBDS_OBJECT_VERSION_NUMBER 9U
|
||||
#define RGBDS_OBJECT_REV 9U
|
||||
#define RGBDS_OBJECT_REV 7U
|
||||
|
||||
enum AssertionType {
|
||||
ASSERT_WARN,
|
||||
@@ -23,45 +23,42 @@ enum AssertionType {
|
||||
};
|
||||
|
||||
enum RPNCommand {
|
||||
RPN_ADD = 0x00,
|
||||
RPN_SUB = 0x01,
|
||||
RPN_MUL = 0x02,
|
||||
RPN_DIV = 0x03,
|
||||
RPN_MOD = 0x04,
|
||||
RPN_UNSUB = 0x05, // FIXME: should be renamed to "NEG" for consistency
|
||||
RPN_EXP = 0x06,
|
||||
RPN_ADD = 0x00,
|
||||
RPN_SUB = 0x01,
|
||||
RPN_MUL = 0x02,
|
||||
RPN_DIV = 0x03,
|
||||
RPN_MOD = 0x04,
|
||||
RPN_UNSUB = 0x05,
|
||||
RPN_EXP = 0x06,
|
||||
|
||||
RPN_OR = 0x10,
|
||||
RPN_AND = 0x11,
|
||||
RPN_XOR = 0x12,
|
||||
RPN_UNNOT = 0x13, // FIXME: should be renamed to "NOT" for consistency
|
||||
RPN_OR = 0x10,
|
||||
RPN_AND = 0x11,
|
||||
RPN_XOR = 0x12,
|
||||
RPN_UNNOT = 0x13,
|
||||
|
||||
RPN_LOGAND = 0x21,
|
||||
RPN_LOGOR = 0x22,
|
||||
RPN_LOGUNNOT = 0x23, // FIXME: should be renamed to "LOGNOT" for consistency
|
||||
RPN_LOGAND = 0x21,
|
||||
RPN_LOGOR = 0x22,
|
||||
RPN_LOGUNNOT = 0x23,
|
||||
|
||||
RPN_LOGEQ = 0x30,
|
||||
RPN_LOGNE = 0x31,
|
||||
RPN_LOGGT = 0x32,
|
||||
RPN_LOGLT = 0x33,
|
||||
RPN_LOGGE = 0x34,
|
||||
RPN_LOGLE = 0x35,
|
||||
RPN_LOGEQ = 0x30,
|
||||
RPN_LOGNE = 0x31,
|
||||
RPN_LOGGT = 0x32,
|
||||
RPN_LOGLT = 0x33,
|
||||
RPN_LOGGE = 0x34,
|
||||
RPN_LOGLE = 0x35,
|
||||
|
||||
RPN_SHL = 0x40,
|
||||
RPN_SHR = 0x41,
|
||||
RPN_USHR = 0x42,
|
||||
RPN_SHL = 0x40,
|
||||
RPN_SHR = 0x41,
|
||||
|
||||
RPN_BANK_SYM = 0x50,
|
||||
RPN_BANK_SECT = 0x51,
|
||||
RPN_BANK_SELF = 0x52,
|
||||
RPN_SIZEOF_SECT = 0x53,
|
||||
RPN_STARTOF_SECT = 0x54,
|
||||
RPN_BANK_SYM = 0x50,
|
||||
RPN_BANK_SECT = 0x51,
|
||||
RPN_BANK_SELF = 0x52,
|
||||
|
||||
RPN_HRAM = 0x60,
|
||||
RPN_RST = 0x61,
|
||||
RPN_HRAM = 0x60,
|
||||
RPN_RST = 0x61,
|
||||
|
||||
RPN_CONST = 0x80,
|
||||
RPN_SYM = 0x81
|
||||
RPN_CONST = 0x80,
|
||||
RPN_SYM = 0x81
|
||||
};
|
||||
|
||||
enum SectionType {
|
||||
|
||||
@@ -16,6 +16,5 @@ int32_t op_modulo(int32_t dividend, int32_t divisor);
|
||||
int32_t op_exponent(int32_t base, uint32_t power);
|
||||
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_unsigned(int32_t value, int32_t amount);
|
||||
|
||||
#endif /* RGBDS_OP_MATH_H */
|
||||
|
||||
@@ -11,10 +11,9 @@
|
||||
#ifndef RGBDS_PLATFORM_H
|
||||
#define RGBDS_PLATFORM_H
|
||||
|
||||
// MSVC doesn't have str(n)casecmp, use a suitable replacement
|
||||
/* MSVC doesn't have strncasecmp, use a suitable replacement */
|
||||
#ifdef _MSC_VER
|
||||
# include <string.h>
|
||||
# define strcasecmp _stricmp
|
||||
# define strncasecmp _strnicmp
|
||||
#else
|
||||
# include <strings.h>
|
||||
@@ -46,15 +45,11 @@
|
||||
# include <unistd.h>
|
||||
#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
|
||||
# define MIN_NB_ELMS(N)
|
||||
# define ARR_QUALS(...)
|
||||
# define NONNULL(ptr) *ptr
|
||||
#else
|
||||
# define MIN_NB_ELMS(N) static (N)
|
||||
# define ARR_QUALS(...) __VA_ARGS__
|
||||
# define NONNULL(ptr) ptr[static 1]
|
||||
#endif
|
||||
|
||||
// MSVC uses a different name for O_RDWR, and needs an additional _O_BINARY flag
|
||||
|
||||
16
include/types.h
Normal file
16
include/types.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef RGBDS_TYPES_H
|
||||
#define RGBDS_TYPES_H
|
||||
|
||||
#ifndef _MAX_PATH
|
||||
#define _MAX_PATH 512
|
||||
#endif
|
||||
|
||||
#endif /* RGBDS_TYPES_H */
|
||||
@@ -9,19 +9,11 @@
|
||||
#ifndef EXTERN_VERSION_H
|
||||
#define EXTERN_VERSION_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PACKAGE_VERSION_MAJOR 0
|
||||
#define PACKAGE_VERSION_MINOR 6
|
||||
#define PACKAGE_VERSION_MINOR 5
|
||||
#define PACKAGE_VERSION_PATCH 0
|
||||
#define PACKAGE_VERSION_RC 1
|
||||
#define PACKAGE_VERSION_RC 2
|
||||
|
||||
char const *get_package_version_string(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
const char *get_package_version_string(void);
|
||||
|
||||
#endif /* EXTERN_VERSION_H */
|
||||
|
||||
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 to darkest.
|
||||
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 puts a limit on the amount of palettes, but does not fix this file's size.
|
||||
.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 .
|
||||
2
src/.gitignore
vendored
2
src/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
# Generated by CMake
|
||||
/.version.c
|
||||
@@ -6,14 +6,20 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
|
||||
configure_file(version.c _version.c ESCAPE_QUOTES)
|
||||
|
||||
set(common_src
|
||||
"error.c"
|
||||
"extern/err.c"
|
||||
"extern/getopt.c"
|
||||
"_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)
|
||||
set(BISON_FLAGS "-Wall")
|
||||
# Set sompe optimization flags on versions that support them
|
||||
@@ -62,16 +68,9 @@ set(rgbfix_src
|
||||
)
|
||||
|
||||
set(rgbgfx_src
|
||||
"gfx/main.cpp"
|
||||
"gfx/pal_packing.cpp"
|
||||
"gfx/pal_sorting.cpp"
|
||||
"gfx/pal_spec.cpp"
|
||||
"gfx/process.cpp"
|
||||
"gfx/proto_palette.cpp"
|
||||
"gfx/reverse.cpp"
|
||||
"gfx/rgba.cpp"
|
||||
"extern/getopt.c"
|
||||
"error.c"
|
||||
"gfx/gb.c"
|
||||
"gfx/main.c"
|
||||
"gfx/makepng.c"
|
||||
)
|
||||
|
||||
set(rgblink_src
|
||||
@@ -96,6 +95,22 @@ foreach(PROG "asm" "fix" "gfx" "link")
|
||||
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin)
|
||||
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
|
||||
target_include_directories(rgbgfx PRIVATE ${LIBPNG_INCLUDE_DIRS})
|
||||
target_link_directories(rgbgfx PRIVATE ${LIBPNG_LIBRARY_DIRS})
|
||||
|
||||
@@ -44,41 +44,38 @@ struct Charmap {
|
||||
|
||||
static HashMap charmaps;
|
||||
|
||||
/*
|
||||
* 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 Charmap **charmap;
|
||||
struct Charmap *charmap;
|
||||
struct CharmapStackEntry *next;
|
||||
};
|
||||
|
||||
struct CharmapStackEntry *charmapStack;
|
||||
|
||||
static struct Charmap *charmap_Get(char const *name)
|
||||
static inline struct Charmap *charmap_Get(const char *name)
|
||||
{
|
||||
return hash_GetElement(charmaps, name);
|
||||
}
|
||||
|
||||
static void resizeCharmap(struct Charmap **map, size_t capacity)
|
||||
static inline struct Charmap *resizeCharmap(struct Charmap *map, size_t capacity)
|
||||
{
|
||||
*map = realloc(*map, sizeof(**map) + sizeof(*(*map)->nodes) * capacity);
|
||||
struct Charmap *new = realloc(map, sizeof(*map) + sizeof(*map->nodes) * capacity);
|
||||
|
||||
if (!*map)
|
||||
if (!new)
|
||||
fatalerror("Failed to %s charmap: %s\n",
|
||||
*map ? "create" : "resize", strerror(errno));
|
||||
(**map).capacity = capacity;
|
||||
map ? "create" : "resize", strerror(errno));
|
||||
new->capacity = capacity;
|
||||
return new;
|
||||
}
|
||||
|
||||
static void initNode(struct Charnode *node)
|
||||
static inline void initNode(struct Charnode *node)
|
||||
{
|
||||
node->isTerminal = false;
|
||||
memset(node->next, 0, sizeof(node->next));
|
||||
}
|
||||
|
||||
struct Charmap *charmap_New(char const *name, char const *baseName)
|
||||
struct Charmap *charmap_New(const char *name, const char *baseName)
|
||||
{
|
||||
struct Charmap *base = NULL;
|
||||
|
||||
@@ -98,18 +95,19 @@ struct Charmap *charmap_New(char const *name, char const *baseName)
|
||||
|
||||
/* Init the new charmap's fields */
|
||||
if (base) {
|
||||
resizeCharmap(&charmap, base->capacity);
|
||||
charmap = resizeCharmap(NULL, base->capacity);
|
||||
charmap->usedNodes = base->usedNodes;
|
||||
|
||||
memcpy(charmap->nodes, base->nodes, sizeof(base->nodes[0]) * charmap->usedNodes);
|
||||
} else {
|
||||
resizeCharmap(&charmap, INITIAL_CAPACITY);
|
||||
charmap = resizeCharmap(NULL, INITIAL_CAPACITY);
|
||||
charmap->usedNodes = 1;
|
||||
initNode(&charmap->nodes[0]); /* Init the root node */
|
||||
}
|
||||
charmap->name = strdup(name);
|
||||
|
||||
currentCharmap = (struct Charmap **)hash_AddElement(charmaps, charmap->name, charmap);
|
||||
hash_AddElement(charmaps, charmap->name, charmap);
|
||||
currentCharmap = charmap;
|
||||
|
||||
return charmap;
|
||||
}
|
||||
@@ -120,9 +118,9 @@ void charmap_Delete(struct Charmap *charmap)
|
||||
free(charmap);
|
||||
}
|
||||
|
||||
void charmap_Set(char const *name)
|
||||
void charmap_Set(const char *name)
|
||||
{
|
||||
struct Charmap **charmap = (struct Charmap **)hash_GetNode(charmaps, name);
|
||||
struct Charmap *charmap = charmap_Get(name);
|
||||
|
||||
if (charmap == NULL)
|
||||
error("Charmap '%s' doesn't exist\n", name);
|
||||
@@ -160,26 +158,25 @@ void charmap_Pop(void)
|
||||
|
||||
void charmap_Add(char *mapping, uint8_t value)
|
||||
{
|
||||
struct Charmap *charmap = *currentCharmap;
|
||||
struct Charnode *node = &charmap->nodes[0];
|
||||
struct Charnode *node = ¤tCharmap->nodes[0];
|
||||
|
||||
for (uint8_t c; *mapping; mapping++) {
|
||||
c = *mapping - 1;
|
||||
|
||||
if (node->next[c]) {
|
||||
node = &charmap->nodes[node->next[c]];
|
||||
node = ¤tCharmap->nodes[node->next[c]];
|
||||
} else {
|
||||
/* Register next available node */
|
||||
node->next[c] = charmap->usedNodes;
|
||||
node->next[c] = currentCharmap->usedNodes;
|
||||
/* If no more nodes are available, get new ones */
|
||||
if (charmap->usedNodes == charmap->capacity) {
|
||||
charmap->capacity *= 2;
|
||||
resizeCharmap(currentCharmap, charmap->capacity);
|
||||
charmap = *currentCharmap;
|
||||
if (currentCharmap->usedNodes == currentCharmap->capacity) {
|
||||
currentCharmap->capacity *= 2;
|
||||
currentCharmap = resizeCharmap(currentCharmap, currentCharmap->capacity);
|
||||
hash_ReplaceElement(charmaps, currentCharmap->name, currentCharmap);
|
||||
}
|
||||
|
||||
/* Switch to and init new node */
|
||||
node = &charmap->nodes[charmap->usedNodes++];
|
||||
node = ¤tCharmap->nodes[currentCharmap->usedNodes++];
|
||||
initNode(node);
|
||||
}
|
||||
}
|
||||
@@ -192,16 +189,6 @@ void charmap_Add(char *mapping, uint8_t value)
|
||||
}
|
||||
|
||||
size_t charmap_Convert(char const *input, uint8_t *output)
|
||||
{
|
||||
uint8_t *start = output;
|
||||
|
||||
while (charmap_ConvertNext(&input, &output))
|
||||
;
|
||||
|
||||
return output - start;
|
||||
}
|
||||
|
||||
size_t charmap_ConvertNext(char const **input, uint8_t **output)
|
||||
{
|
||||
/*
|
||||
* The goal is to match the longest mapping possible.
|
||||
@@ -209,56 +196,51 @@ size_t charmap_ConvertNext(char const **input, uint8_t **output)
|
||||
* 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 Charnode const *node = &charmap->nodes[0];
|
||||
size_t outputLen = 0;
|
||||
struct Charnode const *node = ¤tCharmap->nodes[0];
|
||||
struct Charnode const *match = NULL;
|
||||
size_t rewindDistance = 0;
|
||||
|
||||
for (;;) {
|
||||
uint8_t c = **input - 1;
|
||||
/* We still want NULs to reach the `else` path, to give a chance to rewind */
|
||||
uint8_t c = *input - 1;
|
||||
|
||||
if (**input && node->next[c]) {
|
||||
// Consume that char
|
||||
(*input)++;
|
||||
if (*input && node->next[c]) {
|
||||
input++; /* Consume that char */
|
||||
rewindDistance++;
|
||||
|
||||
// Advance to next node (index starts at 1)
|
||||
node = &charmap->nodes[node->next[c]];
|
||||
node = ¤tCharmap->nodes[node->next[c]];
|
||||
if (node->isTerminal) {
|
||||
// This node matches, register it
|
||||
match = node;
|
||||
rewindDistance = 0; // If no longer match is found, rewind here
|
||||
rewindDistance = 0; /* Rewind from after the match */
|
||||
}
|
||||
|
||||
} else {
|
||||
// We are at a dead end (either because we reached the end of input, or of
|
||||
// the trie), so rewind up to the last match, and output.
|
||||
*input -= rewindDistance; // This will rewind all the way if no match found
|
||||
input -= rewindDistance; /* Rewind */
|
||||
rewindDistance = 0;
|
||||
node = ¤tCharmap->nodes[0];
|
||||
|
||||
if (match) { // A match was found, use it
|
||||
if (output)
|
||||
*(*output)++ = match->value;
|
||||
if (match) { /* Arrived at a dead end with a match found */
|
||||
*output++ = match->value;
|
||||
outputLen++;
|
||||
match = NULL; /* Reset match for next round */
|
||||
|
||||
return 1;
|
||||
} else if (*input) { /* No match found */
|
||||
size_t codepointLen = readUTF8Char(output, input);
|
||||
|
||||
} else if (**input) { // No match found, but there is some input left
|
||||
// This will write the codepoint's value to `output`, little-endian
|
||||
size_t codepointLen = readUTF8Char(output ? *output : NULL,
|
||||
*input);
|
||||
|
||||
if (codepointLen == 0)
|
||||
if (codepointLen == 0) {
|
||||
error("Input string is not valid UTF-8!\n");
|
||||
|
||||
// OK because UTF-8 has no NUL in multi-byte chars
|
||||
*input += codepointLen;
|
||||
if (output)
|
||||
*output += codepointLen;
|
||||
|
||||
return codepointLen;
|
||||
|
||||
} else { // End of input
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
input += codepointLen; /* OK because UTF-8 has no NUL in multi-byte chars */
|
||||
output += codepointLen;
|
||||
outputLen += codepointLen;
|
||||
}
|
||||
|
||||
if (!*input)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return outputLen;
|
||||
}
|
||||
|
||||
@@ -30,13 +30,23 @@
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Return the _PI symbol value
|
||||
*/
|
||||
int32_t fix_Callback_PI(void)
|
||||
{
|
||||
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)
|
||||
{
|
||||
uint32_t u = i;
|
||||
char const *sign = "";
|
||||
const char *sign = "";
|
||||
|
||||
if (i < 0) {
|
||||
u = -u;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
@@ -149,24 +148,20 @@ void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, cha
|
||||
size_t len = strlen(value);
|
||||
size_t totalLen = fmt->width > len ? fmt->width : len;
|
||||
|
||||
if (totalLen > bufLen - 1) { /* bufLen includes terminator */
|
||||
if (totalLen + 1 > bufLen) /* bufLen includes terminator */
|
||||
error("Formatted string value too long\n");
|
||||
totalLen = bufLen - 1;
|
||||
if (len > totalLen)
|
||||
len = totalLen;
|
||||
}
|
||||
assert(len < bufLen && totalLen < bufLen && len <= totalLen);
|
||||
|
||||
size_t padLen = totalLen - len;
|
||||
size_t padLen = fmt->width > len ? fmt->width - len : 0;
|
||||
|
||||
if (fmt->alignLeft) {
|
||||
memcpy(buf, value, len);
|
||||
for (size_t i = len; i < totalLen; i++)
|
||||
buf[i] = ' ';
|
||||
strncpy(buf, value, len < bufLen ? len : bufLen);
|
||||
for (size_t i = 0; i < totalLen && len + i < bufLen; i++)
|
||||
buf[len + i] = ' ';
|
||||
} else {
|
||||
for (size_t i = 0; i < padLen; i++)
|
||||
for (size_t i = 0; i < padLen && i < bufLen; i++)
|
||||
buf[i] = ' ';
|
||||
memcpy(buf + padLen, value, len);
|
||||
if (bufLen > padLen)
|
||||
strncpy(buf + padLen, value, bufLen - padLen - 1);
|
||||
}
|
||||
|
||||
buf[totalLen] = '\0';
|
||||
@@ -226,15 +221,17 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
||||
/* Special case for fixed-point */
|
||||
|
||||
/* Default fractional width (C's is 6 for "%f"; here 5 is enough) */
|
||||
size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
|
||||
uint8_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
|
||||
|
||||
if (fracWidth > 255) {
|
||||
error("Fractional width %zu too long, limiting to 255\n",
|
||||
fracWidth);
|
||||
fracWidth = 255;
|
||||
if (fracWidth) {
|
||||
char spec[16]; /* Max "%" + 5-char PRIu32 + ".%0255.f" + terminator */
|
||||
|
||||
snprintf(spec, sizeof(spec), "%%" PRIu32 ".%%0%d.f", fracWidth);
|
||||
snprintf(valueBuf, sizeof(valueBuf), spec, value >> 16,
|
||||
(value % 65536) / 65536.0 * pow(10, fracWidth) + 0.5);
|
||||
} else {
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, value >> 16);
|
||||
}
|
||||
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth, value / 65536.0);
|
||||
} else {
|
||||
char const *spec = fmt->type == 'd' ? "%" PRId32
|
||||
: fmt->type == 'u' ? "%" PRIu32
|
||||
@@ -247,49 +244,55 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
|
||||
}
|
||||
|
||||
size_t len = strlen(valueBuf);
|
||||
size_t numLen = !!sign + !!prefix + len;
|
||||
size_t numLen = len;
|
||||
|
||||
if (sign)
|
||||
numLen++;
|
||||
if (prefix)
|
||||
numLen++;
|
||||
|
||||
size_t totalLen = fmt->width > numLen ? fmt->width : numLen;
|
||||
|
||||
if (totalLen > bufLen - 1) { /* bufLen includes terminator */
|
||||
if (totalLen + 1 > bufLen) /* bufLen includes terminator */
|
||||
error("Formatted numeric value too long\n");
|
||||
totalLen = bufLen - 1;
|
||||
if (numLen > totalLen) {
|
||||
len -= numLen - totalLen;
|
||||
numLen = totalLen;
|
||||
}
|
||||
}
|
||||
assert(numLen < bufLen && totalLen < bufLen && numLen <= totalLen && len <= numLen);
|
||||
|
||||
size_t padLen = totalLen - numLen;
|
||||
size_t pos = 0;
|
||||
size_t padLen = fmt->width > numLen ? fmt->width - numLen : 0;
|
||||
|
||||
if (fmt->alignLeft) {
|
||||
if (sign)
|
||||
size_t pos = 0;
|
||||
|
||||
if (sign && pos < bufLen)
|
||||
buf[pos++] = sign;
|
||||
if (prefix)
|
||||
if (prefix && pos < bufLen)
|
||||
buf[pos++] = prefix;
|
||||
memcpy(buf + pos, valueBuf, len);
|
||||
for (size_t i = pos + len; i < totalLen; i++)
|
||||
buf[i] = ' ';
|
||||
|
||||
strcpy(buf + pos, valueBuf);
|
||||
pos += len;
|
||||
|
||||
for (size_t i = 0; i < totalLen && pos + i < bufLen; i++)
|
||||
buf[pos + i] = ' ';
|
||||
} else {
|
||||
size_t pos = 0;
|
||||
|
||||
if (fmt->padZero) {
|
||||
/* sign, then prefix, then zero padding */
|
||||
if (sign)
|
||||
if (sign && pos < bufLen)
|
||||
buf[pos++] = sign;
|
||||
if (prefix)
|
||||
if (prefix && pos < bufLen)
|
||||
buf[pos++] = prefix;
|
||||
for (size_t i = 0; i < padLen; i++)
|
||||
for (size_t i = 0; i < padLen && pos < bufLen; i++)
|
||||
buf[pos++] = '0';
|
||||
} else {
|
||||
/* space padding, then sign, then prefix */
|
||||
for (size_t i = 0; i < padLen; i++)
|
||||
for (size_t i = 0; i < padLen && pos < bufLen; i++)
|
||||
buf[pos++] = ' ';
|
||||
if (sign)
|
||||
if (sign && pos < bufLen)
|
||||
buf[pos++] = sign;
|
||||
if (prefix)
|
||||
if (prefix && pos < bufLen)
|
||||
buf[pos++] = prefix;
|
||||
}
|
||||
memcpy(buf + pos, valueBuf, len);
|
||||
if (bufLen > pos)
|
||||
strcpy(buf + pos, valueBuf);
|
||||
}
|
||||
|
||||
buf[totalLen] = '\0';
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
|
||||
#define MAXINCPATHS 128
|
||||
|
||||
#ifdef LEXER_DEBUG
|
||||
#define dbgPrint(...) fprintf(stderr, "[fstack] " __VA_ARGS__)
|
||||
#else
|
||||
#define dbgPrint(...)
|
||||
#endif
|
||||
|
||||
struct Context {
|
||||
struct Context *parent;
|
||||
struct FileStackNode *fileInfo;
|
||||
@@ -38,7 +44,7 @@ struct Context {
|
||||
static struct Context *contextStack;
|
||||
static size_t contextDepth = 0;
|
||||
#define DEFAULT_MAX_DEPTH 64
|
||||
size_t maxRecursionDepth;
|
||||
size_t nMaxRecursionDepth;
|
||||
|
||||
static unsigned int nbIncPaths = 0;
|
||||
static char const *includePaths[MAXINCPATHS];
|
||||
@@ -137,8 +143,8 @@ void fstk_AddIncludePath(char const *path)
|
||||
static void printDep(char const *path)
|
||||
{
|
||||
if (dependfile) {
|
||||
fprintf(dependfile, "%s: %s\n", targetFileName, path);
|
||||
if (generatePhonyDeps)
|
||||
fprintf(dependfile, "%s: %s\n", tzTargetFileName, path);
|
||||
if (oGeneratePhonyDeps)
|
||||
fprintf(dependfile, "%s:\n", path);
|
||||
}
|
||||
}
|
||||
@@ -185,11 +191,6 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
||||
break;
|
||||
}
|
||||
len = sprintf(*fullPath, "%s%s", incPath, path);
|
||||
if (len < 0) {
|
||||
error("sprintf error during include path search: %s\n",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPathValid(*fullPath)) {
|
||||
@@ -200,18 +201,18 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
||||
}
|
||||
|
||||
errno = ENOENT;
|
||||
if (generatedMissingIncludes)
|
||||
if (oGeneratedMissingIncludes)
|
||||
printDep(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool yywrap(void)
|
||||
{
|
||||
uint32_t ifDepth = lexer_GetIFDepth();
|
||||
uint32_t nIFDepth = lexer_GetIFDepth();
|
||||
|
||||
if (ifDepth != 0)
|
||||
if (nIFDepth != 0)
|
||||
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||
ifDepth, ifDepth == 1 ? "" : "s");
|
||||
nIFDepth, nIFDepth == 1 ? "" : "s");
|
||||
|
||||
if (contextStack->fileInfo->type == NODE_REPT) { /* The context is a REPT block, which may loop */
|
||||
struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo;
|
||||
@@ -235,11 +236,11 @@ bool yywrap(void)
|
||||
/* If this is a FOR, update the symbol value */
|
||||
if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
|
||||
contextStack->forValue += contextStack->forStep;
|
||||
struct Symbol *sym = sym_AddVar(contextStack->forName,
|
||||
struct Symbol *sym = sym_AddSet(contextStack->forName,
|
||||
contextStack->forValue);
|
||||
|
||||
/* This error message will refer to the current iteration */
|
||||
if (sym->type != SYM_VAR)
|
||||
if (sym->type != SYM_SET)
|
||||
fatalerror("Failed to update FOR symbol value\n");
|
||||
}
|
||||
/* Advance to the next iteration */
|
||||
@@ -253,6 +254,7 @@ bool yywrap(void)
|
||||
} else if (!contextStack->parent) {
|
||||
return true;
|
||||
}
|
||||
dbgPrint("Popping context\n");
|
||||
|
||||
struct Context *context = contextStack;
|
||||
|
||||
@@ -262,8 +264,10 @@ bool yywrap(void)
|
||||
|
||||
lexer_DeleteState(context->lexerState);
|
||||
/* Restore args if a macro (not REPT) saved them */
|
||||
if (context->fileInfo->type == NODE_MACRO)
|
||||
if (context->fileInfo->type == NODE_MACRO) {
|
||||
dbgPrint("Restoring macro args %p\n", (void *)contextStack->macroArgs);
|
||||
macro_UseNewArgs(contextStack->macroArgs);
|
||||
}
|
||||
/* Free the file stack node */
|
||||
if (!context->fileInfo->referenced)
|
||||
free(context->fileInfo);
|
||||
@@ -284,9 +288,8 @@ bool yywrap(void)
|
||||
*/
|
||||
static void newContext(struct FileStackNode *fileInfo)
|
||||
{
|
||||
++contextDepth;
|
||||
fstk_NewRecursionDepth(maxRecursionDepth); // Only checks if the max depth was exceeded
|
||||
|
||||
if (++contextDepth >= nMaxRecursionDepth)
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", nMaxRecursionDepth);
|
||||
struct Context *context = malloc(sizeof(*context));
|
||||
|
||||
if (!context)
|
||||
@@ -307,21 +310,24 @@ static void newContext(struct FileStackNode *fileInfo)
|
||||
|
||||
void fstk_RunInclude(char const *path)
|
||||
{
|
||||
dbgPrint("Including path \"%s\"\n", path);
|
||||
|
||||
char *fullPath = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
if (!fstk_FindFile(path, &fullPath, &size)) {
|
||||
free(fullPath);
|
||||
if (generatedMissingIncludes) {
|
||||
if (oGeneratedMissingIncludes) {
|
||||
if (verbose)
|
||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n",
|
||||
path, strerror(errno));
|
||||
failedOnMissingInclude = true;
|
||||
oFailedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Unable to open included file '%s': %s\n", path, strerror(errno));
|
||||
}
|
||||
return;
|
||||
}
|
||||
dbgPrint("Full path: \"%s\"\n", fullPath);
|
||||
|
||||
struct FileStackNamedNode *fileInfo = malloc(sizeof(*fileInfo) + size);
|
||||
|
||||
@@ -345,6 +351,8 @@ void fstk_RunInclude(char const *path)
|
||||
|
||||
void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
||||
{
|
||||
dbgPrint("Running macro \"%s\"\n", macroName);
|
||||
|
||||
struct Symbol *macro = sym_FindExactSymbol(macroName);
|
||||
|
||||
if (!macro) {
|
||||
@@ -404,7 +412,7 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
||||
memcpy(dest, macro->name, macroNameLen + 1);
|
||||
|
||||
newContext((struct FileStackNode *)fileInfo);
|
||||
contextStack->lexerState = lexer_OpenFileView("MACRO", macro->macro, macro->macroSize,
|
||||
contextStack->lexerState = lexer_OpenFileView(macro->macro, macro->macroSize,
|
||||
macro->fileLine);
|
||||
if (!contextStack->lexerState)
|
||||
fatalerror("Failed to set up lexer for macro invocation\n");
|
||||
@@ -438,7 +446,7 @@ static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
|
||||
/* Correct our line number, which currently points to the `ENDR` line */
|
||||
contextStack->fileInfo->lineNo = reptLineNo;
|
||||
|
||||
contextStack->lexerState = lexer_OpenFileView("REPT", body, size, reptLineNo);
|
||||
contextStack->lexerState = lexer_OpenFileView(body, size, reptLineNo);
|
||||
if (!contextStack->lexerState)
|
||||
fatalerror("Failed to set up lexer for REPT block\n");
|
||||
lexer_SetStateAtEOL(contextStack->lexerState);
|
||||
@@ -448,6 +456,8 @@ static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
|
||||
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
|
||||
{
|
||||
dbgPrint("Running REPT(%" PRIu32 ")\n", count);
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
if (!newReptContext(reptLineNo, body, size))
|
||||
@@ -460,9 +470,12 @@ void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
|
||||
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
||||
int32_t reptLineNo, char *body, size_t size)
|
||||
{
|
||||
struct Symbol *sym = sym_AddVar(symName, start);
|
||||
dbgPrint("Running FOR(\"%s\", %" PRId32 ", %" PRId32 ", %" PRId32 ")\n",
|
||||
symName, start, stop, step);
|
||||
|
||||
if (sym->type != SYM_VAR)
|
||||
struct Symbol *sym = sym_AddSet(symName, start);
|
||||
|
||||
if (sym->type != SYM_SET)
|
||||
return;
|
||||
|
||||
uint32_t count = 0;
|
||||
@@ -474,10 +487,6 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
||||
else if (step == 0)
|
||||
error("FOR cannot have a step value of 0\n");
|
||||
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop))
|
||||
warning(WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n",
|
||||
start, stop, step);
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
if (!newReptContext(reptLineNo, body, size))
|
||||
@@ -499,6 +508,8 @@ void fstk_StopRept(void)
|
||||
|
||||
bool fstk_Break(void)
|
||||
{
|
||||
dbgPrint("Breaking out of REPT/FOR\n");
|
||||
|
||||
if (contextStack->fileInfo->type != NODE_REPT) {
|
||||
error("BREAK can only be used inside a REPT/FOR block\n");
|
||||
return false;
|
||||
@@ -508,14 +519,7 @@ bool fstk_Break(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth)
|
||||
{
|
||||
if (contextDepth >= newDepth)
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||
maxRecursionDepth = newDepth;
|
||||
}
|
||||
|
||||
void fstk_Init(char const *mainPath, size_t maxDepth)
|
||||
void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
|
||||
{
|
||||
struct LexerState *state = lexer_OpenFile(mainPath);
|
||||
|
||||
@@ -557,12 +561,12 @@ void fstk_Init(char const *mainPath, size_t maxDepth)
|
||||
* This assumes that the rept node is larger
|
||||
*/
|
||||
#define DEPTH_LIMIT ((SIZE_MAX - sizeof(struct FileStackReptNode)) / sizeof(uint32_t))
|
||||
if (maxDepth > DEPTH_LIMIT) {
|
||||
if (maxRecursionDepth > DEPTH_LIMIT) {
|
||||
error("Recursion depth may not be higher than %zu, defaulting to "
|
||||
EXPAND_AND_STR(DEFAULT_MAX_DEPTH) "\n", DEPTH_LIMIT);
|
||||
maxRecursionDepth = DEFAULT_MAX_DEPTH;
|
||||
nMaxRecursionDepth = DEFAULT_MAX_DEPTH;
|
||||
} else {
|
||||
maxRecursionDepth = maxDepth;
|
||||
nMaxRecursionDepth = maxRecursionDepth;
|
||||
}
|
||||
/* Make sure that the default of 64 is OK, though */
|
||||
assert(DEPTH_LIMIT >= DEFAULT_MAX_DEPTH);
|
||||
|
||||
1494
src/asm/lexer.c
1494
src/asm/lexer.c
File diff suppressed because it is too large
Load Diff
@@ -101,7 +101,7 @@ char const *macro_GetArg(uint32_t i)
|
||||
: macroArgs->args[realIndex];
|
||||
}
|
||||
|
||||
char const *macro_GetAllArgs(void)
|
||||
char *macro_GetAllArgs(void)
|
||||
{
|
||||
if (!macroArgs)
|
||||
return NULL;
|
||||
|
||||
148
src/asm/main.c
148
src/asm/main.c
@@ -30,49 +30,39 @@
|
||||
#include "asm/warning.h"
|
||||
#include "parser.h"
|
||||
|
||||
#include "extern/err.h"
|
||||
#include "extern/getopt.h"
|
||||
|
||||
#include "helpers.h"
|
||||
#include "error.h"
|
||||
#include "version.h"
|
||||
|
||||
#ifdef __clang__
|
||||
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
|
||||
#define __SANITIZE_ADDRESS__
|
||||
#endif /* __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) */
|
||||
#endif /* __clang__ */
|
||||
|
||||
#ifdef __SANITIZE_ADDRESS__
|
||||
// There are known, non-trivial to fix leaks. We would still like to have `make develop'
|
||||
// detect memory corruption, though.
|
||||
char const *__asan_default_options(void) { return "detect_leaks=0"; }
|
||||
#endif
|
||||
|
||||
// Old Bison versions (confirmed for 2.3) do not forward-declare `yyparse` in the generated header
|
||||
// Unfortunately, macOS still ships 2.3, which is from 2008...
|
||||
int yyparse(void);
|
||||
|
||||
#if defined(YYDEBUG) && YYDEBUG
|
||||
extern int yydebug;
|
||||
#endif
|
||||
|
||||
FILE * dependfile;
|
||||
bool generatedMissingIncludes;
|
||||
bool failedOnMissingInclude;
|
||||
bool generatePhonyDeps;
|
||||
char *targetFileName;
|
||||
bool oGeneratedMissingIncludes;
|
||||
bool oFailedOnMissingInclude;
|
||||
bool oGeneratePhonyDeps;
|
||||
char *tzTargetFileName;
|
||||
|
||||
bool haltnop;
|
||||
bool warnOnHaltNop;
|
||||
bool optimizeLoads;
|
||||
bool warnOnLdOpt;
|
||||
bool optimizeloads;
|
||||
bool verbose;
|
||||
bool warnings; /* True to enable warnings, false to disable them. */
|
||||
|
||||
/* Escapes Make-special chars from a string */
|
||||
static char *make_escape(char const *str)
|
||||
static char *make_escape(const char *str)
|
||||
{
|
||||
char * const escaped_str = malloc(strlen(str) * 2 + 1);
|
||||
char *dest = escaped_str;
|
||||
|
||||
if (escaped_str == NULL)
|
||||
err("%s: Failed to allocate memory", __func__);
|
||||
err(1, "%s: Failed to allocate memory", __func__);
|
||||
|
||||
while (*str) {
|
||||
/* All dollars needs to be doubled */
|
||||
@@ -86,7 +76,7 @@ static char *make_escape(char const *str)
|
||||
}
|
||||
|
||||
/* Short options */
|
||||
static const char *optstring = "b:D:Eg:Hhi:LlM:o:p:r:VvW:w";
|
||||
static const char *optstring = "b:D:Eg:hi:LM:o:p:r:VvW:w";
|
||||
|
||||
/* Variables for the long-only options */
|
||||
static int depType; /* Variants of `-M` */
|
||||
@@ -106,11 +96,9 @@ static struct option const longopts[] = {
|
||||
{ "define", required_argument, NULL, 'D' },
|
||||
{ "export-all", no_argument, NULL, 'E' },
|
||||
{ "gfx-chars", required_argument, NULL, 'g' },
|
||||
{ "nop-after-halt", no_argument, NULL, 'H' },
|
||||
{ "halt-without-nop", no_argument, NULL, 'h' },
|
||||
{ "include", required_argument, NULL, 'i' },
|
||||
{ "preserve-ld", no_argument, NULL, 'L' },
|
||||
{ "auto-ldh", no_argument, NULL, 'l' },
|
||||
{ "dependfile", required_argument, NULL, 'M' },
|
||||
{ "MG", no_argument, &depType, 'G' },
|
||||
{ "MP", no_argument, &depType, 'P' },
|
||||
@@ -161,28 +149,30 @@ int main(int argc, char *argv[])
|
||||
|
||||
dependfile = NULL;
|
||||
|
||||
#if defined(YYDEBUG) && YYDEBUG
|
||||
yydebug = 1;
|
||||
#endif
|
||||
|
||||
// Perform some init for below
|
||||
sym_Init(now);
|
||||
|
||||
// Set defaults
|
||||
|
||||
generatePhonyDeps = false;
|
||||
generatedMissingIncludes = false;
|
||||
failedOnMissingInclude = false;
|
||||
targetFileName = NULL;
|
||||
oGeneratePhonyDeps = false;
|
||||
oGeneratedMissingIncludes = false;
|
||||
oFailedOnMissingInclude = false;
|
||||
tzTargetFileName = NULL;
|
||||
|
||||
opt_B("01");
|
||||
opt_G("0123");
|
||||
opt_P(0);
|
||||
optimizeloads = true;
|
||||
haltnop = true;
|
||||
warnOnHaltNop = true;
|
||||
optimizeLoads = true;
|
||||
warnOnLdOpt = true;
|
||||
verbose = false;
|
||||
warnings = true;
|
||||
sym_SetExportAll(false);
|
||||
uint32_t maxDepth = 64;
|
||||
size_t targetFileNameLen = 0;
|
||||
uint32_t maxRecursionDepth = 64;
|
||||
size_t nTargetFileNameLen = 0;
|
||||
|
||||
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
|
||||
switch (ch) {
|
||||
@@ -190,7 +180,7 @@ int main(int argc, char *argv[])
|
||||
if (strlen(musl_optarg) == 2)
|
||||
opt_B(&musl_optarg[1]);
|
||||
else
|
||||
errx("Must specify exactly 2 characters for option 'b'");
|
||||
errx(1, "Must specify exactly 2 characters for option 'b'");
|
||||
break;
|
||||
|
||||
char *equals;
|
||||
@@ -212,17 +202,10 @@ int main(int argc, char *argv[])
|
||||
if (strlen(musl_optarg) == 4)
|
||||
opt_G(&musl_optarg[1]);
|
||||
else
|
||||
errx("Must specify exactly 4 characters for option 'g'");
|
||||
errx(1, "Must specify exactly 4 characters for option 'g'");
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
if (!haltnop)
|
||||
errx("`-H` and `-h` don't make sense together");
|
||||
warnOnHaltNop = false;
|
||||
break;
|
||||
case 'h':
|
||||
if (!warnOnHaltNop)
|
||||
errx("`-H` and `-h` don't make sense together");
|
||||
haltnop = false;
|
||||
break;
|
||||
|
||||
@@ -231,14 +214,7 @@ int main(int argc, char *argv[])
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
if (!warnOnLdOpt)
|
||||
errx("`-L` and `-l` don't make sense together");
|
||||
optimizeLoads = false;
|
||||
break;
|
||||
case 'l':
|
||||
if (!optimizeLoads)
|
||||
errx("`-L` and `-l` don't make sense together");
|
||||
warnOnLdOpt = false;
|
||||
optimizeloads = false;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
@@ -247,7 +223,7 @@ int main(int argc, char *argv[])
|
||||
else
|
||||
dependfile = fopen(musl_optarg, "w");
|
||||
if (dependfile == NULL)
|
||||
err("Could not open dependfile %s", musl_optarg);
|
||||
err(1, "Could not open dependfile %s", musl_optarg);
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
@@ -259,19 +235,19 @@ int main(int argc, char *argv[])
|
||||
fill = strtoul(musl_optarg, &ep, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *ep != '\0')
|
||||
errx("Invalid argument for option 'p'");
|
||||
errx(1, "Invalid argument for option 'p'");
|
||||
|
||||
if (fill > 0xFF)
|
||||
errx("Argument for option 'p' must be between 0 and 0xFF");
|
||||
if (fill < 0 || fill > 0xFF)
|
||||
errx(1, "Argument for option 'p' must be between 0 and 0xFF");
|
||||
|
||||
opt_P(fill);
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
maxDepth = strtoul(musl_optarg, &ep, 0);
|
||||
maxRecursionDepth = strtoul(musl_optarg, &ep, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *ep != '\0')
|
||||
errx("Invalid argument for option 'r'");
|
||||
errx(1, "Invalid argument for option 'r'");
|
||||
break;
|
||||
|
||||
case 'V':
|
||||
@@ -293,30 +269,40 @@ int main(int argc, char *argv[])
|
||||
case 0:
|
||||
switch (depType) {
|
||||
case 'G':
|
||||
generatedMissingIncludes = true;
|
||||
oGeneratedMissingIncludes = true;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
generatePhonyDeps = true;
|
||||
oGeneratePhonyDeps = true;
|
||||
break;
|
||||
|
||||
char *newTarget;
|
||||
case 'Q':
|
||||
case 'T':
|
||||
newTarget = musl_optarg;
|
||||
if (musl_optind == argc)
|
||||
errx(1, "-M%c takes a target file name argument", depType);
|
||||
ep = musl_optarg;
|
||||
if (depType == 'Q')
|
||||
newTarget = make_escape(newTarget);
|
||||
size_t newTargetLen = strlen(newTarget) + 1; // Plus the space
|
||||
ep = make_escape(ep);
|
||||
|
||||
targetFileName = realloc(targetFileName,
|
||||
targetFileNameLen + newTargetLen + 1);
|
||||
if (targetFileName == NULL)
|
||||
err("Cannot append new file to target file list");
|
||||
memcpy(&targetFileName[targetFileNameLen], newTarget, newTargetLen);
|
||||
nTargetFileNameLen += strlen(ep) + 1;
|
||||
if (!tzTargetFileName) {
|
||||
/* On first alloc, make an empty str */
|
||||
tzTargetFileName = malloc(nTargetFileNameLen + 1);
|
||||
if (tzTargetFileName)
|
||||
*tzTargetFileName = '\0';
|
||||
} else {
|
||||
tzTargetFileName = realloc(tzTargetFileName,
|
||||
nTargetFileNameLen + 1);
|
||||
}
|
||||
if (tzTargetFileName == NULL)
|
||||
err(1, "Cannot append new file to target file list");
|
||||
strcat(tzTargetFileName, ep);
|
||||
if (depType == 'Q')
|
||||
free(newTarget);
|
||||
targetFileNameLen += newTargetLen;
|
||||
targetFileName[targetFileNameLen - 1] = ' ';
|
||||
free(ep);
|
||||
char *ptr = tzTargetFileName + strlen(tzTargetFileName);
|
||||
|
||||
*ptr++ = ' ';
|
||||
*ptr = '\0';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -328,10 +314,8 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
if (targetFileName == NULL)
|
||||
targetFileName = objectName;
|
||||
else
|
||||
targetFileName[targetFileNameLen - 1] = '\0'; // Overwrite the last space
|
||||
if (tzTargetFileName == NULL)
|
||||
tzTargetFileName = tzObjectname;
|
||||
|
||||
if (argc == musl_optind) {
|
||||
fputs("FATAL: No input files\n", stderr);
|
||||
@@ -347,17 +331,17 @@ int main(int argc, char *argv[])
|
||||
printf("Assembling %s\n", mainFileName);
|
||||
|
||||
if (dependfile) {
|
||||
if (!targetFileName)
|
||||
errx("Dependency files can only be created if a target file is specified with either -o, -MQ or -MT");
|
||||
if (!tzTargetFileName)
|
||||
errx(1, "Dependency files can only be created if a target file is specified with either -o, -MQ or -MT\n");
|
||||
|
||||
fprintf(dependfile, "%s: %s\n", targetFileName, mainFileName);
|
||||
fprintf(dependfile, "%s: %s\n", tzTargetFileName, mainFileName);
|
||||
}
|
||||
|
||||
charmap_New("main", NULL);
|
||||
|
||||
// Init lexer and file stack, prodiving file info
|
||||
lexer_Init();
|
||||
fstk_Init(mainFileName, maxDepth);
|
||||
fstk_Init(mainFileName, maxRecursionDepth);
|
||||
|
||||
// Perform parse (yyparse is auto-generated from `parser.y`)
|
||||
if (yyparse() != 0 && nbErrors == 0)
|
||||
@@ -369,15 +353,15 @@ int main(int argc, char *argv[])
|
||||
sect_CheckUnionClosed();
|
||||
|
||||
if (nbErrors != 0)
|
||||
errx("Assembly aborted (%u error%s)!", nbErrors,
|
||||
errx(1, "Assembly aborted (%u error%s)!", nbErrors,
|
||||
nbErrors == 1 ? "" : "s");
|
||||
|
||||
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
||||
if (failedOnMissingInclude)
|
||||
if (oFailedOnMissingInclude)
|
||||
return 0;
|
||||
|
||||
/* If no path specified, don't write file */
|
||||
if (objectName != NULL)
|
||||
if (tzObjectname != NULL)
|
||||
out_WriteObject();
|
||||
return 0;
|
||||
}
|
||||
|
||||
146
src/asm/opt.c
146
src/asm/opt.c
@@ -1,4 +1,4 @@
|
||||
#include <ctype.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
@@ -6,9 +6,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "asm/fstack.h"
|
||||
#include "asm/lexer.h"
|
||||
#include "asm/main.h"
|
||||
#include "asm/section.h"
|
||||
#include "asm/warning.h"
|
||||
|
||||
@@ -16,25 +14,17 @@ struct OptStackEntry {
|
||||
char binary[2];
|
||||
char gbgfx[4];
|
||||
int32_t fillByte;
|
||||
bool haltnop;
|
||||
bool warnOnHaltNop;
|
||||
bool optimizeLoads;
|
||||
bool warnOnLdOpt;
|
||||
bool warningsAreErrors;
|
||||
size_t maxRecursionDepth;
|
||||
// Don't be confused: we use the size of the **global variable** `warningStates`!
|
||||
enum WarningState warningStates[sizeof(warningStates)];
|
||||
struct OptStackEntry *next;
|
||||
};
|
||||
|
||||
static struct OptStackEntry *stack = NULL;
|
||||
|
||||
void opt_B(char const chars[2])
|
||||
void opt_B(char chars[2])
|
||||
{
|
||||
lexer_SetBinDigits(chars);
|
||||
}
|
||||
|
||||
void opt_G(char const chars[4])
|
||||
void opt_G(char chars[4])
|
||||
{
|
||||
lexer_SetGfxDigits(chars);
|
||||
}
|
||||
@@ -44,37 +34,6 @@ void opt_P(uint8_t fill)
|
||||
fillByte = fill;
|
||||
}
|
||||
|
||||
void opt_R(size_t newDepth)
|
||||
{
|
||||
fstk_NewRecursionDepth(newDepth);
|
||||
lexer_CheckRecursionDepth();
|
||||
}
|
||||
|
||||
void opt_H(bool warn)
|
||||
{
|
||||
warnOnHaltNop = warn;
|
||||
}
|
||||
|
||||
void opt_h(bool halt)
|
||||
{
|
||||
haltnop = halt;
|
||||
}
|
||||
|
||||
void opt_L(bool optimize)
|
||||
{
|
||||
optimizeLoads = optimize;
|
||||
}
|
||||
|
||||
void opt_l(bool warn)
|
||||
{
|
||||
warnOnLdOpt = warn;
|
||||
}
|
||||
|
||||
void opt_W(char *flag)
|
||||
{
|
||||
processWarningFlag(flag);
|
||||
}
|
||||
|
||||
void opt_Parse(char *s)
|
||||
{
|
||||
switch (s[0]) {
|
||||
@@ -107,86 +66,6 @@ void opt_Parse(char *s)
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r': {
|
||||
++s; // Skip 'r'
|
||||
while (isblank(*s))
|
||||
++s; // Skip leading whitespace
|
||||
|
||||
if (s[0] == '\0') {
|
||||
error("Missing argument to option 'r'\n");
|
||||
break;
|
||||
}
|
||||
|
||||
char *endptr;
|
||||
unsigned long newDepth = strtoul(s, &endptr, 10);
|
||||
|
||||
if (*endptr != '\0') {
|
||||
error("Invalid argument to option 'r' (\"%s\")\n", s);
|
||||
} else if (errno == ERANGE) {
|
||||
error("Argument to 'r' is out of range (\"%s\")\n", s);
|
||||
} else {
|
||||
opt_R(newDepth);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'H':
|
||||
if (s[1] == '\0')
|
||||
opt_H(false);
|
||||
else
|
||||
error("Option 'H' does not take an argument\n");
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
if (s[1] == '\0')
|
||||
opt_h(false);
|
||||
else
|
||||
error("Option 'h' does not take an argument\n");
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
if (s[1] == '\0')
|
||||
opt_L(false);
|
||||
else
|
||||
error("Option 'L' does not take an argument\n");
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (s[1] == '\0')
|
||||
opt_l(false);
|
||||
else
|
||||
error("Option 'l' does not take an argument\n");
|
||||
break;
|
||||
|
||||
case 'W':
|
||||
if (strlen(&s[1]) > 0)
|
||||
opt_W(&s[1]);
|
||||
else
|
||||
error("Must specify an argument for option 'W'\n");
|
||||
break;
|
||||
|
||||
case '!': // negates flag options that do not take an argument
|
||||
switch (s[1]) {
|
||||
case 'h':
|
||||
if (s[2] == '\0')
|
||||
opt_h(true);
|
||||
else
|
||||
error("Option '!h' does not take an argument\n");
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
if (s[2] == '\0')
|
||||
opt_L(true);
|
||||
else
|
||||
error("Option '!L' does not take an argument\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
error("Unknown option '!%c'\n", s[1]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
error("Unknown option '%c'\n", s[0]);
|
||||
break;
|
||||
@@ -211,16 +90,6 @@ void opt_Push(void)
|
||||
|
||||
entry->fillByte = fillByte; // Pulled from section.h
|
||||
|
||||
entry->haltnop = haltnop; // Pulled from main.h
|
||||
entry->warnOnHaltNop = warnOnHaltNop;
|
||||
|
||||
entry->optimizeLoads = optimizeLoads; // Pulled from main.h
|
||||
entry->warnOnLdOpt = warnOnLdOpt;
|
||||
|
||||
// Both of these pulled from warning.h
|
||||
entry->warningsAreErrors = warningsAreErrors;
|
||||
memcpy(entry->warningStates, warningStates, sizeof(warningStates));
|
||||
|
||||
entry->next = stack;
|
||||
stack = entry;
|
||||
}
|
||||
@@ -237,15 +106,6 @@ void opt_Pop(void)
|
||||
opt_B(entry->binary);
|
||||
opt_G(entry->gbgfx);
|
||||
opt_P(entry->fillByte);
|
||||
opt_H(entry->warnOnHaltNop);
|
||||
opt_h(entry->haltnop);
|
||||
opt_L(entry->optimizeLoads);
|
||||
opt_l(entry->warnOnLdOpt);
|
||||
|
||||
// opt_W does not apply a whole warning state; it processes one flag string
|
||||
warningsAreErrors = entry->warningsAreErrors;
|
||||
memcpy(warningStates, entry->warningStates, sizeof(warningStates));
|
||||
|
||||
stack = entry->next;
|
||||
free(entry);
|
||||
}
|
||||
|
||||
142
src/asm/output.c
142
src/asm/output.c
@@ -27,19 +27,20 @@
|
||||
#include "asm/symbol.h"
|
||||
#include "asm/warning.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "extern/err.h"
|
||||
|
||||
#include "linkdefs.h"
|
||||
#include "platform.h" // strdup
|
||||
|
||||
struct Patch {
|
||||
struct FileStackNode const *src;
|
||||
uint32_t lineNo;
|
||||
uint32_t offset;
|
||||
uint32_t nOffset;
|
||||
struct Section *pcSection;
|
||||
uint32_t pcOffset;
|
||||
uint8_t type;
|
||||
uint32_t rpnSize;
|
||||
uint8_t *rpn;
|
||||
uint32_t nRPNSize;
|
||||
uint8_t *pRPN;
|
||||
struct Patch *next;
|
||||
};
|
||||
|
||||
@@ -50,9 +51,10 @@ struct Assertion {
|
||||
struct Assertion *next;
|
||||
};
|
||||
|
||||
char *objectName;
|
||||
char *tzObjectname;
|
||||
|
||||
struct Section *sectionList;
|
||||
/* TODO: shouldn't `pCurrentSection` be somewhere else? */
|
||||
struct Section *pSectionList, *pCurrentSection;
|
||||
|
||||
/* Linked list of symbols to put in the object file */
|
||||
static struct Symbol *objectSymbols = NULL;
|
||||
@@ -70,7 +72,7 @@ static uint32_t countSections(void)
|
||||
{
|
||||
uint32_t count = 0;
|
||||
|
||||
for (struct Section const *sect = sectionList; sect; sect = sect->next)
|
||||
for (struct Section const *sect = pSectionList; sect; sect = sect->next)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
@@ -177,7 +179,7 @@ This is code intended to replace a node, which is pretty useless until ref count
|
||||
*/
|
||||
static uint32_t getsectid(struct Section const *sect)
|
||||
{
|
||||
struct Section const *sec = sectionList;
|
||||
struct Section const *sec = pSectionList;
|
||||
uint32_t ID = 0;
|
||||
|
||||
while (sec) {
|
||||
@@ -192,7 +194,7 @@ static uint32_t getsectid(struct Section const *sect)
|
||||
|
||||
static uint32_t getSectIDIfAny(struct Section const *sect)
|
||||
{
|
||||
return sect ? getsectid(sect) : (uint32_t)-1;
|
||||
return sect ? getsectid(sect) : -1;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -200,15 +202,15 @@ static uint32_t getSectIDIfAny(struct Section const *sect)
|
||||
*/
|
||||
static void writepatch(struct Patch const *patch, FILE *f)
|
||||
{
|
||||
assert(patch->src->ID != (uint32_t)-1);
|
||||
assert(patch->src->ID != -1);
|
||||
putlong(patch->src->ID, f);
|
||||
putlong(patch->lineNo, f);
|
||||
putlong(patch->offset, f);
|
||||
putlong(patch->nOffset, f);
|
||||
putlong(getSectIDIfAny(patch->pcSection), f);
|
||||
putlong(patch->pcOffset, f);
|
||||
putc(patch->type, f);
|
||||
putlong(patch->rpnSize, f);
|
||||
fwrite(patch->rpn, 1, patch->rpnSize, f);
|
||||
putlong(patch->nRPNSize, f);
|
||||
fwrite(patch->pRPN, 1, patch->nRPNSize, f);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -240,21 +242,6 @@ static void writesection(struct Section const *sect, FILE *f)
|
||||
}
|
||||
}
|
||||
|
||||
static void freesection(struct Section const *sect)
|
||||
{
|
||||
if (sect_HasData(sect->type)) {
|
||||
struct Patch *patch = sect->patches;
|
||||
|
||||
while (patch != NULL) {
|
||||
struct Patch *next = patch->next;
|
||||
|
||||
free(patch->rpn);
|
||||
free(patch);
|
||||
patch = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a symbol to a file
|
||||
*/
|
||||
@@ -264,7 +251,7 @@ static void writesymbol(struct Symbol const *sym, FILE *f)
|
||||
if (!sym_IsDefined(sym)) {
|
||||
putc(SYMTYPE_IMPORT, f);
|
||||
} else {
|
||||
assert(sym->src->ID != (uint32_t)-1);
|
||||
assert(sym->src->ID != -1);
|
||||
|
||||
putc(sym->isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, f);
|
||||
putlong(sym->src->ID, f);
|
||||
@@ -299,7 +286,7 @@ static uint32_t getSymbolID(struct Symbol *sym)
|
||||
static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
|
||||
uint32_t rpnlen)
|
||||
{
|
||||
char symName[512];
|
||||
char tzSym[512];
|
||||
|
||||
for (size_t offset = 0; offset < rpnlen; ) {
|
||||
#define popbyte() rpn[offset++]
|
||||
@@ -323,14 +310,14 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
|
||||
case RPN_SYM:
|
||||
i = 0;
|
||||
do {
|
||||
symName[i] = popbyte();
|
||||
} while (symName[i++]);
|
||||
tzSym[i] = popbyte();
|
||||
} while (tzSym[i++]);
|
||||
|
||||
// The symbol name is always written expanded
|
||||
sym = sym_FindExactSymbol(symName);
|
||||
sym = sym_FindExactSymbol(tzSym);
|
||||
if (sym_IsConstant(sym)) {
|
||||
writebyte(RPN_CONST);
|
||||
value = sym_GetConstantValue(symName);
|
||||
value = sym_GetConstantValue(tzSym);
|
||||
} else {
|
||||
writebyte(RPN_SYM);
|
||||
value = getSymbolID(sym);
|
||||
@@ -345,11 +332,11 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
|
||||
case RPN_BANK_SYM:
|
||||
i = 0;
|
||||
do {
|
||||
symName[i] = popbyte();
|
||||
} while (symName[i++]);
|
||||
tzSym[i] = popbyte();
|
||||
} while (tzSym[i++]);
|
||||
|
||||
// The symbol name is always written expanded
|
||||
sym = sym_FindExactSymbol(symName);
|
||||
sym = sym_FindExactSymbol(tzSym);
|
||||
value = getSymbolID(sym);
|
||||
|
||||
writebyte(RPN_BANK_SYM);
|
||||
@@ -367,22 +354,6 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
|
||||
} while (b != 0);
|
||||
break;
|
||||
|
||||
case RPN_SIZEOF_SECT:
|
||||
writebyte(RPN_SIZEOF_SECT);
|
||||
do {
|
||||
b = popbyte();
|
||||
writebyte(b);
|
||||
} while (b != 0);
|
||||
break;
|
||||
|
||||
case RPN_STARTOF_SECT:
|
||||
writebyte(RPN_STARTOF_SECT);
|
||||
do {
|
||||
b = popbyte();
|
||||
writebyte(b);
|
||||
} while (b != 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
writebyte(rpndata);
|
||||
break;
|
||||
@@ -399,38 +370,38 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
|
||||
static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
|
||||
{
|
||||
struct Patch *patch = malloc(sizeof(struct Patch));
|
||||
uint32_t rpnSize = expr->isKnown ? 5 : expr->rpnPatchSize;
|
||||
uint32_t rpnSize = expr->isKnown ? 5 : expr->nRPNPatchSize;
|
||||
struct FileStackNode *node = fstk_GetFileStack();
|
||||
|
||||
if (!patch)
|
||||
fatalerror("No memory for patch: %s\n", strerror(errno));
|
||||
|
||||
patch->rpn = malloc(sizeof(*patch->rpn) * rpnSize);
|
||||
if (!patch->rpn)
|
||||
fatalerror("No memory for patch's RPN rpnSize: %s\n", strerror(errno));
|
||||
patch->pRPN = malloc(sizeof(*patch->pRPN) * rpnSize);
|
||||
if (!patch->pRPN)
|
||||
fatalerror("No memory for patch's RPN expression: %s\n", strerror(errno));
|
||||
|
||||
patch->type = type;
|
||||
patch->src = node;
|
||||
out_RegisterNode(node);
|
||||
patch->lineNo = lexer_GetLineNo();
|
||||
patch->offset = ofs;
|
||||
patch->nOffset = ofs;
|
||||
patch->pcSection = sect_GetSymbolSection();
|
||||
patch->pcOffset = sect_GetSymbolOffset();
|
||||
|
||||
/* If the rpnSize's value is known, output a constant RPN rpnSize directly */
|
||||
/* If the expression's value is known, output a constant RPN expression directly */
|
||||
if (expr->isKnown) {
|
||||
patch->rpnSize = rpnSize;
|
||||
patch->nRPNSize = rpnSize;
|
||||
/* Make sure to update `rpnSize` above if modifying this! */
|
||||
patch->rpn[0] = RPN_CONST;
|
||||
patch->rpn[1] = (uint32_t)(expr->val) & 0xFF;
|
||||
patch->rpn[2] = (uint32_t)(expr->val) >> 8;
|
||||
patch->rpn[3] = (uint32_t)(expr->val) >> 16;
|
||||
patch->rpn[4] = (uint32_t)(expr->val) >> 24;
|
||||
patch->pRPN[0] = RPN_CONST;
|
||||
patch->pRPN[1] = (uint32_t)(expr->nVal) & 0xFF;
|
||||
patch->pRPN[2] = (uint32_t)(expr->nVal) >> 8;
|
||||
patch->pRPN[3] = (uint32_t)(expr->nVal) >> 16;
|
||||
patch->pRPN[4] = (uint32_t)(expr->nVal) >> 24;
|
||||
} else {
|
||||
patch->rpnSize = 0;
|
||||
writerpn(patch->rpn, &patch->rpnSize, expr->rpn, expr->rpnLength);
|
||||
patch->nRPNSize = 0;
|
||||
writerpn(patch->pRPN, &patch->nRPNSize, expr->tRPN, expr->nRPNLength);
|
||||
}
|
||||
assert(patch->rpnSize == rpnSize);
|
||||
assert(patch->nRPNSize == rpnSize);
|
||||
|
||||
return patch;
|
||||
}
|
||||
@@ -447,8 +418,8 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs,
|
||||
// before those bytes.
|
||||
patch->pcOffset -= pcShift;
|
||||
|
||||
patch->next = currentSection->patches;
|
||||
currentSection->patches = patch;
|
||||
patch->next = pCurrentSection->patches;
|
||||
pCurrentSection->patches = patch;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -481,16 +452,9 @@ static void writeassert(struct Assertion *assert, FILE *f)
|
||||
putstring(assert->message, f);
|
||||
}
|
||||
|
||||
static void freeassert(struct Assertion *assert)
|
||||
{
|
||||
free(assert->patch->rpn);
|
||||
free(assert->patch);
|
||||
free(assert);
|
||||
}
|
||||
|
||||
static void writeFileStackNode(struct FileStackNode const *node, FILE *f)
|
||||
{
|
||||
putlong(node->parent ? node->parent->ID : (uint32_t)-1, f);
|
||||
putlong(node->parent ? node->parent->ID : -1, f);
|
||||
putlong(node->lineNo, f);
|
||||
putc(node->type, f);
|
||||
if (node->type != NODE_REPT) {
|
||||
@@ -521,13 +485,13 @@ static void registerUnregisteredSymbol(struct Symbol *symbol, void *arg)
|
||||
void out_WriteObject(void)
|
||||
{
|
||||
FILE *f;
|
||||
if (strcmp(objectName, "-") != 0)
|
||||
f = fopen(objectName, "wb");
|
||||
if (strcmp(tzObjectname, "-") != 0)
|
||||
f = fopen(tzObjectname, "wb");
|
||||
else
|
||||
f = fdopen(1, "wb");
|
||||
|
||||
if (!f)
|
||||
err("Couldn't write file '%s'", objectName);
|
||||
err(1, "Couldn't write file '%s'", tzObjectname);
|
||||
|
||||
/* Also write symbols that weren't written above */
|
||||
sym_ForEach(registerUnregisteredSymbol, NULL);
|
||||
@@ -550,21 +514,13 @@ void out_WriteObject(void)
|
||||
for (struct Symbol const *sym = objectSymbols; sym; sym = sym->next)
|
||||
writesymbol(sym, f);
|
||||
|
||||
for (struct Section *sect = sectionList; sect; sect = sect->next) {
|
||||
for (struct Section *sect = pSectionList; sect; sect = sect->next)
|
||||
writesection(sect, f);
|
||||
freesection(sect);
|
||||
}
|
||||
|
||||
putlong(countAsserts(), f);
|
||||
struct Assertion *assert = assertions;
|
||||
|
||||
while (assert != NULL) {
|
||||
struct Assertion *next = assert->next;
|
||||
|
||||
for (struct Assertion *assert = assertions; assert;
|
||||
assert = assert->next)
|
||||
writeassert(assert, f);
|
||||
freeassert(assert);
|
||||
assert = next;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
@@ -574,7 +530,7 @@ void out_WriteObject(void)
|
||||
*/
|
||||
void out_SetFileName(char *s)
|
||||
{
|
||||
objectName = s;
|
||||
tzObjectname = s;
|
||||
if (verbose)
|
||||
printf("Output filename %s\n", s);
|
||||
}
|
||||
|
||||
1653
src/asm/parser.y
1653
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
@@ -55,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
|
||||
Add a string symbol to the compiled source code.
|
||||
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
|
||||
.Ql Ar name Ic EQUS No \(dq1\(dq
|
||||
.Ql Ar name Ic EQUS \(dq1\(dq
|
||||
if
|
||||
.Ar value
|
||||
is not specified.
|
||||
@@ -83,7 +83,7 @@ Add an include path.
|
||||
Disable the optimization that turns loads of the form
|
||||
.Ic LD [$FF00+n8],A
|
||||
into the opcode
|
||||
.Ic LDH [$FF00+n8],A
|
||||
.Ic LD [H $FF00+n8],A
|
||||
in order to have full control of the result in the final ROM.
|
||||
.It Fl M Ar depend_file , Fl Fl dependfile Ar depend_file
|
||||
Print
|
||||
@@ -189,7 +189,7 @@ Ignoring the
|
||||
prefix, entries are listed alphabetically.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Wno-assert
|
||||
Warn when
|
||||
Warns when
|
||||
.Ic WARN Ns No -type
|
||||
assertions fail. (See
|
||||
.Dq Aborting the assembly process
|
||||
@@ -197,12 +197,6 @@ in
|
||||
.Xr rgbasm 5
|
||||
for
|
||||
.Ic ASSERT ) .
|
||||
.It Fl Wbackwards-for
|
||||
Warn when
|
||||
.Ic FOR
|
||||
loops have their start and stop values switched according to the step value.
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wbuiltin-args
|
||||
Warn about incorrect arguments to built-in functions, such as
|
||||
.Fn STRSUB
|
||||
@@ -214,7 +208,7 @@ Warn when re-defining a charmap mapping.
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wdiv
|
||||
Warn when dividing the smallest negative integer (-2**31) by -1, which yields itself due to integer overflow.
|
||||
Warn when dividing the smallest negative integer by -1, which yields itself due to integer overflow.
|
||||
.It Fl Wempty-macro-arg
|
||||
Warn when a macro argument is empty.
|
||||
This warning is enabled by
|
||||
@@ -243,37 +237,15 @@ Warn when obsolete constructs such as the
|
||||
constant or
|
||||
.Ic PRINTT
|
||||
directive are encountered.
|
||||
.It Fl Wnumeric-string=
|
||||
Warn when a multi-character string is treated as a number.
|
||||
.Fl Wnumeric-string=0
|
||||
or
|
||||
.Fl Wno-numeric-string
|
||||
disables this warning.
|
||||
.Fl Wnumeric-string=1
|
||||
or just
|
||||
.Fl Wnumeric-string
|
||||
warns about strings longer than four characters, since four or fewer characters fit within a 32-bit integer.
|
||||
.Fl Wnumeric-string=2
|
||||
warns about any multi-character string.
|
||||
.It Fl Wshift
|
||||
Warn when shifting right a negative value.
|
||||
Use a division by 2**N instead.
|
||||
Use a division by 2^N instead.
|
||||
.It Fl Wshift-amount
|
||||
Warn when a shift's operand is negative or greater than 32.
|
||||
.It Fl Wtruncation=
|
||||
.It Fl Wno-truncation
|
||||
Warn when an implicit truncation (for example,
|
||||
.Ic db
|
||||
to an 8-bit value) loses some bits.
|
||||
.Fl Wtruncation=0
|
||||
or
|
||||
.Fl Wno-truncation
|
||||
disables this warning.
|
||||
.Fl Wtruncation=1
|
||||
warns when an N-bit value's absolute value is 2**N or greater.
|
||||
.Fl Wtruncation=2
|
||||
or just
|
||||
.Fl Wtruncation
|
||||
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
|
||||
.Ic LD [B @] )
|
||||
loses some bits.
|
||||
.It Fl Wno-user
|
||||
Warn when the
|
||||
.Ic WARN
|
||||
File diff suppressed because it is too large
Load Diff
379
src/asm/rpn.c
379
src/asm/rpn.c
@@ -13,7 +13,6 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -39,8 +38,6 @@
|
||||
int size = snprintf(_expr->reason, 128, __VA_ARGS__); \
|
||||
if (size >= 128) { /* If this wasn't enough, try again */ \
|
||||
_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__); \
|
||||
} \
|
||||
} while (0)
|
||||
@@ -48,47 +45,47 @@
|
||||
static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
|
||||
{
|
||||
/* This assumes the RPN length is always less than the capacity */
|
||||
if (expr->rpnCapacity - expr->rpnLength < size) {
|
||||
if (expr->nRPNCapacity - expr->nRPNLength < size) {
|
||||
/* If there isn't enough room to reserve the space, realloc */
|
||||
if (!expr->rpn)
|
||||
expr->rpnCapacity = 256; /* Initial size */
|
||||
while (expr->rpnCapacity - expr->rpnLength < size) {
|
||||
if (expr->rpnCapacity >= MAXRPNLEN)
|
||||
if (!expr->tRPN)
|
||||
expr->nRPNCapacity = 256; /* Initial size */
|
||||
while (expr->nRPNCapacity - expr->nRPNLength < size) {
|
||||
if (expr->nRPNCapacity >= MAXRPNLEN)
|
||||
/*
|
||||
* To avoid generating humongous object files, cap the
|
||||
* size of RPN expressions
|
||||
*/
|
||||
fatalerror("RPN expression cannot grow larger than "
|
||||
EXPAND_AND_STR(MAXRPNLEN) " bytes\n");
|
||||
else if (expr->rpnCapacity > MAXRPNLEN / 2)
|
||||
expr->rpnCapacity = MAXRPNLEN;
|
||||
else if (expr->nRPNCapacity > MAXRPNLEN / 2)
|
||||
expr->nRPNCapacity = MAXRPNLEN;
|
||||
else
|
||||
expr->rpnCapacity *= 2;
|
||||
expr->nRPNCapacity *= 2;
|
||||
}
|
||||
expr->rpn = realloc(expr->rpn, expr->rpnCapacity);
|
||||
expr->tRPN = realloc(expr->tRPN, expr->nRPNCapacity);
|
||||
|
||||
if (!expr->rpn)
|
||||
if (!expr->tRPN)
|
||||
fatalerror("Failed to grow RPN expression: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
uint8_t *ptr = expr->rpn + expr->rpnLength;
|
||||
uint8_t *ptr = expr->tRPN + expr->nRPNLength;
|
||||
|
||||
expr->rpnLength += size;
|
||||
expr->nRPNLength += size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Init a RPN expression
|
||||
* Init the RPN expression
|
||||
*/
|
||||
static void rpn_Init(struct Expression *expr)
|
||||
{
|
||||
expr->reason = NULL;
|
||||
expr->isKnown = true;
|
||||
expr->isSymbol = false;
|
||||
expr->rpn = NULL;
|
||||
expr->rpnCapacity = 0;
|
||||
expr->rpnLength = 0;
|
||||
expr->rpnPatchSize = 0;
|
||||
expr->tRPN = NULL;
|
||||
expr->nRPNCapacity = 0;
|
||||
expr->nRPNLength = 0;
|
||||
expr->nRPNPatchSize = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -96,7 +93,7 @@ static void rpn_Init(struct Expression *expr)
|
||||
*/
|
||||
void rpn_Free(struct Expression *expr)
|
||||
{
|
||||
free(expr->rpn);
|
||||
free(expr->tRPN);
|
||||
free(expr->reason);
|
||||
rpn_Init(expr);
|
||||
}
|
||||
@@ -107,12 +104,12 @@ void rpn_Free(struct Expression *expr)
|
||||
void rpn_Number(struct Expression *expr, uint32_t i)
|
||||
{
|
||||
rpn_Init(expr);
|
||||
expr->val = i;
|
||||
expr->nVal = i;
|
||||
}
|
||||
|
||||
void rpn_Symbol(struct Expression *expr, char const *symName)
|
||||
void rpn_Symbol(struct Expression *expr, char const *tzSym)
|
||||
{
|
||||
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
struct Symbol *sym = sym_FindScopedSymbol(tzSym);
|
||||
|
||||
if (sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||
error("PC has no value outside a section\n");
|
||||
@@ -122,16 +119,16 @@ void rpn_Symbol(struct Expression *expr, char const *symName)
|
||||
expr->isSymbol = true;
|
||||
|
||||
makeUnknown(expr, sym_IsPC(sym) ? "PC is not constant at assembly time"
|
||||
: "'%s' is not constant at assembly time", symName);
|
||||
sym = sym_Ref(symName);
|
||||
expr->rpnPatchSize += 5; /* 1-byte opcode + 4-byte symbol ID */
|
||||
: "'%s' is not constant at assembly time", tzSym);
|
||||
sym = sym_Ref(tzSym);
|
||||
expr->nRPNPatchSize += 5; /* 1-byte opcode + 4-byte symbol ID */
|
||||
|
||||
size_t nameLen = strlen(sym->name) + 1; /* Don't forget NUL! */
|
||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||
*ptr++ = RPN_SYM;
|
||||
memcpy(ptr, sym->name, nameLen);
|
||||
} else {
|
||||
rpn_Number(expr, sym_GetConstantValue(symName));
|
||||
rpn_Number(expr, sym_GetConstantValue(tzSym));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,21 +136,21 @@ void rpn_BankSelf(struct Expression *expr)
|
||||
{
|
||||
rpn_Init(expr);
|
||||
|
||||
if (!currentSection) {
|
||||
if (!pCurrentSection) {
|
||||
error("PC has no bank outside a section\n");
|
||||
expr->val = 1;
|
||||
} else if (currentSection->bank == (uint32_t)-1) {
|
||||
expr->nVal = 1;
|
||||
} else if (pCurrentSection->bank == (uint32_t)-1) {
|
||||
makeUnknown(expr, "Current section's bank is not known");
|
||||
expr->rpnPatchSize++;
|
||||
expr->nRPNPatchSize++;
|
||||
*reserveSpace(expr, 1) = RPN_BANK_SELF;
|
||||
} else {
|
||||
expr->val = currentSection->bank;
|
||||
expr->nVal = pCurrentSection->bank;
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_BankSymbol(struct Expression *expr, char const *symName)
|
||||
void rpn_BankSymbol(struct Expression *expr, char const *tzSym)
|
||||
{
|
||||
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
||||
struct Symbol const *sym = sym_FindScopedSymbol(tzSym);
|
||||
|
||||
/* The @ symbol is treated differently. */
|
||||
if (sym_IsPC(sym)) {
|
||||
@@ -165,15 +162,15 @@ void rpn_BankSymbol(struct Expression *expr, char const *symName)
|
||||
if (sym && !sym_IsLabel(sym)) {
|
||||
error("BANK argument must be a label\n");
|
||||
} else {
|
||||
sym = sym_Ref(symName);
|
||||
sym = sym_Ref(tzSym);
|
||||
assert(sym); // If the symbol didn't exist, it should have been created
|
||||
|
||||
if (sym_GetSection(sym) && sym_GetSection(sym)->bank != (uint32_t)-1) {
|
||||
/* Symbol's section is known and bank is fixed */
|
||||
expr->val = sym_GetSection(sym)->bank;
|
||||
expr->nVal = sym_GetSection(sym)->bank;
|
||||
} else {
|
||||
makeUnknown(expr, "\"%s\"'s bank is not known", symName);
|
||||
expr->rpnPatchSize += 5; /* opcode + 4-byte sect ID */
|
||||
makeUnknown(expr, "\"%s\"'s bank is not known", tzSym);
|
||||
expr->nRPNPatchSize += 5; /* opcode + 4-byte sect ID */
|
||||
|
||||
size_t nameLen = strlen(sym->name) + 1; /* Room for NUL! */
|
||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||
@@ -183,63 +180,24 @@ void rpn_BankSymbol(struct Expression *expr, char const *symName)
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_BankSection(struct Expression *expr, char const *sectionName)
|
||||
void rpn_BankSection(struct Expression *expr, char const *tzSectionName)
|
||||
{
|
||||
rpn_Init(expr);
|
||||
|
||||
struct Section *section = sect_FindSectionByName(sectionName);
|
||||
struct Section *pSection = out_FindSectionByName(tzSectionName);
|
||||
|
||||
if (section && section->bank != (uint32_t)-1) {
|
||||
expr->val = section->bank;
|
||||
if (pSection && pSection->bank != (uint32_t)-1) {
|
||||
expr->nVal = pSection->bank;
|
||||
} else {
|
||||
makeUnknown(expr, "Section \"%s\"'s bank is not known", sectionName);
|
||||
makeUnknown(expr, "Section \"%s\"'s bank is not known",
|
||||
tzSectionName);
|
||||
|
||||
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
|
||||
size_t nameLen = strlen(tzSectionName) + 1; /* Room for NUL! */
|
||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||
|
||||
expr->rpnPatchSize += nameLen + 1;
|
||||
expr->nRPNPatchSize += nameLen + 1;
|
||||
*ptr++ = RPN_BANK_SECT;
|
||||
memcpy(ptr, sectionName, nameLen);
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_SizeOfSection(struct Expression *expr, char const *sectionName)
|
||||
{
|
||||
rpn_Init(expr);
|
||||
|
||||
struct Section *section = sect_FindSectionByName(sectionName);
|
||||
|
||||
if (section && sect_IsSizeKnown(section)) {
|
||||
expr->val = section->size;
|
||||
} else {
|
||||
makeUnknown(expr, "Section \"%s\"'s size is not known", sectionName);
|
||||
|
||||
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
|
||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||
|
||||
expr->rpnPatchSize += nameLen + 1;
|
||||
*ptr++ = RPN_SIZEOF_SECT;
|
||||
memcpy(ptr, sectionName, nameLen);
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_StartOfSection(struct Expression *expr, char const *sectionName)
|
||||
{
|
||||
rpn_Init(expr);
|
||||
|
||||
struct Section *section = sect_FindSectionByName(sectionName);
|
||||
|
||||
if (section && section->org != (uint32_t)-1) {
|
||||
expr->val = section->org;
|
||||
} else {
|
||||
makeUnknown(expr, "Section \"%s\"'s start is not known", sectionName);
|
||||
|
||||
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
|
||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||
|
||||
expr->rpnPatchSize += nameLen + 1;
|
||||
*ptr++ = RPN_STARTOF_SECT;
|
||||
memcpy(ptr, sectionName, nameLen);
|
||||
memcpy(ptr, tzSectionName, nameLen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,13 +207,13 @@ void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src)
|
||||
expr->isSymbol = false;
|
||||
|
||||
if (!rpn_isKnown(expr)) {
|
||||
expr->rpnPatchSize++;
|
||||
expr->nRPNPatchSize++;
|
||||
*reserveSpace(expr, 1) = RPN_HRAM;
|
||||
} else if (expr->val >= 0xFF00 && expr->val <= 0xFFFF) {
|
||||
} else if (expr->nVal >= 0xFF00 && expr->nVal <= 0xFFFF) {
|
||||
/* That range is valid, but only keep the lower byte */
|
||||
expr->val &= 0xFF;
|
||||
} else if (expr->val < 0 || expr->val > 0xFF) {
|
||||
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->val);
|
||||
expr->nVal &= 0xFF;
|
||||
} else if (expr->nVal < 0 || expr->nVal > 0xFF) {
|
||||
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->nVal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,52 +223,25 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src)
|
||||
|
||||
if (rpn_isKnown(expr)) {
|
||||
/* A valid RST address must be masked with 0x38 */
|
||||
if (expr->val & ~0x38)
|
||||
error("Invalid address $%" PRIx32 " for RST\n", expr->val);
|
||||
if (expr->nVal & ~0x38)
|
||||
error("Invalid address $%" PRIx32 " for RST\n", expr->nVal);
|
||||
/* The target is in the "0x38" bits, all other bits are set */
|
||||
expr->val |= 0xC7;
|
||||
expr->nVal |= 0xC7;
|
||||
} else {
|
||||
expr->rpnPatchSize++;
|
||||
expr->nRPNPatchSize++;
|
||||
*reserveSpace(expr, 1) = RPN_RST;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||
*/
|
||||
void rpn_CheckNBit(struct Expression const *expr, uint8_t n)
|
||||
{
|
||||
assert(n != 0); // That doesn't make sense
|
||||
assert(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
||||
|
||||
if (rpn_isKnown(expr)) {
|
||||
int32_t val = expr->val;
|
||||
|
||||
if (val <= -(1 << n) || val >= 1 << n)
|
||||
warning(WARNING_TRUNCATION_1, "Expression must be %u-bit\n", n);
|
||||
else if (val < -(1 << (n - 1)))
|
||||
warning(WARNING_TRUNCATION_2, "Expression must be %u-bit\n", n);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t rpn_GetConstVal(struct Expression const *expr)
|
||||
{
|
||||
if (!rpn_isKnown(expr)) {
|
||||
error("Expected constant expression: %s\n", expr->reason);
|
||||
return 0;
|
||||
}
|
||||
return expr->val;
|
||||
}
|
||||
|
||||
void rpn_LOGNOT(struct Expression *expr, const struct Expression *src)
|
||||
{
|
||||
*expr = *src;
|
||||
expr->isSymbol = false;
|
||||
|
||||
if (rpn_isKnown(expr)) {
|
||||
expr->val = !expr->val;
|
||||
expr->nVal = !expr->nVal;
|
||||
} else {
|
||||
expr->rpnPatchSize++;
|
||||
expr->nRPNPatchSize++;
|
||||
*reserveSpace(expr, 1) = RPN_LOGUNNOT;
|
||||
}
|
||||
}
|
||||
@@ -319,7 +250,7 @@ struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
|
||||
{
|
||||
if (!rpn_isSymbol(expr))
|
||||
return NULL;
|
||||
return sym_FindScopedSymbol((char const *)expr->rpn + 1);
|
||||
return sym_FindScopedSymbol((char *)expr->tRPN + 1);
|
||||
}
|
||||
|
||||
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym)
|
||||
@@ -341,51 +272,10 @@ static bool isDiffConstant(struct Expression const *src1,
|
||||
return rpn_IsDiffConstant(src1, rpn_SymbolOf(src2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* a constant that only keeps (some of) the lower N bits.
|
||||
*
|
||||
* @return The constant result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
static int32_t tryConstMask(struct Expression const *lhs, struct Expression const *rhs)
|
||||
{
|
||||
struct Symbol const *sym = rpn_SymbolOf(lhs);
|
||||
struct Expression const *expr = rhs;
|
||||
|
||||
if (!sym || !sym_GetSection(sym)) {
|
||||
// If the lhs isn't a symbol, try again the other way around
|
||||
sym = rpn_SymbolOf(rhs);
|
||||
expr = lhs;
|
||||
|
||||
if (!sym || !sym_GetSection(sym))
|
||||
return -1;
|
||||
}
|
||||
assert(sym_IsNumeric(sym));
|
||||
|
||||
if (!rpn_isKnown(expr))
|
||||
return -1;
|
||||
// We can now safely use `expr->val`
|
||||
struct Section const *sect = sym_GetSection(sym);
|
||||
int32_t unknownBits = (1 << 16) - (1 << sect->align); // The max alignment is 16
|
||||
|
||||
// The mask must ignore all unknown bits
|
||||
if ((expr->val & unknownBits) != 0)
|
||||
return -1;
|
||||
|
||||
// `sym_GetValue()` attempts to add the section's address,
|
||||
// but that's "-1" because the section is floating (otherwise we wouldn't be here)
|
||||
assert(sect->org == (uint32_t)-1);
|
||||
int32_t symbolOfs = sym_GetValue(sym) + 1;
|
||||
|
||||
return (symbolOfs + sect->alignOfs) & ~unknownBits;
|
||||
}
|
||||
|
||||
void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
||||
const struct Expression *src1, const struct Expression *src2)
|
||||
{
|
||||
expr->isSymbol = false;
|
||||
int32_t constMaskVal;
|
||||
|
||||
/* First, check if the expression is known */
|
||||
expr->isKnown = src1->isKnown && src2->isKnown;
|
||||
@@ -393,123 +283,111 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
||||
rpn_Init(expr); /* Init the expression to something sane */
|
||||
|
||||
/* If both expressions are known, just compute the value */
|
||||
uint32_t uleft = src1->val, uright = src2->val;
|
||||
uint32_t uleft = src1->nVal, uright = src2->nVal;
|
||||
|
||||
switch (op) {
|
||||
case RPN_LOGOR:
|
||||
expr->val = src1->val || src2->val;
|
||||
expr->nVal = src1->nVal || src2->nVal;
|
||||
break;
|
||||
case RPN_LOGAND:
|
||||
expr->val = src1->val && src2->val;
|
||||
expr->nVal = src1->nVal && src2->nVal;
|
||||
break;
|
||||
case RPN_LOGEQ:
|
||||
expr->val = src1->val == src2->val;
|
||||
expr->nVal = src1->nVal == src2->nVal;
|
||||
break;
|
||||
case RPN_LOGGT:
|
||||
expr->val = src1->val > src2->val;
|
||||
expr->nVal = src1->nVal > src2->nVal;
|
||||
break;
|
||||
case RPN_LOGLT:
|
||||
expr->val = src1->val < src2->val;
|
||||
expr->nVal = src1->nVal < src2->nVal;
|
||||
break;
|
||||
case RPN_LOGGE:
|
||||
expr->val = src1->val >= src2->val;
|
||||
expr->nVal = src1->nVal >= src2->nVal;
|
||||
break;
|
||||
case RPN_LOGLE:
|
||||
expr->val = src1->val <= src2->val;
|
||||
expr->nVal = src1->nVal <= src2->nVal;
|
||||
break;
|
||||
case RPN_LOGNE:
|
||||
expr->val = src1->val != src2->val;
|
||||
expr->nVal = src1->nVal != src2->nVal;
|
||||
break;
|
||||
case RPN_ADD:
|
||||
expr->val = uleft + uright;
|
||||
expr->nVal = uleft + uright;
|
||||
break;
|
||||
case RPN_SUB:
|
||||
expr->val = uleft - uright;
|
||||
expr->nVal = uleft - uright;
|
||||
break;
|
||||
case RPN_XOR:
|
||||
expr->val = src1->val ^ src2->val;
|
||||
expr->nVal = src1->nVal ^ src2->nVal;
|
||||
break;
|
||||
case RPN_OR:
|
||||
expr->val = src1->val | src2->val;
|
||||
expr->nVal = src1->nVal | src2->nVal;
|
||||
break;
|
||||
case RPN_AND:
|
||||
expr->val = src1->val & src2->val;
|
||||
expr->nVal = src1->nVal & src2->nVal;
|
||||
break;
|
||||
case RPN_SHL:
|
||||
if (src2->val < 0)
|
||||
if (src2->nVal < 0)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting left by negative amount %" PRId32 "\n",
|
||||
src2->val);
|
||||
src2->nVal);
|
||||
|
||||
if (src2->val >= 32)
|
||||
if (src2->nVal >= 32)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting left by large amount %" PRId32 "\n", src2->val);
|
||||
"Shifting left by large amount %" PRId32 "\n",
|
||||
src2->nVal);
|
||||
|
||||
expr->val = op_shift_left(src1->val, src2->val);
|
||||
expr->nVal = op_shift_left(src1->nVal, src2->nVal);
|
||||
break;
|
||||
case RPN_SHR:
|
||||
if (src1->val < 0)
|
||||
warning(WARNING_SHIFT,
|
||||
"Shifting right negative value %" PRId32 "\n", src1->val);
|
||||
if (src1->nVal < 0)
|
||||
warning(WARNING_SHIFT, "Shifting right negative value %"
|
||||
PRId32 "\n",
|
||||
src1->nVal);
|
||||
|
||||
if (src2->val < 0)
|
||||
if (src2->nVal < 0)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by negative amount %" PRId32 "\n",
|
||||
src2->val);
|
||||
src2->nVal);
|
||||
|
||||
if (src2->val >= 32)
|
||||
if (src2->nVal >= 32)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by large amount %" PRId32 "\n",
|
||||
src2->val);
|
||||
src2->nVal);
|
||||
|
||||
expr->val = op_shift_right(src1->val, src2->val);
|
||||
break;
|
||||
case RPN_USHR:
|
||||
if (src2->val < 0)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by negative amount %" PRId32 "\n",
|
||||
src2->val);
|
||||
|
||||
if (src2->val >= 32)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by large amount %" PRId32 "\n",
|
||||
src2->val);
|
||||
|
||||
expr->val = op_shift_right_unsigned(src1->val, src2->val);
|
||||
expr->nVal = op_shift_right(src1->nVal, src2->nVal);
|
||||
break;
|
||||
case RPN_MUL:
|
||||
expr->val = uleft * uright;
|
||||
expr->nVal = uleft * uright;
|
||||
break;
|
||||
case RPN_DIV:
|
||||
if (src2->val == 0)
|
||||
if (src2->nVal == 0)
|
||||
fatalerror("Division by zero\n");
|
||||
|
||||
if (src1->val == INT32_MIN && src2->val == -1) {
|
||||
warning(WARNING_DIV,
|
||||
"Division of %" PRId32 " by -1 yields %" PRId32 "\n",
|
||||
INT32_MIN, INT32_MIN);
|
||||
expr->val = INT32_MIN;
|
||||
if (src1->nVal == INT32_MIN && src2->nVal == -1) {
|
||||
warning(WARNING_DIV, "Division of %" PRId32 " by -1 yields %"
|
||||
PRId32 "\n", INT32_MIN, INT32_MIN);
|
||||
expr->nVal = INT32_MIN;
|
||||
} else {
|
||||
expr->val = op_divide(src1->val, src2->val);
|
||||
expr->nVal = op_divide(src1->nVal, src2->nVal);
|
||||
}
|
||||
break;
|
||||
case RPN_MOD:
|
||||
if (src2->val == 0)
|
||||
if (src2->nVal == 0)
|
||||
fatalerror("Modulo by zero\n");
|
||||
|
||||
if (src1->val == INT32_MIN && src2->val == -1)
|
||||
expr->val = 0;
|
||||
if (src1->nVal == INT32_MIN && src2->nVal == -1)
|
||||
expr->nVal = 0;
|
||||
else
|
||||
expr->val = op_modulo(src1->val, src2->val);
|
||||
expr->nVal = op_modulo(src1->nVal, src2->nVal);
|
||||
break;
|
||||
case RPN_EXP:
|
||||
if (src2->val < 0)
|
||||
if (src2->nVal < 0)
|
||||
fatalerror("Exponentiation by negative power\n");
|
||||
|
||||
if (src1->val == INT32_MIN && src2->val == -1)
|
||||
expr->val = 0;
|
||||
if (src1->nVal == INT32_MIN && src2->nVal == -1)
|
||||
expr->nVal = 0;
|
||||
else
|
||||
expr->val = op_exponent(src1->val, src2->val);
|
||||
expr->nVal = op_exponent(src1->nVal, src2->nVal);
|
||||
break;
|
||||
|
||||
case RPN_UNSUB:
|
||||
@@ -518,8 +396,6 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
||||
case RPN_BANK_SYM:
|
||||
case RPN_BANK_SECT:
|
||||
case RPN_BANK_SELF:
|
||||
case RPN_SIZEOF_SECT:
|
||||
case RPN_STARTOF_SECT:
|
||||
case RPN_HRAM:
|
||||
case RPN_RST:
|
||||
case RPN_CONST:
|
||||
@@ -531,23 +407,20 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
||||
struct Symbol const *symbol1 = rpn_SymbolOf(src1);
|
||||
struct Symbol const *symbol2 = rpn_SymbolOf(src2);
|
||||
|
||||
expr->val = sym_GetValue(symbol1) - sym_GetValue(symbol2);
|
||||
expr->isKnown = true;
|
||||
} else if (op == RPN_AND && (constMaskVal = tryConstMask(src1, src2)) != -1) {
|
||||
expr->val = constMaskVal;
|
||||
expr->nVal = sym_GetValue(symbol1) - sym_GetValue(symbol2);
|
||||
expr->isKnown = true;
|
||||
} else {
|
||||
/* If it's not known, start computing the RPN expression */
|
||||
|
||||
/* Convert the left-hand expression if it's constant */
|
||||
if (src1->isKnown) {
|
||||
uint32_t lval = src1->val;
|
||||
uint32_t lval = src1->nVal;
|
||||
uint8_t bytes[] = {RPN_CONST, lval, lval >> 8,
|
||||
lval >> 16, lval >> 24};
|
||||
expr->rpnPatchSize = sizeof(bytes);
|
||||
expr->rpn = NULL;
|
||||
expr->rpnCapacity = 0;
|
||||
expr->rpnLength = 0;
|
||||
expr->nRPNPatchSize = sizeof(bytes);
|
||||
expr->tRPN = NULL;
|
||||
expr->nRPNCapacity = 0;
|
||||
expr->nRPNLength = 0;
|
||||
memcpy(reserveSpace(expr, sizeof(bytes)), bytes,
|
||||
sizeof(bytes));
|
||||
|
||||
@@ -556,21 +429,21 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
||||
free(src1->reason);
|
||||
} else {
|
||||
/* Otherwise just reuse its RPN buffer */
|
||||
expr->rpnPatchSize = src1->rpnPatchSize;
|
||||
expr->rpn = src1->rpn;
|
||||
expr->rpnCapacity = src1->rpnCapacity;
|
||||
expr->rpnLength = src1->rpnLength;
|
||||
expr->nRPNPatchSize = src1->nRPNPatchSize;
|
||||
expr->tRPN = src1->tRPN;
|
||||
expr->nRPNCapacity = src1->nRPNCapacity;
|
||||
expr->nRPNLength = src1->nRPNLength;
|
||||
expr->reason = src1->reason;
|
||||
free(src2->reason);
|
||||
}
|
||||
|
||||
/* Now, merge the right expression into the left one */
|
||||
uint8_t *ptr = src2->rpn; /* Pointer to the right RPN */
|
||||
uint32_t len = src2->rpnLength; /* Size of the right RPN */
|
||||
uint32_t patchSize = src2->rpnPatchSize;
|
||||
uint8_t *ptr = src2->tRPN; /* Pointer to the right RPN */
|
||||
uint32_t len = src2->nRPNLength; /* Size of the right RPN */
|
||||
uint32_t patchSize = src2->nRPNPatchSize;
|
||||
|
||||
/* If the right expression is constant, merge a shim instead */
|
||||
uint32_t rval = src2->val;
|
||||
uint32_t rval = src2->nVal;
|
||||
uint8_t bytes[] = {RPN_CONST, rval, rval >> 8, rval >> 16,
|
||||
rval >> 24};
|
||||
if (src2->isKnown) {
|
||||
@@ -584,8 +457,8 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
||||
memcpy(buf, ptr, len);
|
||||
buf[len] = op;
|
||||
|
||||
free(src2->rpn); /* If there was none, this is `free(NULL)` */
|
||||
expr->rpnPatchSize += patchSize + 1;
|
||||
free(src2->tRPN); /* If there was none, this is `free(NULL)` */
|
||||
expr->nRPNPatchSize += patchSize + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,11 +468,11 @@ void rpn_HIGH(struct Expression *expr, const struct Expression *src)
|
||||
expr->isSymbol = false;
|
||||
|
||||
if (rpn_isKnown(expr)) {
|
||||
expr->val = (uint32_t)expr->val >> 8 & 0xFF;
|
||||
expr->nVal = (uint32_t)expr->nVal >> 8 & 0xFF;
|
||||
} else {
|
||||
uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR,
|
||||
RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
|
||||
expr->rpnPatchSize += sizeof(bytes);
|
||||
expr->nRPNPatchSize += sizeof(bytes);
|
||||
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
|
||||
}
|
||||
}
|
||||
@@ -610,11 +483,11 @@ void rpn_LOW(struct Expression *expr, const struct Expression *src)
|
||||
expr->isSymbol = false;
|
||||
|
||||
if (rpn_isKnown(expr)) {
|
||||
expr->val = expr->val & 0xFF;
|
||||
expr->nVal = expr->nVal & 0xFF;
|
||||
} else {
|
||||
uint8_t bytes[] = {RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
|
||||
|
||||
expr->rpnPatchSize += sizeof(bytes);
|
||||
expr->nRPNPatchSize += sizeof(bytes);
|
||||
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
|
||||
}
|
||||
}
|
||||
@@ -622,7 +495,7 @@ void rpn_LOW(struct Expression *expr, const struct Expression *src)
|
||||
void rpn_ISCONST(struct Expression *expr, const struct Expression *src)
|
||||
{
|
||||
rpn_Init(expr);
|
||||
expr->val = rpn_isKnown(src);
|
||||
expr->nVal = rpn_isKnown(src);
|
||||
expr->isKnown = true;
|
||||
expr->isSymbol = false;
|
||||
}
|
||||
@@ -633,9 +506,9 @@ void rpn_UNNEG(struct Expression *expr, const struct Expression *src)
|
||||
expr->isSymbol = false;
|
||||
|
||||
if (rpn_isKnown(expr)) {
|
||||
expr->val = -(uint32_t)expr->val;
|
||||
expr->nVal = -(uint32_t)expr->nVal;
|
||||
} else {
|
||||
expr->rpnPatchSize++;
|
||||
expr->nRPNPatchSize++;
|
||||
*reserveSpace(expr, 1) = RPN_UNSUB;
|
||||
}
|
||||
}
|
||||
@@ -646,9 +519,9 @@ void rpn_UNNOT(struct Expression *expr, const struct Expression *src)
|
||||
expr->isSymbol = false;
|
||||
|
||||
if (rpn_isKnown(expr)) {
|
||||
expr->val = ~expr->val;
|
||||
expr->nVal = ~expr->nVal;
|
||||
} else {
|
||||
expr->rpnPatchSize++;
|
||||
expr->nRPNPatchSize++;
|
||||
*reserveSpace(expr, 1) = RPN_UNNOT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,104 +15,79 @@
|
||||
#include "asm/symbol.h"
|
||||
#include "asm/warning.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "extern/err.h"
|
||||
#include "platform.h" // strdup
|
||||
|
||||
uint8_t fillByte;
|
||||
|
||||
struct SectionStackEntry {
|
||||
struct Section *section;
|
||||
char const *scope; /* Section's symbol scope */
|
||||
uint32_t offset;
|
||||
struct SectionStackEntry *next;
|
||||
};
|
||||
|
||||
struct SectionStackEntry *sectionStack;
|
||||
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */
|
||||
static struct Section *currentLoadSection = NULL;
|
||||
int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */
|
||||
|
||||
struct UnionStackEntry {
|
||||
uint32_t start;
|
||||
uint32_t size;
|
||||
struct UnionStackEntry *next;
|
||||
} *unionStack = NULL;
|
||||
|
||||
struct SectionStackEntry {
|
||||
struct Section *section;
|
||||
struct Section *loadSection;
|
||||
char const *scope; /* Section's symbol scope */
|
||||
uint32_t offset;
|
||||
int32_t loadOffset;
|
||||
struct UnionStackEntry *unionStack;
|
||||
struct SectionStackEntry *next;
|
||||
};
|
||||
|
||||
struct SectionStackEntry *sectionStack;
|
||||
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */
|
||||
struct Section *currentSection = NULL;
|
||||
static struct Section *currentLoadSection = NULL;
|
||||
int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */
|
||||
|
||||
/*
|
||||
* A quick check to see if we have an initialized section
|
||||
*/
|
||||
attr_(warn_unused_result) static bool checksection(void)
|
||||
static inline void checksection(void)
|
||||
{
|
||||
if (currentSection)
|
||||
return true;
|
||||
|
||||
error("Cannot output data outside of a SECTION\n");
|
||||
return false;
|
||||
if (pCurrentSection == NULL)
|
||||
fatalerror("Code generation before SECTION directive\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
static inline void checkcodesection(void)
|
||||
{
|
||||
if (!checksection())
|
||||
return false;
|
||||
checksection();
|
||||
|
||||
if (sect_HasData(currentSection->type))
|
||||
return true;
|
||||
|
||||
error("Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
||||
currentSection->name);
|
||||
return false;
|
||||
if (!sect_HasData(pCurrentSection->type))
|
||||
fatalerror("Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
||||
pCurrentSection->name);
|
||||
}
|
||||
|
||||
attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sect, uint32_t size)
|
||||
static inline void checkSectionSize(struct Section const *sect, uint32_t size)
|
||||
{
|
||||
uint32_t maxSize = maxsize[sect->type];
|
||||
|
||||
// If the new size is reasonable, keep going
|
||||
if (size <= maxSize)
|
||||
return true;
|
||||
|
||||
error("Section '%s' grew too big (max size = 0x%" PRIX32
|
||||
" bytes, reached 0x%" PRIX32 ").\n", sect->name, maxSize, size);
|
||||
return false;
|
||||
if (size > maxSize)
|
||||
fatalerror("Section '%s' grew too big (max size = 0x%" PRIX32
|
||||
" bytes, reached 0x%" PRIX32 ").\n", sect->name, maxSize, size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the section has grown too much.
|
||||
*/
|
||||
attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
|
||||
static inline void 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
|
||||
* memory.
|
||||
* 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 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 (currentSection->size != UINT32_MAX
|
||||
&& !checkSectionSize(currentSection, curOffset + loadOffset + delta_size))
|
||||
// Mark the section as overflowed, to avoid repeating the error
|
||||
currentSection->size = UINT32_MAX;
|
||||
|
||||
if (currentLoadSection && currentLoadSection->size != UINT32_MAX
|
||||
&& !checkSectionSize(currentLoadSection, curOffset + delta_size))
|
||||
currentLoadSection->size = UINT32_MAX;
|
||||
|
||||
return currentSection->size != UINT32_MAX
|
||||
&& (!currentLoadSection || currentLoadSection->size != UINT32_MAX);
|
||||
checkSectionSize(pCurrentSection, curOffset + loadOffset + delta_size);
|
||||
if (currentLoadSection)
|
||||
checkSectionSize(currentLoadSection, curOffset + delta_size);
|
||||
}
|
||||
|
||||
struct Section *sect_FindSectionByName(char const *name)
|
||||
struct Section *out_FindSectionByName(const char *name)
|
||||
{
|
||||
for (struct Section *sect = sectionList; sect; sect = sect->next) {
|
||||
for (struct Section *sect = pSectionList; sect; sect = sect->next) {
|
||||
if (strcmp(name, sect->name) == 0)
|
||||
return sect;
|
||||
}
|
||||
@@ -160,9 +135,9 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
|
||||
/* Check if alignment offsets are compatible */
|
||||
} else if ((alignOffset & mask(sect->align))
|
||||
!= (sect->alignOfs & mask(alignment))) {
|
||||
fail("Section already declared with incompatible %u"
|
||||
fail("Section already declared with incompatible %" PRIu8
|
||||
"-byte alignment (offset %" PRIu16 ")\n",
|
||||
1u << sect->align, sect->alignOfs);
|
||||
sect->align, sect->alignOfs);
|
||||
} else if (alignment > sect->align) {
|
||||
// If the section is not fixed, its alignment is the largest of both
|
||||
sect->align = alignment;
|
||||
@@ -213,9 +188,9 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
|
||||
PRIx32 "\n", sect->org);
|
||||
/* Check if alignment offsets are compatible */
|
||||
} else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) {
|
||||
fail("Section already declared with incompatible %u"
|
||||
fail("Section already declared with incompatible %" PRIu8
|
||||
"-byte alignment (offset %" PRIu16 ")\n",
|
||||
1u << sect->align, sect->alignOfs);
|
||||
sect->align, sect->alignOfs);
|
||||
} else if (alignment > sect->align) {
|
||||
// If the section is not fixed, its alignment is the largest of both
|
||||
sect->align = alignment;
|
||||
@@ -364,8 +339,6 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
||||
} else if (startaddr[type] & mask) {
|
||||
error("Section \"%s\"'s alignment cannot be attained in %s\n",
|
||||
name, typeNames[type]);
|
||||
alignment = 0; /* Ignore it if it's unattainable */
|
||||
org = 0;
|
||||
} else if (alignment == 16) {
|
||||
// Treat an alignment of 16 as being fixed at address 0
|
||||
alignment = 0;
|
||||
@@ -376,15 +349,15 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
|
||||
|
||||
// Check if another section exists with the same name; merge if yes, otherwise create one
|
||||
|
||||
struct Section *sect = sect_FindSectionByName(name);
|
||||
struct Section *sect = out_FindSectionByName(name);
|
||||
|
||||
if (sect) {
|
||||
mergeSections(sect, type, org, bank, alignment, alignOffset, mod);
|
||||
} else {
|
||||
sect = createSection(name, type, org, bank, alignment, alignOffset, mod);
|
||||
// Add the new section to the list (order doesn't matter)
|
||||
sect->next = sectionList;
|
||||
sectionList = sect;
|
||||
sect->next = pSectionList;
|
||||
pSectionList = sect;
|
||||
}
|
||||
|
||||
return sect;
|
||||
@@ -404,8 +377,8 @@ static void changeSection(void)
|
||||
/*
|
||||
* Set the current section by name and type
|
||||
*/
|
||||
void sect_NewSection(char const *name, uint32_t type, uint32_t org,
|
||||
struct SectionSpec const *attribs, enum SectionModifier mod)
|
||||
void out_NewSection(char const *name, uint32_t type, uint32_t org,
|
||||
struct SectionSpec const *attribs, enum SectionModifier mod)
|
||||
{
|
||||
if (currentLoadSection)
|
||||
fatalerror("Cannot change the section within a `LOAD` block\n");
|
||||
@@ -419,38 +392,23 @@ void sect_NewSection(char const *name, uint32_t type, uint32_t org,
|
||||
|
||||
changeSection();
|
||||
curOffset = mod == SECTION_UNION ? 0 : sect->size;
|
||||
loadOffset = 0; // This is still used when checking for section size overflow!
|
||||
currentSection = sect;
|
||||
pCurrentSection = sect;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the current section by name and type
|
||||
*/
|
||||
void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org,
|
||||
struct SectionSpec const *attribs, enum SectionModifier mod)
|
||||
void out_SetLoadSection(char const *name, uint32_t type, uint32_t org,
|
||||
struct SectionSpec const *attribs,
|
||||
enum SectionModifier mod)
|
||||
{
|
||||
// Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in
|
||||
// "code" sections, whereas LOAD is restricted to them.
|
||||
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
|
||||
// your own peril! ^^
|
||||
checkcodesection();
|
||||
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (currentLoadSection)
|
||||
fatalerror("`LOAD` blocks cannot be nested\n");
|
||||
|
||||
if (currentLoadSection) {
|
||||
error("`LOAD` blocks cannot be nested\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sect_HasData(type)) {
|
||||
if (sect_HasData(type))
|
||||
error("`LOAD` blocks cannot create a ROM section\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mod == SECTION_FRAGMENT) {
|
||||
error("`LOAD FRAGMENT` is not allowed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
struct Section *sect = getSection(name, type, org, attribs, mod);
|
||||
|
||||
@@ -460,12 +418,10 @@ void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org,
|
||||
currentLoadSection = sect;
|
||||
}
|
||||
|
||||
void sect_EndLoadSection(void)
|
||||
void out_EndLoadSection(void)
|
||||
{
|
||||
if (!currentLoadSection) {
|
||||
if (!currentLoadSection)
|
||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
||||
return;
|
||||
}
|
||||
|
||||
changeSection();
|
||||
curOffset += loadOffset;
|
||||
@@ -475,7 +431,7 @@ void sect_EndLoadSection(void)
|
||||
|
||||
struct Section *sect_GetSymbolSection(void)
|
||||
{
|
||||
return currentLoadSection ? currentLoadSection : currentSection;
|
||||
return currentLoadSection ? currentLoadSection : pCurrentSection;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -493,9 +449,7 @@ uint32_t sect_GetOutputOffset(void)
|
||||
|
||||
void sect_AlignPC(uint8_t alignment, uint16_t offset)
|
||||
{
|
||||
if (!checksection())
|
||||
return;
|
||||
|
||||
checksection();
|
||||
struct Section *sect = sect_GetSymbolSection();
|
||||
uint16_t alignSize = 1 << alignment; // Size of an aligned "block"
|
||||
|
||||
@@ -518,28 +472,28 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset)
|
||||
}
|
||||
}
|
||||
|
||||
static void growSection(uint32_t growth)
|
||||
static inline void growSection(uint32_t growth)
|
||||
{
|
||||
curOffset += growth;
|
||||
if (curOffset + loadOffset > currentSection->size)
|
||||
currentSection->size = curOffset + loadOffset;
|
||||
if (curOffset + loadOffset > pCurrentSection->size)
|
||||
pCurrentSection->size = curOffset + loadOffset;
|
||||
if (currentLoadSection && curOffset > currentLoadSection->size)
|
||||
currentLoadSection->size = curOffset;
|
||||
}
|
||||
|
||||
static void writebyte(uint8_t byte)
|
||||
static inline void writebyte(uint8_t byte)
|
||||
{
|
||||
currentSection->data[sect_GetOutputOffset()] = byte;
|
||||
pCurrentSection->data[sect_GetOutputOffset()] = byte;
|
||||
growSection(1);
|
||||
}
|
||||
|
||||
static void writeword(uint16_t b)
|
||||
static inline void writeword(uint16_t b)
|
||||
{
|
||||
writebyte(b & 0xFF);
|
||||
writebyte(b >> 8);
|
||||
}
|
||||
|
||||
static void writelong(uint32_t b)
|
||||
static inline void writelong(uint32_t b)
|
||||
{
|
||||
writebyte(b & 0xFF);
|
||||
writebyte(b >> 8);
|
||||
@@ -547,26 +501,18 @@ static void writelong(uint32_t b)
|
||||
writebyte(b >> 24);
|
||||
}
|
||||
|
||||
static void createPatch(enum PatchType type, struct Expression const *expr, uint32_t pcShift)
|
||||
static inline void createPatch(enum PatchType type, struct Expression const *expr,
|
||||
uint32_t pcShift)
|
||||
{
|
||||
out_CreatePatch(type, expr, sect_GetOutputOffset(), pcShift);
|
||||
}
|
||||
|
||||
void sect_StartUnion(void)
|
||||
{
|
||||
// Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in
|
||||
// "code" sections, whereas LOAD is restricted to them.
|
||||
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
|
||||
// your own peril! ^^
|
||||
|
||||
if (!currentSection) {
|
||||
error("UNIONs must be inside a SECTION\n");
|
||||
return;
|
||||
}
|
||||
if (sect_HasData(currentSection->type)) {
|
||||
error("Cannot use UNION inside of ROM0 or ROMX sections\n");
|
||||
return;
|
||||
}
|
||||
if (!pCurrentSection)
|
||||
fatalerror("UNIONs must be inside a SECTION\n");
|
||||
if (sect_HasData(pCurrentSection->type))
|
||||
fatalerror("Cannot use UNION inside of ROM0 or ROMX sections\n");
|
||||
struct UnionStackEntry *entry = malloc(sizeof(*entry));
|
||||
|
||||
if (!entry)
|
||||
@@ -588,19 +534,15 @@ static void endUnionMember(void)
|
||||
|
||||
void sect_NextUnionMember(void)
|
||||
{
|
||||
if (!unionStack) {
|
||||
error("Found NEXTU outside of a UNION construct\n");
|
||||
return;
|
||||
}
|
||||
if (!unionStack)
|
||||
fatalerror("Found NEXTU outside of a UNION construct\n");
|
||||
endUnionMember();
|
||||
}
|
||||
|
||||
void sect_EndUnion(void)
|
||||
{
|
||||
if (!unionStack) {
|
||||
error("Found ENDU outside of a UNION construct\n");
|
||||
return;
|
||||
}
|
||||
if (!unionStack)
|
||||
fatalerror("Found ENDU outside of a UNION construct\n");
|
||||
endUnionMember();
|
||||
curOffset += unionStack->size;
|
||||
struct UnionStackEntry *next = unionStack->next;
|
||||
@@ -618,44 +560,36 @@ void sect_CheckUnionClosed(void)
|
||||
/*
|
||||
* Output an absolute byte
|
||||
*/
|
||||
void sect_AbsByte(uint8_t b)
|
||||
void out_AbsByte(uint8_t b)
|
||||
{
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(1))
|
||||
return;
|
||||
checkcodesection();
|
||||
reserveSpace(1);
|
||||
|
||||
writebyte(b);
|
||||
}
|
||||
|
||||
void sect_AbsByteGroup(uint8_t const *s, size_t length)
|
||||
void out_AbsByteGroup(uint8_t const *s, int32_t length)
|
||||
{
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(length))
|
||||
return;
|
||||
checkcodesection();
|
||||
reserveSpace(length);
|
||||
|
||||
while (length--)
|
||||
writebyte(*s++);
|
||||
}
|
||||
|
||||
void sect_AbsWordGroup(uint8_t const *s, size_t length)
|
||||
void out_AbsWordGroup(uint8_t const *s, int32_t length)
|
||||
{
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(length * 2))
|
||||
return;
|
||||
checkcodesection();
|
||||
reserveSpace(length * 2);
|
||||
|
||||
while (length--)
|
||||
writeword(*s++);
|
||||
}
|
||||
|
||||
void sect_AbsLongGroup(uint8_t const *s, size_t length)
|
||||
void out_AbsLongGroup(uint8_t const *s, int32_t length)
|
||||
{
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(length * 4))
|
||||
return;
|
||||
checkcodesection();
|
||||
reserveSpace(length * 4);
|
||||
|
||||
while (length--)
|
||||
writelong(*s++);
|
||||
@@ -664,20 +598,19 @@ void sect_AbsLongGroup(uint8_t const *s, size_t length)
|
||||
/*
|
||||
* Skip this many bytes
|
||||
*/
|
||||
void sect_Skip(uint32_t skip, bool ds)
|
||||
void out_Skip(int32_t skip, bool ds)
|
||||
{
|
||||
if (!checksection())
|
||||
return;
|
||||
if (!reserveSpace(skip))
|
||||
return;
|
||||
checksection();
|
||||
reserveSpace(skip);
|
||||
|
||||
if (!sect_HasData(currentSection->type)) {
|
||||
if (!ds && sect_HasData(pCurrentSection->type))
|
||||
warning(WARNING_EMPTY_DATA_DIRECTIVE, "%s directive without data in ROM\n",
|
||||
(skip == 4) ? "DL" : (skip == 2) ? "DW" : "DB");
|
||||
|
||||
if (!sect_HasData(pCurrentSection->type)) {
|
||||
growSection(skip);
|
||||
} else {
|
||||
if (!ds)
|
||||
warning(WARNING_EMPTY_DATA_DIRECTIVE, "%s directive without data in ROM\n",
|
||||
(skip == 4) ? "DL" : (skip == 2) ? "DW" : "DB");
|
||||
// We know we're in a code SECTION
|
||||
checkcodesection();
|
||||
while (skip--)
|
||||
writebyte(fillByte);
|
||||
}
|
||||
@@ -686,12 +619,10 @@ void sect_Skip(uint32_t skip, bool ds)
|
||||
/*
|
||||
* Output a NULL terminated string (excluding the NULL-character)
|
||||
*/
|
||||
void sect_String(char const *s)
|
||||
void out_String(char const *s)
|
||||
{
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(strlen(s)))
|
||||
return;
|
||||
checkcodesection();
|
||||
reserveSpace(strlen(s));
|
||||
|
||||
while (*s)
|
||||
writebyte(*s++);
|
||||
@@ -701,18 +632,16 @@ void sect_String(char const *s)
|
||||
* 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 out_RelByte(struct Expression *expr, uint32_t pcShift)
|
||||
{
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(1))
|
||||
return;
|
||||
checkcodesection();
|
||||
reserveSpace(1);
|
||||
|
||||
if (!rpn_isKnown(expr)) {
|
||||
createPatch(PATCHTYPE_BYTE, expr, pcShift);
|
||||
writebyte(0);
|
||||
} else {
|
||||
writebyte(expr->val);
|
||||
writebyte(expr->nVal);
|
||||
}
|
||||
rpn_Free(expr);
|
||||
}
|
||||
@@ -721,12 +650,10 @@ void sect_RelByte(struct Expression *expr, uint32_t pcShift)
|
||||
* 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 out_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
|
||||
{
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(n))
|
||||
return;
|
||||
checkcodesection();
|
||||
reserveSpace(n);
|
||||
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
struct Expression *expr = &exprs[i % size];
|
||||
@@ -735,7 +662,7 @@ void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
|
||||
createPatch(PATCHTYPE_BYTE, expr, i);
|
||||
writebyte(0);
|
||||
} else {
|
||||
writebyte(expr->val);
|
||||
writebyte(expr->nVal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,18 +674,16 @@ void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
|
||||
* 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 out_RelWord(struct Expression *expr, uint32_t pcShift)
|
||||
{
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(2))
|
||||
return;
|
||||
checkcodesection();
|
||||
reserveSpace(2);
|
||||
|
||||
if (!rpn_isKnown(expr)) {
|
||||
createPatch(PATCHTYPE_WORD, expr, pcShift);
|
||||
writeword(0);
|
||||
} else {
|
||||
writeword(expr->val);
|
||||
writeword(expr->nVal);
|
||||
}
|
||||
rpn_Free(expr);
|
||||
}
|
||||
@@ -767,18 +692,16 @@ void sect_RelWord(struct Expression *expr, uint32_t pcShift)
|
||||
* 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 out_RelLong(struct Expression *expr, uint32_t pcShift)
|
||||
{
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(2))
|
||||
return;
|
||||
checkcodesection();
|
||||
reserveSpace(2);
|
||||
|
||||
if (!rpn_isKnown(expr)) {
|
||||
createPatch(PATCHTYPE_LONG, expr, pcShift);
|
||||
writelong(0);
|
||||
} else {
|
||||
writelong(expr->val);
|
||||
writelong(expr->nVal);
|
||||
}
|
||||
rpn_Free(expr);
|
||||
}
|
||||
@@ -787,12 +710,10 @@ void sect_RelLong(struct Expression *expr, uint32_t pcShift)
|
||||
* 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 out_PCRelByte(struct Expression *expr, uint32_t pcShift)
|
||||
{
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(1))
|
||||
return;
|
||||
checkcodesection();
|
||||
reserveSpace(1);
|
||||
struct Symbol const *pc = sym_GetPC();
|
||||
|
||||
if (!rpn_IsDiffConstant(expr, pc)) {
|
||||
@@ -823,14 +744,12 @@ void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
|
||||
/*
|
||||
* Output a binary file
|
||||
*/
|
||||
void sect_BinaryFile(char const *s, int32_t startPos)
|
||||
void out_BinaryFile(char const *s, int32_t startPos)
|
||||
{
|
||||
if (startPos < 0) {
|
||||
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
||||
startPos = 0;
|
||||
}
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
|
||||
char *fullPath = NULL;
|
||||
size_t size = 0;
|
||||
@@ -841,10 +760,10 @@ void sect_BinaryFile(char const *s, int32_t startPos)
|
||||
free(fullPath);
|
||||
|
||||
if (!f) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (oGeneratedMissingIncludes) {
|
||||
if (verbose)
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", s, strerror(errno));
|
||||
failedOnMissingInclude = true;
|
||||
oFailedOnMissingInclude = true;
|
||||
return;
|
||||
}
|
||||
error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
|
||||
@@ -854,21 +773,22 @@ void sect_BinaryFile(char const *s, int32_t startPos)
|
||||
int32_t fsize = -1;
|
||||
int byte;
|
||||
|
||||
checkcodesection();
|
||||
if (fseek(f, 0, SEEK_END) != -1) {
|
||||
fsize = ftell(f);
|
||||
|
||||
if (startPos > fsize) {
|
||||
error("Specified start position is greater than length of file\n");
|
||||
goto cleanup;
|
||||
fclose(f);
|
||||
return;
|
||||
}
|
||||
|
||||
fseek(f, startPos, SEEK_SET);
|
||||
if (!reserveSpace(fsize - startPos))
|
||||
goto cleanup;
|
||||
reserveSpace(fsize - startPos);
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
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 */
|
||||
while (startPos--)
|
||||
(void)fgetc(f);
|
||||
@@ -883,11 +803,10 @@ void sect_BinaryFile(char const *s, int32_t startPos)
|
||||
if (ferror(f))
|
||||
error("Error reading INCBIN file '%s': %s\n", s, strerror(errno));
|
||||
|
||||
cleanup:
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
||||
void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
||||
{
|
||||
if (start_pos < 0) {
|
||||
error("Start position cannot be negative (%" PRId32 ")\n", start_pos);
|
||||
@@ -898,13 +817,8 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
||||
error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length);
|
||||
length = 0;
|
||||
}
|
||||
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (length == 0) /* Don't even bother with 0-byte slices */
|
||||
return;
|
||||
if (!reserveSpace(length))
|
||||
return;
|
||||
|
||||
char *fullPath = NULL;
|
||||
size_t size = 0;
|
||||
@@ -912,19 +826,22 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
||||
|
||||
if (fstk_FindFile(s, &fullPath, &size))
|
||||
f = fopen(fullPath, "rb");
|
||||
free(fullPath);
|
||||
|
||||
if (!f) {
|
||||
if (generatedMissingIncludes) {
|
||||
free(fullPath);
|
||||
if (oGeneratedMissingIncludes) {
|
||||
if (verbose)
|
||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", s, strerror(errno));
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
|
||||
oFailedOnMissingInclude = true;
|
||||
return;
|
||||
}
|
||||
error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
checkcodesection();
|
||||
reserveSpace(length);
|
||||
|
||||
int32_t fsize;
|
||||
|
||||
if (fseek(f, 0, SEEK_END) != -1) {
|
||||
@@ -932,13 +849,13 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
||||
|
||||
if (start_pos > fsize) {
|
||||
error("Specified start position is greater than length of file\n");
|
||||
goto cleanup;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((start_pos + length) > fsize) {
|
||||
error("Specified range in INCBIN is out of bounds (%" PRIu32 " + %" PRIu32
|
||||
" > %" PRIu32 ")\n", start_pos, length, fsize);
|
||||
goto cleanup;
|
||||
return;
|
||||
}
|
||||
|
||||
fseek(f, start_pos, SEEK_SET);
|
||||
@@ -951,7 +868,9 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
||||
(void)fgetc(f);
|
||||
}
|
||||
|
||||
while (length--) {
|
||||
int32_t todo = length;
|
||||
|
||||
while (todo--) {
|
||||
int byte = fgetc(f);
|
||||
|
||||
if (byte != EOF) {
|
||||
@@ -960,40 +879,32 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
||||
error("Error reading INCBIN file '%s': %s\n", s, strerror(errno));
|
||||
} else {
|
||||
error("Premature end of file (%" PRId32 " bytes left to read)\n",
|
||||
length + 1);
|
||||
todo + 1);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
fclose(f);
|
||||
free(fullPath);
|
||||
}
|
||||
|
||||
/*
|
||||
* Section stack routines
|
||||
*/
|
||||
void sect_PushSection(void)
|
||||
void out_PushSection(void)
|
||||
{
|
||||
struct SectionStackEntry *entry = malloc(sizeof(*entry));
|
||||
struct SectionStackEntry *sect = malloc(sizeof(*sect));
|
||||
|
||||
if (entry == NULL)
|
||||
if (sect == NULL)
|
||||
fatalerror("No memory for section stack: %s\n", strerror(errno));
|
||||
entry->section = currentSection;
|
||||
entry->loadSection = currentLoadSection;
|
||||
entry->scope = sym_GetCurrentSymbolScope();
|
||||
entry->offset = curOffset;
|
||||
entry->loadOffset = loadOffset;
|
||||
entry->unionStack = unionStack;
|
||||
entry->next = sectionStack;
|
||||
sectionStack = entry;
|
||||
|
||||
// Reset the section scope
|
||||
currentSection = NULL;
|
||||
currentLoadSection = NULL;
|
||||
sym_SetCurrentSymbolScope(NULL);
|
||||
unionStack = NULL;
|
||||
sect->section = pCurrentSection;
|
||||
sect->scope = sym_GetCurrentSymbolScope();
|
||||
sect->offset = curOffset;
|
||||
sect->next = sectionStack;
|
||||
sectionStack = sect;
|
||||
/* TODO: maybe set current section to NULL? */
|
||||
}
|
||||
|
||||
void sect_PopSection(void)
|
||||
void out_PopSection(void)
|
||||
{
|
||||
if (!sectionStack)
|
||||
fatalerror("No entries in the section stack\n");
|
||||
@@ -1001,35 +912,14 @@ void sect_PopSection(void)
|
||||
if (currentLoadSection)
|
||||
fatalerror("Cannot change the section within a `LOAD` block!\n");
|
||||
|
||||
struct SectionStackEntry *entry = sectionStack;
|
||||
struct SectionStackEntry *sect;
|
||||
|
||||
sect = sectionStack;
|
||||
changeSection();
|
||||
currentSection = entry->section;
|
||||
currentLoadSection = entry->loadSection;
|
||||
sym_SetCurrentSymbolScope(entry->scope);
|
||||
curOffset = entry->offset;
|
||||
loadOffset = entry->loadOffset;
|
||||
unionStack = entry->unionStack;
|
||||
pCurrentSection = sect->section;
|
||||
sym_SetCurrentSymbolScope(sect->scope);
|
||||
curOffset = sect->offset;
|
||||
|
||||
sectionStack = entry->next;
|
||||
free(entry);
|
||||
}
|
||||
|
||||
bool sect_IsSizeKnown(struct Section const NONNULL(sect))
|
||||
{
|
||||
// SECTION UNION and SECTION FRAGMENT can still grow
|
||||
if (sect->modifier != SECTION_NORMAL)
|
||||
return false;
|
||||
|
||||
// The current section (or current load section if within one) is still growing
|
||||
if (sect == currentSection || sect == currentLoadSection)
|
||||
return false;
|
||||
|
||||
// Any section on the stack is still growing
|
||||
for (struct SectionStackEntry *stack = sectionStack; stack; stack = stack->next) {
|
||||
if (stack->section && !strcmp(sect->name, stack->section->name))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
sectionStack = sect->next;
|
||||
free(sect);
|
||||
}
|
||||
|
||||
350
src/asm/symbol.c
350
src/asm/symbol.c
@@ -29,7 +29,8 @@
|
||||
#include "asm/util.h"
|
||||
#include "asm/warning.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "extern/err.h"
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "helpers.h"
|
||||
#include "version.h"
|
||||
@@ -42,6 +43,12 @@ static char savedTIME[256];
|
||||
static char savedDATE[256];
|
||||
static char savedTIMESTAMP_ISO8601_LOCAL[256];
|
||||
static char savedTIMESTAMP_ISO8601_UTC[256];
|
||||
static char savedDAY[3];
|
||||
static char savedMONTH[3];
|
||||
static char savedYEAR[20];
|
||||
static char savedHOUR[3];
|
||||
static char savedMINUTE[3];
|
||||
static char savedSECOND[3];
|
||||
static bool exportall;
|
||||
|
||||
bool sym_IsPC(struct Symbol const *sym)
|
||||
@@ -50,16 +57,16 @@ bool sym_IsPC(struct Symbol const *sym)
|
||||
}
|
||||
|
||||
struct ForEachArgs {
|
||||
void (*func)(struct Symbol *sym, void *arg);
|
||||
void (*func)(struct Symbol *symbol, void *arg);
|
||||
void *arg;
|
||||
};
|
||||
|
||||
static void forEachWrapper(void *_sym, void *_argWrapper)
|
||||
static void forEachWrapper(void *_symbol, void *_argWrapper)
|
||||
{
|
||||
struct ForEachArgs *argWrapper = _argWrapper;
|
||||
struct Symbol *sym = _sym;
|
||||
struct Symbol *symbol = _symbol;
|
||||
|
||||
argWrapper->func(sym, argWrapper->arg);
|
||||
argWrapper->func(symbol, argWrapper->arg);
|
||||
}
|
||||
|
||||
void sym_ForEach(void (*func)(struct Symbol *, void *), void *arg)
|
||||
@@ -95,6 +102,7 @@ static char const *Callback__FILE__(void)
|
||||
char const *fileName = fstk_GetFileName();
|
||||
size_t j = 1;
|
||||
|
||||
/* TODO: is there a way for a file name to be empty? */
|
||||
assert(fileName[0]);
|
||||
/* The assertion above ensures the loop runs at least once */
|
||||
for (size_t i = 0; fileName[i]; i++, j++) {
|
||||
@@ -173,32 +181,32 @@ static void updateSymbolFilename(struct Symbol *sym)
|
||||
/* If the old node was referenced, ensure the new one is */
|
||||
if (oldSrc && oldSrc->referenced && oldSrc->ID != (uint32_t)-1)
|
||||
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 if deleting it */
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new symbol by name
|
||||
*/
|
||||
static struct Symbol *createsymbol(char const *symName)
|
||||
static struct Symbol *createsymbol(char const *s)
|
||||
{
|
||||
struct Symbol *sym = malloc(sizeof(*sym));
|
||||
struct Symbol *symbol = malloc(sizeof(*symbol));
|
||||
|
||||
if (!sym)
|
||||
fatalerror("Failed to create symbol '%s': %s\n", symName, strerror(errno));
|
||||
if (!symbol)
|
||||
fatalerror("Failed to create symbol '%s': %s\n", s, strerror(errno));
|
||||
|
||||
if (snprintf(sym->name, MAXSYMLEN + 1, "%s", symName) > MAXSYMLEN)
|
||||
warning(WARNING_LONG_STR, "Symbol name is too long: '%s'\n", symName);
|
||||
if (snprintf(symbol->name, MAXSYMLEN + 1, "%s", s) > MAXSYMLEN)
|
||||
warning(WARNING_LONG_STR, "Symbol name is too long: '%s'\n", s);
|
||||
|
||||
sym->isExported = false;
|
||||
sym->isBuiltin = false;
|
||||
sym->hasCallback = false;
|
||||
sym->section = NULL;
|
||||
setSymbolFilename(sym);
|
||||
sym->ID = -1;
|
||||
sym->next = NULL;
|
||||
symbol->isExported = false;
|
||||
symbol->isBuiltin = false;
|
||||
symbol->hasCallback = false;
|
||||
symbol->section = NULL;
|
||||
setSymbolFilename(symbol);
|
||||
symbol->ID = -1;
|
||||
symbol->next = NULL;
|
||||
|
||||
hash_AddElement(symbols, sym->name, sym);
|
||||
return sym;
|
||||
hash_AddElement(symbols, symbol->name, symbol);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -220,45 +228,46 @@ static void assignStringSymbol(struct Symbol *sym, char const *value)
|
||||
{
|
||||
char *string = strdup(value);
|
||||
|
||||
if (!string)
|
||||
if (string == NULL)
|
||||
fatalerror("No memory for string equate: %s\n", strerror(errno));
|
||||
|
||||
sym->type = SYM_EQUS;
|
||||
/* TODO: use other fields */
|
||||
sym->macro = string;
|
||||
sym->macroSize = strlen(string);
|
||||
}
|
||||
|
||||
struct Symbol *sym_FindExactSymbol(char const *symName)
|
||||
struct Symbol *sym_FindExactSymbol(char const *name)
|
||||
{
|
||||
return hash_GetElement(symbols, symName);
|
||||
return hash_GetElement(symbols, name);
|
||||
}
|
||||
|
||||
struct Symbol *sym_FindUnscopedSymbol(char const *symName)
|
||||
struct Symbol *sym_FindUnscopedSymbol(char const *name)
|
||||
{
|
||||
if (strchr(symName, '.')) {
|
||||
error("Expected non-scoped symbol name, not \"%s\"\n", symName);
|
||||
if (strchr(name, '.')) {
|
||||
error("Expected non-scoped symbol name, not \"%s\"\n", name);
|
||||
return NULL;
|
||||
}
|
||||
return sym_FindExactSymbol(symName);
|
||||
return sym_FindExactSymbol(name);
|
||||
}
|
||||
|
||||
struct Symbol *sym_FindScopedSymbol(char const *symName)
|
||||
struct Symbol *sym_FindScopedSymbol(char const *name)
|
||||
{
|
||||
char const *localName = strchr(symName, '.');
|
||||
char const *dotPtr = strchr(name, '.');
|
||||
|
||||
if (localName) {
|
||||
if (strchr(localName + 1, '.'))
|
||||
if (dotPtr) {
|
||||
if (strchr(dotPtr + 1, '.'))
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n",
|
||||
symName);
|
||||
name);
|
||||
/* If auto-scoped local label, expand the name */
|
||||
if (localName == symName) { /* Meaning, the name begins with the dot */
|
||||
char fullName[MAXSYMLEN + 1];
|
||||
if (dotPtr == name) { /* Meaning, the name begins with the dot */
|
||||
char fullname[MAXSYMLEN + 1];
|
||||
|
||||
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
|
||||
return sym_FindExactSymbol(fullName);
|
||||
fullSymbolName(fullname, sizeof(fullname), name, labelScope);
|
||||
return sym_FindExactSymbol(fullname);
|
||||
}
|
||||
}
|
||||
return sym_FindExactSymbol(symName);
|
||||
return sym_FindExactSymbol(name);
|
||||
}
|
||||
|
||||
struct Symbol const *sym_GetPC(void)
|
||||
@@ -266,7 +275,7 @@ struct Symbol const *sym_GetPC(void)
|
||||
return PCSymbol;
|
||||
}
|
||||
|
||||
static bool isReferenced(struct Symbol const *sym)
|
||||
static inline bool isReferenced(struct Symbol const *sym)
|
||||
{
|
||||
return sym->ID != (uint32_t)-1;
|
||||
}
|
||||
@@ -276,26 +285,27 @@ static bool isReferenced(struct Symbol const *sym)
|
||||
*/
|
||||
void sym_Purge(char const *symName)
|
||||
{
|
||||
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
struct Symbol *symbol = sym_FindScopedSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
if (!symbol) {
|
||||
error("'%s' not defined\n", symName);
|
||||
} else if (sym->isBuiltin) {
|
||||
} else if (symbol->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be purged\n", symName);
|
||||
} else if (isReferenced(sym)) {
|
||||
} else if (isReferenced(symbol)) {
|
||||
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName);
|
||||
} else {
|
||||
/* Do not keep a reference to the label's name after purging it */
|
||||
if (sym->name == labelScope)
|
||||
sym_SetCurrentSymbolScope(NULL);
|
||||
if (symbol->name == labelScope)
|
||||
labelScope = 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 symbol->macro for SYM_EQUS and SYM_MACRO, but this can't
|
||||
* free(symbol->macro) because the expansion may be purging itself.
|
||||
*/
|
||||
hash_RemoveElement(symbols, sym->name);
|
||||
|
||||
hash_RemoveElement(symbols, symbol->name);
|
||||
/* TODO: ideally, also unref the file stack nodes */
|
||||
free(sym);
|
||||
free(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,12 +340,12 @@ uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
|
||||
/*
|
||||
* Return a constant symbol's value
|
||||
*/
|
||||
uint32_t sym_GetConstantValue(char const *symName)
|
||||
uint32_t sym_GetConstantValue(char const *s)
|
||||
{
|
||||
struct Symbol const *sym = sym_FindScopedSymbol(symName);
|
||||
struct Symbol const *sym = sym_FindScopedSymbol(s);
|
||||
|
||||
if (!sym)
|
||||
error("'%s' not defined\n", symName);
|
||||
if (sym == NULL)
|
||||
error("'%s' not defined\n", s);
|
||||
else
|
||||
return sym_GetConstantSymValue(sym);
|
||||
|
||||
@@ -356,29 +366,28 @@ void sym_SetCurrentSymbolScope(char const *newScope)
|
||||
* Create a symbol that will be non-relocatable and ensure that it
|
||||
* hasn't already been defined or referenced in a context that would
|
||||
* require that it be relocatable
|
||||
* @param symName The name of the symbol to create
|
||||
* @param symbolName The name of the symbol to create
|
||||
* @param numeric If false, the symbol may not have been referenced earlier
|
||||
*/
|
||||
static struct Symbol *createNonrelocSymbol(char const *symName, bool numeric)
|
||||
static struct Symbol *createNonrelocSymbol(char const *symbolName, bool numeric)
|
||||
{
|
||||
struct Symbol *sym = sym_FindExactSymbol(symName);
|
||||
struct Symbol *symbol = sym_FindExactSymbol(symbolName);
|
||||
|
||||
if (!sym) {
|
||||
sym = createsymbol(symName);
|
||||
} else if (sym_IsDefined(sym)) {
|
||||
error("'%s' already defined at ", symName);
|
||||
dumpFilename(sym);
|
||||
if (!symbol) {
|
||||
symbol = createsymbol(symbolName);
|
||||
} else if (sym_IsDefined(symbol)) {
|
||||
error("'%s' already defined at ", symbolName);
|
||||
dumpFilename(symbol);
|
||||
putc('\n', stderr);
|
||||
return NULL; // Don't allow overriding the symbol, that'd be bad!
|
||||
} else if (!numeric) {
|
||||
// The symbol has already been referenced, but it's not allowed
|
||||
error("'%s' already referenced at ", symName);
|
||||
dumpFilename(sym);
|
||||
error("'%s' already referenced at ", symbolName);
|
||||
dumpFilename(symbol);
|
||||
putc('\n', stderr);
|
||||
return NULL; // Don't allow overriding the symbol, that'd be bad!
|
||||
}
|
||||
|
||||
return sym;
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -397,30 +406,6 @@ struct Symbol *sym_AddEqu(char const *symName, int32_t value)
|
||||
return sym;
|
||||
}
|
||||
|
||||
struct Symbol *sym_RedefEqu(char const *symName, int32_t value)
|
||||
{
|
||||
struct Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
return sym_AddEqu(symName, value);
|
||||
|
||||
if (sym_IsDefined(sym) && sym->type != SYM_EQU) {
|
||||
error("'%s' already defined as non-EQU at ", symName);
|
||||
dumpFilename(sym);
|
||||
putc('\n', stderr);
|
||||
return NULL;
|
||||
} else if (sym->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be redefined\n", symName);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
updateSymbolFilename(sym);
|
||||
sym->type = SYM_EQU;
|
||||
sym->value = value;
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a string equated symbol.
|
||||
*
|
||||
@@ -448,42 +433,34 @@ struct Symbol *sym_RedefString(char const *symName, char const *value)
|
||||
{
|
||||
struct Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
return sym_AddString(symName, value);
|
||||
|
||||
if (sym->type != SYM_EQUS) {
|
||||
if (sym_IsDefined(sym))
|
||||
error("'%s' already defined as non-EQUS at ", symName);
|
||||
else
|
||||
error("'%s' already referenced at ", symName);
|
||||
if (!sym) {
|
||||
sym = createsymbol(symName);
|
||||
} else if (sym->type != SYM_EQUS) {
|
||||
error("'%s' already defined as non-EQUS at ", symName);
|
||||
dumpFilename(sym);
|
||||
putc('\n', stderr);
|
||||
return NULL;
|
||||
} else if (sym->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be redefined\n", symName);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
updateSymbolFilename(sym);
|
||||
/*
|
||||
* 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);
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
||||
/*
|
||||
* Alter a mutable symbol's value
|
||||
* Alter a SET symbols value
|
||||
*/
|
||||
struct Symbol *sym_AddVar(char const *symName, int32_t value)
|
||||
struct Symbol *sym_AddSet(char const *symName, int32_t value)
|
||||
{
|
||||
struct Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
if (sym == NULL) {
|
||||
sym = createsymbol(symName);
|
||||
} else if (sym_IsDefined(sym) && sym->type != SYM_VAR) {
|
||||
} else if (sym_IsDefined(sym) && sym->type != SYM_SET) {
|
||||
error("'%s' already defined as %s at ",
|
||||
symName, sym->type == SYM_LABEL ? "label" : "constant");
|
||||
dumpFilename(sym);
|
||||
@@ -493,7 +470,7 @@ struct Symbol *sym_AddVar(char const *symName, int32_t value)
|
||||
updateSymbolFilename(sym);
|
||||
}
|
||||
|
||||
sym->type = SYM_VAR;
|
||||
sym->type = SYM_SET;
|
||||
sym->value = value;
|
||||
|
||||
return sym;
|
||||
@@ -501,18 +478,18 @@ struct Symbol *sym_AddVar(char const *symName, int32_t value)
|
||||
|
||||
/*
|
||||
* Add a label (aka "relocatable symbol")
|
||||
* @param symName The label's full name (so `.name` is invalid)
|
||||
* @param name The label's full name (so `.name` is invalid)
|
||||
* @return The created symbol
|
||||
*/
|
||||
static struct Symbol *addLabel(char const *symName)
|
||||
static struct Symbol *addLabel(char const *name)
|
||||
{
|
||||
assert(symName[0] != '.'); /* The symbol name must have been expanded prior */
|
||||
struct Symbol *sym = sym_FindExactSymbol(symName);
|
||||
assert(name[0] != '.'); /* The symbol name must have been expanded prior */
|
||||
struct Symbol *sym = sym_FindExactSymbol(name);
|
||||
|
||||
if (!sym) {
|
||||
sym = createsymbol(symName);
|
||||
sym = createsymbol(name);
|
||||
} else if (sym_IsDefined(sym)) {
|
||||
error("'%s' already defined at ", symName);
|
||||
error("'%s' already defined at ", name);
|
||||
dumpFilename(sym);
|
||||
putc('\n', stderr);
|
||||
return NULL;
|
||||
@@ -527,68 +504,62 @@ static struct Symbol *addLabel(char const *symName)
|
||||
sym->section = sect_GetSymbolSection();
|
||||
|
||||
if (sym && !sym->section)
|
||||
error("Label \"%s\" created outside of a SECTION\n", symName);
|
||||
error("Label \"%s\" created outside of a SECTION\n", name);
|
||||
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 *name)
|
||||
{
|
||||
if (!labelScope) {
|
||||
error("Local label '%s' in main scope\n", symName);
|
||||
error("Local label '%s' in main scope\n", name);
|
||||
return NULL;
|
||||
}
|
||||
assert(!strchr(labelScope, '.')); /* Assuming no dots in `labelScope` */
|
||||
|
||||
char fullName[MAXSYMLEN + 1];
|
||||
char const *localName = strchr(symName, '.');
|
||||
char fullname[MAXSYMLEN + 1];
|
||||
|
||||
assert(localName); /* There should be at least one dot in `symName` */
|
||||
/* Check for something after the dot in `localName` */
|
||||
if (localName[1] == '\0') {
|
||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n",
|
||||
symName);
|
||||
}
|
||||
/* Check for more than one dot in `localName` */
|
||||
if (strchr(localName + 1, '.'))
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
|
||||
symName);
|
||||
|
||||
if (localName == symName) {
|
||||
/* Expand `symName` to the full `labelScope.symName` name */
|
||||
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
|
||||
symName = fullName;
|
||||
if (name[0] == '.') {
|
||||
/* If symbol is of the form `.name`, expand to the full `Parent.name` name */
|
||||
fullSymbolName(fullname, sizeof(fullname), name, labelScope);
|
||||
name = fullname; /* Use the expanded name instead */
|
||||
} else {
|
||||
size_t i = 0;
|
||||
|
||||
/* Find where `labelScope` and `symName` first differ */
|
||||
while (labelScope[i] && symName[i] == labelScope[i])
|
||||
/* Otherwise, check that `Parent` is in fact the current scope */
|
||||
while (labelScope[i] && name[i] == labelScope[i])
|
||||
i++;
|
||||
/* Assuming no dots in `labelScope` */
|
||||
assert(strchr(&name[i], '.')); /* There should be at least one dot, though */
|
||||
size_t parentLen = i + (strchr(&name[i], '.') - name);
|
||||
|
||||
/* Check that `symName` starts with `labelScope` and then a '.' */
|
||||
if (labelScope[i] != '\0' || symName[i] != '.') {
|
||||
size_t parentLen = localName - symName;
|
||||
|
||||
/*
|
||||
* Check that `labelScope[i]` ended the check, guaranteeing that `name` is at least
|
||||
* as long, and then that this was the entirety of the `Parent` part of `name`.
|
||||
*/
|
||||
if (labelScope[i] != '\0' || name[i] != '.') {
|
||||
assert(parentLen <= INT_MAX);
|
||||
error("Not currently in the scope of '%.*s'\n", (int)parentLen, symName);
|
||||
error("Not currently in the scope of '%.*s'\n", (int)parentLen, name);
|
||||
}
|
||||
if (strchr(&name[parentLen + 1], '.')) /* There will at least be a terminator */
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
|
||||
name);
|
||||
}
|
||||
|
||||
return addLabel(symName);
|
||||
return addLabel(name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a relocatable symbol
|
||||
*/
|
||||
struct Symbol *sym_AddLabel(char const *symName)
|
||||
struct Symbol *sym_AddLabel(char const *name)
|
||||
{
|
||||
struct Symbol *sym = addLabel(symName);
|
||||
struct Symbol *sym = addLabel(name);
|
||||
|
||||
/* Set the symbol as the new scope */
|
||||
if (sym)
|
||||
sym_SetCurrentSymbolScope(sym->name);
|
||||
labelScope = sym->name;
|
||||
return sym;
|
||||
}
|
||||
|
||||
@@ -683,9 +654,9 @@ struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body,
|
||||
*/
|
||||
struct Symbol *sym_Ref(char const *symName)
|
||||
{
|
||||
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
struct Symbol *nsym = sym_FindScopedSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
if (nsym == NULL) {
|
||||
char fullname[MAXSYMLEN + 1];
|
||||
|
||||
if (symName[0] == '.') {
|
||||
@@ -695,11 +666,11 @@ struct Symbol *sym_Ref(char const *symName)
|
||||
symName = fullname;
|
||||
}
|
||||
|
||||
sym = createsymbol(symName);
|
||||
sym->type = SYM_REF;
|
||||
nsym = createsymbol(symName);
|
||||
nsym->type = SYM_REF;
|
||||
}
|
||||
|
||||
return sym;
|
||||
return nsym;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -710,9 +681,20 @@ void sym_SetExportAll(bool set)
|
||||
exportall = set;
|
||||
}
|
||||
|
||||
static struct Symbol *createBuiltinSymbol(char const *symName)
|
||||
/**
|
||||
* Returns a pointer to the first non-zero character in a string
|
||||
* Non-'0', not non-'\0'.
|
||||
*/
|
||||
static inline char const *removeLeadingZeros(char const *ptr)
|
||||
{
|
||||
struct Symbol *sym = createsymbol(symName);
|
||||
while (*ptr == '0')
|
||||
ptr++;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static inline struct Symbol *createBuiltinSymbol(char const *name)
|
||||
{
|
||||
struct Symbol *sym = createsymbol(name);
|
||||
|
||||
sym->isBuiltin = true;
|
||||
sym->hasCallback = true;
|
||||
@@ -740,23 +722,13 @@ void sym_Init(time_t now)
|
||||
__LINE__Symbol->numCallback = Callback__LINE__;
|
||||
__FILE__Symbol->type = SYM_EQUS;
|
||||
__FILE__Symbol->strCallback = Callback__FILE__;
|
||||
sym_AddSet("_RS", 0)->isBuiltin = true;
|
||||
|
||||
sym_AddVar("_RS", 0)->isBuiltin = true;
|
||||
|
||||
#define addSym(fn, name, val) do { \
|
||||
struct Symbol *sym = fn(name, val); \
|
||||
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());
|
||||
addNumber("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR);
|
||||
addNumber("__RGBDS_MINOR__", PACKAGE_VERSION_MINOR);
|
||||
addNumber("__RGBDS_PATCH__", PACKAGE_VERSION_PATCH);
|
||||
sym_AddEqu("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR)->isBuiltin = true;
|
||||
sym_AddEqu("__RGBDS_MINOR__", PACKAGE_VERSION_MINOR)->isBuiltin = true;
|
||||
sym_AddEqu("__RGBDS_PATCH__", PACKAGE_VERSION_PATCH)->isBuiltin = true;
|
||||
#ifdef PACKAGE_VERSION_RC
|
||||
addNumber("__RGBDS_RC__", PACKAGE_VERSION_RC);
|
||||
sym_AddEqu("__RGBDS_RC__", PACKAGE_VERSION_RC)->isBuiltin = true;
|
||||
#endif
|
||||
|
||||
if (now == (time_t)-1) {
|
||||
@@ -765,6 +737,7 @@ void sym_Init(time_t now)
|
||||
now = 0;
|
||||
}
|
||||
|
||||
const struct tm *time_utc = gmtime(&now);
|
||||
const struct tm *time_local = localtime(&now);
|
||||
|
||||
strftime(savedTIME, sizeof(savedTIME), "\"%H:%M:%S\"", time_local);
|
||||
@@ -773,27 +746,40 @@ void sym_Init(time_t now)
|
||||
sizeof(savedTIMESTAMP_ISO8601_LOCAL), "\"%Y-%m-%dT%H:%M:%S%z\"",
|
||||
time_local);
|
||||
|
||||
const struct tm *time_utc = gmtime(&now);
|
||||
|
||||
strftime(savedTIMESTAMP_ISO8601_UTC,
|
||||
sizeof(savedTIMESTAMP_ISO8601_UTC), "\"%Y-%m-%dT%H:%M:%SZ\"",
|
||||
time_utc);
|
||||
|
||||
strftime(savedYEAR, sizeof(savedYEAR), "%Y", time_utc);
|
||||
strftime(savedMONTH, sizeof(savedMONTH), "%m", time_utc);
|
||||
strftime(savedDAY, sizeof(savedDAY), "%d", time_utc);
|
||||
strftime(savedHOUR, sizeof(savedHOUR), "%H", time_utc);
|
||||
strftime(savedMINUTE, sizeof(savedMINUTE), "%M", time_utc);
|
||||
strftime(savedSECOND, sizeof(savedSECOND), "%S", time_utc);
|
||||
|
||||
#define addString(name, val) sym_AddString(name, val)->isBuiltin = true
|
||||
addString("__TIME__", savedTIME);
|
||||
addString("__DATE__", savedDATE);
|
||||
addString("__ISO_8601_LOCAL__", savedTIMESTAMP_ISO8601_LOCAL);
|
||||
addString("__ISO_8601_UTC__", savedTIMESTAMP_ISO8601_UTC);
|
||||
|
||||
addNumber("__UTC_YEAR__", time_utc->tm_year + 1900);
|
||||
addNumber("__UTC_MONTH__", time_utc->tm_mon + 1);
|
||||
addNumber("__UTC_DAY__", time_utc->tm_mday);
|
||||
addNumber("__UTC_HOUR__", time_utc->tm_hour);
|
||||
addNumber("__UTC_MINUTE__", time_utc->tm_min);
|
||||
addNumber("__UTC_SECOND__", time_utc->tm_sec);
|
||||
|
||||
#undef addNumber
|
||||
/* This cannot start with zeros */
|
||||
addString("__UTC_YEAR__", savedYEAR);
|
||||
addString("__UTC_MONTH__", removeLeadingZeros(savedMONTH));
|
||||
addString("__UTC_DAY__", removeLeadingZeros(savedDAY));
|
||||
addString("__UTC_HOUR__", removeLeadingZeros(savedHOUR));
|
||||
addString("__UTC_MINUTE__", removeLeadingZeros(savedMINUTE));
|
||||
addString("__UTC_SECOND__", removeLeadingZeros(savedSECOND));
|
||||
#undef addString
|
||||
#undef addSym
|
||||
|
||||
sym_SetCurrentSymbolScope(NULL);
|
||||
anonLabelID = 0;}
|
||||
labelScope = NULL;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -15,45 +15,52 @@
|
||||
|
||||
#include "extern/utf8decoder.h"
|
||||
|
||||
char const *printChar(int c)
|
||||
/*
|
||||
* Calculate the hash value for a string.
|
||||
* Uses the djb2 algorithm (xor version).
|
||||
* http://www.cse.yorku.ca/~oz/hash.html
|
||||
*/
|
||||
uint32_t calchash(const char *s)
|
||||
{
|
||||
// "'A'" + '\0': 4 bytes
|
||||
// "'\\n'" + '\0': 5 bytes
|
||||
// "0xFF" + '\0': 5 bytes
|
||||
static char buf[5];
|
||||
uint32_t hash = 5381;
|
||||
|
||||
while (*s != 0)
|
||||
hash = (hash * 33) ^ (*s++);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
char const *print(int c)
|
||||
{
|
||||
static char buf[5]; /* '\xNN' + '\0' */
|
||||
|
||||
if (c == EOF)
|
||||
return "EOF";
|
||||
|
||||
if (isprint(c)) {
|
||||
buf[0] = '\'';
|
||||
buf[1] = c;
|
||||
buf[2] = '\'';
|
||||
buf[3] = '\0';
|
||||
buf[0] = c;
|
||||
buf[1] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
buf[0] = '\\';
|
||||
switch (c) {
|
||||
case '\n':
|
||||
buf[2] = 'n';
|
||||
buf[1] = 'n';
|
||||
break;
|
||||
case '\r':
|
||||
buf[2] = 'r';
|
||||
buf[1] = 'r';
|
||||
break;
|
||||
case '\t':
|
||||
buf[2] = 't';
|
||||
buf[1] = 't';
|
||||
break;
|
||||
|
||||
default: /* Print as hex */
|
||||
buf[0] = '0';
|
||||
buf[1] = 'x';
|
||||
snprintf(&buf[2], 3, "%02hhX", (uint8_t)c); // includes the '\0'
|
||||
sprintf(&buf[2], "%02hhx", (uint8_t)c);
|
||||
return buf;
|
||||
}
|
||||
buf[0] = '\'';
|
||||
buf[1] = '\\';
|
||||
buf[3] = '\'';
|
||||
buf[4] = '\0';
|
||||
buf[2] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -67,8 +74,7 @@ size_t readUTF8Char(uint8_t *dest, char const *src)
|
||||
if (decode(&state, &codep, src[i]) == 1)
|
||||
return 0;
|
||||
|
||||
if (dest)
|
||||
dest[i] = src[i];
|
||||
dest[i] = src[i];
|
||||
i++;
|
||||
|
||||
if (state == 0)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
@@ -18,13 +17,19 @@
|
||||
#include "asm/main.h"
|
||||
#include "asm/warning.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "extern/err.h"
|
||||
|
||||
unsigned int nbErrors = 0;
|
||||
|
||||
static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
|
||||
enum WarningState {
|
||||
WARNING_DEFAULT,
|
||||
WARNING_DISABLED,
|
||||
WARNING_ENABLED,
|
||||
WARNING_ERROR
|
||||
};
|
||||
|
||||
static enum WarningState const defaultWarnings[NB_WARNINGS] = {
|
||||
[WARNING_ASSERT] = WARNING_ENABLED,
|
||||
[WARNING_BACKWARDS_FOR] = WARNING_DISABLED,
|
||||
[WARNING_BUILTIN_ARG] = WARNING_DISABLED,
|
||||
[WARNING_CHARMAP_REDEF] = WARNING_DISABLED,
|
||||
[WARNING_DIV] = WARNING_DISABLED,
|
||||
@@ -38,17 +43,13 @@ static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
|
||||
[WARNING_OBSOLETE] = WARNING_ENABLED,
|
||||
[WARNING_SHIFT] = WARNING_DISABLED,
|
||||
[WARNING_SHIFT_AMOUNT] = WARNING_DISABLED,
|
||||
[WARNING_TRUNCATION] = WARNING_ENABLED,
|
||||
[WARNING_USER] = WARNING_ENABLED,
|
||||
|
||||
[WARNING_NUMERIC_STRING_1] = WARNING_ENABLED,
|
||||
[WARNING_NUMERIC_STRING_2] = WARNING_DISABLED,
|
||||
[WARNING_TRUNCATION_1] = WARNING_ENABLED,
|
||||
[WARNING_TRUNCATION_2] = WARNING_DISABLED,
|
||||
};
|
||||
|
||||
enum WarningState warningStates[ARRAY_SIZE(warningStates)];
|
||||
static enum WarningState warningStates[NB_WARNINGS];
|
||||
|
||||
bool warningsAreErrors; /* Set if `-Werror` was specified */
|
||||
static bool warningsAreErrors; /* Set if `-Werror` was specified */
|
||||
|
||||
static enum WarningState warningState(enum WarningID id)
|
||||
{
|
||||
@@ -69,9 +70,8 @@ static enum WarningState warningState(enum WarningID id)
|
||||
return state;
|
||||
}
|
||||
|
||||
static const char * const warningFlags[NB_WARNINGS] = {
|
||||
static char const *warningFlags[NB_WARNINGS_ALL] = {
|
||||
"assert",
|
||||
"backwards-for",
|
||||
"builtin-args",
|
||||
"charmap-redef",
|
||||
"div",
|
||||
@@ -85,82 +85,27 @@ static const char * const warningFlags[NB_WARNINGS] = {
|
||||
"obsolete",
|
||||
"shift",
|
||||
"shift-amount",
|
||||
"truncation",
|
||||
"user",
|
||||
|
||||
// Parametric warnings
|
||||
"numeric-string",
|
||||
"numeric-string",
|
||||
"truncation",
|
||||
"truncation",
|
||||
|
||||
/* Meta warnings */
|
||||
"all",
|
||||
"extra",
|
||||
"everything", /* Especially useful for testing */
|
||||
"everything" /* Especially useful for testing */
|
||||
};
|
||||
|
||||
static const struct {
|
||||
char const *name;
|
||||
uint8_t nbLevels;
|
||||
uint8_t defaultLevel;
|
||||
} paramWarnings[] = {
|
||||
{ "numeric-string", 2, 1 },
|
||||
{ "truncation", 2, 2 },
|
||||
};
|
||||
|
||||
static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state)
|
||||
{
|
||||
enum WarningID baseID = PARAM_WARNINGS_START;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) {
|
||||
uint8_t maxParam = paramWarnings[i].nbLevels;
|
||||
|
||||
if (!strcmp(paramWarnings[i].name, flag)) { // Match!
|
||||
// If making the warning an error but param is 0, set to the maximum
|
||||
// This accommodates `-Werror=flag`, but also `-Werror=flag=0`, which is
|
||||
// thus filtered out by the caller.
|
||||
// A param of 0 makes sense for disabling everything, but neither for
|
||||
// enabling nor "erroring". Use the default for those.
|
||||
if (param == 0 && state != WARNING_DISABLED) {
|
||||
param = paramWarnings[i].defaultLevel;
|
||||
} else if (param > maxParam) {
|
||||
if (param != 255) // Don't warn if already capped
|
||||
warnx("Got parameter %" PRIu8
|
||||
" for warning flag \"%s\", but the maximum is %"
|
||||
PRIu8 "; capping.\n",
|
||||
param, flag, maxParam);
|
||||
param = maxParam;
|
||||
}
|
||||
|
||||
// Set the first <param> to enabled/error, and disable the rest
|
||||
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
||||
warningStates[baseID + ofs] =
|
||||
ofs < param ? state : WARNING_DISABLED;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
baseID += maxParam;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
enum MetaWarningCommand {
|
||||
META_WARNING_DONE = NB_WARNINGS
|
||||
};
|
||||
|
||||
/* Warnings that probably indicate an error */
|
||||
static uint8_t const _wallCommands[] = {
|
||||
WARNING_BACKWARDS_FOR,
|
||||
WARNING_BUILTIN_ARG,
|
||||
WARNING_CHARMAP_REDEF,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
WARNING_EMPTY_STRRPL,
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_LONG_STR,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_NUMERIC_STRING_1,
|
||||
META_WARNING_DONE
|
||||
};
|
||||
|
||||
@@ -169,16 +114,11 @@ static uint8_t const _wextraCommands[] = {
|
||||
WARNING_EMPTY_MACRO_ARG,
|
||||
WARNING_MACRO_SHIFT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
META_WARNING_DONE
|
||||
};
|
||||
|
||||
/* Literally everything. Notably useful for testing */
|
||||
static uint8_t const _weverythingCommands[] = {
|
||||
WARNING_BACKWARDS_FOR,
|
||||
WARNING_BUILTIN_ARG,
|
||||
WARNING_DIV,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
@@ -191,10 +131,7 @@ static uint8_t const _weverythingCommands[] = {
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_SHIFT,
|
||||
WARNING_SHIFT_AMOUNT,
|
||||
WARNING_NUMERIC_STRING_1,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
/* WARNING_TRUNCATION, */
|
||||
/* WARNING_USER, */
|
||||
META_WARNING_DONE
|
||||
};
|
||||
@@ -205,33 +142,37 @@ static uint8_t const *metaWarningCommands[NB_META_WARNINGS] = {
|
||||
_weverythingCommands
|
||||
};
|
||||
|
||||
void processWarningFlag(char *flag)
|
||||
void processWarningFlag(char const *flag)
|
||||
{
|
||||
static bool setError = false;
|
||||
|
||||
/* First, try to match against a "meta" warning */
|
||||
for (enum WarningID id = META_WARNINGS_START; id < NB_WARNINGS; id++) {
|
||||
for (enum WarningID id = NB_WARNINGS; id < NB_WARNINGS_ALL; id++) {
|
||||
/* TODO: improve the matching performance? */
|
||||
if (!strcmp(flag, warningFlags[id])) {
|
||||
/* We got a match! */
|
||||
if (setError)
|
||||
errx("Cannot make meta warning \"%s\" into an error",
|
||||
errx(1, "Cannot make meta warning \"%s\" into an error",
|
||||
flag);
|
||||
|
||||
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
|
||||
*ptr != META_WARNING_DONE; ptr++) {
|
||||
uint8_t const *ptr =
|
||||
metaWarningCommands[id - NB_WARNINGS];
|
||||
|
||||
for (;;) {
|
||||
if (*ptr == META_WARNING_DONE)
|
||||
return;
|
||||
|
||||
/* Warning flag, set without override */
|
||||
if (warningStates[*ptr] == WARNING_DEFAULT)
|
||||
warningStates[*ptr] = WARNING_ENABLED;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If it's not a meta warning, specially check against `-Werror` */
|
||||
if (!strncmp(flag, "error", strlen("error"))) {
|
||||
char *errorFlag = flag + strlen("error");
|
||||
char const *errorFlag = flag + strlen("error");
|
||||
|
||||
switch (*errorFlag) {
|
||||
case '\0':
|
||||
@@ -240,7 +181,7 @@ void processWarningFlag(char *flag)
|
||||
return;
|
||||
|
||||
case '=':
|
||||
/* `-Werror=XXX` */
|
||||
/* `-Werror=XXX */
|
||||
setError = true;
|
||||
processWarningFlag(errorFlag + 1); /* Skip the `=` */
|
||||
setError = false;
|
||||
@@ -252,60 +193,14 @@ void processWarningFlag(char *flag)
|
||||
|
||||
/* Well, it's either a normal warning or a mistake */
|
||||
|
||||
/* Check if this is a negation */
|
||||
bool isNegation = !strncmp(flag, "no-", strlen("no-")) && !setError;
|
||||
char const *rootFlag = isNegation ? flag + strlen("no-") : flag;
|
||||
enum WarningState state = setError ? WARNING_ERROR :
|
||||
/* Not an error, then check if this is a negation */
|
||||
strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
|
||||
: WARNING_DISABLED;
|
||||
char const *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
|
||||
|
||||
// Is this a "parametric" warning?
|
||||
if (state != WARNING_DISABLED) { // The `no-` form cannot be parametrized
|
||||
// First, check if there is an "equals" sign followed by a decimal number
|
||||
char *equals = strchr(rootFlag, '=');
|
||||
|
||||
if (equals && equals[1] != '\0') { // Ignore an equal sign at the very end as well
|
||||
// Is the rest of the string a decimal number?
|
||||
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually
|
||||
uint8_t param = 0;
|
||||
char const *ptr = equals + 1;
|
||||
bool warned = false;
|
||||
|
||||
// The `if`'s condition above ensures that this will run at least once
|
||||
do {
|
||||
// If we don't have a digit, bail
|
||||
if (*ptr < '0' || *ptr > '9')
|
||||
break;
|
||||
// Avoid overflowing!
|
||||
if (param > UINT8_MAX - (*ptr - '0')) {
|
||||
if (!warned)
|
||||
warnx("Invalid warning flag \"%s\": capping parameter at 255\n",
|
||||
flag);
|
||||
warned = true; // Only warn once, cap always
|
||||
param = 255;
|
||||
continue;
|
||||
}
|
||||
param = param * 10 + (*ptr - '0');
|
||||
|
||||
ptr++;
|
||||
} while (*ptr);
|
||||
|
||||
// If we managed to the end of the string, check that the warning indeed
|
||||
// accepts a parameter
|
||||
if (*ptr == '\0') {
|
||||
if (setError && param == 0) {
|
||||
warnx("Ignoring nonsensical warning flag \"%s\"\n", flag);
|
||||
return;
|
||||
}
|
||||
*equals = '\0'; // Truncate the param at the '='
|
||||
if (tryProcessParamWarning(rootFlag, param,
|
||||
param == 0 ? WARNING_DISABLED : state))
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
isNegation ? WARNING_DISABLED : WARNING_ENABLED;
|
||||
|
||||
/* 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_WARNINGS; id++) {
|
||||
if (!strcmp(rootFlag, warningFlags[id])) {
|
||||
/* We got a match! */
|
||||
warningStates[id] = state;
|
||||
@@ -313,15 +208,10 @@ void processWarningFlag(char *flag)
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, this might be a "parametric" warning without an equals sign
|
||||
// If it is, treat the param as 1 if enabling, or 0 if disabling
|
||||
if (tryProcessParamWarning(rootFlag, 0, state))
|
||||
return;
|
||||
|
||||
warnx("Unknown warning `%s`", flag);
|
||||
}
|
||||
|
||||
void printDiag(char const *fmt, va_list args, char const *type,
|
||||
void printDiag(const char *fmt, va_list args, char const *type,
|
||||
char const *flagfmt, char const *flag)
|
||||
{
|
||||
fputs(type, stderr);
|
||||
@@ -331,17 +221,17 @@ void printDiag(char const *fmt, va_list args, char const *type,
|
||||
lexer_DumpStringExpansions();
|
||||
}
|
||||
|
||||
void error(char const *fmt, ...)
|
||||
void error(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
printDiag(fmt, args, "error: ", ":\n ", NULL);
|
||||
printDiag(fmt, args, "ERROR: ", ":\n ", NULL);
|
||||
va_end(args);
|
||||
nbErrors++;
|
||||
}
|
||||
|
||||
_Noreturn void fatalerror(char const *fmt, ...)
|
||||
_Noreturn void fatalerror(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
@@ -364,7 +254,7 @@ void warning(enum WarningID id, char const *fmt, ...)
|
||||
return;
|
||||
|
||||
case WARNING_ERROR:
|
||||
printDiag(fmt, args, "error: ", ": [-Werror=%s]\n ", flag);
|
||||
printDiag(fmt, args, "ERROR: ", ": [-Werror=%s]\n ", flag);
|
||||
va_end(args);
|
||||
return;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/sh
|
||||
bison -V | awk -v major="$1" -v minor="$2" '
|
||||
/^bison.*[0-9]+(\.[0-9]+)(\.[0-9]+)?$/ {
|
||||
match($0, /[0-9]+(\.[0-9]+)(\.[0-9]+)?$/);
|
||||
|
||||
87
src/error.c
87
src/error.c
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 2005-2021, Rich Felker and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "platform.h"
|
||||
|
||||
static void vwarn(char const NONNULL(fmt), va_list ap)
|
||||
{
|
||||
fprintf(stderr, "warning: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fputs(": ", stderr);
|
||||
perror(NULL);
|
||||
}
|
||||
|
||||
static void vwarnx(char const NONNULL(fmt), va_list ap)
|
||||
{
|
||||
fprintf(stderr, "warning");
|
||||
fputs(": ", stderr);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
_Noreturn static void verr(char const NONNULL(fmt), va_list ap)
|
||||
{
|
||||
fprintf(stderr, "error: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fputs(": ", stderr);
|
||||
fputs(strerror(errno), stderr);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
_Noreturn static void verrx(char const NONNULL(fmt), va_list ap)
|
||||
{
|
||||
fprintf(stderr, "error");
|
||||
fputs(": ", stderr);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
putc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void warn(char const NONNULL(fmt), ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vwarn(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void warnx(char const NONNULL(fmt), ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vwarnx(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
_Noreturn void err(char const NONNULL(fmt), ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
verr(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
_Noreturn void errx(char const NONNULL(fmt), ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
verrx(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
94
src/extern/err.c
vendored
Normal file
94
src/extern/err.c
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 2005-2018, Rich Felker and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "extern/err.h"
|
||||
|
||||
void rgbds_vwarn(const char *fmt, va_list ap)
|
||||
{
|
||||
fprintf(stderr, "warning: ");
|
||||
if (fmt) {
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fputs(": ", stderr);
|
||||
}
|
||||
perror(NULL);
|
||||
}
|
||||
|
||||
void rgbds_vwarnx(const char *fmt, va_list ap)
|
||||
{
|
||||
fprintf(stderr, "warning");
|
||||
if (fmt) {
|
||||
fputs(": ", stderr);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
}
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
_Noreturn void rgbds_verr(int status, const char *fmt, va_list ap)
|
||||
{
|
||||
fprintf(stderr, "error: ");
|
||||
if (fmt) {
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fputs(": ", stderr);
|
||||
}
|
||||
fputs(strerror(errno), stderr);
|
||||
putc('\n', stderr);
|
||||
exit(status);
|
||||
}
|
||||
|
||||
_Noreturn void rgbds_verrx(int status, const char *fmt, va_list ap)
|
||||
{
|
||||
fprintf(stderr, "error");
|
||||
if (fmt) {
|
||||
fputs(": ", stderr);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
}
|
||||
putc('\n', stderr);
|
||||
exit(status);
|
||||
}
|
||||
|
||||
void rgbds_warn(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vwarn(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void rgbds_warnx(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vwarnx(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
_Noreturn void rgbds_err(int status, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
verr(status, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
_Noreturn void rgbds_errx(int status, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
verrx(status, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
18
src/extern/getopt.c
vendored
18
src/extern/getopt.c
vendored
@@ -37,7 +37,7 @@ int musl_optind = 1, musl_opterr = 1, musl_optopt;
|
||||
int musl_optreset = 0;
|
||||
static int musl_optpos;
|
||||
|
||||
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l)
|
||||
static void musl_getopt_msg(const char *a, const char *b, const char *c, size_t l)
|
||||
{
|
||||
FILE *f = stderr;
|
||||
|
||||
@@ -47,7 +47,7 @@ static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t
|
||||
putc('\n', f);
|
||||
}
|
||||
|
||||
static int getopt(int argc, char *argv[], char const *optstring)
|
||||
static int getopt(int argc, char *argv[], const char *optstring)
|
||||
{
|
||||
int i;
|
||||
wchar_t c, d;
|
||||
@@ -140,11 +140,9 @@ static void permute(char **argv, int dest, int src)
|
||||
argv[dest] = tmp;
|
||||
}
|
||||
|
||||
static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
|
||||
const struct option *longopts, int *idx, int longonly);
|
||||
static int musl_getopt_long_core(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly);
|
||||
|
||||
static int musl_getopt_long(int argc, char **argv, char const *optstring,
|
||||
const struct option *longopts, int *idx, int longonly)
|
||||
static int musl_getopt_long(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly)
|
||||
{
|
||||
int ret, skipped, resumed;
|
||||
|
||||
@@ -180,8 +178,7 @@ static int musl_getopt_long(int argc, char **argv, char const *optstring,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
|
||||
const struct option *longopts, int *idx, int longonly)
|
||||
static int musl_getopt_long_core(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly)
|
||||
{
|
||||
musl_optarg = 0;
|
||||
if (longopts && argv[musl_optind][0] == '-' &&
|
||||
@@ -192,7 +189,7 @@ static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
|
||||
char *arg = 0, *opt, *start = argv[musl_optind] + 1;
|
||||
|
||||
for (cnt = i = 0; longopts[i].name; i++) {
|
||||
char const *name = longopts[i].name;
|
||||
const char *name = longopts[i].name;
|
||||
|
||||
opt = start;
|
||||
if (*opt == '-')
|
||||
@@ -280,8 +277,7 @@ static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
|
||||
return getopt(argc, argv, optstring);
|
||||
}
|
||||
|
||||
int musl_getopt_long_only(int argc, char **argv, char const *optstring,
|
||||
const struct option *longopts, int *idx)
|
||||
int musl_getopt_long_only(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx)
|
||||
{
|
||||
return musl_getopt_long(argc, argv, optstring, longopts, idx, 1);
|
||||
}
|
||||
|
||||
440
src/fix/main.c
440
src/fix/main.c
@@ -25,12 +25,12 @@
|
||||
#include "platform.h"
|
||||
#include "version.h"
|
||||
|
||||
#define UNSPECIFIED 0x200 // Should not be in byte range
|
||||
#define UNSPECIFIED 0x100 // May not be in byte range
|
||||
|
||||
#define BANK_SIZE 0x4000
|
||||
|
||||
/* 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:p:r:st:Vv";
|
||||
|
||||
/*
|
||||
* Equivalent long options
|
||||
@@ -52,7 +52,6 @@ static struct option const longopts[] = {
|
||||
{ "old-licensee", required_argument, NULL, 'l' },
|
||||
{ "mbc-type", required_argument, NULL, 'm' },
|
||||
{ "rom-version", required_argument, NULL, 'n' },
|
||||
{ "overwrite", no_argument, NULL, 'O' },
|
||||
{ "pad-value", required_argument, NULL, 'p' },
|
||||
{ "ram-size", required_argument, NULL, 'r' },
|
||||
{ "sgb-compatible", no_argument, NULL, 's' },
|
||||
@@ -65,7 +64,7 @@ static struct option const longopts[] = {
|
||||
static void printUsage(void)
|
||||
{
|
||||
fputs(
|
||||
"Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
"Usage: rgbfix [-jsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
" [-l <licensee_byte>] [-m <mbc_type>] [-n <rom_version>]\n"
|
||||
" [-p <pad_value>] [-r <ram_size>] [-t <title_str>] [<file> ...]\n"
|
||||
"Useful options:\n"
|
||||
@@ -80,20 +79,6 @@ static void printUsage(void)
|
||||
stderr);
|
||||
}
|
||||
|
||||
static uint8_t nbErrors;
|
||||
|
||||
static format_(printf, 1, 2) void report(char const *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (nbErrors != UINT8_MAX)
|
||||
nbErrors++;
|
||||
}
|
||||
|
||||
enum MbcType {
|
||||
ROM = 0x00,
|
||||
ROM_RAM = 0x08,
|
||||
@@ -135,28 +120,6 @@ enum MbcType {
|
||||
|
||||
HUC1_RAM_BATTERY = 0xFF,
|
||||
|
||||
// "Extended" values (still valid, but not directly actionable)
|
||||
|
||||
// A high byte of 0x01 means TPP1, the low byte is the requested features
|
||||
// This does not include SRAM, which is instead implied by a non-zero SRAM size
|
||||
// Note: Multiple rumble speeds imply rumble
|
||||
TPP1 = 0x100,
|
||||
TPP1_RUMBLE = 0x101,
|
||||
TPP1_MULTIRUMBLE = 0x102, // Should not be possible
|
||||
TPP1_MULTIRUMBLE_RUMBLE = 0x103,
|
||||
TPP1_TIMER = 0x104,
|
||||
TPP1_TIMER_RUMBLE = 0x105,
|
||||
TPP1_TIMER_MULTIRUMBLE = 0x106, // Should not be possible
|
||||
TPP1_TIMER_MULTIRUMBLE_RUMBLE = 0x107,
|
||||
TPP1_BATTERY = 0x108,
|
||||
TPP1_BATTERY_RUMBLE = 0x109,
|
||||
TPP1_BATTERY_MULTIRUMBLE = 0x10A, // Should not be possible
|
||||
TPP1_BATTERY_MULTIRUMBLE_RUMBLE = 0x10B,
|
||||
TPP1_BATTERY_TIMER = 0x10C,
|
||||
TPP1_BATTERY_TIMER_RUMBLE = 0x10D,
|
||||
TPP1_BATTERY_TIMER_MULTIRUMBLE = 0x10E, // Should not be possible
|
||||
TPP1_BATTERY_TIMER_MULTIRUMBLE_RUMBLE = 0x10F,
|
||||
|
||||
// Error values
|
||||
MBC_NONE = UNSPECIFIED, // No MBC specified, do not act on it
|
||||
MBC_BAD, // Specified MBC does not exist / syntax error
|
||||
@@ -164,33 +127,6 @@ enum MbcType {
|
||||
MBC_BAD_RANGE, // MBC number out of range
|
||||
};
|
||||
|
||||
static void printAcceptedMBCNames(void)
|
||||
{
|
||||
fputs("\tROM ($00) [aka ROM_ONLY]\n", stderr);
|
||||
fputs("\tMBC1 ($01), MBC1+RAM ($02), MBC1+RAM+BATTERY ($03)\n", stderr);
|
||||
fputs("\tMBC2 ($05), MBC2+BATTERY ($06)\n", stderr);
|
||||
fputs("\tROM+RAM ($08) [deprecated], ROM+RAM+BATTERY ($09) [deprecated]\n", stderr);
|
||||
fputs("\tMMM01 ($0B), MMM01+RAM ($0C), MMM01+RAM+BATTERY ($0D)\n", stderr);
|
||||
fputs("\tMBC3+TIMER+BATTERY ($0F), MBC3+TIMER+RAM+BATTERY ($10)\n", stderr);
|
||||
fputs("\tMBC3 ($11), MBC3+RAM ($12), MBC3+RAM+BATTERY ($13)\n", stderr);
|
||||
fputs("\tMBC5 ($19), MBC5+RAM ($1A), MBC5+RAM+BATTERY ($1B)\n", stderr);
|
||||
fputs("\tMBC5+RUMBLE ($1C), MBC5+RUMBLE+RAM ($1D), MBC5+RUMBLE+RAM+BATTERY ($1E)\n", stderr);
|
||||
fputs("\tMBC6 ($20)\n", stderr);
|
||||
fputs("\tMBC7+SENSOR+RUMBLE+RAM+BATTERY ($22)\n", stderr);
|
||||
fputs("\tPOCKET_CAMERA ($FC)\n", stderr);
|
||||
fputs("\tBANDAI_TAMA5 ($FD)\n", stderr);
|
||||
fputs("\tHUC3 ($FE)\n", stderr);
|
||||
fputs("\tHUC1+RAM+BATTERY ($FF)\n", stderr);
|
||||
|
||||
fputs("\n\tTPP1_1.0, TPP1_1.0+RUMBLE, TPP1_1.0+MULTIRUMBLE, TPP1_1.0+TIMER,\n", stderr);
|
||||
fputs("\tTPP1_1.0+TIMER+RUMBLE, TPP1_1.0+TIMER+MULTIRUMBLE, TPP1_1.0+BATTERY,\n", stderr);
|
||||
fputs("\tTPP1_1.0+BATTERY+RUMBLE, TPP1_1.0+BATTERY+MULTIRUMBLE,\n", stderr);
|
||||
fputs("\tTPP1_1.0+BATTERY+TIMER, TPP1_1.0+BATTERY+TIMER+RUMBLE,\n", stderr);
|
||||
fputs("\tTPP1_1.0+BATTERY+TIMER+MULTIRUMBLE\n", stderr);
|
||||
}
|
||||
|
||||
static uint8_t tpp1Rev[2];
|
||||
|
||||
/**
|
||||
* @return False on failure
|
||||
*/
|
||||
@@ -215,22 +151,10 @@ static bool readMBCSlice(char const **name, char const *expected)
|
||||
|
||||
static enum MbcType parseMBC(char const *name)
|
||||
{
|
||||
if (!strcasecmp(name, "help")) {
|
||||
fputs("Accepted MBC names:\n", stderr);
|
||||
printAcceptedMBCNames();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ((name[0] >= '0' && name[0] <= '9') || name[0] == '$') {
|
||||
int base = 0;
|
||||
|
||||
if (name[0] == '$') {
|
||||
name++;
|
||||
base = 16;
|
||||
}
|
||||
if (name[0] >= '0' && name[0] <= '9') {
|
||||
// Parse number, and return it as-is (unless it's too large)
|
||||
char *endptr;
|
||||
unsigned long mbc = strtoul(name, &endptr, base);
|
||||
unsigned long mbc = strtoul(name, &endptr, 0);
|
||||
|
||||
if (*endptr)
|
||||
return MBC_BAD;
|
||||
@@ -324,50 +248,10 @@ do { \
|
||||
mbc = BANDAI_TAMA5;
|
||||
break;
|
||||
|
||||
case 'T': // TAMA5 / TPP1
|
||||
case 'T': // TAMA5
|
||||
case 't':
|
||||
switch (*ptr++) {
|
||||
case 'A':
|
||||
tryReadSlice("MA5");
|
||||
mbc = BANDAI_TAMA5;
|
||||
break;
|
||||
case 'P':
|
||||
tryReadSlice("P1");
|
||||
// Parse version
|
||||
while (*ptr == ' ' || *ptr == '_')
|
||||
ptr++;
|
||||
// Major
|
||||
char *endptr;
|
||||
unsigned long val = strtoul(ptr, &endptr, 10);
|
||||
|
||||
if (endptr == ptr) {
|
||||
report("error: Failed to parse TPP1 major revision number\n");
|
||||
return MBC_BAD;
|
||||
}
|
||||
ptr = endptr;
|
||||
if (val != 1) {
|
||||
report("error: RGBFIX only supports TPP1 versions 1.0\n");
|
||||
return MBC_BAD;
|
||||
}
|
||||
tpp1Rev[0] = val;
|
||||
tryReadSlice(".");
|
||||
// Minor
|
||||
val = strtoul(ptr, &endptr, 10);
|
||||
if (endptr == ptr) {
|
||||
report("error: Failed to parse TPP1 minor revision number\n");
|
||||
return MBC_BAD;
|
||||
}
|
||||
ptr = endptr;
|
||||
if (val > 0xFF) {
|
||||
report("error: TPP1 minor revision number must be 8-bit\n");
|
||||
return MBC_BAD;
|
||||
}
|
||||
tpp1Rev[1] = val;
|
||||
mbc = TPP1;
|
||||
break;
|
||||
default:
|
||||
return MBC_BAD;
|
||||
}
|
||||
tryReadSlice("AMA5");
|
||||
mbc = BANDAI_TAMA5;
|
||||
break;
|
||||
|
||||
case 'H': // HuC{1, 3}
|
||||
@@ -396,7 +280,6 @@ do { \
|
||||
#define TIMER 0x20
|
||||
#define RUMBLE 0x10
|
||||
#define SENSOR 0x08
|
||||
#define MULTIRUMBLE 0x04
|
||||
|
||||
for (;;) {
|
||||
// Trim off trailing whitespace
|
||||
@@ -420,12 +303,6 @@ do { \
|
||||
features |= BATTERY;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
case 'm':
|
||||
tryReadSlice("ULTIRUMBLE");
|
||||
features |= MULTIRUMBLE;
|
||||
break;
|
||||
|
||||
case 'R': // RAM or RUMBLE
|
||||
case 'r':
|
||||
switch (*ptr++) {
|
||||
@@ -494,9 +371,7 @@ do { \
|
||||
|
||||
case MBC3:
|
||||
// Handle timer, which also requires battery
|
||||
if (features & TIMER) {
|
||||
if (!(features & BATTERY))
|
||||
fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n");
|
||||
if (features & (TIMER & BATTERY)) {
|
||||
features &= ~(TIMER | BATTERY); // Reset those bits
|
||||
mbc = MBC3_TIMER_BATTERY;
|
||||
// RAM is handled below
|
||||
@@ -549,22 +424,6 @@ do { \
|
||||
if (features != (RAM | BATTERY)) // HuC1 expects RAM+BATTERY
|
||||
return MBC_WRONG_FEATURES;
|
||||
break;
|
||||
|
||||
case TPP1:
|
||||
if (features & RAM)
|
||||
fprintf(stderr,
|
||||
"warning: TPP1 requests RAM implicitly if given a non-zero RAM size");
|
||||
if (features & BATTERY)
|
||||
mbc |= 0x08;
|
||||
if (features & TIMER)
|
||||
mbc |= 0x04;
|
||||
if (features & MULTIRUMBLE)
|
||||
mbc |= 0x03; // Also set the rumble flag
|
||||
if (features & RUMBLE)
|
||||
mbc |= 0x01;
|
||||
if (features & SENSOR)
|
||||
return MBC_WRONG_FEATURES;
|
||||
break;
|
||||
}
|
||||
|
||||
// Trim off trailing whitespace
|
||||
@@ -638,34 +497,6 @@ static char const *mbcName(enum MbcType type)
|
||||
return "HUC3";
|
||||
case HUC1_RAM_BATTERY:
|
||||
return "HUC1+RAM+BATTERY";
|
||||
case TPP1:
|
||||
return "TPP1";
|
||||
case TPP1_RUMBLE:
|
||||
return "TPP1+RUMBLE";
|
||||
case TPP1_MULTIRUMBLE:
|
||||
case TPP1_MULTIRUMBLE_RUMBLE:
|
||||
return "TPP1+MULTIRUMBLE";
|
||||
case TPP1_TIMER:
|
||||
return "TPP1+TIMER";
|
||||
case TPP1_TIMER_RUMBLE:
|
||||
return "TPP1+TIMER+RUMBLE";
|
||||
case TPP1_TIMER_MULTIRUMBLE:
|
||||
case TPP1_TIMER_MULTIRUMBLE_RUMBLE:
|
||||
return "TPP1+TIMER+MULTIRUMBLE";
|
||||
case TPP1_BATTERY:
|
||||
return "TPP1+BATTERY";
|
||||
case TPP1_BATTERY_RUMBLE:
|
||||
return "TPP1+BATTERY+RUMBLE";
|
||||
case TPP1_BATTERY_MULTIRUMBLE:
|
||||
case TPP1_BATTERY_MULTIRUMBLE_RUMBLE:
|
||||
return "TPP1+BATTERY+MULTIRUMBLE";
|
||||
case TPP1_BATTERY_TIMER:
|
||||
return "TPP1+BATTERY+TIMER";
|
||||
case TPP1_BATTERY_TIMER_RUMBLE:
|
||||
return "TPP1+BATTERY+TIMER+RUMBLE";
|
||||
case TPP1_BATTERY_TIMER_MULTIRUMBLE:
|
||||
case TPP1_BATTERY_TIMER_MULTIRUMBLE_RUMBLE:
|
||||
return "TPP1+BATTERY+TIMER+MULTIRUMBLE";
|
||||
|
||||
// Error values
|
||||
case MBC_NONE:
|
||||
@@ -716,30 +547,25 @@ static bool hasRAM(enum MbcType type)
|
||||
case HUC3:
|
||||
case HUC1_RAM_BATTERY:
|
||||
return true;
|
||||
|
||||
// TPP1 may or may not have RAM, don't call this function for it
|
||||
case TPP1:
|
||||
case TPP1_RUMBLE:
|
||||
case TPP1_MULTIRUMBLE:
|
||||
case TPP1_MULTIRUMBLE_RUMBLE:
|
||||
case TPP1_TIMER:
|
||||
case TPP1_TIMER_RUMBLE:
|
||||
case TPP1_TIMER_MULTIRUMBLE:
|
||||
case TPP1_TIMER_MULTIRUMBLE_RUMBLE:
|
||||
case TPP1_BATTERY:
|
||||
case TPP1_BATTERY_RUMBLE:
|
||||
case TPP1_BATTERY_MULTIRUMBLE:
|
||||
case TPP1_BATTERY_MULTIRUMBLE_RUMBLE:
|
||||
case TPP1_BATTERY_TIMER:
|
||||
case TPP1_BATTERY_TIMER_RUMBLE:
|
||||
case TPP1_BATTERY_TIMER_MULTIRUMBLE:
|
||||
case TPP1_BATTERY_TIMER_MULTIRUMBLE_RUMBLE:
|
||||
break;
|
||||
}
|
||||
|
||||
unreachable_();
|
||||
}
|
||||
|
||||
static uint8_t nbErrors;
|
||||
|
||||
static format_(printf, 1, 2) void report(char const *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (nbErrors != UINT8_MAX)
|
||||
nbErrors++;
|
||||
}
|
||||
|
||||
static const uint8_t ninLogo[] = {
|
||||
0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B,
|
||||
0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D,
|
||||
@@ -749,15 +575,6 @@ static const uint8_t ninLogo[] = {
|
||||
0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E
|
||||
};
|
||||
|
||||
static const uint8_t trashLogo[] = {
|
||||
0xFF^0xCE, 0xFF^0xED, 0xFF^0x66, 0xFF^0x66, 0xFF^0xCC, 0xFF^0x0D, 0xFF^0x00, 0xFF^0x0B,
|
||||
0xFF^0x03, 0xFF^0x73, 0xFF^0x00, 0xFF^0x83, 0xFF^0x00, 0xFF^0x0C, 0xFF^0x00, 0xFF^0x0D,
|
||||
0xFF^0x00, 0xFF^0x08, 0xFF^0x11, 0xFF^0x1F, 0xFF^0x88, 0xFF^0x89, 0xFF^0x00, 0xFF^0x0E,
|
||||
0xFF^0xDC, 0xFF^0xCC, 0xFF^0x6E, 0xFF^0xE6, 0xFF^0xDD, 0xFF^0xDD, 0xFF^0xD9, 0xFF^0x99,
|
||||
0xFF^0xBB, 0xFF^0xBB, 0xFF^0x67, 0xFF^0x63, 0xFF^0x6E, 0xFF^0x0E, 0xFF^0xEC, 0xFF^0xCC,
|
||||
0xFF^0xDD, 0xFF^0xDC, 0xFF^0x99, 0xFF^0x9F, 0xFF^0xBB, 0xFF^0xB9, 0xFF^0x33, 0xFF^0x3E
|
||||
};
|
||||
|
||||
static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone
|
||||
#define FIX_LOGO 0x80
|
||||
#define TRASH_LOGO 0x40
|
||||
@@ -774,7 +591,6 @@ static uint8_t newLicenseeLen;
|
||||
static uint16_t oldLicensee = UNSPECIFIED;
|
||||
static enum MbcType cartridgeType = MBC_NONE;
|
||||
static uint16_t romVersion = UNSPECIFIED;
|
||||
static bool overwriteRom = false; // If false, warn when overwriting non-zero non-identical bytes
|
||||
static uint16_t padValue = UNSPECIFIED;
|
||||
static uint16_t ramSize = UNSPECIFIED;
|
||||
static bool sgb = false; // If false, SGB flags are left alone
|
||||
@@ -783,7 +599,7 @@ static uint8_t titleLen;
|
||||
|
||||
static uint8_t maxTitleLen(void)
|
||||
{
|
||||
return gameID ? 11 : model != DMG ? 15 : 16;
|
||||
return gameID ? 11 : model != DMG ? 15 : 16;
|
||||
}
|
||||
|
||||
static ssize_t readBytes(int fd, uint8_t *buf, size_t len)
|
||||
@@ -837,47 +653,6 @@ static ssize_t writeBytes(int fd, void *buf, size_t len)
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rom0 A pointer to rom0
|
||||
* @param addr What address to check
|
||||
* @param fixedByte The fixed byte at the address
|
||||
* @param areaName Name to be displayed in the warning message
|
||||
*/
|
||||
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName)
|
||||
{
|
||||
uint8_t origByte = rom0[addr];
|
||||
|
||||
if (!overwriteRom && origByte != 0 && origByte != fixedByte)
|
||||
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
|
||||
|
||||
rom0[addr] = fixedByte;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rom0 A pointer to rom0
|
||||
* @param startAddr What address to begin checking from
|
||||
* @param fixed The fixed bytes at the address
|
||||
* @param size How many bytes to check
|
||||
* @param areaName Name to be displayed in the warning message
|
||||
*/
|
||||
static void overwriteBytes(uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size,
|
||||
char const *areaName)
|
||||
{
|
||||
if (!overwriteRom) {
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
uint8_t origByte = rom0[i + startAddr];
|
||||
|
||||
if (origByte != 0 && origByte != fixed[i]) {
|
||||
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n",
|
||||
areaName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(&rom0[startAddr], fixed, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param input File descriptor to be used for reading
|
||||
* @param output File descriptor to be used for writing, may be equal to `input`
|
||||
@@ -894,82 +669,56 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
|
||||
uint8_t rom0[BANK_SIZE];
|
||||
ssize_t rom0Len = readBytes(input, rom0, sizeof(rom0));
|
||||
// Also used as how many bytes to write back when fixing in-place
|
||||
ssize_t headerSize = (cartridgeType & 0xFF00) == TPP1 ? 0x154 : 0x150;
|
||||
|
||||
if (rom0Len == -1) {
|
||||
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
|
||||
return;
|
||||
} else if (rom0Len < headerSize) {
|
||||
report("FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
||||
name, (intmax_t)headerSize, (intmax_t)headerSize, (intmax_t)rom0Len);
|
||||
} else if (rom0Len < 0x150) {
|
||||
report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %ld\n",
|
||||
name, rom0Len);
|
||||
return;
|
||||
}
|
||||
// Accept partial reads if the file contains at least the header
|
||||
|
||||
if (fixSpec & (FIX_LOGO | TRASH_LOGO)) {
|
||||
if (fixSpec & FIX_LOGO)
|
||||
overwriteBytes(rom0, 0x0104, ninLogo, sizeof(ninLogo), "Nintendo logo");
|
||||
else
|
||||
overwriteBytes(rom0, 0x0104, trashLogo, sizeof(trashLogo), "Nintendo logo");
|
||||
if (fixSpec & FIX_LOGO) {
|
||||
memcpy(&rom0[0x104], ninLogo, sizeof(ninLogo));
|
||||
} else {
|
||||
for (uint8_t i = 0; i < sizeof(ninLogo); i++)
|
||||
rom0[i + 0x104] = ~ninLogo[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (title)
|
||||
overwriteBytes(rom0, 0x134, (uint8_t const *)title, titleLen, "title");
|
||||
memcpy(&rom0[0x134], title, titleLen);
|
||||
|
||||
if (gameID)
|
||||
overwriteBytes(rom0, 0x13F, (uint8_t const *)gameID, gameIDLen, "manufacturer code");
|
||||
memcpy(&rom0[0x13f], gameID, gameIDLen);
|
||||
|
||||
if (model != DMG)
|
||||
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
|
||||
rom0[0x143] = model == BOTH ? 0x80 : 0xc0;
|
||||
|
||||
if (newLicensee)
|
||||
overwriteBytes(rom0, 0x144, (uint8_t const *)newLicensee, newLicenseeLen,
|
||||
"new licensee code");
|
||||
memcpy(&rom0[0x144], newLicensee, newLicenseeLen);
|
||||
|
||||
if (sgb)
|
||||
overwriteByte(rom0, 0x146, 0x03, "SGB flag");
|
||||
rom0[0x146] = 0x03;
|
||||
|
||||
// If a valid MBC was specified...
|
||||
if (cartridgeType < MBC_NONE) {
|
||||
uint8_t byte = cartridgeType;
|
||||
if (cartridgeType < MBC_NONE)
|
||||
rom0[0x147] = cartridgeType;
|
||||
|
||||
if ((cartridgeType & 0xFF00) == TPP1) {
|
||||
// Cartridge type isn't directly actionable, translate it
|
||||
byte = 0xBC;
|
||||
// The other TPP1 identification bytes will be written below
|
||||
}
|
||||
overwriteByte(rom0, 0x147, byte, "cartridge type");
|
||||
}
|
||||
if (ramSize != UNSPECIFIED)
|
||||
rom0[0x149] = ramSize;
|
||||
|
||||
// ROM size will be written last, after evaluating the file's size
|
||||
|
||||
if ((cartridgeType & 0xFF00) == TPP1) {
|
||||
uint8_t const tpp1Code[2] = {0xC1, 0x65};
|
||||
|
||||
overwriteBytes(rom0, 0x149, tpp1Code, sizeof(tpp1Code), "TPP1 identification code");
|
||||
|
||||
overwriteBytes(rom0, 0x150, tpp1Rev, sizeof(tpp1Rev), "TPP1 revision number");
|
||||
|
||||
if (ramSize != UNSPECIFIED)
|
||||
overwriteByte(rom0, 0x152, ramSize, "RAM size");
|
||||
|
||||
overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags");
|
||||
} else {
|
||||
// Regular mappers
|
||||
|
||||
if (ramSize != UNSPECIFIED)
|
||||
overwriteByte(rom0, 0x149, ramSize, "RAM size");
|
||||
|
||||
if (!japanese)
|
||||
overwriteByte(rom0, 0x14A, 0x01, "destination code");
|
||||
}
|
||||
if (!japanese)
|
||||
rom0[0x14a] = 0x01;
|
||||
|
||||
if (oldLicensee != UNSPECIFIED)
|
||||
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
|
||||
rom0[0x14b] = oldLicensee;
|
||||
|
||||
if (romVersion != UNSPECIFIED)
|
||||
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
|
||||
rom0[0x14c] = romVersion;
|
||||
|
||||
// Remain to be handled the ROM size, and header checksum.
|
||||
// The latter depends on the former, and so will be handled after it.
|
||||
@@ -1016,7 +765,8 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
static_assert(0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS");
|
||||
if (nbBanks == 0x10000) {
|
||||
report("FATAL: \"%s\" has more than 65536 banks\n", name);
|
||||
goto cleanup;
|
||||
free(romx);
|
||||
return;
|
||||
}
|
||||
nbBanks++;
|
||||
|
||||
@@ -1066,19 +816,16 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (fixSpec & (FIX_HEADER_SUM | TRASH_HEADER_SUM)) {
|
||||
uint8_t sum = 0;
|
||||
|
||||
for (uint16_t i = 0x134; i < 0x14D; i++)
|
||||
for (uint16_t i = 0x134; i < 0x14d; i++)
|
||||
sum -= rom0[i] + 1;
|
||||
|
||||
overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum,
|
||||
"header checksum");
|
||||
rom0[0x14d] = fixSpec & TRASH_HEADER_SUM ? ~sum : sum;
|
||||
}
|
||||
|
||||
if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
|
||||
// Computation of the global checksum does not include the checksum bytes
|
||||
assert(rom0Len >= 0x14E);
|
||||
for (uint16_t i = 0; i < 0x14E; i++)
|
||||
globalSum += rom0[i];
|
||||
for (uint16_t i = 0x150; i < rom0Len; i++)
|
||||
// Computation of the global checksum assumes 0s being stored in its place
|
||||
rom0[0x14e] = 0;
|
||||
rom0[0x14f] = 0;
|
||||
for (uint16_t i = 0; i < rom0Len; i++)
|
||||
globalSum += rom0[i];
|
||||
// Pipes have already read ROMX and updated globalSum, but not regular files
|
||||
if (input == output) {
|
||||
@@ -1094,10 +841,8 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
|
||||
if (fixSpec & TRASH_GLOBAL_SUM)
|
||||
globalSum = ~globalSum;
|
||||
|
||||
uint8_t bytes[2] = {globalSum >> 8, globalSum & 0xFF};
|
||||
|
||||
overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
|
||||
rom0[0x14e] = globalSum >> 8;
|
||||
rom0[0x14f] = globalSum & 0xff;
|
||||
}
|
||||
|
||||
// In case the output depends on the input, reset to the beginning of the file, and only
|
||||
@@ -1105,22 +850,22 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_SET) == (off_t)-1) {
|
||||
report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
|
||||
goto cleanup;
|
||||
goto free_romx;
|
||||
}
|
||||
// If modifying the file in-place, we only need to edit the header
|
||||
// However, padding may have modified ROM0 (added padding), so don't in that case
|
||||
if (padValue == UNSPECIFIED)
|
||||
rom0Len = headerSize;
|
||||
rom0Len = 0x150;
|
||||
}
|
||||
ssize_t writeLen = writeBytes(output, rom0, rom0Len);
|
||||
|
||||
if (writeLen == -1) {
|
||||
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
|
||||
goto cleanup;
|
||||
goto free_romx;
|
||||
} else if (writeLen < rom0Len) {
|
||||
report("FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
||||
(intmax_t)writeLen, name, (intmax_t)rom0Len);
|
||||
goto cleanup;
|
||||
report("FATAL: Could only write %ld of \"%s\"'s %ld ROM0 bytes\n",
|
||||
writeLen, name, rom0Len);
|
||||
goto free_romx;
|
||||
}
|
||||
|
||||
// Output ROMX if it was buffered
|
||||
@@ -1130,11 +875,11 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
writeLen = writeBytes(output, romx, totalRomxLen);
|
||||
if (writeLen == -1) {
|
||||
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
||||
goto cleanup;
|
||||
goto free_romx;
|
||||
} else if ((size_t)writeLen < totalRomxLen) {
|
||||
report("FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
||||
(intmax_t)writeLen, name, totalRomxLen);
|
||||
goto cleanup;
|
||||
report("FATAL: Could only write %ld of \"%s\"'s %ld ROMX bytes\n",
|
||||
writeLen, name, totalRomxLen);
|
||||
goto free_romx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1144,7 +889,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (lseek(output, 0, SEEK_END) == (off_t)-1) {
|
||||
report("FATAL: Failed to seek to end of \"%s\": %s\n",
|
||||
name, strerror(errno));
|
||||
goto cleanup;
|
||||
goto free_romx;
|
||||
}
|
||||
}
|
||||
memset(bank, padValue, sizeof(bank));
|
||||
@@ -1166,10 +911,12 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
free_romx:
|
||||
free(romx);
|
||||
}
|
||||
|
||||
#undef trySeek
|
||||
|
||||
static bool processFilename(char const *name)
|
||||
{
|
||||
nbErrors = 0;
|
||||
@@ -1191,7 +938,7 @@ static bool processFilename(char const *name)
|
||||
if (input == -1) {
|
||||
report("FATAL: Failed to open \"%s\" for reading+writing: %s\n",
|
||||
name, strerror(errno));
|
||||
goto finish;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (fstat(input, &stat) == -1) {
|
||||
@@ -1202,16 +949,16 @@ static bool processFilename(char const *name)
|
||||
} else if (stat.st_size < 0x150) {
|
||||
// This check is in theory redundant with the one in `processFile`, but it
|
||||
// prevents passing a file size of 0, which usually indicates pipes
|
||||
report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n",
|
||||
name, (intmax_t)stat.st_size);
|
||||
report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %ld\n",
|
||||
name, stat.st_size);
|
||||
} else {
|
||||
processFile(input, input, name, stat.st_size);
|
||||
}
|
||||
|
||||
close(input);
|
||||
}
|
||||
finish:
|
||||
if (nbErrors)
|
||||
fail:
|
||||
fprintf(stderr, "Fixing \"%s\" failed with %u error%s\n",
|
||||
name, nbErrors, nbErrors == 1 ? "" : "s");
|
||||
return nbErrors;
|
||||
@@ -1220,7 +967,7 @@ finish:
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
nbErrors = 0;
|
||||
int ch;
|
||||
char ch;
|
||||
|
||||
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
|
||||
switch (ch) {
|
||||
@@ -1268,38 +1015,38 @@ do { \
|
||||
#define SPEC_H TRASH_HEADER_SUM
|
||||
#define SPEC_g FIX_GLOBAL_SUM
|
||||
#define SPEC_G TRASH_GLOBAL_SUM
|
||||
#define overrideSpec(cur, bad) \
|
||||
#define or(new, bad) \
|
||||
do { \
|
||||
if (fixSpec & SPEC_##bad) \
|
||||
fprintf(stderr, \
|
||||
"warning: '" #cur "' overriding '" #bad "' in fix spec\n"); \
|
||||
fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##cur; \
|
||||
"warning: '" #new "' overriding '" #bad "' in fix spec\n"); \
|
||||
fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##new; \
|
||||
} while (0)
|
||||
case 'l':
|
||||
overrideSpec(l, L);
|
||||
or(l, L);
|
||||
break;
|
||||
case 'L':
|
||||
overrideSpec(L, l);
|
||||
or(L, l);
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
overrideSpec(h, H);
|
||||
or(h, H);
|
||||
break;
|
||||
case 'H':
|
||||
overrideSpec(H, h);
|
||||
or(H, h);
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
overrideSpec(g, G);
|
||||
or(g, G);
|
||||
break;
|
||||
case 'G':
|
||||
overrideSpec(G, g);
|
||||
or(G, g);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "warning: Ignoring '%c' in fix spec\n",
|
||||
*musl_optarg);
|
||||
#undef overrideSpec
|
||||
#undef or
|
||||
}
|
||||
musl_optarg++;
|
||||
}
|
||||
@@ -1344,13 +1091,10 @@ do { \
|
||||
case 'm':
|
||||
cartridgeType = parseMBC(musl_optarg);
|
||||
if (cartridgeType == MBC_BAD) {
|
||||
report("error: Unknown MBC \"%s\"\nAccepted MBC names:\n",
|
||||
musl_optarg);
|
||||
printAcceptedMBCNames();
|
||||
report("error: Unknown MBC \"%s\"\n", musl_optarg);
|
||||
} else if (cartridgeType == MBC_WRONG_FEATURES) {
|
||||
report("error: Features incompatible with MBC (\"%s\")\nAccepted combinations:\n",
|
||||
report("error: Features incompatible with MBC (\"%s\")\n",
|
||||
musl_optarg);
|
||||
printAcceptedMBCNames();
|
||||
} else if (cartridgeType == MBC_BAD_RANGE) {
|
||||
report("error: Specified MBC ID out of range 0-255: %s\n",
|
||||
musl_optarg);
|
||||
@@ -1363,10 +1107,6 @@ do { \
|
||||
parseByte(romVersion, "n");
|
||||
break;
|
||||
|
||||
case 'O':
|
||||
overwriteRom = true;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
parseByte(padValue, "p");
|
||||
break;
|
||||
@@ -1408,11 +1148,7 @@ do { \
|
||||
#undef parseByte
|
||||
}
|
||||
|
||||
if ((cartridgeType & 0xFF00) == TPP1 && !japanese)
|
||||
fprintf(stderr, "warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n");
|
||||
|
||||
// Check that RAM size is correct for "standard" mappers
|
||||
if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) {
|
||||
if (ramSize != UNSPECIFIED && cartridgeType < UNSPECIFIED) {
|
||||
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
|
||||
if (ramSize != 1)
|
||||
fprintf(stderr, "warning: MBC \"%s\" should have 2kiB of RAM (-r 1)\n",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
.Nd Game Boy header utility and checksum fixer
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl jOsVv
|
||||
.Op Fl jsVv
|
||||
.Op Fl C | c
|
||||
.Op Fl f Ar fix_spec
|
||||
.Op Fl i Ar game_id
|
||||
@@ -112,21 +112,11 @@ This value is deprecated and should be set to 0x33 in all new software.
|
||||
Set the MBC type
|
||||
.Pq Ad 0x147
|
||||
to a given value from 0 to 0xFF.
|
||||
.Pp
|
||||
This value may also be an MBC name.
|
||||
The list of accepted names can be obtained by passing
|
||||
.Ql Cm help
|
||||
as the argument.
|
||||
Any amount of whitespace (space and tabs) is allowed around plus signs, and the order of "components" is free, as long as the MBC name is first.
|
||||
There are special considerations to take for the TPP1 mapper; see the
|
||||
.Sx TPP1
|
||||
section below.
|
||||
This value may also be an MBC name from the Pan Docs.
|
||||
.It Fl n Ar rom_version , Fl Fl rom-version Ar rom_version
|
||||
Set the ROM version
|
||||
.Pq Ad 0x14C
|
||||
to a given value from 0 to 0xFF.
|
||||
.It Fl O , Fl Fl overwrite
|
||||
Allow overwriting different non-zero bytes in the header without a warning being emitted.
|
||||
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
|
||||
Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
|
||||
.Nm
|
||||
@@ -189,46 +179,6 @@ sans global checksum:
|
||||
.Pp
|
||||
.D1 $ rgbfix -cjsv -k A4 -l 0x33 -m 0x1B -p 0xFF -r 3 -t SURVIVALKIDAVKE \
|
||||
SurvivalKids.gbc
|
||||
.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.
|
||||
Its specification, as well as more resources, can be found online at
|
||||
.Lk https://github.com/aaaaaa123456789/tpp1 .
|
||||
.Ss MBC name
|
||||
The MBC name for TPP1 is more complex than standard mappers.
|
||||
It must be followed with the revision number, of the form
|
||||
.Ql major.minor ,
|
||||
where both
|
||||
.Ql major
|
||||
and
|
||||
.Ql minor
|
||||
are decimal, 8-bit integers.
|
||||
There may be any amount of spaces or underscores between
|
||||
.Ql TPP1
|
||||
and the revision number.
|
||||
.Nm
|
||||
only supports 1.x revisions, and will reject everything else.
|
||||
.Pp
|
||||
Like other mappers, the name may be followed with a list of optional,
|
||||
.Ql + Ns
|
||||
-separated features; however,
|
||||
.Ql RAM
|
||||
should not be specified, as the TPP1 mapper implicitly requests RAM if a non-zero RAM size is specified.
|
||||
Therefore,
|
||||
.Nm
|
||||
will ignore the
|
||||
.Ql RAM
|
||||
feature on a TPP1 mapper with a warning.
|
||||
.Ss Special considerations
|
||||
TPP1 overwrites the byte at
|
||||
.Ad 0x14A ,
|
||||
usually indicating the region destination
|
||||
.Pq see Fl j ,
|
||||
with one of its three identification bytes.
|
||||
Therefore,
|
||||
.Nm
|
||||
will warn about and ignore
|
||||
.Fl j
|
||||
if used in combination with TPP1.
|
||||
.Sh BUGS
|
||||
Please report bugs on
|
||||
.Lk https://github.com/gbdev/rgbds/issues GitHub .
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user