mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 02:02:06 +00:00
Enable RGBGFX's CLI "at-files" for all programs (#1848)
This commit is contained in:
@@ -120,6 +120,9 @@ These files in the `src/` directory are shared across multiple programs: often a
|
|||||||
|
|
||||||
- **`backtrace.cpp`:**
|
- **`backtrace.cpp`:**
|
||||||
Generic printing of location backtraces for RGBASM and RGBLINK. Allows configuring backtrace styles with a command-line flag (conventionally `-B/--backtrace`). Renders warnings in yellow, errors in red, and locations in cyan.
|
Generic printing of location backtraces for RGBASM and RGBLINK. Allows configuring backtrace styles with a command-line flag (conventionally `-B/--backtrace`). Renders warnings in yellow, errors in red, and locations in cyan.
|
||||||
|
- **`cli.cpp`:**
|
||||||
|
A function for parsing command-line options, including RGBDS-specific "at-files" (a filename containing more options, prepended with an "`@`").
|
||||||
|
This is the only file to use the extern/getopt.cpp variables and functions.
|
||||||
- **`diagnostics.cpp`:**
|
- **`diagnostics.cpp`:**
|
||||||
Generic warning/error diagnostic support for all programs. Allows command-line flags (conventionally `-W`) to have `no-`, `error=`, or `no-error=` prefixes, and `=` level suffixes; allows "meta" flags to affect groups of individual flags; and counts how many total errors there have been. Every program has its own `warning.cpp` file that uses this.
|
Generic warning/error diagnostic support for all programs. Allows command-line flags (conventionally `-W`) to have `no-`, `error=`, or `no-error=` prefixes, and `=` level suffixes; allows "meta" flags to affect groups of individual flags; and counts how many total errors there have been. Every program has its own `warning.cpp` file that uses this.
|
||||||
- **`linkdefs.cpp`:**
|
- **`linkdefs.cpp`:**
|
||||||
|
|||||||
1
Makefile
1
Makefile
@@ -51,6 +51,7 @@ all: rgbasm rgblink rgbfix rgbgfx
|
|||||||
|
|
||||||
common_obj := \
|
common_obj := \
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
|
src/cli.o \
|
||||||
src/diagnostics.o \
|
src/diagnostics.o \
|
||||||
src/style.o \
|
src/style.o \
|
||||||
src/usage.o \
|
src/usage.o \
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#ifndef RGBDS_ASM_MAIN_HPP
|
#ifndef RGBDS_ASM_MAIN_HPP
|
||||||
#define RGBDS_ASM_MAIN_HPP
|
#define RGBDS_ASM_MAIN_HPP
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -20,10 +21,10 @@ struct Options {
|
|||||||
char binDigits[2] = {'0', '1'}; // -b
|
char binDigits[2] = {'0', '1'}; // -b
|
||||||
char gfxDigits[4] = {'0', '1', '2', '3'}; // -g
|
char gfxDigits[4] = {'0', '1', '2', '3'}; // -g
|
||||||
FILE *dependFile = nullptr; // -M
|
FILE *dependFile = nullptr; // -M
|
||||||
std::string targetFileName; // -MQ, -MT
|
std::optional<std::string> targetFileName{}; // -MQ, -MT
|
||||||
MissingInclude missingIncludeState = INC_ERROR; // -MC, -MG
|
MissingInclude missingIncludeState = INC_ERROR; // -MC, -MG
|
||||||
bool generatePhonyDeps = false; // -MP
|
bool generatePhonyDeps = false; // -MP
|
||||||
std::string objectFileName; // -o
|
std::optional<std::string> objectFileName{}; // -o
|
||||||
uint8_t padByte = 0; // -p
|
uint8_t padByte = 0; // -p
|
||||||
uint64_t maxErrors = 0; // -X
|
uint64_t maxErrors = 0; // -X
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ struct Options {
|
|||||||
|
|
||||||
void printDep(std::string const &depName) {
|
void printDep(std::string const &depName) {
|
||||||
if (dependFile) {
|
if (dependFile) {
|
||||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), depName.c_str());
|
fprintf(dependFile, "%s: %s\n", targetFileName->c_str(), depName.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
20
include/cli.hpp
Normal file
20
include/cli.hpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef RGBDS_CLI_HPP
|
||||||
|
#define RGBDS_CLI_HPP
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "extern/getopt.hpp" // option
|
||||||
|
|
||||||
|
void cli_ParseArgs(
|
||||||
|
int argc,
|
||||||
|
char *argv[],
|
||||||
|
char const *shortOpts,
|
||||||
|
option const *longOpts,
|
||||||
|
void (*parseArg)(int, char *),
|
||||||
|
void (*fatal)(char const *, ...)
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif // RGBDS_CLI_HPP
|
||||||
@@ -3,7 +3,9 @@
|
|||||||
#ifndef RGBDS_FIX_MAIN_HPP
|
#ifndef RGBDS_FIX_MAIN_HPP
|
||||||
#define RGBDS_FIX_MAIN_HPP
|
#define RGBDS_FIX_MAIN_HPP
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "fix/mbc.hpp" // UNSPECIFIED, MbcType
|
#include "fix/mbc.hpp" // UNSPECIFIED, MbcType
|
||||||
|
|
||||||
@@ -28,19 +30,19 @@ struct Options {
|
|||||||
uint16_t ramSize = UNSPECIFIED; // -r
|
uint16_t ramSize = UNSPECIFIED; // -r
|
||||||
bool sgb = false; // -s
|
bool sgb = false; // -s
|
||||||
|
|
||||||
char const *gameID = nullptr; // -i
|
std::optional<std::string> gameID; // -i
|
||||||
uint8_t gameIDLen;
|
uint8_t gameIDLen;
|
||||||
|
|
||||||
char const *newLicensee = nullptr; // -k
|
std::optional<std::string> newLicensee; // -k
|
||||||
uint8_t newLicenseeLen;
|
uint8_t newLicenseeLen;
|
||||||
|
|
||||||
char const *logoFilename = nullptr; // -L
|
std::optional<std::string> logoFilename; // -L
|
||||||
uint8_t logo[48] = {};
|
uint8_t logo[48] = {};
|
||||||
|
|
||||||
MbcType cartridgeType = MBC_NONE; // -m
|
MbcType cartridgeType = MBC_NONE; // -m
|
||||||
uint8_t tpp1Rev[2];
|
uint8_t tpp1Rev[2];
|
||||||
|
|
||||||
char const *title = nullptr; // -t
|
std::optional<std::string> title; // -t
|
||||||
uint8_t titleLen;
|
uint8_t titleLen;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ void lexer_TraceCurrent();
|
|||||||
void lexer_IncludeFile(std::string &&path);
|
void lexer_IncludeFile(std::string &&path);
|
||||||
void lexer_IncLineNo();
|
void lexer_IncLineNo();
|
||||||
|
|
||||||
bool lexer_Init(char const *linkerScriptName);
|
bool lexer_Init(std::string const &linkerScriptName);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_LEXER_HPP
|
#endif // RGBDS_LINK_LEXER_HPP
|
||||||
|
|||||||
@@ -3,15 +3,17 @@
|
|||||||
#ifndef RGBDS_LINK_MAIN_HPP
|
#ifndef RGBDS_LINK_MAIN_HPP
|
||||||
#define RGBDS_LINK_MAIN_HPP
|
#define RGBDS_LINK_MAIN_HPP
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
bool isDmgMode; // -d
|
bool isDmgMode; // -d
|
||||||
char const *mapFileName; // -m
|
std::optional<std::string> mapFileName; // -m
|
||||||
bool noSymInMap; // -M
|
bool noSymInMap; // -M
|
||||||
char const *symFileName; // -n
|
std::optional<std::string> symFileName; // -n
|
||||||
char const *overlayFileName; // -O
|
std::optional<std::string> overlayFileName; // -O
|
||||||
char const *outputFileName; // -o
|
std::optional<std::string> outputFileName; // -o
|
||||||
uint8_t padValue; // -p
|
uint8_t padValue; // -p
|
||||||
bool hasPadValue = false;
|
bool hasPadValue = false;
|
||||||
// Setting these three to 0 disables the functionality
|
// Setting these three to 0 disables the functionality
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
#ifndef RGBDS_LINK_OBJECT_HPP
|
#ifndef RGBDS_LINK_OBJECT_HPP
|
||||||
#define RGBDS_LINK_OBJECT_HPP
|
#define RGBDS_LINK_OBJECT_HPP
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
// Read an object (.o) file, and add its info to the data structures.
|
// Read an object (.o) file, and add its info to the data structures.
|
||||||
void obj_ReadFile(char const *fileName, unsigned int fileID);
|
void obj_ReadFile(std::string const &filePath, size_t fileID);
|
||||||
|
|
||||||
// Sets up object file reading
|
// Sets up object file reading
|
||||||
void obj_Setup(unsigned int nbFiles);
|
void obj_Setup(size_t nbFiles);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_OBJECT_HPP
|
#endif // RGBDS_LINK_OBJECT_HPP
|
||||||
|
|||||||
17
man/rgbasm.1
17
man/rgbasm.1
@@ -351,6 +351,23 @@ disables this behavior.
|
|||||||
The default is 100 if
|
The default is 100 if
|
||||||
.Nm
|
.Nm
|
||||||
is printing errors to a terminal, and 0 otherwise.
|
is printing errors to a terminal, and 0 otherwise.
|
||||||
|
.It @ Ns Ar at_file
|
||||||
|
Read more options and arguments from a file, as if its contents were given on the command line.
|
||||||
|
Arguments are separated by whitespace or newlines.
|
||||||
|
Lines starting with a hash sign
|
||||||
|
.Pq Ql #
|
||||||
|
are considered comments and ignored.
|
||||||
|
.Pp
|
||||||
|
No shell processing is performed, such as wildcard or variable expansion.
|
||||||
|
There is no support for escaping or quoting whitespace to be included in arguments.
|
||||||
|
The standard
|
||||||
|
.Ql --
|
||||||
|
to stop option processing also disables at-file processing.
|
||||||
|
Note that while
|
||||||
|
.Ql --
|
||||||
|
can be used
|
||||||
|
.Em inside
|
||||||
|
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
|
||||||
.El
|
.El
|
||||||
.Sh DIAGNOSTICS
|
.Sh DIAGNOSTICS
|
||||||
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the assembling process.
|
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the assembling process.
|
||||||
|
|||||||
17
man/rgbfix.1
17
man/rgbfix.1
@@ -243,6 +243,23 @@ See the
|
|||||||
section for a list of warnings.
|
section for a list of warnings.
|
||||||
.It Fl w
|
.It Fl w
|
||||||
Disable all warning output, even when turned into errors.
|
Disable all warning output, even when turned into errors.
|
||||||
|
.It @ Ns Ar at_file
|
||||||
|
Read more options and arguments from a file, as if its contents were given on the command line.
|
||||||
|
Arguments are separated by whitespace or newlines.
|
||||||
|
Lines starting with a hash sign
|
||||||
|
.Pq Ql #
|
||||||
|
are considered comments and ignored.
|
||||||
|
.Pp
|
||||||
|
No shell processing is performed, such as wildcard or variable expansion.
|
||||||
|
There is no support for escaping or quoting whitespace to be included in arguments.
|
||||||
|
The standard
|
||||||
|
.Ql --
|
||||||
|
to stop option processing also disables at-file processing.
|
||||||
|
Note that while
|
||||||
|
.Ql --
|
||||||
|
can be used
|
||||||
|
.Em inside
|
||||||
|
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
|
||||||
.El
|
.El
|
||||||
.Sh DIAGNOSTICS
|
.Sh DIAGNOSTICS
|
||||||
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the header-fixing process.
|
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the header-fixing process.
|
||||||
|
|||||||
70
man/rgbgfx.1
70
man/rgbgfx.1
@@ -487,69 +487,53 @@ Implies
|
|||||||
.It Fl Z , Fl \-columns
|
.It Fl Z , Fl \-columns
|
||||||
Read squares from the PNG in column-major order (column by column), instead of the default row-major order (line by line).
|
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.
|
This primarily affects tile map and attribute map output, although it may also change generated tile data and palettes.
|
||||||
|
.It @ Ns Ar at_file
|
||||||
|
Read more options and arguments from a file, as if its contents were given on the command line.
|
||||||
|
Arguments are separated by whitespace or newlines.
|
||||||
|
Lines starting with a hash sign
|
||||||
|
.Pq Ql #
|
||||||
|
are considered comments and ignored.
|
||||||
|
.Pp
|
||||||
|
No shell processing is performed, such as wildcard or variable expansion.
|
||||||
|
There is no support for escaping or quoting whitespace to be included in arguments.
|
||||||
|
The standard
|
||||||
|
.Ql --
|
||||||
|
to stop option processing also disables at-file processing.
|
||||||
|
Note that while
|
||||||
|
.Ql --
|
||||||
|
can be used
|
||||||
|
.Em inside
|
||||||
|
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
|
||||||
|
.Pp
|
||||||
|
See
|
||||||
|
.Sx At-files
|
||||||
|
below for an explanation of how this can be useful.
|
||||||
.El
|
.El
|
||||||
.Ss At-files
|
.Ss At-files
|
||||||
In a given project, many images are to be converted with different flags.
|
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.
|
The traditional way of solving this problem has been to specify the different flags for each image in the Makefile or build script; this can be inconvenient, as it centralizes all those flags away from the images they concern.
|
||||||
.Pp
|
.Pp
|
||||||
To avoid these drawbacks,
|
To avoid these drawbacks, you can use
|
||||||
.Nm
|
|
||||||
supports
|
|
||||||
.Dq at-files :
|
.Dq at-files :
|
||||||
any command-line argument that begins with an at sign
|
any command-line argument that begins with an at sign
|
||||||
.Pq Ql @
|
.Pq Ql @
|
||||||
is interpreted as one.
|
is interpreted as one, as documented above.
|
||||||
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:
|
At-files can be stored right next to the corresponding image, for example:
|
||||||
.Pp
|
.Pp
|
||||||
.Dl $ rgbgfx -o image.2bpp -t image.tilemap @image.flags image.png
|
.Dl $ rgbgfx -o image.2bpp -t image.tilemap @image.flags image.png
|
||||||
.Pp
|
.Pp
|
||||||
This will read additional flags from file
|
This will read additional flags from the file
|
||||||
.Ql image.flags ,
|
.Ql image.flags ,
|
||||||
which could contains for example
|
which could contain, for example,
|
||||||
.Ql -b 128
|
.Ql -b 128
|
||||||
to specify a base offset for the image's tiles.
|
to specify a base offset for the image's tiles.
|
||||||
The above command could be generated from the following
|
The above command could be generated from the following
|
||||||
.Xr make 1
|
.Xr make 1
|
||||||
rule, for example:
|
rule:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
%.2bpp %.tilemap: %.flags %.png
|
%.2bpp %.tilemap: %.flags %.png
|
||||||
rgbgfx -o $*.2bpp -t $*.tilemap @$*.flags $*.png
|
rgbgfx -o $*.2bpp -t $*.tilemap @$*.flags $*.png
|
||||||
.Ed
|
.Ed
|
||||||
.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 a leading
|
|
||||||
.Ql @
|
|
||||||
has no special meaning on option arguments, and that the standard
|
|
||||||
.Ql --
|
|
||||||
to stop option processing also disables at-file processing.
|
|
||||||
For example, the following command line reads command-line options from
|
|
||||||
.Ql tilesets/town.flags
|
|
||||||
then
|
|
||||||
.Ql tilesets.flags ,
|
|
||||||
but processes
|
|
||||||
.Ql @tilesets/town.png
|
|
||||||
as the input image and outputs tile data to
|
|
||||||
.Ql @tilesets/town.2bpp :
|
|
||||||
.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
|
.Sh PALETTE SPECIFICATION FORMATS
|
||||||
The following formats are supported:
|
The following formats are supported:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
|
|||||||
@@ -235,6 +235,23 @@ You can use this to make binary files that are not a ROM.
|
|||||||
When making a ROM, note that not using this is not a replacement for
|
When making a ROM, note that not using this is not a replacement for
|
||||||
.Xr rgbfix 1 Ap s Fl p
|
.Xr rgbfix 1 Ap s Fl p
|
||||||
option!
|
option!
|
||||||
|
.It @ Ns Ar at_file
|
||||||
|
Read more options and arguments from a file, as if its contents were given on the command line.
|
||||||
|
Arguments are separated by whitespace or newlines.
|
||||||
|
Lines starting with a hash sign
|
||||||
|
.Pq Ql #
|
||||||
|
are considered comments and ignored.
|
||||||
|
.Pp
|
||||||
|
No shell processing is performed, such as wildcard or variable expansion.
|
||||||
|
There is no support for escaping or quoting whitespace to be included in arguments.
|
||||||
|
The standard
|
||||||
|
.Ql --
|
||||||
|
to stop option processing also disables at-file processing.
|
||||||
|
Note that while
|
||||||
|
.Ql --
|
||||||
|
can be used
|
||||||
|
.Em inside
|
||||||
|
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
|
||||||
.El
|
.El
|
||||||
.Ss Scrambling algorithm
|
.Ss Scrambling algorithm
|
||||||
The default section placement algorithm tries to place sections into as few banks as possible.
|
The default section placement algorithm tries to place sections into as few banks as possible.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ configure_file(version.cpp _version.cpp ESCAPE_QUOTES)
|
|||||||
|
|
||||||
set(common_src
|
set(common_src
|
||||||
"extern/getopt.cpp"
|
"extern/getopt.cpp"
|
||||||
|
"cli.cpp"
|
||||||
"diagnostics.cpp"
|
"diagnostics.cpp"
|
||||||
"style.cpp"
|
"style.cpp"
|
||||||
"usage.cpp"
|
"usage.cpp"
|
||||||
|
|||||||
415
src/asm/main.cpp
415
src/asm/main.cpp
@@ -19,8 +19,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "backtrace.hpp"
|
#include "backtrace.hpp"
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "parser.hpp" // Generated from parser.y
|
#include "parser.hpp" // Generated from parser.y
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
@@ -40,13 +40,17 @@
|
|||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
|
|
||||||
static char const *dependFileName = nullptr; // -M
|
// Flags which must be processed after the option parsing finishes
|
||||||
static std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
|
static struct LocalOptions {
|
||||||
|
std::optional<std::string> dependFileName; // -M
|
||||||
|
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
|
||||||
|
std::optional<std::string> inputFileName; // <file>
|
||||||
|
} localOptions;
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "B:b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
static char const *optstring = "B:b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
||||||
|
|
||||||
// Variables for the long-only options
|
// Long-only option variable
|
||||||
static int longOpt; // `--color` and variants of `-M`
|
static int longOpt; // `--color` and variants of `-M`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -105,134 +109,6 @@ static Usage usage = {
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
static void verboseOutputConfig(int argc, char *argv[]) {
|
|
||||||
if (!checkVerbosity(VERB_CONFIG)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
style_Set(stderr, STYLE_MAGENTA, false);
|
|
||||||
|
|
||||||
fprintf(stderr, "rgbasm %s\n", get_package_version_string());
|
|
||||||
|
|
||||||
printVVVVVVerbosity();
|
|
||||||
|
|
||||||
fputs("Options:\n", stderr);
|
|
||||||
// -E/--export-all
|
|
||||||
if (options.exportAll) {
|
|
||||||
fputs("\tExport all labels by default\n", stderr);
|
|
||||||
}
|
|
||||||
// -b/--binary-digits
|
|
||||||
if (options.binDigits[0] != '0' || options.binDigits[1] != '1') {
|
|
||||||
fprintf(
|
|
||||||
stderr, "\tBinary digits: '%c', '%c'\n", options.binDigits[0], options.binDigits[1]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// -g/--gfx-chars
|
|
||||||
if (options.gfxDigits[0] != '0' || options.gfxDigits[1] != '1' || options.gfxDigits[2] != '2'
|
|
||||||
|| options.gfxDigits[3] != '3') {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"\tGraphics characters: '%c', '%c', '%c', '%c'\n",
|
|
||||||
options.gfxDigits[0],
|
|
||||||
options.gfxDigits[1],
|
|
||||||
options.gfxDigits[2],
|
|
||||||
options.gfxDigits[3]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// -Q/--q-precision
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"\tFixed-point precision: Q%d.%" PRIu8 "\n",
|
|
||||||
32 - options.fixPrecision,
|
|
||||||
options.fixPrecision
|
|
||||||
);
|
|
||||||
// -p/--pad-value
|
|
||||||
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padByte);
|
|
||||||
// -r/--recursion-depth
|
|
||||||
fprintf(stderr, "\tMaximum recursion depth %zu\n", options.maxRecursionDepth);
|
|
||||||
// -X/--max-errors
|
|
||||||
if (options.maxErrors) {
|
|
||||||
fprintf(stderr, "\tMaximum %" PRIu64 " errors\n", options.maxErrors);
|
|
||||||
}
|
|
||||||
// -D/--define
|
|
||||||
static bool hasDefines = false; // `static` so `sym_ForEach` callback can see it
|
|
||||||
sym_ForEach([](Symbol &sym) {
|
|
||||||
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
|
|
||||||
if (!hasDefines) {
|
|
||||||
fputs("\tDefinitions:\n", stderr);
|
|
||||||
hasDefines = true;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "\t - def %s equs \"%s\"\n", sym.name.c_str(), sym.getEqus()->c_str());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// -s/--state
|
|
||||||
if (!stateFileSpecs.empty()) {
|
|
||||||
fputs("\tOutput state files:\n", stderr);
|
|
||||||
static char const *featureNames[NB_STATE_FEATURES] = {
|
|
||||||
"equ",
|
|
||||||
"var",
|
|
||||||
"equs",
|
|
||||||
"char",
|
|
||||||
"macro",
|
|
||||||
};
|
|
||||||
for (auto const &[name, features] : stateFileSpecs) {
|
|
||||||
fprintf(stderr, "\t - %s: ", name == "-" ? "<stdout>" : name.c_str());
|
|
||||||
for (size_t i = 0; i < features.size(); ++i) {
|
|
||||||
if (i > 0) {
|
|
||||||
fputs(", ", stderr);
|
|
||||||
}
|
|
||||||
fputs(featureNames[features[i]], stderr);
|
|
||||||
}
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// asmfile
|
|
||||||
if (musl_optind < argc) {
|
|
||||||
fprintf(stderr, "\tInput asm file: %s", argv[musl_optind]);
|
|
||||||
if (musl_optind + 1 < argc) {
|
|
||||||
fprintf(stderr, " (and %d more)", argc - musl_optind - 1);
|
|
||||||
}
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
// -o/--output
|
|
||||||
if (!options.objectFileName.empty()) {
|
|
||||||
fprintf(stderr, "\tOutput object file: %s\n", options.objectFileName.c_str());
|
|
||||||
}
|
|
||||||
fstk_VerboseOutputConfig();
|
|
||||||
if (dependFileName) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"\tOutput dependency file: %s\n",
|
|
||||||
strcmp(dependFileName, "-") ? dependFileName : "<stdout>"
|
|
||||||
);
|
|
||||||
// -MT or -MQ
|
|
||||||
if (!options.targetFileName.empty()) {
|
|
||||||
fprintf(stderr, "\tTarget file(s): %s\n", options.targetFileName.c_str());
|
|
||||||
}
|
|
||||||
// -MG or -MC
|
|
||||||
switch (options.missingIncludeState) {
|
|
||||||
case INC_ERROR:
|
|
||||||
fputs("\tExit with an error on a missing dependency\n", stderr);
|
|
||||||
break;
|
|
||||||
case GEN_EXIT:
|
|
||||||
fputs("\tExit normally on a missing dependency\n", stderr);
|
|
||||||
break;
|
|
||||||
case GEN_CONTINUE:
|
|
||||||
fputs("\tContinue processing after a missing dependency\n", stderr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// -MP
|
|
||||||
if (options.generatePhonyDeps) {
|
|
||||||
fputs("\tGenerate phony dependencies\n", stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fputs("Ready.\n", stderr);
|
|
||||||
|
|
||||||
style_Reset(stderr);
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
static std::string escapeMakeChars(std::string &str) {
|
static std::string escapeMakeChars(std::string &str) {
|
||||||
std::string escaped;
|
std::string escaped;
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
@@ -295,46 +171,29 @@ static std::vector<StateFeature> parseStateFeatures(char *str) {
|
|||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
static void parseArg(int ch, char *arg) {
|
||||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
|
||||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
|
||||||
time_t now = time(nullptr);
|
|
||||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
|
||||||
// Use `strtoul`, not `parseWholeNumber`, because SOURCE_DATE_EPOCH does
|
|
||||||
// not conventionally support our custom base prefixes
|
|
||||||
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
|
||||||
}
|
|
||||||
sym_Init(now);
|
|
||||||
|
|
||||||
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal
|
|
||||||
if (isatty(STDERR_FILENO)) {
|
|
||||||
options.maxErrors = 100; // LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse CLI options
|
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 'B':
|
case 'B':
|
||||||
if (!trace_ParseTraceDepth(musl_optarg)) {
|
if (!trace_ParseTraceDepth(arg)) {
|
||||||
fatal("Invalid argument for option '-B'");
|
fatal("Invalid argument for option '-B'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'b':
|
case 'b':
|
||||||
if (strlen(musl_optarg) == 2) {
|
if (strlen(arg) == 2) {
|
||||||
opt_B(musl_optarg);
|
opt_B(arg);
|
||||||
} else {
|
} else {
|
||||||
fatal("Must specify exactly 2 characters for option '-b'");
|
fatal("Must specify exactly 2 characters for option '-b'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'D': {
|
case 'D': {
|
||||||
char *equals = strchr(musl_optarg, '=');
|
char *equals = strchr(arg, '=');
|
||||||
if (equals) {
|
if (equals) {
|
||||||
*equals = '\0';
|
*equals = '\0';
|
||||||
sym_AddString(musl_optarg, std::make_shared<std::string>(equals + 1));
|
sym_AddString(arg, std::make_shared<std::string>(equals + 1));
|
||||||
} else {
|
} else {
|
||||||
sym_AddString(musl_optarg, std::make_shared<std::string>("1"));
|
sym_AddString(arg, std::make_shared<std::string>("1"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -344,8 +203,8 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'g':
|
case 'g':
|
||||||
if (strlen(musl_optarg) == 4) {
|
if (strlen(arg) == 4) {
|
||||||
opt_G(musl_optarg);
|
opt_G(arg);
|
||||||
} else {
|
} else {
|
||||||
fatal("Must specify exactly 4 characters for option '-g'");
|
fatal("Must specify exactly 4 characters for option '-g'");
|
||||||
}
|
}
|
||||||
@@ -357,32 +216,33 @@ int main(int argc, char *argv[]) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'I':
|
case 'I':
|
||||||
fstk_AddIncludePath(musl_optarg);
|
fstk_AddIncludePath(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
if (dependFileName) {
|
if (localOptions.dependFileName) {
|
||||||
warnx(
|
warnx(
|
||||||
"Overriding dependency file \"%s\"",
|
"Overriding dependency file \"%s\"",
|
||||||
strcmp(dependFileName, "-") ? dependFileName : "<stdout>"
|
*localOptions.dependFileName == "-" ? "<stdout>"
|
||||||
|
: localOptions.dependFileName->c_str()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
dependFileName = musl_optarg;
|
localOptions.dependFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'o':
|
case 'o':
|
||||||
if (!options.objectFileName.empty()) {
|
if (options.objectFileName) {
|
||||||
warnx("Overriding output file \"%s\"", options.objectFileName.c_str());
|
warnx("Overriding output file \"%s\"", options.objectFileName->c_str());
|
||||||
}
|
}
|
||||||
options.objectFileName = musl_optarg;
|
options.objectFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'P':
|
case 'P':
|
||||||
fstk_AddPreIncludeFile(musl_optarg);
|
fstk_AddPreIncludeFile(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
if (std::optional<uint64_t> padByte = parseWholeNumber(musl_optarg); !padByte) {
|
if (std::optional<uint64_t> padByte = parseWholeNumber(arg); !padByte) {
|
||||||
fatal("Invalid argument for option '-p'");
|
fatal("Invalid argument for option '-p'");
|
||||||
} else if (*padByte > 0xFF) {
|
} else if (*padByte > 0xFF) {
|
||||||
fatal("Argument for option '-p' must be between 0 and 0xFF");
|
fatal("Argument for option '-p' must be between 0 and 0xFF");
|
||||||
@@ -392,7 +252,7 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Q': {
|
case 'Q': {
|
||||||
char const *precisionArg = musl_optarg;
|
char const *precisionArg = arg;
|
||||||
if (precisionArg[0] == '.') {
|
if (precisionArg[0] == '.') {
|
||||||
++precisionArg;
|
++precisionArg;
|
||||||
}
|
}
|
||||||
@@ -408,7 +268,7 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'r':
|
case 'r':
|
||||||
if (std::optional<uint64_t> maxDepth = parseWholeNumber(musl_optarg); !maxDepth) {
|
if (std::optional<uint64_t> maxDepth = parseWholeNumber(arg); !maxDepth) {
|
||||||
fatal("Invalid argument for option '-r'");
|
fatal("Invalid argument for option '-r'");
|
||||||
} else if (errno == ERANGE) {
|
} else if (errno == ERANGE) {
|
||||||
fatal("Argument for option '-r' is out of range");
|
fatal("Argument for option '-r' is out of range");
|
||||||
@@ -418,19 +278,19 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 's': {
|
case 's': {
|
||||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
// Split "<features>:<name>" so `arg` is "<features>" and `name` is "<name>"
|
||||||
char *name = strchr(musl_optarg, ':');
|
char *name = strchr(arg, ':');
|
||||||
if (!name) {
|
if (!name) {
|
||||||
fatal("Invalid argument for option '-s'");
|
fatal("Invalid argument for option '-s'");
|
||||||
}
|
}
|
||||||
*name++ = '\0';
|
*name++ = '\0';
|
||||||
|
|
||||||
std::vector<StateFeature> features = parseStateFeatures(musl_optarg);
|
std::vector<StateFeature> features = parseStateFeatures(arg);
|
||||||
|
|
||||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
if (localOptions.stateFileSpecs.find(name) != localOptions.stateFileSpecs.end()) {
|
||||||
warnx("Overriding state file \"%s\"", name);
|
warnx("Overriding state file \"%s\"", name);
|
||||||
}
|
}
|
||||||
stateFileSpecs.emplace(name, std::move(features));
|
localOptions.stateFileSpecs.emplace(name, std::move(features));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +305,7 @@ int main(int argc, char *argv[]) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
opt_W(musl_optarg);
|
opt_W(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
@@ -453,7 +313,7 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'X':
|
case 'X':
|
||||||
if (std::optional<uint64_t> maxErrors = parseWholeNumber(musl_optarg); !maxErrors) {
|
if (std::optional<uint64_t> maxErrors = parseWholeNumber(arg); !maxErrors) {
|
||||||
fatal("Invalid argument for option '-X'");
|
fatal("Invalid argument for option '-X'");
|
||||||
} else if (*maxErrors > UINT64_MAX) {
|
} else if (*maxErrors > UINT64_MAX) {
|
||||||
fatal("Argument for option '-X' must be between 0 and %" PRIu64, UINT64_MAX);
|
fatal("Argument for option '-X' must be between 0 and %" PRIu64, UINT64_MAX);
|
||||||
@@ -465,7 +325,7 @@ int main(int argc, char *argv[]) {
|
|||||||
case 0: // Long-only options
|
case 0: // Long-only options
|
||||||
switch (longOpt) {
|
switch (longOpt) {
|
||||||
case 'c':
|
case 'c':
|
||||||
if (!style_Parse(musl_optarg)) {
|
if (!style_Parse(arg)) {
|
||||||
fatal("Invalid argument for option '--color'");
|
fatal("Invalid argument for option '--color'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -484,66 +344,223 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
case 'Q':
|
case 'Q':
|
||||||
case 'T': {
|
case 'T': {
|
||||||
std::string newTarget = musl_optarg;
|
std::string newTarget = arg;
|
||||||
if (longOpt == 'Q') {
|
if (longOpt == 'Q') {
|
||||||
newTarget = escapeMakeChars(newTarget);
|
newTarget = escapeMakeChars(newTarget);
|
||||||
}
|
}
|
||||||
if (!options.targetFileName.empty()) {
|
if (options.targetFileName) {
|
||||||
options.targetFileName += ' ';
|
*options.targetFileName += ' ';
|
||||||
|
*options.targetFileName += newTarget;
|
||||||
|
} else {
|
||||||
|
options.targetFileName = newTarget;
|
||||||
}
|
}
|
||||||
options.targetFileName += newTarget;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 1: // Positional argument
|
||||||
|
if (localOptions.inputFileName) {
|
||||||
|
usage.printAndExit("More than one input file specified");
|
||||||
|
}
|
||||||
|
localOptions.inputFileName = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
default:
|
default:
|
||||||
usage.printAndExit(1);
|
usage.printAndExit(1);
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
static void verboseOutputConfig() {
|
||||||
|
if (!checkVerbosity(VERB_CONFIG)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.targetFileName.empty() && !options.objectFileName.empty()) {
|
style_Set(stderr, STYLE_MAGENTA, false);
|
||||||
|
|
||||||
|
fprintf(stderr, "rgbasm %s\n", get_package_version_string());
|
||||||
|
|
||||||
|
printVVVVVVerbosity();
|
||||||
|
|
||||||
|
fputs("Options:\n", stderr);
|
||||||
|
// -E/--export-all
|
||||||
|
if (options.exportAll) {
|
||||||
|
fputs("\tExport all labels by default\n", stderr);
|
||||||
|
}
|
||||||
|
// -b/--binary-digits
|
||||||
|
if (options.binDigits[0] != '0' || options.binDigits[1] != '1') {
|
||||||
|
fprintf(
|
||||||
|
stderr, "\tBinary digits: '%c', '%c'\n", options.binDigits[0], options.binDigits[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// -g/--gfx-chars
|
||||||
|
if (options.gfxDigits[0] != '0' || options.gfxDigits[1] != '1' || options.gfxDigits[2] != '2'
|
||||||
|
|| options.gfxDigits[3] != '3') {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"\tGraphics characters: '%c', '%c', '%c', '%c'\n",
|
||||||
|
options.gfxDigits[0],
|
||||||
|
options.gfxDigits[1],
|
||||||
|
options.gfxDigits[2],
|
||||||
|
options.gfxDigits[3]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// -Q/--q-precision
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"\tFixed-point precision: Q%d.%" PRIu8 "\n",
|
||||||
|
32 - options.fixPrecision,
|
||||||
|
options.fixPrecision
|
||||||
|
);
|
||||||
|
// -p/--pad-value
|
||||||
|
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padByte);
|
||||||
|
// -r/--recursion-depth
|
||||||
|
fprintf(stderr, "\tMaximum recursion depth %zu\n", options.maxRecursionDepth);
|
||||||
|
// -X/--max-errors
|
||||||
|
if (options.maxErrors) {
|
||||||
|
fprintf(stderr, "\tMaximum %" PRIu64 " errors\n", options.maxErrors);
|
||||||
|
}
|
||||||
|
// -D/--define
|
||||||
|
static bool hasDefines = false; // `static` so `sym_ForEach` callback can see it
|
||||||
|
sym_ForEach([](Symbol &sym) {
|
||||||
|
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
|
||||||
|
if (!hasDefines) {
|
||||||
|
fputs("\tDefinitions:\n", stderr);
|
||||||
|
hasDefines = true;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "\t - def %s equs \"%s\"\n", sym.name.c_str(), sym.getEqus()->c_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// -s/--state
|
||||||
|
if (!localOptions.stateFileSpecs.empty()) {
|
||||||
|
fputs("\tOutput state files:\n", stderr);
|
||||||
|
static char const *featureNames[NB_STATE_FEATURES] = {
|
||||||
|
"equ",
|
||||||
|
"var",
|
||||||
|
"equs",
|
||||||
|
"char",
|
||||||
|
"macro",
|
||||||
|
};
|
||||||
|
for (auto const &[name, features] : localOptions.stateFileSpecs) {
|
||||||
|
fprintf(stderr, "\t - %s: ", name == "-" ? "<stdout>" : name.c_str());
|
||||||
|
for (size_t i = 0; i < features.size(); ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
fputs(", ", stderr);
|
||||||
|
}
|
||||||
|
fputs(featureNames[features[i]], stderr);
|
||||||
|
}
|
||||||
|
putc('\n', stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// asmfile
|
||||||
|
if (localOptions.inputFileName) {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"\tInput asm file: %s\n",
|
||||||
|
*localOptions.inputFileName == "-" ? "<stdin>" : localOptions.inputFileName->c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// -o/--output
|
||||||
|
if (options.objectFileName) {
|
||||||
|
fprintf(stderr, "\tOutput object file: %s\n", options.objectFileName->c_str());
|
||||||
|
}
|
||||||
|
fstk_VerboseOutputConfig();
|
||||||
|
if (localOptions.dependFileName) {
|
||||||
|
fprintf(stderr, "\tOutput dependency file: %s\n", localOptions.dependFileName->c_str());
|
||||||
|
// -MT or -MQ
|
||||||
|
if (options.targetFileName) {
|
||||||
|
fprintf(stderr, "\tTarget file(s): %s\n", options.targetFileName->c_str());
|
||||||
|
}
|
||||||
|
// -MG or -MC
|
||||||
|
switch (options.missingIncludeState) {
|
||||||
|
case INC_ERROR:
|
||||||
|
fputs("\tExit with an error on a missing dependency\n", stderr);
|
||||||
|
break;
|
||||||
|
case GEN_EXIT:
|
||||||
|
fputs("\tExit normally on a missing dependency\n", stderr);
|
||||||
|
break;
|
||||||
|
case GEN_CONTINUE:
|
||||||
|
fputs("\tContinue processing after a missing dependency\n", stderr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// -MP
|
||||||
|
if (options.generatePhonyDeps) {
|
||||||
|
fputs("\tGenerate phony dependencies\n", stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fputs("Ready.\n", stderr);
|
||||||
|
|
||||||
|
style_Reset(stderr);
|
||||||
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||||
|
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
||||||
|
// Use `strtoul`, not `parseWholeNumber`, because SOURCE_DATE_EPOCH does
|
||||||
|
// not conventionally support our custom base prefixes
|
||||||
|
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
||||||
|
}
|
||||||
|
sym_Init(now);
|
||||||
|
|
||||||
|
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal
|
||||||
|
if (isatty(STDERR_FILENO)) {
|
||||||
|
options.maxErrors = 100; // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
|
||||||
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
||||||
|
|
||||||
|
if (!options.targetFileName && options.objectFileName) {
|
||||||
options.targetFileName = options.objectFileName;
|
options.targetFileName = options.objectFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseOutputConfig(argc, argv);
|
verboseOutputConfig();
|
||||||
|
|
||||||
if (argc == musl_optind) {
|
if (!localOptions.inputFileName) {
|
||||||
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
||||||
} else if (argc != musl_optind + 1) {
|
|
||||||
usage.printAndExit("More than one input file specified");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string mainFileName = argv[musl_optind];
|
// LCOV_EXCL_START
|
||||||
|
verbosePrint(
|
||||||
|
VERB_NOTICE,
|
||||||
|
"Assembling \"%s\"\n",
|
||||||
|
*localOptions.inputFileName == "-" ? "<stdin>" : localOptions.inputFileName->c_str()
|
||||||
|
);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
verbosePrint(VERB_NOTICE, "Assembling \"%s\"\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
if (localOptions.dependFileName) {
|
||||||
|
if (!options.targetFileName) {
|
||||||
if (dependFileName) {
|
|
||||||
if (options.targetFileName.empty()) {
|
|
||||||
fatal("Dependency files can only be created if a target file is specified with either "
|
fatal("Dependency files can only be created if a target file is specified with either "
|
||||||
"'-o', '-MQ' or '-MT'");
|
"'-o', '-MQ' or '-MT'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp("-", dependFileName)) {
|
if (*localOptions.dependFileName == "-") {
|
||||||
options.dependFile = fopen(dependFileName, "w");
|
options.dependFile = stdout;
|
||||||
|
} else {
|
||||||
|
options.dependFile = fopen(localOptions.dependFileName->c_str(), "w");
|
||||||
if (options.dependFile == nullptr) {
|
if (options.dependFile == nullptr) {
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to open dependency file \"%s\": %s", dependFileName, strerror(errno));
|
fatal(
|
||||||
|
"Failed to open dependency file \"%s\": %s",
|
||||||
|
localOptions.dependFileName->c_str(),
|
||||||
|
strerror(errno)
|
||||||
|
);
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
options.dependFile = stdout;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options.printDep(mainFileName);
|
options.printDep(*localOptions.inputFileName);
|
||||||
|
|
||||||
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
|
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
|
||||||
|
|
||||||
// Init lexer and file stack, providing file info
|
// Init lexer and file stack, providing file info
|
||||||
fstk_Init(mainFileName);
|
fstk_Init(*localOptions.inputFileName);
|
||||||
|
|
||||||
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
||||||
if (yy::parser parser; parser.parse() != 0) {
|
if (yy::parser parser; parser.parse() != 0) {
|
||||||
@@ -569,7 +586,7 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
out_WriteObject();
|
out_WriteObject();
|
||||||
|
|
||||||
for (auto const &[name, features] : stateFileSpecs) {
|
for (auto const &[name, features] : localOptions.stateFileSpecs) {
|
||||||
out_WriteState(name, features);
|
out_WriteState(name, features);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -191,23 +191,22 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void out_WriteObject() {
|
void out_WriteObject() {
|
||||||
if (options.objectFileName.empty()) {
|
if (!options.objectFileName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FILE *file; // `static` so `sect_ForEach` callback can see it
|
static FILE *file; // `static` so `sect_ForEach` callback can see it
|
||||||
if (options.objectFileName != "-") {
|
char const *objectFileName = options.objectFileName->c_str();
|
||||||
file = fopen(options.objectFileName.c_str(), "wb");
|
if (*options.objectFileName != "-") {
|
||||||
|
file = fopen(objectFileName, "wb");
|
||||||
} else {
|
} else {
|
||||||
options.objectFileName = "<stdout>";
|
objectFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
file = stdout;
|
file = stdout;
|
||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
fatal(
|
fatal("Failed to open object file \"%s\": %s", objectFileName, strerror(errno));
|
||||||
"Failed to open object file \"%s\": %s", options.objectFileName.c_str(), strerror(errno)
|
|
||||||
);
|
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|||||||
153
src/cli.cpp
Normal file
153
src/cli.cpp
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
#include "cli.hpp"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "extern/getopt.hpp"
|
||||||
|
#include "util.hpp" // isBlankSpace
|
||||||
|
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
|
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
|
||||||
|
static std::vector<size_t> readAtFile(
|
||||||
|
std::string const &path, std::vector<char> &argPool, void (*fatal)(char const *, ...)
|
||||||
|
) {
|
||||||
|
std::vector<size_t> argvOfs;
|
||||||
|
|
||||||
|
std::filebuf file;
|
||||||
|
if (!file.open(path, std::ios_base::in)) {
|
||||||
|
std::string msg = "Error reading at-file \""s + path + "\": " + strerror(errno);
|
||||||
|
fatal(msg.c_str());
|
||||||
|
return argvOfs; // Since we can't mark the `fatal` function pointer as [[noreturn]]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int c = file.sbumpc();
|
||||||
|
|
||||||
|
// First, discard any leading blank space
|
||||||
|
while (isBlankSpace(c)) {
|
||||||
|
c = file.sbumpc();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a comment, discard everything until EOL
|
||||||
|
if (c == '#') {
|
||||||
|
c = file.sbumpc();
|
||||||
|
while (c != EOF && !isNewline(c)) {
|
||||||
|
c = file.sbumpc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == EOF) {
|
||||||
|
return argvOfs;
|
||||||
|
} else if (isNewline(c)) {
|
||||||
|
continue; // Start processing the next line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alright, now we can parse the line
|
||||||
|
do {
|
||||||
|
argvOfs.push_back(argPool.size());
|
||||||
|
|
||||||
|
// Read one argument (until the next whitespace char).
|
||||||
|
// We know there is one because we already have its first character in `c`.
|
||||||
|
for (; c != EOF && !isWhitespace(c); c = file.sbumpc()) {
|
||||||
|
argPool.push_back(c);
|
||||||
|
}
|
||||||
|
argPool.push_back('\0');
|
||||||
|
|
||||||
|
// Discard blank space until the next argument (candidate)
|
||||||
|
while (isBlankSpace(c)) {
|
||||||
|
c = file.sbumpc();
|
||||||
|
}
|
||||||
|
} while (c != EOF && !isNewline(c)); // End if we reached EOL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cli_ParseArgs(
|
||||||
|
int argc,
|
||||||
|
char *argv[],
|
||||||
|
char const *shortOpts,
|
||||||
|
option const *longOpts,
|
||||||
|
void (*parseArg)(int, char *),
|
||||||
|
void (*fatal)(char const *, ...)
|
||||||
|
) {
|
||||||
|
struct AtFileStackEntry {
|
||||||
|
int parentInd; // Saved offset into parent argv
|
||||||
|
std::vector<char *> argv; // This context's arg pointer vec
|
||||||
|
|
||||||
|
AtFileStackEntry(int parentInd_, std::vector<char *> argv_)
|
||||||
|
: parentInd(parentInd_), argv(argv_) {}
|
||||||
|
};
|
||||||
|
std::vector<AtFileStackEntry> atFileStack;
|
||||||
|
|
||||||
|
int curArgc = argc;
|
||||||
|
char **curArgv = argv;
|
||||||
|
std::string optString = "-"s + shortOpts; // Request position arguments with a leading '-'
|
||||||
|
std::vector<std::vector<char>> argPools;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
char *atFileName = nullptr;
|
||||||
|
for (int ch;
|
||||||
|
(ch = musl_getopt_long_only(curArgc, curArgv, optString.c_str(), longOpts, nullptr))
|
||||||
|
!= -1;) {
|
||||||
|
if (ch == 1 && musl_optarg[0] == '@') {
|
||||||
|
atFileName = &musl_optarg[1];
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
parseArg(ch, musl_optarg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atFileName) {
|
||||||
|
// We need to allocate a new arg pool for each at-file, so as not to invalidate pointers
|
||||||
|
// previous at-files may have generated to their own arg pools.
|
||||||
|
// But for the same reason, the arg pool must also outlive the at-file's stack entry!
|
||||||
|
std::vector<char> &argPool = argPools.emplace_back();
|
||||||
|
|
||||||
|
// Copy `argv[0]` for error reporting, and because option parsing skips it
|
||||||
|
AtFileStackEntry &stackEntry =
|
||||||
|
atFileStack.emplace_back(musl_optind, std::vector{atFileName});
|
||||||
|
|
||||||
|
// It would be nice to compute the char pointers on the fly, but reallocs don't allow
|
||||||
|
// that; so we must compute the offsets after the pool is fixed
|
||||||
|
std::vector<size_t> offsets = readAtFile(&musl_optarg[1], argPool, fatal);
|
||||||
|
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
|
||||||
|
for (size_t ofs : offsets) {
|
||||||
|
stackEntry.argv.push_back(&argPool.data()[ofs]);
|
||||||
|
}
|
||||||
|
stackEntry.argv.push_back(nullptr); // Don't forget the arg vector terminator!
|
||||||
|
|
||||||
|
curArgc = stackEntry.argv.size() - 1;
|
||||||
|
curArgv = stackEntry.argv.data();
|
||||||
|
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
|
||||||
|
} else {
|
||||||
|
if (musl_optind != curArgc) {
|
||||||
|
// This happens if `--` is passed, process the remaining arg(s) as positional
|
||||||
|
assume(musl_optind < curArgc);
|
||||||
|
for (int i = musl_optind; i < curArgc; ++i) {
|
||||||
|
parseArg(1, argv[i]); // Positional argument
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop off the top stack entry, or end parsing if none
|
||||||
|
if (atFileStack.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK to restore `optind` directly, because `optpos` must be 0 right now.
|
||||||
|
// (Providing 0 would be a "proper" reset, but we want to resume parsing)
|
||||||
|
musl_optind = atFileStack.back().parentInd;
|
||||||
|
atFileStack.pop_back();
|
||||||
|
if (atFileStack.empty()) {
|
||||||
|
curArgc = argc;
|
||||||
|
curArgv = argv;
|
||||||
|
} else {
|
||||||
|
std::vector<char *> &vec = atFileStack.back().argv;
|
||||||
|
curArgc = vec.size();
|
||||||
|
curArgv = vec.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,7 +141,11 @@ static void
|
|||||||
|
|
||||||
if (options.title) {
|
if (options.title) {
|
||||||
overwriteBytes(
|
overwriteBytes(
|
||||||
rom0, 0x134, reinterpret_cast<uint8_t const *>(options.title), options.titleLen, "title"
|
rom0,
|
||||||
|
0x134,
|
||||||
|
reinterpret_cast<uint8_t const *>(options.title->c_str()),
|
||||||
|
options.titleLen,
|
||||||
|
"title"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +153,7 @@ static void
|
|||||||
overwriteBytes(
|
overwriteBytes(
|
||||||
rom0,
|
rom0,
|
||||||
0x13F,
|
0x13F,
|
||||||
reinterpret_cast<uint8_t const *>(options.gameID),
|
reinterpret_cast<uint8_t const *>(options.gameID->c_str()),
|
||||||
options.gameIDLen,
|
options.gameIDLen,
|
||||||
"manufacturer code"
|
"manufacturer code"
|
||||||
);
|
);
|
||||||
@@ -163,7 +167,7 @@ static void
|
|||||||
overwriteBytes(
|
overwriteBytes(
|
||||||
rom0,
|
rom0,
|
||||||
0x144,
|
0x144,
|
||||||
reinterpret_cast<uint8_t const *>(options.newLicensee),
|
reinterpret_cast<uint8_t const *>(options.newLicensee->c_str()),
|
||||||
options.newLicenseeLen,
|
options.newLicenseeLen,
|
||||||
"new licensee code"
|
"new licensee code"
|
||||||
);
|
);
|
||||||
|
|||||||
389
src/fix/main.cpp
389
src/fix/main.cpp
@@ -12,8 +12,8 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
#include "style.hpp"
|
#include "style.hpp"
|
||||||
@@ -27,10 +27,16 @@
|
|||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
|
|
||||||
|
// Flags which must be processed after the option parsing finishes
|
||||||
|
static struct LocalOptions {
|
||||||
|
std::optional<std::string> outputFileName; // -o
|
||||||
|
std::vector<std::string> inputFileNames; // <file>...
|
||||||
|
} localOptions;
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:VvW:w";
|
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:VvW:w";
|
||||||
|
|
||||||
// Variables for the long-only options
|
// Long-only option variable
|
||||||
static int longOpt; // `--color`
|
static int longOpt; // `--color`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -91,13 +97,191 @@ static Usage usage = {
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
static void parseByte(uint16_t &output, char name) {
|
static uint16_t parseByte(char const *input, char name) {
|
||||||
if (std::optional<uint64_t> value = parseWholeNumber(musl_optarg); !value) {
|
if (std::optional<uint64_t> value = parseWholeNumber(input); !value) {
|
||||||
fatal("Invalid argument for option '-%c'", name);
|
fatal("Invalid argument for option '-%c'", name);
|
||||||
} else if (*value > 0xFF) {
|
} else if (*value > 0xFF) {
|
||||||
fatal("Argument for option '-%c' must be between 0 and 0xFF", name);
|
fatal("Argument for option '-%c' must be between 0 and 0xFF", name);
|
||||||
} else {
|
} else {
|
||||||
output = *value;
|
return *value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parseArg(int ch, char *arg) {
|
||||||
|
switch (ch) {
|
||||||
|
case 'C':
|
||||||
|
case 'c':
|
||||||
|
options.model = ch == 'c' ? BOTH : CGB;
|
||||||
|
if (options.titleLen > 15) {
|
||||||
|
options.titleLen = 15;
|
||||||
|
assume(options.title.has_value());
|
||||||
|
warning(
|
||||||
|
WARNING_TRUNCATION, "Truncating title \"%s\" to 15 chars", options.title->c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
options.fixSpec = 0;
|
||||||
|
while (*arg) {
|
||||||
|
switch (*arg) {
|
||||||
|
#define overrideSpec(cur, bad, curFlag, badFlag) \
|
||||||
|
case cur: \
|
||||||
|
if (options.fixSpec & badFlag) { \
|
||||||
|
warnx("'%c' overriding '%c' in fix spec", cur, bad); \
|
||||||
|
} \
|
||||||
|
options.fixSpec = (options.fixSpec & ~badFlag) | curFlag; \
|
||||||
|
break
|
||||||
|
#define overrideSpecPair(fix, fixFlag, trash, trashFlag) \
|
||||||
|
overrideSpec(fix, trash, fixFlag, trashFlag); \
|
||||||
|
overrideSpec(trash, fix, trashFlag, fixFlag)
|
||||||
|
overrideSpecPair('l', FIX_LOGO, 'L', TRASH_LOGO);
|
||||||
|
overrideSpecPair('h', FIX_HEADER_SUM, 'H', TRASH_HEADER_SUM);
|
||||||
|
overrideSpecPair('g', FIX_GLOBAL_SUM, 'G', TRASH_GLOBAL_SUM);
|
||||||
|
#undef overrideSpec
|
||||||
|
#undef overrideSpecPair
|
||||||
|
|
||||||
|
default:
|
||||||
|
fatal("Invalid character '%c' in fix spec", *arg);
|
||||||
|
}
|
||||||
|
++arg;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
case 'h':
|
||||||
|
usage.printAndExit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
case 'i': {
|
||||||
|
options.gameID = arg;
|
||||||
|
size_t len = options.gameID->length();
|
||||||
|
if (len > 4) {
|
||||||
|
len = 4;
|
||||||
|
warning(
|
||||||
|
WARNING_TRUNCATION, "Truncating game ID \"%s\" to 4 chars", options.gameID->c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
options.gameIDLen = len;
|
||||||
|
if (options.titleLen > 11) {
|
||||||
|
options.titleLen = 11;
|
||||||
|
assume(options.title.has_value());
|
||||||
|
warning(
|
||||||
|
WARNING_TRUNCATION, "Truncating title \"%s\" to 11 chars", options.title->c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'j':
|
||||||
|
options.japanese = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'k': {
|
||||||
|
options.newLicensee = arg;
|
||||||
|
size_t len = options.newLicensee->length();
|
||||||
|
if (len > 2) {
|
||||||
|
len = 2;
|
||||||
|
warning(
|
||||||
|
WARNING_TRUNCATION,
|
||||||
|
"Truncating new licensee \"%s\" to 2 chars",
|
||||||
|
options.newLicensee->c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
options.newLicenseeLen = len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'L':
|
||||||
|
options.logoFilename = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'l':
|
||||||
|
options.oldLicensee = parseByte(arg, 'l');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
options.cartridgeType = mbc_ParseName(arg, options.tpp1Rev[0], options.tpp1Rev[1]);
|
||||||
|
if (options.cartridgeType == ROM_RAM || options.cartridgeType == ROM_RAM_BATTERY) {
|
||||||
|
warning(WARNING_MBC, "MBC \"%s\" is under-specified and poorly supported", arg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
options.romVersion = parseByte(arg, 'n');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'O':
|
||||||
|
warning(WARNING_OBSOLETE, "'-O' is deprecated; use '-Wno-overwrite' instead");
|
||||||
|
warnings.processWarningFlag("no-overwrite");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
localOptions.outputFileName = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
options.padValue = parseByte(arg, 'p');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r':
|
||||||
|
options.ramSize = parseByte(arg, 'r');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
options.sgb = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 't': {
|
||||||
|
options.title = arg;
|
||||||
|
size_t len = options.title->length();
|
||||||
|
uint8_t maxLen = options.gameID ? 11 : options.model != DMG ? 15 : 16;
|
||||||
|
|
||||||
|
if (len > maxLen) {
|
||||||
|
len = maxLen;
|
||||||
|
warning(
|
||||||
|
WARNING_TRUNCATION,
|
||||||
|
"Truncating title \"%s\" to %u chars",
|
||||||
|
options.title->c_str(),
|
||||||
|
maxLen
|
||||||
|
);
|
||||||
|
}
|
||||||
|
options.titleLen = len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
case 'V':
|
||||||
|
printf("rgbfix %s\n", get_package_version_string());
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
options.fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
|
||||||
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
case 'W':
|
||||||
|
warnings.processWarningFlag(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'w':
|
||||||
|
warnings.state.warningsEnabled = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0: // Long-only options
|
||||||
|
if (longOpt == 'c' && !style_Parse(arg)) {
|
||||||
|
fatal("Invalid argument for option '--color'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // Positional arguments
|
||||||
|
localOptions.inputFileNames.push_back(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
default:
|
||||||
|
usage.printAndExit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,18 +294,19 @@ static uint8_t const nintendoLogo[] = {
|
|||||||
static void initLogo() {
|
static void initLogo() {
|
||||||
if (options.logoFilename) {
|
if (options.logoFilename) {
|
||||||
FILE *logoFile;
|
FILE *logoFile;
|
||||||
if (strcmp(options.logoFilename, "-")) {
|
char const *logoFilename = options.logoFilename->c_str();
|
||||||
logoFile = fopen(options.logoFilename, "rb");
|
if (*options.logoFilename != "-") {
|
||||||
|
logoFile = fopen(logoFilename, "rb");
|
||||||
} else {
|
} else {
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
options.logoFilename = "<stdin>";
|
logoFilename = "<stdin>";
|
||||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
logoFile = stdin;
|
logoFile = stdin;
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
if (!logoFile) {
|
if (!logoFile) {
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to open \"%s\" for reading: %s", options.logoFilename, strerror(errno));
|
fatal("Failed to open \"%s\" for reading: %s", logoFilename, strerror(errno));
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
Defer closeLogo{[&] { fclose(logoFile); }};
|
Defer closeLogo{[&] { fclose(logoFile); }};
|
||||||
@@ -129,7 +314,7 @@ static void initLogo() {
|
|||||||
uint8_t logoBpp[sizeof(options.logo)];
|
uint8_t logoBpp[sizeof(options.logo)];
|
||||||
if (size_t nbRead = fread(logoBpp, 1, sizeof(logoBpp), logoFile);
|
if (size_t nbRead = fread(logoBpp, 1, sizeof(logoBpp), logoFile);
|
||||||
nbRead != sizeof(options.logo) || fgetc(logoFile) != EOF || ferror(logoFile)) {
|
nbRead != sizeof(options.logo) || fgetc(logoFile) != EOF || ferror(logoFile)) {
|
||||||
fatal("\"%s\" is not %zu bytes", options.logoFilename, sizeof(options.logo));
|
fatal("\"%s\" is not %zu bytes", logoFilename, sizeof(options.logo));
|
||||||
}
|
}
|
||||||
auto highs = [&logoBpp](size_t i) {
|
auto highs = [&logoBpp](size_t i) {
|
||||||
return (logoBpp[i * 2] & 0xF0) | ((logoBpp[i * 2 + 1] & 0xF0) >> 4);
|
return (logoBpp[i * 2] & 0xF0) | ((logoBpp[i * 2 + 1] & 0xF0) >> 4);
|
||||||
@@ -161,176 +346,7 @@ static void initLogo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
char const *outputFilename = nullptr;
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
||||||
|
|
||||||
// Parse CLI options
|
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
|
||||||
switch (ch) {
|
|
||||||
case 'C':
|
|
||||||
case 'c':
|
|
||||||
options.model = ch == 'c' ? BOTH : CGB;
|
|
||||||
if (options.titleLen > 15) {
|
|
||||||
options.titleLen = 15;
|
|
||||||
assume(options.title != nullptr);
|
|
||||||
warning(WARNING_TRUNCATION, "Truncating title \"%s\" to 15 chars", options.title);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'f':
|
|
||||||
options.fixSpec = 0;
|
|
||||||
while (*musl_optarg) {
|
|
||||||
switch (*musl_optarg) {
|
|
||||||
#define overrideSpec(cur, bad, curFlag, badFlag) \
|
|
||||||
case cur: \
|
|
||||||
if (options.fixSpec & badFlag) { \
|
|
||||||
warnx("'%c' overriding '%c' in fix spec", cur, bad); \
|
|
||||||
} \
|
|
||||||
options.fixSpec = (options.fixSpec & ~badFlag) | curFlag; \
|
|
||||||
break
|
|
||||||
#define overrideSpecPair(fix, fixFlag, trash, trashFlag) \
|
|
||||||
overrideSpec(fix, trash, fixFlag, trashFlag); \
|
|
||||||
overrideSpec(trash, fix, trashFlag, fixFlag)
|
|
||||||
overrideSpecPair('l', FIX_LOGO, 'L', TRASH_LOGO);
|
|
||||||
overrideSpecPair('h', FIX_HEADER_SUM, 'H', TRASH_HEADER_SUM);
|
|
||||||
overrideSpecPair('g', FIX_GLOBAL_SUM, 'G', TRASH_GLOBAL_SUM);
|
|
||||||
#undef overrideSpec
|
|
||||||
#undef overrideSpecPair
|
|
||||||
|
|
||||||
default:
|
|
||||||
fatal("Invalid character '%c' in fix spec", *musl_optarg);
|
|
||||||
}
|
|
||||||
++musl_optarg;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
case 'h':
|
|
||||||
usage.printAndExit(0);
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
case 'i': {
|
|
||||||
options.gameID = musl_optarg;
|
|
||||||
size_t len = strlen(options.gameID);
|
|
||||||
if (len > 4) {
|
|
||||||
len = 4;
|
|
||||||
warning(WARNING_TRUNCATION, "Truncating game ID \"%s\" to 4 chars", options.gameID);
|
|
||||||
}
|
|
||||||
options.gameIDLen = len;
|
|
||||||
if (options.titleLen > 11) {
|
|
||||||
options.titleLen = 11;
|
|
||||||
assume(options.title != nullptr);
|
|
||||||
warning(WARNING_TRUNCATION, "Truncating title \"%s\" to 11 chars", options.title);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'j':
|
|
||||||
options.japanese = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'k': {
|
|
||||||
options.newLicensee = musl_optarg;
|
|
||||||
size_t len = strlen(options.newLicensee);
|
|
||||||
if (len > 2) {
|
|
||||||
len = 2;
|
|
||||||
warning(
|
|
||||||
WARNING_TRUNCATION,
|
|
||||||
"Truncating new licensee \"%s\" to 2 chars",
|
|
||||||
options.newLicensee
|
|
||||||
);
|
|
||||||
}
|
|
||||||
options.newLicenseeLen = len;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'L':
|
|
||||||
options.logoFilename = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'l':
|
|
||||||
parseByte(options.oldLicensee, 'l');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'm':
|
|
||||||
options.cartridgeType =
|
|
||||||
mbc_ParseName(musl_optarg, options.tpp1Rev[0], options.tpp1Rev[1]);
|
|
||||||
if (options.cartridgeType == ROM_RAM || options.cartridgeType == ROM_RAM_BATTERY) {
|
|
||||||
warning(
|
|
||||||
WARNING_MBC, "MBC \"%s\" is under-specified and poorly supported", musl_optarg
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'n':
|
|
||||||
parseByte(options.romVersion, 'n');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'O':
|
|
||||||
warning(WARNING_OBSOLETE, "'-O' is deprecated; use '-Wno-overwrite' instead");
|
|
||||||
warnings.processWarningFlag("no-overwrite");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'o':
|
|
||||||
outputFilename = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'p':
|
|
||||||
parseByte(options.padValue, 'p');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'r':
|
|
||||||
parseByte(options.ramSize, 'r');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 's':
|
|
||||||
options.sgb = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 't': {
|
|
||||||
options.title = musl_optarg;
|
|
||||||
size_t len = strlen(options.title);
|
|
||||||
uint8_t maxLen = options.gameID ? 11 : options.model != DMG ? 15 : 16;
|
|
||||||
|
|
||||||
if (len > maxLen) {
|
|
||||||
len = maxLen;
|
|
||||||
warning(
|
|
||||||
WARNING_TRUNCATION, "Truncating title \"%s\" to %u chars", options.title, maxLen
|
|
||||||
);
|
|
||||||
}
|
|
||||||
options.titleLen = len;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
case 'V':
|
|
||||||
printf("rgbfix %s\n", get_package_version_string());
|
|
||||||
exit(0);
|
|
||||||
|
|
||||||
case 'v':
|
|
||||||
options.fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
|
|
||||||
break;
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
case 'W':
|
|
||||||
warnings.processWarningFlag(musl_optarg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'w':
|
|
||||||
warnings.state.warningsEnabled = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0: // Long-only options
|
|
||||||
if (longOpt == 'c' && !style_Parse(musl_optarg)) {
|
|
||||||
fatal("Invalid argument for option '--color'");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
default:
|
|
||||||
usage.printAndExit(1);
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((options.cartridgeType & 0xFF00) == TPP1 && !options.japanese) {
|
if ((options.cartridgeType & 0xFF00) == TPP1 && !options.japanese) {
|
||||||
warning(
|
warning(
|
||||||
@@ -382,19 +398,20 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
initLogo();
|
initLogo();
|
||||||
|
|
||||||
argv += musl_optind;
|
if (localOptions.inputFileNames.empty()) {
|
||||||
if (!*argv) {
|
|
||||||
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputFilename && argc != musl_optind + 1) {
|
if (localOptions.outputFileName && localOptions.inputFileNames.size() != 1) {
|
||||||
usage.printAndExit("If '-o' is set then only a single input file may be specified");
|
usage.printAndExit("If '-o' is set then only a single input file may be specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char const *outputFileName =
|
||||||
|
localOptions.outputFileName ? localOptions.outputFileName->c_str() : nullptr;
|
||||||
bool failed = warnings.nbErrors > 0;
|
bool failed = warnings.nbErrors > 0;
|
||||||
do {
|
for (std::string const &inputFileName : localOptions.inputFileNames) {
|
||||||
failed |= fix_ProcessFile(*argv, outputFilename);
|
failed |= fix_ProcessFile(inputFileName.c_str(), outputFileName);
|
||||||
} while (*++argv);
|
}
|
||||||
|
|
||||||
return failed;
|
return failed;
|
||||||
}
|
}
|
||||||
|
|||||||
347
src/gfx/main.cpp
347
src/gfx/main.cpp
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <ios>
|
#include <ios>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -11,11 +10,12 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
@@ -35,22 +35,23 @@ using namespace std::literals::string_view_literals;
|
|||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
|
|
||||||
|
// Flags which must be processed after the option parsing finishes
|
||||||
static struct LocalOptions {
|
static struct LocalOptions {
|
||||||
char const *externalPalSpec;
|
std::optional<std::string> externalPalSpec; // -c
|
||||||
bool autoAttrmap;
|
bool autoAttrmap; // -A
|
||||||
bool autoTilemap;
|
bool autoTilemap; // -T
|
||||||
bool autoPalettes;
|
bool autoPalettes; // -P
|
||||||
bool autoPalmap;
|
bool autoPalmap; // -Q
|
||||||
bool groupOutputs;
|
bool groupOutputs; // -O
|
||||||
bool reverse;
|
bool reverse; // -r
|
||||||
|
|
||||||
bool autoAny() const { return autoAttrmap || autoTilemap || autoPalettes || autoPalmap; }
|
bool autoAny() const { return autoAttrmap || autoTilemap || autoPalettes || autoPalmap; }
|
||||||
} localOptions;
|
} localOptions;
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "-Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:wXx:YZ";
|
static char const *optstring = "Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:wXx:YZ";
|
||||||
|
|
||||||
// Variables for the long-only options
|
// Long-only option variable
|
||||||
static int longOpt; // `--color`
|
static int longOpt; // `--color`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -137,75 +138,8 @@ static void skipBlankSpace(char const *&arg) {
|
|||||||
arg += strspn(arg, " \t");
|
arg += strspn(arg, " \t");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerInput(char const *arg) {
|
static void parseArg(int ch, char *arg) {
|
||||||
if (!options.input.empty()) {
|
char const *argPtr = arg; // Make a copy for scanning
|
||||||
usage.printAndExit(
|
|
||||||
"Input image specified more than once! (first \"%s\", then \"%s\")",
|
|
||||||
options.input.c_str(),
|
|
||||||
arg
|
|
||||||
);
|
|
||||||
} else if (arg[0] == '\0') { // Empty input path
|
|
||||||
usage.printAndExit("Input image path cannot be empty");
|
|
||||||
} else {
|
|
||||||
options.input = arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
|
|
||||||
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
|
||||||
File file;
|
|
||||||
if (!file.open(path, std::ios_base::in)) {
|
|
||||||
fatal("Error reading at-file \"%s\": %s", file.c_str(path), strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (std::vector<size_t> argvOfs;;) {
|
|
||||||
int c = file->sbumpc();
|
|
||||||
|
|
||||||
// First, discard any leading blank space
|
|
||||||
while (isBlankSpace(c)) {
|
|
||||||
c = file->sbumpc();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's a comment, discard everything until EOL
|
|
||||||
if (c == '#') {
|
|
||||||
c = file->sbumpc();
|
|
||||||
while (c != EOF && !isNewline(c)) {
|
|
||||||
c = file->sbumpc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == EOF) {
|
|
||||||
return argvOfs;
|
|
||||||
} else if (isNewline(c)) {
|
|
||||||
continue; // Start processing the next line
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alright, now we can parse the line
|
|
||||||
do {
|
|
||||||
argvOfs.push_back(argPool.size());
|
|
||||||
|
|
||||||
// Read one argument (until the next whitespace char).
|
|
||||||
// We know there is one because we already have its first character in `c`.
|
|
||||||
for (; c != EOF && !isWhitespace(c); c = file->sbumpc()) {
|
|
||||||
argPool.push_back(c);
|
|
||||||
}
|
|
||||||
argPool.push_back('\0');
|
|
||||||
|
|
||||||
// Discard blank space until the next argument (candidate)
|
|
||||||
while (isBlankSpace(c)) {
|
|
||||||
c = file->sbumpc();
|
|
||||||
}
|
|
||||||
} while (c != EOF && !isNewline(c)); // End if we reached EOL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses an arg vector, modifying `options` and `localOptions` as options are read.
|
|
||||||
// The `localOptions` struct is for flags which must be processed after the option parsing finishes.
|
|
||||||
// Returns `nullptr` if the vector was fully parsed, or a pointer (which is part of the arg vector)
|
|
||||||
// to an "at-file" path if one is encountered.
|
|
||||||
static char *parseArgv(int argc, char *argv[]) {
|
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
|
||||||
char const *arg = musl_optarg; // Make a copy for scanning
|
|
||||||
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 'A':
|
case 'A':
|
||||||
@@ -217,45 +151,39 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.attrmap.empty()) {
|
if (!options.attrmap.empty()) {
|
||||||
warnx("Overriding attrmap file \"%s\"", options.attrmap.c_str());
|
warnx("Overriding attrmap file \"%s\"", options.attrmap.c_str());
|
||||||
}
|
}
|
||||||
options.attrmap = musl_optarg;
|
options.attrmap = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'B':
|
case 'B':
|
||||||
parseBackgroundPalSpec(musl_optarg);
|
parseBackgroundPalSpec(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'b': {
|
case 'b': {
|
||||||
uint16_t number = readNumber(arg, "Bank 0 base tile ID", 0);
|
uint16_t number = readNumber(argPtr, "Bank 0 base tile ID", 0);
|
||||||
if (number >= 256) {
|
if (number >= 256) {
|
||||||
error("Bank 0 base tile ID must be below 256");
|
error("Bank 0 base tile ID must be below 256");
|
||||||
} else {
|
} else {
|
||||||
options.baseTileIDs[0] = number;
|
options.baseTileIDs[0] = number;
|
||||||
}
|
}
|
||||||
if (*arg == '\0') {
|
if (*argPtr == '\0') {
|
||||||
options.baseTileIDs[1] = 0;
|
options.baseTileIDs[1] = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
if (*arg != ',') {
|
if (*argPtr != ',') {
|
||||||
error(
|
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++arg; // Skip comma
|
++argPtr; // Skip comma
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
number = readNumber(arg, "Bank 1 base tile ID", 0);
|
number = readNumber(argPtr, "Bank 1 base tile ID", 0);
|
||||||
if (number >= 256) {
|
if (number >= 256) {
|
||||||
error("Bank 1 base tile ID must be below 256");
|
error("Bank 1 base tile ID must be below 256");
|
||||||
} else {
|
} else {
|
||||||
options.baseTileIDs[1] = number;
|
options.baseTileIDs[1] = number;
|
||||||
}
|
}
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error(
|
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -266,31 +194,31 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'c':
|
case 'c':
|
||||||
localOptions.externalPalSpec = nullptr; // Allow overriding a previous pal spec
|
localOptions.externalPalSpec = std::nullopt; // Allow overriding a previous pal spec
|
||||||
if (musl_optarg[0] == '#') {
|
if (arg[0] == '#') {
|
||||||
options.palSpecType = Options::EXPLICIT;
|
options.palSpecType = Options::EXPLICIT;
|
||||||
parseInlinePalSpec(musl_optarg);
|
parseInlinePalSpec(arg);
|
||||||
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
|
} else if (strcasecmp(arg, "embedded") == 0) {
|
||||||
// Use PLTE, error out if missing
|
// Use PLTE, error out if missing
|
||||||
options.palSpecType = Options::EMBEDDED;
|
options.palSpecType = Options::EMBEDDED;
|
||||||
} else if (strcasecmp(musl_optarg, "auto") == 0) {
|
} else if (strcasecmp(arg, "auto") == 0) {
|
||||||
options.palSpecType = Options::NO_SPEC;
|
options.palSpecType = Options::NO_SPEC;
|
||||||
} else if (strcasecmp(musl_optarg, "dmg") == 0) {
|
} else if (strcasecmp(arg, "dmg") == 0) {
|
||||||
options.palSpecType = Options::DMG;
|
options.palSpecType = Options::DMG;
|
||||||
parseDmgPalSpec(0xE4); // Same darkest-first order as `sortGrayscale`
|
parseDmgPalSpec(0xE4); // Same darkest-first order as `sortGrayscale`
|
||||||
} else if (strncasecmp(musl_optarg, "dmg=", literal_strlen("dmg=")) == 0) {
|
} else if (strncasecmp(arg, "dmg=", literal_strlen("dmg=")) == 0) {
|
||||||
options.palSpecType = Options::DMG;
|
options.palSpecType = Options::DMG;
|
||||||
parseDmgPalSpec(&musl_optarg[literal_strlen("dmg=")]);
|
parseDmgPalSpec(&arg[literal_strlen("dmg=")]);
|
||||||
} else {
|
} else {
|
||||||
options.palSpecType = Options::EXPLICIT;
|
options.palSpecType = Options::EXPLICIT;
|
||||||
localOptions.externalPalSpec = musl_optarg;
|
localOptions.externalPalSpec = arg;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'd':
|
case 'd':
|
||||||
options.bitDepth = readNumber(arg, "Bit depth", 2);
|
options.bitDepth = readNumber(argPtr, "Bit depth", 2);
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error("Bit depth ('-b') argument must be a valid number, not \"%s\"", musl_optarg);
|
error("Bit depth ('-b') argument must be a valid number, not \"%s\"", arg);
|
||||||
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
|
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
|
||||||
error("Bit depth must be 1 or 2, not %" PRIu8, options.bitDepth);
|
error("Bit depth must be 1 or 2, not %" PRIu8, options.bitDepth);
|
||||||
options.bitDepth = 2;
|
options.bitDepth = 2;
|
||||||
@@ -306,54 +234,54 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.inputTileset.empty()) {
|
if (!options.inputTileset.empty()) {
|
||||||
warnx("Overriding input tileset file \"%s\"", options.inputTileset.c_str());
|
warnx("Overriding input tileset file \"%s\"", options.inputTileset.c_str());
|
||||||
}
|
}
|
||||||
options.inputTileset = musl_optarg;
|
options.inputTileset = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'L':
|
case 'L':
|
||||||
options.inputSlice.left = readNumber(arg, "Input slice left coordinate");
|
options.inputSlice.left = readNumber(argPtr, "Input slice left coordinate");
|
||||||
if (options.inputSlice.left > INT16_MAX) {
|
if (options.inputSlice.left > INT16_MAX) {
|
||||||
error("Input slice left coordinate is out of range!");
|
error("Input slice left coordinate is out of range!");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
if (*arg != ',') {
|
if (*argPtr != ',') {
|
||||||
error("Missing comma after left coordinate in \"%s\"", musl_optarg);
|
error("Missing comma after left coordinate in \"%s\"", arg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++arg;
|
++argPtr;
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
options.inputSlice.top = readNumber(arg, "Input slice upper coordinate");
|
options.inputSlice.top = readNumber(argPtr, "Input slice upper coordinate");
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
if (*arg != ':') {
|
if (*argPtr != ':') {
|
||||||
error("Missing colon after upper coordinate in \"%s\"", musl_optarg);
|
error("Missing colon after upper coordinate in \"%s\"", arg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++arg;
|
++argPtr;
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
options.inputSlice.width = readNumber(arg, "Input slice width");
|
options.inputSlice.width = readNumber(argPtr, "Input slice width");
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
if (options.inputSlice.width == 0) {
|
if (options.inputSlice.width == 0) {
|
||||||
error("Input slice width may not be 0!");
|
error("Input slice width may not be 0!");
|
||||||
}
|
}
|
||||||
if (*arg != ',') {
|
if (*argPtr != ',') {
|
||||||
error("Missing comma after width in \"%s\"", musl_optarg);
|
error("Missing comma after width in \"%s\"", arg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++arg;
|
++argPtr;
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
options.inputSlice.height = readNumber(arg, "Input slice height");
|
options.inputSlice.height = readNumber(argPtr, "Input slice height");
|
||||||
if (options.inputSlice.height == 0) {
|
if (options.inputSlice.height == 0) {
|
||||||
error("Input slice height may not be 0!");
|
error("Input slice height may not be 0!");
|
||||||
}
|
}
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error("Unexpected extra characters after slice spec in \"%s\"", musl_optarg);
|
error("Unexpected extra characters after slice spec in \"%s\"", arg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'l': {
|
case 'l': {
|
||||||
uint16_t number = readNumber(arg, "Base palette ID", 0);
|
uint16_t number = readNumber(argPtr, "Base palette ID", 0);
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error("Base palette ID must be a valid number, not \"%s\"", musl_optarg);
|
error("Base palette ID must be a valid number, not \"%s\"", arg);
|
||||||
} else if (number >= 256) {
|
} else if (number >= 256) {
|
||||||
error("Base palette ID must be below 256");
|
error("Base palette ID must be below 256");
|
||||||
} else {
|
} else {
|
||||||
@@ -372,41 +300,35 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'N':
|
case 'N':
|
||||||
options.maxNbTiles[0] = readNumber(arg, "Number of tiles in bank 0", 256);
|
options.maxNbTiles[0] = readNumber(argPtr, "Number of tiles in bank 0", 256);
|
||||||
if (options.maxNbTiles[0] > 256) {
|
if (options.maxNbTiles[0] > 256) {
|
||||||
error("Bank 0 cannot contain more than 256 tiles");
|
error("Bank 0 cannot contain more than 256 tiles");
|
||||||
}
|
}
|
||||||
if (*arg == '\0') {
|
if (*argPtr == '\0') {
|
||||||
options.maxNbTiles[1] = 0;
|
options.maxNbTiles[1] = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
if (*arg != ',') {
|
if (*argPtr != ',') {
|
||||||
error(
|
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++arg; // Skip comma
|
++argPtr; // Skip comma
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
options.maxNbTiles[1] = readNumber(arg, "Number of tiles in bank 1", 256);
|
options.maxNbTiles[1] = readNumber(argPtr, "Number of tiles in bank 1", 256);
|
||||||
if (options.maxNbTiles[1] > 256) {
|
if (options.maxNbTiles[1] > 256) {
|
||||||
error("Bank 1 cannot contain more than 256 tiles");
|
error("Bank 1 cannot contain more than 256 tiles");
|
||||||
}
|
}
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error(
|
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'n': {
|
case 'n': {
|
||||||
uint16_t number = readNumber(arg, "Number of palettes", 256);
|
uint16_t number = readNumber(argPtr, "Number of palettes", 256);
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error("Number of palettes ('-n') must be a valid number, not \"%s\"", musl_optarg);
|
error("Number of palettes ('-n') must be a valid number, not \"%s\"", arg);
|
||||||
}
|
}
|
||||||
if (number > 256) {
|
if (number > 256) {
|
||||||
error("Number of palettes ('-n') must not exceed 256!");
|
error("Number of palettes ('-n') must not exceed 256!");
|
||||||
@@ -426,7 +348,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.output.empty()) {
|
if (!options.output.empty()) {
|
||||||
warnx("Overriding tile data file %s", options.output.c_str());
|
warnx("Overriding tile data file %s", options.output.c_str());
|
||||||
}
|
}
|
||||||
options.output = musl_optarg;
|
options.output = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'P':
|
case 'P':
|
||||||
@@ -438,7 +360,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.palettes.empty()) {
|
if (!options.palettes.empty()) {
|
||||||
warnx("Overriding palettes file %s", options.palettes.c_str());
|
warnx("Overriding palettes file %s", options.palettes.c_str());
|
||||||
}
|
}
|
||||||
options.palettes = musl_optarg;
|
options.palettes = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Q':
|
case 'Q':
|
||||||
@@ -450,23 +372,21 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.palmap.empty()) {
|
if (!options.palmap.empty()) {
|
||||||
warnx("Overriding palette map file %s", options.palmap.c_str());
|
warnx("Overriding palette map file %s", options.palmap.c_str());
|
||||||
}
|
}
|
||||||
options.palmap = musl_optarg;
|
options.palmap = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'r':
|
case 'r':
|
||||||
localOptions.reverse = true;
|
localOptions.reverse = true;
|
||||||
options.reversedWidth = readNumber(arg, "Reversed image stride");
|
options.reversedWidth = readNumber(argPtr, "Reversed image stride");
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error(
|
error("Reversed image stride ('-r') must be a valid number, not \"%s\"", arg);
|
||||||
"Reversed image stride ('-r') must be a valid number, not \"%s\"", musl_optarg
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 's':
|
case 's':
|
||||||
options.nbColorsPerPal = readNumber(arg, "Number of colors per palette", 4);
|
options.nbColorsPerPal = readNumber(argPtr, "Number of colors per palette", 4);
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error("Palette size ('-s') must be a valid number, not \"%s\"", musl_optarg);
|
error("Palette size ('-s') must be a valid number, not \"%s\"", arg);
|
||||||
}
|
}
|
||||||
if (options.nbColorsPerPal > 4) {
|
if (options.nbColorsPerPal > 4) {
|
||||||
error("Palette size ('-s') must not exceed 4!");
|
error("Palette size ('-s') must not exceed 4!");
|
||||||
@@ -484,7 +404,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.tilemap.empty()) {
|
if (!options.tilemap.empty()) {
|
||||||
warnx("Overriding tilemap file %s", options.tilemap.c_str());
|
warnx("Overriding tilemap file %s", options.tilemap.c_str());
|
||||||
}
|
}
|
||||||
options.tilemap = musl_optarg;
|
options.tilemap = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
@@ -498,7 +418,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
warnings.processWarningFlag(musl_optarg);
|
warnings.processWarningFlag(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
@@ -506,9 +426,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'x':
|
case 'x':
|
||||||
options.trim = readNumber(arg, "Number of tiles to trim", 0);
|
options.trim = readNumber(argPtr, "Number of tiles to trim", 0);
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error("Tile trim ('-x') argument must be a valid number, not \"%s\"", musl_optarg);
|
error("Tile trim ('-x') argument must be a valid number, not \"%s\"", arg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -527,17 +447,22 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0: // Long-only options
|
case 0: // Long-only options
|
||||||
if (longOpt == 'c' && !style_Parse(musl_optarg)) {
|
if (longOpt == 'c' && !style_Parse(arg)) {
|
||||||
fatal("Invalid argument for option '--color'");
|
fatal("Invalid argument for option '--color'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1: // Positional argument, requested by leading `-` in opt string
|
case 1: // Positional argument
|
||||||
if (musl_optarg[0] == '@') {
|
if (!options.input.empty()) {
|
||||||
// Instruct the caller to process that at-file
|
usage.printAndExit(
|
||||||
return &musl_optarg[1];
|
"Input image specified more than once! (first \"%s\", then \"%s\")",
|
||||||
|
options.input.c_str(),
|
||||||
|
arg
|
||||||
|
);
|
||||||
|
} else if (arg[0] == '\0') { // Empty input path
|
||||||
|
usage.printAndExit("Input image path cannot be empty");
|
||||||
} else {
|
} else {
|
||||||
registerInput(musl_optarg);
|
options.input = arg;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -546,9 +471,6 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
usage.printAndExit(1);
|
usage.printAndExit(1);
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr; // Done processing this argv
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
@@ -717,70 +639,7 @@ static void replaceExtension(std::string &path, char const *extension) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
struct AtFileStackEntry {
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
||||||
int parentInd; // Saved offset into parent argv
|
|
||||||
std::vector<char *> argv; // This context's arg pointer vec
|
|
||||||
|
|
||||||
AtFileStackEntry(int parentInd_, std::vector<char *> argv_)
|
|
||||||
: parentInd(parentInd_), argv(argv_) {}
|
|
||||||
};
|
|
||||||
std::vector<AtFileStackEntry> atFileStack;
|
|
||||||
|
|
||||||
// Parse CLI options
|
|
||||||
int curArgc = argc;
|
|
||||||
char **curArgv = argv;
|
|
||||||
std::vector<std::vector<char>> argPools;
|
|
||||||
for (;;) {
|
|
||||||
char *atFileName = parseArgv(curArgc, curArgv);
|
|
||||||
if (atFileName) {
|
|
||||||
// We need to allocate a new arg pool for each at-file, so as not to invalidate pointers
|
|
||||||
// previous at-files may have generated to their own arg pools.
|
|
||||||
// But for the same reason, the arg pool must also outlive the at-file's stack entry!
|
|
||||||
std::vector<char> &argPool = argPools.emplace_back();
|
|
||||||
|
|
||||||
// Copy `argv[0]` for error reporting, and because option parsing skips it
|
|
||||||
AtFileStackEntry &stackEntry =
|
|
||||||
atFileStack.emplace_back(musl_optind, std::vector{atFileName});
|
|
||||||
// It would be nice to compute the char pointers on the fly, but reallocs don't allow
|
|
||||||
// that; so we must compute the offsets after the pool is fixed
|
|
||||||
std::vector<size_t> offsets = readAtFile(&musl_optarg[1], argPool);
|
|
||||||
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
|
|
||||||
for (size_t ofs : offsets) {
|
|
||||||
stackEntry.argv.push_back(&argPool.data()[ofs]);
|
|
||||||
}
|
|
||||||
stackEntry.argv.push_back(nullptr); // Don't forget the arg vector terminator!
|
|
||||||
|
|
||||||
curArgc = stackEntry.argv.size() - 1;
|
|
||||||
curArgv = stackEntry.argv.data();
|
|
||||||
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
|
|
||||||
continue; // Begin scanning that arg vector
|
|
||||||
}
|
|
||||||
|
|
||||||
if (musl_optind != curArgc) {
|
|
||||||
// This happens if `--` is passed, process the remaining arg(s) as positional
|
|
||||||
assume(musl_optind < curArgc);
|
|
||||||
for (int i = musl_optind; i < curArgc; ++i) {
|
|
||||||
registerInput(argv[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop off the top stack entry, or end parsing if none
|
|
||||||
if (atFileStack.empty()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// OK to restore `optind` directly, because `optpos` must be 0 right now.
|
|
||||||
// (Providing 0 would be a "proper" reset, but we want to resume parsing)
|
|
||||||
musl_optind = atFileStack.back().parentInd;
|
|
||||||
atFileStack.pop_back();
|
|
||||||
if (atFileStack.empty()) {
|
|
||||||
curArgc = argc;
|
|
||||||
curArgv = argv;
|
|
||||||
} else {
|
|
||||||
std::vector<char *> &vec = atFileStack.back().argv;
|
|
||||||
curArgc = vec.size();
|
|
||||||
curArgv = vec.data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.nbColorsPerPal == 0) {
|
if (options.nbColorsPerPal == 0) {
|
||||||
options.nbColorsPerPal = 1u << options.bitDepth;
|
options.nbColorsPerPal = 1u << options.bitDepth;
|
||||||
@@ -823,7 +682,7 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
// Execute deferred external pal spec parsing, now that all other params are known
|
// Execute deferred external pal spec parsing, now that all other params are known
|
||||||
if (localOptions.externalPalSpec) {
|
if (localOptions.externalPalSpec) {
|
||||||
parseExternalPalSpec(localOptions.externalPalSpec);
|
parseExternalPalSpec(localOptions.externalPalSpec->c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseOutputConfig(); // LCOV_EXCL_LINE
|
verboseOutputConfig(); // LCOV_EXCL_LINE
|
||||||
|
|||||||
@@ -307,10 +307,10 @@ yy::parser::symbol_type yylex() {
|
|||||||
// Not marking as unreachable; this will generate a warning if any codepath forgets to return.
|
// Not marking as unreachable; this will generate a warning if any codepath forgets to return.
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lexer_Init(char const *linkerScriptName) {
|
bool lexer_Init(std::string const &linkerScriptName) {
|
||||||
if (LexerStackEntry &newContext = lexerStack.emplace_back(std::string(linkerScriptName));
|
if (LexerStackEntry &newContext = lexerStack.emplace_back(std::string(linkerScriptName));
|
||||||
!newContext.file.open(newContext.path, std::ios_base::in)) {
|
!newContext.file.open(newContext.path, std::ios_base::in)) {
|
||||||
error("Failed to open linker script \"%s\"", linkerScriptName);
|
error("Failed to open linker script \"%s\"", linkerScriptName.c_str());
|
||||||
lexerStack.clear();
|
lexerStack.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "backtrace.hpp"
|
#include "backtrace.hpp"
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "script.hpp" // Generated from script.y
|
#include "script.hpp" // Generated from script.y
|
||||||
#include "style.hpp"
|
#include "style.hpp"
|
||||||
@@ -33,12 +33,16 @@
|
|||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
|
|
||||||
static char const *linkerScriptName = nullptr; // -l
|
// Flags which must be processed after the option parsing finishes
|
||||||
|
static struct LocalOptions {
|
||||||
|
std::optional<std::string> linkerScriptName; // -l
|
||||||
|
std::vector<std::string> inputFileNames; // <file>...
|
||||||
|
} localOptions;
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "B:dhl:m:Mn:O:o:p:S:tVvW:wx";
|
static char const *optstring = "B:dhl:m:Mn:O:o:p:S:tVvW:wx";
|
||||||
|
|
||||||
// Variables for the long-only options
|
// Long-only option variable
|
||||||
static int longOpt; // `--color`
|
static int longOpt; // `--color`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -90,97 +94,6 @@ static Usage usage = {
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
static void verboseOutputConfig(int argc, char *argv[]) {
|
|
||||||
if (!checkVerbosity(VERB_CONFIG)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
style_Set(stderr, STYLE_MAGENTA, false);
|
|
||||||
|
|
||||||
fprintf(stderr, "rgblink %s\n", get_package_version_string());
|
|
||||||
|
|
||||||
printVVVVVVerbosity();
|
|
||||||
|
|
||||||
fputs("Options:\n", stderr);
|
|
||||||
// -d/--dmg
|
|
||||||
if (options.isDmgMode) {
|
|
||||||
fputs("\tDMG mode prohibits non-DMG section types\n", stderr);
|
|
||||||
}
|
|
||||||
// -t/--tiny
|
|
||||||
if (options.is32kMode) {
|
|
||||||
fputs("\tROM0 covers the full 32 KiB of ROM\n", stderr);
|
|
||||||
}
|
|
||||||
// -w/--wramx
|
|
||||||
if (options.isWRAM0Mode) {
|
|
||||||
fputs("\tWRAM0 covers the full 8 KiB of WRAM\n", stderr);
|
|
||||||
}
|
|
||||||
// -x/--nopad
|
|
||||||
if (options.disablePadding) {
|
|
||||||
fputs("\tNo padding at the end of the ROM file\n", stderr);
|
|
||||||
}
|
|
||||||
// -p/--pad
|
|
||||||
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padValue);
|
|
||||||
// -S/--scramble
|
|
||||||
if (options.scrambleROMX || options.scrambleWRAMX || options.scrambleSRAM) {
|
|
||||||
fputs("\tScramble: ", stderr);
|
|
||||||
if (options.scrambleROMX) {
|
|
||||||
fprintf(stderr, "ROMX = %" PRIu16, options.scrambleROMX);
|
|
||||||
if (options.scrambleWRAMX || options.scrambleSRAM) {
|
|
||||||
fputs(", ", stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options.scrambleWRAMX) {
|
|
||||||
fprintf(stderr, "WRAMX = %" PRIu16, options.scrambleWRAMX);
|
|
||||||
if (options.scrambleSRAM) {
|
|
||||||
fputs(", ", stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options.scrambleSRAM) {
|
|
||||||
fprintf(stderr, "SRAM = %" PRIu16, options.scrambleSRAM);
|
|
||||||
}
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
// file ...
|
|
||||||
if (musl_optind < argc) {
|
|
||||||
fprintf(stderr, "\tInput object files: ");
|
|
||||||
for (int i = musl_optind; i < argc; ++i) {
|
|
||||||
if (i > musl_optind) {
|
|
||||||
fputs(", ", stderr);
|
|
||||||
}
|
|
||||||
if (i - musl_optind == 10) {
|
|
||||||
fprintf(stderr, "and %d more", argc - i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fputs(argv[i], stderr);
|
|
||||||
}
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
auto printPath = [](char const *name, char const *path) {
|
|
||||||
if (path) {
|
|
||||||
fprintf(stderr, "\t%s: %s\n", name, path);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// -O/--overlay
|
|
||||||
printPath("Overlay file", options.overlayFileName);
|
|
||||||
// -l/--linkerscript
|
|
||||||
printPath("Linker script", linkerScriptName);
|
|
||||||
// -o/--output
|
|
||||||
printPath("Output ROM file", options.outputFileName);
|
|
||||||
// -m/--map
|
|
||||||
printPath("Output map file", options.mapFileName);
|
|
||||||
// -M/--no-sym-in-map
|
|
||||||
if (options.mapFileName && options.noSymInMap) {
|
|
||||||
fputs("\tNo symbols in map file\n", stderr);
|
|
||||||
}
|
|
||||||
// -n/--sym
|
|
||||||
printPath("Output sym file", options.symFileName);
|
|
||||||
fputs("Ready.\n", stderr);
|
|
||||||
|
|
||||||
style_Reset(stderr);
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
static size_t skipBlankSpace(char const *str) {
|
static size_t skipBlankSpace(char const *str) {
|
||||||
return strspn(str, " \t");
|
return strspn(str, " \t");
|
||||||
}
|
}
|
||||||
@@ -292,12 +205,10 @@ static void parseScrambleSpec(char *spec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
static void parseArg(int ch, char *arg) {
|
||||||
// Parse CLI options
|
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 'B':
|
case 'B':
|
||||||
if (!trace_ParseTraceDepth(musl_optarg)) {
|
if (!trace_ParseTraceDepth(arg)) {
|
||||||
fatal("Invalid argument for option '-B'");
|
fatal("Invalid argument for option '-B'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -313,10 +224,10 @@ int main(int argc, char *argv[]) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'l':
|
case 'l':
|
||||||
if (linkerScriptName) {
|
if (localOptions.linkerScriptName) {
|
||||||
warnx("Overriding linker script file \"%s\"", linkerScriptName);
|
warnx("Overriding linker script file \"%s\"", localOptions.linkerScriptName->c_str());
|
||||||
}
|
}
|
||||||
linkerScriptName = musl_optarg;
|
localOptions.linkerScriptName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
@@ -325,34 +236,34 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
case 'm':
|
case 'm':
|
||||||
if (options.mapFileName) {
|
if (options.mapFileName) {
|
||||||
warnx("Overriding map file \"%s\"", options.mapFileName);
|
warnx("Overriding map file \"%s\"", options.mapFileName->c_str());
|
||||||
}
|
}
|
||||||
options.mapFileName = musl_optarg;
|
options.mapFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'n':
|
case 'n':
|
||||||
if (options.symFileName) {
|
if (options.symFileName) {
|
||||||
warnx("Overriding sym file \"%s\"", options.symFileName);
|
warnx("Overriding sym file \"%s\"", options.symFileName->c_str());
|
||||||
}
|
}
|
||||||
options.symFileName = musl_optarg;
|
options.symFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'O':
|
case 'O':
|
||||||
if (options.overlayFileName) {
|
if (options.overlayFileName) {
|
||||||
warnx("Overriding overlay file \"%s\"", options.overlayFileName);
|
warnx("Overriding overlay file \"%s\"", options.overlayFileName->c_str());
|
||||||
}
|
}
|
||||||
options.overlayFileName = musl_optarg;
|
options.overlayFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'o':
|
case 'o':
|
||||||
if (options.outputFileName) {
|
if (options.outputFileName) {
|
||||||
warnx("Overriding output file \"%s\"", options.outputFileName);
|
warnx("Overriding output file \"%s\"", options.outputFileName->c_str());
|
||||||
}
|
}
|
||||||
options.outputFileName = musl_optarg;
|
options.outputFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
if (std::optional<uint64_t> value = parseWholeNumber(musl_optarg); !value) {
|
if (std::optional<uint64_t> value = parseWholeNumber(arg); !value) {
|
||||||
fatal("Invalid argument for option '-p'");
|
fatal("Invalid argument for option '-p'");
|
||||||
} else if (*value > 0xFF) {
|
} else if (*value > 0xFF) {
|
||||||
fatal("Argument for option '-p' must be between 0 and 0xFF");
|
fatal("Argument for option '-p' must be between 0 and 0xFF");
|
||||||
@@ -363,7 +274,7 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'S':
|
case 'S':
|
||||||
parseScrambleSpec(musl_optarg);
|
parseScrambleSpec(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 't':
|
case 't':
|
||||||
@@ -381,7 +292,7 @@ int main(int argc, char *argv[]) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
warnings.processWarningFlag(musl_optarg);
|
warnings.processWarningFlag(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
@@ -395,21 +306,120 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0: // Long-only options
|
case 0: // Long-only options
|
||||||
if (longOpt == 'c' && !style_Parse(musl_optarg)) {
|
if (longOpt == 'c' && !style_Parse(arg)) {
|
||||||
fatal("Invalid argument for option '--color'");
|
fatal("Invalid argument for option '--color'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 1: // Positional argument
|
||||||
|
localOptions.inputFileNames.push_back(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
default:
|
default:
|
||||||
usage.printAndExit(1);
|
usage.printAndExit(1);
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
static void verboseOutputConfig() {
|
||||||
|
if (!checkVerbosity(VERB_CONFIG)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseOutputConfig(argc, argv);
|
style_Set(stderr, STYLE_MAGENTA, false);
|
||||||
|
|
||||||
if (musl_optind == argc) {
|
fprintf(stderr, "rgblink %s\n", get_package_version_string());
|
||||||
|
|
||||||
|
printVVVVVVerbosity();
|
||||||
|
|
||||||
|
fputs("Options:\n", stderr);
|
||||||
|
// -d/--dmg
|
||||||
|
if (options.isDmgMode) {
|
||||||
|
fputs("\tDMG mode prohibits non-DMG section types\n", stderr);
|
||||||
|
}
|
||||||
|
// -t/--tiny
|
||||||
|
if (options.is32kMode) {
|
||||||
|
fputs("\tROM0 covers the full 32 KiB of ROM\n", stderr);
|
||||||
|
}
|
||||||
|
// -w/--wramx
|
||||||
|
if (options.isWRAM0Mode) {
|
||||||
|
fputs("\tWRAM0 covers the full 8 KiB of WRAM\n", stderr);
|
||||||
|
}
|
||||||
|
// -x/--nopad
|
||||||
|
if (options.disablePadding) {
|
||||||
|
fputs("\tNo padding at the end of the ROM file\n", stderr);
|
||||||
|
}
|
||||||
|
// -p/--pad
|
||||||
|
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padValue);
|
||||||
|
// -S/--scramble
|
||||||
|
if (options.scrambleROMX || options.scrambleWRAMX || options.scrambleSRAM) {
|
||||||
|
fputs("\tScramble: ", stderr);
|
||||||
|
if (options.scrambleROMX) {
|
||||||
|
fprintf(stderr, "ROMX = %" PRIu16, options.scrambleROMX);
|
||||||
|
if (options.scrambleWRAMX || options.scrambleSRAM) {
|
||||||
|
fputs(", ", stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.scrambleWRAMX) {
|
||||||
|
fprintf(stderr, "WRAMX = %" PRIu16, options.scrambleWRAMX);
|
||||||
|
if (options.scrambleSRAM) {
|
||||||
|
fputs(", ", stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.scrambleSRAM) {
|
||||||
|
fprintf(stderr, "SRAM = %" PRIu16, options.scrambleSRAM);
|
||||||
|
}
|
||||||
|
putc('\n', stderr);
|
||||||
|
}
|
||||||
|
// file ...
|
||||||
|
if (!localOptions.inputFileNames.empty()) {
|
||||||
|
fprintf(stderr, "\tInput object files: ");
|
||||||
|
size_t nbFiles = localOptions.inputFileNames.size();
|
||||||
|
for (size_t i = 0; i < nbFiles; ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
fputs(", ", stderr);
|
||||||
|
}
|
||||||
|
if (i == 10) {
|
||||||
|
fprintf(stderr, "and %zu more", nbFiles - i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fputs(localOptions.inputFileNames[i].c_str(), stderr);
|
||||||
|
}
|
||||||
|
putc('\n', stderr);
|
||||||
|
}
|
||||||
|
auto printPath = [](char const *name, std::optional<std::string> const &path) {
|
||||||
|
if (path) {
|
||||||
|
fprintf(stderr, "\t%s: %s\n", name, path->c_str());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// -O/--overlay
|
||||||
|
printPath("Overlay file", options.overlayFileName);
|
||||||
|
// -l/--linkerscript
|
||||||
|
printPath("Linker script", localOptions.linkerScriptName);
|
||||||
|
// -o/--output
|
||||||
|
printPath("Output ROM file", options.outputFileName);
|
||||||
|
// -m/--map
|
||||||
|
printPath("Output map file", options.mapFileName);
|
||||||
|
// -M/--no-sym-in-map
|
||||||
|
if (options.mapFileName && options.noSymInMap) {
|
||||||
|
fputs("\tNo symbols in map file\n", stderr);
|
||||||
|
}
|
||||||
|
// -n/--sym
|
||||||
|
printPath("Output sym file", options.symFileName);
|
||||||
|
fputs("Ready.\n", stderr);
|
||||||
|
|
||||||
|
style_Reset(stderr);
|
||||||
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
||||||
|
|
||||||
|
verboseOutputConfig();
|
||||||
|
|
||||||
|
if (localOptions.inputFileNames.empty()) {
|
||||||
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,16 +437,17 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read all object files first,
|
// Read all object files first,
|
||||||
obj_Setup(argc - musl_optind);
|
size_t nbFiles = localOptions.inputFileNames.size();
|
||||||
for (int i = musl_optind; i < argc; ++i) {
|
obj_Setup(nbFiles);
|
||||||
obj_ReadFile(argv[i], argc - i - 1);
|
for (size_t i = 0; i < nbFiles; ++i) {
|
||||||
|
obj_ReadFile(localOptions.inputFileNames[i], nbFiles - i - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply the linker script's modifications,
|
// apply the linker script's modifications,
|
||||||
if (linkerScriptName) {
|
if (localOptions.linkerScriptName) {
|
||||||
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
|
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
|
||||||
|
|
||||||
if (lexer_Init(linkerScriptName)) {
|
if (lexer_Init(*localOptions.linkerScriptName)) {
|
||||||
if (yy::parser parser; parser.parse() != 0) {
|
if (yy::parser parser; parser.parse() != 0) {
|
||||||
// Exited due to YYABORT or YYNOMEM
|
// Exited due to YYABORT or YYNOMEM
|
||||||
fatal("Unrecoverable error while reading linker script"); // LCOV_EXCL_LINE
|
fatal("Unrecoverable error while reading linker script"); // LCOV_EXCL_LINE
|
||||||
|
|||||||
@@ -405,9 +405,10 @@ static void readAssertion(
|
|||||||
tryReadString(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
|
tryReadString(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
void obj_ReadFile(std::string const &filePath, size_t fileID) {
|
||||||
FILE *file;
|
FILE *file;
|
||||||
if (strcmp(fileName, "-")) {
|
char const *fileName = filePath.c_str();
|
||||||
|
if (filePath != "-") {
|
||||||
file = fopen(fileName, "rb");
|
file = fopen(fileName, "rb");
|
||||||
} else {
|
} else {
|
||||||
fileName = "<stdin>";
|
fileName = "<stdin>";
|
||||||
@@ -553,6 +554,6 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void obj_Setup(unsigned int nbFiles) {
|
void obj_Setup(size_t nbFiles) {
|
||||||
nodes.resize(nbFiles);
|
nodes.resize(nbFiles);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,15 +208,16 @@ static void
|
|||||||
|
|
||||||
static void writeROM() {
|
static void writeROM() {
|
||||||
if (options.outputFileName) {
|
if (options.outputFileName) {
|
||||||
if (strcmp(options.outputFileName, "-")) {
|
char const *outputFileName = options.outputFileName->c_str();
|
||||||
outputFile = fopen(options.outputFileName, "wb");
|
if (*options.outputFileName != "-") {
|
||||||
|
outputFile = fopen(outputFileName, "wb");
|
||||||
} else {
|
} else {
|
||||||
options.outputFileName = "<stdout>";
|
outputFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
outputFile = stdout;
|
outputFile = stdout;
|
||||||
}
|
}
|
||||||
if (!outputFile) {
|
if (!outputFile) {
|
||||||
fatal("Failed to open output file \"%s\": %s", options.outputFileName, strerror(errno));
|
fatal("Failed to open output file \"%s\": %s", outputFileName, strerror(errno));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Defer closeOutputFile{[&] {
|
Defer closeOutputFile{[&] {
|
||||||
@@ -226,17 +227,16 @@ static void writeROM() {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
if (options.overlayFileName) {
|
if (options.overlayFileName) {
|
||||||
if (strcmp(options.overlayFileName, "-")) {
|
char const *overlayFileName = options.overlayFileName->c_str();
|
||||||
overlayFile = fopen(options.overlayFileName, "rb");
|
if (*options.overlayFileName != "-") {
|
||||||
|
overlayFile = fopen(overlayFileName, "rb");
|
||||||
} else {
|
} else {
|
||||||
options.overlayFileName = "<stdin>";
|
overlayFileName = "<stdin>";
|
||||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
overlayFile = stdin;
|
overlayFile = stdin;
|
||||||
}
|
}
|
||||||
if (!overlayFile) {
|
if (!overlayFile) {
|
||||||
fatal(
|
fatal("Failed to open overlay file \"%s\": %s", overlayFileName, strerror(errno));
|
||||||
"Failed to open overlay file \"%s\": %s", options.overlayFileName, strerror(errno)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Defer closeOverlayFile{[&] {
|
Defer closeOverlayFile{[&] {
|
||||||
@@ -548,15 +548,16 @@ static void writeSym() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(options.symFileName, "-")) {
|
char const *symFileName = options.symFileName->c_str();
|
||||||
symFile = fopen(options.symFileName, "w");
|
if (*options.symFileName != "-") {
|
||||||
|
symFile = fopen(symFileName, "w");
|
||||||
} else {
|
} else {
|
||||||
options.symFileName = "<stdout>";
|
symFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
||||||
symFile = stdout;
|
symFile = stdout;
|
||||||
}
|
}
|
||||||
if (!symFile) {
|
if (!symFile) {
|
||||||
fatal("Failed to open sym file \"%s\": %s", options.symFileName, strerror(errno));
|
fatal("Failed to open sym file \"%s\": %s", symFileName, strerror(errno));
|
||||||
}
|
}
|
||||||
Defer closeSymFile{[&] { fclose(symFile); }};
|
Defer closeSymFile{[&] { fclose(symFile); }};
|
||||||
|
|
||||||
@@ -598,15 +599,16 @@ static void writeMap() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(options.mapFileName, "-")) {
|
char const *mapFileName = options.mapFileName->c_str();
|
||||||
mapFile = fopen(options.mapFileName, "w");
|
if (*options.mapFileName != "-") {
|
||||||
|
mapFile = fopen(mapFileName, "w");
|
||||||
} else {
|
} else {
|
||||||
options.mapFileName = "<stdout>";
|
mapFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
||||||
mapFile = stdout;
|
mapFile = stdout;
|
||||||
}
|
}
|
||||||
if (!mapFile) {
|
if (!mapFile) {
|
||||||
fatal("Failed to open map file \"%s\": %s", options.mapFileName, strerror(errno));
|
fatal("Failed to open map file \"%s\": %s", mapFileName, strerror(errno));
|
||||||
}
|
}
|
||||||
Defer closeMapFile{[&] { fclose(mapFile); }};
|
Defer closeMapFile{[&] { fclose(mapFile); }};
|
||||||
|
|
||||||
|
|||||||
@@ -61,10 +61,9 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
for i in *.asm notexist.asm; do
|
for i in *.asm notexist.asm; do
|
||||||
flags=${i%.asm}.flags
|
|
||||||
RGBASMFLAGS="-Weverything -Bcollapse"
|
RGBASMFLAGS="-Weverything -Bcollapse"
|
||||||
if [ -f "$flags" ]; then
|
if [ -f "${i%.asm}.flags" ]; then
|
||||||
RGBASMFLAGS="$RGBASMFLAGS $(head -n 1 "$flags")" # Allow other lines to serve as comments
|
RGBASMFLAGS="$RGBASMFLAGS @${i%.asm}.flags"
|
||||||
fi
|
fi
|
||||||
for variant in '' ' piped'; do
|
for variant in '' ' piped'; do
|
||||||
(( tests++ ))
|
(( tests++ ))
|
||||||
@@ -134,7 +133,6 @@ for i in *.asm notexist.asm; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
for i in cli/*.flags; do
|
for i in cli/*.flags; do
|
||||||
RGBASMFLAGS="$(head -n 1 "$i")" # Allow other lines to serve as comments
|
|
||||||
(( tests++ ))
|
(( tests++ ))
|
||||||
echo "${bold}${green}${i%.flags}...${rescolors}${resbold}"
|
echo "${bold}${green}${i%.flags}...${rescolors}${resbold}"
|
||||||
if [ -e "${i%.flags}.out" ]; then
|
if [ -e "${i%.flags}.out" ]; then
|
||||||
@@ -147,7 +145,7 @@ for i in cli/*.flags; do
|
|||||||
else
|
else
|
||||||
desired_errput=/dev/null
|
desired_errput=/dev/null
|
||||||
fi
|
fi
|
||||||
"$RGBASM" $RGBASMFLAGS >"$output" 2>"$errput"
|
"$RGBASM" "@$i" >"$output" 2>"$errput"
|
||||||
|
|
||||||
tryDiff "$desired_output" "$output" out
|
tryDiff "$desired_output" "$output" out
|
||||||
our_rc=$?
|
our_rc=$?
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
-w -m mbc3+ram -r 0
|
-w -m mbc3+ram -r 0
|
||||||
The "-w" suppresses "-Wmbc" and "-Woverwrite" warnings
|
# The "-w" suppresses "-Wmbc" and "-Woverwrite" warnings
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
-m '$2a'
|
-m $2a
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
-i 'FOUR!'
|
-i FOUR!
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
-Cf h
|
-Cf h
|
||||||
Checks that the header checksum properly accounts for header modifications
|
# Checks that the header checksum properly accounts for header modifications
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
-Wno-overwrite -Cjv -t PM_CRYSTAL -i BYTE -n 0 -k 01 -l 0x33 -m 0x10 -r 3 -p 0
|
-Wno-overwrite -Cjv -t PM_CRYSTAL -i BYTE -n 0 -k 01 -l 0x33 -m 0x10 -r 3 -p 0
|
||||||
Checks that the -Wno-overwrite flag suppresses "Overwrote a non-zero byte" warnings from the rest
|
# Checks that the -Wno-overwrite flag suppresses "Overwrote a non-zero byte" warnings from the rest
|
||||||
|
|||||||
@@ -50,10 +50,14 @@ tryCmp () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runTest () {
|
runTest () {
|
||||||
|
if grep -qF ' ./' "$2/$1.flags"; then
|
||||||
flags=$(
|
flags=$(
|
||||||
head -n 1 "$2/$1.flags" | # Allow other lines to serve as comments
|
head -n 1 "$2/$1.flags" | # Allow other lines to serve as comments
|
||||||
sed "s# ./# ${src//#/\\#}/#g" # Prepend src directory to path arguments
|
sed "s# ./# ${src//#/\\#}/#g" # Prepend src directory to path arguments
|
||||||
)
|
)
|
||||||
|
else
|
||||||
|
flags="@$2/$1.flags"
|
||||||
|
fi
|
||||||
|
|
||||||
for variant in '' ' piped' ' output'; do
|
for variant in '' ' piped' ' output'; do
|
||||||
(( tests++ ))
|
(( tests++ ))
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
-t 0123456789ABCDEF -C
|
-t 0123456789ABCDEF -C
|
||||||
Checks that the CGB flag correctly truncates the title to 15 chars only,
|
# Checks that the CGB flag correctly truncates the title to 15 chars only,
|
||||||
even when it's specified *after* the title..!
|
# even when it's specified *after* the title..!
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
-C -t 0123456789ABCDEF
|
-C -t 0123456789ABCDEF
|
||||||
Checks that the CGB flag correctly truncates the title to 15 chars only
|
# Checks that the CGB flag correctly truncates the title to 15 chars only
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
-t 0123456789ABCDEF -c
|
-t 0123456789ABCDEF -c
|
||||||
Checks that the CGB compat flag correctly truncates the title to 15 chars only,
|
# Checks that the CGB compat flag correctly truncates the title to 15 chars only,
|
||||||
even when it's specified *after* the title..!
|
# even when it's specified *after* the title..!
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
-c -t 0123456789ABCDEF
|
-c -t 0123456789ABCDEF
|
||||||
Checks that the CGB compat flag correctly truncates the title to 15 chars only
|
# Checks that the CGB compat flag correctly truncates the title to 15 chars only
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
-t 0123456789ABCDEF -i rgbd
|
-t 0123456789ABCDEF -i rgbd
|
||||||
Checks that the game ID flag correctly truncates the title to 11 chars only,
|
# Checks that the game ID flag correctly truncates the title to 11 chars only,
|
||||||
even when it's specified *after* the title..!
|
# even when it's specified *after* the title..!
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
-i rgbd -t 0123456789ABCDEF
|
-i rgbd -t 0123456789ABCDEF
|
||||||
Checks that the game ID flag correctly truncates the title to 11 chars only
|
# Checks that the game ID flag correctly truncates the title to 11 chars only
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
-t "I LOVE YOU"
|
-t I_LOVE_YOU
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
-t "Game Boy dev rox"
|
-t Game_Boy_dev_rox
|
||||||
|
|||||||
Binary file not shown.
@@ -1,2 +1 @@
|
|||||||
-m MBC1337
|
-m MBC1337
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
-vp 69
|
-vp 69
|
||||||
Check that the global checksum is correctly affected by padding:
|
# Check that the global checksum is correctly affected by padding:
|
||||||
Padding adds extra bytes (carefully picked *not* to be 0, or other values),
|
# Padding adds extra bytes (carefully picked *not* to be 0, or other values),
|
||||||
which must be properly accounted for.
|
# which must be properly accounted for.
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
-f LHG
|
-f LHG
|
||||||
Checks that the global checksum is correctly affected by the header checksum
|
# Checks that the global checksum is correctly affected by the header checksum
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
-v
|
-v
|
||||||
Checks that the global checksum is correctly affected by the header checksum
|
# Checks that the global checksum is correctly affected by the header checksum
|
||||||
|
|||||||
Reference in New Issue
Block a user