Extend RGBASM and RGBLINK verbosity flags to have multiple levels like RGBGFX (#1772)

This commit is contained in:
Rangi
2025-08-02 17:10:10 -04:00
committed by GitHub
parent b51056f743
commit 752b273aec
28 changed files with 688 additions and 347 deletions

View File

@@ -53,6 +53,7 @@ set(rgbasm_src
"linkdefs.cpp"
"opmath.cpp"
"util.cpp"
"verbosity.cpp"
)
set(rgblink_src
@@ -72,6 +73,7 @@ set(rgblink_src
"linkdefs.cpp"
"opmath.cpp"
"util.cpp"
"verbosity.cpp"
)
set(rgbfix_src
@@ -92,6 +94,7 @@ set(rgbgfx_src
"gfx/rgba.cpp"
"gfx/warning.cpp"
"util.cpp"
"verbosity.cpp"
)
foreach(PROG "asm" "fix" "gfx" "link")

View File

@@ -15,6 +15,7 @@
#include "helpers.hpp"
#include "linkdefs.hpp"
#include "platform.hpp" // S_ISDIR (stat macro)
#include "verbosity.hpp"
#include "asm/lexer.hpp"
#include "asm/macro.hpp"
@@ -88,6 +89,28 @@ bool fstk_DumpCurrent() {
return true;
}
// LCOV_EXCL_START
void fstk_VerboseOutputConfig() {
assume(checkVerbosity(VERB_CONFIG));
// -I/--include
if (includePaths.size() > 1) {
fputs("\tInclude file paths:\n", stderr);
for (std::string const &path : includePaths) {
if (!path.empty()) {
fprintf(stderr, "\t - %s\n", path.c_str());
}
}
}
// -P/--preinclude
if (!preIncludeNames.empty()) {
fputs("\tPreincluded files:\n", stderr);
for (std::string const &name : preIncludeNames) {
fprintf(stderr, "\t - %s\n", name.c_str());
}
}
}
// LCOV_EXCL_STOP
std::shared_ptr<FileStackNode> fstk_GetFileStack() {
return contextStack.empty() ? nullptr : contextStack.top().fileInfo;
}
@@ -124,7 +147,6 @@ void fstk_AddIncludePath(std::string const &path) {
void fstk_AddPreIncludeFile(std::string const &path) {
preIncludeNames.emplace_front(path);
verbosePrint("Pre-included filename %s\n", path.c_str()); // LCOV_EXCL_LINE
}
static bool isValidFilePath(std::string const &path) {
@@ -308,7 +330,11 @@ bool fstk_FileError(std::string const &path, char const *functionName) {
// LCOV_EXCL_START
if (options.missingIncludeState == GEN_EXIT) {
verbosePrint(
"Aborting (-MG) on %s file '%s' (%s)\n", functionName, path.c_str(), strerror(errno)
VERB_NOTICE,
"Aborting (-MG) on %s file '%s' (%s)\n",
functionName,
path.c_str(),
strerror(errno)
);
return true;
}

View File

@@ -18,6 +18,7 @@
#include "helpers.hpp"
#include "util.hpp"
#include "verbosity.hpp"
#include "asm/fixpoint.hpp"
#include "asm/format.hpp"
@@ -325,7 +326,7 @@ void LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
if (filePath == "-") {
path = "<stdin>";
content.emplace<BufferedContent>(STDIN_FILENO);
verbosePrint("Opening stdin\n"); // LCOV_EXCL_LINE
verbosePrint(VERB_INFO, "Opening stdin\n"); // LCOV_EXCL_LINE
} else {
struct stat statBuf;
if (stat(filePath.c_str(), &statBuf) != 0) {
@@ -352,13 +353,17 @@ void LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
}
content.emplace<ViewedContent>(ptr, size);
verbosePrint("File \"%s\" is fully read\n", path.c_str()); // LCOV_EXCL_LINE
// LCOV_EXCL_START
verbosePrint(VERB_INFO, "File \"%s\" is fully read\n", path.c_str());
// LCOV_EXCL_STOP
} else {
// LCOV_EXCL_START
if (statBuf.st_size == 0) {
verbosePrint("File \"%s\" is empty\n", path.c_str());
verbosePrint(VERB_INFO, "File \"%s\" is empty\n", path.c_str());
} else {
verbosePrint("Failed to stat file \"%s\": %s\n", path.c_str(), strerror(errno));
verbosePrint(
VERB_INFO, "Failed to stat file \"%s\": %s\n", path.c_str(), strerror(errno)
);
}
// LCOV_EXCL_STOP
@@ -371,7 +376,7 @@ void LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
}
content.emplace<BufferedContent>(fd);
verbosePrint("File \"%s\" is opened\n", path.c_str()); // LCOV_EXCL_LINE
verbosePrint(VERB_INFO, "File \"%s\" is opened\n", path.c_str()); // LCOV_EXCL_LINE
}
}

View File

@@ -17,6 +17,7 @@
#include "parser.hpp" // Generated from parser.y
#include "usage.hpp"
#include "util.hpp" // UpperMap
#include "verbosity.hpp"
#include "version.hpp"
#include "asm/charmap.hpp"
@@ -28,23 +29,8 @@
Options options;
// Escapes Make-special chars from a string
static std::string make_escape(std::string &str) {
std::string escaped;
size_t pos = 0;
for (;;) {
// All dollars needs to be doubled
size_t nextPos = str.find('$', pos);
if (nextPos == std::string::npos) {
break;
}
escaped.append(str, pos, nextPos - pos);
escaped.append("$$");
pos = nextPos + literal_strlen("$");
}
escaped.append(str, pos, str.length() - pos);
return escaped;
}
static char const *dependFileName = nullptr; // -M
static std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
// Short options
static char const *optstring = "b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
@@ -105,6 +91,148 @@ static Usage usage(
);
// clang-format on
// LCOV_EXCL_START
static void verboseOutputConfig(int argc, char *argv[]) {
if (!checkVerbosity(VERB_CONFIG)) {
return;
}
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 [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);
}
// [-MG] [-MC]
}
fputs("Ready.\n", stderr);
}
// LCOV_EXCL_STOP
static std::string escapeMakeChars(std::string &str) {
std::string escaped;
size_t pos = 0;
for (;;) {
// All dollars needs to be doubled
size_t nextPos = str.find('$', pos);
if (nextPos == std::string::npos) {
break;
}
escaped.append(str, pos, nextPos - pos);
escaped.append("$$");
pos = nextPos + literal_strlen("$");
}
escaped.append(str, pos, str.length() - pos);
return escaped;
}
// Parse a comma-separated string of '-s/--state' features
static std::vector<StateFeature> parseStateFeatures(char *str) {
std::vector<StateFeature> features;
@@ -164,10 +292,6 @@ int main(int argc, char *argv[]) {
options.maxErrors = 100;
}
// Local options
char const *dependFileName = nullptr; // -M
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
switch (ch) {
char *endptr;
@@ -192,7 +316,7 @@ int main(int argc, char *argv[]) {
break;
case 'E':
sym_SetExportAll(true);
options.exportAll = true;
break;
case 'g':
@@ -211,21 +335,13 @@ int main(int argc, char *argv[]) {
break;
case 'M':
if (options.dependFile) {
warnx("Overriding dependfile %s", dependFileName);
}
if (strcmp("-", musl_optarg)) {
options.dependFile = fopen(musl_optarg, "w");
dependFileName = musl_optarg;
} else {
options.dependFile = stdout;
dependFileName = "<stdout>";
}
if (options.dependFile == nullptr) {
// LCOV_EXCL_START
fatal("Failed to open dependfile \"%s\": %s", dependFileName, strerror(errno));
// LCOV_EXCL_STOP
if (dependFileName) {
warnx(
"Overriding dependency file %s",
strcmp(dependFileName, "-") ? dependFileName : "<stdout>"
);
}
dependFileName = musl_optarg;
break;
case 'o':
@@ -233,7 +349,6 @@ int main(int argc, char *argv[]) {
warnx("Overriding output filename %s", options.objectFileName.c_str());
}
options.objectFileName = musl_optarg;
verbosePrint("Output filename %s\n", options.objectFileName.c_str()); // LCOV_EXCL_LINE
break;
case 'P':
@@ -295,7 +410,6 @@ int main(int argc, char *argv[]) {
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
warnx("Overriding state filename %s", name);
}
verbosePrint("State filename %s\n", name); // LCOV_EXCL_LINE
stateFileSpecs.emplace(name, std::move(features));
break;
}
@@ -306,7 +420,7 @@ int main(int argc, char *argv[]) {
case 'v':
// LCOV_EXCL_START
options.verbose = true;
incrementVerbosity();
break;
// LCOV_EXCL_STOP
@@ -352,7 +466,7 @@ int main(int argc, char *argv[]) {
case 'T': {
std::string newTarget = musl_optarg;
if (depType == 'Q') {
newTarget = make_escape(newTarget);
newTarget = escapeMakeChars(newTarget);
}
if (!options.targetFileName.empty()) {
options.targetFileName += ' ';
@@ -373,6 +487,8 @@ int main(int argc, char *argv[]) {
options.targetFileName = options.objectFileName;
}
verboseOutputConfig(argc, argv);
if (argc == musl_optind) {
usage.printAndExit("Please specify an input file (pass `-` to read from standard input)");
} else if (argc != musl_optind + 1) {
@@ -381,7 +497,20 @@ int main(int argc, char *argv[]) {
std::string mainFileName = argv[musl_optind];
verbosePrint("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
verbosePrint(VERB_NOTICE, "Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
if (dependFileName) {
if (strcmp("-", dependFileName)) {
options.dependFile = fopen(dependFileName, "w");
if (options.dependFile == nullptr) {
// LCOV_EXCL_START
fatal("Failed to open dependency file \"%s\": %s", dependFileName, strerror(errno));
// LCOV_EXCL_STOP
}
} else {
options.dependFile = stdout;
}
}
if (options.dependFile && options.targetFileName.empty()) {
fatal("Dependency files can only be created if a target file is specified with either "

View File

@@ -17,6 +17,7 @@
#include "asm/fstack.hpp"
#include "asm/lexer.hpp"
#include "asm/macro.hpp"
#include "asm/main.hpp"
#include "asm/output.hpp"
#include "asm/warning.hpp"
@@ -39,8 +40,6 @@ static char savedDATE[256];
static char savedTIMESTAMP_ISO8601_LOCAL[256];
static char savedTIMESTAMP_ISO8601_UTC[256];
static bool exportAll = false; // -E
bool sym_IsPC(Symbol const *sym) {
return sym == PCSymbol;
}
@@ -497,7 +496,7 @@ static Symbol *addLabel(std::string const &symName) {
sym->type = SYM_LABEL;
sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
// Don't export anonymous labels
if (exportAll && !symName.starts_with('!')) {
if (options.exportAll && !symName.starts_with('!')) {
sym->isExported = true;
}
sym->section = sect_GetSymbolSection();
@@ -635,11 +634,6 @@ Symbol *sym_Ref(std::string const &symName) {
return sym;
}
// Set whether to export all relocatable symbols by default
void sym_SetExportAll(bool set) {
exportAll = set;
}
// Define the built-in symbols
void sym_Init(time_t now) {
PCSymbol = &createSymbol("@"s);

View File

@@ -65,13 +65,14 @@ static Usage usage(
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
" [-W warning] <file> ...\n"
"Useful options:\n"
" -m, --mbc-type <value> set the MBC type byte to this value; refer\n"
" to the man page for a list of values\n"
" -p, --pad-value <value> pad to the next valid size using this value\n"
" -r, --ram-size <code> set the cart RAM size byte to this value\n"
" -o, --output <path> set the output file\n"
" -V, --version print RGBFIX version and exit\n"
" -v, --validate fix the header logo and both checksums (-f lhg)\n"
" -m, --mbc-type <value> set the MBC type byte to this value; `-m help'\n"
" or `-m list' prints the accepted values\n"
" -p, --pad-value <value> pad to the next valid size using this value\n"
" -r, --ram-size <code> set the cart RAM size byte to this value\n"
" -o, --output <path> set the output file\n"
" -V, --version print RGBFIX version and exit\n"
" -v, --validate fix the header logo and both checksums (`-f lhg')\n"
" -W, --warning <warning> enable or disable warnings\n"
"\n"
"For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n"
);

View File

@@ -19,6 +19,7 @@
#include "file.hpp"
#include "platform.hpp"
#include "usage.hpp"
#include "verbosity.hpp"
#include "version.hpp"
#include "gfx/pal_spec.hpp"
@@ -40,18 +41,6 @@ static struct LocalOptions {
bool reverse;
} localOptions;
void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
// LCOV_EXCL_START
if (verbosity >= level) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
// LCOV_EXCL_STOP
}
// 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";
@@ -107,11 +96,12 @@ static Usage usage(
" [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
"Useful options:\n"
" -m, --mirror-tiles optimize out mirrored tiles\n"
" -o, --output <path> output the tile data to this path\n"
" -t, --tilemap <path> output the tile map to this path\n"
" -u, --unique-tiles optimize out identical tiles\n"
" -V, --version print RGBGFX version and exit\n"
" -m, --mirror-tiles optimize out mirrored tiles\n"
" -o, --output <path> output the tile data to this path\n"
" -t, --tilemap <path> output the tile map to this path\n"
" -u, --unique-tiles optimize out identical tiles\n"
" -V, --version print RGBGFX version and exit\n"
" -W, --warning <warning> enable or disable warnings\n"
"\n"
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n"
);
@@ -536,9 +526,7 @@ static char *parseArgv(int argc, char *argv[]) {
// LCOV_EXCL_STOP
case 'v':
// LCOV_EXCL_START
if (options.verbosity < Options::VERB_VVVVVV) {
++options.verbosity;
}
incrementVerbosity();
break;
// LCOV_EXCL_STOP
case 'W':
@@ -580,93 +568,66 @@ static char *parseArgv(int argc, char *argv[]) {
return nullptr; // Done processing this argv
}
// LCOV_EXCL_START
static void verboseOutputConfig() {
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
if (options.verbosity >= Options::VERB_VVVVVV) {
putc('\n', stderr);
// clang-format off: vertically align values
static std::array<uint16_t, 21> gfx{
0b0111111110,
0b1111111111,
0b1110011001,
0b1110011001,
0b1111111111,
0b1111111111,
0b1110000001,
0b1111000011,
0b0111111110,
0b0001111000,
0b0111111110,
0b1111111111,
0b1111111111,
0b1111111111,
0b1101111011,
0b1101111011,
0b0011111100,
0b0011001100,
0b0111001110,
0b0111001110,
0b0111001110,
};
// clang-format on
static std::array<char const *, 3> textbox{
" ,----------------------------------------.",
" | Augh, dimensional interference again?! |",
" `----------------------------------------'",
};
for (size_t i = 0; i < gfx.size(); ++i) {
uint16_t row = gfx[i];
for (uint8_t _ = 0; _ < 10; ++_) {
unsigned char c = row & 1 ? '0' : ' ';
putc(c, stderr);
// Double the pixel horizontally, otherwise the aspect ratio looks wrong
putc(c, stderr);
row >>= 1;
}
if (i < textbox.size()) {
fputs(textbox[i], stderr);
}
putc('\n', stderr);
}
putc('\n', stderr);
if (!checkVerbosity(VERB_CONFIG)) {
return;
}
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
printVVVVVVerbosity();
fputs("Options:\n", stderr);
// -Z/--columns
if (options.columnMajor) {
fputs("\tVisit image in column-major order\n", stderr);
}
// -u/--unique-tiles
if (options.allowDedup) {
fputs("\tAllow deduplicating tiles\n", stderr);
fputs("\tAllow deduplicating identical tiles\n", stderr);
}
if (options.allowMirroringX) {
// -m/--mirror-tiles
if (options.allowMirroringX && options.allowMirroringY) {
fputs("\tAllow deduplicating mirrored tiles\n", stderr);
}
// -X/--mirror-x
else if (options.allowMirroringX) {
fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr);
}
if (options.allowMirroringY) {
// -Y/--mirror-y
else if (options.allowMirroringY) {
fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr);
}
// -C/--color-curve
if (options.useColorCurve) {
fputs("\tUse color curve\n", stderr);
}
// -d/--depth
fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth);
// -x/--trim-end
if (options.trim != 0) {
fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim);
}
// -n/--nb-palettes
fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes);
// -s/--palette-size
fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal);
fprintf(stderr, "\t%s palette spec\n", [] {
switch (options.palSpecType) {
case Options::NO_SPEC:
return "No";
case Options::EXPLICIT:
return "Explicit";
case Options::EMBEDDED:
return "Embedded";
case Options::DMG:
return "DMG";
}
return "???";
}());
// -c/--colors
if (options.palSpecType != Options::NO_SPEC) {
fprintf(stderr, "\t%s palette spec\n", [] {
switch (options.palSpecType) {
case Options::EXPLICIT:
return "Explicit";
case Options::EMBEDDED:
return "Embedded";
case Options::DMG:
return "DMG";
default:
return "???";
}
}());
}
if (options.palSpecType == Options::EXPLICIT) {
fputs("\t[\n", stderr);
for (auto const &pal : options.palSpec) {
@@ -682,40 +643,65 @@ static void verboseOutputConfig() {
}
fputs("\t]\n", stderr);
}
// -L/--slice
if (options.inputSlice.specified()) {
fprintf(
stderr,
"\tInput image slice: %" PRIu16 "x%" PRIu16 " pixels starting at (%" PRIu16 ", %" PRIu16
")\n",
options.inputSlice.width,
options.inputSlice.height,
options.inputSlice.left,
options.inputSlice.top
);
}
// -b/--base-tiles
if (options.baseTileIDs[0] || options.baseTileIDs[1]) {
fprintf(
stderr,
"\tBase tile IDs: bank 0 = 0x%02" PRIx8 ", bank 1 = 0x%02" PRIx8 "\n",
options.baseTileIDs[0],
options.baseTileIDs[1]
);
}
// -l/--base-palette
if (options.basePalID) {
fprintf(stderr, "\tBase palette ID: %" PRIu8 "\n", options.basePalID);
}
// -N/--nb-tiles
fprintf(
stderr,
"\tInput image slice: %" PRIu16 "x%" PRIu16 " pixels starting at (%" PRIu16 ", %" PRIu16
")\n",
options.inputSlice.width,
options.inputSlice.height,
options.inputSlice.left,
options.inputSlice.top
);
fprintf(
stderr,
"\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n",
options.baseTileIDs[0],
options.baseTileIDs[1]
);
fprintf(stderr, "\tBase palette ID: %" PRIu8 "\n", options.basePalID);
fprintf(
stderr,
"\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
"\tMaximum %" PRIu16 " tiles in bank 0, and %" PRIu16 " in bank 1\n",
options.maxNbTiles[0],
options.maxNbTiles[1]
);
// -O/--group-outputs (influences other options)
auto printPath = [](char const *name, std::string const &path) {
if (!path.empty()) {
fprintf(stderr, "\t%s: %s\n", name, path.c_str());
}
};
// file
printPath("Input image", options.input);
// -i/--input-tileset
printPath("Input tileset", options.inputTileset);
// -o/--output
printPath("Output tile data", options.output);
// -t/--tilemap or -T/--auto-tilemap
printPath("Output tilemap", options.tilemap);
// -a/--attrmap or -A/--auto-attrmap
printPath("Output attrmap", options.attrmap);
// -p/--palette or -P/--auto-palette
printPath("Output palettes", options.palettes);
// -q/--palette-map or -Q/--auto-palette-map
printPath("Output palette map", options.palmap);
// -r/--reverse
if (localOptions.reverse) {
fprintf(stderr, "\tReverse image width: %" PRIu16 " tiles\n", options.reversedWidth);
}
fputs("Ready.\n", stderr);
}
// LCOV_EXCL_STOP
// Manual implementation of std::filesystem::path.replace_extension().
// macOS <10.15 did not support std::filesystem::path.
@@ -839,11 +825,7 @@ int main(int argc, char *argv[]) {
parseExternalPalSpec(localOptions.externalPalSpec);
}
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_CFG) {
verboseOutputConfig();
}
// LCOV_EXCL_STOP
verboseOutputConfig(); // LCOV_EXCL_LINE
// Do not do anything if option parsing went wrong.
requireZeroErrors();

View File

@@ -14,6 +14,7 @@
#include <utility>
#include "helpers.hpp"
#include "verbosity.hpp"
#include "gfx/color_set.hpp"
#include "gfx/main.hpp"
@@ -247,6 +248,10 @@ public:
static void verboseOutputAssignments(
std::vector<AssignedSets> const &assignments, std::vector<ColorSet> const &colorSets
) {
if (!checkVerbosity(VERB_INFO)) {
return;
}
for (AssignedSets const &assignment : assignments) {
fputs("{ ", stderr);
for (ColorSetAttrs const &attrs : assignment) {
@@ -284,9 +289,7 @@ static void decant(std::vector<AssignedSets> &assignments, std::vector<ColorSet>
}
};
options.verbosePrint(
Options::VERB_DEBUG, "%zu palettes before decanting\n", assignments.size()
);
verbosePrint(VERB_DEBUG, "%zu palettes before decanting\n", assignments.size());
// Decant on palettes
decantOn([&colorSets](AssignedSets &to, AssignedSets &from) {
@@ -298,9 +301,7 @@ static void decant(std::vector<AssignedSets> &assignments, std::vector<ColorSet>
from.clear();
}
});
options.verbosePrint(
Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n", assignments.size()
);
verbosePrint(VERB_DEBUG, "%zu palettes after decanting on palettes\n", assignments.size());
// Decant on "components" (color sets sharing colors)
decantOn([&colorSets](AssignedSets &to, AssignedSets &from) {
@@ -344,8 +345,8 @@ static void decant(std::vector<AssignedSets> &assignments, std::vector<ColorSet>
}
}
});
options.verbosePrint(
Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", assignments.size()
verbosePrint(
VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", assignments.size()
);
// Decant on individual color sets
@@ -357,15 +358,11 @@ static void decant(std::vector<AssignedSets> &assignments, std::vector<ColorSet>
}
}
});
options.verbosePrint(
Options::VERB_DEBUG, "%zu palettes after decanting on color sets\n", assignments.size()
);
verbosePrint(VERB_DEBUG, "%zu palettes after decanting on color sets\n", assignments.size());
}
std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet> const &colorSets) {
options.verbosePrint(
Options::VERB_LOG_ACT, "Paginating palettes using \"overload-and-remove\" strategy...\n"
);
verbosePrint(VERB_NOTICE, "Paginating palettes using \"overload-and-remove\" strategy...\n");
// Sort the color sets by size, which improves the packing algorithm's efficiency
auto const indexOfLargestColorSetFirst = [&colorSets](size_t left, size_t right) {
@@ -389,7 +386,7 @@ std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet>
!queue.empty();
queue.pop()) {
ColorSetAttrs const &attrs = queue.front(); // Valid until the `queue.pop()`
options.verbosePrint(Options::VERB_TRACE, "Handling color set %zu\n", attrs.colorSetIndex);
verbosePrint(VERB_TRACE, "Handling color set %zu\n", attrs.colorSetIndex);
ColorSet const &colorSet = colorSets[attrs.colorSetIndex];
size_t bestPalIndex = assignments.size();
@@ -404,8 +401,8 @@ std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet>
}
uint32_t relSize = assignments[i].relSizeOf(colorSet);
options.verbosePrint(
Options::VERB_TRACE,
verbosePrint(
VERB_TRACE,
" Relative size to palette %zu (of %zu): %" PRIu32 " (size = %zu)\n",
i,
assignments.size(),
@@ -420,8 +417,8 @@ std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet>
if (bestPalIndex == assignments.size()) {
// Found nowhere to put it, create a new page containing just that one
options.verbosePrint(
Options::VERB_TRACE,
verbosePrint(
VERB_TRACE,
"Assigning color set %zu to new palette %zu\n",
attrs.colorSetIndex,
bestPalIndex
@@ -430,8 +427,8 @@ std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet>
continue;
}
options.verbosePrint(
Options::VERB_TRACE,
verbosePrint(
VERB_TRACE,
"Assigning color set %zu to palette %zu\n",
attrs.colorSetIndex,
bestPalIndex
@@ -448,8 +445,8 @@ std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet>
uint32_t relSize1 = bestPal.relSizeOf(colorSet1);
uint32_t relSize2 = bestPal.relSizeOf(colorSet2);
options.verbosePrint(
Options::VERB_TRACE,
verbosePrint(
VERB_TRACE,
" Color sets %zu <=> %zu: Efficiency: %zu / %" PRIu32 " <=> %zu / "
"%" PRIu32 "\n",
attrs1.colorSetIndex,
@@ -470,8 +467,8 @@ std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet>
// If this overloads the palette, get it back to normal (if possible)
while (bestPal.volume() > options.maxOpaqueColors()) {
options.verbosePrint(
Options::VERB_TRACE,
verbosePrint(
VERB_TRACE,
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
bestPalIndex,
bestPal.volume(),
@@ -488,13 +485,13 @@ std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet>
// All efficiencies are identical iff min equals max
if (compareEfficiency(*minEfficiencyIter, *maxEfficiencyIter) == 0) {
options.verbosePrint(Options::VERB_TRACE, " All efficiencies are identical\n");
verbosePrint(VERB_TRACE, " All efficiencies are identical\n");
break;
}
// Remove the color set with minimal efficiency
options.verbosePrint(
Options::VERB_TRACE, " Removing color set %zu\n", minEfficiencyIter->colorSetIndex
verbosePrint(
VERB_TRACE, " Removing color set %zu\n", minEfficiencyIter->colorSetIndex
);
queue.emplace(std::move(*minEfficiencyIter));
queue.back().banFrom(bestPalIndex); // Ban it from this palette
@@ -526,16 +523,16 @@ std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet>
return pal.canFit(colorSet);
});
if (iter == assignments.end()) { // No such page, create a new one
options.verbosePrint(
Options::VERB_DEBUG,
verbosePrint(
VERB_DEBUG,
"Adding new palette (%zu) for overflowing color set %zu\n",
assignments.size(),
attrs.colorSetIndex
);
assignments.emplace_back(colorSets, std::move(attrs));
} else {
options.verbosePrint(
Options::VERB_DEBUG,
verbosePrint(
VERB_DEBUG,
"Assigning overflowing color set %zu to palette %zu\n",
attrs.colorSetIndex,
iter - assignments.begin()
@@ -544,21 +541,13 @@ std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet>
}
}
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) {
verboseOutputAssignments(assignments, colorSets);
}
// LCOV_EXCL_STOP
verboseOutputAssignments(assignments, colorSets); // LCOV_EXCL_LINE
// "Decant" the result
decant(assignments, colorSets);
// Note that the result does not contain any empty palettes
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) {
verboseOutputAssignments(assignments, colorSets);
}
// LCOV_EXCL_STOP
verboseOutputAssignments(assignments, colorSets); // LCOV_EXCL_LINE
std::vector<size_t> mappings(colorSets.size());
for (size_t i = 0; i < assignments.size(); ++i) {

View File

@@ -5,11 +5,12 @@
#include <algorithm>
#include "helpers.hpp"
#include "verbosity.hpp"
#include "gfx/main.hpp"
void sortIndexed(std::vector<Palette> &palettes, std::vector<Rgba> const &embPal) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes using embedded palette...\n");
verbosePrint(VERB_NOTICE, "Sorting palettes using embedded palette...\n");
for (Palette &pal : palettes) {
std::sort(RANGE(pal), [&](uint16_t lhs, uint16_t rhs) {
@@ -35,7 +36,7 @@ void sortIndexed(std::vector<Palette> &palettes, std::vector<Rgba> const &embPal
void sortGrayscale(
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, NB_COLOR_SLOTS> const &colors
) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palette by grayscale bins...\n");
verbosePrint(VERB_NOTICE, "Sorting palette by grayscale bins...\n");
// This method is only applicable if there are at most as many colors as colors per palette, so
// we should only have a single palette.
@@ -59,7 +60,7 @@ static unsigned int luminance(uint16_t color) {
}
void sortRgb(std::vector<Palette> &palettes) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by luminance...\n");
verbosePrint(VERB_NOTICE, "Sorting palettes by luminance...\n");
for (Palette &pal : palettes) {
// Sort from lightest to darkest

View File

@@ -4,6 +4,8 @@
#include <png.h>
#include "verbosity.hpp"
#include "gfx/main.hpp"
#include "gfx/rgba.hpp"
#include "gfx/warning.hpp"
@@ -47,7 +49,7 @@ static void readData(png_structp png, png_bytep data, size_t length) {
Png::Png(char const *filename, std::streambuf &file) {
Input input(filename, file);
options.verbosePrint(Options::VERB_LOG_ACT, "Reading PNG file \"%s\"\n", input.filename);
verbosePrint(VERB_NOTICE, "Reading PNG file \"%s\"\n", input.filename);
std::array<unsigned char, 8> pngHeader;
if (input.file.sgetn(reinterpret_cast<char *>(pngHeader.data()), pngHeader.size())
@@ -56,7 +58,7 @@ Png::Png(char const *filename, std::streambuf &file) {
fatal("File \"%s\" is not a valid PNG image", input.filename); // LCOV_EXCL_LINE
}
options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n");
verbosePrint(VERB_INFO, "PNG header signature is OK\n");
png_structp png = png_create_read_struct(
PNG_LIBPNG_VER_STRING, static_cast<png_voidp>(&input), handleError, handleWarning
@@ -110,8 +112,8 @@ Png::Png(char const *filename, std::streambuf &file) {
return "unknown interlace type";
}
};
options.verbosePrint(
Options::VERB_INTERM,
verbosePrint(
VERB_INFO,
"PNG image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n",
width,
height,
@@ -139,18 +141,15 @@ Png::Png(char const *filename, std::streambuf &file) {
);
}
options.verbosePrint(
Options::VERB_INTERM, "Embedded PNG palette has %d colors: [", nbColors
);
for (int i = 0; i < nbColors; ++i) {
if (i > 0) {
options.verbosePrint(Options::VERB_INTERM, ", ");
if (checkVerbosity(VERB_INFO)) {
fprintf(stderr, "Embedded PNG palette has %d colors: [", nbColors);
for (int i = 0; i < nbColors; ++i) {
fprintf(stderr, "%s#%08x", i > 0 ? ", " : "", palette[i].toCSS());
}
options.verbosePrint(Options::VERB_INTERM, "#%08x", palette[i].toCSS());
fprintf(stderr, "]\n");
}
options.verbosePrint(Options::VERB_INTERM, "]\n");
} else {
options.verbosePrint(Options::VERB_INTERM, "No embedded PNG palette\n");
verbosePrint(VERB_INFO, "No embedded PNG palette\n");
}
// Set up transformations to turn everything into RGBA8888 for simplicity of handling

View File

@@ -19,6 +19,7 @@
#include "file.hpp"
#include "helpers.hpp"
#include "itertools.hpp"
#include "verbosity.hpp"
#include "gfx/color_set.hpp"
#include "gfx/main.hpp"
@@ -79,8 +80,8 @@ struct Image {
bool isSuitableForGrayscale() const {
// Check that all of the grays don't fall into the same "bin"
if (colors.size() > options.maxOpaqueColors()) { // Apply the Pigeonhole Principle
options.verbosePrint(
Options::VERB_DEBUG,
verbosePrint(
VERB_DEBUG,
"Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n",
colors.size(),
options.maxOpaqueColors()
@@ -93,8 +94,8 @@ struct Image {
continue;
}
if (!color->isGray()) {
options.verbosePrint(
Options::VERB_DEBUG,
verbosePrint(
VERB_DEBUG,
"Found non-gray color #%08x, not using grayscale sorting\n",
color->toCSS()
);
@@ -102,8 +103,8 @@ struct Image {
}
uint8_t mask = 1 << color->grayIndex();
if (bins & mask) { // Two in the same bin!
options.verbosePrint(
Options::VERB_DEBUG,
verbosePrint(
VERB_DEBUG,
"Color #%08x conflicts with another one, not using grayscale sorting\n",
color->toCSS()
);
@@ -336,7 +337,7 @@ static std::tuple<std::vector<size_t>, std::vector<Palette>>
assume(mappings.size() == colorSets.size());
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) {
if (checkVerbosity(VERB_INFO)) {
fprintf(
stderr, "Color set mappings: (%zu palette%s)\n", nbPalettes, nbPalettes != 1 ? "s" : ""
);
@@ -438,7 +439,7 @@ static std::tuple<std::vector<size_t>, std::vector<Palette>>
static void outputPalettes(std::vector<Palette> const &palettes) {
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) {
if (checkVerbosity(VERB_INFO)) {
for (Palette const &palette : palettes) {
fputs("{ ", stderr);
for (uint16_t colorIndex : palette) {
@@ -897,7 +898,7 @@ static void
}
void processPalettes() {
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
verbosePrint(VERB_CONFIG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
std::vector<ColorSet> colorSets;
std::vector<Palette> palettes;
@@ -907,13 +908,13 @@ void processPalettes() {
}
void process() {
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
verbosePrint(VERB_CONFIG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n");
verbosePrint(VERB_NOTICE, "Reading tiles...\n");
Image image(options.input); // This also sets `hasTransparentPixels` as a side effect
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) {
if (checkVerbosity(VERB_INFO)) {
fputs("Image colors: [ ", stderr);
for (std::optional<Rgba> const &slot : image.colors) {
if (!slot.has_value()) {
@@ -1025,14 +1026,14 @@ void process() {
continue_visiting_tiles:;
}
options.verbosePrint(
Options::VERB_INTERM,
verbosePrint(
VERB_INFO,
"Image contains %zu color set%s\n",
colorSets.size(),
colorSets.size() != 1 ? "s" : ""
);
// LCOV_EXCL_START
if (options.verbosity >= Options::VERB_INTERM) {
if (checkVerbosity(VERB_INFO)) {
for (ColorSet const &colorSet : colorSets) {
fputs("[ ", stderr);
for (uint16_t color : colorSet) {
@@ -1074,20 +1075,19 @@ continue_visiting_tiles:;
}
if (!options.output.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n");
verbosePrint(VERB_NOTICE, "Generating unoptimized tile data...\n");
outputUnoptimizedTileData(image, attrmap, palettes, mappings);
}
if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) {
options.verbosePrint(
Options::VERB_LOG_ACT,
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n"
verbosePrint(
VERB_NOTICE, "Generating unoptimized tilemap and/or attrmap and/or palmap...\n"
);
outputUnoptimizedMaps(attrmap, mappings);
}
} else {
// All of these require the deduplication process to be performed to be output
options.verbosePrint(Options::VERB_LOG_ACT, "Deduplicating tiles...\n");
verbosePrint(VERB_NOTICE, "Deduplicating tiles...\n");
UniqueTiles tiles = dedupTiles(image, attrmap, palettes, mappings);
if (size_t nbTiles = tiles.size();
@@ -1101,22 +1101,22 @@ continue_visiting_tiles:;
}
if (!options.output.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tile data...\n");
verbosePrint(VERB_NOTICE, "Generating optimized tile data...\n");
outputTileData(tiles);
}
if (!options.tilemap.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tilemap...\n");
verbosePrint(VERB_NOTICE, "Generating optimized tilemap...\n");
outputTilemap(attrmap);
}
if (!options.attrmap.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n");
verbosePrint(VERB_NOTICE, "Generating optimized attrmap...\n");
outputAttrmap(attrmap, mappings);
}
if (!options.palmap.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized palmap...\n");
verbosePrint(VERB_NOTICE, "Generating optimized palmap...\n");
outputPalmap(attrmap, mappings);
}
}

View File

@@ -15,6 +15,7 @@
#include "diagnostics.hpp"
#include "file.hpp"
#include "helpers.hpp" // assume
#include "verbosity.hpp"
#include "gfx/main.hpp"
#include "gfx/warning.hpp"
@@ -98,7 +99,7 @@ static void printPalette(std::array<std::optional<Rgba>, 4> const &palette) {
}
void reverse() {
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
verbosePrint(VERB_CONFIG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
// Check for weird flag combinations
@@ -127,7 +128,7 @@ void reverse() {
);
}
options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n");
verbosePrint(VERB_NOTICE, "Reading tiles...\n");
std::vector<uint8_t> const tiles = readInto(options.output);
uint8_t tileSize = 8 * options.bitDepth;
if (tiles.size() % tileSize != 0) {
@@ -140,13 +141,13 @@ void reverse() {
// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
size_t const nbTiles = tiles.size() / tileSize;
options.verbosePrint(Options::VERB_INTERM, "Read %zu tiles.\n", nbTiles);
verbosePrint(VERB_INFO, "Read %zu tiles.\n", nbTiles);
size_t mapSize = nbTiles + options.trim; // Image size in tiles
std::optional<std::vector<uint8_t>> tilemap;
if (!options.tilemap.empty()) {
tilemap = readInto(options.tilemap);
mapSize = tilemap->size();
options.verbosePrint(Options::VERB_INTERM, "Read %zu tilemap entries.\n", mapSize);
verbosePrint(VERB_INFO, "Read %zu tilemap entries.\n", mapSize);
}
if (mapSize == 0) {
@@ -172,7 +173,7 @@ void reverse() {
break;
}
}
options.verbosePrint(Options::VERB_INTERM, "Picked reversing width of %zu tiles\n", width);
verbosePrint(VERB_INFO, "Picked reversing width of %zu tiles\n", width);
}
if (mapSize % width != 0) {
if (options.trim == 0 && !tilemap) {
@@ -192,9 +193,7 @@ void reverse() {
}
height = mapSize / width;
options.verbosePrint(
Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height
);
verbosePrint(VERB_INFO, "Reversed image dimensions: %zux%zu tiles\n", width, height);
Rgba const grayColors[4] = {
Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)
@@ -320,8 +319,8 @@ void reverse() {
}
}
options.verbosePrint(
Options::VERB_INTERM,
verbosePrint(
VERB_INFO,
"Number of tiles in bank {0: %" PRIu16 ", 1: %" PRIu16 "}\n",
nbTilesInBank[0],
nbTilesInBank[1]
@@ -404,7 +403,7 @@ void reverse() {
}
}
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
verbosePrint(VERB_NOTICE, "Writing image...\n");
File pngFile;
if (!pngFile.open(options.input, std::ios::out | std::ios::binary)) {
// LCOV_EXCL_START

View File

@@ -14,6 +14,7 @@
#include "itertools.hpp"
#include "linkdefs.hpp"
#include "platform.hpp"
#include "verbosity.hpp"
#include "link/main.hpp"
#include "link/output.hpp"
@@ -405,7 +406,7 @@ static std::vector<Section const *> checkOverlayCompat() {
}
void assign_AssignSections() {
verbosePrint("Beginning assignment...\n");
verbosePrint(VERB_NOTICE, "Beginning assignment...\n");
// Initialize assignment
initFreeSpace();
@@ -443,7 +444,7 @@ void assign_AssignSections() {
// Assign sections in decreasing constraint order
for (uint8_t constraints = std::size(unassignedSections); constraints--;) {
if (char const *constraintName = constraintNames[constraints]; constraintName) {
verbosePrint("Assigning %sconstrained sections...\n", constraintName);
verbosePrint(VERB_INFO, "Assigning %sconstrained sections...\n", constraintName);
} else {
assume(unassignedSections[constraints].empty());
}

View File

@@ -18,6 +18,7 @@
#include "script.hpp" // Generated from script.y
#include "usage.hpp"
#include "util.hpp" // UpperMap, printChar
#include "verbosity.hpp"
#include "version.hpp"
#include "link/assign.hpp"
@@ -31,28 +32,7 @@
Options options;
std::string const &FileStackNode::dump(uint32_t curLineNo) const {
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
assume(parent); // REPT nodes use their parent's name
std::string const &lastName = parent->dump(lineNo);
fputs(" -> ", stderr);
fputs(lastName.c_str(), stderr);
for (uint32_t iter : iters()) {
fprintf(stderr, "::REPT~%" PRIu32, iter);
}
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
return lastName;
} else {
if (parent) {
parent->dump(lineNo);
fputs(" -> ", stderr);
}
std::string const &nodeName = name();
fputs(nodeName.c_str(), stderr);
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
return nodeName;
}
}
static char const *linkerScriptName = nullptr; // -l
// Short options
static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvW:wx";
@@ -96,18 +76,131 @@ static Usage usage(
" -o, --output <path> set the output file\n"
" -p, --pad <value> set the value to pad between sections with\n"
" -x, --nopad disable padding of output binary\n"
" -V, --version print RGBLINK version and exits\n"
" -V, --version print RGBLINK version and exit\n"
" -W, --warning <warning> enable or disable warnings\n"
"\n"
"For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n"
);
// clang-format on
static void parseScrambleSpec(char *spec) {
static UpperMap<std::pair<uint16_t *, uint16_t>> scrambleSpecs{
{"ROMX", std::pair{&options.scrambleROMX, 65535}},
{"SRAM", std::pair{&options.scrambleSRAM, 255} },
{"WRAMX", std::pair{&options.scrambleWRAMX, 7} },
// LCOV_EXCL_START
static void verboseOutputConfig(int argc, char *argv[]) {
if (!checkVerbosity(VERB_CONFIG)) {
return;
}
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);
}
// LCOV_EXCL_STOP
std::string const &FileStackNode::dump(uint32_t curLineNo) const {
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
assume(parent); // REPT nodes use their parent's name
std::string const &lastName = parent->dump(lineNo);
fputs(" -> ", stderr);
fputs(lastName.c_str(), stderr);
for (uint32_t iter : iters()) {
fprintf(stderr, "::REPT~%" PRIu32, iter);
}
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
return lastName;
} else {
if (parent) {
parent->dump(lineNo);
fputs(" -> ", stderr);
}
std::string const &nodeName = name();
fputs(nodeName.c_str(), stderr);
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
return nodeName;
}
}
static void parseScrambleSpec(char *spec) {
// clang-format off: vertically align nested initializers
static UpperMap<std::pair<uint16_t *, uint16_t>> scrambleSpecs{
{"ROMX", std::pair{&options.scrambleROMX, 65535}},
{"SRAM", std::pair{&options.scrambleSRAM, 255 }},
{"WRAMX", std::pair{&options.scrambleWRAMX, 7 }},
};
// clang-format on
// Skip leading whitespace before the regions.
spec += strspn(spec, " \t");
@@ -211,8 +304,6 @@ static void parseScrambleSpec(char *spec) {
}
int main(int argc, char *argv[]) {
char const *linkerScriptName = nullptr; // -l
// Parse options
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
switch (ch) {
@@ -283,7 +374,7 @@ int main(int argc, char *argv[]) {
// LCOV_EXCL_STOP
case 'v':
// LCOV_EXCL_START
options.beVerbose = true;
incrementVerbosity();
break;
// LCOV_EXCL_STOP
case 'W':
@@ -302,10 +393,10 @@ int main(int argc, char *argv[]) {
}
}
int curArgIndex = musl_optind;
verboseOutputConfig(argc, argv);
// If no input files were specified, the user must have screwed up
if (curArgIndex == argc) {
if (musl_optind == argc) {
usage.printAndExit("Please specify an input file (pass `-` to read from standard input)");
}
@@ -323,13 +414,14 @@ int main(int argc, char *argv[]) {
}
// Read all object files first,
for (obj_Setup(argc - curArgIndex); curArgIndex < argc; ++curArgIndex) {
obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1);
obj_Setup(argc - musl_optind);
for (int i = musl_optind; i < argc; ++i) {
obj_ReadFile(argv[i], argc - i - 1);
}
// apply the linker script's modifications,
if (linkerScriptName) {
verbosePrint("Reading linker script...\n");
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
if (lexer_Init(linkerScriptName)) {
yy::parser parser;

View File

@@ -17,6 +17,7 @@
#include "helpers.hpp"
#include "linkdefs.hpp"
#include "platform.hpp"
#include "verbosity.hpp"
#include "version.hpp"
#include "link/assign.hpp"
@@ -480,7 +481,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
fatal("%s: Not a RGBDS object file", fileName);
}
verbosePrint("Reading object file %s\n", fileName);
verbosePrint(VERB_NOTICE, "Reading object file %s\n", fileName);
uint32_t revNum;
tryReadLong(revNum, file, "%s: Cannot read revision number: %s", fileName);
@@ -506,7 +507,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
uint32_t nbNodes;
tryReadLong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
nodes[fileID].resize(nbNodes);
verbosePrint("Reading %u nodes...\n", nbNodes);
verbosePrint(VERB_INFO, "Reading %u nodes...\n", nbNodes);
for (uint32_t i = nbNodes; i--;) {
readFileStackNode(file, nodes[fileID], i, fileName);
}
@@ -515,7 +516,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
std::vector<Symbol> &fileSymbols = symbolLists.emplace_front(nbSymbols);
std::vector<uint32_t> nbSymPerSect(nbSections, 0);
verbosePrint("Reading %" PRIu32 " symbols...\n", nbSymbols);
verbosePrint(VERB_INFO, "Reading %" PRIu32 " symbols...\n", nbSymbols);
for (uint32_t i = 0; i < nbSymbols; ++i) {
// Read symbol
Symbol &symbol = fileSymbols[i];
@@ -531,7 +532,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
// This file's sections, stored in a table to link symbols to them
std::vector<std::unique_ptr<Section>> fileSections(nbSections);
verbosePrint("Reading %" PRIu32 " sections...\n", nbSections);
verbosePrint(VERB_INFO, "Reading %" PRIu32 " sections...\n", nbSections);
for (uint32_t i = 0; i < nbSections; ++i) {
// Read section
fileSections[i] = std::make_unique<Section>();
@@ -543,7 +544,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
uint32_t nbAsserts;
tryReadLong(nbAsserts, file, "%s: Cannot read number of assertions: %s", fileName);
verbosePrint("Reading %" PRIu32 " assertions...\n", nbAsserts);
verbosePrint(VERB_INFO, "Reading %" PRIu32 " assertions...\n", nbAsserts);
for (uint32_t i = 0; i < nbAsserts; ++i) {
Assertion &assertion = patch_AddAssertion();

View File

@@ -10,6 +10,7 @@
#include "helpers.hpp" // assume, clz, ctz
#include "linkdefs.hpp"
#include "opmath.hpp"
#include "verbosity.hpp"
#include "link/main.hpp"
#include "link/section.hpp"
@@ -462,7 +463,7 @@ Assertion &patch_AddAssertion() {
}
void patch_CheckAssertions() {
verbosePrint("Checking assertions...\n");
verbosePrint(VERB_NOTICE, "Checking assertions...\n");
for (Assertion &assert : assertions) {
int32_t value = computeRPNExpr(assert.patch, *assert.fileSymbols);
@@ -505,7 +506,7 @@ void patch_CheckAssertions() {
// Applies all of a section's patches to a data section
static void applyFilePatches(Section &section, Section &dataSection) {
verbosePrint("Patching section \"%s\"...\n", section.name.c_str());
verbosePrint(VERB_INFO, "Patching section \"%s\"...\n", section.name.c_str());
for (Patch &patch : section.patches) {
int32_t value = computeRPNExpr(patch, *section.fileSymbols);
uint16_t offset = patch.offset + section.offset;

69
src/verbosity.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include "verbosity.hpp"
#include <array>
#include <bitset>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
static Verbosity verbosity = VERB_NONE;
void incrementVerbosity() {
if (verbosity < VERB_VVVVVV) {
verbosity = static_cast<Verbosity>(verbosity + 1);
}
}
bool checkVerbosity(Verbosity level) {
return verbosity >= level;
}
void printVVVVVVerbosity() {
if (!checkVerbosity(VERB_VVVVVV)) {
return;
}
putc('\n', stderr);
// clang-format off: vertically align values
static std::array<std::bitset<10>, 21> gfx{
0b0111111110,
0b1111111111,
0b1110011001,
0b1110011001,
0b1111111111,
0b1111111111,
0b1110000001,
0b1111000011,
0b0111111110,
0b0001111000,
0b0111111110,
0b1111111111,
0b1111111111,
0b1111111111,
0b1101111011,
0b1101111011,
0b0011111100,
0b0011001100,
0b0111001110,
0b0111001110,
0b0111001110,
};
// clang-format on
static std::array<char const *, 3> textbox{
" ,----------------------------------------.",
" | Augh, dimensional interference again?! |",
" `----------------------------------------'",
};
for (size_t i = 0; i < gfx.size(); ++i) {
std::bitset<10> const &row = gfx[i];
for (uint8_t j = row.size(); j--;) {
// Double the pixel horizontally, otherwise the aspect ratio looks wrong
fputs(row[j] ? "00" : " ", stderr);
}
if (i < textbox.size()) {
fputs(textbox[i], stderr);
}
putc('\n', stderr);
}
putc('\n', stderr);
}