mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Run clang-format on everything (#1332)
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AlignArrayOfStructures: Left
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignConsecutiveBitFields: Consecutive
|
||||
AlignConsecutiveDeclarations: None
|
||||
AlignConsecutiveMacros: Consecutive
|
||||
AlignEscapedNewlines: Left
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: false
|
||||
AlignTrailingComments: true
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
@@ -21,8 +21,8 @@ AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros:
|
||||
- format_
|
||||
- attr_
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Attach
|
||||
@@ -60,8 +60,8 @@ IndentPPDirectives: BeforeHash
|
||||
IndentRequires: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
# Only support for Javascript as of clang-format 13...
|
||||
# InsertTrailingCommas: true
|
||||
# Only support for Javascript as of clang-format 14...
|
||||
# InsertTrailingCommas: Wrapped
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
LambdaBodyIndentation: Signature
|
||||
Language: Cpp
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#ifndef RGBDS_FORMAT_SPEC_H
|
||||
#define RGBDS_FORMAT_SPEC_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum FormatState {
|
||||
@@ -30,7 +31,7 @@ class FormatSpec {
|
||||
public:
|
||||
bool isEmpty() const { return !state; }
|
||||
bool isValid() const { return valid || state == FORMAT_DONE; }
|
||||
bool isFinished() const { return state >= FORMAT_DONE;}
|
||||
bool isFinished() const { return state >= FORMAT_DONE; }
|
||||
|
||||
void useCharacter(int c);
|
||||
void finishCharacters();
|
||||
|
||||
@@ -11,24 +11,25 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "asm/lexer.hpp"
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "asm/lexer.hpp"
|
||||
|
||||
struct FileStackNode {
|
||||
FileStackNode *parent; // Pointer to parent node, for error reporting
|
||||
// Line at which the parent context was exited; meaningless for the root level
|
||||
uint32_t lineNo;
|
||||
|
||||
bool referenced; // If referenced by a Symbol, Section, or Patch's `src`, don't `delete`!
|
||||
uint32_t ID; // Set only if referenced: ID within the object file, -1 if not output yet
|
||||
uint32_t ID; // Set only if referenced: ID within the object file, -1 if not output yet
|
||||
|
||||
enum FileStackNodeType type;
|
||||
std::variant<
|
||||
std::monostate, // Default constructed; `.type` and `.data` must be set manually
|
||||
std::vector<uint32_t>, // NODE_REPT
|
||||
std::string // NODE_FILE, NODE_MACRO
|
||||
> data;
|
||||
std::monostate, // Default constructed; `.type` and `.data` must be set manually
|
||||
std::vector<uint32_t>, // NODE_REPT
|
||||
std::string // NODE_FILE, NODE_MACRO
|
||||
>
|
||||
data;
|
||||
|
||||
// REPT iteration counts since last named node, in reverse depth order
|
||||
std::vector<uint32_t> &iters();
|
||||
@@ -62,8 +63,15 @@ bool yywrap();
|
||||
void fstk_RunInclude(char const *path);
|
||||
void fstk_RunMacro(char const *macroName, MacroArgs &args);
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char const *body, size_t size);
|
||||
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
||||
int32_t reptLineNo, char const *body, size_t size);
|
||||
void fstk_RunFor(
|
||||
char const *symName,
|
||||
int32_t start,
|
||||
int32_t stop,
|
||||
int32_t step,
|
||||
int32_t reptLineNo,
|
||||
char const *body,
|
||||
size_t size
|
||||
);
|
||||
void fstk_StopRept();
|
||||
bool fstk_Break();
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@ struct Expansion {
|
||||
char const *unowned;
|
||||
char *owned; // Non-`const` only so it can be `delete []`d
|
||||
} contents;
|
||||
size_t size; // Length of the contents
|
||||
size_t size; // Length of the contents
|
||||
size_t offset; // Cursor into the contents
|
||||
bool owned; // Whether or not to free contents when this expansion is freed
|
||||
bool owned; // Whether or not to free contents when this expansion is freed
|
||||
};
|
||||
|
||||
struct IfStackEntry {
|
||||
bool ranIfBlock; // Whether an IF/ELIF/ELSE block ran already
|
||||
bool ranIfBlock; // Whether an IF/ELIF/ELSE block ran already
|
||||
bool reachedElseBlock; // Whether an ELSE block ran already
|
||||
};
|
||||
|
||||
@@ -59,9 +59,9 @@ struct ViewedLexerState {
|
||||
|
||||
struct BufferedLexerState {
|
||||
int fd;
|
||||
size_t index; // Read index into the buffer
|
||||
size_t index; // Read index into the buffer
|
||||
char buf[LEXER_BUF_SIZE]; // Circular buffer
|
||||
size_t nbChars; // Number of "fresh" chars in the buffer
|
||||
size_t nbChars; // Number of "fresh" chars in the buffer
|
||||
};
|
||||
|
||||
struct LexerState {
|
||||
@@ -75,8 +75,8 @@ struct LexerState {
|
||||
|
||||
std::deque<IfStackEntry> ifStack;
|
||||
|
||||
bool capturing; // Whether the text being lexed should be captured
|
||||
size_t captureSize; // Amount of text captured
|
||||
bool capturing; // Whether the text being lexed should be captured
|
||||
size_t captureSize; // Amount of text captured
|
||||
std::vector<char> *captureBuf; // Buffer to send the captured text to if non-null
|
||||
|
||||
bool disableMacroArgs;
|
||||
@@ -85,38 +85,29 @@ struct LexerState {
|
||||
bool expandStrings;
|
||||
std::deque<Expansion> expansions; // Front is the innermost current expansion
|
||||
|
||||
std::variant<
|
||||
std::monostate,
|
||||
MmappedLexerState,
|
||||
ViewedLexerState,
|
||||
BufferedLexerState
|
||||
> content;
|
||||
std::variant<std::monostate, MmappedLexerState, ViewedLexerState, BufferedLexerState> content;
|
||||
};
|
||||
|
||||
extern LexerState *lexerState;
|
||||
extern LexerState *lexerStateEOL;
|
||||
|
||||
static inline void lexer_SetState(LexerState *state)
|
||||
{
|
||||
static inline void lexer_SetState(LexerState *state) {
|
||||
lexerState = state;
|
||||
}
|
||||
|
||||
static inline void lexer_SetStateAtEOL(LexerState *state)
|
||||
{
|
||||
static inline void lexer_SetStateAtEOL(LexerState *state) {
|
||||
lexerStateEOL = state;
|
||||
}
|
||||
|
||||
extern char binDigits[2];
|
||||
extern char gfxDigits[4];
|
||||
|
||||
static inline void lexer_SetBinDigits(char const digits[2])
|
||||
{
|
||||
static inline void lexer_SetBinDigits(char const digits[2]) {
|
||||
binDigits[0] = digits[0];
|
||||
binDigits[1] = digits[1];
|
||||
}
|
||||
|
||||
static inline void lexer_SetGfxDigits(char const digits[4])
|
||||
{
|
||||
static inline void lexer_SetGfxDigits(char const digits[4]) {
|
||||
gfxDigits[0] = digits[0];
|
||||
gfxDigits[1] = digits[1];
|
||||
gfxDigits[2] = digits[2];
|
||||
@@ -125,8 +116,9 @@ static inline void lexer_SetGfxDigits(char const digits[4])
|
||||
|
||||
// `path` is referenced, but not held onto..!
|
||||
bool lexer_OpenFile(LexerState &state, char const *path);
|
||||
void lexer_OpenFileView(LexerState &state, char const *path, char const *buf, size_t size,
|
||||
uint32_t lineNo);
|
||||
void lexer_OpenFileView(
|
||||
LexerState &state, char const *path, char const *buf, size_t size, uint32_t lineNo
|
||||
);
|
||||
void lexer_RestartRept(uint32_t lineNo);
|
||||
void lexer_CleanupState(LexerState &state);
|
||||
void lexer_Init();
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
struct MacroArgs {
|
||||
unsigned int shift;
|
||||
std::vector<std::string> args;
|
||||
|
||||
@@ -16,7 +16,9 @@ void out_RegisterNode(FileStackNode *node);
|
||||
void out_ReplaceNode(FileStackNode *node);
|
||||
void out_SetFileName(char *s);
|
||||
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift);
|
||||
void out_CreateAssert(enum AssertionType type, Expression const &expr, char const *message, uint32_t ofs);
|
||||
void out_CreateAssert(
|
||||
enum AssertionType type, Expression const &expr, char const *message, uint32_t ofs
|
||||
);
|
||||
void out_WriteObject();
|
||||
|
||||
#endif // RGBDS_ASM_OUTPUT_H
|
||||
|
||||
@@ -5,18 +5,19 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
struct Symbol;
|
||||
|
||||
struct Expression {
|
||||
int32_t val; // If the expression's value is known, it's here
|
||||
int32_t val; // If the expression's value is known, it's here
|
||||
std::string *reason; // Why the expression is not known, if it isn't
|
||||
bool isKnown; // Whether the expression's value is known at assembly time
|
||||
bool isSymbol; // Whether the expression represents a symbol suitable for const diffing
|
||||
bool isKnown; // Whether the expression's value is known at assembly time
|
||||
bool isSymbol; // Whether the expression represents a symbol suitable for const diffing
|
||||
std::vector<uint8_t> *rpn; // Bytes serializing the RPN expression
|
||||
uint32_t rpnPatchSize; // Size the expression will take in the object file
|
||||
uint32_t rpnPatchSize; // Size the expression will take in the object file
|
||||
|
||||
int32_t getConstVal() const;
|
||||
Symbol const *symbolOf() const;
|
||||
@@ -26,7 +27,9 @@ struct Expression {
|
||||
void rpn_Number(Expression &expr, uint32_t val);
|
||||
void rpn_Symbol(Expression &expr, char const *symName);
|
||||
void rpn_LOGNOT(Expression &expr, const Expression &src);
|
||||
void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1, const Expression &src2);
|
||||
void rpn_BinaryOp(
|
||||
enum RPNCommand op, Expression &expr, const Expression &src1, const Expression &src2
|
||||
);
|
||||
void rpn_HIGH(Expression &expr, const Expression &src);
|
||||
void rpn_LOW(Expression &expr, const Expression &src);
|
||||
void rpn_ISCONST(Expression &expr, const Expression &src);
|
||||
|
||||
@@ -30,7 +30,7 @@ struct Section {
|
||||
enum SectionType type;
|
||||
enum SectionModifier modifier;
|
||||
FileStackNode const *src; // Where the section was defined
|
||||
uint32_t fileLine; // Line where the section was defined
|
||||
uint32_t fileLine; // Line where the section was defined
|
||||
uint32_t size;
|
||||
uint32_t org;
|
||||
uint32_t bank;
|
||||
@@ -52,10 +52,20 @@ extern std::deque<Section> sectionList;
|
||||
extern Section *currentSection;
|
||||
|
||||
Section *sect_FindSectionByName(char const *name);
|
||||
void sect_NewSection(char const *name, enum SectionType type, uint32_t org,
|
||||
SectionSpec const &attrs, enum SectionModifier mod);
|
||||
void sect_SetLoadSection(char const *name, enum SectionType type, uint32_t org,
|
||||
SectionSpec const &attrs, enum SectionModifier mod);
|
||||
void sect_NewSection(
|
||||
char const *name,
|
||||
enum SectionType type,
|
||||
uint32_t org,
|
||||
SectionSpec const &attrs,
|
||||
enum SectionModifier mod
|
||||
);
|
||||
void sect_SetLoadSection(
|
||||
char const *name,
|
||||
enum SectionType type,
|
||||
uint32_t org,
|
||||
SectionSpec const &attrs,
|
||||
enum SectionModifier mod
|
||||
);
|
||||
void sect_EndLoadSection();
|
||||
|
||||
Section *sect_GetSymbolSection();
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#define RGBDS_SYMBOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <time.h>
|
||||
#include <variant>
|
||||
@@ -23,7 +23,7 @@ enum SymbolType {
|
||||
SYM_REF // Forward reference to a label
|
||||
};
|
||||
|
||||
struct Symbol; // For the `sym_IsPC` forward declaration
|
||||
struct Symbol; // For the `sym_IsPC` forward declaration
|
||||
bool sym_IsPC(Symbol const *sym); // For the inline `getSection` method
|
||||
|
||||
struct Symbol {
|
||||
@@ -33,14 +33,15 @@ struct Symbol {
|
||||
bool isBuiltin; // Whether the symbol is a built-in
|
||||
Section *section;
|
||||
FileStackNode *src; // Where the symbol was defined
|
||||
uint32_t fileLine; // Line where the symbol was defined
|
||||
uint32_t fileLine; // Line where the symbol was defined
|
||||
|
||||
std::variant<
|
||||
int32_t, // If isNumeric()
|
||||
int32_t (*)(), // If isNumeric() and has a callback
|
||||
std::string_view *, // For SYM_MACRO
|
||||
std::string * // For SYM_EQUS
|
||||
> data;
|
||||
int32_t, // If isNumeric()
|
||||
int32_t (*)(), // If isNumeric() and has a callback
|
||||
std::string_view *, // For SYM_MACRO
|
||||
std::string * // For SYM_EQUS
|
||||
>
|
||||
data;
|
||||
|
||||
uint32_t ID; // ID of the symbol in the object file (-1 if none)
|
||||
|
||||
|
||||
@@ -7,34 +7,29 @@
|
||||
|
||||
extern unsigned int nbErrors, maxErrors;
|
||||
|
||||
enum WarningState {
|
||||
WARNING_DEFAULT,
|
||||
WARNING_DISABLED,
|
||||
WARNING_ENABLED,
|
||||
WARNING_ERROR
|
||||
};
|
||||
enum WarningState { WARNING_DEFAULT, WARNING_DISABLED, WARNING_ENABLED, WARNING_ERROR };
|
||||
|
||||
enum WarningID {
|
||||
WARNING_ASSERT, // Assertions
|
||||
WARNING_BACKWARDS_FOR, // `for` loop with backwards range
|
||||
WARNING_BUILTIN_ARG, // Invalid args to builtins
|
||||
WARNING_CHARMAP_REDEF, // Charmap entry re-definition
|
||||
WARNING_DIV, // Division undefined behavior
|
||||
WARNING_ASSERT, // Assertions
|
||||
WARNING_BACKWARDS_FOR, // `for` loop with backwards range
|
||||
WARNING_BUILTIN_ARG, // Invalid args to builtins
|
||||
WARNING_CHARMAP_REDEF, // Charmap entry re-definition
|
||||
WARNING_DIV, // Division undefined behavior
|
||||
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
|
||||
WARNING_EMPTY_MACRO_ARG, // Empty macro argument
|
||||
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
|
||||
WARNING_LARGE_CONSTANT, // Constants too large
|
||||
WARNING_LONG_STR, // String too long for internal buffers
|
||||
WARNING_MACRO_SHIFT, // Shift past available arguments in macro
|
||||
WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
|
||||
WARNING_OBSOLETE, // Obsolete things
|
||||
WARNING_SHIFT, // Shifting undefined behavior
|
||||
WARNING_SHIFT_AMOUNT, // Strange shift amount
|
||||
WARNING_USER, // User warnings
|
||||
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
|
||||
WARNING_LARGE_CONSTANT, // Constants too large
|
||||
WARNING_LONG_STR, // String too long for internal buffers
|
||||
WARNING_MACRO_SHIFT, // Shift past available arguments in macro
|
||||
WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
|
||||
WARNING_OBSOLETE, // Obsolete things
|
||||
WARNING_SHIFT, // Shifting undefined behavior
|
||||
WARNING_SHIFT_AMOUNT, // Strange shift amount
|
||||
WARNING_USER, // User warnings
|
||||
|
||||
NB_PLAIN_WARNINGS,
|
||||
|
||||
// Warnings past this point are "parametric" warnings, only mapping to a single flag
|
||||
// Warnings past this point are "parametric" warnings, only mapping to a single flag
|
||||
#define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
|
||||
// Treating string as number may lose some bits
|
||||
WARNING_NUMERIC_STRING_1 = PARAM_WARNINGS_START,
|
||||
@@ -49,7 +44,7 @@ enum WarningID {
|
||||
NB_PLAIN_AND_PARAM_WARNINGS,
|
||||
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
|
||||
|
||||
// Warnings past this point are "meta" warnings
|
||||
// Warnings past this point are "meta" warnings
|
||||
#define META_WARNINGS_START NB_PLAIN_AND_PARAM_WARNINGS
|
||||
WARNING_ALL = META_WARNINGS_START,
|
||||
WARNING_EXTRA,
|
||||
|
||||
@@ -8,12 +8,11 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
void warn(char const *fmt ...) format_(printf, 1, 2);
|
||||
void warn(char const *fmt...) format_(printf, 1, 2);
|
||||
void warnx(char const *fmt, ...) format_(printf, 1, 2);
|
||||
|
||||
[[noreturn]] void err(char const *fmt, ...) format_(printf, 1, 2);
|
||||
[[noreturn]] void errx(char const *fmt, ...) format_(printf, 1, 2);
|
||||
|
||||
}
|
||||
|
||||
#endif // RGBDS_ERROR_H
|
||||
|
||||
11
include/extern/getopt.hpp
vendored
11
include/extern/getopt.hpp
vendored
@@ -17,12 +17,13 @@ struct option {
|
||||
int val;
|
||||
};
|
||||
|
||||
int musl_getopt_long_only(int argc, char **argv, char const *optstring,
|
||||
const option *longopts, int *idx);
|
||||
int musl_getopt_long_only(
|
||||
int argc, char **argv, char const *optstring, const option *longopts, int *idx
|
||||
);
|
||||
|
||||
#define no_argument 0
|
||||
#define required_argument 1
|
||||
#define optional_argument 2
|
||||
#define no_argument 0
|
||||
#define required_argument 1
|
||||
#define optional_argument 2
|
||||
|
||||
} // extern "C"
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
#include <ios>
|
||||
#include <iostream>
|
||||
#include <streambuf>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
@@ -40,8 +40,11 @@ public:
|
||||
assert(!(mode & std::ios_base::out));
|
||||
_file.emplace<std::streambuf *>(std::cin.rdbuf());
|
||||
if (setmode(STDIN_FILENO, (mode & std::ios_base::binary) ? O_BINARY : O_TEXT) == -1) {
|
||||
fatal("Failed to set stdin to %s mode: %s",
|
||||
mode & std::ios_base::binary ? "binary" : "text", strerror(errno));
|
||||
fatal(
|
||||
"Failed to set stdin to %s mode: %s",
|
||||
mode & std::ios_base::binary ? "binary" : "text",
|
||||
strerror(errno)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
assert(mode & std::ios_base::out);
|
||||
@@ -50,9 +53,12 @@ public:
|
||||
return this;
|
||||
}
|
||||
std::streambuf &operator*() {
|
||||
return std::visit(Visitor{[](std::filebuf &file) -> std::streambuf & { return file; },
|
||||
[](std::streambuf *buf) -> std::streambuf & { return *buf; }},
|
||||
_file);
|
||||
return std::visit(
|
||||
Visitor{
|
||||
[](std::filebuf &file) -> std::streambuf & { return file; },
|
||||
[](std::streambuf *buf) -> std::streambuf & { return *buf; }},
|
||||
_file
|
||||
);
|
||||
}
|
||||
std::streambuf const &operator*() const {
|
||||
// The non-`const` version does not perform any modifications, so it's okay.
|
||||
@@ -65,25 +71,32 @@ public:
|
||||
}
|
||||
|
||||
File *close() {
|
||||
return std::visit(Visitor{[this](std::filebuf &file) {
|
||||
// This is called by the destructor, and an explicit `close`
|
||||
// shouldn't close twice.
|
||||
_file.emplace<std::streambuf *>(nullptr);
|
||||
return file.close() != nullptr;
|
||||
},
|
||||
[](std::streambuf *buf) { return buf != nullptr; }},
|
||||
_file)
|
||||
return std::visit(
|
||||
Visitor{
|
||||
[this](std::filebuf &file) {
|
||||
// This is called by the destructor, and an explicit `close`
|
||||
// shouldn't close twice.
|
||||
_file.emplace<std::streambuf *>(nullptr);
|
||||
return file.close() != nullptr;
|
||||
},
|
||||
[](std::streambuf *buf) { return buf != nullptr; },
|
||||
},
|
||||
_file
|
||||
)
|
||||
? this
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
char const *c_str(std::string const &path) const {
|
||||
return std::visit(Visitor{[&path](std::filebuf const &) { return path.c_str(); },
|
||||
[](std::streambuf const *buf) {
|
||||
return buf == std::cin.rdbuf()
|
||||
? "<stdin>" : "<stdout>";
|
||||
}},
|
||||
_file);
|
||||
return std::visit(
|
||||
Visitor{
|
||||
[&path](std::filebuf const &) { return path.c_str(); },
|
||||
[](std::streambuf const *buf) {
|
||||
return buf == std::cin.rdbuf() ? "<stdin>" : "<stdout>";
|
||||
},
|
||||
},
|
||||
_file
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@ struct Options {
|
||||
uint16_t reversedWidth = 0; // -r, in tiles
|
||||
bool reverse() const { return reversedWidth != 0; }
|
||||
|
||||
bool useColorCurve = false; // -C
|
||||
bool useColorCurve = false; // -C
|
||||
bool allowMirroring = false; // -m
|
||||
bool allowDedup = false; // -u
|
||||
bool columnMajor = false; // -Z, previously -h
|
||||
uint8_t verbosity = 0; // -v
|
||||
bool allowDedup = false; // -u
|
||||
bool columnMajor = false; // -Z, previously -h
|
||||
uint8_t verbosity = 0; // -v
|
||||
|
||||
std::string attrmap{}; // -a, -A
|
||||
std::string attrmap{}; // -a, -A
|
||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
||||
enum {
|
||||
NO_SPEC,
|
||||
@@ -39,25 +39,25 @@ struct Options {
|
||||
uint16_t top;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
} inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS)
|
||||
} inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS)
|
||||
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
|
||||
uint8_t nbPalettes = 8; // -n
|
||||
std::string output{}; // -o
|
||||
std::string palettes{}; // -p, -P
|
||||
std::string palmap{}; // -q, -Q
|
||||
uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth;
|
||||
std::string tilemap{}; // -t, -T
|
||||
uint64_t trim = 0; // -x
|
||||
uint8_t nbPalettes = 8; // -n
|
||||
std::string output{}; // -o
|
||||
std::string palettes{}; // -p, -P
|
||||
std::string palmap{}; // -q, -Q
|
||||
uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth;
|
||||
std::string tilemap{}; // -t, -T
|
||||
uint64_t trim = 0; // -x
|
||||
|
||||
std::string input{}; // positional arg
|
||||
|
||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
||||
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far
|
||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
||||
format_(printf, 3, 4) void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||
|
||||
mutable bool hasTransparentPixels = false;
|
||||
|
||||
@@ -15,10 +15,16 @@ struct Palette;
|
||||
|
||||
namespace sorting {
|
||||
|
||||
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
|
||||
int palAlphaSize, png_byte *palAlpha);
|
||||
void grayscale(std::vector<Palette> &palettes,
|
||||
std::array<std::optional<Rgba>, 0x8001> const &colors);
|
||||
void indexed(
|
||||
std::vector<Palette> &palettes,
|
||||
int palSize,
|
||||
png_color const *palRGB,
|
||||
int palAlphaSize,
|
||||
png_byte *palAlpha
|
||||
);
|
||||
void grayscale(
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
|
||||
);
|
||||
void rgb(std::vector<Palette> &palettes);
|
||||
|
||||
} // namespace sorting
|
||||
|
||||
@@ -25,8 +25,12 @@ struct Rgba {
|
||||
fiveBpp &= 0b11111; // For caller's convenience
|
||||
return fiveBpp << 3 | fiveBpp >> 2;
|
||||
};
|
||||
return {_5to8(cgbColor), _5to8(cgbColor >> 5), _5to8(cgbColor >> 10),
|
||||
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF)};
|
||||
return {
|
||||
_5to8(cgbColor),
|
||||
_5to8(cgbColor >> 5),
|
||||
_5to8(cgbColor >> 10),
|
||||
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF),
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
// Ideally, we'd use `__has_attribute` and `__has_builtin`, but these were only introduced in GCC 9
|
||||
#ifdef __GNUC__ // GCC or compatible
|
||||
#define format_(archetype, str_index, first_arg) \
|
||||
__attribute__ ((format (archetype, str_index, first_arg)))
|
||||
#define attr_(...) __attribute__ ((__VA_ARGS__))
|
||||
__attribute__((format(archetype, str_index, first_arg)))
|
||||
#define attr_(...) __attribute__((__VA_ARGS__))
|
||||
// In release builds, define "unreachable" as such, but trap in debug builds
|
||||
#ifdef NDEBUG
|
||||
#define unreachable_ __builtin_unreachable
|
||||
@@ -18,9 +18,10 @@
|
||||
// Unsupported, but no need to throw a fit
|
||||
#define format_(archetype, str_index, first_arg)
|
||||
#define attr_(...)
|
||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||
// Note that executing this is undefined behavior (declared [[noreturn]], but does return)
|
||||
[[noreturn]] static inline void unreachable_() {}
|
||||
// This seems to generate similar code to __builtin_unreachable, despite different semantics
|
||||
// Note that executing this is undefined behavior (declared [[noreturn]], but does return)
|
||||
[[noreturn]] static inline void unreachable_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Use builtins whenever possible, and shim them otherwise
|
||||
@@ -32,50 +33,46 @@
|
||||
#include <assert.h>
|
||||
#include <intrin.h>
|
||||
#pragma intrinsic(_BitScanReverse, _BitScanForward)
|
||||
static inline int ctz(unsigned int x)
|
||||
{
|
||||
unsigned long cnt;
|
||||
static inline int ctz(unsigned int x) {
|
||||
unsigned long cnt;
|
||||
|
||||
assert(x != 0);
|
||||
_BitScanForward(&cnt, x);
|
||||
return cnt;
|
||||
}
|
||||
static inline int clz(unsigned int x)
|
||||
{
|
||||
unsigned long cnt;
|
||||
assert(x != 0);
|
||||
_BitScanForward(&cnt, x);
|
||||
return cnt;
|
||||
}
|
||||
static inline int clz(unsigned int x) {
|
||||
unsigned long cnt;
|
||||
|
||||
assert(x != 0);
|
||||
_BitScanReverse(&cnt, x);
|
||||
return 31 - cnt;
|
||||
}
|
||||
assert(x != 0);
|
||||
_BitScanReverse(&cnt, x);
|
||||
return 31 - cnt;
|
||||
}
|
||||
|
||||
#else
|
||||
#include <limits.h>
|
||||
static inline int ctz(unsigned int x)
|
||||
{
|
||||
int cnt = 0;
|
||||
static inline int ctz(unsigned int x) {
|
||||
int cnt = 0;
|
||||
|
||||
while (!(x & 1)) {
|
||||
x >>= 1;
|
||||
cnt++;
|
||||
}
|
||||
return cnt;
|
||||
while (!(x & 1)) {
|
||||
x >>= 1;
|
||||
cnt++;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static inline int clz(unsigned int x)
|
||||
{
|
||||
int cnt = 0;
|
||||
static inline int clz(unsigned int x) {
|
||||
int cnt = 0;
|
||||
|
||||
while (x <= UINT_MAX / 2) {
|
||||
x <<= 1;
|
||||
cnt++;
|
||||
}
|
||||
return cnt;
|
||||
while (x <= UINT_MAX / 2) {
|
||||
x <<= 1;
|
||||
cnt++;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Macros for stringification
|
||||
#define STR(x) #x
|
||||
#define STR(x) #x
|
||||
#define EXPAND_AND_STR(x) STR(x)
|
||||
|
||||
// Obtaining the size of an array; `arr` must be an expression, not a type!
|
||||
|
||||
@@ -64,8 +64,9 @@ public:
|
||||
}
|
||||
|
||||
auto operator*() const {
|
||||
return std::apply([](auto &&...it) { return std::tuple<decltype(*it)...>(*it...); },
|
||||
_iters);
|
||||
return std::apply(
|
||||
[](auto &&...it) { return std::tuple<decltype(*it)...>(*it...); }, _iters
|
||||
);
|
||||
}
|
||||
|
||||
friend auto operator==(Zip const &lhs, Zip const &rhs) {
|
||||
@@ -92,7 +93,8 @@ public:
|
||||
using std::begin;
|
||||
return std::make_tuple(begin(containers)...);
|
||||
},
|
||||
_containers));
|
||||
_containers
|
||||
));
|
||||
}
|
||||
|
||||
auto end() {
|
||||
@@ -101,14 +103,15 @@ public:
|
||||
using std::end;
|
||||
return std::make_tuple(end(containers)...);
|
||||
},
|
||||
_containers));
|
||||
_containers
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Take ownership of objects and rvalue refs passed to us, but not lvalue refs
|
||||
template<typename T>
|
||||
using Holder = std::conditional_t<std::is_lvalue_reference_v<T>, T,
|
||||
std::remove_cv_t<std::remove_reference_t<T>>>;
|
||||
using Holder = std::
|
||||
conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cv_t<std::remove_reference_t<T>>>;
|
||||
} // namespace detail
|
||||
|
||||
// Does the same number of iterations as the first container's iterator!
|
||||
|
||||
@@ -31,10 +31,11 @@ extern bool isWRAM0Mode;
|
||||
extern bool disablePadding;
|
||||
|
||||
// Helper macro for printing verbose-mode messages
|
||||
#define verbosePrint(...) do { \
|
||||
if (beVerbose) \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#define verbosePrint(...) \
|
||||
do { \
|
||||
if (beVerbose) \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
struct FileStackNode {
|
||||
FileStackNode *parent;
|
||||
@@ -43,10 +44,11 @@ struct FileStackNode {
|
||||
|
||||
enum FileStackNodeType type;
|
||||
std::variant<
|
||||
std::monostate, // Default constructed; `.type` and `.data` must be set manually
|
||||
std::vector<uint32_t>, // NODE_REPT
|
||||
std::string // NODE_FILE, NODE_MACRO
|
||||
> data;
|
||||
std::monostate, // Default constructed; `.type` and `.data` must be set manually
|
||||
std::vector<uint32_t>, // NODE_REPT
|
||||
std::string // NODE_FILE, NODE_MACRO
|
||||
>
|
||||
data;
|
||||
|
||||
// REPT iteration counts since last named node, in reverse depth order
|
||||
std::vector<uint32_t> &iters();
|
||||
@@ -58,8 +60,10 @@ struct FileStackNode {
|
||||
std::string const *dumpFileStack() const;
|
||||
};
|
||||
|
||||
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) format_(printf, 3, 4);
|
||||
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
|
||||
format_(printf, 3, 4);
|
||||
void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) format_(printf, 3, 4);
|
||||
[[noreturn]] void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) format_(printf, 3, 4);
|
||||
[[noreturn]] void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
|
||||
format_(printf, 3, 4);
|
||||
|
||||
#endif // RGBDS_LINK_MAIN_H
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#include "link/section.hpp"
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "link/section.hpp"
|
||||
|
||||
struct Assertion {
|
||||
Patch patch; // Also used for its `.type`
|
||||
std::string message;
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "link/main.hpp"
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "link/main.hpp"
|
||||
|
||||
struct FileStackNode;
|
||||
struct Section;
|
||||
struct Symbol;
|
||||
|
||||
@@ -30,9 +30,10 @@ struct Symbol {
|
||||
FileStackNode const *src;
|
||||
int32_t lineNo;
|
||||
std::variant<
|
||||
int32_t, // Constants just have a numeric value
|
||||
Label // Label values refer to an offset within a specific section
|
||||
> data;
|
||||
int32_t, // Constants just have a numeric value
|
||||
Label // Label values refer to an offset within a specific section
|
||||
>
|
||||
data;
|
||||
|
||||
Label &label();
|
||||
Label const &label() const;
|
||||
|
||||
@@ -8,56 +8,52 @@
|
||||
#include <string>
|
||||
|
||||
#define RGBDS_OBJECT_VERSION_STRING "RGBA"
|
||||
#define RGBDS_OBJECT_REV 10U
|
||||
#define RGBDS_OBJECT_REV 10U
|
||||
|
||||
enum AssertionType {
|
||||
ASSERT_WARN,
|
||||
ASSERT_ERROR,
|
||||
ASSERT_FATAL
|
||||
};
|
||||
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
|
||||
|
||||
enum RPNCommand {
|
||||
RPN_ADD = 0x00,
|
||||
RPN_SUB = 0x01,
|
||||
RPN_MUL = 0x02,
|
||||
RPN_DIV = 0x03,
|
||||
RPN_MOD = 0x04,
|
||||
RPN_NEG = 0x05,
|
||||
RPN_EXP = 0x06,
|
||||
RPN_ADD = 0x00,
|
||||
RPN_SUB = 0x01,
|
||||
RPN_MUL = 0x02,
|
||||
RPN_DIV = 0x03,
|
||||
RPN_MOD = 0x04,
|
||||
RPN_NEG = 0x05,
|
||||
RPN_EXP = 0x06,
|
||||
|
||||
RPN_OR = 0x10,
|
||||
RPN_AND = 0x11,
|
||||
RPN_XOR = 0x12,
|
||||
RPN_NOT = 0x13,
|
||||
RPN_OR = 0x10,
|
||||
RPN_AND = 0x11,
|
||||
RPN_XOR = 0x12,
|
||||
RPN_NOT = 0x13,
|
||||
|
||||
RPN_LOGAND = 0x21,
|
||||
RPN_LOGOR = 0x22,
|
||||
RPN_LOGNOT = 0x23,
|
||||
RPN_LOGAND = 0x21,
|
||||
RPN_LOGOR = 0x22,
|
||||
RPN_LOGNOT = 0x23,
|
||||
|
||||
RPN_LOGEQ = 0x30,
|
||||
RPN_LOGNE = 0x31,
|
||||
RPN_LOGGT = 0x32,
|
||||
RPN_LOGLT = 0x33,
|
||||
RPN_LOGGE = 0x34,
|
||||
RPN_LOGLE = 0x35,
|
||||
RPN_LOGEQ = 0x30,
|
||||
RPN_LOGNE = 0x31,
|
||||
RPN_LOGGT = 0x32,
|
||||
RPN_LOGLT = 0x33,
|
||||
RPN_LOGGE = 0x34,
|
||||
RPN_LOGLE = 0x35,
|
||||
|
||||
RPN_SHL = 0x40,
|
||||
RPN_SHR = 0x41,
|
||||
RPN_USHR = 0x42,
|
||||
RPN_SHL = 0x40,
|
||||
RPN_SHR = 0x41,
|
||||
RPN_USHR = 0x42,
|
||||
|
||||
RPN_BANK_SYM = 0x50,
|
||||
RPN_BANK_SECT = 0x51,
|
||||
RPN_BANK_SELF = 0x52,
|
||||
RPN_SIZEOF_SECT = 0x53,
|
||||
RPN_STARTOF_SECT = 0x54,
|
||||
RPN_SIZEOF_SECTTYPE = 0x55,
|
||||
RPN_STARTOF_SECTTYPE = 0x56,
|
||||
RPN_BANK_SYM = 0x50,
|
||||
RPN_BANK_SECT = 0x51,
|
||||
RPN_BANK_SELF = 0x52,
|
||||
RPN_SIZEOF_SECT = 0x53,
|
||||
RPN_STARTOF_SECT = 0x54,
|
||||
RPN_SIZEOF_SECTTYPE = 0x55,
|
||||
RPN_STARTOF_SECTTYPE = 0x56,
|
||||
|
||||
RPN_HRAM = 0x60,
|
||||
RPN_RST = 0x61,
|
||||
RPN_HRAM = 0x60,
|
||||
RPN_RST = 0x61,
|
||||
|
||||
RPN_CONST = 0x80,
|
||||
RPN_SYM = 0x81
|
||||
RPN_CONST = 0x80,
|
||||
RPN_SYM = 0x81
|
||||
};
|
||||
|
||||
enum SectionType {
|
||||
@@ -96,8 +92,7 @@ extern struct SectionTypeInfo {
|
||||
* @param type The section's type
|
||||
* @return `true` if the section's definition includes data
|
||||
*/
|
||||
static inline bool sect_HasData(enum SectionType type)
|
||||
{
|
||||
static inline bool sect_HasData(enum SectionType type) {
|
||||
assert(type != SECTTYPE_INVALID);
|
||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||
}
|
||||
@@ -106,8 +101,7 @@ static inline bool sect_HasData(enum SectionType type)
|
||||
* Computes a memory region's end address (last byte), eg. 0x7FFF
|
||||
* @return The address of the last byte in that memory region
|
||||
*/
|
||||
static inline uint16_t endaddr(enum SectionType type)
|
||||
{
|
||||
static inline uint16_t endaddr(enum SectionType type) {
|
||||
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
||||
}
|
||||
|
||||
@@ -115,24 +109,15 @@ static inline uint16_t endaddr(enum SectionType type)
|
||||
* Computes a memory region's number of banks
|
||||
* @return The number of banks, 1 for regions without banking
|
||||
*/
|
||||
static inline uint32_t nbbanks(enum SectionType type)
|
||||
{
|
||||
static inline uint32_t nbbanks(enum SectionType type) {
|
||||
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
|
||||
}
|
||||
|
||||
enum SectionModifier {
|
||||
SECTION_NORMAL,
|
||||
SECTION_UNION,
|
||||
SECTION_FRAGMENT
|
||||
};
|
||||
enum SectionModifier { SECTION_NORMAL, SECTION_UNION, SECTION_FRAGMENT };
|
||||
|
||||
extern char const * const sectionModNames[];
|
||||
|
||||
enum ExportLevel {
|
||||
SYMTYPE_LOCAL,
|
||||
SYMTYPE_IMPORT,
|
||||
SYMTYPE_EXPORT
|
||||
};
|
||||
enum ExportLevel { SYMTYPE_LOCAL, SYMTYPE_IMPORT, SYMTYPE_EXPORT };
|
||||
|
||||
enum PatchType {
|
||||
PATCHTYPE_BYTE,
|
||||
|
||||
@@ -7,62 +7,62 @@
|
||||
|
||||
// MSVC doesn't have str(n)casecmp, use a suitable replacement
|
||||
#ifdef _MSC_VER
|
||||
# include <string.h>
|
||||
# define strcasecmp _stricmp
|
||||
# define strncasecmp _strnicmp
|
||||
#include <string.h>
|
||||
#define strcasecmp _stricmp
|
||||
#define strncasecmp _strnicmp
|
||||
#else
|
||||
# include <strings.h>
|
||||
#include <strings.h>
|
||||
#endif
|
||||
|
||||
// MSVC prefixes the names of S_* macros with underscores,
|
||||
// and doesn't define any S_IS* macros; define them ourselves
|
||||
#ifdef _MSC_VER
|
||||
# define S_IFMT _S_IFMT
|
||||
# define S_IFDIR _S_IFDIR
|
||||
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
|
||||
#define S_IFMT _S_IFMT
|
||||
#define S_IFDIR _S_IFDIR
|
||||
#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR)
|
||||
#endif
|
||||
|
||||
// gcc has __PRETTY_FUNCTION__, MSVC has __FUNCSIG__, __func__ is standard
|
||||
#ifndef __PRETTY_FUNCTION__
|
||||
# ifdef __FUNCSIG__
|
||||
# define __PRETTY_FUNCTION__ __FUNCSIG__
|
||||
# else
|
||||
# define __PRETTY_FUNCTION__ __func__
|
||||
# endif
|
||||
#ifdef __FUNCSIG__
|
||||
#define __PRETTY_FUNCTION__ __FUNCSIG__
|
||||
#else
|
||||
#define __PRETTY_FUNCTION__ __func__
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// MSVC doesn't use POSIX types or defines for `read`
|
||||
#ifdef _MSC_VER
|
||||
# include <io.h>
|
||||
# define STDIN_FILENO 0
|
||||
# define STDOUT_FILENO 1
|
||||
# define STDERR_FILENO 2
|
||||
# define ssize_t int
|
||||
# define SSIZE_MAX INT_MAX
|
||||
#include <io.h>
|
||||
#define STDIN_FILENO 0
|
||||
#define STDOUT_FILENO 1
|
||||
#define STDERR_FILENO 2
|
||||
#define ssize_t int
|
||||
#define SSIZE_MAX INT_MAX
|
||||
#else
|
||||
# include <fcntl.h>
|
||||
# include <limits.h>
|
||||
# include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
// MSVC uses a different name for O_RDWR, and needs an additional _O_BINARY flag
|
||||
#ifdef _MSC_VER
|
||||
# include <fcntl.h>
|
||||
# define O_RDWR _O_RDWR
|
||||
# define S_ISREG(field) ((field) & _S_IFREG)
|
||||
# define O_BINARY _O_BINARY
|
||||
# define O_TEXT _O_TEXT
|
||||
#include <fcntl.h>
|
||||
#define O_RDWR _O_RDWR
|
||||
#define S_ISREG(field) ((field)&_S_IFREG)
|
||||
#define O_BINARY _O_BINARY
|
||||
#define O_TEXT _O_TEXT
|
||||
#elif !defined(O_BINARY) // Cross-compilers define O_BINARY
|
||||
# define O_BINARY 0 // POSIX says we shouldn't care!
|
||||
# define O_TEXT 0 // Assume that it's not defined either
|
||||
#endif // _MSC_VER
|
||||
#define O_BINARY 0 // POSIX says we shouldn't care!
|
||||
#define O_TEXT 0 // Assume that it's not defined either
|
||||
#endif // _MSC_VER
|
||||
|
||||
// Windows has stdin and stdout open as text by default, which we may not want
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
# include <io.h>
|
||||
# define setmode(fd, mode) _setmode(fd, mode)
|
||||
#include <io.h>
|
||||
#define setmode(fd, mode) _setmode(fd, mode)
|
||||
#else
|
||||
# define setmode(fd, mode) (0)
|
||||
#define setmode(fd, mode) (0)
|
||||
#endif
|
||||
|
||||
#endif // RGBDS_PLATFORM_H
|
||||
|
||||
@@ -10,7 +10,6 @@ extern "C" {
|
||||
#define PACKAGE_VERSION_PATCH 0
|
||||
|
||||
char const *get_package_version_string();
|
||||
|
||||
}
|
||||
|
||||
#endif // EXTERN_VERSION_H
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
|
||||
#include <errno.h>
|
||||
#include <new>
|
||||
#include <map>
|
||||
#include <new>
|
||||
#include <stack>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include "asm/main.hpp"
|
||||
#include "asm/output.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
// Charmaps are stored using a structure known as "trie".
|
||||
// Essentially a tree, where each nodes stores a single character's worth of info:
|
||||
// whether there exists a mapping that ends at the current character,
|
||||
struct CharmapNode {
|
||||
bool isTerminal; // Whether there exists a mapping that ends here
|
||||
uint8_t value; // If the above is true, its corresponding value
|
||||
uint8_t value; // If the above is true, its corresponding value
|
||||
// This MUST be indexes and not pointers, because pointers get invalidated by reallocation!
|
||||
size_t next[255]; // Indexes of where to go next, 0 = nowhere
|
||||
};
|
||||
@@ -38,8 +39,7 @@ static std::map<std::string, Charmap> charmaps;
|
||||
static Charmap *currentCharmap;
|
||||
std::stack<Charmap *> charmapStack;
|
||||
|
||||
void charmap_New(char const *name, char const *baseName)
|
||||
{
|
||||
void charmap_New(char const *name, char const *baseName) {
|
||||
Charmap *base = nullptr;
|
||||
|
||||
if (baseName != nullptr) {
|
||||
@@ -68,8 +68,7 @@ void charmap_New(char const *name, char const *baseName)
|
||||
currentCharmap = &charmap;
|
||||
}
|
||||
|
||||
void charmap_Set(char const *name)
|
||||
{
|
||||
void charmap_Set(char const *name) {
|
||||
auto search = charmaps.find(name);
|
||||
|
||||
if (search == charmaps.end())
|
||||
@@ -78,13 +77,11 @@ void charmap_Set(char const *name)
|
||||
currentCharmap = &search->second;
|
||||
}
|
||||
|
||||
void charmap_Push()
|
||||
{
|
||||
void charmap_Push() {
|
||||
charmapStack.push(currentCharmap);
|
||||
}
|
||||
|
||||
void charmap_Pop()
|
||||
{
|
||||
void charmap_Pop() {
|
||||
if (charmapStack.empty()) {
|
||||
error("No entries in the charmap stack\n");
|
||||
return;
|
||||
@@ -94,8 +91,7 @@ void charmap_Pop()
|
||||
charmapStack.pop();
|
||||
}
|
||||
|
||||
void charmap_Add(char *mapping, uint8_t value)
|
||||
{
|
||||
void charmap_Add(char *mapping, uint8_t value) {
|
||||
Charmap &charmap = *currentCharmap;
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
@@ -124,8 +120,7 @@ void charmap_Add(char *mapping, uint8_t value)
|
||||
node.value = value;
|
||||
}
|
||||
|
||||
bool charmap_HasChar(char const *input)
|
||||
{
|
||||
bool charmap_HasChar(char const *input) {
|
||||
Charmap const &charmap = *currentCharmap;
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
@@ -139,14 +134,12 @@ bool charmap_HasChar(char const *input)
|
||||
return charmap.nodes[nodeIdx].isTerminal;
|
||||
}
|
||||
|
||||
void charmap_Convert(char const *input, std::vector<uint8_t> &output)
|
||||
{
|
||||
void charmap_Convert(char const *input, std::vector<uint8_t> &output) {
|
||||
while (charmap_ConvertNext(input, &output))
|
||||
;
|
||||
}
|
||||
|
||||
size_t charmap_ConvertNext(char const *&input, std::vector<uint8_t> *output)
|
||||
{
|
||||
size_t charmap_ConvertNext(char const *&input, std::vector<uint8_t> *output) {
|
||||
// The goal is to match the longest mapping possible.
|
||||
// For that, advance through the trie with each character read.
|
||||
// If that would lead to a dead end, rewind characters until the last match, and output.
|
||||
@@ -194,11 +187,13 @@ size_t charmap_ConvertNext(char const *&input, std::vector<uint8_t> *output)
|
||||
|
||||
// Warn if this character is not mapped but any others are
|
||||
if (charmap.nodes.size() > 1)
|
||||
warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n",
|
||||
printChar(firstChar));
|
||||
warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n", printChar(firstChar));
|
||||
else if (charmap.name != DEFAULT_CHARMAP_NAME)
|
||||
warning(WARNING_UNMAPPED_CHAR_2, "Unmapped character %s not in "
|
||||
DEFAULT_CHARMAP_NAME " charmap\n", printChar(firstChar));
|
||||
warning(
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n",
|
||||
printChar(firstChar)
|
||||
);
|
||||
|
||||
return codepointLen;
|
||||
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
|
||||
// Fixed-point math routines
|
||||
|
||||
#include "asm/fixpoint.hpp"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "asm/fixpoint.hpp"
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#define fix2double(i, q) ((double)((i) / pow(2.0, q)))
|
||||
#define double2fix(d, q) ((int32_t)round((d) * pow(2.0, q)))
|
||||
#define double2fix(d, q) ((int32_t)round((d)*pow(2.0, q)))
|
||||
|
||||
// 2*pi radians == 1 turn
|
||||
#define turn2rad(f) ((f) * (M_PI * 2))
|
||||
@@ -23,87 +24,70 @@
|
||||
|
||||
uint8_t fixPrecision;
|
||||
|
||||
uint8_t fix_Precision()
|
||||
{
|
||||
uint8_t fix_Precision() {
|
||||
return fixPrecision;
|
||||
}
|
||||
|
||||
double fix_PrecisionFactor()
|
||||
{
|
||||
double fix_PrecisionFactor() {
|
||||
return pow(2.0, fixPrecision);
|
||||
}
|
||||
|
||||
int32_t fix_Sin(int32_t i, int32_t q)
|
||||
{
|
||||
int32_t fix_Sin(int32_t i, int32_t q) {
|
||||
return double2fix(sin(turn2rad(fix2double(i, q))), q);
|
||||
}
|
||||
|
||||
int32_t fix_Cos(int32_t i, int32_t q)
|
||||
{
|
||||
int32_t fix_Cos(int32_t i, int32_t q) {
|
||||
return double2fix(cos(turn2rad(fix2double(i, q))), q);
|
||||
}
|
||||
|
||||
int32_t fix_Tan(int32_t i, int32_t q)
|
||||
{
|
||||
int32_t fix_Tan(int32_t i, int32_t q) {
|
||||
return double2fix(tan(turn2rad(fix2double(i, q))), q);
|
||||
}
|
||||
|
||||
int32_t fix_ASin(int32_t i, int32_t q)
|
||||
{
|
||||
int32_t fix_ASin(int32_t i, int32_t q) {
|
||||
return double2fix(rad2turn(asin(fix2double(i, q))), q);
|
||||
}
|
||||
|
||||
int32_t fix_ACos(int32_t i, int32_t q)
|
||||
{
|
||||
int32_t fix_ACos(int32_t i, int32_t q) {
|
||||
return double2fix(rad2turn(acos(fix2double(i, q))), q);
|
||||
}
|
||||
|
||||
int32_t fix_ATan(int32_t i, int32_t q)
|
||||
{
|
||||
int32_t fix_ATan(int32_t i, int32_t q) {
|
||||
return double2fix(rad2turn(atan(fix2double(i, q))), q);
|
||||
}
|
||||
|
||||
int32_t fix_ATan2(int32_t i, int32_t j, int32_t q)
|
||||
{
|
||||
int32_t fix_ATan2(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(rad2turn(atan2(fix2double(i, q), fix2double(j, q))), q);
|
||||
}
|
||||
|
||||
int32_t fix_Mul(int32_t i, int32_t j, int32_t q)
|
||||
{
|
||||
int32_t fix_Mul(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(fix2double(i, q) * fix2double(j, q), q);
|
||||
}
|
||||
|
||||
int32_t fix_Div(int32_t i, int32_t j, int32_t q)
|
||||
{
|
||||
int32_t fix_Div(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(fix2double(i, q) / fix2double(j, q), q);
|
||||
}
|
||||
|
||||
int32_t fix_Mod(int32_t i, int32_t j, int32_t q)
|
||||
{
|
||||
int32_t fix_Mod(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(fmod(fix2double(i, q), fix2double(j, q)), q);
|
||||
}
|
||||
|
||||
int32_t fix_Pow(int32_t i, int32_t j, int32_t q)
|
||||
{
|
||||
int32_t fix_Pow(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(pow(fix2double(i, q), fix2double(j, q)), q);
|
||||
}
|
||||
|
||||
int32_t fix_Log(int32_t i, int32_t j, int32_t q)
|
||||
{
|
||||
int32_t fix_Log(int32_t i, int32_t j, int32_t q) {
|
||||
return double2fix(log(fix2double(i, q)) / log(fix2double(j, q)), q);
|
||||
}
|
||||
|
||||
int32_t fix_Round(int32_t i, int32_t q)
|
||||
{
|
||||
int32_t fix_Round(int32_t i, int32_t q) {
|
||||
return double2fix(round(fix2double(i, q)), q);
|
||||
}
|
||||
|
||||
int32_t fix_Ceil(int32_t i, int32_t q)
|
||||
{
|
||||
int32_t fix_Ceil(int32_t i, int32_t q) {
|
||||
return double2fix(ceil(fix2double(i, q)), q);
|
||||
}
|
||||
|
||||
int32_t fix_Floor(int32_t i, int32_t q)
|
||||
{
|
||||
int32_t fix_Floor(int32_t i, int32_t q) {
|
||||
return double2fix(floor(fix2double(i, q)), q);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "asm/format.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
@@ -9,11 +11,9 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "asm/fixpoint.hpp"
|
||||
#include "asm/format.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
void FormatSpec::useCharacter(int c)
|
||||
{
|
||||
void FormatSpec::useCharacter(int c) {
|
||||
if (state == FORMAT_INVALID)
|
||||
return;
|
||||
|
||||
@@ -99,14 +99,12 @@ invalid:
|
||||
}
|
||||
}
|
||||
|
||||
void FormatSpec::finishCharacters()
|
||||
{
|
||||
void FormatSpec::finishCharacters() {
|
||||
if (!isValid())
|
||||
state = FORMAT_INVALID;
|
||||
}
|
||||
|
||||
void FormatSpec::printString(char *buf, size_t bufLen, char const *value)
|
||||
{
|
||||
void FormatSpec::printString(char *buf, size_t bufLen, char const *value) {
|
||||
if (isEmpty()) {
|
||||
// No format was specified
|
||||
type = 's';
|
||||
@@ -149,8 +147,7 @@ void FormatSpec::printString(char *buf, size_t bufLen, char const *value)
|
||||
buf[totalLen] = '\0';
|
||||
}
|
||||
|
||||
void FormatSpec::printNumber(char *buf, size_t bufLen, uint32_t value)
|
||||
{
|
||||
void FormatSpec::printNumber(char *buf, size_t bufLen, uint32_t value) {
|
||||
if (isEmpty()) {
|
||||
// No format was specified; default to uppercase $hex
|
||||
type = 'X';
|
||||
@@ -175,12 +172,12 @@ void FormatSpec::printNumber(char *buf, size_t bufLen, uint32_t value)
|
||||
}
|
||||
}
|
||||
|
||||
char prefixChar = !prefix ? 0
|
||||
: type == 'X' ? '$'
|
||||
: type == 'x' ? '$'
|
||||
: type == 'b' ? '%'
|
||||
: type == 'o' ? '&'
|
||||
: 0;
|
||||
char prefixChar = !prefix ? 0
|
||||
: type == 'X' ? '$'
|
||||
: type == 'x' ? '$'
|
||||
: type == 'b' ? '%'
|
||||
: type == 'o' ? '&'
|
||||
: 0;
|
||||
|
||||
char valueBuf[262]; // Max 5 digits + decimal + 255 fraction digits + terminator
|
||||
|
||||
@@ -215,15 +212,16 @@ void FormatSpec::printNumber(char *buf, size_t bufLen, uint32_t value)
|
||||
cappedFracWidth = 255;
|
||||
}
|
||||
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)cappedFracWidth,
|
||||
value / fix_PrecisionFactor());
|
||||
snprintf(
|
||||
valueBuf, sizeof(valueBuf), "%.*f", (int)cappedFracWidth, value / fix_PrecisionFactor()
|
||||
);
|
||||
} else {
|
||||
char const *spec = type == 'd' ? "%" PRId32
|
||||
: type == 'u' ? "%" PRIu32
|
||||
: type == 'X' ? "%" PRIX32
|
||||
: type == 'x' ? "%" PRIx32
|
||||
: type == 'o' ? "%" PRIo32
|
||||
: "%" PRId32;
|
||||
char const *spec = type == 'd' ? "%" PRId32
|
||||
: type == 'u' ? "%" PRIu32
|
||||
: type == 'X' ? "%" PRIX32
|
||||
: type == 'x' ? "%" PRIx32
|
||||
: type == 'o' ? "%" PRIo32
|
||||
: "%" PRId32;
|
||||
|
||||
snprintf(valueBuf, sizeof(valueBuf), spec, value);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <new>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
@@ -15,13 +15,14 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "platform.hpp" // S_ISDIR (stat macro)
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/macro.hpp"
|
||||
#include "asm/main.hpp"
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
#include "error.hpp"
|
||||
#include "platform.hpp" // S_ISDIR (stat macro)
|
||||
|
||||
struct Context {
|
||||
FileStackNode *fileInfo;
|
||||
@@ -39,7 +40,7 @@ static std::stack<Context> contextStack;
|
||||
size_t maxRecursionDepth;
|
||||
|
||||
// The first include path for `fstk_FindFile` to try is none at all
|
||||
static std::vector<std::string> includePaths = { "" };
|
||||
static std::vector<std::string> includePaths = {""};
|
||||
|
||||
static const char *preIncludeName;
|
||||
|
||||
@@ -63,8 +64,7 @@ std::string const &FileStackNode::name() const {
|
||||
return std::get<std::string>(data);
|
||||
}
|
||||
|
||||
static const char *dumpNodeAndParents(FileStackNode const &node)
|
||||
{
|
||||
static const char *dumpNodeAndParents(FileStackNode const &node) {
|
||||
char const *name;
|
||||
|
||||
if (node.type == NODE_REPT) {
|
||||
@@ -73,7 +73,7 @@ static const char *dumpNodeAndParents(FileStackNode const &node)
|
||||
|
||||
name = dumpNodeAndParents(*node.parent);
|
||||
fprintf(stderr, "(%" PRIu32 ") -> %s", node.lineNo, name);
|
||||
for (uint32_t i = nodeIters.size(); i--; )
|
||||
for (uint32_t i = nodeIters.size(); i--;)
|
||||
fprintf(stderr, "::REPT~%" PRIu32, nodeIters[i]);
|
||||
} else {
|
||||
name = node.name().c_str();
|
||||
@@ -87,14 +87,12 @@ static const char *dumpNodeAndParents(FileStackNode const &node)
|
||||
return name;
|
||||
}
|
||||
|
||||
void FileStackNode::dump(uint32_t curLineNo) const
|
||||
{
|
||||
void FileStackNode::dump(uint32_t curLineNo) const {
|
||||
dumpNodeAndParents(*this);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
}
|
||||
|
||||
void fstk_DumpCurrent()
|
||||
{
|
||||
void fstk_DumpCurrent() {
|
||||
if (contextStack.empty()) {
|
||||
fputs("at top level", stderr);
|
||||
return;
|
||||
@@ -102,8 +100,7 @@ void fstk_DumpCurrent()
|
||||
contextStack.top().fileInfo->dump(lexer_GetLineNo());
|
||||
}
|
||||
|
||||
FileStackNode *fstk_GetFileStack()
|
||||
{
|
||||
FileStackNode *fstk_GetFileStack() {
|
||||
if (contextStack.empty())
|
||||
return nullptr;
|
||||
|
||||
@@ -117,8 +114,7 @@ FileStackNode *fstk_GetFileStack()
|
||||
return topNode;
|
||||
}
|
||||
|
||||
char const *fstk_GetFileName()
|
||||
{
|
||||
char const *fstk_GetFileName() {
|
||||
// Iterating via the nodes themselves skips nested REPTs
|
||||
FileStackNode const *node = contextStack.top().fileInfo;
|
||||
|
||||
@@ -127,8 +123,7 @@ char const *fstk_GetFileName()
|
||||
return node->name().c_str();
|
||||
}
|
||||
|
||||
void fstk_AddIncludePath(char const *path)
|
||||
{
|
||||
void fstk_AddIncludePath(char const *path) {
|
||||
if (path[0] == '\0')
|
||||
return;
|
||||
|
||||
@@ -138,8 +133,7 @@ void fstk_AddIncludePath(char const *path)
|
||||
str += '/';
|
||||
}
|
||||
|
||||
void fstk_SetPreIncludeFile(char const *path)
|
||||
{
|
||||
void fstk_SetPreIncludeFile(char const *path) {
|
||||
if (preIncludeName)
|
||||
warnx("Overriding pre-included filename %s", preIncludeName);
|
||||
preIncludeName = path;
|
||||
@@ -147,8 +141,7 @@ void fstk_SetPreIncludeFile(char const *path)
|
||||
printf("Pre-included filename %s\n", preIncludeName);
|
||||
}
|
||||
|
||||
static void printDep(char const *path)
|
||||
{
|
||||
static void printDep(char const *path) {
|
||||
if (dependfile) {
|
||||
fprintf(dependfile, "%s: %s\n", targetFileName.c_str(), path);
|
||||
if (generatePhonyDeps)
|
||||
@@ -156,8 +149,7 @@ static void printDep(char const *path)
|
||||
}
|
||||
}
|
||||
|
||||
static bool isPathValid(char const *path)
|
||||
{
|
||||
static bool isPathValid(char const *path) {
|
||||
struct stat statbuf;
|
||||
|
||||
if (stat(path, &statbuf) != 0)
|
||||
@@ -167,9 +159,8 @@ static bool isPathValid(char const *path)
|
||||
return !S_ISDIR(statbuf.st_mode);
|
||||
}
|
||||
|
||||
std::string *fstk_FindFile(char const *path)
|
||||
{
|
||||
std::string *fullPath = new(std::nothrow) std::string();
|
||||
std::string *fstk_FindFile(char const *path) {
|
||||
std::string *fullPath = new (std::nothrow) std::string();
|
||||
|
||||
if (!fullPath) {
|
||||
error("Failed to allocate string during include path search: %s\n", strerror(errno));
|
||||
@@ -189,20 +180,22 @@ std::string *fstk_FindFile(char const *path)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool yywrap()
|
||||
{
|
||||
bool yywrap() {
|
||||
uint32_t ifDepth = lexer_GetIFDepth();
|
||||
|
||||
if (ifDepth != 0)
|
||||
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||
ifDepth, ifDepth == 1 ? "" : "s");
|
||||
fatalerror(
|
||||
"Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||
ifDepth,
|
||||
ifDepth == 1 ? "" : "s"
|
||||
);
|
||||
|
||||
if (Context &context = contextStack.top(); context.fileInfo->type == NODE_REPT) {
|
||||
// The context is a REPT or FOR block, which may loop
|
||||
|
||||
// If the node is referenced, we can't edit it; duplicate it
|
||||
if (context.fileInfo->referenced) {
|
||||
context.fileInfo = new(std::nothrow) FileStackNode(*context.fileInfo);
|
||||
context.fileInfo = new (std::nothrow) FileStackNode(*context.fileInfo);
|
||||
if (!context.fileInfo)
|
||||
fatalerror("Failed to duplicate REPT file node: %s\n", strerror(errno));
|
||||
// Copy all info but the referencing
|
||||
@@ -255,8 +248,7 @@ bool yywrap()
|
||||
// Make sure not to switch the lexer state before calling this, so the saved line no is correct.
|
||||
// BE CAREFUL! This modifies the file stack directly, you should have set up the file info first.
|
||||
// Callers should set `contextStack.top().lexerState` after this so it is not `nullptr`.
|
||||
static Context &newContext(FileStackNode &fileInfo)
|
||||
{
|
||||
static Context &newContext(FileStackNode &fileInfo) {
|
||||
if (contextStack.size() > maxRecursionDepth)
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
|
||||
|
||||
@@ -277,15 +269,13 @@ static Context &newContext(FileStackNode &fileInfo)
|
||||
return context;
|
||||
}
|
||||
|
||||
void fstk_RunInclude(char const *path)
|
||||
{
|
||||
void fstk_RunInclude(char const *path) {
|
||||
std::string *fullPath = fstk_FindFile(path);
|
||||
|
||||
if (!fullPath) {
|
||||
if (generatedMissingIncludes) {
|
||||
if (verbose)
|
||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n",
|
||||
path, strerror(errno));
|
||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path, strerror(errno));
|
||||
failedOnMissingInclude = true;
|
||||
} else {
|
||||
error("Unable to open included file '%s': %s\n", path, strerror(errno));
|
||||
@@ -293,7 +283,7 @@ void fstk_RunInclude(char const *path)
|
||||
return;
|
||||
}
|
||||
|
||||
FileStackNode *fileInfo = new(std::nothrow) FileStackNode();
|
||||
FileStackNode *fileInfo = new (std::nothrow) FileStackNode();
|
||||
|
||||
if (!fileInfo) {
|
||||
error("Failed to alloc file info for INCLUDE: %s\n", strerror(errno));
|
||||
@@ -317,8 +307,7 @@ void fstk_RunInclude(char const *path)
|
||||
|
||||
// Similar to `fstk_RunInclude`, but not subject to `-MG`, and
|
||||
// calling `lexer_SetState` instead of `lexer_SetStateAtEOL`.
|
||||
static void runPreIncludeFile()
|
||||
{
|
||||
static void runPreIncludeFile() {
|
||||
if (!preIncludeName)
|
||||
return;
|
||||
|
||||
@@ -329,7 +318,7 @@ static void runPreIncludeFile()
|
||||
return;
|
||||
}
|
||||
|
||||
FileStackNode *fileInfo = new(std::nothrow) FileStackNode();
|
||||
FileStackNode *fileInfo = new (std::nothrow) FileStackNode();
|
||||
|
||||
if (!fileInfo) {
|
||||
error("Failed to alloc file info for pre-include: %s\n", strerror(errno));
|
||||
@@ -348,8 +337,7 @@ static void runPreIncludeFile()
|
||||
context.uniqueID = macro_UndefUniqueID();
|
||||
}
|
||||
|
||||
void fstk_RunMacro(char const *macroName, MacroArgs &args)
|
||||
{
|
||||
void fstk_RunMacro(char const *macroName, MacroArgs &args) {
|
||||
Symbol *macro = sym_FindExactSymbol(macroName);
|
||||
|
||||
if (!macro) {
|
||||
@@ -362,7 +350,7 @@ void fstk_RunMacro(char const *macroName, MacroArgs &args)
|
||||
}
|
||||
contextStack.top().macroArgs = macro_GetCurrentArgs();
|
||||
|
||||
FileStackNode *fileInfo = new(std::nothrow) FileStackNode();
|
||||
FileStackNode *fileInfo = new (std::nothrow) FileStackNode();
|
||||
|
||||
if (!fileInfo) {
|
||||
error("Failed to alloc file info for \"%s\": %s\n", macro->name, strerror(errno));
|
||||
@@ -383,12 +371,11 @@ void fstk_RunMacro(char const *macroName, MacroArgs &args)
|
||||
if (macro->src->type == NODE_REPT) {
|
||||
std::vector<uint32_t> const &srcIters = macro->src->iters();
|
||||
|
||||
for (uint32_t i = srcIters.size(); i--; ) {
|
||||
for (uint32_t i = srcIters.size(); i--;) {
|
||||
char buf[sizeof("::REPT~4294967295")]; // UINT32_MAX
|
||||
|
||||
if (sprintf(buf, "::REPT~%" PRIu32, srcIters[i]) < 0)
|
||||
fatalerror("Failed to write macro invocation info: %s\n",
|
||||
strerror(errno));
|
||||
fatalerror("Failed to write macro invocation info: %s\n", strerror(errno));
|
||||
fileInfoName.append(buf);
|
||||
}
|
||||
}
|
||||
@@ -398,19 +385,19 @@ void fstk_RunMacro(char const *macroName, MacroArgs &args)
|
||||
Context &context = newContext(*fileInfo);
|
||||
std::string_view *macroView = macro->getMacro();
|
||||
|
||||
lexer_OpenFileView(context.lexerState, "MACRO", macroView->data(), macroView->size(),
|
||||
macro->fileLine);
|
||||
lexer_OpenFileView(
|
||||
context.lexerState, "MACRO", macroView->data(), macroView->size(), macro->fileLine
|
||||
);
|
||||
lexer_SetStateAtEOL(&context.lexerState);
|
||||
context.uniqueID = macro_UseNewUniqueID();
|
||||
macro_UseNewArgs(&args);
|
||||
}
|
||||
|
||||
static bool newReptContext(int32_t reptLineNo, char const *body, size_t size)
|
||||
{
|
||||
static bool newReptContext(int32_t reptLineNo, char const *body, size_t size) {
|
||||
uint32_t reptDepth = contextStack.top().fileInfo->type == NODE_REPT
|
||||
? contextStack.top().fileInfo->iters().size()
|
||||
: 0;
|
||||
FileStackNode *fileInfo = new(std::nothrow) FileStackNode();
|
||||
? contextStack.top().fileInfo->iters().size()
|
||||
: 0;
|
||||
FileStackNode *fileInfo = new (std::nothrow) FileStackNode();
|
||||
|
||||
if (!fileInfo) {
|
||||
error("Failed to alloc file info for REPT: %s\n", strerror(errno));
|
||||
@@ -420,7 +407,9 @@ static bool newReptContext(int32_t reptLineNo, char const *body, size_t size)
|
||||
fileInfo->data = std::vector<uint32_t>{1};
|
||||
if (reptDepth) {
|
||||
// Append all parent iter counts
|
||||
fileInfo->iters().insert(fileInfo->iters().end(), RANGE(contextStack.top().fileInfo->iters()));
|
||||
fileInfo->iters().insert(
|
||||
fileInfo->iters().end(), RANGE(contextStack.top().fileInfo->iters())
|
||||
);
|
||||
}
|
||||
|
||||
Context &context = newContext(*fileInfo);
|
||||
@@ -434,8 +423,7 @@ static bool newReptContext(int32_t reptLineNo, char const *body, size_t size)
|
||||
return true;
|
||||
}
|
||||
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char const *body, size_t size)
|
||||
{
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char const *body, size_t size) {
|
||||
if (count == 0)
|
||||
return;
|
||||
if (!newReptContext(reptLineNo, body, size))
|
||||
@@ -444,9 +432,15 @@ void fstk_RunRept(uint32_t count, int32_t reptLineNo, char const *body, size_t s
|
||||
contextStack.top().nbReptIters = count;
|
||||
}
|
||||
|
||||
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
||||
int32_t reptLineNo, char const *body, size_t size)
|
||||
{
|
||||
void fstk_RunFor(
|
||||
char const *symName,
|
||||
int32_t start,
|
||||
int32_t stop,
|
||||
int32_t step,
|
||||
int32_t reptLineNo,
|
||||
char const *body,
|
||||
size_t size
|
||||
) {
|
||||
Symbol *sym = sym_AddVar(symName, start);
|
||||
|
||||
if (sym->type != SYM_VAR)
|
||||
@@ -462,8 +456,9 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
||||
error("FOR cannot have a step value of 0\n");
|
||||
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop))
|
||||
warning(WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n",
|
||||
start, stop, step);
|
||||
warning(
|
||||
WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step
|
||||
);
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
@@ -479,14 +474,12 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
||||
context.forName = symName;
|
||||
}
|
||||
|
||||
void fstk_StopRept()
|
||||
{
|
||||
void fstk_StopRept() {
|
||||
// Prevent more iterations
|
||||
contextStack.top().nbReptIters = 0;
|
||||
}
|
||||
|
||||
bool fstk_Break()
|
||||
{
|
||||
bool fstk_Break() {
|
||||
if (contextStack.top().fileInfo->type != NODE_REPT) {
|
||||
error("BREAK can only be used inside a REPT/FOR block\n");
|
||||
return false;
|
||||
@@ -496,22 +489,20 @@ bool fstk_Break()
|
||||
return true;
|
||||
}
|
||||
|
||||
void fstk_NewRecursionDepth(size_t newDepth)
|
||||
{
|
||||
void fstk_NewRecursionDepth(size_t newDepth) {
|
||||
if (contextStack.size() > newDepth + 1)
|
||||
fatalerror("Recursion limit (%zu) exceeded\n", newDepth);
|
||||
maxRecursionDepth = newDepth;
|
||||
}
|
||||
|
||||
void fstk_Init(char const *mainPath, size_t maxDepth)
|
||||
{
|
||||
void fstk_Init(char const *mainPath, size_t maxDepth) {
|
||||
Context &context = contextStack.emplace();
|
||||
|
||||
if (!lexer_OpenFile(context.lexerState, mainPath))
|
||||
fatalerror("Failed to open main file\n");
|
||||
lexer_SetState(&context.lexerState);
|
||||
|
||||
FileStackNode *fileInfo = new(std::nothrow) FileStackNode();
|
||||
FileStackNode *fileInfo = new (std::nothrow) FileStackNode();
|
||||
|
||||
if (!fileInfo)
|
||||
fatalerror("Failed to allocate memory for main file info: %s\n", strerror(errno));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,15 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "asm/macro.hpp"
|
||||
|
||||
#include <errno.h>
|
||||
#include <new>
|
||||
#include <inttypes.h>
|
||||
#include <new>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include "asm/macro.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#define MAXMACROARGS 99999
|
||||
@@ -19,8 +20,7 @@ static uint32_t maxUniqueID = 0;
|
||||
static char uniqueIDBuf[sizeof("_u4294967295")] = {}; // UINT32_MAX
|
||||
static char *uniqueIDPtr = nullptr;
|
||||
|
||||
void MacroArgs::append(std::string s)
|
||||
{
|
||||
void MacroArgs::append(std::string s) {
|
||||
if (s.empty())
|
||||
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
|
||||
if (args.size() == MAXMACROARGS)
|
||||
@@ -28,18 +28,15 @@ void MacroArgs::append(std::string s)
|
||||
args.push_back(s);
|
||||
}
|
||||
|
||||
MacroArgs *macro_GetCurrentArgs()
|
||||
{
|
||||
MacroArgs *macro_GetCurrentArgs() {
|
||||
return macroArgs;
|
||||
}
|
||||
|
||||
void macro_UseNewArgs(MacroArgs *args)
|
||||
{
|
||||
void macro_UseNewArgs(MacroArgs *args) {
|
||||
macroArgs = args;
|
||||
}
|
||||
|
||||
char const *macro_GetArg(uint32_t i)
|
||||
{
|
||||
char const *macro_GetArg(uint32_t i) {
|
||||
if (!macroArgs)
|
||||
return nullptr;
|
||||
|
||||
@@ -48,8 +45,7 @@ char const *macro_GetArg(uint32_t i)
|
||||
return realIndex >= macroArgs->args.size() ? nullptr : macroArgs->args[realIndex].c_str();
|
||||
}
|
||||
|
||||
char const *macro_GetAllArgs()
|
||||
{
|
||||
char const *macro_GetAllArgs() {
|
||||
if (!macroArgs)
|
||||
return nullptr;
|
||||
|
||||
@@ -85,13 +81,11 @@ char const *macro_GetAllArgs()
|
||||
return str;
|
||||
}
|
||||
|
||||
uint32_t macro_GetUniqueID()
|
||||
{
|
||||
uint32_t macro_GetUniqueID() {
|
||||
return uniqueID;
|
||||
}
|
||||
|
||||
char const *macro_GetUniqueIDStr()
|
||||
{
|
||||
char const *macro_GetUniqueIDStr() {
|
||||
// Generate a new unique ID on the first use of `\@`
|
||||
if (uniqueID == 0)
|
||||
macro_SetUniqueID(++maxUniqueID);
|
||||
@@ -99,8 +93,7 @@ char const *macro_GetUniqueIDStr()
|
||||
return uniqueIDPtr;
|
||||
}
|
||||
|
||||
void macro_SetUniqueID(uint32_t id)
|
||||
{
|
||||
void macro_SetUniqueID(uint32_t id) {
|
||||
uniqueID = id;
|
||||
if (id == 0 || id == (uint32_t)-1) {
|
||||
uniqueIDPtr = nullptr;
|
||||
@@ -112,26 +105,23 @@ void macro_SetUniqueID(uint32_t id)
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t macro_UseNewUniqueID()
|
||||
{
|
||||
uint32_t macro_UseNewUniqueID() {
|
||||
// A new ID will be generated on the first use of `\@`
|
||||
macro_SetUniqueID(0);
|
||||
return uniqueID;
|
||||
}
|
||||
|
||||
uint32_t macro_UndefUniqueID()
|
||||
{
|
||||
uint32_t macro_UndefUniqueID() {
|
||||
// No ID will be generated; use of `\@` is an error
|
||||
macro_SetUniqueID((uint32_t)-1);
|
||||
return uniqueID;
|
||||
}
|
||||
|
||||
void macro_ShiftCurrentArgs(int32_t count)
|
||||
{
|
||||
void macro_ShiftCurrentArgs(int32_t count) {
|
||||
if (!macroArgs) {
|
||||
error("Cannot shift macro arguments outside of a macro\n");
|
||||
} else if (size_t nbArgs = macroArgs->args.size();
|
||||
count > 0 && ((uint32_t)count > nbArgs || macroArgs->shift > nbArgs - count)) {
|
||||
count > 0 && ((uint32_t)count > nbArgs || macroArgs->shift > nbArgs - count)) {
|
||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end\n");
|
||||
macroArgs->shift = nbArgs;
|
||||
} else if (count < 0 && macroArgs->shift < (uint32_t)-count) {
|
||||
@@ -142,7 +132,6 @@ void macro_ShiftCurrentArgs(int32_t count)
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t macro_NbArgs()
|
||||
{
|
||||
uint32_t macro_NbArgs() {
|
||||
return macroArgs->args.size() - macroArgs->shift;
|
||||
}
|
||||
|
||||
138
src/asm/main.cpp
138
src/asm/main.cpp
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "asm/main.hpp"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <float.h>
|
||||
@@ -7,43 +9,43 @@
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <time.h>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "extern/getopt.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "parser.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
#include "asm/fixpoint.hpp"
|
||||
#include "asm/format.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/lexer.hpp"
|
||||
#include "asm/main.hpp"
|
||||
#include "asm/opt.hpp"
|
||||
#include "asm/output.hpp"
|
||||
#include "asm/rpn.hpp"
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
#include "parser.hpp"
|
||||
|
||||
#include "extern/getopt.hpp"
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "error.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#ifdef __clang__
|
||||
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
|
||||
#define __SANITIZE_ADDRESS__
|
||||
#endif // __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
|
||||
#endif // __clang__
|
||||
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
|
||||
#define __SANITIZE_ADDRESS__
|
||||
#endif // __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
|
||||
#endif // __clang__
|
||||
|
||||
#ifdef __SANITIZE_ADDRESS__
|
||||
// There are known, non-trivial to fix leaks. We would still like to have `make develop'
|
||||
// detect memory corruption, though.
|
||||
extern "C" {
|
||||
char const *__asan_default_options() { return "detect_leaks=0"; }
|
||||
char const *__asan_default_options() {
|
||||
return "detect_leaks=0";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -65,8 +67,7 @@ bool verbose;
|
||||
bool warnings; // True to enable warnings, false to disable them.
|
||||
|
||||
// Escapes Make-special chars from a string
|
||||
static std::string make_escape(std::string &str)
|
||||
{
|
||||
static std::string make_escape(std::string &str) {
|
||||
std::string escaped;
|
||||
size_t pos = 0;
|
||||
|
||||
@@ -98,54 +99,53 @@ static int depType; // Variants of `-M`
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching
|
||||
static option const longopts[] = {
|
||||
{ "binary-digits", required_argument, nullptr, 'b' },
|
||||
{ "define", required_argument, nullptr, 'D' },
|
||||
{ "export-all", no_argument, nullptr, 'E' },
|
||||
{ "gfx-chars", required_argument, nullptr, 'g' },
|
||||
{ "nop-after-halt", no_argument, nullptr, 'H' },
|
||||
{ "halt-without-nop", no_argument, nullptr, 'h' },
|
||||
{ "include", required_argument, nullptr, 'I' },
|
||||
{ "preserve-ld", no_argument, nullptr, 'L' },
|
||||
{ "auto-ldh", no_argument, nullptr, 'l' },
|
||||
{ "dependfile", required_argument, nullptr, 'M' },
|
||||
{ "MG", no_argument, &depType, 'G' },
|
||||
{ "MP", no_argument, &depType, 'P' },
|
||||
{ "MT", required_argument, &depType, 'T' },
|
||||
{ "warning", required_argument, nullptr, 'W' },
|
||||
{ "MQ", required_argument, &depType, 'Q' },
|
||||
{ "output", required_argument, nullptr, 'o' },
|
||||
{ "preinclude", required_argument, nullptr, 'P' },
|
||||
{ "pad-value", required_argument, nullptr, 'p' },
|
||||
{ "q-precision", required_argument, nullptr, 'Q' },
|
||||
{ "recursion-depth", required_argument, nullptr, 'r' },
|
||||
{ "version", no_argument, nullptr, 'V' },
|
||||
{ "verbose", no_argument, nullptr, 'v' },
|
||||
{ "warning", required_argument, nullptr, 'W' },
|
||||
{ "max-errors", required_argument, nullptr, 'X' },
|
||||
{ nullptr, no_argument, nullptr, 0 }
|
||||
{"binary-digits", required_argument, nullptr, 'b'},
|
||||
{"define", required_argument, nullptr, 'D'},
|
||||
{"export-all", no_argument, nullptr, 'E'},
|
||||
{"gfx-chars", required_argument, nullptr, 'g'},
|
||||
{"nop-after-halt", no_argument, nullptr, 'H'},
|
||||
{"halt-without-nop", no_argument, nullptr, 'h'},
|
||||
{"include", required_argument, nullptr, 'I'},
|
||||
{"preserve-ld", no_argument, nullptr, 'L'},
|
||||
{"auto-ldh", no_argument, nullptr, 'l'},
|
||||
{"dependfile", required_argument, nullptr, 'M'},
|
||||
{"MG", no_argument, &depType, 'G'},
|
||||
{"MP", no_argument, &depType, 'P'},
|
||||
{"MT", required_argument, &depType, 'T'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"MQ", required_argument, &depType, 'Q'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"preinclude", required_argument, nullptr, 'P'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
{"q-precision", required_argument, nullptr, 'Q'},
|
||||
{"recursion-depth", required_argument, nullptr, 'r'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"max-errors", required_argument, nullptr, 'X'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
static void printUsage()
|
||||
{
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbasm [-EHhLlVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
||||
" [-r depth] [-W warning] [-X max_errors] <file>\n"
|
||||
"Useful options:\n"
|
||||
" -E, --export-all export all labels\n"
|
||||
" -M, --dependfile <path> set the output dependency file\n"
|
||||
" -o, --output <path> set the output object file\n"
|
||||
" -p, --pad-value <value> set the value to use for `ds'\n"
|
||||
" -V, --version print RGBASM version and exit\n"
|
||||
" -W, --warning <warning> enable or disable warnings\n"
|
||||
"\n"
|
||||
"For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n",
|
||||
stderr);
|
||||
"Usage: rgbasm [-EHhLlVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
||||
" [-r depth] [-W warning] [-X max_errors] <file>\n"
|
||||
"Useful options:\n"
|
||||
" -E, --export-all export all labels\n"
|
||||
" -M, --dependfile <path> set the output dependency file\n"
|
||||
" -o, --output <path> set the output object file\n"
|
||||
" -p, --pad-value <value> set the value to use for `ds'\n"
|
||||
" -V, --version print RGBASM version and exit\n"
|
||||
" -W, --warning <warning> enable or disable warnings\n"
|
||||
"\n"
|
||||
"For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n",
|
||||
stderr
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int main(int argc, char *argv[]) {
|
||||
time_t now = time(nullptr);
|
||||
char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH");
|
||||
|
||||
@@ -213,8 +213,9 @@ int main(int argc, char *argv[])
|
||||
|
||||
case 'H':
|
||||
if (warnOnHaltNop)
|
||||
warning(WARNING_OBSOLETE,
|
||||
"Automatic `nop` after `halt` (the `-H` flag) is deprecated\n");
|
||||
warning(
|
||||
WARNING_OBSOLETE, "Automatic `nop` after `halt` (the `-H` flag) is deprecated\n"
|
||||
);
|
||||
else
|
||||
errx("`-H` and `-h` don't make sense together");
|
||||
haltNop = true;
|
||||
@@ -240,8 +241,10 @@ int main(int argc, char *argv[])
|
||||
break;
|
||||
case 'l':
|
||||
if (warnOnLdOpt)
|
||||
warning(WARNING_OBSOLETE,
|
||||
"Automatic `ld` to `ldh` optimization (the `-l` flag) is deprecated\n");
|
||||
warning(
|
||||
WARNING_OBSOLETE,
|
||||
"Automatic `ld` to `ldh` optimization (the `-l` flag) is deprecated\n"
|
||||
);
|
||||
else
|
||||
errx("`-L` and `-l` don't make sense together");
|
||||
optimizeLoads = true;
|
||||
@@ -371,7 +374,9 @@ int main(int argc, char *argv[])
|
||||
targetFileName = objectName;
|
||||
|
||||
if (argc == musl_optind) {
|
||||
fputs("FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr);
|
||||
fputs(
|
||||
"FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr
|
||||
);
|
||||
printUsage();
|
||||
exit(1);
|
||||
} else if (argc != musl_optind + 1) {
|
||||
@@ -387,7 +392,8 @@ int main(int argc, char *argv[])
|
||||
|
||||
if (dependfile) {
|
||||
if (targetFileName.empty())
|
||||
errx("Dependency files can only be created if a target file is specified with either -o, -MQ or -MT");
|
||||
errx("Dependency files can only be created if a target file is specified with either "
|
||||
"-o, -MQ or -MT");
|
||||
|
||||
fprintf(dependfile, "%s: %s\n", targetFileName.c_str(), mainFileName);
|
||||
}
|
||||
|
||||
@@ -33,59 +33,48 @@ struct OptStackEntry {
|
||||
|
||||
static std::stack<OptStackEntry> stack;
|
||||
|
||||
void opt_B(char const chars[2])
|
||||
{
|
||||
void opt_B(char const chars[2]) {
|
||||
lexer_SetBinDigits(chars);
|
||||
}
|
||||
|
||||
void opt_G(char const chars[4])
|
||||
{
|
||||
void opt_G(char const chars[4]) {
|
||||
lexer_SetGfxDigits(chars);
|
||||
}
|
||||
|
||||
void opt_P(uint8_t padByte)
|
||||
{
|
||||
void opt_P(uint8_t padByte) {
|
||||
fillByte = padByte;
|
||||
}
|
||||
|
||||
void opt_Q(uint8_t precision)
|
||||
{
|
||||
void opt_Q(uint8_t precision) {
|
||||
fixPrecision = precision;
|
||||
}
|
||||
|
||||
void opt_R(size_t newDepth)
|
||||
{
|
||||
void opt_R(size_t newDepth) {
|
||||
fstk_NewRecursionDepth(newDepth);
|
||||
lexer_CheckRecursionDepth();
|
||||
}
|
||||
|
||||
void opt_H(bool warn)
|
||||
{
|
||||
void opt_H(bool warn) {
|
||||
warnOnHaltNop = warn;
|
||||
}
|
||||
|
||||
void opt_h(bool halt)
|
||||
{
|
||||
void opt_h(bool halt) {
|
||||
haltNop = halt;
|
||||
}
|
||||
|
||||
void opt_L(bool optimize)
|
||||
{
|
||||
void opt_L(bool optimize) {
|
||||
optimizeLoads = optimize;
|
||||
}
|
||||
|
||||
void opt_l(bool warn)
|
||||
{
|
||||
void opt_l(bool warn) {
|
||||
warnOnLdOpt = warn;
|
||||
}
|
||||
|
||||
void opt_W(char *flag)
|
||||
{
|
||||
void opt_W(char *flag) {
|
||||
processWarningFlag(flag);
|
||||
}
|
||||
|
||||
void opt_Parse(char *s)
|
||||
{
|
||||
void opt_Parse(char *s) {
|
||||
switch (s[0]) {
|
||||
case 'b':
|
||||
if (strlen(&s[1]) == 2)
|
||||
@@ -239,8 +228,7 @@ void opt_Parse(char *s)
|
||||
}
|
||||
}
|
||||
|
||||
void opt_Push()
|
||||
{
|
||||
void opt_Push() {
|
||||
OptStackEntry entry;
|
||||
|
||||
// Both of these are pulled from lexer.hpp
|
||||
@@ -273,8 +261,7 @@ void opt_Push()
|
||||
stack.push(entry);
|
||||
}
|
||||
|
||||
void opt_Pop()
|
||||
{
|
||||
void opt_Pop() {
|
||||
if (stack.empty()) {
|
||||
error("No entries in the option stack\n");
|
||||
return;
|
||||
|
||||
@@ -2,30 +2,31 @@
|
||||
|
||||
// Outputs an objectfile
|
||||
|
||||
#include "asm/output.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <deque>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/main.hpp"
|
||||
#include "asm/output.hpp"
|
||||
#include "asm/rpn.hpp"
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
struct Assertion {
|
||||
Patch patch;
|
||||
Section *section;
|
||||
@@ -42,8 +43,7 @@ static std::deque<Assertion> assertions;
|
||||
static std::deque<FileStackNode *> fileStackNodes;
|
||||
|
||||
// Write a long to a file (little-endian)
|
||||
static void putlong(uint32_t i, FILE *f)
|
||||
{
|
||||
static void putlong(uint32_t i, FILE *f) {
|
||||
putc(i, f);
|
||||
putc(i >> 8, f);
|
||||
putc(i >> 16, f);
|
||||
@@ -51,15 +51,13 @@ static void putlong(uint32_t i, FILE *f)
|
||||
}
|
||||
|
||||
// Write a NUL-terminated string to a file
|
||||
static void putstring(char const *s, FILE *f)
|
||||
{
|
||||
static void putstring(char const *s, FILE *f) {
|
||||
while (*s)
|
||||
putc(*s++, f);
|
||||
putc(0, f);
|
||||
}
|
||||
|
||||
void out_RegisterNode(FileStackNode *node)
|
||||
{
|
||||
void out_RegisterNode(FileStackNode *node) {
|
||||
// If node is not already registered, register it (and parents), and give it a unique ID
|
||||
for (; node && node->ID == (uint32_t)-1; node = node->parent) {
|
||||
node->ID = fileStackNodes.size();
|
||||
@@ -67,8 +65,7 @@ void out_RegisterNode(FileStackNode *node)
|
||||
}
|
||||
}
|
||||
|
||||
void out_ReplaceNode(FileStackNode * /* node */)
|
||||
{
|
||||
void out_ReplaceNode(FileStackNode * /* node */) {
|
||||
#if 0
|
||||
This is code intended to replace a node, which is pretty useless until ref counting is added...
|
||||
|
||||
@@ -84,8 +81,7 @@ This is code intended to replace a node, which is pretty useless until ref count
|
||||
}
|
||||
|
||||
// Return a section's ID, or -1 if the section is not in the list
|
||||
static uint32_t getSectIDIfAny(Section *sect)
|
||||
{
|
||||
static uint32_t getSectIDIfAny(Section *sect) {
|
||||
if (!sect)
|
||||
return (uint32_t)-1;
|
||||
|
||||
@@ -98,8 +94,7 @@ static uint32_t getSectIDIfAny(Section *sect)
|
||||
}
|
||||
|
||||
// Write a patch to a file
|
||||
static void writepatch(Patch const &patch, FILE *f)
|
||||
{
|
||||
static void writepatch(Patch const &patch, FILE *f) {
|
||||
assert(patch.src->ID != (uint32_t)-1);
|
||||
putlong(patch.src->ID, f);
|
||||
putlong(patch.lineNo, f);
|
||||
@@ -112,8 +107,7 @@ static void writepatch(Patch const &patch, FILE *f)
|
||||
}
|
||||
|
||||
// Write a section to a file
|
||||
static void writesection(Section const §, FILE *f)
|
||||
{
|
||||
static void writesection(Section const §, FILE *f) {
|
||||
putstring(sect.name.c_str(), f);
|
||||
|
||||
putlong(sect.size, f);
|
||||
@@ -138,8 +132,7 @@ static void writesection(Section const §, FILE *f)
|
||||
}
|
||||
|
||||
// Write a symbol to a file
|
||||
static void writesymbol(Symbol const &sym, FILE *f)
|
||||
{
|
||||
static void writesymbol(Symbol const &sym, FILE *f) {
|
||||
putstring(sym.name, f);
|
||||
if (!sym.isDefined()) {
|
||||
putc(SYMTYPE_IMPORT, f);
|
||||
@@ -154,8 +147,7 @@ static void writesymbol(Symbol const &sym, FILE *f)
|
||||
}
|
||||
}
|
||||
|
||||
static void registerSymbol(Symbol &sym)
|
||||
{
|
||||
static void registerSymbol(Symbol &sym) {
|
||||
sym.ID = objectSymbols.size();
|
||||
objectSymbols.push_back(&sym);
|
||||
out_RegisterNode(sym.src);
|
||||
@@ -163,19 +155,17 @@ static void registerSymbol(Symbol &sym)
|
||||
|
||||
// Returns a symbol's ID within the object file
|
||||
// If the symbol does not have one, one is assigned by registering the symbol
|
||||
static uint32_t getSymbolID(Symbol &sym)
|
||||
{
|
||||
static uint32_t getSymbolID(Symbol &sym) {
|
||||
if (sym.ID == (uint32_t)-1 && !sym_IsPC(&sym))
|
||||
registerSymbol(sym);
|
||||
return sym.ID;
|
||||
}
|
||||
|
||||
static void writerpn(std::vector<uint8_t> &rpnexpr, const std::vector<uint8_t> &rpn)
|
||||
{
|
||||
static void writerpn(std::vector<uint8_t> &rpnexpr, const std::vector<uint8_t> &rpn) {
|
||||
char symName[512];
|
||||
size_t rpnptr = 0;
|
||||
|
||||
for (size_t offset = 0; offset < rpn.size(); ) {
|
||||
for (size_t offset = 0; offset < rpn.size();) {
|
||||
uint8_t rpndata = rpn[offset++];
|
||||
|
||||
switch (rpndata) {
|
||||
@@ -262,8 +252,7 @@ static void writerpn(std::vector<uint8_t> &rpnexpr, const std::vector<uint8_t> &
|
||||
}
|
||||
}
|
||||
|
||||
static void initpatch(Patch &patch, uint32_t type, Expression const &expr, uint32_t ofs)
|
||||
{
|
||||
static void initpatch(Patch &patch, uint32_t type, Expression const &expr, uint32_t ofs) {
|
||||
FileStackNode *node = fstk_GetFileStack();
|
||||
|
||||
patch.type = type;
|
||||
@@ -290,8 +279,7 @@ static void initpatch(Patch &patch, uint32_t type, Expression const &expr, uint3
|
||||
}
|
||||
|
||||
// Create a new patch (includes the rpn expr)
|
||||
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift)
|
||||
{
|
||||
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift) {
|
||||
// Add the patch to the list
|
||||
Patch &patch = currentSection->patches.emplace_front();
|
||||
|
||||
@@ -304,22 +292,21 @@ void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32
|
||||
}
|
||||
|
||||
// Creates an assert that will be written to the object file
|
||||
void out_CreateAssert(enum AssertionType type, Expression const &expr, char const *message, uint32_t ofs)
|
||||
{
|
||||
void out_CreateAssert(
|
||||
enum AssertionType type, Expression const &expr, char const *message, uint32_t ofs
|
||||
) {
|
||||
Assertion &assertion = assertions.emplace_front();
|
||||
|
||||
initpatch(assertion.patch, type, expr, ofs);
|
||||
assertion.message = message;
|
||||
}
|
||||
|
||||
static void writeassert(Assertion &assert, FILE *f)
|
||||
{
|
||||
static void writeassert(Assertion &assert, FILE *f) {
|
||||
writepatch(assert.patch, f);
|
||||
putstring(assert.message.c_str(), f);
|
||||
}
|
||||
|
||||
static void writeFileStackNode(FileStackNode const &node, FILE *f)
|
||||
{
|
||||
static void writeFileStackNode(FileStackNode const &node, FILE *f) {
|
||||
putlong(node.parent ? node.parent->ID : (uint32_t)-1, f);
|
||||
putlong(node.lineNo, f);
|
||||
putc(node.type, f);
|
||||
@@ -330,13 +317,12 @@ static void writeFileStackNode(FileStackNode const &node, FILE *f)
|
||||
|
||||
putlong(nodeIters.size(), f);
|
||||
// Iters are stored by decreasing depth, so reverse the order for output
|
||||
for (uint32_t i = nodeIters.size(); i--; )
|
||||
for (uint32_t i = nodeIters.size(); i--;)
|
||||
putlong(nodeIters[i], f);
|
||||
}
|
||||
}
|
||||
|
||||
static void registerUnregisteredSymbol(Symbol &sym)
|
||||
{
|
||||
static void registerUnregisteredSymbol(Symbol &sym) {
|
||||
// Check for symbol->src, to skip any built-in symbol from rgbasm
|
||||
if (sym.src && sym.ID == (uint32_t)-1) {
|
||||
registerSymbol(sym);
|
||||
@@ -344,8 +330,7 @@ static void registerUnregisteredSymbol(Symbol &sym)
|
||||
}
|
||||
|
||||
// Write an objectfile
|
||||
void out_WriteObject()
|
||||
{
|
||||
void out_WriteObject() {
|
||||
FILE *f;
|
||||
|
||||
if (strcmp(objectName, "-")) {
|
||||
@@ -374,9 +359,12 @@ void out_WriteObject()
|
||||
|
||||
// The list is supposed to have decrementing IDs
|
||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node->ID - 1)
|
||||
fatalerror("Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
||||
". Please report this to the developers!\n",
|
||||
it[1]->ID, node->ID);
|
||||
fatalerror(
|
||||
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
||||
". Please report this to the developers!\n",
|
||||
it[1]->ID,
|
||||
node->ID
|
||||
);
|
||||
}
|
||||
|
||||
for (Symbol const *sym : objectSymbols)
|
||||
@@ -394,8 +382,7 @@ void out_WriteObject()
|
||||
}
|
||||
|
||||
// Set the objectfilename
|
||||
void out_SetFileName(char *s)
|
||||
{
|
||||
void out_SetFileName(char *s) {
|
||||
if (objectName)
|
||||
warnx("Overriding output filename %s", objectName);
|
||||
objectName = s;
|
||||
|
||||
4002
src/asm/parser.y
4002
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
166
src/asm/rpn.cpp
166
src/asm/rpn.cpp
@@ -2,6 +2,8 @@
|
||||
|
||||
// Controls RPN expressions for objectfiles
|
||||
|
||||
#include "asm/rpn.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
@@ -12,18 +14,16 @@
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include "opmath.hpp"
|
||||
|
||||
#include "asm/main.hpp"
|
||||
#include "asm/output.hpp"
|
||||
#include "asm/rpn.hpp"
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#include "opmath.hpp"
|
||||
|
||||
// Init a RPN expression
|
||||
static void initExpression(Expression &expr)
|
||||
{
|
||||
static void initExpression(Expression &expr) {
|
||||
expr.reason = nullptr;
|
||||
expr.isKnown = true;
|
||||
expr.isSymbol = false;
|
||||
@@ -33,8 +33,7 @@ static void initExpression(Expression &expr)
|
||||
|
||||
// Makes an expression "not known", also setting its error message
|
||||
template<typename... Ts>
|
||||
static void makeUnknown(Expression &expr, Ts ...parts)
|
||||
{
|
||||
static void makeUnknown(Expression &expr, Ts... parts) {
|
||||
expr.isKnown = false;
|
||||
expr.reason = new std::string();
|
||||
if (!expr.reason)
|
||||
@@ -42,10 +41,9 @@ static void makeUnknown(Expression &expr, Ts ...parts)
|
||||
(expr.reason->append(parts), ...);
|
||||
}
|
||||
|
||||
static uint8_t *reserveSpace(Expression &expr, uint32_t size)
|
||||
{
|
||||
static uint8_t *reserveSpace(Expression &expr, uint32_t size) {
|
||||
if (!expr.rpn) {
|
||||
expr.rpn = new(std::nothrow) std::vector<uint8_t>();
|
||||
expr.rpn = new (std::nothrow) std::vector<uint8_t>();
|
||||
if (!expr.rpn)
|
||||
fatalerror("Failed to allocate RPN expression: %s\n", strerror(errno));
|
||||
}
|
||||
@@ -57,22 +55,19 @@ static uint8_t *reserveSpace(Expression &expr, uint32_t size)
|
||||
}
|
||||
|
||||
// Free the RPN expression
|
||||
void rpn_Free(Expression &expr)
|
||||
{
|
||||
void rpn_Free(Expression &expr) {
|
||||
delete expr.rpn;
|
||||
delete expr.reason;
|
||||
initExpression(expr);
|
||||
}
|
||||
|
||||
// Add symbols, constants and operators to expression
|
||||
void rpn_Number(Expression &expr, uint32_t val)
|
||||
{
|
||||
void rpn_Number(Expression &expr, uint32_t val) {
|
||||
initExpression(expr);
|
||||
expr.val = val;
|
||||
}
|
||||
|
||||
void rpn_Symbol(Expression &expr, char const *symName)
|
||||
{
|
||||
void rpn_Symbol(Expression &expr, char const *symName) {
|
||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
|
||||
if (sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||
@@ -98,8 +93,7 @@ void rpn_Symbol(Expression &expr, char const *symName)
|
||||
}
|
||||
}
|
||||
|
||||
static void bankSelf(Expression &expr)
|
||||
{
|
||||
static void bankSelf(Expression &expr) {
|
||||
initExpression(expr);
|
||||
|
||||
if (!currentSection) {
|
||||
@@ -114,8 +108,7 @@ static void bankSelf(Expression &expr)
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_BankSymbol(Expression &expr, char const *symName)
|
||||
{
|
||||
void rpn_BankSymbol(Expression &expr, char const *symName) {
|
||||
Symbol const *sym = sym_FindScopedSymbol(symName);
|
||||
|
||||
// The @ symbol is treated differently.
|
||||
@@ -147,8 +140,7 @@ void rpn_BankSymbol(Expression &expr, char const *symName)
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_BankSection(Expression &expr, char const *sectionName)
|
||||
{
|
||||
void rpn_BankSection(Expression &expr, char const *sectionName) {
|
||||
initExpression(expr);
|
||||
|
||||
Section *section = sect_FindSectionByName(sectionName);
|
||||
@@ -167,8 +159,7 @@ void rpn_BankSection(Expression &expr, char const *sectionName)
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_SizeOfSection(Expression &expr, char const *sectionName)
|
||||
{
|
||||
void rpn_SizeOfSection(Expression &expr, char const *sectionName) {
|
||||
initExpression(expr);
|
||||
|
||||
Section *section = sect_FindSectionByName(sectionName);
|
||||
@@ -187,8 +178,7 @@ void rpn_SizeOfSection(Expression &expr, char const *sectionName)
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_StartOfSection(Expression &expr, char const *sectionName)
|
||||
{
|
||||
void rpn_StartOfSection(Expression &expr, char const *sectionName) {
|
||||
initExpression(expr);
|
||||
|
||||
Section *section = sect_FindSectionByName(sectionName);
|
||||
@@ -207,8 +197,7 @@ void rpn_StartOfSection(Expression &expr, char const *sectionName)
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_SizeOfSectionType(Expression &expr, enum SectionType type)
|
||||
{
|
||||
void rpn_SizeOfSectionType(Expression &expr, enum SectionType type) {
|
||||
initExpression(expr);
|
||||
makeUnknown(expr, "Section type's size is not known");
|
||||
|
||||
@@ -219,8 +208,7 @@ void rpn_SizeOfSectionType(Expression &expr, enum SectionType type)
|
||||
*ptr++ = type;
|
||||
}
|
||||
|
||||
void rpn_StartOfSectionType(Expression &expr, enum SectionType type)
|
||||
{
|
||||
void rpn_StartOfSectionType(Expression &expr, enum SectionType type) {
|
||||
initExpression(expr);
|
||||
makeUnknown(expr, "Section type's start is not known");
|
||||
|
||||
@@ -231,8 +219,7 @@ void rpn_StartOfSectionType(Expression &expr, enum SectionType type)
|
||||
*ptr++ = type;
|
||||
}
|
||||
|
||||
void rpn_CheckHRAM(Expression &expr, const Expression &src)
|
||||
{
|
||||
void rpn_CheckHRAM(Expression &expr, const Expression &src) {
|
||||
expr = src;
|
||||
expr.isSymbol = false;
|
||||
|
||||
@@ -247,8 +234,7 @@ void rpn_CheckHRAM(Expression &expr, const Expression &src)
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_CheckRST(Expression &expr, const Expression &src)
|
||||
{
|
||||
void rpn_CheckRST(Expression &expr, const Expression &src) {
|
||||
expr = src;
|
||||
|
||||
if (expr.isKnown) {
|
||||
@@ -264,9 +250,8 @@ void rpn_CheckRST(Expression &expr, const Expression &src)
|
||||
}
|
||||
|
||||
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||
void rpn_CheckNBit(Expression const &expr, uint8_t n)
|
||||
{
|
||||
assert(n != 0); // That doesn't make sense
|
||||
void rpn_CheckNBit(Expression const &expr, uint8_t n) {
|
||||
assert(n != 0); // That doesn't make sense
|
||||
assert(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
||||
|
||||
if (expr.isKnown) {
|
||||
@@ -279,8 +264,7 @@ void rpn_CheckNBit(Expression const &expr, uint8_t n)
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Expression::getConstVal() const
|
||||
{
|
||||
int32_t Expression::getConstVal() const {
|
||||
if (!isKnown) {
|
||||
error("Expected constant expression: %s\n", reason->c_str());
|
||||
return 0;
|
||||
@@ -288,8 +272,7 @@ int32_t Expression::getConstVal() const
|
||||
return val;
|
||||
}
|
||||
|
||||
void rpn_LOGNOT(Expression &expr, const Expression &src)
|
||||
{
|
||||
void rpn_LOGNOT(Expression &expr, const Expression &src) {
|
||||
expr = src;
|
||||
expr.isSymbol = false;
|
||||
|
||||
@@ -301,15 +284,13 @@ void rpn_LOGNOT(Expression &expr, const Expression &src)
|
||||
}
|
||||
}
|
||||
|
||||
Symbol const *Expression::symbolOf() const
|
||||
{
|
||||
Symbol const *Expression::symbolOf() const {
|
||||
if (!isSymbol)
|
||||
return nullptr;
|
||||
return sym_FindScopedSymbol((char const *)&(*rpn)[1]);
|
||||
}
|
||||
|
||||
bool Expression::isDiffConstant(Symbol const *sym) const
|
||||
{
|
||||
bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||
// Check if both expressions only refer to a single symbol
|
||||
Symbol const *sym1 = symbolOf();
|
||||
|
||||
@@ -328,8 +309,7 @@ bool Expression::isDiffConstant(Symbol const *sym) const
|
||||
*
|
||||
* @return The constant result if it can be computed, or -1 otherwise.
|
||||
*/
|
||||
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs)
|
||||
{
|
||||
static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
Symbol const *lhsSymbol = lhs.symbolOf();
|
||||
Symbol const *rhsSymbol = lhsSymbol ? nullptr : rhs.symbolOf();
|
||||
bool lhsIsSymbol = lhsSymbol && lhsSymbol->getSection();
|
||||
@@ -362,8 +342,9 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs)
|
||||
return (symbolOfs + sect.alignOfs) & ~unknownBits;
|
||||
}
|
||||
|
||||
void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1, const Expression &src2)
|
||||
{
|
||||
void rpn_BinaryOp(
|
||||
enum RPNCommand op, Expression &expr, const Expression &src1, const Expression &src2
|
||||
) {
|
||||
expr.isSymbol = false;
|
||||
int32_t constMaskVal;
|
||||
|
||||
@@ -417,43 +398,47 @@ void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1,
|
||||
break;
|
||||
case RPN_SHL:
|
||||
if (src2.val < 0)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting left by negative amount %" PRId32 "\n",
|
||||
src2.val);
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", src2.val
|
||||
);
|
||||
|
||||
if (src2.val >= 32)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting left by large amount %" PRId32 "\n", src2.val);
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", src2.val
|
||||
);
|
||||
|
||||
expr.val = op_shift_left(src1.val, src2.val);
|
||||
break;
|
||||
case RPN_SHR:
|
||||
if (src1.val < 0)
|
||||
warning(WARNING_SHIFT,
|
||||
"Shifting right negative value %" PRId32 "\n", src1.val);
|
||||
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", src1.val);
|
||||
|
||||
if (src2.val < 0)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by negative amount %" PRId32 "\n",
|
||||
src2.val);
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by negative amount %" PRId32 "\n",
|
||||
src2.val
|
||||
);
|
||||
|
||||
if (src2.val >= 32)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by large amount %" PRId32 "\n",
|
||||
src2.val);
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", src2.val
|
||||
);
|
||||
|
||||
expr.val = op_shift_right(src1.val, src2.val);
|
||||
break;
|
||||
case RPN_USHR:
|
||||
if (src2.val < 0)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by negative amount %" PRId32 "\n",
|
||||
src2.val);
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by negative amount %" PRId32 "\n",
|
||||
src2.val
|
||||
);
|
||||
|
||||
if (src2.val >= 32)
|
||||
warning(WARNING_SHIFT_AMOUNT,
|
||||
"Shifting right by large amount %" PRId32 "\n",
|
||||
src2.val);
|
||||
warning(
|
||||
WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", src2.val
|
||||
);
|
||||
|
||||
expr.val = op_shift_right_unsigned(src1.val, src2.val);
|
||||
break;
|
||||
@@ -465,9 +450,12 @@ void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1,
|
||||
fatalerror("Division by zero\n");
|
||||
|
||||
if (src1.val == INT32_MIN && src2.val == -1) {
|
||||
warning(WARNING_DIV,
|
||||
"Division of %" PRId32 " by -1 yields %" PRId32 "\n",
|
||||
INT32_MIN, INT32_MIN);
|
||||
warning(
|
||||
WARNING_DIV,
|
||||
"Division of %" PRId32 " by -1 yields %" PRId32 "\n",
|
||||
INT32_MIN,
|
||||
INT32_MIN
|
||||
);
|
||||
expr.val = INT32_MIN;
|
||||
} else {
|
||||
expr.val = op_divide(src1.val, src2.val);
|
||||
@@ -521,8 +509,13 @@ void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1,
|
||||
// Convert the left-hand expression if it's constant
|
||||
if (src1.isKnown) {
|
||||
uint32_t lval = src1.val;
|
||||
uint8_t bytes[] = {RPN_CONST, (uint8_t)lval, (uint8_t)(lval >> 8),
|
||||
(uint8_t)(lval >> 16), (uint8_t)(lval >> 24)};
|
||||
uint8_t bytes[] = {
|
||||
RPN_CONST,
|
||||
(uint8_t)lval,
|
||||
(uint8_t)(lval >> 8),
|
||||
(uint8_t)(lval >> 16),
|
||||
(uint8_t)(lval >> 24),
|
||||
};
|
||||
expr.rpnPatchSize = sizeof(bytes);
|
||||
expr.rpn = nullptr;
|
||||
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
|
||||
@@ -545,8 +538,13 @@ void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1,
|
||||
|
||||
// If the right expression is constant, merge a shim instead
|
||||
uint32_t rval = src2.val;
|
||||
uint8_t bytes[] = {RPN_CONST, (uint8_t)rval, (uint8_t)(rval >> 8),
|
||||
(uint8_t)(rval >> 16), (uint8_t)(rval >> 24)};
|
||||
uint8_t bytes[] = {
|
||||
RPN_CONST,
|
||||
(uint8_t)rval,
|
||||
(uint8_t)(rval >> 8),
|
||||
(uint8_t)(rval >> 16),
|
||||
(uint8_t)(rval >> 24),
|
||||
};
|
||||
if (src2.isKnown) {
|
||||
ptr = bytes;
|
||||
len = sizeof(bytes);
|
||||
@@ -569,23 +567,20 @@ void rpn_BinaryOp(enum RPNCommand op, Expression &expr, const Expression &src1,
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_HIGH(Expression &expr, const Expression &src)
|
||||
{
|
||||
void rpn_HIGH(Expression &expr, const Expression &src) {
|
||||
expr = src;
|
||||
expr.isSymbol = false;
|
||||
|
||||
if (expr.isKnown) {
|
||||
expr.val = (uint32_t)expr.val >> 8 & 0xFF;
|
||||
} else {
|
||||
uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR,
|
||||
RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
|
||||
uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR, RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
|
||||
expr.rpnPatchSize += sizeof(bytes);
|
||||
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_LOW(Expression &expr, const Expression &src)
|
||||
{
|
||||
void rpn_LOW(Expression &expr, const Expression &src) {
|
||||
expr = src;
|
||||
expr.isSymbol = false;
|
||||
|
||||
@@ -599,16 +594,14 @@ void rpn_LOW(Expression &expr, const Expression &src)
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_ISCONST(Expression &expr, const Expression &src)
|
||||
{
|
||||
void rpn_ISCONST(Expression &expr, const Expression &src) {
|
||||
initExpression(expr);
|
||||
expr.val = src.isKnown;
|
||||
expr.isKnown = true;
|
||||
expr.isSymbol = false;
|
||||
}
|
||||
|
||||
void rpn_NEG(Expression &expr, const Expression &src)
|
||||
{
|
||||
void rpn_NEG(Expression &expr, const Expression &src) {
|
||||
expr = src;
|
||||
expr.isSymbol = false;
|
||||
|
||||
@@ -620,8 +613,7 @@ void rpn_NEG(Expression &expr, const Expression &src)
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_NOT(Expression &expr, const Expression &src)
|
||||
{
|
||||
void rpn_NOT(Expression &expr, const Expression &src) {
|
||||
expr = src;
|
||||
expr.isSymbol = false;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "asm/section.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <deque>
|
||||
@@ -9,21 +11,20 @@
|
||||
#include <stack>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/main.hpp"
|
||||
#include "asm/output.hpp"
|
||||
#include "asm/rpn.hpp"
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
uint8_t fillByte;
|
||||
|
||||
struct UnionStackEntry {
|
||||
@@ -50,8 +51,7 @@ char const *currentLoadScope = nullptr;
|
||||
int32_t loadOffset; // Offset into the LOAD section's parent (see sect_GetOutputOffset)
|
||||
|
||||
// A quick check to see if we have an initialized section
|
||||
attr_(warn_unused_result) static bool checksection()
|
||||
{
|
||||
attr_(warn_unused_result) static bool checksection() {
|
||||
if (currentSection)
|
||||
return true;
|
||||
|
||||
@@ -61,35 +61,38 @@ attr_(warn_unused_result) static bool checksection()
|
||||
|
||||
// A quick check to see if we have an initialized section that can contain
|
||||
// this much initialized data
|
||||
attr_(warn_unused_result) static bool checkcodesection()
|
||||
{
|
||||
attr_(warn_unused_result) static bool checkcodesection() {
|
||||
if (!checksection())
|
||||
return false;
|
||||
|
||||
if (sect_HasData(currentSection->type))
|
||||
return true;
|
||||
|
||||
error("Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
||||
currentSection->name.c_str());
|
||||
error(
|
||||
"Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
||||
currentSection->name.c_str()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
attr_(warn_unused_result) static bool checkSectionSize(Section const §, uint32_t size)
|
||||
{
|
||||
attr_(warn_unused_result) static bool checkSectionSize(Section const §, uint32_t size) {
|
||||
uint32_t maxSize = sectionTypeInfo[sect.type].size;
|
||||
|
||||
// If the new size is reasonable, keep going
|
||||
if (size <= maxSize)
|
||||
return true;
|
||||
|
||||
error("Section '%s' grew too big (max size = 0x%" PRIX32
|
||||
" bytes, reached 0x%" PRIX32 ").\n", sect.name.c_str(), maxSize, size);
|
||||
error(
|
||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32 ").\n",
|
||||
sect.name.c_str(),
|
||||
maxSize,
|
||||
size
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the section has grown too much.
|
||||
attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
|
||||
{
|
||||
attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size) {
|
||||
// This check is here to trap broken code that generates sections that are too big and to
|
||||
// prevent the assembler from generating huge object files or trying to allocate too much
|
||||
// memory.
|
||||
@@ -97,20 +100,19 @@ attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
|
||||
|
||||
// If the section has already overflowed, skip the check to avoid erroring out ad nauseam
|
||||
if (currentSection->size != UINT32_MAX
|
||||
&& !checkSectionSize(*currentSection, curOffset + loadOffset + delta_size))
|
||||
&& !checkSectionSize(*currentSection, curOffset + loadOffset + delta_size))
|
||||
// Mark the section as overflowed, to avoid repeating the error
|
||||
currentSection->size = UINT32_MAX;
|
||||
|
||||
if (currentLoadSection && currentLoadSection->size != UINT32_MAX
|
||||
&& !checkSectionSize(*currentLoadSection, curOffset + delta_size))
|
||||
&& !checkSectionSize(*currentLoadSection, curOffset + delta_size))
|
||||
currentLoadSection->size = UINT32_MAX;
|
||||
|
||||
return currentSection->size != UINT32_MAX
|
||||
&& (!currentLoadSection || currentLoadSection->size != UINT32_MAX);
|
||||
&& (!currentLoadSection || currentLoadSection->size != UINT32_MAX);
|
||||
}
|
||||
|
||||
Section *sect_FindSectionByName(char const *name)
|
||||
{
|
||||
Section *sect_FindSectionByName(char const *name) {
|
||||
for (Section § : sectionList) {
|
||||
if (sect.name == name)
|
||||
return §
|
||||
@@ -119,14 +121,15 @@ Section *sect_FindSectionByName(char const *name)
|
||||
}
|
||||
|
||||
#define mask(align) ((1U << (align)) - 1)
|
||||
#define fail(...) do { \
|
||||
error(__VA_ARGS__); \
|
||||
nbSectErrors++; \
|
||||
} while (0)
|
||||
#define fail(...) \
|
||||
do { \
|
||||
error(__VA_ARGS__); \
|
||||
nbSectErrors++; \
|
||||
} while (0)
|
||||
|
||||
static unsigned int mergeSectUnion(Section §, enum SectionType type, uint32_t org,
|
||||
uint8_t alignment, uint16_t alignOffset)
|
||||
{
|
||||
static unsigned int mergeSectUnion(
|
||||
Section §, enum SectionType type, uint32_t org, uint8_t alignment, uint16_t alignOffset
|
||||
) {
|
||||
assert(alignment < 16); // Should be ensured by the caller
|
||||
unsigned int nbSectErrors = 0;
|
||||
|
||||
@@ -138,11 +141,15 @@ static unsigned int mergeSectUnion(Section §, enum SectionType type, uint32_
|
||||
if (org != (uint32_t)-1) {
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != (uint32_t)-1 && sect.org != org)
|
||||
fail("Section already declared as fixed at different address $%04"
|
||||
PRIx32 "\n", sect.org);
|
||||
fail(
|
||||
"Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org
|
||||
);
|
||||
else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs)))
|
||||
fail("Section already declared as aligned to %u bytes (offset %"
|
||||
PRIu16 ")\n", 1U << sect.align, sect.alignOfs);
|
||||
fail(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
else
|
||||
// Otherwise, just override
|
||||
sect.org = org;
|
||||
@@ -151,13 +158,18 @@ static unsigned int mergeSectUnion(Section §, enum SectionType type, uint32_
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != (uint32_t)-1) {
|
||||
if ((sect.org - alignOffset) & mask(alignment))
|
||||
fail("Section already declared as fixed at incompatible address $%04"
|
||||
PRIx32 "\n", sect.org);
|
||||
// Check if alignment offsets are compatible
|
||||
fail(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
fail("Section already declared with incompatible %u"
|
||||
"-byte alignment (offset %" PRIu16 ")\n",
|
||||
1U << sect.align, sect.alignOfs);
|
||||
fail(
|
||||
"Section already declared with incompatible %u"
|
||||
"-byte alignment (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
} else if (alignment > sect.align) {
|
||||
// If the section is not fixed, its alignment is the largest of both
|
||||
sect.align = alignment;
|
||||
@@ -168,8 +180,8 @@ static unsigned int mergeSectUnion(Section §, enum SectionType type, uint32_
|
||||
return nbSectErrors;
|
||||
}
|
||||
|
||||
static unsigned int mergeFragments(Section §, uint32_t org, uint8_t alignment, uint16_t alignOffset)
|
||||
{
|
||||
static unsigned int
|
||||
mergeFragments(Section §, uint32_t org, uint8_t alignment, uint16_t alignOffset) {
|
||||
assert(alignment < 16); // Should be ensured by the caller
|
||||
unsigned int nbSectErrors = 0;
|
||||
|
||||
@@ -181,11 +193,16 @@ static unsigned int mergeFragments(Section §, uint32_t org, uint8_t alignmen
|
||||
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != (uint32_t)-1 && sect.org != curOrg)
|
||||
fail("Section already declared as fixed at incompatible address $%04"
|
||||
PRIx32 "\n", sect.org);
|
||||
fail(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs)))
|
||||
fail("Section already declared as aligned to %u bytes (offset %"
|
||||
PRIu16 ")\n", 1U << sect.align, sect.alignOfs);
|
||||
fail(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
else
|
||||
// Otherwise, just override
|
||||
sect.org = curOrg;
|
||||
@@ -199,13 +216,18 @@ static unsigned int mergeFragments(Section §, uint32_t org, uint8_t alignmen
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != (uint32_t)-1) {
|
||||
if ((sect.org - curOfs) & mask(alignment))
|
||||
fail("Section already declared as fixed at incompatible address $%04"
|
||||
PRIx32 "\n", sect.org);
|
||||
// Check if alignment offsets are compatible
|
||||
fail(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
);
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
fail("Section already declared with incompatible %u"
|
||||
"-byte alignment (offset %" PRIu16 ")\n",
|
||||
1U << sect.align, sect.alignOfs);
|
||||
fail(
|
||||
"Section already declared with incompatible %u"
|
||||
"-byte alignment (offset %" PRIu16 ")\n",
|
||||
1U << sect.align,
|
||||
sect.alignOfs
|
||||
);
|
||||
} else if (alignment > sect.align) {
|
||||
// If the section is not fixed, its alignment is the largest of both
|
||||
sect.align = alignment;
|
||||
@@ -216,9 +238,15 @@ static unsigned int mergeFragments(Section §, uint32_t org, uint8_t alignmen
|
||||
return nbSectErrors;
|
||||
}
|
||||
|
||||
static void mergeSections(Section §, enum SectionType type, uint32_t org, uint32_t bank,
|
||||
uint8_t alignment, uint16_t alignOffset, enum SectionModifier mod)
|
||||
{
|
||||
static void mergeSections(
|
||||
Section §,
|
||||
enum SectionType type,
|
||||
uint32_t org,
|
||||
uint32_t bank,
|
||||
uint8_t alignment,
|
||||
uint16_t alignOffset,
|
||||
enum SectionModifier mod
|
||||
) {
|
||||
unsigned int nbSectErrors = 0;
|
||||
|
||||
if (type != sect.type)
|
||||
@@ -230,9 +258,9 @@ static void mergeSections(Section §, enum SectionType type, uint32_t org, ui
|
||||
switch (mod) {
|
||||
case SECTION_UNION:
|
||||
case SECTION_FRAGMENT:
|
||||
nbSectErrors += mod == SECTION_UNION ?
|
||||
mergeSectUnion(sect, type, org, alignment, alignOffset) :
|
||||
mergeFragments(sect, org, alignment, alignOffset);
|
||||
nbSectErrors += mod == SECTION_UNION
|
||||
? mergeSectUnion(sect, type, org, alignment, alignOffset)
|
||||
: mergeFragments(sect, org, alignment, alignOffset);
|
||||
|
||||
// Common checks
|
||||
|
||||
@@ -241,8 +269,7 @@ static void mergeSections(Section §, enum SectionType type, uint32_t org, ui
|
||||
sect.bank = bank;
|
||||
// If both specify a bank, it must be the same one
|
||||
else if (bank != (uint32_t)-1 && sect.bank != bank)
|
||||
fail("Section already declared with different bank %" PRIu32 "\n",
|
||||
sect.bank);
|
||||
fail("Section already declared with different bank %" PRIu32 "\n", sect.bank);
|
||||
break;
|
||||
|
||||
case SECTION_NORMAL:
|
||||
@@ -254,16 +281,26 @@ static void mergeSections(Section §, enum SectionType type, uint32_t org, ui
|
||||
}
|
||||
|
||||
if (nbSectErrors)
|
||||
fatalerror("Cannot create section \"%s\" (%u error%s)\n",
|
||||
sect.name.c_str(), nbSectErrors, nbSectErrors == 1 ? "" : "s");
|
||||
fatalerror(
|
||||
"Cannot create section \"%s\" (%u error%s)\n",
|
||||
sect.name.c_str(),
|
||||
nbSectErrors,
|
||||
nbSectErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
|
||||
#undef fail
|
||||
|
||||
// Create a new section, not yet in the list.
|
||||
static Section *createSection(char const *name, enum SectionType type, uint32_t org, uint32_t bank,
|
||||
uint8_t alignment, uint16_t alignOffset, enum SectionModifier mod)
|
||||
{
|
||||
static Section *createSection(
|
||||
char const *name,
|
||||
enum SectionType type,
|
||||
uint32_t org,
|
||||
uint32_t bank,
|
||||
uint8_t alignment,
|
||||
uint16_t alignOffset,
|
||||
enum SectionModifier mod
|
||||
) {
|
||||
// Add the new section to the list (order doesn't matter)
|
||||
Section § = sectionList.emplace_front();
|
||||
|
||||
@@ -286,9 +323,13 @@ static Section *createSection(char const *name, enum SectionType type, uint32_t
|
||||
}
|
||||
|
||||
// Find a section by name and type. If it doesn't exist, create it.
|
||||
static Section *getSection(char const *name, enum SectionType type, uint32_t org,
|
||||
SectionSpec const &attrs, enum SectionModifier mod)
|
||||
{
|
||||
static Section *getSection(
|
||||
char const *name,
|
||||
enum SectionType type,
|
||||
uint32_t org,
|
||||
SectionSpec const &attrs,
|
||||
enum SectionModifier mod
|
||||
) {
|
||||
uint32_t bank = attrs.bank;
|
||||
uint8_t alignment = attrs.alignment;
|
||||
uint16_t alignOffset = attrs.alignOfs;
|
||||
@@ -296,29 +337,41 @@ static Section *getSection(char const *name, enum SectionType type, uint32_t org
|
||||
// First, validate parameters, and normalize them if applicable
|
||||
|
||||
if (bank != (uint32_t)-1) {
|
||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
|
||||
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
|
||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
||||
&& type != SECTTYPE_WRAMX)
|
||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
||||
else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank)
|
||||
error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
|
||||
PRIx32 ")\n", sectionTypeInfo[type].name.c_str(), bank,
|
||||
sectionTypeInfo[type].firstBank, sectionTypeInfo[type].lastBank);
|
||||
error(
|
||||
"%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n",
|
||||
sectionTypeInfo[type].name.c_str(),
|
||||
bank,
|
||||
sectionTypeInfo[type].firstBank,
|
||||
sectionTypeInfo[type].lastBank
|
||||
);
|
||||
} else if (nbbanks(type) == 1) {
|
||||
// If the section type only has a single bank, implicitly force it
|
||||
bank = sectionTypeInfo[type].firstBank;
|
||||
}
|
||||
|
||||
if (alignOffset >= 1 << alignment) {
|
||||
error("Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)\n",
|
||||
alignOffset, 1U << alignment);
|
||||
error(
|
||||
"Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)\n",
|
||||
alignOffset,
|
||||
1U << alignment
|
||||
);
|
||||
alignOffset = 0;
|
||||
}
|
||||
|
||||
if (org != (uint32_t)-1) {
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
|
||||
error("Section \"%s\"'s fixed address $%04" PRIx32
|
||||
" is outside of range [$%04" PRIx16 "; $%04" PRIx16 "]\n",
|
||||
name, org, sectionTypeInfo[type].startAddr, endaddr(type));
|
||||
error(
|
||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||
"; $%04" PRIx16 "]\n",
|
||||
name,
|
||||
org,
|
||||
sectionTypeInfo[type].startAddr,
|
||||
endaddr(type)
|
||||
);
|
||||
}
|
||||
|
||||
if (alignment != 0) {
|
||||
@@ -331,12 +384,14 @@ static Section *getSection(char const *name, enum SectionType type, uint32_t org
|
||||
|
||||
if (org != (uint32_t)-1) {
|
||||
if ((org - alignOffset) & mask)
|
||||
error("Section \"%s\"'s fixed address doesn't match its alignment\n",
|
||||
name);
|
||||
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name);
|
||||
alignment = 0; // Ignore it if it's satisfied
|
||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||
error("Section \"%s\"'s alignment cannot be attained in %s\n",
|
||||
name, sectionTypeInfo[type].name.c_str());
|
||||
error(
|
||||
"Section \"%s\"'s alignment cannot be attained in %s\n",
|
||||
name,
|
||||
sectionTypeInfo[type].name.c_str()
|
||||
);
|
||||
alignment = 0; // Ignore it if it's unattainable
|
||||
org = 0;
|
||||
} else if (alignment == 16) {
|
||||
@@ -361,16 +416,14 @@ static Section *getSection(char const *name, enum SectionType type, uint32_t org
|
||||
}
|
||||
|
||||
// Set the current section
|
||||
static void changeSection()
|
||||
{
|
||||
static void changeSection() {
|
||||
if (!currentUnionStack.empty())
|
||||
fatalerror("Cannot change the section within a UNION\n");
|
||||
|
||||
sym_SetCurrentSymbolScope(nullptr);
|
||||
}
|
||||
|
||||
bool Section::isSizeKnown() const
|
||||
{
|
||||
bool Section::isSizeKnown() const {
|
||||
// SECTION UNION and SECTION FRAGMENT can still grow
|
||||
if (modifier != SECTION_NORMAL)
|
||||
return false;
|
||||
@@ -389,9 +442,13 @@ bool Section::isSizeKnown() const
|
||||
}
|
||||
|
||||
// Set the current section by name and type
|
||||
void sect_NewSection(char const *name, enum SectionType type, uint32_t org,
|
||||
SectionSpec const &attrs, enum SectionModifier mod)
|
||||
{
|
||||
void sect_NewSection(
|
||||
char const *name,
|
||||
enum SectionType type,
|
||||
uint32_t org,
|
||||
SectionSpec const &attrs,
|
||||
enum SectionModifier mod
|
||||
) {
|
||||
if (currentLoadSection)
|
||||
fatalerror("Cannot change the section within a `LOAD` block\n");
|
||||
|
||||
@@ -409,9 +466,13 @@ void sect_NewSection(char const *name, enum SectionType type, uint32_t org,
|
||||
}
|
||||
|
||||
// Set the current section by name and type
|
||||
void sect_SetLoadSection(char const *name, enum SectionType type, uint32_t org,
|
||||
SectionSpec const &attrs, enum SectionModifier mod)
|
||||
{
|
||||
void sect_SetLoadSection(
|
||||
char const *name,
|
||||
enum SectionType type,
|
||||
uint32_t org,
|
||||
SectionSpec const &attrs,
|
||||
enum SectionModifier mod
|
||||
) {
|
||||
// Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in
|
||||
// "code" sections, whereas LOAD is restricted to them.
|
||||
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
|
||||
@@ -444,8 +505,7 @@ void sect_SetLoadSection(char const *name, enum SectionType type, uint32_t org,
|
||||
currentLoadSection = sect;
|
||||
}
|
||||
|
||||
void sect_EndLoadSection()
|
||||
{
|
||||
void sect_EndLoadSection() {
|
||||
if (!currentLoadSection) {
|
||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
||||
return;
|
||||
@@ -458,25 +518,21 @@ void sect_EndLoadSection()
|
||||
sym_SetCurrentSymbolScope(currentLoadScope);
|
||||
}
|
||||
|
||||
Section *sect_GetSymbolSection()
|
||||
{
|
||||
Section *sect_GetSymbolSection() {
|
||||
return currentLoadSection ? currentLoadSection : currentSection;
|
||||
}
|
||||
|
||||
// The offset into the section above
|
||||
uint32_t sect_GetSymbolOffset()
|
||||
{
|
||||
uint32_t sect_GetSymbolOffset() {
|
||||
return curOffset;
|
||||
}
|
||||
|
||||
uint32_t sect_GetOutputOffset()
|
||||
{
|
||||
uint32_t sect_GetOutputOffset() {
|
||||
return curOffset + loadOffset;
|
||||
}
|
||||
|
||||
// Returns how many bytes need outputting for the specified alignment and offset to succeed
|
||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset)
|
||||
{
|
||||
uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
if (!sect)
|
||||
return 0;
|
||||
@@ -492,11 +548,10 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset)
|
||||
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
|
||||
uint16_t pcValue = isFixed ? sect->org : sect->alignOfs;
|
||||
return static_cast<uint16_t>(offset - curOffset - pcValue)
|
||||
% (1u << std::min(alignment, curAlignment));
|
||||
% (1u << std::min(alignment, curAlignment));
|
||||
}
|
||||
|
||||
void sect_AlignPC(uint8_t alignment, uint16_t offset)
|
||||
{
|
||||
void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
if (!checksection())
|
||||
return;
|
||||
|
||||
@@ -505,12 +560,16 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset)
|
||||
|
||||
if (sect->org != (uint32_t)-1) {
|
||||
if ((sect->org + curOffset - offset) % alignSize)
|
||||
error("Section's fixed address fails required alignment (PC = $%04" PRIx32
|
||||
")\n", sect->org + curOffset);
|
||||
} else if (sect->align != 0 && (((sect->alignOfs + curOffset) % (1u << sect->align))
|
||||
- offset) % alignSize) {
|
||||
error("Section's alignment fails required alignment (offset from section start = $%04"
|
||||
PRIx32 ")\n", curOffset);
|
||||
error(
|
||||
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
|
||||
sect->org + curOffset
|
||||
);
|
||||
} else if (sect->align != 0 && (((sect->alignOfs + curOffset) % (1u << sect->align)) - offset) % alignSize) {
|
||||
error(
|
||||
"Section's alignment fails required alignment (offset from section start = $%04" PRIx32
|
||||
")\n",
|
||||
curOffset
|
||||
);
|
||||
} else if (alignment >= 16) {
|
||||
// Treat an alignment large enough as fixing the address.
|
||||
// Note that this also ensures that a section's alignment never becomes 16 or greater.
|
||||
@@ -526,8 +585,7 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset)
|
||||
}
|
||||
}
|
||||
|
||||
static void growSection(uint32_t growth)
|
||||
{
|
||||
static void growSection(uint32_t growth) {
|
||||
curOffset += growth;
|
||||
if (curOffset + loadOffset > currentSection->size)
|
||||
currentSection->size = curOffset + loadOffset;
|
||||
@@ -535,33 +593,28 @@ static void growSection(uint32_t growth)
|
||||
currentLoadSection->size = curOffset;
|
||||
}
|
||||
|
||||
static void writebyte(uint8_t byte)
|
||||
{
|
||||
static void writebyte(uint8_t byte) {
|
||||
currentSection->data[sect_GetOutputOffset()] = byte;
|
||||
growSection(1);
|
||||
}
|
||||
|
||||
static void writeword(uint16_t b)
|
||||
{
|
||||
static void writeword(uint16_t b) {
|
||||
writebyte(b & 0xFF);
|
||||
writebyte(b >> 8);
|
||||
}
|
||||
|
||||
static void writelong(uint32_t b)
|
||||
{
|
||||
static void writelong(uint32_t b) {
|
||||
writebyte(b & 0xFF);
|
||||
writebyte(b >> 8);
|
||||
writebyte(b >> 16);
|
||||
writebyte(b >> 24);
|
||||
}
|
||||
|
||||
static void createPatch(enum PatchType type, Expression const &expr, uint32_t pcShift)
|
||||
{
|
||||
static void createPatch(enum PatchType type, Expression const &expr, uint32_t pcShift) {
|
||||
out_CreatePatch(type, expr, sect_GetOutputOffset(), pcShift);
|
||||
}
|
||||
|
||||
void sect_StartUnion()
|
||||
{
|
||||
void sect_StartUnion() {
|
||||
// Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in
|
||||
// "code" sections, whereas LOAD is restricted to them.
|
||||
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
|
||||
@@ -576,11 +629,10 @@ void sect_StartUnion()
|
||||
return;
|
||||
}
|
||||
|
||||
currentUnionStack.push({ .start = curOffset, .size = 0 });
|
||||
currentUnionStack.push({.start = curOffset, .size = 0});
|
||||
}
|
||||
|
||||
static void endUnionMember()
|
||||
{
|
||||
static void endUnionMember() {
|
||||
UnionStackEntry &member = currentUnionStack.top();
|
||||
uint32_t memberSize = curOffset - member.start;
|
||||
|
||||
@@ -589,8 +641,7 @@ static void endUnionMember()
|
||||
curOffset = member.start;
|
||||
}
|
||||
|
||||
void sect_NextUnionMember()
|
||||
{
|
||||
void sect_NextUnionMember() {
|
||||
if (currentUnionStack.empty()) {
|
||||
error("Found NEXTU outside of a UNION construct\n");
|
||||
return;
|
||||
@@ -598,8 +649,7 @@ void sect_NextUnionMember()
|
||||
endUnionMember();
|
||||
}
|
||||
|
||||
void sect_EndUnion()
|
||||
{
|
||||
void sect_EndUnion() {
|
||||
if (currentUnionStack.empty()) {
|
||||
error("Found ENDU outside of a UNION construct\n");
|
||||
return;
|
||||
@@ -609,15 +659,13 @@ void sect_EndUnion()
|
||||
currentUnionStack.pop();
|
||||
}
|
||||
|
||||
void sect_CheckUnionClosed()
|
||||
{
|
||||
void sect_CheckUnionClosed() {
|
||||
if (!currentUnionStack.empty())
|
||||
error("Unterminated UNION construct\n");
|
||||
}
|
||||
|
||||
// Output an absolute byte
|
||||
void sect_AbsByte(uint8_t b)
|
||||
{
|
||||
void sect_AbsByte(uint8_t b) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(1))
|
||||
@@ -626,8 +674,7 @@ void sect_AbsByte(uint8_t b)
|
||||
writebyte(b);
|
||||
}
|
||||
|
||||
void sect_AbsByteGroup(uint8_t const *s, size_t length)
|
||||
{
|
||||
void sect_AbsByteGroup(uint8_t const *s, size_t length) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(length))
|
||||
@@ -637,8 +684,7 @@ void sect_AbsByteGroup(uint8_t const *s, size_t length)
|
||||
writebyte(*s++);
|
||||
}
|
||||
|
||||
void sect_AbsWordGroup(uint8_t const *s, size_t length)
|
||||
{
|
||||
void sect_AbsWordGroup(uint8_t const *s, size_t length) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(length * 2))
|
||||
@@ -648,8 +694,7 @@ void sect_AbsWordGroup(uint8_t const *s, size_t length)
|
||||
writeword(*s++);
|
||||
}
|
||||
|
||||
void sect_AbsLongGroup(uint8_t const *s, size_t length)
|
||||
{
|
||||
void sect_AbsLongGroup(uint8_t const *s, size_t length) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(length * 4))
|
||||
@@ -660,8 +705,7 @@ void sect_AbsLongGroup(uint8_t const *s, size_t length)
|
||||
}
|
||||
|
||||
// Skip this many bytes
|
||||
void sect_Skip(uint32_t skip, bool ds)
|
||||
{
|
||||
void sect_Skip(uint32_t skip, bool ds) {
|
||||
if (!checksection())
|
||||
return;
|
||||
if (!reserveSpace(skip))
|
||||
@@ -671,8 +715,13 @@ void sect_Skip(uint32_t skip, bool ds)
|
||||
growSection(skip);
|
||||
} else {
|
||||
if (!ds)
|
||||
warning(WARNING_EMPTY_DATA_DIRECTIVE, "%s directive without data in ROM\n",
|
||||
(skip == 4) ? "DL" : (skip == 2) ? "DW" : "DB");
|
||||
warning(
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
"%s directive without data in ROM\n",
|
||||
(skip == 4) ? "DL"
|
||||
: (skip == 2) ? "DW"
|
||||
: "DB"
|
||||
);
|
||||
// We know we're in a code SECTION
|
||||
while (skip--)
|
||||
writebyte(fillByte);
|
||||
@@ -681,8 +730,7 @@ void sect_Skip(uint32_t skip, bool ds)
|
||||
|
||||
// Output a relocatable byte. Checking will be done to see if it
|
||||
// is an absolute value in disguise.
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift)
|
||||
{
|
||||
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(1))
|
||||
@@ -699,8 +747,7 @@ void sect_RelByte(Expression &expr, uint32_t pcShift)
|
||||
|
||||
// Output several copies of a relocatable byte. Checking will be done to see if
|
||||
// it is an absolute value in disguise.
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs)
|
||||
{
|
||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(n))
|
||||
@@ -723,8 +770,7 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs)
|
||||
|
||||
// Output a relocatable word. Checking will be done to see if
|
||||
// it's an absolute value in disguise.
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift)
|
||||
{
|
||||
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(2))
|
||||
@@ -741,8 +787,7 @@ void sect_RelWord(Expression &expr, uint32_t pcShift)
|
||||
|
||||
// Output a relocatable longword. Checking will be done to see if
|
||||
// is an absolute value in disguise.
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift)
|
||||
{
|
||||
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(2))
|
||||
@@ -759,8 +804,7 @@ void sect_RelLong(Expression &expr, uint32_t pcShift)
|
||||
|
||||
// Output a PC-relative relocatable byte. Checking will be done to see if it
|
||||
// is an absolute value in disguise.
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift)
|
||||
{
|
||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
||||
if (!checkcodesection())
|
||||
return;
|
||||
if (!reserveSpace(1))
|
||||
@@ -782,8 +826,7 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift)
|
||||
offset = sym->getValue() - (pc->getValue() + 1);
|
||||
|
||||
if (offset < -128 || offset > 127) {
|
||||
error("jr target out of reach (expected -129 < %" PRId16 " < 128)\n",
|
||||
offset);
|
||||
error("jr target out of reach (expected -129 < %" PRId16 " < 128)\n", offset);
|
||||
writebyte(0);
|
||||
} else {
|
||||
writebyte(offset);
|
||||
@@ -793,8 +836,7 @@ void sect_PCRelByte(Expression &expr, uint32_t pcShift)
|
||||
}
|
||||
|
||||
// Output a binary file
|
||||
void sect_BinaryFile(char const *s, int32_t startPos)
|
||||
{
|
||||
void sect_BinaryFile(char const *s, int32_t startPos) {
|
||||
if (startPos < 0) {
|
||||
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
||||
startPos = 0;
|
||||
@@ -834,8 +876,7 @@ void sect_BinaryFile(char const *s, int32_t startPos)
|
||||
goto cleanup;
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
error("Error determining size of INCBIN file '%s': %s\n",
|
||||
s, strerror(errno));
|
||||
error("Error determining size of INCBIN file '%s': %s\n", s, strerror(errno));
|
||||
// The file isn't seekable, so we'll just skip bytes
|
||||
while (startPos--)
|
||||
(void)fgetc(f);
|
||||
@@ -854,8 +895,7 @@ cleanup:
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
||||
{
|
||||
void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length) {
|
||||
if (start_pos < 0) {
|
||||
error("Start position cannot be negative (%" PRId32 ")\n", start_pos);
|
||||
start_pos = 0;
|
||||
@@ -900,16 +940,20 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
||||
}
|
||||
|
||||
if ((start_pos + length) > fsize) {
|
||||
error("Specified range in INCBIN is out of bounds (%" PRIu32 " + %" PRIu32
|
||||
" > %" PRIu32 ")\n", start_pos, length, fsize);
|
||||
error(
|
||||
"Specified range in INCBIN is out of bounds (%" PRIu32 " + %" PRIu32 " > %" PRIu32
|
||||
")\n",
|
||||
start_pos,
|
||||
length,
|
||||
fsize
|
||||
);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fseek(f, start_pos, SEEK_SET);
|
||||
} else {
|
||||
if (errno != ESPIPE)
|
||||
error("Error determining size of INCBIN file '%s': %s\n",
|
||||
s, strerror(errno));
|
||||
error("Error determining size of INCBIN file '%s': %s\n", s, strerror(errno));
|
||||
// The file isn't seekable, so we'll just skip bytes
|
||||
while (start_pos--)
|
||||
(void)fgetc(f);
|
||||
@@ -923,8 +967,7 @@ void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
||||
} else if (ferror(f)) {
|
||||
error("Error reading INCBIN file '%s': %s\n", s, strerror(errno));
|
||||
} else {
|
||||
error("Premature end of file (%" PRId32 " bytes left to read)\n",
|
||||
length + 1);
|
||||
error("Premature end of file (%" PRId32 " bytes left to read)\n", length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -933,15 +976,14 @@ cleanup:
|
||||
}
|
||||
|
||||
// Section stack routines
|
||||
void sect_PushSection()
|
||||
{
|
||||
void sect_PushSection() {
|
||||
sectionStack.push_front({
|
||||
.section = currentSection,
|
||||
.loadSection = currentLoadSection,
|
||||
.scope = sym_GetCurrentSymbolScope(),
|
||||
.offset = curOffset,
|
||||
.loadOffset = loadOffset,
|
||||
.unionStack = {},
|
||||
.section = currentSection,
|
||||
.loadSection = currentLoadSection,
|
||||
.scope = sym_GetCurrentSymbolScope(),
|
||||
.offset = curOffset,
|
||||
.loadOffset = loadOffset,
|
||||
.unionStack = {},
|
||||
});
|
||||
|
||||
// Reset the section scope
|
||||
@@ -951,8 +993,7 @@ void sect_PushSection()
|
||||
std::swap(currentUnionStack, sectionStack.front().unionStack);
|
||||
}
|
||||
|
||||
void sect_PopSection()
|
||||
{
|
||||
void sect_PopSection() {
|
||||
if (sectionStack.empty())
|
||||
fatalerror("No entries in the section stack\n");
|
||||
|
||||
@@ -971,8 +1012,7 @@ void sect_PopSection()
|
||||
std::swap(currentUnionStack, entry.unionStack);
|
||||
}
|
||||
|
||||
void sect_EndSection()
|
||||
{
|
||||
void sect_EndSection() {
|
||||
if (!currentSection)
|
||||
fatalerror("Cannot end the section outside of a SECTION\n");
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "asm/symbol.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
@@ -7,26 +9,25 @@
|
||||
#include <map>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <time.h>
|
||||
#include <variant>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "util.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include "asm/fixpoint.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/macro.hpp"
|
||||
#include "asm/main.hpp"
|
||||
#include "asm/output.hpp"
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/symbol.hpp"
|
||||
#include "util.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
std::map<std::string, Symbol> symbols;
|
||||
|
||||
static const char *labelScope; // Current section's label scope
|
||||
@@ -38,19 +39,16 @@ static char savedTIMESTAMP_ISO8601_LOCAL[256];
|
||||
static char savedTIMESTAMP_ISO8601_UTC[256];
|
||||
static bool exportAll;
|
||||
|
||||
bool sym_IsPC(Symbol const *sym)
|
||||
{
|
||||
bool sym_IsPC(Symbol const *sym) {
|
||||
return sym == PCSymbol;
|
||||
}
|
||||
|
||||
void sym_ForEach(void (*callback)(Symbol &))
|
||||
{
|
||||
void sym_ForEach(void (*callback)(Symbol &)) {
|
||||
for (auto &it : symbols)
|
||||
callback(it.second);
|
||||
}
|
||||
|
||||
static int32_t Callback_NARG()
|
||||
{
|
||||
static int32_t Callback_NARG() {
|
||||
if (!macro_GetCurrentArgs()) {
|
||||
error("_NARG does not make sense outside of a macro\n");
|
||||
return 0;
|
||||
@@ -58,15 +56,13 @@ static int32_t Callback_NARG()
|
||||
return macro_NbArgs();
|
||||
}
|
||||
|
||||
static int32_t CallbackPC()
|
||||
{
|
||||
static int32_t CallbackPC() {
|
||||
Section const *section = sect_GetSymbolSection();
|
||||
|
||||
return section ? section->org + sect_GetSymbolOffset() : 0;
|
||||
}
|
||||
|
||||
int32_t Symbol::getValue() const
|
||||
{
|
||||
int32_t Symbol::getValue() const {
|
||||
assert(std::holds_alternative<int32_t>(data) || std::holds_alternative<int32_t (*)()>(data));
|
||||
if (int32_t const *value = std::get_if<int32_t>(&data); value) {
|
||||
// TODO: do not use section's org directly
|
||||
@@ -75,29 +71,28 @@ int32_t Symbol::getValue() const
|
||||
return getOutputValue();
|
||||
}
|
||||
|
||||
int32_t Symbol::getOutputValue() const
|
||||
{
|
||||
return std::visit(Visitor{
|
||||
[](int32_t value) -> int32_t { return value; },
|
||||
[](int32_t (*callback)()) -> int32_t { return callback(); },
|
||||
[](auto &) -> int32_t { return 0; }
|
||||
}, data);
|
||||
int32_t Symbol::getOutputValue() const {
|
||||
return std::visit(
|
||||
Visitor{
|
||||
[](int32_t value) -> int32_t { return value; },
|
||||
[](int32_t (*callback)()) -> int32_t { return callback(); },
|
||||
[](auto &) -> int32_t { return 0; },
|
||||
},
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
std::string_view *Symbol::getMacro() const
|
||||
{
|
||||
std::string_view *Symbol::getMacro() const {
|
||||
assert(std::holds_alternative<std::string_view *>(data));
|
||||
return std::get<std::string_view *>(data);
|
||||
}
|
||||
|
||||
std::string *Symbol::getEqus() const
|
||||
{
|
||||
std::string *Symbol::getEqus() const {
|
||||
assert(std::holds_alternative<std::string *>(data));
|
||||
return std::get<std::string *>(data);
|
||||
}
|
||||
|
||||
static void dumpFilename(Symbol const &sym)
|
||||
{
|
||||
static void dumpFilename(Symbol const &sym) {
|
||||
if (sym.src)
|
||||
sym.src->dump(sym.fileLine);
|
||||
else if (sym.fileLine == 0)
|
||||
@@ -107,15 +102,13 @@ static void dumpFilename(Symbol const &sym)
|
||||
}
|
||||
|
||||
// Set a symbol's definition filename and line
|
||||
static void setSymbolFilename(Symbol &sym)
|
||||
{
|
||||
sym.src = fstk_GetFileStack(); // This is `nullptr` for built-ins
|
||||
static void setSymbolFilename(Symbol &sym) {
|
||||
sym.src = fstk_GetFileStack(); // This is `nullptr` for built-ins
|
||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0; // This is 1 for built-ins
|
||||
}
|
||||
|
||||
// Update a symbol's definition filename and line
|
||||
static void updateSymbolFilename(Symbol &sym)
|
||||
{
|
||||
static void updateSymbolFilename(Symbol &sym) {
|
||||
FileStackNode *oldSrc = sym.src;
|
||||
|
||||
setSymbolFilename(sym);
|
||||
@@ -126,8 +119,7 @@ static void updateSymbolFilename(Symbol &sym)
|
||||
}
|
||||
|
||||
// Create a new symbol by name
|
||||
static Symbol &createsymbol(char const *symName)
|
||||
{
|
||||
static Symbol &createsymbol(char const *symName) {
|
||||
Symbol &sym = symbols[symName];
|
||||
|
||||
if (snprintf(sym.name, MAXSYMLEN + 1, "%s", symName) > MAXSYMLEN)
|
||||
@@ -144,9 +136,8 @@ static Symbol &createsymbol(char const *symName)
|
||||
|
||||
// Creates the full name of a local symbol in a given scope, by prepending
|
||||
// the name with the parent symbol's name.
|
||||
static void fullSymbolName(char *output, size_t outputSize,
|
||||
char const *localName, char const *scopeName)
|
||||
{
|
||||
static void
|
||||
fullSymbolName(char *output, size_t outputSize, char const *localName, char const *scopeName) {
|
||||
int ret = snprintf(output, outputSize, "%s%s", scopeName, localName);
|
||||
|
||||
if (ret < 0)
|
||||
@@ -155,27 +146,23 @@ static void fullSymbolName(char *output, size_t outputSize,
|
||||
fatalerror("Symbol name is too long: '%s%s'\n", scopeName, localName);
|
||||
}
|
||||
|
||||
static void assignStringSymbol(Symbol &sym, char const *value)
|
||||
{
|
||||
std::string *equs = new(std::nothrow) std::string(value);
|
||||
static void assignStringSymbol(Symbol &sym, char const *value) {
|
||||
std::string *equs = new (std::nothrow) std::string(value);
|
||||
if (!equs)
|
||||
fatalerror("No memory for string equate: %s\n", strerror(errno));
|
||||
sym.type = SYM_EQUS;
|
||||
sym.data = equs;
|
||||
}
|
||||
|
||||
Symbol *sym_FindExactSymbol(char const *symName)
|
||||
{
|
||||
Symbol *sym_FindExactSymbol(char const *symName) {
|
||||
auto search = symbols.find(symName);
|
||||
return search != symbols.end() ? &search->second : nullptr;
|
||||
}
|
||||
|
||||
Symbol *sym_FindScopedSymbol(char const *symName)
|
||||
{
|
||||
Symbol *sym_FindScopedSymbol(char const *symName) {
|
||||
if (char const *localName = strchr(symName, '.'); localName) {
|
||||
if (strchr(localName + 1, '.'))
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n",
|
||||
symName);
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n", symName);
|
||||
// If auto-scoped local label, expand the name
|
||||
if (localName == symName) { // Meaning, the name begins with the dot
|
||||
char fullName[MAXSYMLEN + 1];
|
||||
@@ -187,8 +174,7 @@ Symbol *sym_FindScopedSymbol(char const *symName)
|
||||
return sym_FindExactSymbol(symName);
|
||||
}
|
||||
|
||||
Symbol *sym_FindScopedValidSymbol(char const *symName)
|
||||
{
|
||||
Symbol *sym_FindScopedValidSymbol(char const *symName) {
|
||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
|
||||
// `@` has no value outside a section
|
||||
@@ -202,14 +188,12 @@ Symbol *sym_FindScopedValidSymbol(char const *symName)
|
||||
return sym;
|
||||
}
|
||||
|
||||
Symbol const *sym_GetPC()
|
||||
{
|
||||
Symbol const *sym_GetPC() {
|
||||
return PCSymbol;
|
||||
}
|
||||
|
||||
// Purge a symbol
|
||||
void sym_Purge(std::string const &symName)
|
||||
{
|
||||
void sym_Purge(std::string const &symName) {
|
||||
Symbol *sym = sym_FindScopedValidSymbol(symName.c_str());
|
||||
|
||||
if (!sym) {
|
||||
@@ -230,8 +214,7 @@ void sym_Purge(std::string const &symName)
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t sym_GetPCValue()
|
||||
{
|
||||
uint32_t sym_GetPCValue() {
|
||||
Section const *sect = sect_GetSymbolSection();
|
||||
|
||||
if (!sect)
|
||||
@@ -244,8 +227,7 @@ uint32_t sym_GetPCValue()
|
||||
}
|
||||
|
||||
// Return a constant symbol's value, assuming it's defined
|
||||
uint32_t Symbol::getConstantValue() const
|
||||
{
|
||||
uint32_t Symbol::getConstantValue() const {
|
||||
if (sym_IsPC(this))
|
||||
return sym_GetPCValue();
|
||||
|
||||
@@ -257,8 +239,7 @@ uint32_t Symbol::getConstantValue() const
|
||||
}
|
||||
|
||||
// Return a constant symbol's value
|
||||
uint32_t sym_GetConstantValue(char const *symName)
|
||||
{
|
||||
uint32_t sym_GetConstantValue(char const *symName) {
|
||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym)
|
||||
return sym->getConstantValue();
|
||||
|
||||
@@ -266,13 +247,11 @@ uint32_t sym_GetConstantValue(char const *symName)
|
||||
return 0;
|
||||
}
|
||||
|
||||
char const *sym_GetCurrentSymbolScope()
|
||||
{
|
||||
char const *sym_GetCurrentSymbolScope() {
|
||||
return labelScope;
|
||||
}
|
||||
|
||||
void sym_SetCurrentSymbolScope(char const *newScope)
|
||||
{
|
||||
void sym_SetCurrentSymbolScope(char const *newScope) {
|
||||
labelScope = newScope;
|
||||
}
|
||||
|
||||
@@ -283,8 +262,7 @@ void sym_SetCurrentSymbolScope(char const *newScope)
|
||||
* @param symName The name of the symbol to create
|
||||
* @param numeric If false, the symbol may not have been referenced earlier
|
||||
*/
|
||||
static Symbol *createNonrelocSymbol(char const *symName, bool numeric)
|
||||
{
|
||||
static Symbol *createNonrelocSymbol(char const *symName, bool numeric) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
@@ -306,8 +284,7 @@ static Symbol *createNonrelocSymbol(char const *symName, bool numeric)
|
||||
}
|
||||
|
||||
// Add an equated symbol
|
||||
Symbol *sym_AddEqu(char const *symName, int32_t value)
|
||||
{
|
||||
Symbol *sym_AddEqu(char const *symName, int32_t value) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, true);
|
||||
|
||||
if (!sym)
|
||||
@@ -319,8 +296,7 @@ Symbol *sym_AddEqu(char const *symName, int32_t value)
|
||||
return sym;
|
||||
}
|
||||
|
||||
Symbol *sym_RedefEqu(char const *symName, int32_t value)
|
||||
{
|
||||
Symbol *sym_RedefEqu(char const *symName, int32_t value) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
@@ -355,8 +331,7 @@ Symbol *sym_RedefEqu(char const *symName, int32_t value)
|
||||
* of the string are enough: sym_AddString("M_PI", "3.1415"). This is the same
|
||||
* as ``` M_PI EQUS "3.1415" ```
|
||||
*/
|
||||
Symbol *sym_AddString(char const *symName, char const *value)
|
||||
{
|
||||
Symbol *sym_AddString(char const *symName, char const *value) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
if (!sym)
|
||||
@@ -366,8 +341,7 @@ Symbol *sym_AddString(char const *symName, char const *value)
|
||||
return sym;
|
||||
}
|
||||
|
||||
Symbol *sym_RedefString(char const *symName, char const *value)
|
||||
{
|
||||
Symbol *sym_RedefString(char const *symName, char const *value) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym)
|
||||
@@ -395,15 +369,15 @@ Symbol *sym_RedefString(char const *symName, char const *value)
|
||||
}
|
||||
|
||||
// Alter a mutable symbol's value
|
||||
Symbol *sym_AddVar(char const *symName, int32_t value)
|
||||
{
|
||||
Symbol *sym_AddVar(char const *symName, int32_t value) {
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
sym = &createsymbol(symName);
|
||||
} else if (sym->isDefined() && sym->type != SYM_VAR) {
|
||||
error("'%s' already defined as %s at ",
|
||||
symName, sym->type == SYM_LABEL ? "label" : "constant");
|
||||
error(
|
||||
"'%s' already defined as %s at ", symName, sym->type == SYM_LABEL ? "label" : "constant"
|
||||
);
|
||||
dumpFilename(*sym);
|
||||
putc('\n', stderr);
|
||||
return sym;
|
||||
@@ -422,8 +396,7 @@ Symbol *sym_AddVar(char const *symName, int32_t value)
|
||||
* @param symName The label's full name (so `.name` is invalid)
|
||||
* @return The created symbol
|
||||
*/
|
||||
static Symbol *addLabel(char const *symName)
|
||||
{
|
||||
static Symbol *addLabel(char const *symName) {
|
||||
assert(symName[0] != '.'); // The symbol name must have been expanded prior
|
||||
Symbol *sym = sym_FindExactSymbol(symName);
|
||||
|
||||
@@ -452,8 +425,7 @@ static Symbol *addLabel(char const *symName)
|
||||
}
|
||||
|
||||
// Add a local (`.name` or `Parent.name`) relocatable symbol
|
||||
Symbol *sym_AddLocalLabel(char const *symName)
|
||||
{
|
||||
Symbol *sym_AddLocalLabel(char const *symName) {
|
||||
// Assuming no dots in `labelScope` if defined
|
||||
assert(!labelScope || !strchr(labelScope, '.'));
|
||||
|
||||
@@ -464,13 +436,11 @@ Symbol *sym_AddLocalLabel(char const *symName)
|
||||
|
||||
// Check for something after the dot in `localName`
|
||||
if (localName[1] == '\0') {
|
||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n",
|
||||
symName);
|
||||
fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName);
|
||||
}
|
||||
// Check for more than one dot in `localName`
|
||||
if (strchr(localName + 1, '.'))
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
|
||||
symName);
|
||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName);
|
||||
|
||||
if (localName == symName) {
|
||||
if (!labelScope) {
|
||||
@@ -486,8 +456,7 @@ Symbol *sym_AddLocalLabel(char const *symName)
|
||||
}
|
||||
|
||||
// Add a relocatable symbol
|
||||
Symbol *sym_AddLabel(char const *symName)
|
||||
{
|
||||
Symbol *sym_AddLabel(char const *symName) {
|
||||
Symbol *sym = addLabel(symName);
|
||||
|
||||
// Set the symbol as the new scope
|
||||
@@ -499,8 +468,7 @@ Symbol *sym_AddLabel(char const *symName)
|
||||
static uint32_t anonLabelID;
|
||||
|
||||
// Add an anonymous label
|
||||
Symbol *sym_AddAnonLabel()
|
||||
{
|
||||
Symbol *sym_AddAnonLabel() {
|
||||
if (anonLabelID == UINT32_MAX) {
|
||||
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
|
||||
return nullptr;
|
||||
@@ -513,22 +481,29 @@ Symbol *sym_AddAnonLabel()
|
||||
}
|
||||
|
||||
// Write an anonymous label's name to a buffer
|
||||
void sym_WriteAnonLabelName(char buf[MAXSYMLEN + 1], uint32_t ofs, bool neg)
|
||||
{
|
||||
void sym_WriteAnonLabelName(char buf[MAXSYMLEN + 1], uint32_t ofs, bool neg) {
|
||||
uint32_t id = 0;
|
||||
|
||||
if (neg) {
|
||||
if (ofs > anonLabelID)
|
||||
error("Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
||||
" ha%s been created so far\n",
|
||||
ofs, anonLabelID, anonLabelID == 1 ? "s" : "ve");
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
||||
" ha%s been created so far\n",
|
||||
ofs,
|
||||
anonLabelID,
|
||||
anonLabelID == 1 ? "s" : "ve"
|
||||
);
|
||||
else
|
||||
id = anonLabelID - ofs;
|
||||
} else {
|
||||
ofs--; // We're referencing symbols that haven't been created yet...
|
||||
if (ofs > UINT32_MAX - anonLabelID)
|
||||
error("Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||
" may still be created\n", ofs + 1, UINT32_MAX - anonLabelID);
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||
" may still be created\n",
|
||||
ofs + 1,
|
||||
UINT32_MAX - anonLabelID
|
||||
);
|
||||
else
|
||||
id = anonLabelID + ofs;
|
||||
}
|
||||
@@ -537,8 +512,7 @@ void sym_WriteAnonLabelName(char buf[MAXSYMLEN + 1], uint32_t ofs, bool neg)
|
||||
}
|
||||
|
||||
// Export a symbol
|
||||
void sym_Export(char const *symName)
|
||||
{
|
||||
void sym_Export(char const *symName) {
|
||||
if (symName[0] == '!') {
|
||||
error("Anonymous labels cannot be exported\n");
|
||||
return;
|
||||
@@ -553,14 +527,13 @@ void sym_Export(char const *symName)
|
||||
}
|
||||
|
||||
// Add a macro definition
|
||||
Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char const *body, size_t size)
|
||||
{
|
||||
Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char const *body, size_t size) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
if (!sym)
|
||||
return nullptr;
|
||||
|
||||
std::string_view *macro = new(std::nothrow) std::string_view(body, size);
|
||||
std::string_view *macro = new (std::nothrow) std::string_view(body, size);
|
||||
if (!macro)
|
||||
fatalerror("No memory for macro: %s\n", strerror(errno));
|
||||
sym->type = SYM_MACRO;
|
||||
@@ -576,8 +549,7 @@ Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char const *body, s
|
||||
|
||||
// Flag that a symbol is referenced in an RPN expression
|
||||
// and create it if it doesn't exist yet
|
||||
Symbol *sym_Ref(char const *symName)
|
||||
{
|
||||
Symbol *sym_Ref(char const *symName) {
|
||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||
|
||||
if (!sym) {
|
||||
@@ -598,13 +570,11 @@ Symbol *sym_Ref(char const *symName)
|
||||
}
|
||||
|
||||
// Set whether to export all relocatable symbols by default
|
||||
void sym_SetExportAll(bool set)
|
||||
{
|
||||
void sym_SetExportAll(bool set) {
|
||||
exportAll = set;
|
||||
}
|
||||
|
||||
static Symbol *createBuiltinSymbol(char const *symName)
|
||||
{
|
||||
static Symbol *createBuiltinSymbol(char const *symName) {
|
||||
Symbol *sym = &createsymbol(symName);
|
||||
|
||||
sym->isBuiltin = true;
|
||||
@@ -615,8 +585,7 @@ static Symbol *createBuiltinSymbol(char const *symName)
|
||||
}
|
||||
|
||||
// Initialize the symboltable
|
||||
void sym_Init(time_t now)
|
||||
{
|
||||
void sym_Init(time_t now) {
|
||||
PCSymbol = createBuiltinSymbol("@");
|
||||
PCSymbol->type = SYM_LABEL;
|
||||
PCSymbol->data = CallbackPC;
|
||||
@@ -627,11 +596,12 @@ void sym_Init(time_t now)
|
||||
|
||||
sym_AddVar("_RS", 0)->isBuiltin = true;
|
||||
|
||||
#define addSym(fn, name, val) do { \
|
||||
Symbol *sym = fn(name, val); \
|
||||
assert(sym); \
|
||||
sym->isBuiltin = true; \
|
||||
} while (0)
|
||||
#define addSym(fn, name, val) \
|
||||
do { \
|
||||
Symbol *sym = fn(name, val); \
|
||||
assert(sym); \
|
||||
sym->isBuiltin = true; \
|
||||
} while (0)
|
||||
#define addNumber(name, val) addSym(sym_AddEqu, name, val)
|
||||
#define addString(name, val) addSym(sym_AddString, name, val)
|
||||
|
||||
@@ -653,15 +623,21 @@ void sym_Init(time_t now)
|
||||
|
||||
strftime(savedTIME, sizeof(savedTIME), "\"%H:%M:%S\"", time_local);
|
||||
strftime(savedDATE, sizeof(savedDATE), "\"%d %B %Y\"", time_local);
|
||||
strftime(savedTIMESTAMP_ISO8601_LOCAL,
|
||||
sizeof(savedTIMESTAMP_ISO8601_LOCAL), "\"%Y-%m-%dT%H:%M:%S%z\"",
|
||||
time_local);
|
||||
strftime(
|
||||
savedTIMESTAMP_ISO8601_LOCAL,
|
||||
sizeof(savedTIMESTAMP_ISO8601_LOCAL),
|
||||
"\"%Y-%m-%dT%H:%M:%S%z\"",
|
||||
time_local
|
||||
);
|
||||
|
||||
const tm *time_utc = gmtime(&now);
|
||||
|
||||
strftime(savedTIMESTAMP_ISO8601_UTC,
|
||||
sizeof(savedTIMESTAMP_ISO8601_UTC), "\"%Y-%m-%dT%H:%M:%SZ\"",
|
||||
time_utc);
|
||||
strftime(
|
||||
savedTIMESTAMP_ISO8601_UTC,
|
||||
sizeof(savedTIMESTAMP_ISO8601_UTC),
|
||||
"\"%Y-%m-%dT%H:%M:%SZ\"",
|
||||
time_utc
|
||||
);
|
||||
|
||||
addString("__TIME__", savedTIME);
|
||||
addString("__DATE__", savedDATE);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
@@ -8,48 +10,46 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/main.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "itertools.hpp"
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/main.hpp"
|
||||
|
||||
unsigned int nbErrors = 0;
|
||||
unsigned int maxErrors = 0;
|
||||
|
||||
static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
|
||||
WARNING_ENABLED, // WARNING_ASSERT
|
||||
WARNING_DISABLED, // WARNING_BACKWARDS_FOR
|
||||
WARNING_DISABLED, // WARNING_BUILTIN_ARG
|
||||
WARNING_DISABLED, // WARNING_CHARMAP_REDEF
|
||||
WARNING_DISABLED, // WARNING_DIV
|
||||
WARNING_DISABLED, // WARNING_EMPTY_DATA_DIRECTIVE
|
||||
WARNING_DISABLED, // WARNING_EMPTY_MACRO_ARG
|
||||
WARNING_DISABLED, // WARNING_EMPTY_STRRPL
|
||||
WARNING_DISABLED, // WARNING_LARGE_CONSTANT
|
||||
WARNING_DISABLED, // WARNING_LONG_STR
|
||||
WARNING_DISABLED, // WARNING_MACRO_SHIFT
|
||||
WARNING_ENABLED, // WARNING_NESTED_COMMENT
|
||||
WARNING_ENABLED, // WARNING_OBSOLETE
|
||||
WARNING_DISABLED, // WARNING_SHIFT
|
||||
WARNING_DISABLED, // WARNING_SHIFT_AMOUNT
|
||||
WARNING_ENABLED, // WARNING_USER
|
||||
WARNING_ENABLED, // WARNING_ASSERT
|
||||
WARNING_DISABLED, // WARNING_BACKWARDS_FOR
|
||||
WARNING_DISABLED, // WARNING_BUILTIN_ARG
|
||||
WARNING_DISABLED, // WARNING_CHARMAP_REDEF
|
||||
WARNING_DISABLED, // WARNING_DIV
|
||||
WARNING_DISABLED, // WARNING_EMPTY_DATA_DIRECTIVE
|
||||
WARNING_DISABLED, // WARNING_EMPTY_MACRO_ARG
|
||||
WARNING_DISABLED, // WARNING_EMPTY_STRRPL
|
||||
WARNING_DISABLED, // WARNING_LARGE_CONSTANT
|
||||
WARNING_DISABLED, // WARNING_LONG_STR
|
||||
WARNING_DISABLED, // WARNING_MACRO_SHIFT
|
||||
WARNING_ENABLED, // WARNING_NESTED_COMMENT
|
||||
WARNING_ENABLED, // WARNING_OBSOLETE
|
||||
WARNING_DISABLED, // WARNING_SHIFT
|
||||
WARNING_DISABLED, // WARNING_SHIFT_AMOUNT
|
||||
WARNING_ENABLED, // WARNING_USER
|
||||
|
||||
WARNING_ENABLED, // WARNING_NUMERIC_STRING_1
|
||||
WARNING_DISABLED, // WARNING_NUMERIC_STRING_2
|
||||
WARNING_ENABLED, // WARNING_TRUNCATION_1
|
||||
WARNING_DISABLED, // WARNING_TRUNCATION_2
|
||||
WARNING_ENABLED, // WARNING_UNMAPPED_CHAR_1
|
||||
WARNING_DISABLED, // WARNING_UNMAPPED_CHAR_2
|
||||
WARNING_ENABLED, // WARNING_NUMERIC_STRING_1
|
||||
WARNING_DISABLED, // WARNING_NUMERIC_STRING_2
|
||||
WARNING_ENABLED, // WARNING_TRUNCATION_1
|
||||
WARNING_DISABLED, // WARNING_TRUNCATION_2
|
||||
WARNING_ENABLED, // WARNING_UNMAPPED_CHAR_1
|
||||
WARNING_DISABLED, // WARNING_UNMAPPED_CHAR_2
|
||||
};
|
||||
|
||||
enum WarningState warningStates[ARRAY_SIZE(warningStates)];
|
||||
|
||||
bool warningsAreErrors; // Set if `-Werror` was specified
|
||||
|
||||
static enum WarningState warningState(enum WarningID id)
|
||||
{
|
||||
static enum WarningState warningState(enum WarningID id) {
|
||||
// Check if warnings are globally disabled
|
||||
if (!warnings)
|
||||
return WARNING_DISABLED;
|
||||
@@ -68,35 +68,35 @@ static enum WarningState warningState(enum WarningID id)
|
||||
}
|
||||
|
||||
static const char * const warningFlags[NB_WARNINGS] = {
|
||||
"assert",
|
||||
"backwards-for",
|
||||
"builtin-args",
|
||||
"charmap-redef",
|
||||
"div",
|
||||
"empty-data-directive",
|
||||
"empty-macro-arg",
|
||||
"empty-strrpl",
|
||||
"large-constant",
|
||||
"long-string",
|
||||
"macro-shift",
|
||||
"nested-comment",
|
||||
"obsolete",
|
||||
"shift",
|
||||
"shift-amount",
|
||||
"user",
|
||||
"assert",
|
||||
"backwards-for",
|
||||
"builtin-args",
|
||||
"charmap-redef",
|
||||
"div",
|
||||
"empty-data-directive",
|
||||
"empty-macro-arg",
|
||||
"empty-strrpl",
|
||||
"large-constant",
|
||||
"long-string",
|
||||
"macro-shift",
|
||||
"nested-comment",
|
||||
"obsolete",
|
||||
"shift",
|
||||
"shift-amount",
|
||||
"user",
|
||||
|
||||
// Parametric warnings
|
||||
"numeric-string",
|
||||
"numeric-string",
|
||||
"truncation",
|
||||
"truncation",
|
||||
"unmapped-char",
|
||||
"unmapped-char",
|
||||
// Parametric warnings
|
||||
"numeric-string",
|
||||
"numeric-string",
|
||||
"truncation",
|
||||
"truncation",
|
||||
"unmapped-char",
|
||||
"unmapped-char",
|
||||
|
||||
// Meta warnings
|
||||
"all",
|
||||
"extra",
|
||||
"everything", // Especially useful for testing
|
||||
// Meta warnings
|
||||
"all",
|
||||
"extra",
|
||||
"everything", // Especially useful for testing
|
||||
};
|
||||
|
||||
static const struct {
|
||||
@@ -104,13 +104,12 @@ static const struct {
|
||||
uint8_t nbLevels;
|
||||
uint8_t defaultLevel;
|
||||
} paramWarnings[] = {
|
||||
{ "numeric-string", 2, 1 },
|
||||
{ "truncation", 2, 2 },
|
||||
{ "unmapped-char", 2, 1 },
|
||||
{"numeric-string", 2, 1},
|
||||
{"truncation", 2, 2},
|
||||
{"unmapped-char", 2, 1},
|
||||
};
|
||||
|
||||
static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state)
|
||||
{
|
||||
static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state) {
|
||||
enum WarningID baseID = PARAM_WARNINGS_START;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) {
|
||||
@@ -126,17 +125,19 @@ static bool tryProcessParamWarning(char const *flag, uint8_t param, enum Warning
|
||||
param = paramWarnings[i].defaultLevel;
|
||||
} else if (param > maxParam) {
|
||||
if (param != 255) // Don't warn if already capped
|
||||
warnx("Got parameter %" PRIu8
|
||||
" for warning flag \"%s\", but the maximum is %"
|
||||
PRIu8 "; capping.\n",
|
||||
param, flag, maxParam);
|
||||
warnx(
|
||||
"Got parameter %" PRIu8
|
||||
" for warning flag \"%s\", but the maximum is %" PRIu8 "; capping.\n",
|
||||
param,
|
||||
flag,
|
||||
maxParam
|
||||
);
|
||||
param = maxParam;
|
||||
}
|
||||
|
||||
// Set the first <param> to enabled/error, and disable the rest
|
||||
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
||||
warningStates[baseID + ofs] =
|
||||
ofs < param ? state : WARNING_DISABLED;
|
||||
warningStates[baseID + ofs] = ofs < param ? state : WARNING_DISABLED;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -146,73 +147,70 @@ static bool tryProcessParamWarning(char const *flag, uint8_t param, enum Warning
|
||||
return false;
|
||||
}
|
||||
|
||||
enum MetaWarningCommand {
|
||||
META_WARNING_DONE = NB_WARNINGS
|
||||
};
|
||||
enum MetaWarningCommand { META_WARNING_DONE = NB_WARNINGS };
|
||||
|
||||
// Warnings that probably indicate an error
|
||||
static uint8_t const _wallCommands[] = {
|
||||
WARNING_BACKWARDS_FOR,
|
||||
WARNING_BUILTIN_ARG,
|
||||
WARNING_CHARMAP_REDEF,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
WARNING_EMPTY_STRRPL,
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_LONG_STR,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_NUMERIC_STRING_1,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
META_WARNING_DONE
|
||||
WARNING_BACKWARDS_FOR,
|
||||
WARNING_BUILTIN_ARG,
|
||||
WARNING_CHARMAP_REDEF,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
WARNING_EMPTY_STRRPL,
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_LONG_STR,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_NUMERIC_STRING_1,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
META_WARNING_DONE,
|
||||
};
|
||||
|
||||
// Warnings that are less likely to indicate an error
|
||||
static uint8_t const _wextraCommands[] = {
|
||||
WARNING_EMPTY_MACRO_ARG,
|
||||
WARNING_MACRO_SHIFT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
META_WARNING_DONE
|
||||
WARNING_EMPTY_MACRO_ARG,
|
||||
WARNING_MACRO_SHIFT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
META_WARNING_DONE,
|
||||
};
|
||||
|
||||
// Literally everything. Notably useful for testing
|
||||
static uint8_t const _weverythingCommands[] = {
|
||||
WARNING_BACKWARDS_FOR,
|
||||
WARNING_BUILTIN_ARG,
|
||||
WARNING_DIV,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
WARNING_EMPTY_MACRO_ARG,
|
||||
WARNING_EMPTY_STRRPL,
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_LONG_STR,
|
||||
WARNING_MACRO_SHIFT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_SHIFT,
|
||||
WARNING_SHIFT_AMOUNT,
|
||||
WARNING_NUMERIC_STRING_1,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
// WARNING_USER,
|
||||
META_WARNING_DONE
|
||||
WARNING_BACKWARDS_FOR,
|
||||
WARNING_BUILTIN_ARG,
|
||||
WARNING_DIV,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
WARNING_EMPTY_MACRO_ARG,
|
||||
WARNING_EMPTY_STRRPL,
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_LONG_STR,
|
||||
WARNING_MACRO_SHIFT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_SHIFT,
|
||||
WARNING_SHIFT_AMOUNT,
|
||||
WARNING_NUMERIC_STRING_1,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
// WARNING_USER,
|
||||
META_WARNING_DONE,
|
||||
};
|
||||
|
||||
static uint8_t const *metaWarningCommands[NB_META_WARNINGS] = {
|
||||
_wallCommands,
|
||||
_wextraCommands,
|
||||
_weverythingCommands
|
||||
_wallCommands,
|
||||
_wextraCommands,
|
||||
_weverythingCommands,
|
||||
};
|
||||
|
||||
void processWarningFlag(char *flag)
|
||||
{
|
||||
void processWarningFlag(char *flag) {
|
||||
static bool setError = false;
|
||||
|
||||
// First, try to match against a "meta" warning
|
||||
@@ -221,11 +219,11 @@ void processWarningFlag(char *flag)
|
||||
if (!strcmp(flag, warningFlags[id])) {
|
||||
// We got a match!
|
||||
if (setError)
|
||||
errx("Cannot make meta warning \"%s\" into an error",
|
||||
flag);
|
||||
errx("Cannot make meta warning \"%s\" into an error", flag);
|
||||
|
||||
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
|
||||
*ptr != META_WARNING_DONE; ptr++) {
|
||||
*ptr != META_WARNING_DONE;
|
||||
ptr++) {
|
||||
// Warning flag, set without override
|
||||
if (warningStates[*ptr] == WARNING_DEFAULT)
|
||||
warningStates[*ptr] = WARNING_ENABLED;
|
||||
@@ -252,16 +250,16 @@ void processWarningFlag(char *flag)
|
||||
setError = false;
|
||||
return;
|
||||
|
||||
// Otherwise, allow parsing as another flag
|
||||
// Otherwise, allow parsing as another flag
|
||||
}
|
||||
}
|
||||
|
||||
// Well, it's either a normal warning or a mistake
|
||||
|
||||
enum WarningState state = setError ? WARNING_ERROR
|
||||
// Not an error, then check if this is a negation
|
||||
: strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
|
||||
: WARNING_DISABLED;
|
||||
// Not an error, then check if this is a negation
|
||||
: strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
|
||||
: WARNING_DISABLED;
|
||||
char *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
|
||||
|
||||
// Is this a "parametric" warning?
|
||||
@@ -284,8 +282,7 @@ void processWarningFlag(char *flag)
|
||||
// Avoid overflowing!
|
||||
if (param > UINT8_MAX - (*ptr - '0')) {
|
||||
if (!warned)
|
||||
warnx("Invalid warning flag \"%s\": capping parameter at 255\n",
|
||||
flag);
|
||||
warnx("Invalid warning flag \"%s\": capping parameter at 255\n", flag);
|
||||
warned = true; // Only warn once, cap always
|
||||
param = 255;
|
||||
continue;
|
||||
@@ -303,8 +300,7 @@ void processWarningFlag(char *flag)
|
||||
return;
|
||||
}
|
||||
*equals = '\0'; // Truncate the param at the '='
|
||||
if (tryProcessParamWarning(rootFlag, param,
|
||||
param == 0 ? WARNING_DISABLED : state))
|
||||
if (tryProcessParamWarning(rootFlag, param, param == 0 ? WARNING_DISABLED : state))
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -327,9 +323,9 @@ void processWarningFlag(char *flag)
|
||||
warnx("Unknown warning `%s`", flag);
|
||||
}
|
||||
|
||||
void printDiag(char const *fmt, va_list args, char const *type,
|
||||
char const *flagfmt, char const *flag)
|
||||
{
|
||||
void printDiag(
|
||||
char const *fmt, va_list args, char const *type, char const *flagfmt, char const *flag
|
||||
) {
|
||||
fputs(type, stderr);
|
||||
fputs(": ", stderr);
|
||||
fstk_DumpCurrent();
|
||||
@@ -339,8 +335,7 @@ void printDiag(char const *fmt, va_list args, char const *type,
|
||||
lexer_DumpStringExpansions();
|
||||
}
|
||||
|
||||
void error(char const *fmt, ...)
|
||||
{
|
||||
void error(char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
@@ -350,12 +345,15 @@ void error(char const *fmt, ...)
|
||||
// This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)")
|
||||
nbErrors++;
|
||||
if (nbErrors == maxErrors)
|
||||
errx("The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly aborted!",
|
||||
maxErrors, maxErrors == 1 ? "" : "s");
|
||||
errx(
|
||||
"The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly "
|
||||
"aborted!",
|
||||
maxErrors,
|
||||
maxErrors == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
|
||||
[[noreturn]] void fatalerror(char const *fmt, ...)
|
||||
{
|
||||
[[noreturn]] void fatalerror(char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
@@ -365,8 +363,7 @@ void error(char const *fmt, ...)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void warning(enum WarningID id, char const *fmt, ...)
|
||||
{
|
||||
void warning(enum WarningID id, char const *fmt, ...) {
|
||||
char const *flag = warningFlags[id];
|
||||
va_list args;
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "error.hpp"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "error.hpp"
|
||||
|
||||
static void vwarn(char const *fmt, va_list ap)
|
||||
{
|
||||
static void vwarn(char const *fmt, va_list ap) {
|
||||
const char *error = strerror(errno);
|
||||
|
||||
fprintf(stderr, "warning: ");
|
||||
@@ -17,15 +16,13 @@ static void vwarn(char const *fmt, va_list ap)
|
||||
fprintf(stderr, ": %s\n", error);
|
||||
}
|
||||
|
||||
static void vwarnx(char const *fmt, va_list ap)
|
||||
{
|
||||
static void vwarnx(char const *fmt, va_list ap) {
|
||||
fprintf(stderr, "warning: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
[[noreturn]] static void verr(char const *fmt, va_list ap)
|
||||
{
|
||||
[[noreturn]] static void verr(char const *fmt, va_list ap) {
|
||||
const char *error = strerror(errno);
|
||||
|
||||
fprintf(stderr, "error: ");
|
||||
@@ -35,8 +32,7 @@ static void vwarnx(char const *fmt, va_list ap)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
[[noreturn]] static void verrx(char const *fmt, va_list ap)
|
||||
{
|
||||
[[noreturn]] static void verrx(char const *fmt, va_list ap) {
|
||||
fprintf(stderr, "error: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
putc('\n', stderr);
|
||||
@@ -44,8 +40,7 @@ static void vwarnx(char const *fmt, va_list ap)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void warn(char const *fmt, ...)
|
||||
{
|
||||
void warn(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
@@ -53,8 +48,7 @@ void warn(char const *fmt, ...)
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void warnx(char const *fmt, ...)
|
||||
{
|
||||
void warnx(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
@@ -62,16 +56,14 @@ void warnx(char const *fmt, ...)
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
[[noreturn]] void err(char const *fmt, ...)
|
||||
{
|
||||
[[noreturn]] void err(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
verr(fmt, ap);
|
||||
}
|
||||
|
||||
[[noreturn]] void errx(char const *fmt, ...)
|
||||
{
|
||||
[[noreturn]] void errx(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
|
||||
90
src/extern/getopt.cpp
vendored
90
src/extern/getopt.cpp
vendored
@@ -2,32 +2,28 @@
|
||||
|
||||
/* This implementation was taken from musl and modified for RGBDS */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include "extern/getopt.hpp"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "extern/getopt.hpp"
|
||||
|
||||
char *musl_optarg;
|
||||
int musl_optind = 1, musl_opterr = 1, musl_optopt;
|
||||
int musl_optreset = 0;
|
||||
static int musl_optpos;
|
||||
|
||||
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l)
|
||||
{
|
||||
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
|
||||
FILE *f = stderr;
|
||||
|
||||
if (fputs(a, f) >= 0 &&
|
||||
fwrite(b, strlen(b), 1, f) &&
|
||||
fwrite(c, 1, l, f) == l)
|
||||
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l)
|
||||
putc('\n', f);
|
||||
}
|
||||
|
||||
static int getopt(int argc, char *argv[], char const *optstring)
|
||||
{
|
||||
static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
int i;
|
||||
wchar_t c, d;
|
||||
int k, l;
|
||||
@@ -77,7 +73,7 @@ static int getopt(int argc, char *argv[], char const *optstring)
|
||||
i = 0;
|
||||
d = 0;
|
||||
do {
|
||||
l = mbtowc(&d, optstring+i, MB_LEN_MAX);
|
||||
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
||||
if (l > 0)
|
||||
i += l;
|
||||
else
|
||||
@@ -101,30 +97,29 @@ static int getopt(int argc, char *argv[], char const *optstring)
|
||||
if (optstring[0] == ':')
|
||||
return ':';
|
||||
if (musl_opterr)
|
||||
musl_getopt_msg(argv[0], ": option requires an argument: ",
|
||||
optchar, k);
|
||||
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static void permute(char **argv, int dest, int src)
|
||||
{
|
||||
static void permute(char **argv, int dest, int src) {
|
||||
char *tmp = argv[src];
|
||||
int i;
|
||||
|
||||
for (i = src; i > dest; i--)
|
||||
argv[i] = argv[i-1];
|
||||
argv[i] = argv[i - 1];
|
||||
argv[dest] = tmp;
|
||||
}
|
||||
|
||||
static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
|
||||
const option *longopts, int *idx, int longonly);
|
||||
static int musl_getopt_long_core(
|
||||
int argc, char **argv, char const *optstring, const option *longopts, int *idx, int longonly
|
||||
);
|
||||
|
||||
static int musl_getopt_long(int argc, char **argv, char const *optstring,
|
||||
const option *longopts, int *idx, int longonly)
|
||||
{
|
||||
static int musl_getopt_long(
|
||||
int argc, char **argv, char const *optstring, const option *longopts, int *idx, int longonly
|
||||
) {
|
||||
int ret, skipped, resumed;
|
||||
|
||||
if (!musl_optind || musl_optreset) {
|
||||
@@ -139,7 +134,7 @@ static int musl_getopt_long(int argc, char **argv, char const *optstring,
|
||||
skipped = musl_optind;
|
||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||
int i;
|
||||
for (i = musl_optind; ; i++) {
|
||||
for (i = musl_optind;; i++) {
|
||||
if (i >= argc || !argv[i])
|
||||
return -1;
|
||||
if (argv[i][0] == '-' && argv[i][1])
|
||||
@@ -159,13 +154,13 @@ static int musl_getopt_long(int argc, char **argv, char const *optstring,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
|
||||
const option *longopts, int *idx, int longonly)
|
||||
{
|
||||
static int musl_getopt_long_core(
|
||||
int argc, char **argv, char const *optstring, const option *longopts, int *idx, int longonly
|
||||
) {
|
||||
musl_optarg = 0;
|
||||
if (longopts && argv[musl_optind][0] == '-' &&
|
||||
((longonly && argv[musl_optind][1] && argv[musl_optind][1] != '-') ||
|
||||
(argv[musl_optind][1] == '-' && argv[musl_optind][2]))) {
|
||||
if (longopts && argv[musl_optind][0] == '-'
|
||||
&& ((longonly && argv[musl_optind][1] && argv[musl_optind][1] != '-')
|
||||
|| (argv[musl_optind][1] == '-' && argv[musl_optind][2]))) {
|
||||
int colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':';
|
||||
int i, cnt, match = 0;
|
||||
char *arg = 0, *opt, *start = argv[musl_optind] + 1;
|
||||
@@ -213,10 +208,12 @@ static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
|
||||
musl_optopt = longopts[i].val;
|
||||
if (colon || !musl_opterr)
|
||||
return '?';
|
||||
musl_getopt_msg(argv[0],
|
||||
": option does not take an argument: ",
|
||||
longopts[i].name,
|
||||
strlen(longopts[i].name));
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option does not take an argument: ",
|
||||
longopts[i].name,
|
||||
strlen(longopts[i].name)
|
||||
);
|
||||
return '?';
|
||||
}
|
||||
musl_optarg = opt + 1;
|
||||
@@ -228,10 +225,12 @@ static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
|
||||
return ':';
|
||||
if (!musl_opterr)
|
||||
return '?';
|
||||
musl_getopt_msg(argv[0],
|
||||
": option requires an argument: ",
|
||||
longopts[i].name,
|
||||
strlen(longopts[i].name));
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option requires an argument: ",
|
||||
longopts[i].name,
|
||||
strlen(longopts[i].name)
|
||||
);
|
||||
return '?';
|
||||
}
|
||||
musl_optind++;
|
||||
@@ -247,11 +246,12 @@ static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
|
||||
if (argv[musl_optind][1] == '-') {
|
||||
musl_optopt = 0;
|
||||
if (!colon && musl_opterr)
|
||||
musl_getopt_msg(argv[0], cnt ?
|
||||
": option is ambiguous: " :
|
||||
": unrecognized option: ",
|
||||
argv[musl_optind] + 2,
|
||||
strlen(argv[musl_optind] + 2));
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
|
||||
argv[musl_optind] + 2,
|
||||
strlen(argv[musl_optind] + 2)
|
||||
);
|
||||
musl_optind++;
|
||||
return '?';
|
||||
}
|
||||
@@ -259,8 +259,8 @@ static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
|
||||
return getopt(argc, argv, optstring);
|
||||
}
|
||||
|
||||
int musl_getopt_long_only(int argc, char **argv, char const *optstring,
|
||||
const option *longopts, int *idx)
|
||||
{
|
||||
int musl_getopt_long_only(
|
||||
int argc, char **argv, char const *optstring, const option *longopts, int *idx
|
||||
) {
|
||||
return musl_getopt_long(argc, argv, optstring, longopts, idx, 1);
|
||||
}
|
||||
|
||||
57
src/extern/utf8decoder.cpp
vendored
57
src/extern/utf8decoder.cpp
vendored
@@ -5,40 +5,37 @@
|
||||
#include <stdint.h>
|
||||
|
||||
static const uint8_t utf8d[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00..0f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10..1f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20..2f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 30..3f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40..4f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50..5f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60..6f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70..7f */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80..8f */
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, /* 90..9f */
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* a0..af */
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* b0..bf */
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* c0..cf */
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* d0..df */
|
||||
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, /* e0..ef */
|
||||
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* f0..ff */
|
||||
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, /* s0 */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, /* s3 */
|
||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, /* s4 */
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, /* s5 */
|
||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s6 */
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s7 */
|
||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s8 */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00..0f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10..1f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20..2f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 30..3f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40..4f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50..5f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60..6f */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70..7f */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80..8f */
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, /* 90..9f */
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* a0..af */
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* b0..bf */
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* c0..cf */
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* d0..df */
|
||||
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, /* e0..ef */
|
||||
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* f0..ff */
|
||||
0, 1, 2, 3, 5, 8, 7, 1, 1, 1, 4, 6, 1, 1, 1, 1, /* s0 */
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, /* s1 */
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, /* s3 */
|
||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, /* s4 */
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, /* s5 */
|
||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s6 */
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, /* s7 */
|
||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* s8 */
|
||||
};
|
||||
|
||||
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte)
|
||||
{
|
||||
uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) {
|
||||
uint32_t type = utf8d[byte];
|
||||
|
||||
*codep = (*state != 0) ?
|
||||
(byte & 0x3FU) | (*codep << 6) :
|
||||
(0xFF >> type) & (byte);
|
||||
*codep = (*state != 0) ? (byte & 0x3FU) | (*codep << 6) : (0xFF >> type) & (byte);
|
||||
|
||||
*state = utf8d[256 + *state * 16 + type];
|
||||
return *state;
|
||||
|
||||
412
src/fix/main.cpp
412
src/fix/main.cpp
@@ -1,7 +1,8 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
@@ -14,7 +15,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "extern/getopt.hpp"
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "platform.hpp"
|
||||
#include "version.hpp"
|
||||
@@ -37,47 +37,46 @@ static const char *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv";
|
||||
* over short opt matching
|
||||
*/
|
||||
static option const longopts[] = {
|
||||
{ "color-only", no_argument, nullptr, 'C' },
|
||||
{ "color-compatible", no_argument, nullptr, 'c' },
|
||||
{ "fix-spec", required_argument, nullptr, 'f' },
|
||||
{ "game-id", required_argument, nullptr, 'i' },
|
||||
{ "non-japanese", no_argument, nullptr, 'j' },
|
||||
{ "new-licensee", required_argument, nullptr, 'k' },
|
||||
{ "old-licensee", required_argument, nullptr, 'l' },
|
||||
{ "mbc-type", required_argument, nullptr, 'm' },
|
||||
{ "rom-version", required_argument, nullptr, 'n' },
|
||||
{ "overwrite", no_argument, nullptr, 'O' },
|
||||
{ "pad-value", required_argument, nullptr, 'p' },
|
||||
{ "ram-size", required_argument, nullptr, 'r' },
|
||||
{ "sgb-compatible", no_argument, nullptr, 's' },
|
||||
{ "title", required_argument, nullptr, 't' },
|
||||
{ "version", no_argument, nullptr, 'V' },
|
||||
{ "validate", no_argument, nullptr, 'v' },
|
||||
{ nullptr, no_argument, nullptr, 0 }
|
||||
{"color-only", no_argument, nullptr, 'C'},
|
||||
{"color-compatible", no_argument, nullptr, 'c'},
|
||||
{"fix-spec", required_argument, nullptr, 'f'},
|
||||
{"game-id", required_argument, nullptr, 'i'},
|
||||
{"non-japanese", no_argument, nullptr, 'j'},
|
||||
{"new-licensee", required_argument, nullptr, 'k'},
|
||||
{"old-licensee", required_argument, nullptr, 'l'},
|
||||
{"mbc-type", required_argument, nullptr, 'm'},
|
||||
{"rom-version", required_argument, nullptr, 'n'},
|
||||
{"overwrite", no_argument, nullptr, 'O'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
{"ram-size", required_argument, nullptr, 'r'},
|
||||
{"sgb-compatible", no_argument, nullptr, 's'},
|
||||
{"title", required_argument, nullptr, 't'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"validate", no_argument, nullptr, 'v'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
static void printUsage()
|
||||
{
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
" [-l <licensee_byte>] [-m <mbc_type>] [-n <rom_version>]\n"
|
||||
" [-p <pad_value>] [-r <ram_size>] [-t <title_str>] <file> ...\n"
|
||||
"Useful options:\n"
|
||||
" -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"
|
||||
" -V, --version print RGBFIX version and exit\n"
|
||||
" -v, --validate fix the header logo and both checksums (-f lhg)\n"
|
||||
"\n"
|
||||
"For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n",
|
||||
stderr);
|
||||
"Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
" [-l <licensee_byte>] [-m <mbc_type>] [-n <rom_version>]\n"
|
||||
" [-p <pad_value>] [-r <ram_size>] [-t <title_str>] <file> ...\n"
|
||||
"Useful options:\n"
|
||||
" -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"
|
||||
" -V, --version print RGBFIX version and exit\n"
|
||||
" -v, --validate fix the header logo and both checksums (-f lhg)\n"
|
||||
"\n"
|
||||
"For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n",
|
||||
stderr
|
||||
);
|
||||
}
|
||||
|
||||
static uint8_t nbErrors;
|
||||
|
||||
static format_(printf, 1, 2) void report(char const *fmt, ...)
|
||||
{
|
||||
static format_(printf, 1, 2) void report(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
@@ -89,7 +88,7 @@ static format_(printf, 1, 2) void report(char const *fmt, ...)
|
||||
}
|
||||
|
||||
enum MbcType {
|
||||
ROM = 0x00,
|
||||
ROM = 0x00,
|
||||
ROM_RAM = 0x08,
|
||||
ROM_RAM_BATTERY = 0x09,
|
||||
|
||||
@@ -153,13 +152,12 @@ enum MbcType {
|
||||
|
||||
// Error values
|
||||
MBC_NONE = UNSPECIFIED, // No MBC specified, do not act on it
|
||||
MBC_BAD, // Specified MBC does not exist / syntax error
|
||||
MBC_WRONG_FEATURES, // MBC incompatible with specified features
|
||||
MBC_BAD_RANGE, // MBC number out of range
|
||||
MBC_BAD, // Specified MBC does not exist / syntax error
|
||||
MBC_WRONG_FEATURES, // MBC incompatible with specified features
|
||||
MBC_BAD_RANGE, // MBC number out of range
|
||||
};
|
||||
|
||||
static void printAcceptedMBCNames()
|
||||
{
|
||||
static void printAcceptedMBCNames() {
|
||||
fputs("\tROM ($00) [aka ROM_ONLY]\n", stderr);
|
||||
fputs("\tMBC1 ($01), MBC1+RAM ($02), MBC1+RAM+BATTERY ($03)\n", stderr);
|
||||
fputs("\tMBC2 ($05), MBC2+BATTERY ($06)\n", stderr);
|
||||
@@ -188,8 +186,7 @@ static uint8_t tpp1Rev[2];
|
||||
/*
|
||||
* @return False on failure
|
||||
*/
|
||||
static bool readMBCSlice(char const *&name, char const *expected)
|
||||
{
|
||||
static bool readMBCSlice(char const *&name, char const *expected) {
|
||||
while (*expected) {
|
||||
char c = *name++;
|
||||
|
||||
@@ -207,8 +204,7 @@ static bool readMBCSlice(char const *&name, char const *expected)
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum MbcType parseMBC(char const *name)
|
||||
{
|
||||
static enum MbcType parseMBC(char const *name) {
|
||||
if (!strcasecmp(name, "help")) {
|
||||
fputs("Accepted MBC names:\n", stderr);
|
||||
printAcceptedMBCNames();
|
||||
@@ -242,10 +238,10 @@ static enum MbcType parseMBC(char const *name)
|
||||
ptr++;
|
||||
|
||||
#define tryReadSlice(expected) \
|
||||
do { \
|
||||
if (!readMBCSlice(ptr, expected)) \
|
||||
return MBC_BAD; \
|
||||
} while (0)
|
||||
do { \
|
||||
if (!readMBCSlice(ptr, expected)) \
|
||||
return MBC_BAD; \
|
||||
} while (0)
|
||||
|
||||
switch (*ptr++) {
|
||||
case 'R': // ROM / ROM_ONLY
|
||||
@@ -386,11 +382,11 @@ do { \
|
||||
|
||||
// Read "additional features"
|
||||
uint8_t features = 0;
|
||||
#define RAM 0x80
|
||||
#define BATTERY 0x40
|
||||
#define TIMER 0x20
|
||||
#define RUMBLE 0x10
|
||||
#define SENSOR 0x08
|
||||
#define RAM 0x80
|
||||
#define BATTERY 0x40
|
||||
#define TIMER 0x20
|
||||
#define RUMBLE 0x10
|
||||
#define SENSOR 0x08
|
||||
#define MULTIRUMBLE 0x04
|
||||
|
||||
for (;;) {
|
||||
@@ -498,8 +494,9 @@ do { \
|
||||
}
|
||||
static_assert(MBC3 + 1 == MBC3_RAM, "Enum sanity check failed!");
|
||||
static_assert(MBC3 + 2 == MBC3_RAM_BATTERY, "Enum sanity check failed!");
|
||||
static_assert(MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY,
|
||||
"Enum sanity check failed!");
|
||||
static_assert(
|
||||
MBC3_TIMER_BATTERY + 1 == MBC3_TIMER_RAM_BATTERY, "Enum sanity check failed!"
|
||||
);
|
||||
if (features == RAM)
|
||||
mbc++;
|
||||
else if (features == (RAM | BATTERY))
|
||||
@@ -516,8 +513,7 @@ do { \
|
||||
static_assert(MBC5 + 1 == MBC5_RAM, "Enum sanity check failed!");
|
||||
static_assert(MBC5 + 2 == MBC5_RAM_BATTERY, "Enum sanity check failed!");
|
||||
static_assert(MBC5_RUMBLE + 1 == MBC5_RUMBLE_RAM, "Enum sanity check failed!");
|
||||
static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY,
|
||||
"Enum sanity check failed!");
|
||||
static_assert(MBC5_RUMBLE + 2 == MBC5_RUMBLE_RAM_BATTERY, "Enum sanity check failed!");
|
||||
if (features == RAM)
|
||||
mbc++;
|
||||
else if (features == (RAM | BATTERY))
|
||||
@@ -547,8 +543,9 @@ do { \
|
||||
|
||||
case TPP1:
|
||||
if (features & RAM)
|
||||
fprintf(stderr,
|
||||
"warning: TPP1 requests RAM implicitly if given a non-zero RAM size");
|
||||
fprintf(
|
||||
stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size"
|
||||
);
|
||||
if (features & BATTERY)
|
||||
mbc |= 0x08;
|
||||
if (features & TIMER)
|
||||
@@ -574,8 +571,7 @@ do { \
|
||||
}
|
||||
}
|
||||
|
||||
static char const *mbcName(enum MbcType type)
|
||||
{
|
||||
static char const *mbcName(enum MbcType type) {
|
||||
switch (type) {
|
||||
case ROM:
|
||||
return "ROM";
|
||||
@@ -673,8 +669,7 @@ static char const *mbcName(enum MbcType type)
|
||||
unreachable_();
|
||||
}
|
||||
|
||||
static bool hasRAM(enum MbcType type)
|
||||
{
|
||||
static bool hasRAM(enum MbcType type) {
|
||||
switch (type) {
|
||||
case ROM:
|
||||
case MBC1:
|
||||
@@ -685,7 +680,7 @@ static bool hasRAM(enum MbcType type)
|
||||
case MBC3_TIMER_BATTERY:
|
||||
case MBC5:
|
||||
case MBC5_RUMBLE:
|
||||
case MBC6: // TODO: not sure
|
||||
case MBC6: // TODO: not sure
|
||||
case BANDAI_TAMA5: // TODO: not sure
|
||||
case MBC_NONE:
|
||||
case MBC_BAD:
|
||||
@@ -736,30 +731,28 @@ static bool hasRAM(enum MbcType type)
|
||||
}
|
||||
|
||||
static const uint8_t ninLogo[] = {
|
||||
0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B,
|
||||
0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D,
|
||||
0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E,
|
||||
0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99,
|
||||
0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC,
|
||||
0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E
|
||||
0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D,
|
||||
0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99,
|
||||
0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E,
|
||||
};
|
||||
|
||||
static const uint8_t trashLogo[] = {
|
||||
0xFF^0xCE, 0xFF^0xED, 0xFF^0x66, 0xFF^0x66, 0xFF^0xCC, 0xFF^0x0D, 0xFF^0x00, 0xFF^0x0B,
|
||||
0xFF^0x03, 0xFF^0x73, 0xFF^0x00, 0xFF^0x83, 0xFF^0x00, 0xFF^0x0C, 0xFF^0x00, 0xFF^0x0D,
|
||||
0xFF^0x00, 0xFF^0x08, 0xFF^0x11, 0xFF^0x1F, 0xFF^0x88, 0xFF^0x89, 0xFF^0x00, 0xFF^0x0E,
|
||||
0xFF^0xDC, 0xFF^0xCC, 0xFF^0x6E, 0xFF^0xE6, 0xFF^0xDD, 0xFF^0xDD, 0xFF^0xD9, 0xFF^0x99,
|
||||
0xFF^0xBB, 0xFF^0xBB, 0xFF^0x67, 0xFF^0x63, 0xFF^0x6E, 0xFF^0x0E, 0xFF^0xEC, 0xFF^0xCC,
|
||||
0xFF^0xDD, 0xFF^0xDC, 0xFF^0x99, 0xFF^0x9F, 0xFF^0xBB, 0xFF^0xB9, 0xFF^0x33, 0xFF^0x3E
|
||||
0xFF ^ 0xCE, 0xFF ^ 0xED, 0xFF ^ 0x66, 0xFF ^ 0x66, 0xFF ^ 0xCC, 0xFF ^ 0x0D, 0xFF ^ 0x00,
|
||||
0xFF ^ 0x0B, 0xFF ^ 0x03, 0xFF ^ 0x73, 0xFF ^ 0x00, 0xFF ^ 0x83, 0xFF ^ 0x00, 0xFF ^ 0x0C,
|
||||
0xFF ^ 0x00, 0xFF ^ 0x0D, 0xFF ^ 0x00, 0xFF ^ 0x08, 0xFF ^ 0x11, 0xFF ^ 0x1F, 0xFF ^ 0x88,
|
||||
0xFF ^ 0x89, 0xFF ^ 0x00, 0xFF ^ 0x0E, 0xFF ^ 0xDC, 0xFF ^ 0xCC, 0xFF ^ 0x6E, 0xFF ^ 0xE6,
|
||||
0xFF ^ 0xDD, 0xFF ^ 0xDD, 0xFF ^ 0xD9, 0xFF ^ 0x99, 0xFF ^ 0xBB, 0xFF ^ 0xBB, 0xFF ^ 0x67,
|
||||
0xFF ^ 0x63, 0xFF ^ 0x6E, 0xFF ^ 0x0E, 0xFF ^ 0xEC, 0xFF ^ 0xCC, 0xFF ^ 0xDD, 0xFF ^ 0xDC,
|
||||
0xFF ^ 0x99, 0xFF ^ 0x9F, 0xFF ^ 0xBB, 0xFF ^ 0xB9, 0xFF ^ 0x33, 0xFF ^ 0x3E,
|
||||
};
|
||||
|
||||
static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone
|
||||
#define FIX_LOGO 0x80
|
||||
#define TRASH_LOGO 0x40
|
||||
#define FIX_HEADER_SUM 0x20
|
||||
#define TRASH_HEADER_SUM 0x10
|
||||
#define FIX_GLOBAL_SUM 0x08
|
||||
#define TRASH_GLOBAL_SUM 0x04
|
||||
#define FIX_LOGO 0x80
|
||||
#define TRASH_LOGO 0x40
|
||||
#define FIX_HEADER_SUM 0x20
|
||||
#define TRASH_HEADER_SUM 0x10
|
||||
#define FIX_GLOBAL_SUM 0x08
|
||||
#define TRASH_GLOBAL_SUM 0x04
|
||||
static uint8_t fixSpec = 0;
|
||||
static const char *gameID = nullptr;
|
||||
static uint8_t gameIDLen;
|
||||
@@ -776,13 +769,11 @@ static bool sgb = false; // If false, SGB flags are left alone
|
||||
static const char *title = nullptr;
|
||||
static uint8_t titleLen;
|
||||
|
||||
static uint8_t maxTitleLen()
|
||||
{
|
||||
static uint8_t maxTitleLen() {
|
||||
return gameID ? 11 : model != DMG ? 15 : 16;
|
||||
}
|
||||
|
||||
static ssize_t readBytes(int fd, uint8_t *buf, size_t len)
|
||||
{
|
||||
static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
|
||||
// POSIX specifies that lengths greater than SSIZE_MAX yield implementation-defined results
|
||||
assert(len <= SSIZE_MAX);
|
||||
|
||||
@@ -807,8 +798,7 @@ static ssize_t readBytes(int fd, uint8_t *buf, size_t len)
|
||||
return total;
|
||||
}
|
||||
|
||||
static ssize_t writeBytes(int fd, uint8_t *buf, size_t len)
|
||||
{
|
||||
static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
|
||||
// POSIX specifies that lengths greater than SSIZE_MAX yield implementation-defined results
|
||||
assert(len <= SSIZE_MAX);
|
||||
|
||||
@@ -838,8 +828,7 @@ static ssize_t writeBytes(int fd, uint8_t *buf, size_t len)
|
||||
* @param fixedByte The fixed byte at the address
|
||||
* @param areaName Name to be displayed in the warning message
|
||||
*/
|
||||
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName)
|
||||
{
|
||||
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName) {
|
||||
uint8_t origByte = rom0[addr];
|
||||
|
||||
if (!overwriteRom && origByte != 0 && origByte != fixedByte)
|
||||
@@ -855,16 +844,15 @@ static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char
|
||||
* @param size How many bytes to check
|
||||
* @param areaName Name to be displayed in the warning message
|
||||
*/
|
||||
static void overwriteBytes(uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size,
|
||||
char const *areaName)
|
||||
{
|
||||
static void overwriteBytes(
|
||||
uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size, char const *areaName
|
||||
) {
|
||||
if (!overwriteRom) {
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
uint8_t origByte = rom0[i + startAddr];
|
||||
|
||||
if (origByte != 0 && origByte != fixed[i]) {
|
||||
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n",
|
||||
areaName);
|
||||
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -879,8 +867,7 @@ static void overwriteBytes(uint8_t *rom0, uint16_t startAddr, uint8_t const *fix
|
||||
* @param name The file's name, to be displayed for error output
|
||||
* @param fileSize The file's size if known, 0 if not.
|
||||
*/
|
||||
static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
{
|
||||
static void processFile(int input, int output, char const *name, off_t fileSize) {
|
||||
// Both of these should be true for seekable files, and neither otherwise
|
||||
if (input == output)
|
||||
assert(fileSize != 0);
|
||||
@@ -896,8 +883,13 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
|
||||
return;
|
||||
} else if (rom0Len < headerSize) {
|
||||
report("FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
||||
name, (intmax_t)headerSize, (intmax_t)headerSize, (intmax_t)rom0Len);
|
||||
report(
|
||||
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
||||
name,
|
||||
(intmax_t)headerSize,
|
||||
(intmax_t)headerSize,
|
||||
(intmax_t)rom0Len
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Accept partial reads if the file contains at least the header
|
||||
@@ -919,8 +911,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
|
||||
|
||||
if (newLicensee)
|
||||
overwriteBytes(rom0, 0x144, (uint8_t const *)newLicensee, newLicenseeLen,
|
||||
"new licensee code");
|
||||
overwriteBytes(
|
||||
rom0, 0x144, (uint8_t const *)newLicensee, newLicenseeLen, "new licensee code"
|
||||
);
|
||||
|
||||
if (sgb)
|
||||
overwriteByte(rom0, 0x146, 0x03, "SGB flag");
|
||||
@@ -963,9 +956,11 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (oldLicensee != UNSPECIFIED)
|
||||
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
|
||||
else if (sgb && rom0[0x14B] != 0x33)
|
||||
fprintf(stderr,
|
||||
"warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n",
|
||||
rom0[0x14B]);
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n",
|
||||
rom0[0x14B]
|
||||
);
|
||||
|
||||
if (romVersion != UNSPECIFIED)
|
||||
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
|
||||
@@ -981,9 +976,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// 65536 banks = 1 GiB.
|
||||
// This should be reasonable for the time being, and may be extended later.
|
||||
std::vector<uint8_t> romx; // Buffer of ROMX bank data
|
||||
uint32_t nbBanks = 1; // Number of banks *targeted*, including ROM0
|
||||
size_t totalRomxLen = 0; // *Actual* size of ROMX data
|
||||
uint8_t bank[BANK_SIZE]; // Temp buffer used to store a whole bank's worth of data
|
||||
uint32_t nbBanks = 1; // Number of banks *targeted*, including ROM0
|
||||
size_t totalRomxLen = 0; // *Actual* size of ROMX data
|
||||
uint8_t bank[BANK_SIZE]; // Temp buffer used to store a whole bank's worth of data
|
||||
|
||||
// Handle ROMX
|
||||
if (input == output) {
|
||||
@@ -1006,7 +1001,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// Update bank count, ONLY IF at least one byte was read
|
||||
if (bankLen) {
|
||||
// We're gonna read another bank, check that it won't be too much
|
||||
static_assert(0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS");
|
||||
static_assert(
|
||||
0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS"
|
||||
);
|
||||
if (nbBanks == 0x10000) {
|
||||
report("FATAL: \"%s\" has more than 65536 banks\n", name);
|
||||
return;
|
||||
@@ -1062,8 +1059,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
for (uint16_t i = 0x134; i < 0x14D; i++)
|
||||
sum -= rom0[i] + 1;
|
||||
|
||||
overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum,
|
||||
"header checksum");
|
||||
overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum, "header checksum");
|
||||
}
|
||||
|
||||
if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
|
||||
@@ -1113,8 +1109,12 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
|
||||
return;
|
||||
} else if (writeLen < rom0Len) {
|
||||
report("FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
||||
(intmax_t)writeLen, name, (intmax_t)rom0Len);
|
||||
report(
|
||||
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
||||
(intmax_t)writeLen,
|
||||
name,
|
||||
(intmax_t)rom0Len
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1127,8 +1127,12 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
||||
return;
|
||||
} else if ((size_t)writeLen < totalRomxLen) {
|
||||
report("FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
||||
(intmax_t)writeLen, name, totalRomxLen);
|
||||
report(
|
||||
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
||||
(intmax_t)writeLen,
|
||||
name,
|
||||
totalRomxLen
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1137,8 +1141,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (padValue != UNSPECIFIED) {
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_END) == (off_t)-1) {
|
||||
report("FATAL: Failed to seek to end of \"%s\": %s\n",
|
||||
name, strerror(errno));
|
||||
report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1153,8 +1156,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// The return value is either -1, or at most `thisLen`,
|
||||
// so it's fine to cast to `size_t`
|
||||
if ((size_t)ret != thisLen) {
|
||||
report("FATAL: Failed to write \"%s\"'s padding: %s\n",
|
||||
name, strerror(errno));
|
||||
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
|
||||
break;
|
||||
}
|
||||
len -= thisLen;
|
||||
@@ -1162,8 +1164,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
}
|
||||
}
|
||||
|
||||
static bool processFilename(char const *name)
|
||||
{
|
||||
static bool processFilename(char const *name) {
|
||||
nbErrors = 0;
|
||||
if (!strcmp(name, "-")) {
|
||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||
@@ -1181,21 +1182,24 @@ static bool processFilename(char const *name)
|
||||
struct stat stat;
|
||||
|
||||
if (input == -1) {
|
||||
report("FATAL: Failed to open \"%s\" for reading+writing: %s\n",
|
||||
name, strerror(errno));
|
||||
report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", name, strerror(errno));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (fstat(input, &stat) == -1) {
|
||||
report("FATAL: Failed to stat \"%s\": %s\n", name, strerror(errno));
|
||||
} else if (!S_ISREG(stat.st_mode)) { // TODO: Do we want to support other types?
|
||||
report("FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n",
|
||||
name);
|
||||
report(
|
||||
"FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n", name
|
||||
);
|
||||
} else if (stat.st_size < 0x150) {
|
||||
// This check is in theory redundant with the one in `processFile`, but it
|
||||
// prevents passing a file size of 0, which usually indicates pipes
|
||||
report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n",
|
||||
name, (intmax_t)stat.st_size);
|
||||
report(
|
||||
"FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n",
|
||||
name,
|
||||
(intmax_t)stat.st_size
|
||||
);
|
||||
} else {
|
||||
processFile(input, input, name, stat.st_size);
|
||||
}
|
||||
@@ -1204,48 +1208,53 @@ static bool processFilename(char const *name)
|
||||
}
|
||||
finish:
|
||||
if (nbErrors)
|
||||
fprintf(stderr, "Fixing \"%s\" failed with %u error%s\n",
|
||||
name, nbErrors, nbErrors == 1 ? "" : "s");
|
||||
fprintf(
|
||||
stderr,
|
||||
"Fixing \"%s\" failed with %u error%s\n",
|
||||
name,
|
||||
nbErrors,
|
||||
nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
return nbErrors;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int main(int argc, char *argv[]) {
|
||||
nbErrors = 0;
|
||||
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
switch (ch) {
|
||||
size_t len;
|
||||
#define parseByte(output, name) \
|
||||
do { \
|
||||
char *endptr; \
|
||||
unsigned long tmp; \
|
||||
\
|
||||
if (musl_optarg[0] == 0) { \
|
||||
report("error: Argument to option '" name "' may not be empty\n"); \
|
||||
} else { \
|
||||
if (musl_optarg[0] == '$') { \
|
||||
tmp = strtoul(&musl_optarg[1], &endptr, 16); \
|
||||
do { \
|
||||
char *endptr; \
|
||||
unsigned long tmp; \
|
||||
\
|
||||
if (musl_optarg[0] == 0) { \
|
||||
report("error: Argument to option '" name "' may not be empty\n"); \
|
||||
} else { \
|
||||
tmp = strtoul(musl_optarg, &endptr, 0); \
|
||||
if (musl_optarg[0] == '$') { \
|
||||
tmp = strtoul(&musl_optarg[1], &endptr, 16); \
|
||||
} else { \
|
||||
tmp = strtoul(musl_optarg, &endptr, 0); \
|
||||
} \
|
||||
if (*endptr) \
|
||||
report( \
|
||||
"error: Expected number as argument to option '" name "', got %s\n", \
|
||||
musl_optarg \
|
||||
); \
|
||||
else if (tmp > 0xFF) \
|
||||
report("error: Argument to option '" name "' is larger than 255: %lu\n", tmp); \
|
||||
else \
|
||||
output = tmp; \
|
||||
} \
|
||||
if (*endptr) \
|
||||
report("error: Expected number as argument to option '" name "', got %s\n", \
|
||||
musl_optarg); \
|
||||
else if (tmp > 0xFF) \
|
||||
report("error: Argument to option '" name "' is larger than 255: %lu\n", tmp); \
|
||||
else \
|
||||
output = tmp; \
|
||||
} \
|
||||
} while (0)
|
||||
} while (0)
|
||||
|
||||
case 'C':
|
||||
case 'c':
|
||||
model = ch == 'c' ? BOTH : CGB;
|
||||
if (titleLen > 15) {
|
||||
titleLen = 15;
|
||||
fprintf(stderr, "warning: Truncating title \"%s\" to 15 chars\n",
|
||||
title);
|
||||
fprintf(stderr, "warning: Truncating title \"%s\" to 15 chars\n", title);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1260,12 +1269,11 @@ do { \
|
||||
#define SPEC_g FIX_GLOBAL_SUM
|
||||
#define SPEC_G TRASH_GLOBAL_SUM
|
||||
#define overrideSpec(cur, bad) \
|
||||
do { \
|
||||
if (fixSpec & SPEC_##bad) \
|
||||
fprintf(stderr, \
|
||||
"warning: '" #cur "' overriding '" #bad "' in fix spec\n"); \
|
||||
fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##cur; \
|
||||
} while (0)
|
||||
do { \
|
||||
if (fixSpec & SPEC_##bad) \
|
||||
fprintf(stderr, "warning: '" #cur "' overriding '" #bad "' in fix spec\n"); \
|
||||
fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##cur; \
|
||||
} while (0)
|
||||
case 'l':
|
||||
overrideSpec(l, L);
|
||||
break;
|
||||
@@ -1288,8 +1296,7 @@ do { \
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "warning: Ignoring '%c' in fix spec\n",
|
||||
*musl_optarg);
|
||||
fprintf(stderr, "warning: Ignoring '%c' in fix spec\n", *musl_optarg);
|
||||
#undef overrideSpec
|
||||
}
|
||||
musl_optarg++;
|
||||
@@ -1301,14 +1308,12 @@ do { \
|
||||
len = strlen(gameID);
|
||||
if (len > 4) {
|
||||
len = 4;
|
||||
fprintf(stderr, "warning: Truncating game ID \"%s\" to 4 chars\n",
|
||||
gameID);
|
||||
fprintf(stderr, "warning: Truncating game ID \"%s\" to 4 chars\n", gameID);
|
||||
}
|
||||
gameIDLen = len;
|
||||
if (titleLen > 11) {
|
||||
titleLen = 11;
|
||||
fprintf(stderr, "warning: Truncating title \"%s\" to 11 chars\n",
|
||||
title);
|
||||
fprintf(stderr, "warning: Truncating title \"%s\" to 11 chars\n", title);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1321,9 +1326,9 @@ do { \
|
||||
len = strlen(newLicensee);
|
||||
if (len > 2) {
|
||||
len = 2;
|
||||
fprintf(stderr,
|
||||
"warning: Truncating new licensee \"%s\" to 2 chars\n",
|
||||
newLicensee);
|
||||
fprintf(
|
||||
stderr, "warning: Truncating new licensee \"%s\" to 2 chars\n", newLicensee
|
||||
);
|
||||
}
|
||||
newLicenseeLen = len;
|
||||
break;
|
||||
@@ -1335,18 +1340,22 @@ do { \
|
||||
case 'm':
|
||||
cartridgeType = parseMBC(musl_optarg);
|
||||
if (cartridgeType == MBC_BAD) {
|
||||
report("error: Unknown MBC \"%s\"\nAccepted MBC names:\n",
|
||||
musl_optarg);
|
||||
report("error: Unknown MBC \"%s\"\nAccepted MBC names:\n", musl_optarg);
|
||||
printAcceptedMBCNames();
|
||||
} else if (cartridgeType == MBC_WRONG_FEATURES) {
|
||||
report("error: Features incompatible with MBC (\"%s\")\nAccepted combinations:\n",
|
||||
musl_optarg);
|
||||
report(
|
||||
"error: Features incompatible with MBC (\"%s\")\nAccepted combinations:\n",
|
||||
musl_optarg
|
||||
);
|
||||
printAcceptedMBCNames();
|
||||
} else if (cartridgeType == MBC_BAD_RANGE) {
|
||||
report("error: Specified MBC ID out of range 0-255: %s\n",
|
||||
musl_optarg);
|
||||
report("error: Specified MBC ID out of range 0-255: %s\n", musl_optarg);
|
||||
} else if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
|
||||
fprintf(stderr, "warning: ROM+RAM / ROM+RAM+BATTERY are under-specified and poorly supported\n");
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: ROM+RAM / ROM+RAM+BATTERY are under-specified and poorly "
|
||||
"supported\n"
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1377,8 +1386,7 @@ do { \
|
||||
|
||||
if (len > maxLen) {
|
||||
len = maxLen;
|
||||
fprintf(stderr, "warning: Truncating title \"%s\" to %u chars\n",
|
||||
title, maxLen);
|
||||
fprintf(stderr, "warning: Truncating title \"%s\" to %u chars\n", title, maxLen);
|
||||
}
|
||||
titleLen = len;
|
||||
break;
|
||||
@@ -1401,42 +1409,58 @@ do { \
|
||||
}
|
||||
|
||||
if ((cartridgeType & 0xFF00) == TPP1 && !japanese)
|
||||
fprintf(stderr, "warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n");
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n"
|
||||
);
|
||||
|
||||
// Check that RAM size is correct for "standard" mappers
|
||||
if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) {
|
||||
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
|
||||
if (ramSize != 1)
|
||||
fprintf(stderr, "warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n",
|
||||
mbcName(cartridgeType));
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n",
|
||||
mbcName(cartridgeType)
|
||||
);
|
||||
} else if (hasRAM(cartridgeType)) {
|
||||
if (!ramSize) {
|
||||
fprintf(stderr,
|
||||
"warning: MBC \"%s\" has RAM, but RAM size was set to 0\n",
|
||||
mbcName(cartridgeType));
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: MBC \"%s\" has RAM, but RAM size was set to 0\n",
|
||||
mbcName(cartridgeType)
|
||||
);
|
||||
} else if (ramSize == 1) {
|
||||
fprintf(stderr,
|
||||
"warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n",
|
||||
mbcName(cartridgeType));
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n",
|
||||
mbcName(cartridgeType)
|
||||
);
|
||||
} // TODO: check possible values?
|
||||
} else if (ramSize) {
|
||||
fprintf(stderr,
|
||||
"warning: MBC \"%s\" has no RAM, but RAM size was set to %u\n",
|
||||
mbcName(cartridgeType), ramSize);
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: MBC \"%s\" has no RAM, but RAM size was set to %u\n",
|
||||
mbcName(cartridgeType),
|
||||
ramSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33)
|
||||
fprintf(stderr,
|
||||
"warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n",
|
||||
oldLicensee);
|
||||
fprintf(
|
||||
stderr,
|
||||
"warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n",
|
||||
oldLicensee
|
||||
);
|
||||
|
||||
argv += musl_optind;
|
||||
bool failed = nbErrors;
|
||||
|
||||
if (!*argv) {
|
||||
fputs("FATAL: Please specify an input file (pass `-` to read from standard input)\n",
|
||||
stderr);
|
||||
fputs(
|
||||
"FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr
|
||||
);
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
205
src/gfx/main.cpp
205
src/gfx/main.cpp
@@ -118,52 +118,54 @@ static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvx:Z";
|
||||
* over short opt matching
|
||||
*/
|
||||
static option const longopts[] = {
|
||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||
{"output-attr-map", no_argument, nullptr, -'A'}, // Deprecated
|
||||
{"attr-map", required_argument, nullptr, 'a'},
|
||||
{"base-tiles", required_argument, nullptr, 'b'},
|
||||
{"color-curve", no_argument, nullptr, 'C'},
|
||||
{"colors", required_argument, nullptr, 'c'},
|
||||
{"depth", required_argument, nullptr, 'd'},
|
||||
{"slice", required_argument, nullptr, 'L'},
|
||||
{"mirror-tiles", no_argument, nullptr, 'm'},
|
||||
{"nb-tiles", required_argument, nullptr, 'N'},
|
||||
{"nb-palettes", required_argument, nullptr, 'n'},
|
||||
{"group-outputs", no_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"auto-palette", no_argument, nullptr, 'P'},
|
||||
{"output-palette", no_argument, nullptr, -'P'}, // Deprecated
|
||||
{"palette", required_argument, nullptr, 'p'},
|
||||
{"auto-palette-map", no_argument, nullptr, 'Q'},
|
||||
{"output-palette-map", no_argument, nullptr, -'Q'}, // Deprecated
|
||||
{"palette-map", required_argument, nullptr, 'q'},
|
||||
{"reverse", required_argument, nullptr, 'r'},
|
||||
{"auto-tilemap", no_argument, nullptr, 'T'},
|
||||
{"output-tilemap", no_argument, nullptr, -'T'}, // Deprecated
|
||||
{"tilemap", required_argument, nullptr, 't'},
|
||||
{"unit-size", required_argument, nullptr, 'U'},
|
||||
{"unique-tiles", no_argument, nullptr, 'u'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"trim-end", required_argument, nullptr, 'x'},
|
||||
{"columns", no_argument, nullptr, 'Z'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
{"auto-attr-map", no_argument, nullptr, 'A' },
|
||||
{"output-attr-map", no_argument, nullptr, -'A'}, // Deprecated
|
||||
{"attr-map", required_argument, nullptr, 'a' },
|
||||
{"base-tiles", required_argument, nullptr, 'b' },
|
||||
{"color-curve", no_argument, nullptr, 'C' },
|
||||
{"colors", required_argument, nullptr, 'c' },
|
||||
{"depth", required_argument, nullptr, 'd' },
|
||||
{"slice", required_argument, nullptr, 'L' },
|
||||
{"mirror-tiles", no_argument, nullptr, 'm' },
|
||||
{"nb-tiles", required_argument, nullptr, 'N' },
|
||||
{"nb-palettes", required_argument, nullptr, 'n' },
|
||||
{"group-outputs", no_argument, nullptr, 'O' },
|
||||
{"output", required_argument, nullptr, 'o' },
|
||||
{"auto-palette", no_argument, nullptr, 'P' },
|
||||
{"output-palette", no_argument, nullptr, -'P'}, // Deprecated
|
||||
{"palette", required_argument, nullptr, 'p' },
|
||||
{"auto-palette-map", no_argument, nullptr, 'Q' },
|
||||
{"output-palette-map", no_argument, nullptr, -'Q'}, // Deprecated
|
||||
{"palette-map", required_argument, nullptr, 'q' },
|
||||
{"reverse", required_argument, nullptr, 'r' },
|
||||
{"auto-tilemap", no_argument, nullptr, 'T' },
|
||||
{"output-tilemap", no_argument, nullptr, -'T'}, // Deprecated
|
||||
{"tilemap", required_argument, nullptr, 't' },
|
||||
{"unit-size", required_argument, nullptr, 'U' },
|
||||
{"unique-tiles", no_argument, nullptr, 'u' },
|
||||
{"version", no_argument, nullptr, 'V' },
|
||||
{"verbose", no_argument, nullptr, 'v' },
|
||||
{"trim-end", required_argument, nullptr, 'x' },
|
||||
{"columns", no_argument, nullptr, 'Z' },
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
static void printUsage() {
|
||||
fputs("Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
|
||||
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
|
||||
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
|
||||
"Useful options:\n"
|
||||
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
||||
" -o, --output <path> output the tile data to this path\n"
|
||||
" -t, --tilemap <path> output the tile map to this path\n"
|
||||
" -u, --unique-tiles optimize out identical tiles\n"
|
||||
" -V, --version print RGBGFX version and exit\n"
|
||||
"\n"
|
||||
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
|
||||
stderr);
|
||||
fputs(
|
||||
"Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
|
||||
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
|
||||
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
|
||||
"Useful options:\n"
|
||||
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
||||
" -o, --output <path> output the tile data to this path\n"
|
||||
" -t, --tilemap <path> output the tile map to this path\n"
|
||||
" -u, --unique-tiles optimize out identical tiles\n"
|
||||
" -V, --version print RGBGFX version and exit\n"
|
||||
"\n"
|
||||
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
|
||||
stderr
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -215,8 +217,9 @@ static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVa
|
||||
};
|
||||
|
||||
if (charIndex(*string) == 255) {
|
||||
error("%s: expected digit%s, but found nothing", errPrefix,
|
||||
base != 10 ? " after base" : "");
|
||||
error(
|
||||
"%s: expected digit%s, but found nothing", errPrefix, base != 10 ? " after base" : ""
|
||||
);
|
||||
return errVal;
|
||||
}
|
||||
uint16_t number = 0;
|
||||
@@ -246,10 +249,13 @@ static void skipWhitespace(char *&arg) {
|
||||
|
||||
static void registerInput(char const *arg) {
|
||||
if (!options.input.empty()) {
|
||||
fprintf(stderr,
|
||||
"FATAL: input image specified more than once! (first \"%s\", then "
|
||||
"\"%s\")\n",
|
||||
options.input.c_str(), arg);
|
||||
fprintf(
|
||||
stderr,
|
||||
"FATAL: input image specified more than once! (first \"%s\", then "
|
||||
"\"%s\")\n",
|
||||
options.input.c_str(),
|
||||
arg
|
||||
);
|
||||
printUsage();
|
||||
exit(1);
|
||||
} else if (arg[0] == '\0') { // Empty input path
|
||||
@@ -272,8 +278,10 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
||||
}
|
||||
|
||||
// We only filter out `EOF`, but calling `isblank()` on anything else is UB!
|
||||
static_assert(std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
|
||||
"isblank(char_traits<...>::eof()) is UB!");
|
||||
static_assert(
|
||||
std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
|
||||
"isblank(char_traits<...>::eof()) is UB!"
|
||||
);
|
||||
std::vector<size_t> argvOfs;
|
||||
|
||||
for (;;) {
|
||||
@@ -296,7 +304,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
||||
}
|
||||
continue; // Start processing the next line
|
||||
// If it's an empty line, ignore it
|
||||
case '\r': // Assuming CRLF here
|
||||
case '\r': // Assuming CRLF here
|
||||
file->sbumpc(); // Discard the upcoming '\n'
|
||||
[[fallthrough]];
|
||||
case '\n':
|
||||
@@ -371,8 +379,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
}
|
||||
skipWhitespace(arg);
|
||||
if (*arg != ',') {
|
||||
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg);
|
||||
error(
|
||||
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg
|
||||
);
|
||||
break;
|
||||
}
|
||||
++arg; // Skip comma
|
||||
@@ -384,8 +394,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
options.baseTileIDs[1] = number;
|
||||
}
|
||||
if (*arg != '\0') {
|
||||
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg);
|
||||
error(
|
||||
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -474,8 +486,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
}
|
||||
skipWhitespace(arg);
|
||||
if (*arg != ',') {
|
||||
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg);
|
||||
error(
|
||||
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg
|
||||
);
|
||||
break;
|
||||
}
|
||||
++arg; // Skip comma
|
||||
@@ -485,8 +499,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
error("Bank 1 cannot contain more than 256 tiles");
|
||||
}
|
||||
if (*arg != '\0') {
|
||||
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg);
|
||||
error(
|
||||
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -604,7 +620,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct AtFileStackEntry {
|
||||
int parentInd; // Saved offset into parent argv
|
||||
int parentInd; // Saved offset into parent argv
|
||||
std::vector<char *> argv; // This context's arg pointer vec
|
||||
std::vector<char> argPool;
|
||||
|
||||
@@ -633,7 +649,7 @@ int main(int argc, char *argv[]) {
|
||||
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
|
||||
continue; // Begin scanning that arg vector
|
||||
}
|
||||
|
||||
if (musl_optind != curArgc) {
|
||||
@@ -665,16 +681,23 @@ int main(int argc, char *argv[]) {
|
||||
if (options.nbColorsPerPal == 0) {
|
||||
options.nbColorsPerPal = 1u << options.bitDepth;
|
||||
} else if (options.nbColorsPerPal > 1u << options.bitDepth) {
|
||||
error("%" PRIu8 "bpp palettes can only contain %u colors, not %" PRIu8, options.bitDepth,
|
||||
1u << options.bitDepth, options.nbColorsPerPal);
|
||||
error(
|
||||
"%" PRIu8 "bpp palettes can only contain %u colors, not %" PRIu8,
|
||||
options.bitDepth,
|
||||
1u << options.bitDepth,
|
||||
options.nbColorsPerPal
|
||||
);
|
||||
}
|
||||
|
||||
auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) {
|
||||
if (autoOptEnabled) {
|
||||
auto &image = localOptions.groupOutputs ? options.output : options.input;
|
||||
if (image.empty()) {
|
||||
fprintf(stderr, "FATAL: No %s specified\n", localOptions.groupOutputs
|
||||
? "output tile data file" : "input image");
|
||||
fprintf(
|
||||
stderr,
|
||||
"FATAL: No %s specified\n",
|
||||
localOptions.groupOutputs ? "output tile data file" : "input image"
|
||||
);
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
@@ -723,7 +746,8 @@ int main(int argc, char *argv[]) {
|
||||
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; ++_) {
|
||||
@@ -781,15 +805,27 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
fputs("\t]\n", stderr);
|
||||
}
|
||||
fprintf(stderr,
|
||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32
|
||||
", %" PRIi32 ")\n",
|
||||
options.inputSlice.width, options.inputSlice.height, options.inputSlice.left,
|
||||
options.inputSlice.top);
|
||||
fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0],
|
||||
options.baseTileIDs[1]);
|
||||
fprintf(stderr, "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
|
||||
options.maxNbTiles[0], options.maxNbTiles[1]);
|
||||
fprintf(
|
||||
stderr,
|
||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32 ", %" PRIi32
|
||||
")\n",
|
||||
options.inputSlice.width,
|
||||
options.inputSlice.height,
|
||||
options.inputSlice.left,
|
||||
options.inputSlice.top
|
||||
);
|
||||
fprintf(
|
||||
stderr,
|
||||
"\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n",
|
||||
options.baseTileIDs[0],
|
||||
options.baseTileIDs[1]
|
||||
);
|
||||
fprintf(
|
||||
stderr,
|
||||
"\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
|
||||
options.maxNbTiles[0],
|
||||
options.maxNbTiles[1]
|
||||
);
|
||||
auto printPath = [](char const *name, std::string const &path) {
|
||||
if (!path.empty()) {
|
||||
fprintf(stderr, "\t%s: %s\n", name, path.c_str());
|
||||
@@ -814,8 +850,7 @@ int main(int argc, char *argv[]) {
|
||||
} else {
|
||||
process();
|
||||
}
|
||||
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT
|
||||
&& !options.reverse()) {
|
||||
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT && !options.reverse()) {
|
||||
processPalettes();
|
||||
} else {
|
||||
fputs("FATAL: No input image specified\n", stderr);
|
||||
@@ -832,7 +867,7 @@ int main(int argc, char *argv[]) {
|
||||
void Palette::addColor(uint16_t color) {
|
||||
for (size_t i = 0; true; ++i) {
|
||||
assert(i < colors.size()); // The packing should guarantee this
|
||||
if (colors[i] == color) { // The color is already present
|
||||
if (colors[i] == color) { // The color is already present
|
||||
break;
|
||||
} else if (colors[i] == UINT16_MAX) { // Empty slot
|
||||
colors[i] = color;
|
||||
@@ -858,9 +893,9 @@ auto Palette::begin() -> decltype(colors)::iterator {
|
||||
auto Palette::end() -> decltype(colors)::iterator {
|
||||
// Return an iterator pointing past the last non-empty element.
|
||||
// Since the palette may contain gaps, we must scan from the end.
|
||||
return std::find_if(colors.rbegin(), colors.rend(),
|
||||
[](uint16_t c) { return c != UINT16_MAX; })
|
||||
.base();
|
||||
return std::find_if(
|
||||
colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
|
||||
).base();
|
||||
}
|
||||
|
||||
auto Palette::begin() const -> decltype(colors)::const_iterator {
|
||||
@@ -870,9 +905,9 @@ auto Palette::begin() const -> decltype(colors)::const_iterator {
|
||||
|
||||
auto Palette::end() const -> decltype(colors)::const_iterator {
|
||||
// Same as the non-const end().
|
||||
return std::find_if(colors.rbegin(), colors.rend(),
|
||||
[](uint16_t c) { return c != UINT16_MAX; })
|
||||
.base();
|
||||
return std::find_if(
|
||||
colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
|
||||
).base();
|
||||
}
|
||||
|
||||
uint8_t Palette::size() const {
|
||||
|
||||
@@ -140,9 +140,10 @@ public:
|
||||
*/
|
||||
template<typename... Ts>
|
||||
void assign(Ts &&...args) {
|
||||
auto freeSlot = std::find_if_not(
|
||||
RANGE(_assigned),
|
||||
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); });
|
||||
auto freeSlot =
|
||||
std::find_if_not(RANGE(_assigned), [](std::optional<ProtoPalAttrs> const &slot) {
|
||||
return slot.has_value();
|
||||
});
|
||||
|
||||
if (freeSlot == _assigned.end()) { // We are full, use a new slot
|
||||
_assigned.emplace_back(std::forward<Ts>(args)...);
|
||||
@@ -158,15 +159,20 @@ public:
|
||||
bool empty() const {
|
||||
return std::find_if(
|
||||
RANGE(_assigned),
|
||||
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); })
|
||||
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); }
|
||||
)
|
||||
== _assigned.end();
|
||||
}
|
||||
size_t nbProtoPals() const { return std::distance(RANGE(*this)); }
|
||||
|
||||
private:
|
||||
template<typename Iter>
|
||||
static void addUniqueColors(std::unordered_set<uint16_t> &colors, Iter iter, Iter const &end,
|
||||
std::vector<ProtoPalette> const &protoPals) {
|
||||
static void addUniqueColors(
|
||||
std::unordered_set<uint16_t> &colors,
|
||||
Iter iter,
|
||||
Iter const &end,
|
||||
std::vector<ProtoPalette> const &protoPals
|
||||
) {
|
||||
for (; iter != end; ++iter) {
|
||||
ProtoPalette const &protoPal = protoPals[iter->protoPalIndex];
|
||||
colors.insert(RANGE(protoPal));
|
||||
@@ -226,8 +232,8 @@ public:
|
||||
* Computes the "relative size" of a set of proto-palettes on this palette
|
||||
*/
|
||||
template<typename Iter>
|
||||
auto combinedVolume(Iter &&begin, Iter const &end,
|
||||
std::vector<ProtoPalette> const &protoPals) const {
|
||||
auto combinedVolume(Iter &&begin, Iter const &end, std::vector<ProtoPalette> const &protoPals)
|
||||
const {
|
||||
auto &colors = uniqueColors();
|
||||
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
|
||||
return colors.size();
|
||||
@@ -243,8 +249,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static void decant(std::vector<AssignedProtos> &assignments,
|
||||
std::vector<ProtoPalette> const &protoPalettes) {
|
||||
static void decant(
|
||||
std::vector<AssignedProtos> &assignments, std::vector<ProtoPalette> const &protoPalettes
|
||||
) {
|
||||
// "Decanting" is the process of moving all *things* that can fit in a lower index there
|
||||
auto decantOn = [&assignments](auto const &tryDecanting) {
|
||||
// No need to attempt decanting on palette #0, as there are no palettes to decant to
|
||||
@@ -268,8 +275,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
|
||||
}
|
||||
};
|
||||
|
||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes before decanting\n",
|
||||
assignments.size());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG, "%zu palettes before decanting\n", assignments.size()
|
||||
);
|
||||
|
||||
// Decant on palettes
|
||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
||||
@@ -281,8 +289,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
|
||||
from.clear();
|
||||
}
|
||||
});
|
||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n",
|
||||
assignments.size());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n", assignments.size()
|
||||
);
|
||||
|
||||
// Decant on "components" (= proto-pals sharing colors)
|
||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
||||
@@ -331,8 +340,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
|
||||
}
|
||||
}
|
||||
});
|
||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n",
|
||||
assignments.size());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", assignments.size()
|
||||
);
|
||||
|
||||
// Decant on individual proto-palettes
|
||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
||||
@@ -343,14 +353,16 @@ static void decant(std::vector<AssignedProtos> &assignments,
|
||||
}
|
||||
}
|
||||
});
|
||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n",
|
||||
assignments.size());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n", assignments.size()
|
||||
);
|
||||
}
|
||||
|
||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT,
|
||||
"Paginating palettes using \"overload-and-remove\" strategy...\n");
|
||||
options.verbosePrint(
|
||||
Options::VERB_LOG_ACT, "Paginating palettes using \"overload-and-remove\" strategy...\n"
|
||||
);
|
||||
|
||||
// Sort the proto-palettes by size, which improves the packing algorithm's efficiency
|
||||
DefaultInitVec<size_t> sortedProtoPalIDs(protoPalettes.size());
|
||||
@@ -379,9 +391,14 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
continue;
|
||||
}
|
||||
|
||||
options.verbosePrint(Options::VERB_DEBUG, "%zu/%zu: Rel size: %f (size = %zu)\n", i + 1,
|
||||
assignments.size(), assignments[i].relSizeOf(protoPal),
|
||||
protoPal.size());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"%zu/%zu: Rel size: %f (size = %zu)\n",
|
||||
i + 1,
|
||||
assignments.size(),
|
||||
assignments[i].relSizeOf(protoPal),
|
||||
protoPal.size()
|
||||
);
|
||||
if (assignments[i].relSizeOf(protoPal) < bestRelSize) {
|
||||
bestPalIndex = i;
|
||||
}
|
||||
@@ -397,21 +414,26 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
|
||||
// If this overloads the palette, get it back to normal (if possible)
|
||||
while (bestPal.volume() > options.maxOpaqueColors()) {
|
||||
options.verbosePrint(Options::VERB_DEBUG,
|
||||
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
|
||||
bestPalIndex, bestPal.volume(), options.maxOpaqueColors());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
|
||||
bestPalIndex,
|
||||
bestPal.volume(),
|
||||
options.maxOpaqueColors()
|
||||
);
|
||||
|
||||
// Look for a proto-pal minimizing "efficiency" (size / rel_size)
|
||||
auto efficiency = [&bestPal](ProtoPalette const &pal) {
|
||||
return pal.size() / bestPal.relSizeOf(pal);
|
||||
};
|
||||
auto [minEfficiencyIter, maxEfficiencyIter] =
|
||||
std::minmax_element(RANGE(bestPal),
|
||||
[&efficiency, &protoPalettes](ProtoPalAttrs const &lhs,
|
||||
ProtoPalAttrs const &rhs) {
|
||||
return efficiency(protoPalettes[lhs.protoPalIndex])
|
||||
< efficiency(protoPalettes[rhs.protoPalIndex]);
|
||||
});
|
||||
auto [minEfficiencyIter, maxEfficiencyIter] = std::minmax_element(
|
||||
RANGE(bestPal),
|
||||
[&efficiency,
|
||||
&protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) {
|
||||
return efficiency(protoPalettes[lhs.protoPalIndex])
|
||||
< efficiency(protoPalettes[rhs.protoPalIndex]);
|
||||
}
|
||||
);
|
||||
|
||||
// All efficiencies are identical iff min equals max
|
||||
// TODO: maybe not ideal to re-compute these two?
|
||||
@@ -443,18 +465,24 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
while (!queue.empty()) {
|
||||
ProtoPalAttrs const &attrs = queue.front();
|
||||
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
|
||||
auto iter =
|
||||
std::find_if(RANGE(assignments),
|
||||
[&protoPal](AssignedProtos const &pal) { return pal.canFit(protoPal); });
|
||||
auto iter = std::find_if(RANGE(assignments), [&protoPal](AssignedProtos const &pal) {
|
||||
return pal.canFit(protoPal);
|
||||
});
|
||||
if (iter == assignments.end()) { // No such page, create a new one
|
||||
options.verbosePrint(Options::VERB_DEBUG,
|
||||
"Adding new palette (%zu) for overflowing proto-pal %zu\n",
|
||||
assignments.size(), attrs.protoPalIndex);
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Adding new palette (%zu) for overflowing proto-pal %zu\n",
|
||||
assignments.size(),
|
||||
attrs.protoPalIndex
|
||||
);
|
||||
assignments.emplace_back(protoPalettes, std::move(attrs));
|
||||
} else {
|
||||
options.verbosePrint(Options::VERB_DEBUG,
|
||||
"Assigning overflowing proto-pal %zu to palette %zu\n",
|
||||
attrs.protoPalIndex, iter - assignments.begin());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Assigning overflowing proto-pal %zu to palette %zu\n",
|
||||
attrs.protoPalIndex,
|
||||
iter - assignments.begin()
|
||||
);
|
||||
iter->assign(std::move(attrs));
|
||||
}
|
||||
queue.pop();
|
||||
|
||||
@@ -13,13 +13,20 @@
|
||||
|
||||
namespace sorting {
|
||||
|
||||
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
|
||||
int palAlphaSize, png_byte *palAlpha) {
|
||||
void indexed(
|
||||
std::vector<Palette> &palettes,
|
||||
int palSize,
|
||||
png_color const *palRGB,
|
||||
int palAlphaSize,
|
||||
png_byte *palAlpha
|
||||
) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes using embedded palette...\n");
|
||||
|
||||
auto pngToRgb = [&palRGB, &palAlphaSize, &palAlpha](int index) {
|
||||
auto const &c = palRGB[index];
|
||||
return Rgba(c.red, c.green, c.blue, palAlpha && index < palAlphaSize ? palAlpha[index] : 0xFF);
|
||||
return Rgba(
|
||||
c.red, c.green, c.blue, palAlpha && index < palAlphaSize ? palAlpha[index] : 0xFF
|
||||
);
|
||||
};
|
||||
|
||||
for (Palette &pal : palettes) {
|
||||
@@ -43,8 +50,9 @@ void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRG
|
||||
}
|
||||
}
|
||||
|
||||
void grayscale(std::vector<Palette> &palettes,
|
||||
std::array<std::optional<Rgba>, 0x8001> const &colors) {
|
||||
void grayscale(
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
|
||||
) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n");
|
||||
|
||||
// This method is only applicable if there are at most as many colors as colors per palette, so
|
||||
|
||||
@@ -66,10 +66,12 @@ void parseInlinePalSpec(char const * const rawArg) {
|
||||
assert(len <= arg.length());
|
||||
|
||||
errorMessage(msg);
|
||||
fprintf(stderr,
|
||||
"In inline palette spec: %s\n"
|
||||
" ",
|
||||
rawArg);
|
||||
fprintf(
|
||||
stderr,
|
||||
"In inline palette spec: %s\n"
|
||||
" ",
|
||||
rawArg
|
||||
);
|
||||
for (auto i = ofs; i; --i) {
|
||||
putc(' ', stderr);
|
||||
}
|
||||
@@ -97,12 +99,17 @@ void parseInlinePalSpec(char const * const rawArg) {
|
||||
auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
|
||||
switch (pos - n) {
|
||||
case 3:
|
||||
color = Rgba(singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]),
|
||||
0xFF);
|
||||
color = Rgba(
|
||||
singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]), 0xFF
|
||||
);
|
||||
break;
|
||||
case 6:
|
||||
color = Rgba(toHex(arg[n + 0], arg[n + 1]), toHex(arg[n + 2], arg[n + 3]),
|
||||
toHex(arg[n + 4], arg[n + 5]), 0xFF);
|
||||
color = Rgba(
|
||||
toHex(arg[n + 0], arg[n + 1]),
|
||||
toHex(arg[n + 2], arg[n + 3]),
|
||||
toHex(arg[n + 4], arg[n + 5]),
|
||||
0xFF
|
||||
);
|
||||
break;
|
||||
case 0:
|
||||
parseError(n - 1, 1, "Missing color after '#'");
|
||||
@@ -239,36 +246,31 @@ static std::optional<U> parseDec(std::string const &str, std::string::size_type
|
||||
return std::optional<U>{value};
|
||||
}
|
||||
|
||||
static std::optional<Rgba> parseColor(std::string const &str, std::string::size_type &n,
|
||||
uint16_t i) {
|
||||
static std::optional<Rgba>
|
||||
parseColor(std::string const &str, std::string::size_type &n, uint16_t i) {
|
||||
std::optional<uint8_t> r = parseDec<uint8_t>(str, n);
|
||||
if (!r) {
|
||||
error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1,
|
||||
str.c_str());
|
||||
error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1, str.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
skipWhitespace(str, n);
|
||||
if (n == str.length()) {
|
||||
error("Failed to parse color #%d (\"%s\"): missing green component", i + 1,
|
||||
str.c_str());
|
||||
error("Failed to parse color #%d (\"%s\"): missing green component", i + 1, str.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<uint8_t> g = parseDec<uint8_t>(str, n);
|
||||
if (!g) {
|
||||
error("Failed to parse color #%d (\"%s\"): invalid green component", i + 1,
|
||||
str.c_str());
|
||||
error("Failed to parse color #%d (\"%s\"): invalid green component", i + 1, str.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
skipWhitespace(str, n);
|
||||
if (n == str.length()) {
|
||||
error("Failed to parse color #%d (\"%s\"): missing blue component", i + 1,
|
||||
str.c_str());
|
||||
error("Failed to parse color #%d (\"%s\"): missing blue component", i + 1, str.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<uint8_t> b = parseDec<uint8_t>(str, n);
|
||||
if (!b) {
|
||||
error("Failed to parse color #%d (\"%s\"): invalid blue component", i + 1,
|
||||
str.c_str());
|
||||
error("Failed to parse color #%d (\"%s\"): invalid blue component", i + 1, str.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -301,10 +303,14 @@ static void parsePSPFile(std::filebuf &file) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; *nbColors > nbPalColors) {
|
||||
warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
*nbColors, nbPalColors);
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
|
||||
*nbColors > nbPalColors) {
|
||||
warning(
|
||||
"PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
*nbColors,
|
||||
nbPalColors
|
||||
);
|
||||
nbColors = nbPalColors;
|
||||
}
|
||||
|
||||
@@ -320,8 +326,11 @@ static void parsePSPFile(std::filebuf &file) {
|
||||
return;
|
||||
}
|
||||
if (n != line.length()) {
|
||||
error("Failed to parse color #%d (\"%s\"): trailing characters after blue component",
|
||||
i + 1, line.c_str());
|
||||
error(
|
||||
"Failed to parse color #%d (\"%s\"): trailing characters after blue component",
|
||||
i + 1,
|
||||
line.c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -372,9 +381,12 @@ static void parseGPLFile(std::filebuf &file) {
|
||||
}
|
||||
|
||||
if (nbColors > maxNbColors) {
|
||||
warning("GPL file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors, maxNbColors);
|
||||
warning(
|
||||
"GPL file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors,
|
||||
maxNbColors
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,8 +405,11 @@ static void parseHEXFile(std::filebuf &file) {
|
||||
|
||||
if (line.length() != 6
|
||||
|| line.find_first_not_of("0123456789ABCDEFabcdef"sv) != std::string::npos) {
|
||||
error("Failed to parse color #%d (\"%s\"): invalid \"rrggbb\" line",
|
||||
nbColors + 1, line.c_str());
|
||||
error(
|
||||
"Failed to parse color #%d (\"%s\"): invalid \"rrggbb\" line",
|
||||
nbColors + 1,
|
||||
line.c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -411,9 +426,12 @@ static void parseHEXFile(std::filebuf &file) {
|
||||
}
|
||||
|
||||
if (nbColors > maxNbColors) {
|
||||
warning("HEX file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors, maxNbColors);
|
||||
warning(
|
||||
"HEX file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors,
|
||||
maxNbColors
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,10 +454,14 @@ static void parseACTFile(std::filebuf &file) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; nbColors > nbPalColors) {
|
||||
warning("ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors, nbPalColors);
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
|
||||
nbColors > nbPalColors) {
|
||||
warning(
|
||||
"ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors,
|
||||
nbPalColors
|
||||
);
|
||||
nbColors = nbPalColors;
|
||||
}
|
||||
|
||||
@@ -486,10 +508,14 @@ static void parseACOFile(std::filebuf &file) {
|
||||
}
|
||||
uint16_t nbColors = readBE<uint16_t>(buf);
|
||||
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; nbColors > nbPalColors) {
|
||||
warning("ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors, nbPalColors);
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
|
||||
nbColors > nbPalColors) {
|
||||
warning(
|
||||
"ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors,
|
||||
nbPalColors
|
||||
);
|
||||
nbColors = nbPalColors;
|
||||
}
|
||||
|
||||
@@ -543,16 +569,22 @@ static void parseGBCFile(std::filebuf &file) {
|
||||
if (len == 0) {
|
||||
break;
|
||||
} else if (len != sizeof(buf)) {
|
||||
error("GBC palette dump contains %zu 8-byte palette%s, plus %zu byte%s",
|
||||
options.palSpec.size(), options.palSpec.size() == 1 ? "" : "s", len,
|
||||
len == 1 ? "" : "s");
|
||||
error(
|
||||
"GBC palette dump contains %zu 8-byte palette%s, plus %zu byte%s",
|
||||
options.palSpec.size(),
|
||||
options.palSpec.size() == 1 ? "" : "s",
|
||||
len,
|
||||
len == 1 ? "" : "s"
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
options.palSpec.push_back({Rgba::fromCGBColor(readLE<uint16_t>(&buf[0])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[2])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[4])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[6]))});
|
||||
options.palSpec.push_back(
|
||||
{Rgba::fromCGBColor(readLE<uint16_t>(&buf[0])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[2])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[4])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[6]))}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,14 +608,16 @@ void parseExternalPalSpec(char const *arg) {
|
||||
std::tuple{"GBC", &parseGBCFile, std::ios::binary},
|
||||
};
|
||||
|
||||
auto iter = std::find_if(RANGE(parsers),
|
||||
[&arg, &ptr](decltype(parsers)::value_type const &parser) {
|
||||
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
|
||||
});
|
||||
auto iter =
|
||||
std::find_if(RANGE(parsers), [&arg, &ptr](decltype(parsers)::value_type const &parser) {
|
||||
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
|
||||
});
|
||||
if (iter == parsers.end()) {
|
||||
error("Unknown external palette format \"%.*s\"",
|
||||
static_cast<int>(std::min(ptr - arg, static_cast<decltype(ptr - arg)>(INT_MAX))),
|
||||
arg);
|
||||
error(
|
||||
"Unknown external palette format \"%.*s\"",
|
||||
static_cast<int>(std::min(ptr - arg, static_cast<decltype(ptr - arg)>(INT_MAX))),
|
||||
arg
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,10 +59,9 @@ public:
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return std::count_if(RANGE(_colors),
|
||||
[](decltype(_colors)::value_type const &slot) {
|
||||
return slot.has_value() && !slot->isTransparent();
|
||||
});
|
||||
return std::count_if(RANGE(_colors), [](decltype(_colors)::value_type const &slot) {
|
||||
return slot.has_value() && !slot->isTransparent();
|
||||
});
|
||||
}
|
||||
decltype(_colors) const &raw() const { return _colors; }
|
||||
|
||||
@@ -105,10 +104,13 @@ class Png {
|
||||
self->file->sgetn(reinterpret_cast<char *>(data), expectedLen);
|
||||
|
||||
if (nbBytesRead != expectedLen) {
|
||||
fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more "
|
||||
"bytes after reading %zu)",
|
||||
self->c_str(), length - nbBytesRead,
|
||||
(size_t)self->file->pubseekoff(0, std::ios_base::cur));
|
||||
fatal(
|
||||
"Error reading input image (\"%s\"): file too short (expected at least %zd more "
|
||||
"bytes after reading %zu)",
|
||||
self->c_str(),
|
||||
length - nbBytesRead,
|
||||
(size_t)self->file->pubseekoff(0, std::ios_base::cur)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,9 +136,12 @@ public:
|
||||
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,
|
||||
"Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n",
|
||||
colors.size(), options.maxOpaqueColors());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n",
|
||||
colors.size(),
|
||||
options.maxOpaqueColors()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
uint8_t bins = 0;
|
||||
@@ -145,9 +150,11 @@ public:
|
||||
continue;
|
||||
}
|
||||
if (!color->isGray()) {
|
||||
options.verbosePrint(Options::VERB_DEBUG,
|
||||
"Found non-gray color #%08x, not using grayscale sorting\n",
|
||||
color->toCSS());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Found non-gray color #%08x, not using grayscale sorting\n",
|
||||
color->toCSS()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
uint8_t mask = 1 << color->grayIndex();
|
||||
@@ -155,7 +162,8 @@ public:
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Color #%08x conflicts with another one, not using grayscale sorting\n",
|
||||
color->toCSS());
|
||||
color->toCSS()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
bins |= mask;
|
||||
@@ -174,8 +182,7 @@ public:
|
||||
*/
|
||||
explicit Png(std::string const &filePath) : path(filePath), colors() {
|
||||
if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) {
|
||||
fatal("Failed to open input image (\"%s\"): %s", file.c_str(path),
|
||||
strerror(errno));
|
||||
fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno));
|
||||
}
|
||||
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Opened input file\n");
|
||||
@@ -190,8 +197,9 @@ public:
|
||||
|
||||
options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n");
|
||||
|
||||
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError,
|
||||
handleWarning);
|
||||
png = png_create_read_struct(
|
||||
PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError, handleWarning
|
||||
);
|
||||
if (!png) {
|
||||
fatal("Failed to allocate PNG structure: %s", strerror(errno));
|
||||
}
|
||||
@@ -215,8 +223,9 @@ public:
|
||||
|
||||
int bitDepth, interlaceType; //, compressionType, filterMethod;
|
||||
|
||||
png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr,
|
||||
nullptr);
|
||||
png_get_IHDR(
|
||||
png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr, nullptr
|
||||
);
|
||||
|
||||
if (options.inputSlice.width == 0 && width % 8 != 0) {
|
||||
fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8!", width);
|
||||
@@ -253,23 +262,35 @@ public:
|
||||
fatal("Unknown interlace type %d", interlaceType);
|
||||
}
|
||||
};
|
||||
options.verbosePrint(Options::VERB_INTERM,
|
||||
"Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n", width,
|
||||
height, bitDepth, colorTypeName(), interlaceTypeName());
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM,
|
||||
"Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n",
|
||||
width,
|
||||
height,
|
||||
bitDepth,
|
||||
colorTypeName(),
|
||||
interlaceTypeName()
|
||||
);
|
||||
|
||||
if (png_get_PLTE(png, info, &embeddedPal, &nbColors) != 0) {
|
||||
if (png_get_tRNS(png, info, &transparencyPal, &nbTransparentEntries, nullptr)) {
|
||||
assert(nbTransparentEntries <= nbColors);
|
||||
}
|
||||
|
||||
options.verbosePrint(Options::VERB_INTERM, "Embedded palette has %d colors: [",
|
||||
nbColors);
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM, "Embedded palette has %d colors: [", nbColors
|
||||
);
|
||||
for (int i = 0; i < nbColors; ++i) {
|
||||
auto const &color = embeddedPal[i];
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM, "#%02x%02x%02x%02x%s", color.red, color.green, color.blue,
|
||||
Options::VERB_INTERM,
|
||||
"#%02x%02x%02x%02x%s",
|
||||
color.red,
|
||||
color.green,
|
||||
color.blue,
|
||||
transparencyPal && i < nbTransparentEntries ? transparencyPal[i] : 0xFF,
|
||||
i != nbColors - 1 ? ", " : "]\n");
|
||||
i != nbColors - 1 ? ", " : "]\n"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
options.verbosePrint(Options::VERB_INTERM, "No embedded palette\n");
|
||||
@@ -329,40 +350,51 @@ public:
|
||||
std::vector<uint32_t> indeterminates;
|
||||
|
||||
// Assign a color to the given position, and register it in the image palette as well
|
||||
auto assignColor = [this, &conflicts, &indeterminates](png_uint_32 x, png_uint_32 y,
|
||||
Rgba &&color) {
|
||||
if (!color.isTransparent() && !color.isOpaque()) {
|
||||
uint32_t css = color.toCSS();
|
||||
if (std::find(RANGE(indeterminates), css)
|
||||
== indeterminates.end()) {
|
||||
error("Color #%08x is neither transparent (alpha < %u) nor opaque (alpha >= "
|
||||
"%u) [first seen at x: %" PRIu32 ", y: %" PRIu32 "]",
|
||||
css, Rgba::transparency_threshold, Rgba::opacity_threshold, x, y);
|
||||
indeterminates.push_back(css);
|
||||
}
|
||||
} else if (Rgba const *other = colors.registerColor(color); other) {
|
||||
std::tuple conflicting{color.toCSS(), other->toCSS()};
|
||||
// Do not report combinations twice
|
||||
if (std::find(RANGE(conflicts), conflicting) == conflicts.end()) {
|
||||
warning("Fusing colors #%08x and #%08x into Game Boy color $%04x [first seen "
|
||||
auto assignColor =
|
||||
[this, &conflicts, &indeterminates](png_uint_32 x, png_uint_32 y, Rgba &&color) {
|
||||
if (!color.isTransparent() && !color.isOpaque()) {
|
||||
uint32_t css = color.toCSS();
|
||||
if (std::find(RANGE(indeterminates), css) == indeterminates.end()) {
|
||||
error(
|
||||
"Color #%08x is neither transparent (alpha < %u) nor opaque (alpha >= "
|
||||
"%u) [first seen at x: %" PRIu32 ", y: %" PRIu32 "]",
|
||||
css,
|
||||
Rgba::transparency_threshold,
|
||||
Rgba::opacity_threshold,
|
||||
x,
|
||||
y
|
||||
);
|
||||
indeterminates.push_back(css);
|
||||
}
|
||||
} else if (Rgba const *other = colors.registerColor(color); other) {
|
||||
std::tuple conflicting{color.toCSS(), other->toCSS()};
|
||||
// Do not report combinations twice
|
||||
if (std::find(RANGE(conflicts), conflicting) == conflicts.end()) {
|
||||
warning(
|
||||
"Fusing colors #%08x and #%08x into Game Boy color $%04x [first seen "
|
||||
"at x: %" PRIu32 ", y: %" PRIu32 "]",
|
||||
std::get<0>(conflicting), std::get<1>(conflicting), color.cgbColor(), x,
|
||||
y);
|
||||
// Do not report this combination again
|
||||
conflicts.emplace_back(conflicting);
|
||||
}
|
||||
}
|
||||
std::get<0>(conflicting),
|
||||
std::get<1>(conflicting),
|
||||
color.cgbColor(),
|
||||
x,
|
||||
y
|
||||
);
|
||||
// Do not report this combination again
|
||||
conflicts.emplace_back(conflicting);
|
||||
}
|
||||
}
|
||||
|
||||
pixel(x, y) = color;
|
||||
};
|
||||
pixel(x, y) = color;
|
||||
};
|
||||
|
||||
if (interlaceType == PNG_INTERLACE_NONE) {
|
||||
for (png_uint_32 y = 0; y < height; ++y) {
|
||||
png_read_row(png, row.data(), nullptr);
|
||||
|
||||
for (png_uint_32 x = 0; x < width; ++x) {
|
||||
assignColor(x, y,
|
||||
Rgba(row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3]));
|
||||
assignColor(
|
||||
x, y, Rgba(row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3])
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -454,14 +486,17 @@ public:
|
||||
iterator begin() const { return {*this, _limit, 0, 0}; }
|
||||
iterator end() const {
|
||||
iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one...
|
||||
return ++it; // ...now one-past-last!
|
||||
return ++it; // ...now one-past-last!
|
||||
}
|
||||
};
|
||||
public:
|
||||
TilesVisitor visitAsTiles() const {
|
||||
return {*this, options.columnMajor,
|
||||
options.inputSlice.width ? options.inputSlice.width * 8 : width,
|
||||
options.inputSlice.height ? options.inputSlice.height * 8 : height};
|
||||
return {
|
||||
*this,
|
||||
options.columnMajor,
|
||||
options.inputSlice.width ? options.inputSlice.width * 8 : width,
|
||||
options.inputSlice.height ? options.inputSlice.height * 8 : height,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -497,7 +532,7 @@ struct AttrmapEntry {
|
||||
* attrmap entry while correctly handling the above, use `getPalID`.
|
||||
*/
|
||||
size_t protoPaletteID; // Only this field is used when outputting "unoptimized" data
|
||||
uint8_t tileID; // This is the ID as it will be output to the tilemap
|
||||
uint8_t tileID; // This is the ID as it will be output to the tilemap
|
||||
bool bank;
|
||||
bool yFlip;
|
||||
bool xFlip;
|
||||
@@ -523,8 +558,12 @@ static void generatePalSpec(Png const &png) {
|
||||
embPalSize = options.maxOpaqueColors();
|
||||
}
|
||||
for (int i = 0; i < embPalSize; ++i) {
|
||||
options.palSpec[0][i] = Rgba(embPalRGB[i].red, embPalRGB[i].green, embPalRGB[i].blue,
|
||||
embPalAlpha && i < embPalAlphaSize ? embPalAlpha[i] : 0xFF);
|
||||
options.palSpec[0][i] = Rgba(
|
||||
embPalRGB[i].red,
|
||||
embPalRGB[i].green,
|
||||
embPalRGB[i].blue,
|
||||
embPalAlpha && i < embPalAlphaSize ? embPalAlpha[i] : 0xFF
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,8 +575,12 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||
assert(mappings.size() == protoPalettes.size());
|
||||
|
||||
if (options.verbosity >= Options::VERB_INTERM) {
|
||||
fprintf(stderr, "Proto-palette mappings: (%zu palette%s)\n", nbPalettes,
|
||||
nbPalettes != 1 ? "s" : "");
|
||||
fprintf(
|
||||
stderr,
|
||||
"Proto-palette mappings: (%zu palette%s)\n",
|
||||
nbPalettes,
|
||||
nbPalettes != 1 ? "s" : ""
|
||||
);
|
||||
for (size_t i = 0; i < mappings.size(); ++i) {
|
||||
fprintf(stderr, "%zu -> %zu\n", i, mappings[i]);
|
||||
}
|
||||
@@ -615,8 +658,11 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||
mappings[i] = iter - palettes.begin(); // Bogus value, but whatever
|
||||
}
|
||||
if (bad) {
|
||||
fprintf(stderr, "note: The following palette%s specified:\n",
|
||||
palettes.size() == 1 ? " was" : "s were");
|
||||
fprintf(
|
||||
stderr,
|
||||
"note: The following palette%s specified:\n",
|
||||
palettes.size() == 1 ? " was" : "s were"
|
||||
);
|
||||
for (Palette const &pal : palettes) {
|
||||
fprintf(stderr, " [%s]\n", listColors(pal));
|
||||
}
|
||||
@@ -640,15 +686,17 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||
if (palettes.size() > options.nbPalettes) {
|
||||
// If the palette generation is wrong, other (dependee) operations are likely to be
|
||||
// nonsensical, so fatal-error outright
|
||||
fatal("Generated %zu palettes, over the maximum of %" PRIu8, palettes.size(),
|
||||
options.nbPalettes);
|
||||
fatal(
|
||||
"Generated %zu palettes, over the maximum of %" PRIu8,
|
||||
palettes.size(),
|
||||
options.nbPalettes
|
||||
);
|
||||
}
|
||||
|
||||
if (!options.palettes.empty()) {
|
||||
File output;
|
||||
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes),
|
||||
strerror(errno));
|
||||
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), strerror(errno));
|
||||
}
|
||||
|
||||
for (Palette const &palette : palettes) {
|
||||
@@ -675,8 +723,8 @@ public:
|
||||
// of altering the element's hash, but the tile ID is not part of it.
|
||||
mutable uint16_t tileID;
|
||||
|
||||
static uint16_t rowBitplanes(Png::TilesVisitor::Tile const &tile, Palette const &palette,
|
||||
uint32_t y) {
|
||||
static uint16_t
|
||||
rowBitplanes(Png::TilesVisitor::Tile const &tile, Palette const &palette, uint32_t y) {
|
||||
uint16_t row = 0;
|
||||
for (uint32_t x = 0; x < 8; ++x) {
|
||||
row <<= 1;
|
||||
@@ -734,8 +782,9 @@ public:
|
||||
}
|
||||
|
||||
// Check if we have horizontal mirroring, which scans the array forward again
|
||||
if (std::equal(RANGE(_data), other._data.begin(),
|
||||
[](uint8_t lhs, uint8_t rhs) { return lhs == flipTable[rhs]; })) {
|
||||
if (std::equal(RANGE(_data), other._data.begin(), [](uint8_t lhs, uint8_t rhs) {
|
||||
return lhs == flipTable[rhs];
|
||||
})) {
|
||||
return MatchType::HFLIP;
|
||||
}
|
||||
|
||||
@@ -773,16 +822,20 @@ struct std::hash<TileData> {
|
||||
|
||||
namespace unoptimized {
|
||||
|
||||
static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
std::vector<Palette> const &palettes,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
static void outputTileData(
|
||||
Png const &png,
|
||||
DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
std::vector<Palette> const &palettes,
|
||||
DefaultInitVec<size_t> const &mappings
|
||||
) {
|
||||
File output;
|
||||
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno));
|
||||
}
|
||||
|
||||
uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8;
|
||||
uint16_t heightTiles = options.inputSlice.height ? options.inputSlice.height : png.getHeight() / 8;
|
||||
uint16_t heightTiles =
|
||||
options.inputSlice.height ? options.inputSlice.height : png.getHeight() / 8;
|
||||
uint64_t remainingTiles = widthTiles * heightTiles;
|
||||
if (remainingTiles <= options.trim) {
|
||||
return;
|
||||
@@ -808,15 +861,15 @@ static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &a
|
||||
assert(remainingTiles == 0);
|
||||
}
|
||||
|
||||
static void outputMaps(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
static void outputMaps(
|
||||
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
|
||||
) {
|
||||
std::optional<File> tilemapOutput, attrmapOutput, palmapOutput;
|
||||
auto autoOpenPath = [](std::string const &path, std::optional<File> &file) {
|
||||
if (!path.empty()) {
|
||||
file.emplace();
|
||||
if (!file->open(path, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap),
|
||||
strerror(errno));
|
||||
fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -864,8 +917,8 @@ struct UniqueTiles {
|
||||
/*
|
||||
* Adds a tile to the collection, and returns its ID
|
||||
*/
|
||||
std::tuple<uint16_t, TileData::MatchType> addTile(Png::TilesVisitor::Tile const &tile,
|
||||
Palette const &palette) {
|
||||
std::tuple<uint16_t, TileData::MatchType>
|
||||
addTile(Png::TilesVisitor::Tile const &tile, Palette const &palette) {
|
||||
TileData newTile(tile, palette);
|
||||
auto [tileData, inserted] = tileset.insert(newTile);
|
||||
|
||||
@@ -893,9 +946,12 @@ struct UniqueTiles {
|
||||
* 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
|
||||
* twice)
|
||||
*/
|
||||
static UniqueTiles dedupTiles(Png const &png, DefaultInitVec<AttrmapEntry> &attrmap,
|
||||
std::vector<Palette> const &palettes,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
static UniqueTiles dedupTiles(
|
||||
Png const &png,
|
||||
DefaultInitVec<AttrmapEntry> &attrmap,
|
||||
std::vector<Palette> const &palettes,
|
||||
DefaultInitVec<size_t> const &mappings
|
||||
) {
|
||||
// Iterate throughout the image, generating tile data as we go
|
||||
// (We don't need the full tile data to be able to dedup tiles, but we don't lose anything
|
||||
// by caching the full tile data anyway, so we might as well.)
|
||||
@@ -941,8 +997,9 @@ static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) {
|
||||
}
|
||||
}
|
||||
|
||||
static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
static void outputAttrmap(
|
||||
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
|
||||
) {
|
||||
File output;
|
||||
if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
|
||||
@@ -956,8 +1013,9 @@ static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
}
|
||||
}
|
||||
|
||||
static void outputPalmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
static void outputPalmap(
|
||||
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
|
||||
) {
|
||||
File output;
|
||||
if (!output.open(options.palmap, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.palmap), strerror(errno));
|
||||
@@ -1063,21 +1121,33 @@ void process() {
|
||||
}
|
||||
|
||||
if (nbColorsInTile > options.maxOpaqueColors()) {
|
||||
fatal("Tile at (%" PRIu32 ", %" PRIu32 ") has %" PRIu8 " opaque colors, more than %" PRIu8 "!",
|
||||
tile.x, tile.y, nbColorsInTile, options.maxOpaqueColors());
|
||||
fatal(
|
||||
"Tile at (%" PRIu32 ", %" PRIu32 ") has %" PRIu8 " opaque colors, more than %" PRIu8
|
||||
"!",
|
||||
tile.x,
|
||||
tile.y,
|
||||
nbColorsInTile,
|
||||
options.maxOpaqueColors()
|
||||
);
|
||||
}
|
||||
|
||||
attrs.protoPaletteID = protoPalettes.size();
|
||||
if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow
|
||||
fatal("Reached %zu proto-palettes... sorry, this image is too much for me to handle :(",
|
||||
AttrmapEntry::transparent);
|
||||
fatal(
|
||||
"Reached %zu proto-palettes... sorry, this image is too much for me to handle :(",
|
||||
AttrmapEntry::transparent
|
||||
);
|
||||
}
|
||||
protoPalettes.push_back(tileColors);
|
||||
contained:;
|
||||
}
|
||||
|
||||
options.verbosePrint(Options::VERB_INTERM, "Image contains %zu proto-palette%s\n",
|
||||
protoPalettes.size(), protoPalettes.size() != 1 ? "s" : "");
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM,
|
||||
"Image contains %zu proto-palette%s\n",
|
||||
protoPalettes.size(),
|
||||
protoPalettes.size() != 1 ? "s" : ""
|
||||
);
|
||||
if (options.verbosity >= Options::VERB_INTERM) {
|
||||
for (auto const &protoPal : protoPalettes) {
|
||||
fputs("[ ", stderr);
|
||||
@@ -1102,8 +1172,12 @@ contained:;
|
||||
|
||||
// Check the tile count
|
||||
if (nbTilesW * nbTilesH > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
||||
fatal("Image contains %" PRIu32 " tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
|
||||
nbTilesW * nbTilesH, options.maxNbTiles[0], options.maxNbTiles[1]);
|
||||
fatal(
|
||||
"Image contains %" PRIu32 " tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
|
||||
nbTilesW * nbTilesH,
|
||||
options.maxNbTiles[0],
|
||||
options.maxNbTiles[1]
|
||||
);
|
||||
}
|
||||
|
||||
if (!options.output.empty()) {
|
||||
@@ -1114,7 +1188,8 @@ contained:;
|
||||
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");
|
||||
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n"
|
||||
);
|
||||
unoptimized::outputMaps(attrmap, mappings);
|
||||
}
|
||||
} else {
|
||||
@@ -1123,8 +1198,12 @@ contained:;
|
||||
optimized::UniqueTiles tiles = optimized::dedupTiles(png, attrmap, palettes, mappings);
|
||||
|
||||
if (tiles.size() > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
||||
fatal("Image contains %zu tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
|
||||
tiles.size(), options.maxNbTiles[0], options.maxNbTiles[1]);
|
||||
fatal(
|
||||
"Image contains %zu tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
|
||||
tiles.size(),
|
||||
options.maxNbTiles[0],
|
||||
options.maxNbTiles[1]
|
||||
);
|
||||
}
|
||||
|
||||
if (!options.output.empty()) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "gfx/proto_palette.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -10,6 +8,8 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
bool ProtoPalette::add(uint16_t color) {
|
||||
size_t i = 0;
|
||||
|
||||
|
||||
@@ -53,13 +53,19 @@ static DefaultInitVec<uint8_t> readInto(const std::string &path) {
|
||||
}
|
||||
|
||||
[[noreturn]] static void pngError(png_structp png, char const *msg) {
|
||||
fatal("Error writing reversed image (\"%s\"): %s",
|
||||
static_cast<char const *>(png_get_error_ptr(png)), msg);
|
||||
fatal(
|
||||
"Error writing reversed image (\"%s\"): %s",
|
||||
static_cast<char const *>(png_get_error_ptr(png)),
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
static void pngWarning(png_structp png, char const *msg) {
|
||||
warning("While writing reversed image (\"%s\"): %s",
|
||||
static_cast<char const *>(png_get_error_ptr(png)), msg);
|
||||
warning(
|
||||
"While writing reversed image (\"%s\"): %s",
|
||||
static_cast<char const *>(png_get_error_ptr(png)),
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
void writePng(png_structp png, png_bytep data, size_t length) {
|
||||
@@ -94,17 +100,23 @@ void reverse() {
|
||||
warning("\"Sliced-off\" pixels are ignored in reverse mode");
|
||||
}
|
||||
if (options.inputSlice.width != 0 && options.inputSlice.width != options.reversedWidth * 8) {
|
||||
warning("Specified input slice width (%" PRIu16
|
||||
") doesn't match provided reversing width (%" PRIu16 " * 8)",
|
||||
options.inputSlice.width, options.reversedWidth);
|
||||
warning(
|
||||
"Specified input slice width (%" PRIu16
|
||||
") doesn't match provided reversing width (%" PRIu16 " * 8)",
|
||||
options.inputSlice.width,
|
||||
options.reversedWidth
|
||||
);
|
||||
}
|
||||
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n");
|
||||
auto const tiles = readInto(options.output);
|
||||
uint8_t tileSize = 8 * options.bitDepth;
|
||||
if (tiles.size() % tileSize != 0) {
|
||||
fatal("Tile data size (%zu bytes) is not a multiple of %" PRIu8 " bytes",
|
||||
tiles.size(), tileSize);
|
||||
fatal(
|
||||
"Tile data size (%zu bytes) is not a multiple of %" PRIu8 " bytes",
|
||||
tiles.size(),
|
||||
tileSize
|
||||
);
|
||||
}
|
||||
|
||||
// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
|
||||
@@ -121,25 +133,33 @@ void reverse() {
|
||||
fatal("Cannot generate empty image");
|
||||
}
|
||||
if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
||||
warning("Read %zu tiles, more than the limit of %" PRIu16 " + %" PRIu16,
|
||||
nbTileInstances, options.maxNbTiles[0], options.maxNbTiles[1]);
|
||||
warning(
|
||||
"Read %zu tiles, more than the limit of %" PRIu16 " + %" PRIu16,
|
||||
nbTileInstances,
|
||||
options.maxNbTiles[0],
|
||||
options.maxNbTiles[1]
|
||||
);
|
||||
}
|
||||
|
||||
size_t width = options.reversedWidth, height; // In tiles
|
||||
if (nbTileInstances % width != 0) {
|
||||
fatal("Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
|
||||
nbTileInstances, width);
|
||||
fatal(
|
||||
"Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
|
||||
nbTileInstances,
|
||||
width
|
||||
);
|
||||
}
|
||||
height = nbTileInstances / width;
|
||||
|
||||
options.verbosePrint(Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width,
|
||||
height);
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height
|
||||
);
|
||||
|
||||
// TODO: `-U` to configure tile size beyond 8x8px ("deduplication units")
|
||||
|
||||
std::vector<std::array<std::optional<Rgba>, 4>> palettes{
|
||||
{Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)}
|
||||
};
|
||||
};
|
||||
// If a palette file is used as input, it overrides the default colors.
|
||||
if (!options.palettes.empty()) {
|
||||
File file;
|
||||
@@ -155,27 +175,37 @@ void reverse() {
|
||||
if (nbRead == buf.size()) {
|
||||
// Expand the colors
|
||||
auto &palette = palettes.emplace_back();
|
||||
std::generate(palette.begin(), palette.begin() + options.nbColorsPerPal,
|
||||
[&buf, i = 0]() mutable {
|
||||
i += 2;
|
||||
return Rgba::fromCGBColor(buf[i - 2] + (buf[i - 1] << 8));
|
||||
});
|
||||
std::generate(
|
||||
palette.begin(),
|
||||
palette.begin() + options.nbColorsPerPal,
|
||||
[&buf, i = 0]() mutable {
|
||||
i += 2;
|
||||
return Rgba::fromCGBColor(buf[i - 2] + (buf[i - 1] << 8));
|
||||
}
|
||||
);
|
||||
} else if (nbRead != 0) {
|
||||
fatal("Palette data size (%zu) is not a multiple of %zu bytes!\n",
|
||||
palettes.size() * buf.size() + nbRead, buf.size());
|
||||
fatal(
|
||||
"Palette data size (%zu) is not a multiple of %zu bytes!\n",
|
||||
palettes.size() * buf.size() + nbRead,
|
||||
buf.size()
|
||||
);
|
||||
}
|
||||
} while (nbRead != 0);
|
||||
|
||||
if (palettes.size() > options.nbPalettes) {
|
||||
warning("Read %zu palettes, more than the specified limit of %" PRIu8,
|
||||
palettes.size(), options.nbPalettes);
|
||||
warning(
|
||||
"Read %zu palettes, more than the specified limit of %" PRIu8,
|
||||
palettes.size(),
|
||||
options.nbPalettes
|
||||
);
|
||||
}
|
||||
|
||||
if (options.palSpecType == Options::EXPLICIT && palettes != options.palSpec) {
|
||||
warning("Colors in the palette file do not match those specified with `-c`!");
|
||||
}
|
||||
} else if (options.palSpecType == Options::EMBEDDED) {
|
||||
warning("An embedded palette was requested, but no palette file was specified; ignoring request.");
|
||||
warning("An embedded palette was requested, but no palette file was specified; ignoring "
|
||||
"request.");
|
||||
} else if (options.palSpecType == Options::EXPLICIT) {
|
||||
palettes = std::move(options.palSpec); // We won't be using it again.
|
||||
}
|
||||
@@ -184,8 +214,11 @@ void reverse() {
|
||||
if (!options.attrmap.empty()) {
|
||||
attrmap = readInto(options.attrmap);
|
||||
if (attrmap->size() != nbTileInstances) {
|
||||
fatal("Attribute map size (%zu tiles) doesn't match image's (%zu)", attrmap->size(),
|
||||
nbTileInstances);
|
||||
fatal(
|
||||
"Attribute map size (%zu tiles) doesn't match image's (%zu)",
|
||||
attrmap->size(),
|
||||
nbTileInstances
|
||||
);
|
||||
}
|
||||
|
||||
// Scan through the attributes for inconsistencies
|
||||
@@ -195,8 +228,9 @@ void reverse() {
|
||||
bool bad = false;
|
||||
for (auto attr : *attrmap) {
|
||||
if ((attr & 0b111) > palettes.size()) {
|
||||
error("Referencing palette %u, but there are only %zu!",
|
||||
attr & 0b111, palettes.size());
|
||||
error(
|
||||
"Referencing palette %u, but there are only %zu!", attr & 0b111, palettes.size()
|
||||
);
|
||||
bad = true;
|
||||
}
|
||||
if (attr & 0x08 && !tilemap) {
|
||||
@@ -213,16 +247,22 @@ void reverse() {
|
||||
for (auto [id, attr] : zip(*tilemap, *attrmap)) {
|
||||
bool bank = attr & 1 << 3;
|
||||
if (id >= options.maxNbTiles[bank]) {
|
||||
warning("Tile #%" PRIu8
|
||||
" was referenced, but the limit for bank %u is %" PRIu16,
|
||||
id, bank, options.maxNbTiles[bank]);
|
||||
warning(
|
||||
"Tile #%" PRIu8 " was referenced, but the limit for bank %u is %" PRIu16,
|
||||
id,
|
||||
bank,
|
||||
options.maxNbTiles[bank]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto id : *tilemap) {
|
||||
if (id >= options.maxNbTiles[0]) {
|
||||
warning("Tile #%" PRIu8 " was referenced, but the limit is %" PRIu16, id,
|
||||
options.maxNbTiles[0]);
|
||||
warning(
|
||||
"Tile #%" PRIu8 " was referenced, but the limit is %" PRIu16,
|
||||
id,
|
||||
options.maxNbTiles[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,8 +272,11 @@ void reverse() {
|
||||
if (!options.palmap.empty()) {
|
||||
palmap = readInto(options.palmap);
|
||||
if (palmap->size() != nbTileInstances) {
|
||||
fatal("Palette map size (%zu tiles) doesn't match image's (%zu)", palmap->size(),
|
||||
nbTileInstances);
|
||||
fatal(
|
||||
"Palette map size (%zu tiles) doesn't match image's (%zu)",
|
||||
palmap->size(),
|
||||
nbTileInstances
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,8 +287,10 @@ void reverse() {
|
||||
}
|
||||
png_structp png = png_create_write_struct(
|
||||
PNG_LIBPNG_VER_STRING,
|
||||
const_cast<png_voidp>(static_cast<void const *>(pngFile.c_str(options.input))), pngError,
|
||||
pngWarning);
|
||||
const_cast<png_voidp>(static_cast<void const *>(pngFile.c_str(options.input))),
|
||||
pngError,
|
||||
pngWarning
|
||||
);
|
||||
if (!png) {
|
||||
fatal("Failed to create PNG write struct: %s", strerror(errno));
|
||||
}
|
||||
@@ -255,8 +300,17 @@ void reverse() {
|
||||
}
|
||||
png_set_write_fn(png, &pngFile, writePng, flushPng);
|
||||
|
||||
png_set_IHDR(png, pngInfo, options.reversedWidth * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA,
|
||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
png_set_IHDR(
|
||||
png,
|
||||
pngInfo,
|
||||
options.reversedWidth * 8,
|
||||
height * 8,
|
||||
8,
|
||||
PNG_COLOR_TYPE_RGB_ALPHA,
|
||||
PNG_INTERLACE_NONE,
|
||||
PNG_COMPRESSION_TYPE_DEFAULT,
|
||||
PNG_FILTER_TYPE_DEFAULT
|
||||
);
|
||||
png_write_info(png, pngInfo);
|
||||
|
||||
png_color_8 sbitChunk;
|
||||
@@ -270,10 +324,14 @@ void reverse() {
|
||||
size_t const SIZEOF_ROW = options.reversedWidth * 8 * SIZEOF_PIXEL;
|
||||
std::vector<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels
|
||||
uint8_t * const rowPtrs[8] = {
|
||||
&tileRow.data()[0 * SIZEOF_ROW], &tileRow.data()[1 * SIZEOF_ROW],
|
||||
&tileRow.data()[2 * SIZEOF_ROW], &tileRow.data()[3 * SIZEOF_ROW],
|
||||
&tileRow.data()[4 * SIZEOF_ROW], &tileRow.data()[5 * SIZEOF_ROW],
|
||||
&tileRow.data()[6 * SIZEOF_ROW], &tileRow.data()[7 * SIZEOF_ROW],
|
||||
&tileRow.data()[0 * SIZEOF_ROW],
|
||||
&tileRow.data()[1 * SIZEOF_ROW],
|
||||
&tileRow.data()[2 * SIZEOF_ROW],
|
||||
&tileRow.data()[3 * SIZEOF_ROW],
|
||||
&tileRow.data()[4 * SIZEOF_ROW],
|
||||
&tileRow.data()[5 * SIZEOF_ROW],
|
||||
&tileRow.data()[6 * SIZEOF_ROW],
|
||||
&tileRow.data()[7 * SIZEOF_ROW],
|
||||
};
|
||||
|
||||
for (size_t ty = 0; ty < height; ++ty) {
|
||||
@@ -294,8 +352,22 @@ void reverse() {
|
||||
|
||||
// We do not have data for tiles trimmed with `-x`, so assume they are "blank"
|
||||
static std::array<uint8_t, 16> const trimmedTile{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
uint8_t const *tileData = tileID > nbTileInstances - options.trim
|
||||
? trimmedTile.data()
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
* with gaps in the scale curve filled by polynomial interpolation.
|
||||
*/
|
||||
static std::array<uint8_t, 256> reverse_curve{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // These
|
||||
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, // comments
|
||||
3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, // prevent
|
||||
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, // clang-format
|
||||
7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, // from
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // These
|
||||
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, // comments
|
||||
3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, // prevent
|
||||
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, // clang-format
|
||||
7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, // from
|
||||
10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, // reflowing
|
||||
13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, // these
|
||||
16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, // sixteen
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "link/assign.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <inttypes.h>
|
||||
@@ -8,18 +10,17 @@
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include "link/assign.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
#include "link/object.hpp"
|
||||
#include "link/main.hpp"
|
||||
#include "link/output.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "itertools.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "link/main.hpp"
|
||||
#include "link/object.hpp"
|
||||
#include "link/output.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
struct MemoryLocation {
|
||||
uint16_t address;
|
||||
uint32_t bank;
|
||||
@@ -36,15 +37,13 @@ std::vector<std::deque<FreeSpace>> memory[SECTTYPE_INVALID];
|
||||
uint64_t nbSectionsToAssign;
|
||||
|
||||
// Init the free space-modelling structs
|
||||
static void initFreeSpace()
|
||||
{
|
||||
static void initFreeSpace() {
|
||||
for (enum SectionType type : EnumSeq(SECTTYPE_INVALID)) {
|
||||
memory[type].resize(nbbanks(type));
|
||||
for (std::deque<FreeSpace> &bankMem : memory[type]) {
|
||||
bankMem.push_back({
|
||||
.address = sectionTypeInfo[type].startAddr,
|
||||
.size = sectionTypeInfo[type].size
|
||||
});
|
||||
bankMem.push_back(
|
||||
{.address = sectionTypeInfo[type].startAddr, .size = sectionTypeInfo[type].size}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,8 +53,7 @@ static void initFreeSpace()
|
||||
* @param section The section to assign
|
||||
* @param location The location to assign the section to
|
||||
*/
|
||||
static void assignSection(Section §ion, MemoryLocation const &location)
|
||||
{
|
||||
static void assignSection(Section §ion, MemoryLocation const &location) {
|
||||
// Propagate the assigned location to all UNIONs/FRAGMENTs
|
||||
// so `jr` patches in them will have the correct offset
|
||||
for (Section *next = §ion; next != nullptr; next = next->nextu) {
|
||||
@@ -77,9 +75,9 @@ static void assignSection(Section §ion, MemoryLocation const &location)
|
||||
* @param location The location to attempt placing the section at
|
||||
* @return True if the location is suitable, false otherwise.
|
||||
*/
|
||||
static bool isLocationSuitable(Section const §ion, FreeSpace const &freeSpace,
|
||||
MemoryLocation const &location)
|
||||
{
|
||||
static bool isLocationSuitable(
|
||||
Section const §ion, FreeSpace const &freeSpace, MemoryLocation const &location
|
||||
) {
|
||||
if (section.isAddressFixed && section.org != location.address)
|
||||
return false;
|
||||
|
||||
@@ -99,8 +97,7 @@ static bool isLocationSuitable(Section const §ion, FreeSpace const &freeSpac
|
||||
* @return The index into `memory[section->type]` of the free space encompassing the location,
|
||||
* or -1 if none was found
|
||||
*/
|
||||
static ssize_t getPlacement(Section const §ion, MemoryLocation &location)
|
||||
{
|
||||
static ssize_t getPlacement(Section const §ion, MemoryLocation &location) {
|
||||
SectionTypeInfo const &typeInfo = sectionTypeInfo[section.type];
|
||||
|
||||
static uint16_t curScrambleROM = 0;
|
||||
@@ -166,16 +163,16 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location)
|
||||
|
||||
// If that location is past the current block's end,
|
||||
// go forwards until that is no longer the case.
|
||||
while (spaceIdx < bankMem.size() && location.address >=
|
||||
bankMem[spaceIdx].address + bankMem[spaceIdx].size)
|
||||
while (spaceIdx < bankMem.size()
|
||||
&& location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size)
|
||||
spaceIdx++;
|
||||
|
||||
// Try again with the new location/free space combo
|
||||
}
|
||||
|
||||
// Try again in the next bank, if one is available.
|
||||
// Try scrambled banks in descending order until no bank in the scrambled range is available.
|
||||
// Otherwise, try in ascending order.
|
||||
// Try scrambled banks in descending order until no bank in the scrambled range is
|
||||
// available. Otherwise, try in ascending order.
|
||||
if (section.isBankFixed) {
|
||||
return -1;
|
||||
} else if (scrambleROMX && section.type == SECTTYPE_ROMX && location.bank <= scrambleROMX) {
|
||||
@@ -213,20 +210,17 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location)
|
||||
* sections of decreasing size.
|
||||
* @param section The section to place
|
||||
*/
|
||||
static void placeSection(Section §ion)
|
||||
{
|
||||
static void placeSection(Section §ion) {
|
||||
MemoryLocation location;
|
||||
|
||||
// Specially handle 0-byte SECTIONs, as they can't overlap anything
|
||||
if (section.size == 0) {
|
||||
// Unless the SECTION's address was fixed, the starting address
|
||||
// is fine for any alignment, as checked in sect_DoSanityChecks.
|
||||
location.address = section.isAddressFixed
|
||||
? section.org
|
||||
: sectionTypeInfo[section.type].startAddr;
|
||||
location.bank = section.isBankFixed
|
||||
? section.bank
|
||||
: sectionTypeInfo[section.type].firstBank;
|
||||
location.address =
|
||||
section.isAddressFixed ? section.org : sectionTypeInfo[section.type].startAddr;
|
||||
location.bank =
|
||||
section.isBankFixed ? section.bank : sectionTypeInfo[section.type].firstBank;
|
||||
assignSection(section, location);
|
||||
return;
|
||||
}
|
||||
@@ -234,14 +228,14 @@ static void placeSection(Section §ion)
|
||||
// Place section using first-fit decreasing algorithm
|
||||
// https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm
|
||||
if (ssize_t spaceIdx = getPlacement(section, location); spaceIdx != -1) {
|
||||
std::deque<FreeSpace> &bankMem = memory[section.type][location.bank -
|
||||
sectionTypeInfo[section.type].firstBank];
|
||||
std::deque<FreeSpace> &bankMem =
|
||||
memory[section.type][location.bank - sectionTypeInfo[section.type].firstBank];
|
||||
FreeSpace &freeSpace = bankMem[spaceIdx];
|
||||
|
||||
assignSection(section, location);
|
||||
|
||||
// Update the free space
|
||||
bool noLeftSpace = freeSpace.address == section.org;
|
||||
bool noLeftSpace = freeSpace.address == section.org;
|
||||
bool noRightSpace = freeSpace.address + freeSpace.size == section.org + section.size;
|
||||
if (noLeftSpace && noRightSpace) {
|
||||
// The free space is entirely deleted
|
||||
@@ -249,11 +243,12 @@ static void placeSection(Section §ion)
|
||||
} else if (!noLeftSpace && !noRightSpace) {
|
||||
// The free space is split in two
|
||||
// Append the new space after the original one
|
||||
bankMem.insert(bankMem.begin() + spaceIdx + 1, {
|
||||
.address = (uint16_t)(section.org + section.size),
|
||||
.size = (uint16_t)(freeSpace.address + freeSpace.size -
|
||||
section.org - section.size)
|
||||
});
|
||||
bankMem.insert(
|
||||
bankMem.begin() + spaceIdx + 1,
|
||||
{.address = (uint16_t)(section.org + section.size),
|
||||
.size =
|
||||
(uint16_t)(freeSpace.address + freeSpace.size - section.org - section.size)}
|
||||
);
|
||||
// Resize the original space (address is unmodified)
|
||||
freeSpace.size = section.org - freeSpace.address;
|
||||
} else {
|
||||
@@ -271,41 +266,66 @@ static void placeSection(Section §ion)
|
||||
|
||||
if (section.isBankFixed && nbbanks(section.type) != 1) {
|
||||
if (section.isAddressFixed)
|
||||
snprintf(where, sizeof(where), "at $%02" PRIx32 ":%04" PRIx16,
|
||||
section.bank, section.org);
|
||||
snprintf(
|
||||
where, sizeof(where), "at $%02" PRIx32 ":%04" PRIx16, section.bank, section.org
|
||||
);
|
||||
else if (section.isAlignFixed)
|
||||
snprintf(where, sizeof(where), "in bank $%02" PRIx32 " with align mask %" PRIx16,
|
||||
section.bank, (uint16_t)~section.alignMask);
|
||||
snprintf(
|
||||
where,
|
||||
sizeof(where),
|
||||
"in bank $%02" PRIx32 " with align mask %" PRIx16,
|
||||
section.bank,
|
||||
(uint16_t)~section.alignMask
|
||||
);
|
||||
else
|
||||
snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank);
|
||||
} else {
|
||||
if (section.isAddressFixed)
|
||||
snprintf(where, sizeof(where), "at address $%04" PRIx16, section.org);
|
||||
else if (section.isAlignFixed)
|
||||
snprintf(where, sizeof(where), "with align mask %" PRIx16 " and offset %" PRIx16,
|
||||
(uint16_t)~section.alignMask, section.alignOfs);
|
||||
snprintf(
|
||||
where,
|
||||
sizeof(where),
|
||||
"with align mask %" PRIx16 " and offset %" PRIx16,
|
||||
(uint16_t)~section.alignMask,
|
||||
section.alignOfs
|
||||
);
|
||||
else
|
||||
strcpy(where, "anywhere");
|
||||
}
|
||||
|
||||
// If a section failed to go to several places, nothing we can report
|
||||
if (!section.isBankFixed || !section.isAddressFixed)
|
||||
errx("Unable to place \"%s\" (%s section) %s",
|
||||
section.name.c_str(), sectionTypeInfo[section.type].name.c_str(), where);
|
||||
errx(
|
||||
"Unable to place \"%s\" (%s section) %s",
|
||||
section.name.c_str(),
|
||||
sectionTypeInfo[section.type].name.c_str(),
|
||||
where
|
||||
);
|
||||
// If the section just can't fit the bank, report that
|
||||
else if (section.org + section.size > endaddr(section.type) + 1)
|
||||
errx("Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)",
|
||||
section.name.c_str(), sectionTypeInfo[section.type].name.c_str(), where,
|
||||
section.org + section.size, endaddr(section.type) + 1);
|
||||
errx(
|
||||
"Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > "
|
||||
"$%04x)",
|
||||
section.name.c_str(),
|
||||
sectionTypeInfo[section.type].name.c_str(),
|
||||
where,
|
||||
section.org + section.size,
|
||||
endaddr(section.type) + 1
|
||||
);
|
||||
// Otherwise there is overlap with another section
|
||||
else
|
||||
errx("Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
|
||||
section.name.c_str(), sectionTypeInfo[section.type].name.c_str(), where,
|
||||
out_OverlappingSection(section)->name.c_str());
|
||||
errx(
|
||||
"Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
|
||||
section.name.c_str(),
|
||||
sectionTypeInfo[section.type].name.c_str(),
|
||||
where,
|
||||
out_OverlappingSection(section)->name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
#define BANK_CONSTRAINED (1 << 2)
|
||||
#define ORG_CONSTRAINED (1 << 1)
|
||||
#define BANK_CONSTRAINED (1 << 2)
|
||||
#define ORG_CONSTRAINED (1 << 1)
|
||||
#define ALIGN_CONSTRAINED (1 << 0)
|
||||
static std::deque<Section *> unassignedSections[1 << 3];
|
||||
|
||||
@@ -314,8 +334,7 @@ static std::deque<Section *> unassignedSections[1 << 3];
|
||||
* This is so the most-constrained sections are placed first
|
||||
* @param section The section to categorize
|
||||
*/
|
||||
static void categorizeSection(Section §ion)
|
||||
{
|
||||
static void categorizeSection(Section §ion) {
|
||||
uint8_t constraints = 0;
|
||||
|
||||
if (section.isBankFixed)
|
||||
@@ -337,8 +356,7 @@ static void categorizeSection(Section §ion)
|
||||
nbSectionsToAssign++;
|
||||
}
|
||||
|
||||
void assign_AssignSections()
|
||||
{
|
||||
void assign_AssignSections() {
|
||||
verbosePrint("Beginning assignment...\n");
|
||||
|
||||
// Initialize assignment
|
||||
@@ -368,8 +386,7 @@ void assign_AssignSections()
|
||||
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0;
|
||||
constraints--) {
|
||||
for (Section *section : unassignedSections[constraints]) {
|
||||
fprintf(stderr, "%c \"%s\"", nbSections == 0 ? ';': ',',
|
||||
section->name.c_str());
|
||||
fprintf(stderr, "%c \"%s\"", nbSections == 0 ? ';' : ',', section->name.c_str());
|
||||
nbSections++;
|
||||
if (nbSections == 10)
|
||||
goto max_out;
|
||||
@@ -384,7 +401,8 @@ max_out:
|
||||
}
|
||||
|
||||
// Assign all remaining sections by decreasing constraint order
|
||||
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0; constraints--) {
|
||||
for (int8_t constraints = BANK_CONSTRAINED | ALIGN_CONSTRAINED; constraints >= 0;
|
||||
constraints--) {
|
||||
for (Section *section : unassignedSections[constraints])
|
||||
placeSection(*section);
|
||||
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "extern/getopt.hpp"
|
||||
#include "itertools.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
#include "platform.hpp"
|
||||
#include "script.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include "link/assign.hpp"
|
||||
#include "link/object.hpp"
|
||||
#include "link/output.hpp"
|
||||
#include "link/patch.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "script.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
#include "extern/getopt.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "itertools.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
#include "platform.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
bool isDmgMode; // -d
|
||||
char *linkerScriptName; // -l
|
||||
char const *mapFileName; // -m
|
||||
bool noSymInMap; // -M
|
||||
char const *symFileName; // -n
|
||||
char const *overlayFileName; // -O
|
||||
char const *outputFileName; // -o
|
||||
uint8_t padValue; // -p
|
||||
bool isDmgMode; // -d
|
||||
char *linkerScriptName; // -l
|
||||
char const *mapFileName; // -m
|
||||
bool noSymInMap; // -M
|
||||
char const *symFileName; // -n
|
||||
char const *overlayFileName; // -O
|
||||
char const *outputFileName; // -o
|
||||
uint8_t padValue; // -p
|
||||
// Setting these three to 0 disables the functionality
|
||||
uint16_t scrambleROMX = 0; // -S
|
||||
uint16_t scrambleROMX = 0; // -S
|
||||
uint8_t scrambleWRAMX = 0;
|
||||
uint8_t scrambleSRAM = 0;
|
||||
bool is32kMode; // -t
|
||||
bool beVerbose; // -v
|
||||
bool isWRAM0Mode; // -w
|
||||
bool disablePadding; // -x
|
||||
bool is32kMode; // -t
|
||||
bool beVerbose; // -v
|
||||
bool isWRAM0Mode; // -w
|
||||
bool disablePadding; // -x
|
||||
|
||||
FILE *linkerScript;
|
||||
|
||||
@@ -72,8 +72,7 @@ std::string const &FileStackNode::name() const {
|
||||
}
|
||||
|
||||
// Helper function to dump a file stack to stderr
|
||||
std::string const *FileStackNode::dumpFileStack() const
|
||||
{
|
||||
std::string const *FileStackNode::dumpFileStack() const {
|
||||
std::string const *lastName;
|
||||
|
||||
if (parent) {
|
||||
@@ -95,9 +94,9 @@ std::string const *FileStackNode::dumpFileStack() const
|
||||
return lastName;
|
||||
}
|
||||
|
||||
void printDiag(char const *fmt, va_list args, char const *type,
|
||||
FileStackNode const *where, uint32_t lineNo)
|
||||
{
|
||||
void printDiag(
|
||||
char const *fmt, va_list args, char const *type, FileStackNode const *where, uint32_t lineNo
|
||||
) {
|
||||
fputs(type, stderr);
|
||||
fputs(": ", stderr);
|
||||
if (where) {
|
||||
@@ -108,8 +107,7 @@ void printDiag(char const *fmt, va_list args, char const *type,
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
|
||||
{
|
||||
void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
@@ -117,8 +115,7 @@ void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
|
||||
{
|
||||
void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
@@ -129,8 +126,7 @@ void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
|
||||
nbErrors++;
|
||||
}
|
||||
|
||||
void argErr(char flag, char const *fmt, ...)
|
||||
{
|
||||
void argErr(char flag, char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
fprintf(stderr, "error: Invalid argument for option '%c': ", flag);
|
||||
@@ -143,8 +139,7 @@ void argErr(char flag, char const *fmt, ...)
|
||||
nbErrors++;
|
||||
}
|
||||
|
||||
[[noreturn]] void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
|
||||
{
|
||||
[[noreturn]] void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
@@ -154,8 +149,9 @@ void argErr(char flag, char const *fmt, ...)
|
||||
if (nbErrors != UINT32_MAX)
|
||||
nbErrors++;
|
||||
|
||||
fprintf(stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors,
|
||||
nbErrors == 1 ? "" : "s");
|
||||
fprintf(
|
||||
stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -173,41 +169,41 @@ static const char *optstring = "dl:m:Mn:O:o:p:S:s:tVvWwx";
|
||||
* over short opt matching
|
||||
*/
|
||||
static option const longopts[] = {
|
||||
{ "dmg", no_argument, nullptr, 'd' },
|
||||
{ "linkerscript", required_argument, nullptr, 'l' },
|
||||
{ "map", required_argument, nullptr, 'm' },
|
||||
{ "no-sym-in-map", no_argument, nullptr, 'M' },
|
||||
{ "sym", required_argument, nullptr, 'n' },
|
||||
{ "overlay", required_argument, nullptr, 'O' },
|
||||
{ "output", required_argument, nullptr, 'o' },
|
||||
{ "pad", required_argument, nullptr, 'p' },
|
||||
{ "scramble", required_argument, nullptr, 'S' },
|
||||
{ "smart", required_argument, nullptr, 's' },
|
||||
{ "tiny", no_argument, nullptr, 't' },
|
||||
{ "version", no_argument, nullptr, 'V' },
|
||||
{ "verbose", no_argument, nullptr, 'v' },
|
||||
{ "wramx", no_argument, nullptr, 'w' },
|
||||
{ "nopad", no_argument, nullptr, 'x' },
|
||||
{ nullptr, no_argument, nullptr, 0 }
|
||||
{"dmg", no_argument, nullptr, 'd'},
|
||||
{"linkerscript", required_argument, nullptr, 'l'},
|
||||
{"map", required_argument, nullptr, 'm'},
|
||||
{"no-sym-in-map", no_argument, nullptr, 'M'},
|
||||
{"sym", required_argument, nullptr, 'n'},
|
||||
{"overlay", required_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"pad", required_argument, nullptr, 'p'},
|
||||
{"scramble", required_argument, nullptr, 'S'},
|
||||
{"smart", required_argument, nullptr, 's'},
|
||||
{"tiny", no_argument, nullptr, 't'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"wramx", no_argument, nullptr, 'w'},
|
||||
{"nopad", no_argument, nullptr, 'x'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
static void printUsage()
|
||||
{
|
||||
static void printUsage() {
|
||||
fputs(
|
||||
"Usage: rgblink [-dMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
|
||||
" [-O overlay_file] [-o out_file] [-p pad_value]\n"
|
||||
" [-S spec] [-s symbol] <file> ...\n"
|
||||
"Useful options:\n"
|
||||
" -l, --linkerscript <path> set the input linker script\n"
|
||||
" -m, --map <path> set the output map file\n"
|
||||
" -n, --sym <path> set the output symbol list file\n"
|
||||
" -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"
|
||||
"\n"
|
||||
"For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n",
|
||||
stderr);
|
||||
"Usage: rgblink [-dMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
|
||||
" [-O overlay_file] [-o out_file] [-p pad_value]\n"
|
||||
" [-S spec] [-s symbol] <file> ...\n"
|
||||
"Useful options:\n"
|
||||
" -l, --linkerscript <path> set the input linker script\n"
|
||||
" -m, --map <path> set the output map file\n"
|
||||
" -n, --sym <path> set the output symbol list file\n"
|
||||
" -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"
|
||||
"\n"
|
||||
"For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n",
|
||||
stderr
|
||||
);
|
||||
}
|
||||
|
||||
enum ScrambledRegion {
|
||||
@@ -222,13 +218,12 @@ struct {
|
||||
char const *name;
|
||||
uint16_t max;
|
||||
} scrambleSpecs[SCRAMBLE_UNK] = {
|
||||
{ "romx", 65535 }, // SCRAMBLE_ROMX
|
||||
{ "sram", 255 }, // SCRAMBLE_SRAM
|
||||
{ "wramx", 7 }, // SCRAMBLE_WRAMX
|
||||
{"romx", 65535}, // SCRAMBLE_ROMX
|
||||
{"sram", 255 }, // SCRAMBLE_SRAM
|
||||
{"wramx", 7 }, // SCRAMBLE_WRAMX
|
||||
};
|
||||
|
||||
static void parseScrambleSpec(char const *spec)
|
||||
{
|
||||
static void parseScrambleSpec(char const *spec) {
|
||||
// Skip any leading whitespace
|
||||
spec += strspn(spec, " \t");
|
||||
|
||||
@@ -251,7 +246,7 @@ static void parseScrambleSpec(char const *spec)
|
||||
|
||||
if (*spec == '\0')
|
||||
break;
|
||||
if (*spec == '=') // Skip the limit, too
|
||||
if (*spec == '=') // Skip the limit, too
|
||||
spec = strchr(&spec[1], ','); // Skip to next comma, if any
|
||||
goto next;
|
||||
}
|
||||
@@ -259,8 +254,9 @@ static void parseScrambleSpec(char const *spec)
|
||||
// Find the next non-blank char after the region name's end
|
||||
spec += regionNameLen + strspn(&spec[regionNameLen], " \t");
|
||||
if (*spec != '\0' && *spec != ',' && *spec != '=') {
|
||||
argErr('S', "Unexpected '%c' after region name \"%.*s\"",
|
||||
regionNamePrintLen, regionName);
|
||||
argErr(
|
||||
'S', "Unexpected '%c' after region name \"%.*s\"", regionNamePrintLen, regionName
|
||||
);
|
||||
// Skip to next ',' or '=' (or NUL) and keep parsing
|
||||
spec += 1 + strcspn(&spec[1], ",=");
|
||||
}
|
||||
@@ -285,22 +281,30 @@ static void parseScrambleSpec(char const *spec)
|
||||
char *endptr;
|
||||
|
||||
if (*spec == '\0' || *spec == ',') {
|
||||
argErr('S', "Empty limit for region \"%.*s\"",
|
||||
regionNamePrintLen, regionName);
|
||||
argErr('S', "Empty limit for region \"%.*s\"", regionNamePrintLen, regionName);
|
||||
goto next;
|
||||
}
|
||||
limit = strtoul(spec, &endptr, 10);
|
||||
endptr += strspn(endptr, " \t");
|
||||
if (*endptr != '\0' && *endptr != ',') {
|
||||
argErr('S', "Invalid non-numeric limit for region \"%.*s\"",
|
||||
regionNamePrintLen, regionName);
|
||||
argErr(
|
||||
'S',
|
||||
"Invalid non-numeric limit for region \"%.*s\"",
|
||||
regionNamePrintLen,
|
||||
regionName
|
||||
);
|
||||
endptr = strchr(endptr, ',');
|
||||
}
|
||||
spec = endptr;
|
||||
|
||||
if (region != SCRAMBLE_UNK && limit > scrambleSpecs[region].max) {
|
||||
argErr('S', "Limit for region \"%.*s\" may not exceed %" PRIu16,
|
||||
regionNamePrintLen, regionName, scrambleSpecs[region].max);
|
||||
argErr(
|
||||
'S',
|
||||
"Limit for region \"%.*s\" may not exceed %" PRIu16,
|
||||
regionNamePrintLen,
|
||||
regionName,
|
||||
scrambleSpecs[region].max
|
||||
);
|
||||
limit = scrambleSpecs[region].max;
|
||||
}
|
||||
|
||||
@@ -321,8 +325,7 @@ static void parseScrambleSpec(char const *spec)
|
||||
// Only WRAMX can be implied, since ROMX and SRAM size may vary
|
||||
scrambleWRAMX = 7;
|
||||
} else {
|
||||
argErr('S', "Cannot imply limit for region \"%.*s\"",
|
||||
regionNamePrintLen, regionName);
|
||||
argErr('S', "Cannot imply limit for region \"%.*s\"", regionNamePrintLen, regionName);
|
||||
}
|
||||
|
||||
next:
|
||||
@@ -337,13 +340,13 @@ next:
|
||||
}
|
||||
|
||||
[[noreturn]] void reportErrors() {
|
||||
fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n",
|
||||
nbErrors, nbErrors == 1 ? "" : "s");
|
||||
fprintf(
|
||||
stderr, "Linking failed with %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void freeSection(Section §ion)
|
||||
{
|
||||
static void freeSection(Section §ion) {
|
||||
Section *next = §ion;
|
||||
|
||||
for (Section *nextu; next; next = nextu) {
|
||||
@@ -352,13 +355,11 @@ static void freeSection(Section §ion)
|
||||
};
|
||||
}
|
||||
|
||||
static void freeSections()
|
||||
{
|
||||
static void freeSections() {
|
||||
sect_ForEach(freeSection);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int main(int argc, char *argv[]) {
|
||||
// Parse options
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
switch (ch) {
|
||||
@@ -444,8 +445,9 @@ int main(int argc, char *argv[])
|
||||
|
||||
// If no input files were specified, the user must have screwed up
|
||||
if (curArgIndex == argc) {
|
||||
fputs("FATAL: Please specify an input file (pass `-` to read from standard input)\n",
|
||||
stderr);
|
||||
fputs(
|
||||
"FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr
|
||||
);
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "link/object.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <new>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include "link/assign.hpp"
|
||||
#include "link/main.hpp"
|
||||
#include "link/object.hpp"
|
||||
#include "link/patch.hpp"
|
||||
#include "link/sdas_obj.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include "link/assign.hpp"
|
||||
#include "link/main.hpp"
|
||||
#include "link/patch.hpp"
|
||||
#include "link/sdas_obj.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
static std::deque<std::vector<Symbol>> symbolLists;
|
||||
static std::vector<std::vector<FileStackNode>> nodes;
|
||||
static std::deque<Assertion> assertions;
|
||||
@@ -33,25 +34,23 @@ static std::deque<Assertion> assertions;
|
||||
|
||||
// Internal, DO NOT USE.
|
||||
// For helper wrapper macros defined below, such as `tryReadlong`
|
||||
#define tryRead(func, type, errval, vartype, var, file, ...) do { \
|
||||
FILE *tmpFile = file; \
|
||||
type tmpVal = func(tmpFile); \
|
||||
/* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \
|
||||
if (tmpVal == (errval)) { \
|
||||
errx(__VA_ARGS__, feof(tmpFile) \
|
||||
? "Unexpected end of file" \
|
||||
: strerror(errno)); \
|
||||
} \
|
||||
var = (vartype)tmpVal; \
|
||||
} while (0)
|
||||
#define tryRead(func, type, errval, vartype, var, file, ...) \
|
||||
do { \
|
||||
FILE *tmpFile = file; \
|
||||
type tmpVal = func(tmpFile); \
|
||||
/* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \
|
||||
if (tmpVal == (errval)) { \
|
||||
errx(__VA_ARGS__, feof(tmpFile) ? "Unexpected end of file" : strerror(errno)); \
|
||||
} \
|
||||
var = (vartype)tmpVal; \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Reads an unsigned long (32-bit) value from a file.
|
||||
* @param file The file to read from. This will read 4 bytes from the file.
|
||||
* @return The value read, cast to a int64_t, or -1 on failure.
|
||||
*/
|
||||
static int64_t readlong(FILE *file)
|
||||
{
|
||||
static int64_t readlong(FILE *file) {
|
||||
uint32_t value = 0;
|
||||
|
||||
// Read the little-endian value byte by byte
|
||||
@@ -92,8 +91,7 @@ static int64_t readlong(FILE *file)
|
||||
* @param ... A format string and related arguments; note that an extra string
|
||||
* argument is provided, the reason for failure
|
||||
*/
|
||||
#define tryGetc(type, var, file, ...) \
|
||||
tryRead(getc, int, EOF, type, var, file, __VA_ARGS__)
|
||||
#define tryGetc(type, var, file, ...) tryRead(getc, int, EOF, type, var, file, __VA_ARGS__)
|
||||
|
||||
/*
|
||||
* Helper macro for readings '\0'-terminated strings from a file, and errors out if it fails to.
|
||||
@@ -103,19 +101,18 @@ static int64_t readlong(FILE *file)
|
||||
* @param ... A format string and related arguments; note that an extra string
|
||||
* argument is provided, the reason for failure
|
||||
*/
|
||||
#define tryReadstring(var, file, ...) do { \
|
||||
FILE *tmpFile = file; \
|
||||
std::string &tmpVal = var; \
|
||||
for (int tmpByte = getc(tmpFile); tmpByte != '\0'; tmpByte = getc(tmpFile)) { \
|
||||
if (tmpByte == EOF) { \
|
||||
errx(__VA_ARGS__, feof(tmpFile) \
|
||||
? "Unexpected end of file" \
|
||||
: strerror(errno)); \
|
||||
} else { \
|
||||
tmpVal.push_back(tmpByte); \
|
||||
} \
|
||||
}; \
|
||||
} while (0)
|
||||
#define tryReadstring(var, file, ...) \
|
||||
do { \
|
||||
FILE *tmpFile = file; \
|
||||
std::string &tmpVal = var; \
|
||||
for (int tmpByte = getc(tmpFile); tmpByte != '\0'; tmpByte = getc(tmpFile)) { \
|
||||
if (tmpByte == EOF) { \
|
||||
errx(__VA_ARGS__, feof(tmpFile) ? "Unexpected end of file" : strerror(errno)); \
|
||||
} else { \
|
||||
tmpVal.push_back(tmpByte); \
|
||||
} \
|
||||
}; \
|
||||
} while (0)
|
||||
|
||||
// Functions to parse object files
|
||||
|
||||
@@ -126,39 +123,55 @@ static int64_t readlong(FILE *file)
|
||||
* @param i The ID of the node in the array
|
||||
* @param fileName The filename to report in errors
|
||||
*/
|
||||
static void readFileStackNode(FILE *file, std::vector<FileStackNode> &fileNodes, uint32_t i,
|
||||
char const *fileName)
|
||||
{
|
||||
static void readFileStackNode(
|
||||
FILE *file, std::vector<FileStackNode> &fileNodes, uint32_t i, char const *fileName
|
||||
) {
|
||||
FileStackNode &node = fileNodes[i];
|
||||
uint32_t parentID;
|
||||
|
||||
tryReadlong(parentID, file,
|
||||
"%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i);
|
||||
tryReadlong(parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i);
|
||||
node.parent = parentID != (uint32_t)-1 ? &fileNodes[parentID] : nullptr;
|
||||
tryReadlong(node.lineNo, file,
|
||||
"%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i);
|
||||
tryGetc(enum FileStackNodeType, node.type, file,
|
||||
"%s: Cannot read node #%" PRIu32 "'s type: %s", fileName, i);
|
||||
tryReadlong(
|
||||
node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i
|
||||
);
|
||||
tryGetc(
|
||||
enum FileStackNodeType,
|
||||
node.type,
|
||||
file,
|
||||
"%s: Cannot read node #%" PRIu32 "'s type: %s",
|
||||
fileName,
|
||||
i
|
||||
);
|
||||
switch (node.type) {
|
||||
case NODE_FILE:
|
||||
case NODE_MACRO:
|
||||
node.data = "";
|
||||
tryReadstring(node.name(), file,
|
||||
"%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, i);
|
||||
tryReadstring(
|
||||
node.name(), file, "%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, i
|
||||
);
|
||||
break;
|
||||
|
||||
uint32_t depth;
|
||||
case NODE_REPT:
|
||||
tryReadlong(depth, file,
|
||||
"%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, i);
|
||||
tryReadlong(depth, file, "%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, i);
|
||||
node.data = std::vector<uint32_t>(depth);
|
||||
for (uint32_t k = 0; k < depth; k++)
|
||||
tryReadlong(node.iters()[k], file,
|
||||
"%s: Cannot read node #%" PRIu32 "'s iter #%" PRIu32 ": %s",
|
||||
fileName, i, k);
|
||||
tryReadlong(
|
||||
node.iters()[k],
|
||||
file,
|
||||
"%s: Cannot read node #%" PRIu32 "'s iter #%" PRIu32 ": %s",
|
||||
fileName,
|
||||
i,
|
||||
k
|
||||
);
|
||||
if (!node.parent)
|
||||
fatal(nullptr, 0, "%s is not a valid object file: root node (#%"
|
||||
PRIu32 ") may not be REPT", fileName, i);
|
||||
fatal(
|
||||
nullptr,
|
||||
0,
|
||||
"%s is not a valid object file: root node (#%" PRIu32 ") may not be REPT",
|
||||
fileName,
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,34 +181,52 @@ static void readFileStackNode(FILE *file, std::vector<FileStackNode> &fileNodes,
|
||||
* @param symbol The symbol to fill
|
||||
* @param fileName The filename to report in errors
|
||||
*/
|
||||
static void readSymbol(FILE *file, Symbol &symbol, char const *fileName,
|
||||
std::vector<FileStackNode> const &fileNodes)
|
||||
{
|
||||
static void readSymbol(
|
||||
FILE *file, Symbol &symbol, char const *fileName, std::vector<FileStackNode> const &fileNodes
|
||||
) {
|
||||
tryReadstring(symbol.name, file, "%s: Cannot read symbol name: %s", fileName);
|
||||
tryGetc(enum ExportLevel, symbol.type, file, "%s: Cannot read \"%s\"'s type: %s",
|
||||
fileName, symbol.name.c_str());
|
||||
tryGetc(
|
||||
enum ExportLevel,
|
||||
symbol.type,
|
||||
file,
|
||||
"%s: Cannot read \"%s\"'s type: %s",
|
||||
fileName,
|
||||
symbol.name.c_str()
|
||||
);
|
||||
// If the symbol is defined in this file, read its definition
|
||||
if (symbol.type != SYMTYPE_IMPORT) {
|
||||
symbol.objFileName = fileName;
|
||||
uint32_t nodeID;
|
||||
tryReadlong(nodeID, file, "%s: Cannot read \"%s\"'s node ID: %s",
|
||||
fileName, symbol.name.c_str());
|
||||
tryReadlong(
|
||||
nodeID, file, "%s: Cannot read \"%s\"'s node ID: %s", fileName, symbol.name.c_str()
|
||||
);
|
||||
symbol.src = &fileNodes[nodeID];
|
||||
tryReadlong(symbol.lineNo, file, "%s: Cannot read \"%s\"'s line number: %s",
|
||||
fileName, symbol.name.c_str());
|
||||
tryReadlong(
|
||||
symbol.lineNo,
|
||||
file,
|
||||
"%s: Cannot read \"%s\"'s line number: %s",
|
||||
fileName,
|
||||
symbol.name.c_str()
|
||||
);
|
||||
int32_t sectionID, value;
|
||||
tryReadlong(sectionID, file, "%s: Cannot read \"%s\"'s section ID: %s",
|
||||
fileName, symbol.name.c_str());
|
||||
tryReadlong(value, file, "%s: Cannot read \"%s\"'s value: %s",
|
||||
fileName, symbol.name.c_str());
|
||||
tryReadlong(
|
||||
sectionID,
|
||||
file,
|
||||
"%s: Cannot read \"%s\"'s section ID: %s",
|
||||
fileName,
|
||||
symbol.name.c_str()
|
||||
);
|
||||
tryReadlong(
|
||||
value, file, "%s: Cannot read \"%s\"'s value: %s", fileName, symbol.name.c_str()
|
||||
);
|
||||
if (sectionID == -1) {
|
||||
symbol.data = value;
|
||||
} else {
|
||||
symbol.data = Label{
|
||||
.sectionID = sectionID,
|
||||
.offset = value,
|
||||
// Set the `.section` later based on the `.sectionID`
|
||||
.section = nullptr,
|
||||
.sectionID = sectionID,
|
||||
.offset = value,
|
||||
// Set the `.section` later based on the `.sectionID`
|
||||
.section = nullptr,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
@@ -210,53 +241,96 @@ static void readSymbol(FILE *file, Symbol &symbol, char const *fileName,
|
||||
* @param fileName The filename to report in errors
|
||||
* @param i The number of the patch to report in errors
|
||||
*/
|
||||
static void readPatch(FILE *file, Patch &patch, char const *fileName, std::string const §Name,
|
||||
uint32_t i, std::vector<FileStackNode> const &fileNodes)
|
||||
{
|
||||
static void readPatch(
|
||||
FILE *file,
|
||||
Patch &patch,
|
||||
char const *fileName,
|
||||
std::string const §Name,
|
||||
uint32_t i,
|
||||
std::vector<FileStackNode> const &fileNodes
|
||||
) {
|
||||
uint32_t nodeID, rpnSize;
|
||||
enum PatchType type;
|
||||
|
||||
tryReadlong(nodeID, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s",
|
||||
fileName, sectName.c_str(), i);
|
||||
tryReadlong(
|
||||
nodeID,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s",
|
||||
fileName,
|
||||
sectName.c_str(),
|
||||
i
|
||||
);
|
||||
patch.src = &fileNodes[nodeID];
|
||||
tryReadlong(patch.lineNo, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s line number: %s",
|
||||
fileName, sectName.c_str(), i);
|
||||
tryReadlong(patch.offset, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s offset: %s",
|
||||
fileName, sectName.c_str(), i);
|
||||
tryReadlong(patch.pcSectionID, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
||||
fileName, sectName.c_str(), i);
|
||||
tryReadlong(patch.pcOffset, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
||||
fileName, sectName.c_str(), i);
|
||||
tryGetc(enum PatchType, type, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s",
|
||||
fileName, sectName.c_str(), i);
|
||||
tryReadlong(
|
||||
patch.lineNo,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s line number: %s",
|
||||
fileName,
|
||||
sectName.c_str(),
|
||||
i
|
||||
);
|
||||
tryReadlong(
|
||||
patch.offset,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s offset: %s",
|
||||
fileName,
|
||||
sectName.c_str(),
|
||||
i
|
||||
);
|
||||
tryReadlong(
|
||||
patch.pcSectionID,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
||||
fileName,
|
||||
sectName.c_str(),
|
||||
i
|
||||
);
|
||||
tryReadlong(
|
||||
patch.pcOffset,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
||||
fileName,
|
||||
sectName.c_str(),
|
||||
i
|
||||
);
|
||||
tryGetc(
|
||||
enum PatchType,
|
||||
type,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s",
|
||||
fileName,
|
||||
sectName.c_str(),
|
||||
i
|
||||
);
|
||||
patch.type = type;
|
||||
tryReadlong(rpnSize, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s",
|
||||
fileName, sectName.c_str(), i);
|
||||
tryReadlong(
|
||||
rpnSize,
|
||||
file,
|
||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s",
|
||||
fileName,
|
||||
sectName.c_str(),
|
||||
i
|
||||
);
|
||||
|
||||
patch.rpnExpression.resize(rpnSize);
|
||||
size_t nbElementsRead = fread(patch.rpnExpression.data(), 1, rpnSize, file);
|
||||
|
||||
if (nbElementsRead != rpnSize)
|
||||
errx("%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
|
||||
fileName, sectName.c_str(), i,
|
||||
feof(file) ? "Unexpected end of file" : strerror(errno));
|
||||
errx(
|
||||
"%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
|
||||
fileName,
|
||||
sectName.c_str(),
|
||||
i,
|
||||
feof(file) ? "Unexpected end of file" : strerror(errno)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets a patch's pcSection from its pcSectionID.
|
||||
* @param patch The patch to fix
|
||||
*/
|
||||
static void linkPatchToPCSect(Patch &patch, std::vector<Section *> const &fileSections)
|
||||
{
|
||||
patch.pcSection = patch.pcSectionID != (uint32_t)-1 ? fileSections[patch.pcSectionID]
|
||||
: nullptr;
|
||||
static void linkPatchToPCSect(Patch &patch, std::vector<Section *> const &fileSections) {
|
||||
patch.pcSection = patch.pcSectionID != (uint32_t)-1 ? fileSections[patch.pcSectionID] : nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -265,9 +339,9 @@ static void linkPatchToPCSect(Patch &patch, std::vector<Section *> const &fileSe
|
||||
* @param section The section to fill
|
||||
* @param fileName The filename to report in errors
|
||||
*/
|
||||
static void readSection(FILE *file, Section §ion, char const *fileName,
|
||||
std::vector<FileStackNode> const &fileNodes)
|
||||
{
|
||||
static void readSection(
|
||||
FILE *file, Section §ion, char const *fileName, std::vector<FileStackNode> const &fileNodes
|
||||
) {
|
||||
int32_t tmp;
|
||||
uint8_t byte;
|
||||
|
||||
@@ -277,8 +351,9 @@ static void readSection(FILE *file, Section §ion, char const *fileName,
|
||||
errx("\"%s\"'s section size (%" PRId32 ") is invalid", section.name.c_str(), tmp);
|
||||
section.size = tmp;
|
||||
section.offset = 0;
|
||||
tryGetc(uint8_t, byte, file, "%s: Cannot read \"%s\"'s type: %s", fileName,
|
||||
section.name.c_str());
|
||||
tryGetc(
|
||||
uint8_t, byte, file, "%s: Cannot read \"%s\"'s type: %s", fileName, section.name.c_str()
|
||||
);
|
||||
section.type = (enum SectionType)(byte & 0x3F);
|
||||
if (byte >> 7)
|
||||
section.modifier = SECTION_UNION;
|
||||
@@ -296,17 +371,29 @@ static void readSection(FILE *file, Section §ion, char const *fileName,
|
||||
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s", fileName, section.name.c_str());
|
||||
section.isBankFixed = tmp >= 0;
|
||||
section.bank = tmp;
|
||||
tryGetc(uint8_t, byte, file, "%s: Cannot read \"%s\"'s alignment: %s", fileName,
|
||||
section.name.c_str());
|
||||
tryGetc(
|
||||
uint8_t,
|
||||
byte,
|
||||
file,
|
||||
"%s: Cannot read \"%s\"'s alignment: %s",
|
||||
fileName,
|
||||
section.name.c_str()
|
||||
);
|
||||
if (byte > 16)
|
||||
byte = 16;
|
||||
section.isAlignFixed = byte != 0;
|
||||
section.alignMask = (1 << byte) - 1;
|
||||
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s", fileName,
|
||||
section.name.c_str());
|
||||
tryReadlong(
|
||||
tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s", fileName, section.name.c_str()
|
||||
);
|
||||
if (tmp > UINT16_MAX) {
|
||||
error(nullptr, 0, "\"%s\"'s alignment offset is too large (%" PRId32 ")",
|
||||
section.name.c_str(), tmp);
|
||||
error(
|
||||
nullptr,
|
||||
0,
|
||||
"\"%s\"'s alignment offset is too large (%" PRId32 ")",
|
||||
section.name.c_str(),
|
||||
tmp
|
||||
);
|
||||
tmp = UINT16_MAX;
|
||||
}
|
||||
section.alignOfs = tmp;
|
||||
@@ -316,15 +403,23 @@ static void readSection(FILE *file, Section §ion, char const *fileName,
|
||||
section.data.resize(section.size);
|
||||
if (size_t nbRead = fread(section.data.data(), 1, section.size, file);
|
||||
nbRead != section.size)
|
||||
errx("%s: Cannot read \"%s\"'s data: %s", fileName, section.name.c_str(),
|
||||
feof(file) ? "Unexpected end of file" : strerror(errno));
|
||||
errx(
|
||||
"%s: Cannot read \"%s\"'s data: %s",
|
||||
fileName,
|
||||
section.name.c_str(),
|
||||
feof(file) ? "Unexpected end of file" : strerror(errno)
|
||||
);
|
||||
}
|
||||
|
||||
uint32_t nbPatches;
|
||||
|
||||
tryReadlong(nbPatches, file,
|
||||
"%s: Cannot read \"%s\"'s number of patches: %s", fileName,
|
||||
section.name.c_str());
|
||||
tryReadlong(
|
||||
nbPatches,
|
||||
file,
|
||||
"%s: Cannot read \"%s\"'s number of patches: %s",
|
||||
fileName,
|
||||
section.name.c_str()
|
||||
);
|
||||
|
||||
section.patches.resize(nbPatches);
|
||||
for (uint32_t i = 0; i < nbPatches; i++)
|
||||
@@ -337,8 +432,7 @@ static void readSection(FILE *file, Section §ion, char const *fileName,
|
||||
* @param symbol The symbol to link
|
||||
* @param section The section to link
|
||||
*/
|
||||
static void linkSymToSect(Symbol &symbol, Section §ion)
|
||||
{
|
||||
static void linkSymToSect(Symbol &symbol, Section §ion) {
|
||||
uint32_t a = 0, b = section.symbols.size();
|
||||
int32_t symbolOffset = symbol.label().offset;
|
||||
|
||||
@@ -361,9 +455,13 @@ static void linkSymToSect(Symbol &symbol, Section §ion)
|
||||
* @param assert The assertion to fill
|
||||
* @param fileName The filename to report in errors
|
||||
*/
|
||||
static void readAssertion(FILE *file, Assertion &assert, char const *fileName, uint32_t i,
|
||||
std::vector<FileStackNode> const &fileNodes)
|
||||
{
|
||||
static void readAssertion(
|
||||
FILE *file,
|
||||
Assertion &assert,
|
||||
char const *fileName,
|
||||
uint32_t i,
|
||||
std::vector<FileStackNode> const &fileNodes
|
||||
) {
|
||||
char assertName[sizeof("Assertion #4294967295")]; // UINT32_MAX
|
||||
|
||||
snprintf(assertName, sizeof(assertName), "Assertion #%" PRIu32, i);
|
||||
@@ -372,13 +470,11 @@ static void readAssertion(FILE *file, Assertion &assert, char const *fileName, u
|
||||
tryReadstring(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
|
||||
}
|
||||
|
||||
static Section *getMainSection(Section §ion)
|
||||
{
|
||||
static Section *getMainSection(Section §ion) {
|
||||
return section.modifier != SECTION_NORMAL ? sect_GetSection(section.name) : §ion;
|
||||
}
|
||||
|
||||
void obj_ReadFile(char const *fileName, unsigned int fileID)
|
||||
{
|
||||
void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
FILE *file;
|
||||
|
||||
if (strcmp(fileName, "-")) {
|
||||
@@ -405,12 +501,9 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
||||
default: // This is (probably) a SDCC object file, defer the rest of detection to it
|
||||
// Since SDCC does not provide line info, everything will be reported as coming from the
|
||||
// object file. It's better than nothing.
|
||||
nodes[fileID].push_back({
|
||||
.parent = nullptr,
|
||||
.lineNo = 0,
|
||||
.type = NODE_FILE,
|
||||
.data = fileName
|
||||
});
|
||||
nodes[fileID].push_back(
|
||||
{.parent = nullptr, .lineNo = 0, .type = NODE_FILE, .data = fileName}
|
||||
);
|
||||
|
||||
std::vector<Symbol> &fileSymbols = symbolLists.emplace_front();
|
||||
|
||||
@@ -422,7 +515,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
||||
int matchedElems;
|
||||
|
||||
if (fscanf(file, RGBDS_OBJECT_VERSION_STRING "%n", &matchedElems) == 1
|
||||
&& matchedElems != strlen(RGBDS_OBJECT_VERSION_STRING))
|
||||
&& matchedElems != strlen(RGBDS_OBJECT_VERSION_STRING))
|
||||
errx("%s: Not a RGBDS object file", fileName);
|
||||
|
||||
verbosePrint("Reading object file %s\n", fileName);
|
||||
@@ -431,10 +524,16 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
||||
|
||||
tryReadlong(revNum, file, "%s: Cannot read revision number: %s", fileName);
|
||||
if (revNum != RGBDS_OBJECT_REV)
|
||||
errx("%s: Unsupported object file for rgblink %s; try rebuilding \"%s\"%s"
|
||||
" (expected revision %d, got %d)", fileName, get_package_version_string(),
|
||||
fileName, revNum > RGBDS_OBJECT_REV ? " or updating rgblink" : "",
|
||||
RGBDS_OBJECT_REV, revNum);
|
||||
errx(
|
||||
"%s: Unsupported object file for rgblink %s; try rebuilding \"%s\"%s"
|
||||
" (expected revision %d, got %d)",
|
||||
fileName,
|
||||
get_package_version_string(),
|
||||
fileName,
|
||||
revNum > RGBDS_OBJECT_REV ? " or updating rgblink" : "",
|
||||
RGBDS_OBJECT_REV,
|
||||
revNum
|
||||
);
|
||||
|
||||
uint32_t nbNodes;
|
||||
uint32_t nbSymbols;
|
||||
@@ -448,7 +547,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
||||
tryReadlong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
|
||||
nodes[fileID].resize(nbNodes);
|
||||
verbosePrint("Reading %u nodes...\n", nbNodes);
|
||||
for (uint32_t i = nbNodes; i--; )
|
||||
for (uint32_t i = nbNodes; i--;)
|
||||
readFileStackNode(file, nodes[fileID], i, fileName);
|
||||
|
||||
// This file's symbols, kept to link sections to them
|
||||
@@ -474,7 +573,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
||||
verbosePrint("Reading %" PRIu32 " sections...\n", nbSections);
|
||||
for (uint32_t i = 0; i < nbSections; i++) {
|
||||
// Read section
|
||||
fileSections[i] = new(std::nothrow) Section();
|
||||
fileSections[i] = new (std::nothrow) Section();
|
||||
if (!fileSections[i])
|
||||
err("%s: Failed to create new section", fileName);
|
||||
|
||||
@@ -527,12 +626,10 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
void obj_CheckAssertions()
|
||||
{
|
||||
void obj_CheckAssertions() {
|
||||
patch_CheckAssertions(assertions);
|
||||
}
|
||||
|
||||
void obj_Setup(unsigned int nbFiles)
|
||||
{
|
||||
void obj_Setup(unsigned int nbFiles) {
|
||||
nodes.resize(nbFiles);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "link/output.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <deque>
|
||||
@@ -10,17 +12,15 @@
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include "link/output.hpp"
|
||||
#include "error.hpp"
|
||||
#include "extern/utf8decoder.hpp"
|
||||
#include "itertools.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "link/main.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "itertools.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#define BANK_SIZE 0x4000
|
||||
|
||||
FILE *outputFile;
|
||||
@@ -42,42 +42,45 @@ static std::deque<SortedSections> sections[SECTTYPE_INVALID];
|
||||
|
||||
// Defines the order in which types are output to the sym and map files
|
||||
static enum SectionType typeMap[SECTTYPE_INVALID] = {
|
||||
SECTTYPE_ROM0,
|
||||
SECTTYPE_ROMX,
|
||||
SECTTYPE_VRAM,
|
||||
SECTTYPE_SRAM,
|
||||
SECTTYPE_WRAM0,
|
||||
SECTTYPE_WRAMX,
|
||||
SECTTYPE_OAM,
|
||||
SECTTYPE_HRAM
|
||||
SECTTYPE_ROM0,
|
||||
SECTTYPE_ROMX,
|
||||
SECTTYPE_VRAM,
|
||||
SECTTYPE_SRAM,
|
||||
SECTTYPE_WRAM0,
|
||||
SECTTYPE_WRAMX,
|
||||
SECTTYPE_OAM,
|
||||
SECTTYPE_HRAM,
|
||||
};
|
||||
|
||||
void out_AddSection(Section const §ion)
|
||||
{
|
||||
void out_AddSection(Section const §ion) {
|
||||
static const uint32_t maxNbBanks[SECTTYPE_INVALID] = {
|
||||
1, // SECTTYPE_WRAM0
|
||||
2, // SECTTYPE_VRAM
|
||||
UINT32_MAX, // SECTTYPE_ROMX
|
||||
1, // SECTTYPE_ROM0
|
||||
1, // SECTTYPE_HRAM
|
||||
7, // SECTTYPE_WRAMX
|
||||
UINT32_MAX, // SECTTYPE_SRAM
|
||||
1, // SECTTYPE_OAM
|
||||
1, // SECTTYPE_WRAM0
|
||||
2, // SECTTYPE_VRAM
|
||||
UINT32_MAX, // SECTTYPE_ROMX
|
||||
1, // SECTTYPE_ROM0
|
||||
1, // SECTTYPE_HRAM
|
||||
7, // SECTTYPE_WRAMX
|
||||
UINT32_MAX, // SECTTYPE_SRAM
|
||||
1, // SECTTYPE_OAM
|
||||
};
|
||||
|
||||
uint32_t targetBank = section.bank - sectionTypeInfo[section.type].firstBank;
|
||||
uint32_t minNbBanks = targetBank + 1;
|
||||
|
||||
if (minNbBanks > maxNbBanks[section.type])
|
||||
errx("Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
|
||||
section.name.c_str(), section.bank, maxNbBanks[section.type] - 1);
|
||||
errx(
|
||||
"Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
|
||||
section.name.c_str(),
|
||||
section.bank,
|
||||
maxNbBanks[section.type] - 1
|
||||
);
|
||||
|
||||
for (uint32_t i = sections[section.type].size(); i < minNbBanks; i++)
|
||||
sections[section.type].emplace_back();
|
||||
|
||||
std::deque<Section const *> &bankSections = section.size
|
||||
? sections[section.type][targetBank].sections
|
||||
: sections[section.type][targetBank].zeroLenSections;
|
||||
std::deque<Section const *> &bankSections =
|
||||
section.size ? sections[section.type][targetBank].sections
|
||||
: sections[section.type][targetBank].zeroLenSections;
|
||||
auto pos = bankSections.begin();
|
||||
|
||||
while (pos != bankSections.end() && (*pos)->org < section.org)
|
||||
@@ -86,8 +89,7 @@ void out_AddSection(Section const §ion)
|
||||
bankSections.insert(pos, §ion);
|
||||
}
|
||||
|
||||
Section const *out_OverlappingSection(Section const §ion)
|
||||
{
|
||||
Section const *out_OverlappingSection(Section const §ion) {
|
||||
uint32_t bank = section.bank - sectionTypeInfo[section.type].firstBank;
|
||||
|
||||
for (Section const *ptr : sections[section.type][bank].sections) {
|
||||
@@ -101,8 +103,7 @@ Section const *out_OverlappingSection(Section const §ion)
|
||||
* Performs sanity checks on the overlay file.
|
||||
* @return The number of ROM banks in the overlay file
|
||||
*/
|
||||
static uint32_t checkOverlaySize()
|
||||
{
|
||||
static uint32_t checkOverlaySize() {
|
||||
if (!overlayFile)
|
||||
return 0;
|
||||
|
||||
@@ -136,14 +137,13 @@ static uint32_t checkOverlaySize()
|
||||
* covered by any sections.
|
||||
* @param nbOverlayBanks The number of banks in the overlay file
|
||||
*/
|
||||
static void coverOverlayBanks(uint32_t nbOverlayBanks)
|
||||
{
|
||||
static void coverOverlayBanks(uint32_t nbOverlayBanks) {
|
||||
// 2 if is32kMode, 1 otherwise
|
||||
uint32_t nbRom0Banks = sectionTypeInfo[SECTTYPE_ROM0].size / BANK_SIZE;
|
||||
// Discount ROM0 banks to avoid outputting too much
|
||||
uint32_t nbUncoveredBanks = nbOverlayBanks - nbRom0Banks > sections[SECTTYPE_ROMX].size()
|
||||
? nbOverlayBanks - nbRom0Banks
|
||||
: 0;
|
||||
? nbOverlayBanks - nbRom0Banks
|
||||
: 0;
|
||||
|
||||
if (nbUncoveredBanks > sections[SECTTYPE_ROMX].size()) {
|
||||
for (uint32_t i = sections[SECTTYPE_ROMX].size(); i < nbUncoveredBanks; i++)
|
||||
@@ -157,8 +157,8 @@ static void coverOverlayBanks(uint32_t nbOverlayBanks)
|
||||
* @param baseOffset The address of the bank's first byte in GB address space
|
||||
* @param size The size of the bank
|
||||
*/
|
||||
static void writeBank(std::deque<Section const *> *bankSections, uint16_t baseOffset, uint16_t size)
|
||||
{
|
||||
static void
|
||||
writeBank(std::deque<Section const *> *bankSections, uint16_t baseOffset, uint16_t size) {
|
||||
uint16_t offset = 0;
|
||||
|
||||
if (bankSections) {
|
||||
@@ -190,8 +190,7 @@ static void writeBank(std::deque<Section const *> *bankSections, uint16_t baseOf
|
||||
}
|
||||
|
||||
// Writes a ROM file to the output.
|
||||
static void writeROM()
|
||||
{
|
||||
static void writeROM() {
|
||||
if (outputFileName) {
|
||||
if (strcmp(outputFileName, "-")) {
|
||||
outputFile = fopen(outputFileName, "wb");
|
||||
@@ -220,12 +219,18 @@ static void writeROM()
|
||||
coverOverlayBanks(nbOverlayBanks);
|
||||
|
||||
if (outputFile) {
|
||||
writeBank(!sections[SECTTYPE_ROM0].empty() ? §ions[SECTTYPE_ROM0][0].sections : nullptr,
|
||||
sectionTypeInfo[SECTTYPE_ROM0].startAddr, sectionTypeInfo[SECTTYPE_ROM0].size);
|
||||
writeBank(
|
||||
!sections[SECTTYPE_ROM0].empty() ? §ions[SECTTYPE_ROM0][0].sections : nullptr,
|
||||
sectionTypeInfo[SECTTYPE_ROM0].startAddr,
|
||||
sectionTypeInfo[SECTTYPE_ROM0].size
|
||||
);
|
||||
|
||||
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].size(); i++)
|
||||
writeBank(§ions[SECTTYPE_ROMX][i].sections,
|
||||
sectionTypeInfo[SECTTYPE_ROMX].startAddr, sectionTypeInfo[SECTTYPE_ROMX].size);
|
||||
for (uint32_t i = 0; i < sections[SECTTYPE_ROMX].size(); i++)
|
||||
writeBank(
|
||||
§ions[SECTTYPE_ROMX][i].sections,
|
||||
sectionTypeInfo[SECTTYPE_ROMX].startAddr,
|
||||
sectionTypeInfo[SECTTYPE_ROMX].size
|
||||
);
|
||||
}
|
||||
|
||||
if (outputFile)
|
||||
@@ -235,23 +240,20 @@ static void writeROM()
|
||||
}
|
||||
|
||||
// Checks whether this character is legal as the first character of a symbol's name in a sym file
|
||||
static bool canStartSymName(char c)
|
||||
{
|
||||
static bool canStartSymName(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
|
||||
}
|
||||
|
||||
// Checks whether this character is legal in a symbol's name in a sym file
|
||||
static bool isLegalForSymName(char c)
|
||||
{
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
||||
c == '_' || c == '@' || c == '#' || c == '$' || c == '.';
|
||||
static bool isLegalForSymName(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'
|
||||
|| c == '@' || c == '#' || c == '$' || c == '.';
|
||||
}
|
||||
|
||||
// Prints a symbol's name to `symFile`, assuming that the first character is legal.
|
||||
// Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as `\u`/`\U`.
|
||||
static void printSymName(char const *name)
|
||||
{
|
||||
for (char const *ptr = name; *ptr != '\0'; ) {
|
||||
static void printSymName(char const *name) {
|
||||
for (char const *ptr = name; *ptr != '\0';) {
|
||||
char c = *ptr;
|
||||
|
||||
if (isLegalForSymName(c)) {
|
||||
@@ -277,16 +279,14 @@ static void printSymName(char const *name)
|
||||
++ptr;
|
||||
} while (state != 0);
|
||||
|
||||
fprintf(symFile, codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32,
|
||||
codepoint);
|
||||
fprintf(symFile, codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32, codepoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comparator function for `std::stable_sort` to sort symbols
|
||||
// Symbols are ordered by address, then by parentage
|
||||
static int compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2)
|
||||
{
|
||||
static int compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2) {
|
||||
if (sym1.addr != sym2.addr)
|
||||
return sym1.addr < sym2.addr ? -1 : 1;
|
||||
|
||||
@@ -315,24 +315,24 @@ static int compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2)
|
||||
* Write a bank's contents to the sym file
|
||||
* @param bankSections The bank's sections
|
||||
*/
|
||||
static void writeSymBank(SortedSections const &bankSections, enum SectionType type, uint32_t bank)
|
||||
{
|
||||
#define forEachSortedSection(sect, ...) do { \
|
||||
for (auto it = bankSections.zeroLenSections.begin(); it != bankSections.zeroLenSections.end(); it++) { \
|
||||
for (Section const *sect = *it; sect; sect = sect->nextu) \
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
for (auto it = bankSections.sections.begin(); it != bankSections.sections.end(); it++) { \
|
||||
for (Section const *sect = *it; sect; sect = sect->nextu) \
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
} while (0)
|
||||
static void writeSymBank(SortedSections const &bankSections, enum SectionType type, uint32_t bank) {
|
||||
#define forEachSortedSection(sect, ...) \
|
||||
do { \
|
||||
for (auto it = bankSections.zeroLenSections.begin(); \
|
||||
it != bankSections.zeroLenSections.end(); \
|
||||
it++) { \
|
||||
for (Section const *sect = *it; sect; sect = sect->nextu) \
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
for (auto it = bankSections.sections.begin(); it != bankSections.sections.end(); it++) { \
|
||||
for (Section const *sect = *it; sect; sect = sect->nextu) \
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
uint32_t nbSymbols = 0;
|
||||
|
||||
forEachSortedSection(sect, {
|
||||
nbSymbols += sect->symbols.size();
|
||||
});
|
||||
forEachSortedSection(sect, { nbSymbols += sect->symbols.size(); });
|
||||
|
||||
if (!nbSymbols)
|
||||
return;
|
||||
@@ -345,10 +345,8 @@ static void writeSymBank(SortedSections const &bankSections, enum SectionType ty
|
||||
for (Symbol const *sym : sect->symbols) {
|
||||
// Don't output symbols that begin with an illegal character
|
||||
if (!sym->name.empty() && canStartSymName(sym->name[0]))
|
||||
symList.push_back({
|
||||
.sym = sym,
|
||||
.addr = (uint16_t)(sym->label().offset + sect->org)
|
||||
});
|
||||
symList.push_back({.sym = sym, .addr = (uint16_t)(sym->label().offset + sect->org)}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -365,23 +363,31 @@ static void writeSymBank(SortedSections const &bankSections, enum SectionType ty
|
||||
}
|
||||
}
|
||||
|
||||
static void writeEmptySpace(uint16_t begin, uint16_t end)
|
||||
{
|
||||
static void writeEmptySpace(uint16_t begin, uint16_t end) {
|
||||
if (begin < end) {
|
||||
uint16_t len = end - begin;
|
||||
|
||||
fprintf(mapFile, "\tEMPTY: $%04x-$%04x ($%04" PRIx16 " byte%s)\n",
|
||||
begin, end - 1, len, len == 1 ? "" : "s");
|
||||
fprintf(
|
||||
mapFile,
|
||||
"\tEMPTY: $%04x-$%04x ($%04" PRIx16 " byte%s)\n",
|
||||
begin,
|
||||
end - 1,
|
||||
len,
|
||||
len == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a bank's contents to the map file
|
||||
*/
|
||||
static void writeMapBank(SortedSections const §List, enum SectionType type, uint32_t bank)
|
||||
{
|
||||
fprintf(mapFile, "\n%s bank #%" PRIu32 ":\n", sectionTypeInfo[type].name.c_str(),
|
||||
bank + sectionTypeInfo[type].firstBank);
|
||||
static void writeMapBank(SortedSections const §List, enum SectionType type, uint32_t bank) {
|
||||
fprintf(
|
||||
mapFile,
|
||||
"\n%s bank #%" PRIu32 ":\n",
|
||||
sectionTypeInfo[type].name.c_str(),
|
||||
bank + sectionTypeInfo[type].firstBank
|
||||
);
|
||||
|
||||
uint16_t used = 0;
|
||||
auto section = sectList.sections.begin();
|
||||
@@ -390,9 +396,10 @@ static void writeMapBank(SortedSections const §List, enum SectionType type,
|
||||
|
||||
while (section != sectList.sections.end() || zeroLenSection != sectList.zeroLenSections.end()) {
|
||||
// Pick the lowest section by address out of the two
|
||||
auto &pickedSection = section == sectList.sections.end() ? zeroLenSection
|
||||
: zeroLenSection == sectList.zeroLenSections.end() ? section
|
||||
: (*section)->org < (*zeroLenSection)->org ? section : zeroLenSection;
|
||||
auto &pickedSection = section == sectList.sections.end() ? zeroLenSection
|
||||
: zeroLenSection == sectList.zeroLenSections.end() ? section
|
||||
: (*section)->org < (*zeroLenSection)->org ? section
|
||||
: zeroLenSection;
|
||||
Section const *sect = *pickedSection;
|
||||
|
||||
used += sect->size;
|
||||
@@ -403,31 +410,41 @@ static void writeMapBank(SortedSections const §List, enum SectionType type,
|
||||
prevEndAddr = sect->org + sect->size;
|
||||
|
||||
if (sect->size != 0)
|
||||
fprintf(mapFile, "\tSECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16
|
||||
" byte%s) [\"%s\"]\n",
|
||||
sect->org, prevEndAddr - 1, sect->size, sect->size == 1 ? "" : "s",
|
||||
sect->name.c_str());
|
||||
fprintf(
|
||||
mapFile,
|
||||
"\tSECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16 " byte%s) [\"%s\"]\n",
|
||||
sect->org,
|
||||
prevEndAddr - 1,
|
||||
sect->size,
|
||||
sect->size == 1 ? "" : "s",
|
||||
sect->name.c_str()
|
||||
);
|
||||
else
|
||||
fprintf(mapFile, "\tSECTION: $%04" PRIx16 " (0 bytes) [\"%s\"]\n",
|
||||
sect->org, sect->name.c_str());
|
||||
fprintf(
|
||||
mapFile,
|
||||
"\tSECTION: $%04" PRIx16 " (0 bytes) [\"%s\"]\n",
|
||||
sect->org,
|
||||
sect->name.c_str()
|
||||
);
|
||||
|
||||
if (!noSymInMap) {
|
||||
// Also print symbols in the following "pieces"
|
||||
for (uint16_t org = sect->org; sect; sect = sect->nextu) {
|
||||
for (Symbol *sym : sect->symbols)
|
||||
// Space matches "\tSECTION: $xxxx ..."
|
||||
fprintf(mapFile, "\t $%04" PRIx32 " = %s\n",
|
||||
sym->label().offset + org,
|
||||
sym->name.c_str());
|
||||
fprintf(
|
||||
mapFile,
|
||||
"\t $%04" PRIx32 " = %s\n",
|
||||
sym->label().offset + org,
|
||||
sym->name.c_str()
|
||||
);
|
||||
|
||||
if (sect->nextu) {
|
||||
// Announce the following "piece"
|
||||
if (sect->nextu->modifier == SECTION_UNION)
|
||||
fprintf(mapFile,
|
||||
"\t ; Next union\n");
|
||||
fprintf(mapFile, "\t ; Next union\n");
|
||||
else if (sect->nextu->modifier == SECTION_FRAGMENT)
|
||||
fprintf(mapFile,
|
||||
"\t ; Next fragment\n");
|
||||
fprintf(mapFile, "\t ; Next fragment\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -444,16 +461,14 @@ static void writeMapBank(SortedSections const §List, enum SectionType type,
|
||||
|
||||
uint16_t slack = sectionTypeInfo[type].size - used;
|
||||
|
||||
fprintf(mapFile, "\tTOTAL EMPTY: $%04" PRIx16 " byte%s\n", slack,
|
||||
slack == 1 ? "" : "s");
|
||||
fprintf(mapFile, "\tTOTAL EMPTY: $%04" PRIx16 " byte%s\n", slack, slack == 1 ? "" : "s");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the total used and free space by section type to the map file
|
||||
*/
|
||||
static void writeMapSummary()
|
||||
{
|
||||
static void writeMapSummary() {
|
||||
fputs("SUMMARY:\n", mapFile);
|
||||
|
||||
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
|
||||
@@ -480,9 +495,9 @@ static void writeMapSummary()
|
||||
|| zeroLenSection != sectList.zeroLenSections.end()) {
|
||||
// Pick the lowest section by address out of the two
|
||||
auto &pickedSection = section == sectList.sections.end() ? zeroLenSection
|
||||
: zeroLenSection == sectList.zeroLenSections.end() ? section
|
||||
: (*section)->org < (*zeroLenSection)->org ? section
|
||||
: zeroLenSection;
|
||||
: zeroLenSection == sectList.zeroLenSections.end() ? section
|
||||
: (*section)->org < (*zeroLenSection)->org ? section
|
||||
: zeroLenSection;
|
||||
|
||||
used += (*pickedSection)->size;
|
||||
pickedSection++;
|
||||
@@ -491,19 +506,22 @@ static void writeMapSummary()
|
||||
usedTotal += used;
|
||||
}
|
||||
|
||||
fprintf(mapFile, "\t%s: %" PRId32 " byte%s used / %" PRId32 " free",
|
||||
sectionTypeInfo[type].name.c_str(), usedTotal, usedTotal == 1 ? "" : "s",
|
||||
nbBanks * sectionTypeInfo[type].size - usedTotal);
|
||||
if (sectionTypeInfo[type].firstBank != sectionTypeInfo[type].lastBank
|
||||
|| nbBanks > 1)
|
||||
fprintf(
|
||||
mapFile,
|
||||
"\t%s: %" PRId32 " byte%s used / %" PRId32 " free",
|
||||
sectionTypeInfo[type].name.c_str(),
|
||||
usedTotal,
|
||||
usedTotal == 1 ? "" : "s",
|
||||
nbBanks * sectionTypeInfo[type].size - usedTotal
|
||||
);
|
||||
if (sectionTypeInfo[type].firstBank != sectionTypeInfo[type].lastBank || nbBanks > 1)
|
||||
fprintf(mapFile, " in %u bank%s", nbBanks, nbBanks == 1 ? "" : "s");
|
||||
putc('\n', mapFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Writes the sym file, if applicable.
|
||||
static void writeSym()
|
||||
{
|
||||
static void writeSym() {
|
||||
if (!symFileName)
|
||||
return;
|
||||
|
||||
@@ -529,8 +547,7 @@ static void writeSym()
|
||||
}
|
||||
|
||||
// Writes the map file, if applicable.
|
||||
static void writeMap()
|
||||
{
|
||||
static void writeMap() {
|
||||
if (!mapFileName)
|
||||
return;
|
||||
|
||||
@@ -555,8 +572,7 @@ static void writeMap()
|
||||
fclose(mapFile);
|
||||
}
|
||||
|
||||
void out_WriteFiles()
|
||||
{
|
||||
void out_WriteFiles() {
|
||||
writeROM();
|
||||
writeSym();
|
||||
writeMap();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "link/patch.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <deque>
|
||||
#include <inttypes.h>
|
||||
@@ -8,17 +10,16 @@
|
||||
#include <string.h>
|
||||
#include <variant>
|
||||
|
||||
#include "link/object.hpp"
|
||||
#include "link/patch.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
#include "opmath.hpp"
|
||||
#include "platform.hpp"
|
||||
|
||||
#include "link/object.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
struct RPNStackEntry {
|
||||
int32_t value;
|
||||
bool errorFlag; // Whether the value is a placeholder inserted for error recovery
|
||||
@@ -26,17 +27,15 @@ struct RPNStackEntry {
|
||||
|
||||
std::deque<RPNStackEntry> rpnStack;
|
||||
|
||||
static void pushRPN(int32_t value, bool comesFromError)
|
||||
{
|
||||
rpnStack.push_front({ .value = value, .errorFlag = comesFromError });
|
||||
static void pushRPN(int32_t value, bool comesFromError) {
|
||||
rpnStack.push_front({.value = value, .errorFlag = comesFromError});
|
||||
}
|
||||
|
||||
// This flag tracks whether the RPN op that is currently being evaluated
|
||||
// has popped any values with the error flag set.
|
||||
static bool isError = false;
|
||||
|
||||
static int32_t popRPN(FileStackNode const *node, uint32_t lineNo)
|
||||
{
|
||||
static int32_t popRPN(FileStackNode const *node, uint32_t lineNo) {
|
||||
if (rpnStack.empty())
|
||||
fatal(node, lineNo, "Internal error, RPN stack empty");
|
||||
|
||||
@@ -49,17 +48,16 @@ static int32_t popRPN(FileStackNode const *node, uint32_t lineNo)
|
||||
|
||||
// RPN operators
|
||||
|
||||
static uint32_t getRPNByte(uint8_t const *&expression, int32_t &size,
|
||||
FileStackNode const *node, uint32_t lineNo)
|
||||
{
|
||||
static uint32_t getRPNByte(
|
||||
uint8_t const *&expression, int32_t &size, FileStackNode const *node, uint32_t lineNo
|
||||
) {
|
||||
if (!size--)
|
||||
fatal(node, lineNo, "Internal error, RPN expression overread");
|
||||
|
||||
return *expression++;
|
||||
}
|
||||
|
||||
static Symbol const *getSymbol(std::vector<Symbol> const &symbolList, uint32_t index)
|
||||
{
|
||||
static Symbol const *getSymbol(std::vector<Symbol> const &symbolList, uint32_t index) {
|
||||
assert(index != (uint32_t)-1); // PC needs to be handled specially, not here
|
||||
Symbol const &symbol = symbolList[index];
|
||||
|
||||
@@ -78,8 +76,7 @@ static Symbol const *getSymbol(std::vector<Symbol> const &symbolList, uint32_t i
|
||||
* @return isError Set if an error occurred during evaluation, and further
|
||||
* errors caused by the value should be suppressed.
|
||||
*/
|
||||
static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fileSymbols)
|
||||
{
|
||||
static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fileSymbols) {
|
||||
// Small shortcut to avoid a lot of repetition
|
||||
#define popRPN() popRPN(patch.src, patch.lineNo)
|
||||
|
||||
@@ -89,8 +86,8 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
rpnStack.clear();
|
||||
|
||||
while (size > 0) {
|
||||
enum RPNCommand command = (enum RPNCommand)getRPNByte(expression, size,
|
||||
patch.src, patch.lineNo);
|
||||
enum RPNCommand command =
|
||||
(enum RPNCommand)getRPNByte(expression, size, patch.src, patch.lineNo);
|
||||
int32_t value;
|
||||
|
||||
isError = false;
|
||||
@@ -218,22 +215,27 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
case RPN_BANK_SYM:
|
||||
value = 0;
|
||||
for (uint8_t shift = 0; shift < 32; shift += 8)
|
||||
value |= getRPNByte(expression, size,
|
||||
patch.src, patch.lineNo) << shift;
|
||||
value |= getRPNByte(expression, size, patch.src, patch.lineNo) << shift;
|
||||
symbol = getSymbol(fileSymbols, value);
|
||||
|
||||
if (!symbol) {
|
||||
error(patch.src, patch.lineNo,
|
||||
"Requested BANK() of symbol \"%s\", which was not found",
|
||||
fileSymbols[value].name.c_str());
|
||||
error(
|
||||
patch.src,
|
||||
patch.lineNo,
|
||||
"Requested BANK() of symbol \"%s\", which was not found",
|
||||
fileSymbols[value].name.c_str()
|
||||
);
|
||||
isError = true;
|
||||
value = 1;
|
||||
} else if (Label const *label = std::get_if<Label>(&symbol->data); label) {
|
||||
value = label->section->bank;
|
||||
} else {
|
||||
error(patch.src, patch.lineNo,
|
||||
"Requested BANK() of non-label symbol \"%s\"",
|
||||
fileSymbols[value].name.c_str());
|
||||
error(
|
||||
patch.src,
|
||||
patch.lineNo,
|
||||
"Requested BANK() of non-label symbol \"%s\"",
|
||||
fileSymbols[value].name.c_str()
|
||||
);
|
||||
isError = true;
|
||||
value = 1;
|
||||
}
|
||||
@@ -249,9 +251,12 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
sect = sect_GetSection(name);
|
||||
|
||||
if (!sect) {
|
||||
error(patch.src, patch.lineNo,
|
||||
"Requested BANK() of section \"%s\", which was not found",
|
||||
name);
|
||||
error(
|
||||
patch.src,
|
||||
patch.lineNo,
|
||||
"Requested BANK() of section \"%s\", which was not found",
|
||||
name
|
||||
);
|
||||
isError = true;
|
||||
value = 1;
|
||||
} else {
|
||||
@@ -261,8 +266,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
|
||||
case RPN_BANK_SELF:
|
||||
if (!patch.pcSection) {
|
||||
error(patch.src, patch.lineNo,
|
||||
"PC has no bank outside a section");
|
||||
error(patch.src, patch.lineNo, "PC has no bank outside a section");
|
||||
isError = true;
|
||||
value = 1;
|
||||
} else {
|
||||
@@ -279,9 +283,12 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
sect = sect_GetSection(name);
|
||||
|
||||
if (!sect) {
|
||||
error(patch.src, patch.lineNo,
|
||||
"Requested SIZEOF() of section \"%s\", which was not found",
|
||||
name);
|
||||
error(
|
||||
patch.src,
|
||||
patch.lineNo,
|
||||
"Requested SIZEOF() of section \"%s\", which was not found",
|
||||
name
|
||||
);
|
||||
isError = true;
|
||||
value = 1;
|
||||
} else {
|
||||
@@ -299,9 +306,12 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
assert(sect->offset == 0);
|
||||
|
||||
if (!sect) {
|
||||
error(patch.src, patch.lineNo,
|
||||
"Requested STARTOF() of section \"%s\", which was not found",
|
||||
name);
|
||||
error(
|
||||
patch.src,
|
||||
patch.lineNo,
|
||||
"Requested STARTOF() of section \"%s\", which was not found",
|
||||
name
|
||||
);
|
||||
isError = true;
|
||||
value = 1;
|
||||
} else {
|
||||
@@ -312,8 +322,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
case RPN_SIZEOF_SECTTYPE:
|
||||
value = getRPNByte(expression, size, patch.src, patch.lineNo);
|
||||
if (value < 0 || value >= SECTTYPE_INVALID) {
|
||||
error(patch.src, patch.lineNo,
|
||||
"Requested SIZEOF() an invalid section type");
|
||||
error(patch.src, patch.lineNo, "Requested SIZEOF() an invalid section type");
|
||||
isError = true;
|
||||
value = 0;
|
||||
} else {
|
||||
@@ -324,8 +333,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
case RPN_STARTOF_SECTTYPE:
|
||||
value = getRPNByte(expression, size, patch.src, patch.lineNo);
|
||||
if (value < 0 || value >= SECTTYPE_INVALID) {
|
||||
error(patch.src, patch.lineNo,
|
||||
"Requested STARTOF() an invalid section type");
|
||||
error(patch.src, patch.lineNo, "Requested STARTOF() an invalid section type");
|
||||
isError = true;
|
||||
value = 0;
|
||||
} else {
|
||||
@@ -335,11 +343,8 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
|
||||
case RPN_HRAM:
|
||||
value = popRPN();
|
||||
if (!isError && (value < 0
|
||||
|| (value > 0xFF && value < 0xFF00)
|
||||
|| value > 0xFFFF)) {
|
||||
error(patch.src, patch.lineNo,
|
||||
"Value %" PRId32 " is not in HRAM range", value);
|
||||
if (!isError && (value < 0 || (value > 0xFF && value < 0xFF00) || value > 0xFFFF)) {
|
||||
error(patch.src, patch.lineNo, "Value %" PRId32 " is not in HRAM range", value);
|
||||
isError = true;
|
||||
}
|
||||
value &= 0xFF;
|
||||
@@ -351,8 +356,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
// They can be easily checked with a bitmask
|
||||
if (value & ~0x38) {
|
||||
if (!isError)
|
||||
error(patch.src, patch.lineNo,
|
||||
"Value %" PRId32 " is not a RST vector", value);
|
||||
error(patch.src, patch.lineNo, "Value %" PRId32 " is not a RST vector", value);
|
||||
isError = true;
|
||||
}
|
||||
value |= 0xC7;
|
||||
@@ -361,20 +365,17 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
case RPN_CONST:
|
||||
value = 0;
|
||||
for (uint8_t shift = 0; shift < 32; shift += 8)
|
||||
value |= getRPNByte(expression, size,
|
||||
patch.src, patch.lineNo) << shift;
|
||||
value |= getRPNByte(expression, size, patch.src, patch.lineNo) << shift;
|
||||
break;
|
||||
|
||||
case RPN_SYM:
|
||||
value = 0;
|
||||
for (uint8_t shift = 0; shift < 32; shift += 8)
|
||||
value |= getRPNByte(expression, size,
|
||||
patch.src, patch.lineNo) << shift;
|
||||
value |= getRPNByte(expression, size, patch.src, patch.lineNo) << shift;
|
||||
|
||||
if (value == -1) { // PC
|
||||
if (!patch.pcSection) {
|
||||
error(patch.src, patch.lineNo,
|
||||
"PC has no value outside a section");
|
||||
error(patch.src, patch.lineNo, "PC has no value outside a section");
|
||||
value = 0;
|
||||
isError = true;
|
||||
} else {
|
||||
@@ -384,16 +385,23 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
symbol = getSymbol(fileSymbols, value);
|
||||
|
||||
if (!symbol) {
|
||||
error(patch.src, patch.lineNo,
|
||||
"Unknown symbol \"%s\"", fileSymbols[value].name.c_str());
|
||||
error(
|
||||
patch.src,
|
||||
patch.lineNo,
|
||||
"Unknown symbol \"%s\"",
|
||||
fileSymbols[value].name.c_str()
|
||||
);
|
||||
isError = true;
|
||||
} else {
|
||||
value = std::visit(Visitor{
|
||||
[](int32_t val) -> int32_t { return val; },
|
||||
[](Label label) -> int32_t {
|
||||
return label.section->org + label.offset;
|
||||
}
|
||||
}, symbol->data);
|
||||
value = std::visit(
|
||||
Visitor{
|
||||
[](int32_t val) -> int32_t { return val; },
|
||||
[](Label label) -> int32_t {
|
||||
return label.section->org + label.offset;
|
||||
},
|
||||
},
|
||||
symbol->data
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -403,8 +411,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
}
|
||||
|
||||
if (rpnStack.size() > 1)
|
||||
error(patch.src, patch.lineNo,
|
||||
"RPN stack has %zu entries on exit, not 1", rpnStack.size());
|
||||
error(patch.src, patch.lineNo, "RPN stack has %zu entries on exit, not 1", rpnStack.size());
|
||||
|
||||
isError = false;
|
||||
return popRPN();
|
||||
@@ -412,8 +419,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
#undef popRPN
|
||||
}
|
||||
|
||||
void patch_CheckAssertions(std::deque<Assertion> &assertions)
|
||||
{
|
||||
void patch_CheckAssertions(std::deque<Assertion> &assertions) {
|
||||
verbosePrint("Checking assertions...\n");
|
||||
|
||||
for (Assertion &assert : assertions) {
|
||||
@@ -423,24 +429,37 @@ void patch_CheckAssertions(std::deque<Assertion> &assertions)
|
||||
if (!isError && !value) {
|
||||
switch (type) {
|
||||
case ASSERT_FATAL:
|
||||
fatal(assert.patch.src, assert.patch.lineNo, "%s",
|
||||
!assert.message.empty() ? assert.message.c_str()
|
||||
: "assert failure");
|
||||
fatal(
|
||||
assert.patch.src,
|
||||
assert.patch.lineNo,
|
||||
"%s",
|
||||
!assert.message.empty() ? assert.message.c_str() : "assert failure"
|
||||
);
|
||||
case ASSERT_ERROR:
|
||||
error(assert.patch.src, assert.patch.lineNo, "%s",
|
||||
!assert.message.empty() ? assert.message.c_str()
|
||||
: "assert failure");
|
||||
error(
|
||||
assert.patch.src,
|
||||
assert.patch.lineNo,
|
||||
"%s",
|
||||
!assert.message.empty() ? assert.message.c_str() : "assert failure"
|
||||
);
|
||||
break;
|
||||
case ASSERT_WARN:
|
||||
warning(assert.patch.src, assert.patch.lineNo, "%s",
|
||||
!assert.message.empty() ? assert.message.c_str()
|
||||
: "assert failure");
|
||||
warning(
|
||||
assert.patch.src,
|
||||
assert.patch.lineNo,
|
||||
"%s",
|
||||
!assert.message.empty() ? assert.message.c_str() : "assert failure"
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else if (isError && type == ASSERT_FATAL) {
|
||||
fatal(assert.patch.src, assert.patch.lineNo,
|
||||
"Failed to evaluate assertion%s%s",
|
||||
!assert.message.empty() ? ": " : "", assert.message.c_str());
|
||||
fatal(
|
||||
assert.patch.src,
|
||||
assert.patch.lineNo,
|
||||
"Failed to evaluate assertion%s%s",
|
||||
!assert.message.empty() ? ": " : "",
|
||||
assert.message.c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -450,8 +469,7 @@ void patch_CheckAssertions(std::deque<Assertion> &assertions)
|
||||
* @param section The section component to patch
|
||||
* @param dataSection The section to patch
|
||||
*/
|
||||
static void applyFilePatches(Section §ion, Section &dataSection)
|
||||
{
|
||||
static void applyFilePatches(Section §ion, Section &dataSection) {
|
||||
verbosePrint("Patching section \"%s\"...\n", section.name.c_str());
|
||||
for (Patch &patch : section.patches) {
|
||||
int32_t value = computeRPNExpr(patch, *section.fileSymbols);
|
||||
@@ -465,9 +483,12 @@ static void applyFilePatches(Section §ion, Section &dataSection)
|
||||
int16_t jumpOffset = value - address;
|
||||
|
||||
if (!isError && (jumpOffset < -128 || jumpOffset > 127))
|
||||
error(patch.src, patch.lineNo,
|
||||
"jr target out of reach (expected -129 < %" PRId16 " < 128)",
|
||||
jumpOffset);
|
||||
error(
|
||||
patch.src,
|
||||
patch.lineNo,
|
||||
"jr target out of reach (expected -129 < %" PRId16 " < 128)",
|
||||
jumpOffset
|
||||
);
|
||||
dataSection.data[offset] = jumpOffset & 0xFF;
|
||||
} else {
|
||||
// Patch a certain number of bytes
|
||||
@@ -476,17 +497,20 @@ static void applyFilePatches(Section §ion, Section &dataSection)
|
||||
int32_t min;
|
||||
int32_t max;
|
||||
} const types[PATCHTYPE_INVALID] = {
|
||||
{ 1, -128, 255 }, // PATCHTYPE_BYTE
|
||||
{ 2, -32768, 65536 }, // PATCHTYPE_WORD
|
||||
{ 4, INT32_MIN, INT32_MAX }, // PATCHTYPE_LONG
|
||||
{1, -128, 255 }, // PATCHTYPE_BYTE
|
||||
{2, -32768, 65536 }, // PATCHTYPE_WORD
|
||||
{4, INT32_MIN, INT32_MAX}, // PATCHTYPE_LONG
|
||||
};
|
||||
|
||||
if (!isError && (value < types[patch.type].min
|
||||
|| value > types[patch.type].max))
|
||||
error(patch.src, patch.lineNo,
|
||||
"Value %" PRId32 "%s is not %u-bit",
|
||||
value, value < 0 ? " (maybe negative?)" : "",
|
||||
types[patch.type].size * 8U);
|
||||
if (!isError && (value < types[patch.type].min || value > types[patch.type].max))
|
||||
error(
|
||||
patch.src,
|
||||
patch.lineNo,
|
||||
"Value %" PRId32 "%s is not %u-bit",
|
||||
value,
|
||||
value < 0 ? " (maybe negative?)" : "",
|
||||
types[patch.type].size * 8U
|
||||
);
|
||||
for (uint8_t i = 0; i < types[patch.type].size; i++) {
|
||||
dataSection.data[offset + i] = value & 0xFF;
|
||||
value >>= 8;
|
||||
@@ -499,8 +523,7 @@ static void applyFilePatches(Section §ion, Section &dataSection)
|
||||
* Applies all of a section's patches, iterating over "components" of unionized sections
|
||||
* @param section The section to patch
|
||||
*/
|
||||
static void applyPatches(Section §ion)
|
||||
{
|
||||
static void applyPatches(Section §ion) {
|
||||
if (!sect_HasData(section.type))
|
||||
return;
|
||||
|
||||
@@ -508,7 +531,6 @@ static void applyPatches(Section §ion)
|
||||
applyFilePatches(*component, section);
|
||||
}
|
||||
|
||||
void patch_ApplyPatches()
|
||||
{
|
||||
void patch_ApplyPatches() {
|
||||
sect_ForEach(applyPatches);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
struct Keyword {
|
||||
std::string_view name;
|
||||
yy::parser::symbol_type (* tokenGen)();
|
||||
yy::parser::symbol_type (*tokenGen)();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,35 +79,75 @@
|
||||
|
||||
%%
|
||||
|
||||
lines: %empty
|
||||
| line lines
|
||||
lines:
|
||||
%empty
|
||||
| line lines
|
||||
;
|
||||
|
||||
line: INCLUDE string newline { includeFile(std::move($2)); } // Note: this additionally increments the line number!
|
||||
| directive newline { incLineNo(); }
|
||||
| newline { incLineNo(); }
|
||||
| error newline { yyerrok; incLineNo(); } // Error recovery.
|
||||
line:
|
||||
INCLUDE string newline {
|
||||
includeFile(std::move($2)); // Note: this additionally increments the line number!
|
||||
}
|
||||
| directive newline {
|
||||
incLineNo();
|
||||
}
|
||||
| newline {
|
||||
incLineNo();
|
||||
}
|
||||
// Error recovery.
|
||||
| error newline {
|
||||
yyerrok;
|
||||
incLineNo();
|
||||
}
|
||||
;
|
||||
|
||||
directive: section_type { setSectionType($1); }
|
||||
| section_type number { setSectionType($1, $2); }
|
||||
| FLOATING { makeAddrFloating(); }
|
||||
| ORG number { setAddr($2); }
|
||||
| ALIGN number { alignTo($2, 0); }
|
||||
| ALIGN number COMMA number { alignTo($2, $4); }
|
||||
| DS number { pad($2); }
|
||||
| string optional { placeSection($1, $2); }
|
||||
directive:
|
||||
section_type {
|
||||
setSectionType($1);
|
||||
}
|
||||
| section_type number {
|
||||
setSectionType($1, $2);
|
||||
}
|
||||
| FLOATING {
|
||||
makeAddrFloating();
|
||||
}
|
||||
| ORG number {
|
||||
setAddr($2);
|
||||
}
|
||||
| ALIGN number {
|
||||
alignTo($2, 0);
|
||||
}
|
||||
| ALIGN number COMMA number {
|
||||
alignTo($2, $4);
|
||||
}
|
||||
| DS number {
|
||||
pad($2);
|
||||
}
|
||||
| string optional {
|
||||
placeSection($1, $2);
|
||||
}
|
||||
;
|
||||
|
||||
optional: %empty { $$ = false; }
|
||||
| OPTIONAL { $$ = true; }
|
||||
optional:
|
||||
%empty {
|
||||
$$ = false;
|
||||
}
|
||||
| OPTIONAL {
|
||||
$$ = true;
|
||||
}
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
#define scriptError(context, fmt, ...) \
|
||||
::error(nullptr, 0, "%s(%" PRIu32 "): " fmt, \
|
||||
context.path.c_str(), context.lineNo __VA_OPT__(,) __VA_ARGS__)
|
||||
::error( \
|
||||
nullptr, \
|
||||
0, \
|
||||
"%s(%" PRIu32 "): " fmt, \
|
||||
context.path.c_str(), \
|
||||
context.lineNo __VA_OPT__(, ) \
|
||||
__VA_ARGS__ \
|
||||
)
|
||||
|
||||
// Lexer.
|
||||
|
||||
@@ -134,8 +174,9 @@ static void includeFile(std::string &&path) {
|
||||
|
||||
if (!newContext.file.open(newContext.path, std::ios_base::in)) {
|
||||
// The order is important: report the error, increment the line number, modify the stack!
|
||||
scriptError(prevContext, "Failed to open included linker script \"%s\"",
|
||||
newContext.path.c_str());
|
||||
scriptError(
|
||||
prevContext, "Failed to open included linker script \"%s\"", newContext.path.c_str()
|
||||
);
|
||||
++prevContext.lineNo;
|
||||
lexerStack.pop_back();
|
||||
} else {
|
||||
@@ -291,9 +332,8 @@ try_again: // Can't use a `do {} while(0)` loop, otherwise compilers (wrongly) t
|
||||
auto strUpperCmp = [](char cmp, char ref) {
|
||||
// `locale::classic()` yields the "C" locale.
|
||||
assert(!std::use_facet<std::ctype<char>>(std::locale::classic())
|
||||
.is(std::ctype_base::lower, ref));
|
||||
return std::use_facet<std::ctype<char>>(std::locale::classic())
|
||||
.toupper(cmp) == ref;
|
||||
.is(std::ctype_base::lower, ref));
|
||||
return std::use_facet<std::ctype<char>>(std::locale::classic()).toupper(cmp) == ref;
|
||||
};
|
||||
|
||||
ident.push_back(c);
|
||||
@@ -351,8 +391,9 @@ static void setSectionType(SectionType type) {
|
||||
auto const &context = lexerStack.back();
|
||||
|
||||
if (nbbanks(type) != 1) {
|
||||
scriptError(context, "A bank number must be specified for %s",
|
||||
sectionTypeInfo[type].name.c_str());
|
||||
scriptError(
|
||||
context, "A bank number must be specified for %s", sectionTypeInfo[type].name.c_str()
|
||||
);
|
||||
// Keep going with a default value for the bank index.
|
||||
}
|
||||
|
||||
@@ -364,12 +405,16 @@ static void setSectionType(SectionType type, uint32_t bank) {
|
||||
auto const &typeInfo = sectionTypeInfo[type];
|
||||
|
||||
if (bank < typeInfo.firstBank) {
|
||||
scriptError(context, "%s bank %" PRIu32 " doesn't exist (the minimum is %" PRIu32 ")",
|
||||
typeInfo.name.c_str(), bank, typeInfo.firstBank);
|
||||
scriptError(
|
||||
context, "%s bank %" PRIu32 " doesn't exist (the minimum is %" PRIu32 ")",
|
||||
typeInfo.name.c_str(), bank, typeInfo.firstBank
|
||||
);
|
||||
bank = typeInfo.firstBank;
|
||||
} else if (bank > typeInfo.lastBank) {
|
||||
scriptError(context, "%s bank %" PRIu32 " doesn't exist (the maximum is %" PRIu32 ")",
|
||||
typeInfo.name.c_str(), bank, typeInfo.lastBank);
|
||||
scriptError(
|
||||
context, "%s bank %" PRIu32 " doesn't exist (the maximum is %" PRIu32 ")",
|
||||
typeInfo.name.c_str(), bank, typeInfo.lastBank
|
||||
);
|
||||
}
|
||||
|
||||
setActiveTypeAndIdx(type, bank - typeInfo.firstBank);
|
||||
@@ -388,8 +433,10 @@ static void setAddr(uint32_t addr) {
|
||||
if (addr < pc) {
|
||||
scriptError(context, "Cannot decrease the current address (from $%04x to $%04x)", pc, addr);
|
||||
} else if (addr > endaddr(activeType)) { // Allow "one past the end" sections.
|
||||
scriptError(context, "Cannot set the current address to $%04" PRIx32 ": %s ends at $%04" PRIx16 "",
|
||||
addr, typeInfo.name.c_str(), endaddr(activeType));
|
||||
scriptError(
|
||||
context, "Cannot set the current address to $%04" PRIx32 ": %s ends at $%04" PRIx16 "",
|
||||
addr, typeInfo.name.c_str(), endaddr(activeType)
|
||||
);
|
||||
pc = endaddr(activeType);
|
||||
} else {
|
||||
pc = addr;
|
||||
@@ -400,7 +447,9 @@ static void setAddr(uint32_t addr) {
|
||||
static void makeAddrFloating() {
|
||||
auto const &context = lexerStack.back();
|
||||
if (activeType == SECTTYPE_INVALID) {
|
||||
scriptError(context, "Cannot make the current address floating: no memory region is active");
|
||||
scriptError(
|
||||
context, "Cannot make the current address floating: no memory region is active"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -423,9 +472,12 @@ static void alignTo(uint32_t alignment, uint32_t alignOfs) {
|
||||
uint32_t alignSize = 1u << alignment;
|
||||
|
||||
if (alignOfs >= alignSize) {
|
||||
scriptError(context, "Cannot align: The alignment offset (%" PRIu32
|
||||
") must be less than alignment size (%" PRIu32 ")",
|
||||
alignOfs, alignSize);
|
||||
scriptError(
|
||||
context,
|
||||
"Cannot align: The alignment offset (%" PRIu32
|
||||
") must be less than alignment size (%" PRIu32 ")",
|
||||
alignOfs, alignSize
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -439,8 +491,9 @@ static void alignTo(uint32_t alignment, uint32_t alignOfs) {
|
||||
auto &pc = curAddr[activeType][activeBankIdx];
|
||||
|
||||
if (alignment > 16) {
|
||||
scriptError(context, "Cannot align: The alignment (%" PRIu32 ") must be less than 16",
|
||||
alignment);
|
||||
scriptError(
|
||||
context, "Cannot align: The alignment (%" PRIu32 ") must be less than 16", alignment
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -451,9 +504,12 @@ static void alignTo(uint32_t alignment, uint32_t alignOfs) {
|
||||
uint32_t alignSize = 1u << alignment;
|
||||
|
||||
if (alignOfs >= alignSize) {
|
||||
scriptError(context, "Cannot align: The alignment offset (%" PRIu32
|
||||
") must be less than alignment size (%" PRIu32 ")",
|
||||
alignOfs, alignSize);
|
||||
scriptError(
|
||||
context,
|
||||
"Cannot align: The alignment offset (%" PRIu32
|
||||
") must be less than alignment size (%" PRIu32 ")",
|
||||
alignOfs, alignSize
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -462,9 +518,12 @@ static void alignTo(uint32_t alignment, uint32_t alignOfs) {
|
||||
}
|
||||
|
||||
if (uint16_t offset = pc - typeInfo.startAddr; length > typeInfo.size - offset) {
|
||||
scriptError(context, "Cannot align: the next suitable address after $%04"
|
||||
PRIx16 " is $%04" PRIx16 ", past $%04" PRIx16,
|
||||
pc, (uint16_t)(pc + length), (uint16_t)(endaddr(activeType) + 1));
|
||||
scriptError(
|
||||
context,
|
||||
"Cannot align: the next suitable address after $%04" PRIx16 " is $%04" PRIx16
|
||||
", past $%04" PRIx16,
|
||||
pc, (uint16_t)(pc + length), (uint16_t)(endaddr(activeType) + 1)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -488,8 +547,11 @@ static void pad(uint32_t length) {
|
||||
|
||||
assert(pc >= typeInfo.startAddr);
|
||||
if (uint16_t offset = pc - typeInfo.startAddr; length + offset > typeInfo.size) {
|
||||
scriptError(context, "Cannot increase the current address by %u bytes: only %u bytes to $%04" PRIx16,
|
||||
length, typeInfo.size - offset, (uint16_t)(endaddr(activeType) + 1));
|
||||
scriptError(
|
||||
context,
|
||||
"Cannot increase the current address by %u bytes: only %u bytes to $%04" PRIx16, length,
|
||||
typeInfo.size - offset, (uint16_t)(endaddr(activeType) + 1)
|
||||
);
|
||||
} else {
|
||||
pc += length;
|
||||
}
|
||||
@@ -498,8 +560,9 @@ static void pad(uint32_t length) {
|
||||
static void placeSection(std::string const &name, bool isOptional) {
|
||||
auto const &context = lexerStack.back();
|
||||
if (activeType == SECTTYPE_INVALID) {
|
||||
scriptError(context, "No memory region has been specified to place section \"%s\" in",
|
||||
name.c_str());
|
||||
scriptError(
|
||||
context, "No memory region has been specified to place section \"%s\" in", name.c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -520,14 +583,20 @@ static void placeSection(std::string const &name, bool isOptional) {
|
||||
fragment->type = activeType;
|
||||
}
|
||||
} else if (section->type != activeType) {
|
||||
scriptError(context, "\"%s\" is specified to be a %s section, but it is already a %s section",
|
||||
name.c_str(), typeInfo.name.c_str(), sectionTypeInfo[section->type].name.c_str());
|
||||
scriptError(
|
||||
context, "\"%s\" is specified to be a %s section, but it is already a %s section",
|
||||
name.c_str(), typeInfo.name.c_str(), sectionTypeInfo[section->type].name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
uint32_t bank = activeBankIdx + typeInfo.firstBank;
|
||||
if (section->isBankFixed && bank != section->bank) {
|
||||
scriptError(context, "The linker script places section \"%s\" in %s bank %" PRIu32 ", but it was already defined in bank %" PRIu32,
|
||||
name.c_str(), sectionTypeInfo[section->type].name.c_str(), bank, section->bank);
|
||||
scriptError(
|
||||
context,
|
||||
"The linker script places section \"%s\" in %s bank %" PRIu32
|
||||
", but it was already defined in bank %" PRIu32,
|
||||
name.c_str(), sectionTypeInfo[section->type].name.c_str(), bank, section->bank
|
||||
);
|
||||
}
|
||||
section->isBankFixed = true;
|
||||
section->bank = bank;
|
||||
@@ -535,12 +604,22 @@ static void placeSection(std::string const &name, bool isOptional) {
|
||||
if (!isPcFloating) {
|
||||
uint16_t &org = curAddr[activeType][activeBankIdx];
|
||||
if (section->isAddressFixed && org != section->org) {
|
||||
scriptError(context, "The linker script assigns section \"%s\" to address $%04" PRIx16 ", but it was already at $%04" PRIx16,
|
||||
name.c_str(), org, section->org);
|
||||
scriptError(
|
||||
context,
|
||||
"The linker script assigns section \"%s\" to address $%04" PRIx16
|
||||
", but it was already at $%04" PRIx16,
|
||||
name.c_str(), org, section->org
|
||||
);
|
||||
} else if (section->isAlignFixed && (org & section->alignMask) != section->alignOfs) {
|
||||
uint8_t alignment = std::countr_one(section->alignMask);
|
||||
scriptError(context, "The linker script assigns section \"%s\" to address $%04" PRIx16 ", but that would be ALIGN[%" PRIu8 ", %" PRIu16 "] instead of the requested ALIGN[%" PRIu8 ", %" PRIu16 "]",
|
||||
name.c_str(), org, alignment, (uint16_t)(org & section->alignMask), alignment, section->alignOfs);
|
||||
scriptError(
|
||||
context,
|
||||
"The linker script assigns section \"%s\" to address $%04" PRIx16
|
||||
", but that would be ALIGN[%" PRIu8 ", %" PRIu16
|
||||
"] instead of the requested ALIGN[%" PRIu8 ", %" PRIu16 "]",
|
||||
name.c_str(), org, alignment, (uint16_t)(org & section->alignMask), alignment,
|
||||
section->alignOfs
|
||||
);
|
||||
}
|
||||
section->isAddressFixed = true;
|
||||
section->isAlignFixed = false; // This can't be set when the above is.
|
||||
@@ -549,9 +628,12 @@ static void placeSection(std::string const &name, bool isOptional) {
|
||||
uint16_t curOfs = org - typeInfo.startAddr;
|
||||
if (section->size > typeInfo.size - curOfs) {
|
||||
uint16_t overflowSize = section->size - (typeInfo.size - curOfs);
|
||||
scriptError(context, "The linker script assigns section \"%s\" to address $%04" PRIx16 ", but then it would overflow %s by %" PRIx16 " byte%s",
|
||||
name.c_str(), org, typeInfo.name.c_str(),
|
||||
overflowSize, overflowSize == 1 ? "" : "s");
|
||||
scriptError(
|
||||
context,
|
||||
"The linker script assigns section \"%s\" to address $%04" PRIx16
|
||||
", but then it would overflow %s by %" PRIx16 " byte%s",
|
||||
name.c_str(), org, typeInfo.name.c_str(), overflowSize, overflowSize == 1 ? "" : "s"
|
||||
);
|
||||
// Fill as much as possible without going out of bounds.
|
||||
org = typeInfo.startAddr + typeInfo.size;
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "link/sdas_obj.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
@@ -12,20 +14,19 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
#include "platform.hpp"
|
||||
|
||||
#include "link/assign.hpp"
|
||||
#include "link/main.hpp"
|
||||
#include "link/sdas_obj.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
enum NumberType {
|
||||
HEX = 16, // X
|
||||
DEC = 10, // D
|
||||
OCT = 8, // Q
|
||||
OCT = 8, // Q
|
||||
};
|
||||
|
||||
static void consumeLF(FileStackNode const &where, uint32_t lineNo, FILE *file) {
|
||||
@@ -35,8 +36,8 @@ static void consumeLF(FileStackNode const &where, uint32_t lineNo, FILE *file) {
|
||||
|
||||
static char const *delim = " \f\n\r\t\v"; // Whitespace according to the C and POSIX locales
|
||||
|
||||
static int nextLine(std::vector<char> &lineBuf, uint32_t &lineNo,
|
||||
FileStackNode const &where, FILE *file) {
|
||||
static int
|
||||
nextLine(std::vector<char> &lineBuf, uint32_t &lineNo, FileStackNode const &where, FILE *file) {
|
||||
retry:
|
||||
++lineNo;
|
||||
int firstChar = getc(file);
|
||||
@@ -91,7 +92,9 @@ static uint32_t readNumber(char const *str, char const *&endptr, enum NumberType
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t parseNumber(FileStackNode const &where, uint32_t lineNo, char const *str, enum NumberType base) {
|
||||
static uint32_t parseNumber(
|
||||
FileStackNode const &where, uint32_t lineNo, char const *str, enum NumberType base
|
||||
) {
|
||||
if (str[0] == '\0')
|
||||
fatal(&where, lineNo, "Expected number, got empty string");
|
||||
|
||||
@@ -103,7 +106,8 @@ static uint32_t parseNumber(FileStackNode const &where, uint32_t lineNo, char co
|
||||
return res;
|
||||
}
|
||||
|
||||
static uint8_t parseByte(FileStackNode const &where, uint32_t lineNo, char const *str, enum NumberType base) {
|
||||
static uint8_t
|
||||
parseByte(FileStackNode const &where, uint32_t lineNo, char const *str, enum NumberType base) {
|
||||
uint32_t num = parseNumber(where, lineNo, str, base);
|
||||
|
||||
if (num > UINT8_MAX)
|
||||
@@ -113,49 +117,58 @@ static uint8_t parseByte(FileStackNode const &where, uint32_t lineNo, char const
|
||||
|
||||
enum AreaFlags {
|
||||
AREA_TYPE = 2, // 0: Concatenate, 1: overlay
|
||||
AREA_ISABS, // 0: Relative (???) address, 1: absolute address
|
||||
AREA_PAGING, // Unsupported
|
||||
AREA_ISABS, // 0: Relative (???) address, 1: absolute address
|
||||
AREA_PAGING, // Unsupported
|
||||
|
||||
AREA_ALL_FLAGS = 1 << AREA_TYPE | 1 << AREA_ISABS | 1 << AREA_PAGING,
|
||||
};
|
||||
|
||||
enum RelocFlags {
|
||||
RELOC_SIZE, // 0: 16-bit, 1: 8-bit
|
||||
RELOC_ISSYM, // 0: Area, 1: Symbol
|
||||
RELOC_ISPCREL, // 0: Normal, 1: PC-relative
|
||||
RELOC_EXPR16, // Only for 8-bit size; 0: 8-bit expr, 1: 16-bit expr
|
||||
RELOC_SIGNED, // 0: signed, 1: unsigned
|
||||
RELOC_ZPAGE, // Unsupported
|
||||
RELOC_NPAGE, // Unsupported
|
||||
RELOC_SIZE, // 0: 16-bit, 1: 8-bit
|
||||
RELOC_ISSYM, // 0: Area, 1: Symbol
|
||||
RELOC_ISPCREL, // 0: Normal, 1: PC-relative
|
||||
RELOC_EXPR16, // Only for 8-bit size; 0: 8-bit expr, 1: 16-bit expr
|
||||
RELOC_SIGNED, // 0: signed, 1: unsigned
|
||||
RELOC_ZPAGE, // Unsupported
|
||||
RELOC_NPAGE, // Unsupported
|
||||
RELOC_WHICHBYTE, // 8-bit size with 16-bit expr only; 0: LOW(), 1: HIGH()
|
||||
RELOC_EXPR24, // Only for 8-bit size; 0: follow RELOC_EXPR16, 1: 24-bit expr
|
||||
RELOC_BANKBYTE, // 8-bit size with 24-bit expr only; 0: follow RELOC_WHICHBYTE, 1: BANK()
|
||||
RELOC_EXPR24, // Only for 8-bit size; 0: follow RELOC_EXPR16, 1: 24-bit expr
|
||||
RELOC_BANKBYTE, // 8-bit size with 24-bit expr only; 0: follow RELOC_WHICHBYTE, 1: BANK()
|
||||
|
||||
RELOC_ALL_FLAGS = 1 << RELOC_SIZE | 1 << RELOC_ISSYM | 1 << RELOC_ISPCREL | 1 << RELOC_EXPR16
|
||||
| 1 << RELOC_SIGNED | 1 << RELOC_ZPAGE | 1 << RELOC_NPAGE | 1 << RELOC_WHICHBYTE
|
||||
| 1 << RELOC_EXPR24 | 1 << RELOC_BANKBYTE,
|
||||
| 1 << RELOC_SIGNED | 1 << RELOC_ZPAGE | 1 << RELOC_NPAGE
|
||||
| 1 << RELOC_WHICHBYTE | 1 << RELOC_EXPR24 | 1 << RELOC_BANKBYTE,
|
||||
};
|
||||
|
||||
void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol> &fileSymbols) {
|
||||
std::vector<char> line(256);
|
||||
char const *token;
|
||||
|
||||
#define getToken(ptr, ...) do { \
|
||||
token = strtok((ptr), delim); \
|
||||
if (!token) \
|
||||
fatal(&where, lineNo, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#define expectEol(...) do { \
|
||||
token = strtok(nullptr, delim); \
|
||||
if (token) \
|
||||
fatal(&where, lineNo, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#define expectToken(expected, lineType) do { \
|
||||
getToken(nullptr, "'%c' line is too short", (lineType)); \
|
||||
if (strcasecmp(token, (expected)) != 0) \
|
||||
fatal(&where, lineNo, "Malformed '%c' line: expected \"%s\", got \"%s\"", \
|
||||
(lineType), (expected), token); \
|
||||
} while (0)
|
||||
#define getToken(ptr, ...) \
|
||||
do { \
|
||||
token = strtok((ptr), delim); \
|
||||
if (!token) \
|
||||
fatal(&where, lineNo, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#define expectEol(...) \
|
||||
do { \
|
||||
token = strtok(nullptr, delim); \
|
||||
if (token) \
|
||||
fatal(&where, lineNo, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#define expectToken(expected, lineType) \
|
||||
do { \
|
||||
getToken(nullptr, "'%c' line is too short", (lineType)); \
|
||||
if (strcasecmp(token, (expected)) != 0) \
|
||||
fatal( \
|
||||
&where, \
|
||||
lineNo, \
|
||||
"Malformed '%c' line: expected \"%s\", got \"%s\"", \
|
||||
(lineType), \
|
||||
(expected), \
|
||||
token \
|
||||
); \
|
||||
} while (0)
|
||||
|
||||
uint32_t lineNo = 0;
|
||||
int lineType = nextLine(line, lineNo, where, file);
|
||||
@@ -175,8 +188,12 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
numberType = OCT;
|
||||
break;
|
||||
default:
|
||||
fatal(&where, lineNo, "This does not look like a SDCC object file (unknown integer format '%c')",
|
||||
lineType);
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"This does not look like a SDCC object file (unknown integer format '%c')",
|
||||
lineType
|
||||
);
|
||||
}
|
||||
|
||||
switch (line[0]) {
|
||||
@@ -237,9 +254,10 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
|
||||
case 'A': {
|
||||
if (fileSections.size() == expectedNbAreas)
|
||||
warning(&where, lineNo, "Got more 'A' lines than the expected %" PRIu32,
|
||||
expectedNbAreas);
|
||||
Section *curSection = new(std::nothrow) Section();
|
||||
warning(
|
||||
&where, lineNo, "Got more 'A' lines than the expected %" PRIu32, expectedNbAreas
|
||||
);
|
||||
Section *curSection = new (std::nothrow) Section();
|
||||
|
||||
if (!curSection)
|
||||
fatal(&where, lineNo, "Failed to alloc new area: %s", strerror(errno));
|
||||
@@ -249,12 +267,11 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
// The following is required for fragment offsets to be reliably predicted
|
||||
for (FileSection &entry : fileSections) {
|
||||
if (!strcmp(token, entry.section->name.c_str()))
|
||||
fatal(&where, lineNo, "Area \"%s\" already defined earlier",
|
||||
token);
|
||||
fatal(&where, lineNo, "Area \"%s\" already defined earlier", token);
|
||||
}
|
||||
char const *sectionName = token; // We'll deal with the section's name depending on type
|
||||
|
||||
fileSections.push_back({ .section = curSection, .writeIndex = 0 });
|
||||
fileSections.push_back({.section = curSection, .writeIndex = 0});
|
||||
|
||||
expectToken("size", 'A');
|
||||
|
||||
@@ -263,8 +280,12 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
uint32_t tmp = parseNumber(where, lineNo, token, numberType);
|
||||
|
||||
if (tmp > UINT16_MAX)
|
||||
fatal(&where, lineNo, "Area \"%s\" is larger than the GB address space!?",
|
||||
curSection->name.c_str());
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"Area \"%s\" is larger than the GB address space!?",
|
||||
curSection->name.c_str()
|
||||
);
|
||||
curSection->size = tmp;
|
||||
|
||||
expectToken("flags", 'A');
|
||||
@@ -276,7 +297,8 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
curSection->isAddressFixed = tmp & (1 << AREA_ISABS);
|
||||
curSection->isBankFixed = curSection->isAddressFixed;
|
||||
curSection->modifier = curSection->isAddressFixed || (tmp & (1 << AREA_TYPE))
|
||||
? SECTION_NORMAL : SECTION_FRAGMENT;
|
||||
? SECTION_NORMAL
|
||||
: SECTION_FRAGMENT;
|
||||
// If the section is absolute, its name might not be unique; thus, mangle the name
|
||||
if (curSection->modifier == SECTION_NORMAL) {
|
||||
curSection->name.append(where.name());
|
||||
@@ -320,7 +342,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
} else {
|
||||
curSection->type = SECTTYPE_INVALID; // This means "indeterminate"
|
||||
}
|
||||
curSection->isAlignFixed = false; // No such concept!
|
||||
curSection->isAlignFixed = false; // No such concept!
|
||||
curSection->fileSymbols = &fileSymbols; // IDs are instead per-section
|
||||
curSection->nextu = nullptr;
|
||||
break;
|
||||
@@ -328,8 +350,12 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
|
||||
case 'S': {
|
||||
if (fileSymbols.size() == expectedNbSymbols)
|
||||
warning(&where, lineNo, "Got more 'S' lines than the expected %" PRIu32,
|
||||
expectedNbSymbols);
|
||||
warning(
|
||||
&where,
|
||||
lineNo,
|
||||
"Got more 'S' lines than the expected %" PRIu32,
|
||||
expectedNbSymbols
|
||||
);
|
||||
Symbol &symbol = fileSymbols.emplace_back();
|
||||
|
||||
// Init other members
|
||||
@@ -347,16 +373,11 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
// Symbols in sections are labels; their value is an offset
|
||||
Section *section = fileSections.back().section;
|
||||
if (section->isAddressFixed) {
|
||||
assert(value >= section->org &&
|
||||
value <= section->org + section->size);
|
||||
assert(value >= section->org && value <= section->org + section->size);
|
||||
value -= section->org;
|
||||
}
|
||||
symbol.data = Label{
|
||||
// No need to set the `sectionID`, since we set the pointer
|
||||
.sectionID = 0,
|
||||
.offset = value,
|
||||
.section = section
|
||||
};
|
||||
// No need to set the `sectionID`, since we set the pointer
|
||||
symbol.data = Label{.sectionID = 0, .offset = value, .section = section};
|
||||
} else {
|
||||
// Symbols without sections are just constants
|
||||
symbol.data = value;
|
||||
@@ -377,23 +398,30 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
// The same symbol can only be defined twice if neither
|
||||
// definition is in a floating section
|
||||
auto visitor = Visitor{
|
||||
[](int32_t value) -> std::tuple<Section *, int32_t> {
|
||||
return {nullptr, value};
|
||||
},
|
||||
[](Label label) -> std::tuple<Section *, int32_t> {
|
||||
return {label.section, label.offset};
|
||||
}
|
||||
[](int32_t value) -> std::tuple<Section *, int32_t> {
|
||||
return {nullptr, value};
|
||||
},
|
||||
[](Label label) -> std::tuple<Section *, int32_t> {
|
||||
return {label.section, label.offset};
|
||||
},
|
||||
};
|
||||
auto [symbolSection, symbolValue] = std::visit(visitor, symbol.data);
|
||||
auto [otherSection, otherValue] = std::visit(visitor, other->data);
|
||||
|
||||
|
||||
if ((otherSection && !otherSection->isAddressFixed)
|
||||
|| (symbolSection && !symbolSection->isAddressFixed)) {
|
||||
sym_AddSymbol(symbol); // This will error out
|
||||
} else if (otherValue != symbolValue) {
|
||||
error(&where, lineNo,
|
||||
"Definition of \"%s\" conflicts with definition in %s (%" PRId32 " != %" PRId32 ")",
|
||||
symbol.name.c_str(), other->objFileName, symbolValue, otherValue);
|
||||
error(
|
||||
&where,
|
||||
lineNo,
|
||||
"Definition of \"%s\" conflicts with definition in %s (%" PRId32
|
||||
" != %" PRId32 ")",
|
||||
symbol.name.c_str(),
|
||||
other->objFileName,
|
||||
symbolValue,
|
||||
otherValue
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Add a new definition
|
||||
@@ -443,8 +471,13 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
getToken(nullptr, "'R' line is too short");
|
||||
areaIdx |= (uint16_t)parseByte(where, lineNo, token, numberType) << 8;
|
||||
if (areaIdx >= fileSections.size())
|
||||
fatal(&where, lineNo, "'R' line references area #%" PRIu16 ", but there are only %zu (so far)",
|
||||
areaIdx, fileSections.size());
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"'R' line references area #%" PRIu16 ", but there are only %zu (so far)",
|
||||
areaIdx,
|
||||
fileSections.size()
|
||||
);
|
||||
assert(!fileSections.empty()); // There should be at least one, from the above check
|
||||
Section *section = fileSections[areaIdx].section;
|
||||
uint16_t *writeIndex = &fileSections[areaIdx].writeIndex;
|
||||
@@ -453,16 +486,29 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
|
||||
if (section->isAddressFixed) {
|
||||
if (addr < section->org)
|
||||
fatal(&where, lineNo, "'T' line reports address $%04" PRIx16 " in \"%s\", which starts at $%04" PRIx16,
|
||||
addr, section->name.c_str(), section->org);
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"'T' line reports address $%04" PRIx16
|
||||
" in \"%s\", which starts at $%04" PRIx16,
|
||||
addr,
|
||||
section->name.c_str(),
|
||||
section->org
|
||||
);
|
||||
addr -= section->org;
|
||||
}
|
||||
// Lines are emitted that violate this check but contain no "payload";
|
||||
// ignore those. "Empty" lines shouldn't trigger allocation, either.
|
||||
if (data.size() != ADDR_SIZE) {
|
||||
if (addr != *writeIndex)
|
||||
fatal(&where, lineNo, "'T' lines which don't append to their section are not supported (%" PRIu16 " != %" PRIu16 ")",
|
||||
addr, *writeIndex);
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"'T' lines which don't append to their section are not supported (%" PRIu16
|
||||
" != %" PRIu16 ")",
|
||||
addr,
|
||||
*writeIndex
|
||||
);
|
||||
if (section->data.empty()) {
|
||||
assert(section->size != 0);
|
||||
section->data.resize(section->size);
|
||||
@@ -487,18 +533,29 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
|
||||
if ((flags & 0xF0) == 0xF0) {
|
||||
getToken(nullptr, "Incomplete relocation");
|
||||
flags = (flags & 0x0F) | (uint16_t)parseByte(where, lineNo, token, numberType) << 4;
|
||||
flags =
|
||||
(flags & 0x0F) | (uint16_t)parseByte(where, lineNo, token, numberType) << 4;
|
||||
}
|
||||
|
||||
getToken(nullptr, "Incomplete relocation");
|
||||
uint8_t offset = parseByte(where, lineNo, token, numberType);
|
||||
|
||||
if (offset < ADDR_SIZE)
|
||||
fatal(&where, lineNo, "Relocation index cannot point to header (%" PRIu16 " < %u)",
|
||||
offset, ADDR_SIZE);
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"Relocation index cannot point to header (%" PRIu16 " < %u)",
|
||||
offset,
|
||||
ADDR_SIZE
|
||||
);
|
||||
if (offset >= data.size())
|
||||
fatal(&where, lineNo, "Relocation index is out of bounds (%" PRIu16 " >= %zu)",
|
||||
offset, data.size());
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"Relocation index is out of bounds (%" PRIu16 " >= %zu)",
|
||||
offset,
|
||||
data.size()
|
||||
);
|
||||
|
||||
getToken(nullptr, "Incomplete relocation");
|
||||
uint16_t idx = parseByte(where, lineNo, token, numberType);
|
||||
@@ -520,11 +577,17 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
patch.offset = offset - writtenOfs + *writeIndex;
|
||||
if (section->patches.size() > 1) {
|
||||
uint32_t prevOffset = section->patches[section->patches.size() - 2].offset;
|
||||
if (prevOffset>= patch.offset)
|
||||
fatal(&where, lineNo, "Relocs not sorted by offset are not supported (%" PRIu32 " >= %" PRIu32 ")",
|
||||
prevOffset, patch.offset);
|
||||
if (prevOffset >= patch.offset)
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"Relocs not sorted by offset are not supported (%" PRIu32 " >= %" PRIu32
|
||||
")",
|
||||
prevOffset,
|
||||
patch.offset
|
||||
);
|
||||
}
|
||||
patch.pcSection = section; // No need to fill `pcSectionID`, then
|
||||
patch.pcSection = section; // No need to fill `pcSectionID`, then
|
||||
patch.pcOffset = patch.offset - 1; // For `jr`s
|
||||
|
||||
patch.type = (flags & 1 << RELOC_SIZE) ? PATCHTYPE_BYTE : PATCHTYPE_WORD;
|
||||
@@ -533,8 +596,13 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
|
||||
assert(offset < data.size());
|
||||
if (data.size() - offset < nbBaseBytes)
|
||||
fatal(&where, lineNo, "Reloc would patch out of bounds (%" PRIu8 " > %zu)",
|
||||
nbBaseBytes, data.size() - offset);
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"Reloc would patch out of bounds (%" PRIu8 " > %zu)",
|
||||
nbBaseBytes,
|
||||
data.size() - offset
|
||||
);
|
||||
for (uint8_t i = 0; i < nbBaseBytes; ++i)
|
||||
baseValue = baseValue | data[offset + i] << (8 * i);
|
||||
|
||||
@@ -542,8 +610,13 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
// Generate a RPN expression from the info and flags
|
||||
if (flags & 1 << RELOC_ISSYM) {
|
||||
if (idx >= fileSymbols.size())
|
||||
fatal(&where, lineNo, "Reloc refers to symbol #%" PRIu16 " out of %zu",
|
||||
idx, fileSymbols.size());
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"Reloc refers to symbol #%" PRIu16 " out of %zu",
|
||||
idx,
|
||||
fileSymbols.size()
|
||||
);
|
||||
Symbol const &sym = fileSymbols[idx];
|
||||
|
||||
// SDCC has a bunch of "magic symbols" that start with a
|
||||
@@ -552,13 +625,18 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
if (sym.name.starts_with("b_")) {
|
||||
// Look for the symbol being referenced, and use its index instead
|
||||
for (idx = 0; idx < fileSymbols.size(); ++idx) {
|
||||
if (sym.name.ends_with(fileSymbols[idx].name) &&
|
||||
1 + sym.name.length() == fileSymbols[idx].name.length())
|
||||
if (sym.name.ends_with(fileSymbols[idx].name)
|
||||
&& 1 + sym.name.length() == fileSymbols[idx].name.length())
|
||||
break;
|
||||
}
|
||||
if (idx == fileSymbols.size())
|
||||
fatal(&where, lineNo, "\"%s\" is missing a reference to \"%s\"",
|
||||
sym.name.c_str(), &sym.name.c_str()[1]);
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"\"%s\" is missing a reference to \"%s\"",
|
||||
sym.name.c_str(),
|
||||
&sym.name.c_str()[1]
|
||||
);
|
||||
patch.rpnExpression.resize(5);
|
||||
patch.rpnExpression[0] = RPN_BANK_SYM;
|
||||
patch.rpnExpression[1] = idx;
|
||||
@@ -568,11 +646,19 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
} else if (sym.name.starts_with("l_")) {
|
||||
patch.rpnExpression.resize(1 + sym.name.length() - 2 + 1);
|
||||
patch.rpnExpression[0] = RPN_SIZEOF_SECT;
|
||||
memcpy((char *)&patch.rpnExpression[1], &sym.name.c_str()[2], sym.name.length() - 2 + 1);
|
||||
memcpy(
|
||||
(char *)&patch.rpnExpression[1],
|
||||
&sym.name.c_str()[2],
|
||||
sym.name.length() - 2 + 1
|
||||
);
|
||||
} else if (sym.name.starts_with("s_")) {
|
||||
patch.rpnExpression.resize(1 + sym.name.length() - 2 + 1);
|
||||
patch.rpnExpression[0] = RPN_STARTOF_SECT;
|
||||
memcpy((char *)&patch.rpnExpression[1], &sym.name.c_str()[2], sym.name.length() - 2 + 1);
|
||||
memcpy(
|
||||
(char *)&patch.rpnExpression[1],
|
||||
&sym.name.c_str()[2],
|
||||
sym.name.length() - 2 + 1
|
||||
);
|
||||
} else {
|
||||
patch.rpnExpression.resize(5);
|
||||
patch.rpnExpression[0] = RPN_SYM;
|
||||
@@ -583,8 +669,13 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
}
|
||||
} else {
|
||||
if (idx >= fileSections.size())
|
||||
fatal(&where, lineNo, "Reloc refers to area #%" PRIu16 " out of %zu",
|
||||
idx, fileSections.size());
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"Reloc refers to area #%" PRIu16 " out of %zu",
|
||||
idx,
|
||||
fileSections.size()
|
||||
);
|
||||
// It gets funky. If the area is absolute, *actually*, we
|
||||
// must not add its base address, as the assembler will
|
||||
// already have added it in `baseValue`.
|
||||
@@ -625,10 +716,18 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
// are present, so we must skip two of them
|
||||
if (flags & 1 << RELOC_EXPR16) {
|
||||
if (*writeIndex + (offset - writtenOfs) > section->size)
|
||||
fatal(&where, lineNo, "'T' line writes past \"%s\"'s end (%u > %" PRIu16 ")",
|
||||
section->name.c_str(), *writeIndex + (offset - writtenOfs), section->size);
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"'T' line writes past \"%s\"'s end (%u > %" PRIu16 ")",
|
||||
section->name.c_str(),
|
||||
*writeIndex + (offset - writtenOfs),
|
||||
section->size
|
||||
);
|
||||
// Copy all bytes up to those (plus the byte that we'll overwrite)
|
||||
memcpy(§ion->data[*writeIndex], &data[writtenOfs], offset - writtenOfs + 1);
|
||||
memcpy(
|
||||
§ion->data[*writeIndex], &data[writtenOfs], offset - writtenOfs + 1
|
||||
);
|
||||
*writeIndex += offset - writtenOfs + 1;
|
||||
writtenOfs = offset + 3; // Skip all three `baseValue` bytes, though
|
||||
}
|
||||
@@ -644,7 +743,9 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
patch.rpnExpression.push_back(16 >> 8);
|
||||
patch.rpnExpression.push_back(16 >> 16);
|
||||
patch.rpnExpression.push_back(16 >> 24);
|
||||
patch.rpnExpression.push_back((flags & 1 << RELOC_SIGNED) ? RPN_SHR : RPN_USHR);
|
||||
patch.rpnExpression.push_back(
|
||||
(flags & 1 << RELOC_SIGNED) ? RPN_SHR : RPN_USHR
|
||||
);
|
||||
} else {
|
||||
if (flags & 1 << RELOC_EXPR16 && flags & 1 << RELOC_WHICHBYTE) {
|
||||
patch.rpnExpression.push_back(RPN_CONST);
|
||||
@@ -652,7 +753,9 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
patch.rpnExpression.push_back(8 >> 8);
|
||||
patch.rpnExpression.push_back(8 >> 16);
|
||||
patch.rpnExpression.push_back(8 >> 24);
|
||||
patch.rpnExpression.push_back((flags & 1 << RELOC_SIGNED) ? RPN_SHR : RPN_USHR);
|
||||
patch.rpnExpression.push_back(
|
||||
(flags & 1 << RELOC_SIGNED) ? RPN_SHR : RPN_USHR
|
||||
);
|
||||
}
|
||||
patch.rpnExpression.push_back(RPN_CONST);
|
||||
patch.rpnExpression.push_back(0xFF);
|
||||
@@ -665,8 +768,12 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
assert(patch.type == PATCHTYPE_WORD);
|
||||
fatal(&where, lineNo, "16-bit PC-relative relocations are not supported");
|
||||
} else if (flags & (1 << RELOC_EXPR16 | 1 << RELOC_EXPR24)) {
|
||||
fatal(&where, lineNo, "Flags 0x%x are not supported for 16-bit relocs",
|
||||
flags & (1 << RELOC_EXPR16 | 1 << RELOC_EXPR24));
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"Flags 0x%x are not supported for 16-bit relocs",
|
||||
flags & (1 << RELOC_EXPR16 | 1 << RELOC_EXPR24)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,8 +781,14 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
if (writtenOfs != data.size()) {
|
||||
assert(data.size() > writtenOfs);
|
||||
if (*writeIndex + (data.size() - writtenOfs) > section->size)
|
||||
fatal(&where, lineNo, "'T' line writes past \"%s\"'s end (%zu > %" PRIu16 ")",
|
||||
section->name.c_str(), *writeIndex + (data.size() - writtenOfs), section->size);
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"'T' line writes past \"%s\"'s end (%zu > %" PRIu16 ")",
|
||||
section->name.c_str(),
|
||||
*writeIndex + (data.size() - writtenOfs),
|
||||
section->size
|
||||
);
|
||||
memcpy(§ion->data[*writeIndex], &data[writtenOfs], data.size() - writtenOfs);
|
||||
*writeIndex += data.size() - writtenOfs;
|
||||
}
|
||||
@@ -694,11 +807,21 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
if (!data.empty())
|
||||
warning(&where, lineNo, "Last 'T' line had no 'R' line (ignored)");
|
||||
if (fileSections.size() < expectedNbAreas)
|
||||
warning(&where, lineNo, "Expected %" PRIu32 " 'A' lines, got only %zu", expectedNbAreas,
|
||||
fileSections.size());
|
||||
warning(
|
||||
&where,
|
||||
lineNo,
|
||||
"Expected %" PRIu32 " 'A' lines, got only %zu",
|
||||
expectedNbAreas,
|
||||
fileSections.size()
|
||||
);
|
||||
if (fileSymbols.size() < expectedNbSymbols)
|
||||
warning(&where, lineNo, "Expected %" PRIu32 " 'S' lines, got only %zu", expectedNbSymbols,
|
||||
fileSymbols.size());
|
||||
warning(
|
||||
&where,
|
||||
lineNo,
|
||||
"Expected %" PRIu32 " 'S' lines, got only %zu",
|
||||
expectedNbSymbols,
|
||||
fileSymbols.size()
|
||||
);
|
||||
|
||||
nbSectionsToAssign += fileSections.size();
|
||||
|
||||
@@ -707,8 +830,14 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
|
||||
// RAM sections can have a size, but don't get any data (they shouldn't have any)
|
||||
if (entry.writeIndex != section->size && entry.writeIndex != 0)
|
||||
fatal(&where, lineNo, "\"%s\" was not fully written (%" PRIu16 " < %" PRIu16 ")",
|
||||
section->name.c_str(), entry.writeIndex, section->size);
|
||||
fatal(
|
||||
&where,
|
||||
lineNo,
|
||||
"\"%s\" was not fully written (%" PRIu16 " < %" PRIu16 ")",
|
||||
section->name.c_str(),
|
||||
entry.writeIndex,
|
||||
section->size
|
||||
);
|
||||
|
||||
sect_AddSection(*section);
|
||||
|
||||
|
||||
@@ -1,39 +1,47 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "link/section.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <map>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
|
||||
#include "link/main.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include <string>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "link/main.hpp"
|
||||
|
||||
std::map<std::string, Section *> sections;
|
||||
|
||||
void sect_ForEach(void (*callback)(Section &))
|
||||
{
|
||||
void sect_ForEach(void (*callback)(Section &)) {
|
||||
for (auto &it : sections)
|
||||
callback(*it.second);
|
||||
}
|
||||
|
||||
static void checkSectUnionCompat(Section &target, Section &other)
|
||||
{
|
||||
static void checkSectUnionCompat(Section &target, Section &other) {
|
||||
if (other.isAddressFixed) {
|
||||
if (target.isAddressFixed) {
|
||||
if (target.org != other.org)
|
||||
errx("Section \"%s\" is defined with conflicting addresses $%04"
|
||||
PRIx16 " and $%04" PRIx16, other.name.c_str(), target.org,
|
||||
other.org);
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting addresses $%04" PRIx16
|
||||
" and $%04" PRIx16,
|
||||
other.name.c_str(),
|
||||
target.org,
|
||||
other.org
|
||||
);
|
||||
} else if (target.isAlignFixed) {
|
||||
if ((other.org - target.alignOfs) & target.alignMask)
|
||||
errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
|
||||
PRIu16 ") and address $%04" PRIx16, other.name.c_str(),
|
||||
target.alignMask + 1, target.alignOfs, other.org);
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
|
||||
") and address $%04" PRIx16,
|
||||
other.name.c_str(),
|
||||
target.alignMask + 1,
|
||||
target.alignOfs,
|
||||
other.org
|
||||
);
|
||||
}
|
||||
target.isAddressFixed = true;
|
||||
target.org = other.org;
|
||||
@@ -41,17 +49,25 @@ static void checkSectUnionCompat(Section &target, Section &other)
|
||||
} else if (other.isAlignFixed) {
|
||||
if (target.isAddressFixed) {
|
||||
if ((target.org - other.alignOfs) & other.alignMask)
|
||||
errx("Section \"%s\" is defined with conflicting address $%04"
|
||||
PRIx16 " and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(), target.org, other.alignMask + 1,
|
||||
other.alignOfs);
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting address $%04" PRIx16
|
||||
" and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(),
|
||||
target.org,
|
||||
other.alignMask + 1,
|
||||
other.alignOfs
|
||||
);
|
||||
} else if (target.isAlignFixed
|
||||
&& (other.alignMask & target.alignOfs)
|
||||
!= (target.alignMask & other.alignOfs)) {
|
||||
errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
|
||||
PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(), target.alignMask + 1, target.alignOfs,
|
||||
other.alignMask + 1, other.alignOfs);
|
||||
&& (other.alignMask & target.alignOfs) != (target.alignMask & other.alignOfs)) {
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
|
||||
") and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(),
|
||||
target.alignMask + 1,
|
||||
target.alignOfs,
|
||||
other.alignMask + 1,
|
||||
other.alignOfs
|
||||
);
|
||||
} else if (!target.isAlignFixed || (other.alignMask > target.alignMask)) {
|
||||
target.isAlignFixed = true;
|
||||
target.alignMask = other.alignMask;
|
||||
@@ -59,22 +75,30 @@ static void checkSectUnionCompat(Section &target, Section &other)
|
||||
}
|
||||
}
|
||||
|
||||
static void checkFragmentCompat(Section &target, Section &other)
|
||||
{
|
||||
static void checkFragmentCompat(Section &target, Section &other) {
|
||||
if (other.isAddressFixed) {
|
||||
uint16_t org = other.org - target.size;
|
||||
|
||||
if (target.isAddressFixed) {
|
||||
if (target.org != org)
|
||||
errx("Section \"%s\" is defined with conflicting addresses $%04"
|
||||
PRIx16 " and $%04" PRIx16, other.name.c_str(), target.org,
|
||||
other.org);
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting addresses $%04" PRIx16
|
||||
" and $%04" PRIx16,
|
||||
other.name.c_str(),
|
||||
target.org,
|
||||
other.org
|
||||
);
|
||||
|
||||
} else if (target.isAlignFixed) {
|
||||
if ((org - target.alignOfs) & target.alignMask)
|
||||
errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
|
||||
PRIu16 ") and address $%04" PRIx16, other.name.c_str(),
|
||||
target.alignMask + 1, target.alignOfs, other.org);
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
|
||||
") and address $%04" PRIx16,
|
||||
other.name.c_str(),
|
||||
target.alignMask + 1,
|
||||
target.alignOfs,
|
||||
other.org
|
||||
);
|
||||
}
|
||||
target.isAddressFixed = true;
|
||||
target.org = org;
|
||||
@@ -87,17 +111,25 @@ static void checkFragmentCompat(Section &target, Section &other)
|
||||
|
||||
if (target.isAddressFixed) {
|
||||
if ((target.org - ofs) & other.alignMask)
|
||||
errx("Section \"%s\" is defined with conflicting address $%04"
|
||||
PRIx16 " and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(), target.org, other.alignMask + 1,
|
||||
other.alignOfs);
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting address $%04" PRIx16
|
||||
" and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(),
|
||||
target.org,
|
||||
other.alignMask + 1,
|
||||
other.alignOfs
|
||||
);
|
||||
|
||||
} else if (target.isAlignFixed
|
||||
&& (other.alignMask & target.alignOfs) != (target.alignMask & ofs)) {
|
||||
errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
|
||||
PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(), target.alignMask + 1, target.alignOfs,
|
||||
other.alignMask + 1, other.alignOfs);
|
||||
} else if (target.isAlignFixed && (other.alignMask & target.alignOfs) != (target.alignMask & ofs)) {
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting %d-byte alignment (offset %" PRIu16
|
||||
") and %d-byte alignment (offset %" PRIu16 ")",
|
||||
other.name.c_str(),
|
||||
target.alignMask + 1,
|
||||
target.alignOfs,
|
||||
other.alignMask + 1,
|
||||
other.alignOfs
|
||||
);
|
||||
|
||||
} else if (!target.isAlignFixed || (other.alignMask > target.alignMask)) {
|
||||
target.isAlignFixed = true;
|
||||
@@ -107,22 +139,28 @@ static void checkFragmentCompat(Section &target, Section &other)
|
||||
}
|
||||
}
|
||||
|
||||
static void mergeSections(Section &target, Section &other, enum SectionModifier mod)
|
||||
{
|
||||
static void mergeSections(Section &target, Section &other, enum SectionModifier mod) {
|
||||
// Common checks
|
||||
|
||||
if (target.type != other.type)
|
||||
errx("Section \"%s\" is defined with conflicting types %s and %s",
|
||||
other.name.c_str(), sectionTypeInfo[target.type].name.c_str(),
|
||||
sectionTypeInfo[other.type].name.c_str());
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting types %s and %s",
|
||||
other.name.c_str(),
|
||||
sectionTypeInfo[target.type].name.c_str(),
|
||||
sectionTypeInfo[other.type].name.c_str()
|
||||
);
|
||||
|
||||
if (other.isBankFixed) {
|
||||
if (!target.isBankFixed) {
|
||||
target.isBankFixed = true;
|
||||
target.bank = other.bank;
|
||||
} else if (target.bank != other.bank) {
|
||||
errx("Section \"%s\" is defined with conflicting banks %" PRIu32 " and %"
|
||||
PRIu32, other.name.c_str(), target.bank, other.bank);
|
||||
errx(
|
||||
"Section \"%s\" is defined with conflicting banks %" PRIu32 " and %" PRIu32,
|
||||
other.name.c_str(),
|
||||
target.bank,
|
||||
other.bank
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,34 +197,38 @@ static void mergeSections(Section &target, Section &other, enum SectionModifier
|
||||
target.nextu = &other;
|
||||
}
|
||||
|
||||
void sect_AddSection(Section §ion)
|
||||
{
|
||||
void sect_AddSection(Section §ion) {
|
||||
// Check if the section already exists
|
||||
if (Section *other = sect_GetSection(section.name); other) {
|
||||
if (section.modifier != other->modifier)
|
||||
errx("Section \"%s\" defined as %s and %s", section.name.c_str(),
|
||||
sectionModNames[section.modifier], sectionModNames[other->modifier]);
|
||||
errx(
|
||||
"Section \"%s\" defined as %s and %s",
|
||||
section.name.c_str(),
|
||||
sectionModNames[section.modifier],
|
||||
sectionModNames[other->modifier]
|
||||
);
|
||||
else if (section.modifier == SECTION_NORMAL)
|
||||
errx("Section name \"%s\" is already in use", section.name.c_str());
|
||||
else
|
||||
mergeSections(*other, section, section.modifier);
|
||||
} else if (section.modifier == SECTION_UNION && sect_HasData(section.type)) {
|
||||
errx("Section \"%s\" is of type %s, which cannot be unionized",
|
||||
section.name.c_str(), sectionTypeInfo[section.type].name.c_str());
|
||||
errx(
|
||||
"Section \"%s\" is of type %s, which cannot be unionized",
|
||||
section.name.c_str(),
|
||||
sectionTypeInfo[section.type].name.c_str()
|
||||
);
|
||||
} else {
|
||||
// If not, add it
|
||||
sections[section.name] = §ion;
|
||||
}
|
||||
}
|
||||
|
||||
Section *sect_GetSection(std::string const &name)
|
||||
{
|
||||
Section *sect_GetSection(std::string const &name) {
|
||||
auto search = sections.find(name);
|
||||
return search != sections.end() ? search->second : nullptr;
|
||||
}
|
||||
|
||||
static void doSanityChecks(Section §ion)
|
||||
{
|
||||
static void doSanityChecks(Section §ion) {
|
||||
// Sanity check the section's type
|
||||
if (section.type < 0 || section.type >= SECTTYPE_INVALID) {
|
||||
error(nullptr, 0, "Section \"%s\" has an invalid type", section.name.c_str());
|
||||
@@ -195,21 +237,28 @@ static void doSanityChecks(Section §ion)
|
||||
|
||||
if (is32kMode && section.type == SECTTYPE_ROMX) {
|
||||
if (section.isBankFixed && section.bank != 1)
|
||||
error(nullptr, 0, "%s: ROMX sections must be in bank 1 (if any) with option -t",
|
||||
section.name.c_str());
|
||||
error(
|
||||
nullptr,
|
||||
0,
|
||||
"%s: ROMX sections must be in bank 1 (if any) with option -t",
|
||||
section.name.c_str()
|
||||
);
|
||||
else
|
||||
section.type = SECTTYPE_ROM0;
|
||||
}
|
||||
if (isWRAM0Mode && section.type == SECTTYPE_WRAMX) {
|
||||
if (section.isBankFixed && section.bank != 1)
|
||||
error(nullptr, 0, "%s: WRAMX sections must be in bank 1 with options -w or -d",
|
||||
section.name.c_str());
|
||||
error(
|
||||
nullptr,
|
||||
0,
|
||||
"%s: WRAMX sections must be in bank 1 with options -w or -d",
|
||||
section.name.c_str()
|
||||
);
|
||||
else
|
||||
section.type = SECTTYPE_WRAM0;
|
||||
}
|
||||
if (isDmgMode && section.type == SECTTYPE_VRAM && section.bank == 1)
|
||||
error(nullptr, 0, "%s: VRAM bank 1 can't be used with option -d",
|
||||
section.name.c_str());
|
||||
error(nullptr, 0, "%s: VRAM bank 1 can't be used with option -d", section.name.c_str());
|
||||
|
||||
// Check if alignment is reasonable, this is important to avoid UB
|
||||
// An alignment of zero is equivalent to no alignment, basically
|
||||
@@ -218,23 +267,42 @@ static void doSanityChecks(Section §ion)
|
||||
|
||||
// Too large an alignment may not be satisfiable
|
||||
if (section.isAlignFixed && (section.alignMask & sectionTypeInfo[section.type].startAddr))
|
||||
error(nullptr, 0, "%s: %s sections cannot be aligned to $%04x bytes",
|
||||
section.name.c_str(), sectionTypeInfo[section.type].name.c_str(),
|
||||
section.alignMask + 1);
|
||||
error(
|
||||
nullptr,
|
||||
0,
|
||||
"%s: %s sections cannot be aligned to $%04x bytes",
|
||||
section.name.c_str(),
|
||||
sectionTypeInfo[section.type].name.c_str(),
|
||||
section.alignMask + 1
|
||||
);
|
||||
|
||||
uint32_t minbank = sectionTypeInfo[section.type].firstBank, maxbank = sectionTypeInfo[section.type].lastBank;
|
||||
uint32_t minbank = sectionTypeInfo[section.type].firstBank,
|
||||
maxbank = sectionTypeInfo[section.type].lastBank;
|
||||
|
||||
if (section.isBankFixed && section.bank < minbank && section.bank > maxbank)
|
||||
error(nullptr, 0, minbank == maxbank
|
||||
? "Cannot place section \"%s\" in bank %" PRIu32 ", it must be %" PRIu32
|
||||
: "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32 " and %" PRIu32,
|
||||
section.name.c_str(), section.bank, minbank, maxbank);
|
||||
error(
|
||||
nullptr,
|
||||
0,
|
||||
minbank == maxbank
|
||||
? "Cannot place section \"%s\" in bank %" PRIu32 ", it must be %" PRIu32
|
||||
: "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32
|
||||
" and %" PRIu32,
|
||||
section.name.c_str(),
|
||||
section.bank,
|
||||
minbank,
|
||||
maxbank
|
||||
);
|
||||
|
||||
// Check if section has a chance to be placed
|
||||
if (section.size > sectionTypeInfo[section.type].size)
|
||||
error(nullptr, 0, "Section \"%s\" is bigger than the max size for that type: $%"
|
||||
PRIx16 " > $%" PRIx16,
|
||||
section.name.c_str(), section.size, sectionTypeInfo[section.type].size);
|
||||
error(
|
||||
nullptr,
|
||||
0,
|
||||
"Section \"%s\" is bigger than the max size for that type: $%" PRIx16 " > $%" PRIx16,
|
||||
section.name.c_str(),
|
||||
section.size,
|
||||
sectionTypeInfo[section.type].size
|
||||
);
|
||||
|
||||
// Translate loose constraints to strong ones when they're equivalent
|
||||
|
||||
@@ -247,26 +315,41 @@ static void doSanityChecks(Section §ion)
|
||||
// It doesn't make sense to have both org and alignment set
|
||||
if (section.isAlignFixed) {
|
||||
if ((section.org & section.alignMask) != section.alignOfs)
|
||||
error(nullptr, 0, "Section \"%s\"'s fixed address doesn't match its alignment",
|
||||
section.name.c_str());
|
||||
error(
|
||||
nullptr,
|
||||
0,
|
||||
"Section \"%s\"'s fixed address doesn't match its alignment",
|
||||
section.name.c_str()
|
||||
);
|
||||
section.isAlignFixed = false;
|
||||
}
|
||||
|
||||
// Ensure the target address is valid
|
||||
if (section.org < sectionTypeInfo[section.type].startAddr
|
||||
|| section.org > endaddr(section.type))
|
||||
error(nullptr, 0, "Section \"%s\"'s fixed address $%04" PRIx16 " is outside of range [$%04"
|
||||
PRIx16 "; $%04" PRIx16 "]", section.name.c_str(), section.org,
|
||||
sectionTypeInfo[section.type].startAddr, endaddr(section.type));
|
||||
|| section.org > endaddr(section.type))
|
||||
error(
|
||||
nullptr,
|
||||
0,
|
||||
"Section \"%s\"'s fixed address $%04" PRIx16 " is outside of range [$%04" PRIx16
|
||||
"; $%04" PRIx16 "]",
|
||||
section.name.c_str(),
|
||||
section.org,
|
||||
sectionTypeInfo[section.type].startAddr,
|
||||
endaddr(section.type)
|
||||
);
|
||||
|
||||
if (section.org + section.size > endaddr(section.type) + 1)
|
||||
error(nullptr, 0, "Section \"%s\"'s end address $%04x is greater than last address $%04x",
|
||||
section.name.c_str(), section.org + section.size,
|
||||
endaddr(section.type) + 1);
|
||||
error(
|
||||
nullptr,
|
||||
0,
|
||||
"Section \"%s\"'s end address $%04x is greater than last address $%04x",
|
||||
section.name.c_str(),
|
||||
section.org + section.size,
|
||||
endaddr(section.type) + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void sect_DoSanityChecks()
|
||||
{
|
||||
void sect_DoSanityChecks() {
|
||||
sect_ForEach(doSanityChecks);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,33 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "link/symbol.hpp"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <map>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "link/object.hpp"
|
||||
#include "link/section.hpp"
|
||||
#include "link/symbol.hpp"
|
||||
#include "link/main.hpp"
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "link/main.hpp"
|
||||
#include "link/object.hpp"
|
||||
#include "link/section.hpp"
|
||||
|
||||
std::map<std::string, Symbol *> symbols;
|
||||
|
||||
Label &Symbol::label()
|
||||
{
|
||||
Label &Symbol::label() {
|
||||
assert(std::holds_alternative<Label>(data));
|
||||
return std::get<Label>(data);
|
||||
}
|
||||
|
||||
Label const &Symbol::label() const
|
||||
{
|
||||
Label const &Symbol::label() const {
|
||||
assert(std::holds_alternative<Label>(data));
|
||||
return std::get<Label>(data);
|
||||
}
|
||||
|
||||
void sym_AddSymbol(Symbol &symbol)
|
||||
{
|
||||
void sym_AddSymbol(Symbol &symbol) {
|
||||
// Check if the symbol already exists
|
||||
if (Symbol *other = sym_GetSymbol(symbol.name); other) {
|
||||
fprintf(stderr, "error: \"%s\" both in %s from ", symbol.name.c_str(), symbol.objFileName);
|
||||
@@ -44,8 +42,7 @@ void sym_AddSymbol(Symbol &symbol)
|
||||
symbols[symbol.name] = &symbol;
|
||||
}
|
||||
|
||||
Symbol *sym_GetSymbol(std::string const &name)
|
||||
{
|
||||
Symbol *sym_GetSymbol(std::string const &name) {
|
||||
auto search = symbols.find(name);
|
||||
return search != symbols.end() ? search->second : nullptr;
|
||||
}
|
||||
|
||||
123
src/linkdefs.cpp
123
src/linkdefs.cpp
@@ -1,73 +1,74 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "platform.hpp"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
// The default values are the most lax, as they are used as-is by RGBASM; only RGBLINK has the full info,
|
||||
// so RGBASM's job is only to catch unconditional errors earlier.
|
||||
// The default values are the most lax, as they are used as-is by RGBASM; only RGBLINK has the full
|
||||
// info, so RGBASM's job is only to catch unconditional errors earlier.
|
||||
SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID] = {
|
||||
{ // SECTTYPE_WRAM0
|
||||
.name = "WRAM0"s,
|
||||
.startAddr = 0xC000,
|
||||
.size = 0x2000, // Patched to 0x1000 if !isWRAM0Mode
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
{ // SECTTYPE_VRAM
|
||||
.name = "VRAM"s,
|
||||
.startAddr = 0x8000,
|
||||
.size = 0x2000,
|
||||
.firstBank = 0,
|
||||
.lastBank = 1, // Patched to 0 if isDmgMode
|
||||
},
|
||||
{ // SECTTYPE_ROMX
|
||||
.name = "ROMX"s,
|
||||
.startAddr = 0x4000,
|
||||
.size = 0x4000,
|
||||
.firstBank = 1,
|
||||
.lastBank = 65535,
|
||||
},
|
||||
{ // SECTTYPE_ROM0
|
||||
.name = "ROM0"s,
|
||||
.startAddr = 0x0000,
|
||||
.size = 0x8000, // Patched to 0x4000 if !is32kMode
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
{ // SECTTYPE_HRAM
|
||||
.name = "HRAM"s,
|
||||
.startAddr = 0xFF80,
|
||||
.size = 0x007F,
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
{ // SECTTYPE_WRAMX
|
||||
.name = "WRAMX"s,
|
||||
.startAddr = 0xD000,
|
||||
.size = 0x1000,
|
||||
.firstBank = 1,
|
||||
.lastBank = 7,
|
||||
},
|
||||
{ // SECTTYPE_SRAM
|
||||
.name = "SRAM"s,
|
||||
.startAddr = 0xA000,
|
||||
.size = 0x2000,
|
||||
.firstBank = 0,
|
||||
.lastBank = 255,
|
||||
},
|
||||
{ // SECTTYPE_OAM
|
||||
.name = "OAM"s,
|
||||
.startAddr = 0xFE00,
|
||||
.size = 0x00A0,
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
{
|
||||
.name = "WRAM0"s,
|
||||
.startAddr = 0xC000,
|
||||
.size = 0x2000 /* Patched to 0x1000 if !isWRAM0Mode */,
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
{
|
||||
.name = "VRAM"s,
|
||||
.startAddr = 0x8000,
|
||||
.size = 0x2000,
|
||||
.firstBank = 0,
|
||||
.lastBank = 1 /* Patched to 0 if isDmgMode */,
|
||||
},
|
||||
{
|
||||
.name = "ROMX"s,
|
||||
.startAddr = 0x4000,
|
||||
.size = 0x4000,
|
||||
.firstBank = 1,
|
||||
.lastBank = 65535,
|
||||
},
|
||||
{
|
||||
.name = "ROM0"s,
|
||||
.startAddr = 0x0000,
|
||||
.size = 0x8000 /* Patched to 0x4000 if !is32kMode */,
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
{
|
||||
.name = "HRAM"s,
|
||||
.startAddr = 0xFF80,
|
||||
.size = 0x007F,
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
{
|
||||
.name = "WRAMX"s,
|
||||
.startAddr = 0xD000,
|
||||
.size = 0x1000,
|
||||
.firstBank = 1,
|
||||
.lastBank = 7,
|
||||
},
|
||||
{
|
||||
.name = "SRAM"s,
|
||||
.startAddr = 0xA000,
|
||||
.size = 0x2000,
|
||||
.firstBank = 0,
|
||||
.lastBank = 255,
|
||||
},
|
||||
{
|
||||
.name = "OAM"s,
|
||||
.startAddr = 0xFE00,
|
||||
.size = 0x00A0,
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
};
|
||||
|
||||
char const * const sectionModNames[] = {
|
||||
"regular", // SECTION_NORMAL
|
||||
"union", // SECTION_UNION
|
||||
"fragment", // SECTION_FRAGMENT
|
||||
"regular", // SECTION_NORMAL
|
||||
"union", // SECTION_UNION
|
||||
"fragment", // SECTION_FRAGMENT
|
||||
};
|
||||
|
||||
@@ -2,19 +2,17 @@
|
||||
|
||||
// Mathematical operators that don't reuse C++'s behavior
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "opmath.hpp"
|
||||
|
||||
int32_t op_divide(int32_t dividend, int32_t divisor)
|
||||
{
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t op_divide(int32_t dividend, int32_t divisor) {
|
||||
// Adjust division to floor toward negative infinity,
|
||||
// not truncate toward zero
|
||||
return dividend / divisor - ((dividend % divisor < 0) != (divisor < 0));
|
||||
}
|
||||
|
||||
int32_t op_modulo(int32_t dividend, int32_t divisor)
|
||||
{
|
||||
int32_t op_modulo(int32_t dividend, int32_t divisor) {
|
||||
int32_t remainder = dividend % divisor;
|
||||
|
||||
// Adjust modulo to have the sign of the divisor,
|
||||
@@ -22,8 +20,7 @@ int32_t op_modulo(int32_t dividend, int32_t divisor)
|
||||
return remainder + divisor * ((remainder < 0) != (divisor < 0));
|
||||
}
|
||||
|
||||
int32_t op_exponent(int32_t base, uint32_t power)
|
||||
{
|
||||
int32_t op_exponent(int32_t base, uint32_t power) {
|
||||
int32_t result = 1;
|
||||
|
||||
for (;;) {
|
||||
@@ -38,8 +35,7 @@ int32_t op_exponent(int32_t base, uint32_t power)
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t op_shift_left(int32_t value, int32_t amount)
|
||||
{
|
||||
int32_t op_shift_left(int32_t value, int32_t amount) {
|
||||
// Get the easy cases out of the way
|
||||
if (amount == 0)
|
||||
return value;
|
||||
@@ -55,8 +51,7 @@ int32_t op_shift_left(int32_t value, int32_t amount)
|
||||
return (uint32_t)value << amount;
|
||||
}
|
||||
|
||||
int32_t op_shift_right(int32_t value, int32_t amount)
|
||||
{
|
||||
int32_t op_shift_right(int32_t value, int32_t amount) {
|
||||
// Repeat the easy cases here to avoid INT_MIN funny business
|
||||
if (amount == 0)
|
||||
return value;
|
||||
@@ -79,8 +74,7 @@ int32_t op_shift_right(int32_t value, int32_t amount)
|
||||
return ((uint32_t)value >> amount) | amount_high_bits;
|
||||
}
|
||||
|
||||
int32_t op_shift_right_unsigned(int32_t value, int32_t amount)
|
||||
{
|
||||
int32_t op_shift_right_unsigned(int32_t value, int32_t amount) {
|
||||
// Repeat the easy cases here to avoid INT_MIN funny business
|
||||
if (amount == 0)
|
||||
return value;
|
||||
|
||||
10
src/util.cpp
10
src/util.cpp
@@ -1,16 +1,15 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
|
||||
char const *printChar(int c)
|
||||
{
|
||||
char const *printChar(int c) {
|
||||
// "'A'" + '\0': 4 bytes
|
||||
// "'\\n'" + '\0': 5 bytes
|
||||
// "0xFF" + '\0': 5 bytes
|
||||
@@ -51,8 +50,7 @@ char const *printChar(int c)
|
||||
return buf;
|
||||
}
|
||||
|
||||
size_t readUTF8Char(std::vector<uint8_t> *dest, char const *src)
|
||||
{
|
||||
size_t readUTF8Char(std::vector<uint8_t> *dest, char const *src) {
|
||||
uint32_t state = 0;
|
||||
uint32_t codep;
|
||||
size_t i = 0;
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "version.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
// This variable is passed via `-D` from the Makefile, but not from CMake
|
||||
// (in which `configure_file()` is used on this file to replace some syntax)
|
||||
#ifndef BUILD_VERSION_STRING
|
||||
// CMake-specific syntax here
|
||||
#define BUILD_VERSION_STRING "@GIT_REV@"
|
||||
// CMake-specific syntax here
|
||||
#define BUILD_VERSION_STRING "@GIT_REV@"
|
||||
#endif
|
||||
|
||||
char const *get_package_version_string()
|
||||
{
|
||||
char const *get_package_version_string() {
|
||||
// The following conditional should be simplified by the compiler.
|
||||
if (strlen(BUILD_VERSION_STRING) == 0) {
|
||||
// Fallback if version string can't be obtained from Git
|
||||
#ifndef PACKAGE_VERSION_RC
|
||||
return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR)
|
||||
"." EXPAND_AND_STR(PACKAGE_VERSION_MINOR)
|
||||
"." EXPAND_AND_STR(PACKAGE_VERSION_PATCH);
|
||||
return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR) "." EXPAND_AND_STR(PACKAGE_VERSION_MINOR
|
||||
) "." EXPAND_AND_STR(PACKAGE_VERSION_PATCH);
|
||||
#else
|
||||
return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR)
|
||||
"." EXPAND_AND_STR(PACKAGE_VERSION_MINOR)
|
||||
"." EXPAND_AND_STR(PACKAGE_VERSION_PATCH)
|
||||
"-rc" EXPAND_AND_STR(PACKAGE_VERSION_RC);
|
||||
return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR) "." EXPAND_AND_STR(PACKAGE_VERSION_MINOR
|
||||
) "." EXPAND_AND_STR(PACKAGE_VERSION_PATCH) "-rc" EXPAND_AND_STR(PACKAGE_VERSION_RC);
|
||||
#endif
|
||||
} else {
|
||||
return BUILD_VERSION_STRING;
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
#include <png.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct Attributes {
|
||||
@@ -70,8 +70,9 @@ static void generate_tile_attributes(Attributes *attributes) {
|
||||
|
||||
// Use an array to look up the number of colors in the palette; this is faster (and simpler)
|
||||
// than doing a population count over the bits
|
||||
static char const popcount[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3,
|
||||
4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4};
|
||||
static char const popcount[] = {
|
||||
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4,
|
||||
};
|
||||
attributes->nbColors = popcount[pal];
|
||||
}
|
||||
|
||||
@@ -104,8 +105,7 @@ static void generate_tile_data(unsigned char tiledata[8][8], unsigned colorcount
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
copy_tile_data(unsigned char destination[8][8], unsigned char const source[8][8]) {
|
||||
static void copy_tile_data(unsigned char destination[8][8], unsigned char const source[8][8]) {
|
||||
// Apply a random rotation to the copy
|
||||
// coord ^ 7 = inverted coordinate; coord ^ 0 = regular coordinate
|
||||
unsigned xmask = getRandomBits(1) * 7;
|
||||
@@ -148,9 +148,14 @@ static uint8_t _5to8(uint8_t five) {
|
||||
}
|
||||
|
||||
// Can't mark as `const`, as the array type is otherwise not compatible (augh)
|
||||
static void write_image(char const *filename, uint16_t /* const */ palettes[/* 60 */][4],
|
||||
unsigned char /* const */ (*tileData)[8][8], Attributes const *attributes,
|
||||
uint8_t width, uint8_t height) {
|
||||
static void write_image(
|
||||
char const *filename,
|
||||
uint16_t /* const */ palettes[/* 60 */][4],
|
||||
unsigned char /* const */ (*tileData)[8][8],
|
||||
Attributes const *attributes,
|
||||
uint8_t width,
|
||||
uint8_t height
|
||||
) {
|
||||
uint8_t const nbTiles = width * height;
|
||||
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
png_infop pngInfo = png_create_info_struct(png);
|
||||
@@ -167,9 +172,17 @@ static void write_image(char const *filename, uint16_t /* const */ palettes[/* 6
|
||||
}
|
||||
png_init_io(png, file);
|
||||
|
||||
png_set_IHDR(png, pngInfo, width * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA,
|
||||
getRandomBits(1) ? PNG_INTERLACE_NONE : PNG_INTERLACE_ADAM7,
|
||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
png_set_IHDR(
|
||||
png,
|
||||
pngInfo,
|
||||
width * 8,
|
||||
height * 8,
|
||||
8,
|
||||
PNG_COLOR_TYPE_RGB_ALPHA,
|
||||
getRandomBits(1) ? PNG_INTERLACE_NONE : PNG_INTERLACE_ADAM7,
|
||||
PNG_COMPRESSION_TYPE_DEFAULT,
|
||||
PNG_FILTER_TYPE_DEFAULT
|
||||
);
|
||||
|
||||
// While it would be nice to write the image little by little, I really don't want to handle
|
||||
// interlacing myself. (We're doing interlacing to test that RGBGFX correctly handles it.)
|
||||
|
||||
@@ -111,10 +111,13 @@ class Png {
|
||||
std::streamsize nbBytesRead = self->file.sgetn(reinterpret_cast<char *>(data), expectedLen);
|
||||
|
||||
if (nbBytesRead != expectedLen) {
|
||||
fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more "
|
||||
"bytes after reading %zu)",
|
||||
self->path.c_str(), length - nbBytesRead,
|
||||
(size_t)self->file.pubseekoff(0, std::ios_base::cur));
|
||||
fatal(
|
||||
"Error reading input image (\"%s\"): file too short (expected at least %zd more "
|
||||
"bytes after reading %zu)",
|
||||
self->path.c_str(),
|
||||
length - nbBytesRead,
|
||||
(size_t)self->file.pubseekoff(0, std::ios_base::cur)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,8 +152,9 @@ public:
|
||||
fatal("Input file (\"%s\") is not a PNG image!", path.c_str());
|
||||
}
|
||||
|
||||
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError,
|
||||
handleWarning);
|
||||
png = png_create_read_struct(
|
||||
PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError, handleWarning
|
||||
);
|
||||
if (!png) {
|
||||
fatal("Failed to allocate PNG structure: %s", strerror(errno));
|
||||
}
|
||||
@@ -174,8 +178,9 @@ public:
|
||||
|
||||
int bitDepth, interlaceType; //, compressionType, filterMethod;
|
||||
|
||||
png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr,
|
||||
nullptr);
|
||||
png_get_IHDR(
|
||||
png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr, nullptr
|
||||
);
|
||||
|
||||
if (width % 8 != 0) {
|
||||
fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8!", width);
|
||||
@@ -308,19 +313,36 @@ static char *execProg(char const *name, char * const *argv) {
|
||||
fatal("Error waiting for %s: %s", name, strerror(errno));
|
||||
} else if (info.si_code != CLD_EXITED) {
|
||||
assert(info.si_code == CLD_KILLED || info.si_code == CLD_DUMPED);
|
||||
fatal("%s was terminated by signal %s%s\n\tThe command was: [%s]", name, strsignal(info.si_status),
|
||||
info.si_code == CLD_DUMPED ? " (core dumped)" : "", formatArgv());
|
||||
fatal(
|
||||
"%s was terminated by signal %s%s\n\tThe command was: [%s]",
|
||||
name,
|
||||
strsignal(info.si_status),
|
||||
info.si_code == CLD_DUMPED ? " (core dumped)" : "",
|
||||
formatArgv()
|
||||
);
|
||||
} else if (info.si_status != 0) {
|
||||
fatal("%s returned with status %d\n\tThe command was: [%s]", name, info.si_status, formatArgv());
|
||||
fatal(
|
||||
"%s returned with status %d\n\tThe command was: [%s]",
|
||||
name,
|
||||
info.si_status,
|
||||
formatArgv()
|
||||
);
|
||||
}
|
||||
|
||||
#else // defined(_MSC_VER) || defined(__MINGW32__)
|
||||
|
||||
auto winStrerror = [](DWORD errnum) {
|
||||
LPTSTR buf;
|
||||
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
|
||||
| FORMAT_MESSAGE_MAX_WIDTH_MASK,
|
||||
nullptr, errnum, 0, (LPTSTR)&buf, 0, nullptr)
|
||||
if (FormatMessage(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
|
||||
| FORMAT_MESSAGE_MAX_WIDTH_MASK,
|
||||
nullptr,
|
||||
errnum,
|
||||
0,
|
||||
(LPTSTR)&buf,
|
||||
0,
|
||||
nullptr
|
||||
)
|
||||
== 0) {
|
||||
fatal("Failed to get error message for error 0x%x", errnum);
|
||||
}
|
||||
@@ -341,28 +363,31 @@ static char *execProg(char const *name, char * const *argv) {
|
||||
|
||||
STARTUPINFOA startupInfo;
|
||||
GetStartupInfoA(&startupInfo);
|
||||
STARTUPINFOA childStartupInfo{sizeof(startupInfo),
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
0};
|
||||
STARTUPINFOA childStartupInfo{
|
||||
sizeof(startupInfo),
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
};
|
||||
|
||||
PROCESS_INFORMATION child;
|
||||
if (CreateProcessA(nullptr, cmdLine, nullptr, nullptr, true, 0, nullptr, nullptr,
|
||||
&childStartupInfo, &child)
|
||||
if (CreateProcessA(
|
||||
nullptr, cmdLine, nullptr, nullptr, true, 0, nullptr, nullptr, &childStartupInfo, &child
|
||||
)
|
||||
== 0) {
|
||||
return winStrerror(GetLastError());
|
||||
}
|
||||
@@ -395,8 +420,9 @@ int main(int argc, char *argv[]) {
|
||||
char *args[] = {path, argv[1], file, nullptr};
|
||||
|
||||
if (auto ret = execProg("randtilegen", args); ret != nullptr) {
|
||||
fatal("Failed to execute ./randtilegen (%s). Is it in the current working directory?",
|
||||
ret);
|
||||
fatal(
|
||||
"Failed to execute ./randtilegen (%s). Is it in the current working directory?", ret
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +431,8 @@ int main(int argc, char *argv[]) {
|
||||
pal_opt[] = "-p", pal_file[] = "result.pal", attr_opt[] = "-a",
|
||||
attr_file[] = "result.attrmap", in_file[] = "out0.png";
|
||||
std::vector<char *> args(
|
||||
{path, out_opt, out_file, pal_opt, pal_file, attr_opt, attr_file, in_file});
|
||||
{path, out_opt, out_file, pal_opt, pal_file, attr_opt, attr_file, in_file}
|
||||
);
|
||||
// Also copy the trailing `nullptr`
|
||||
std::copy_n(&argv[2], argc - 1, std::back_inserter(args));
|
||||
|
||||
@@ -422,8 +449,17 @@ int main(int argc, char *argv[]) {
|
||||
attr_opt[] = "-a", attr_file[] = "result.attrmap", in_file[] = "result.png";
|
||||
auto width_string = std::to_string(image0.getWidth() / 8);
|
||||
std::vector<char *> args = {
|
||||
path, reverse_opt, width_string.data(), out_opt, out_file, pal_opt,
|
||||
pal_file, attr_opt, attr_file, in_file};
|
||||
path,
|
||||
reverse_opt,
|
||||
width_string.data(),
|
||||
out_opt,
|
||||
out_file,
|
||||
pal_opt,
|
||||
pal_file,
|
||||
attr_opt,
|
||||
attr_file,
|
||||
in_file,
|
||||
};
|
||||
// Also copy the trailing `nullptr`
|
||||
std::copy_n(&argv[2], argc - 1, std::back_inserter(args));
|
||||
|
||||
@@ -456,10 +492,20 @@ int main(int argc, char *argv[]) {
|
||||
};
|
||||
|
||||
if (cgbColor(px0) != cgbColor(px1)) {
|
||||
error("Color mismatch at (%" PRIu32 ", %" PRIu32
|
||||
"): (%u,%u,%u,%u) became (%u,%u,%u,%u) after round-tripping",
|
||||
x, y, px0.red, px0.green, px0.blue, px0.alpha, px1.red, px1.green, px1.blue,
|
||||
px1.alpha);
|
||||
error(
|
||||
"Color mismatch at (%" PRIu32 ", %" PRIu32
|
||||
"): (%u,%u,%u,%u) became (%u,%u,%u,%u) after round-tripping",
|
||||
x,
|
||||
y,
|
||||
px0.red,
|
||||
px0.green,
|
||||
px0.blue,
|
||||
px0.alpha,
|
||||
px1.red,
|
||||
px1.green,
|
||||
px1.blue,
|
||||
px1.alpha
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user