mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
The old "find symbol with auto scope" function is now three: - One finds the exact name passed to it, skipping any checks This is useful e.g. if such checks were already performed. - One checks that the name is not scoped, and calls the first. This is useful for names that cannot be scoped, such as checking for EQUS. Doing this instead of the third should improve performance somehwat, since this specific case is hit by the lexer each time an identifier is read. - The last one checks if the name should be expanded (`.loc` → `Glob.loc`), and that the local part is not scoped. This is essentially the old function.
823 lines
20 KiB
C
823 lines
20 KiB
C
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "asm/fstack.h"
|
|
#include "asm/main.h"
|
|
#include "asm/output.h"
|
|
#include "asm/rpn.h"
|
|
#include "asm/section.h"
|
|
#include "asm/warning.h"
|
|
|
|
#include "extern/err.h"
|
|
#include "platform.h" // strdup
|
|
|
|
struct SectionStackEntry {
|
|
struct Section *section;
|
|
char const *scope; /* Section's symbol scope */
|
|
uint32_t offset;
|
|
struct SectionStackEntry *next;
|
|
};
|
|
|
|
struct SectionStackEntry *sectionStack;
|
|
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */
|
|
static struct Section *currentLoadSection = NULL;
|
|
uint32_t loadOffset; /* The offset of the LOAD section within its parent */
|
|
|
|
struct UnionStackEntry {
|
|
uint32_t start;
|
|
uint32_t size;
|
|
struct UnionStackEntry *next;
|
|
} *unionStack = NULL;
|
|
|
|
/*
|
|
* A quick check to see if we have an initialized section
|
|
*/
|
|
static inline void checksection(void)
|
|
{
|
|
if (pCurrentSection == NULL)
|
|
fatalerror("Code generation before SECTION directive\n");
|
|
}
|
|
|
|
/*
|
|
* A quick check to see if we have an initialized section that can contain
|
|
* this much initialized data
|
|
*/
|
|
static inline void checkcodesection(void)
|
|
{
|
|
checksection();
|
|
|
|
if (!sect_HasData(pCurrentSection->type))
|
|
fatalerror("Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
|
|
pCurrentSection->name);
|
|
}
|
|
|
|
static inline void checkSectionSize(struct Section const *sect, uint32_t size)
|
|
{
|
|
uint32_t maxSize = maxsize[sect->type];
|
|
|
|
if (size > maxSize)
|
|
fatalerror("Section '%s' grew too big (max size = 0x%" PRIX32
|
|
" bytes, reached 0x%" PRIX32 ").\n", sect->name, maxSize, size);
|
|
}
|
|
|
|
/*
|
|
* Check if the section has grown too much.
|
|
*/
|
|
static inline void reserveSpace(uint32_t delta_size)
|
|
{
|
|
/*
|
|
* This check is here to trap broken code that generates sections that
|
|
* are too big and to prevent the assembler from generating huge object
|
|
* files or trying to allocate too much memory.
|
|
* A check at the linking stage is still necessary.
|
|
*/
|
|
checkSectionSize(pCurrentSection, curOffset + loadOffset + delta_size);
|
|
if (currentLoadSection)
|
|
checkSectionSize(currentLoadSection, curOffset + delta_size);
|
|
}
|
|
|
|
struct Section *out_FindSectionByName(const char *name)
|
|
{
|
|
struct Section *sect = pSectionList;
|
|
|
|
while (sect) {
|
|
if (strcmp(name, sect->name) == 0)
|
|
return sect;
|
|
|
|
sect = sect->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Find a section by name and type. If it doesn't exist, create it
|
|
*/
|
|
static struct Section *getSection(char const *name, enum SectionType type,
|
|
uint32_t org, struct SectionSpec const *attrs,
|
|
enum SectionModifier mod)
|
|
{
|
|
#define mask(align) ((1 << (align)) - 1)
|
|
uint32_t bank = attrs->bank;
|
|
uint8_t alignment = attrs->alignment;
|
|
uint16_t alignOffset = attrs->alignOfs;
|
|
|
|
if (bank != -1) {
|
|
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 < bankranges[type][0]
|
|
|| bank > bankranges[type][1])
|
|
error("%s bank value $%" PRIx32 " out of range ($%" PRIx32 " to $%"
|
|
PRIx32 ")\n", typeNames[type], bank,
|
|
bankranges[type][0], bankranges[type][1]);
|
|
}
|
|
|
|
if (alignOffset >= 1 << alignment) {
|
|
error("Alignment offset must not be greater than alignment (%" PRIu16 " < %u)\n",
|
|
alignOffset, 1U << alignment);
|
|
alignOffset = 0;
|
|
}
|
|
|
|
if (alignment != 0) {
|
|
/* It doesn't make sense to have both alignment and org set */
|
|
uint32_t mask = mask(alignment);
|
|
|
|
if (org != -1) {
|
|
if ((org - alignOffset) & mask)
|
|
error("Section \"%s\"'s fixed address doesn't match its alignment\n",
|
|
name);
|
|
alignment = 0; /* Ignore it if it's satisfied */
|
|
} else if (startaddr[type] & mask) {
|
|
error("Section \"%s\"'s alignment cannot be attained in %s\n",
|
|
name, typeNames[type]);
|
|
}
|
|
}
|
|
|
|
if (org != -1) {
|
|
if (org < startaddr[type] || org > endaddr(type))
|
|
error("Section \"%s\"'s fixed address %#" PRIx32
|
|
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
|
|
name, org, startaddr[type], endaddr(type));
|
|
}
|
|
|
|
if (nbbanks(type) == 1)
|
|
bank = bankranges[type][0];
|
|
|
|
struct Section *sect = out_FindSectionByName(name);
|
|
|
|
if (sect) {
|
|
unsigned int nbSectErrors = 0;
|
|
#define fail(...) \
|
|
do { \
|
|
error(__VA_ARGS__); \
|
|
nbSectErrors++; \
|
|
} while (0)
|
|
|
|
if (type != sect->type)
|
|
fail("Section \"%s\" already exists but with type %s\n",
|
|
sect->name, typeNames[sect->type]);
|
|
|
|
if (sect->modifier != mod)
|
|
fail("Section \"%s\" already declared as %s section\n",
|
|
sect->name, sectionModNames[sect->modifier]);
|
|
/*
|
|
* Normal sections need to have exactly identical constraints;
|
|
* but unionized sections only need "compatible" constraints,
|
|
* and they end up with the strictest combination of both
|
|
*/
|
|
if (mod == SECTION_UNION) {
|
|
/*
|
|
* WARNING: see comment about assumption in
|
|
* `EndLoadSection` if modifying the following check!
|
|
*/
|
|
if (sect_HasData(type))
|
|
fail("Cannot declare ROM sections as UNION\n");
|
|
if (org != -1) {
|
|
/* If both are fixed, they must be the same */
|
|
if (sect->org != -1 && sect->org != org)
|
|
fail("Section \"%s\" already declared as fixed at different address $%"
|
|
PRIx32 "\n",
|
|
sect->name, sect->org);
|
|
else if (sect->align != 0
|
|
&& (mask(sect->align)
|
|
& (org - sect->alignOfs)))
|
|
fail("Section \"%s\" already declared as aligned to %u bytes (offset %"
|
|
PRIu16 ")\n", sect->name, 1U << sect->align, sect->alignOfs);
|
|
else
|
|
/* Otherwise, just override */
|
|
sect->org = org;
|
|
} else if (alignment != 0) {
|
|
/* Make sure any fixed address is compatible */
|
|
if (sect->org != -1) {
|
|
if ((sect->org - alignOffset)
|
|
& mask(alignment))
|
|
fail("Section \"%s\" already declared as fixed at incompatible address $%"
|
|
PRIx32 "\n", sect->name, sect->org);
|
|
/* Check if alignment offsets are compatible */
|
|
} else if ((alignOffset & mask(sect->align))
|
|
!= (sect->alignOfs
|
|
& mask(alignment))) {
|
|
fail("Section \"%s\" already declared with incompatible %"
|
|
PRIu8 "-byte alignment (offset %" PRIu16 ")\n",
|
|
sect->name, 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;
|
|
sect->alignOfs = alignOffset;
|
|
}
|
|
}
|
|
/* If the section's bank is unspecified, override it */
|
|
if (sect->bank == -1)
|
|
sect->bank = bank;
|
|
/* If both specify a bank, it must be the same one */
|
|
else if (bank != -1 && sect->bank != bank)
|
|
fail("Section \"%s\" already declared with different bank %"
|
|
PRIu32 "\n", sect->name, sect->bank);
|
|
} else { /* Section fragments are handled identically in RGBASM */
|
|
/* However, concaternating non-fragments will be made an error */
|
|
if (sect->modifier != SECTION_FRAGMENT || mod != SECTION_FRAGMENT)
|
|
warning(WARNING_OBSOLETE,
|
|
"Concatenation of non-fragment sections is deprecated\n");
|
|
|
|
if (org != sect->org) {
|
|
if (sect->org == -1)
|
|
fail("Section \"%s\" already declared as floating\n",
|
|
sect->name);
|
|
else
|
|
fail("Section \"%s\" already declared as fixed at $%"
|
|
PRIx32 "\n", sect->name, sect->org);
|
|
}
|
|
if (bank != sect->bank) {
|
|
if (sect->bank == -1)
|
|
fail("Section \"%s\" already declared as floating bank\n",
|
|
sect->name);
|
|
else
|
|
fail("Section \"%s\" already declared as fixed at bank %"
|
|
PRIu32 "\n", sect->name, sect->bank);
|
|
}
|
|
if (alignment != sect->align) {
|
|
if (sect->align == 0)
|
|
fail("Section \"%s\" already declared as unaligned\n",
|
|
sect->name);
|
|
else
|
|
fail("Section \"%s\" already declared as aligned to %u bytes\n",
|
|
sect->name, 1U << sect->align);
|
|
}
|
|
}
|
|
|
|
if (nbSectErrors)
|
|
fatalerror("Cannot create section \"%s\" (%u errors)\n",
|
|
sect->name, nbSectErrors);
|
|
#undef fail
|
|
return sect;
|
|
}
|
|
|
|
sect = malloc(sizeof(*sect));
|
|
if (sect == NULL)
|
|
fatalerror("Not enough memory for section: %s\n", strerror(errno));
|
|
|
|
sect->name = strdup(name);
|
|
if (sect->name == NULL)
|
|
fatalerror("Not enough memory for section name: %s\n", strerror(errno));
|
|
|
|
sect->type = type;
|
|
sect->modifier = mod;
|
|
sect->size = 0;
|
|
sect->org = org;
|
|
sect->bank = bank;
|
|
sect->align = alignment;
|
|
sect->alignOfs = alignOffset;
|
|
sect->next = pSectionList;
|
|
sect->patches = NULL;
|
|
|
|
/* It is only needed to allocate memory for ROM sections. */
|
|
if (sect_HasData(type)) {
|
|
uint32_t sectsize;
|
|
|
|
sectsize = maxsize[type];
|
|
sect->data = malloc(sectsize);
|
|
if (sect->data == NULL)
|
|
fatalerror("Not enough memory for section: %s\n", strerror(errno));
|
|
} else {
|
|
sect->data = NULL;
|
|
}
|
|
|
|
/*
|
|
* Add the new section to the list
|
|
* at the beginning because order doesn't matter
|
|
*/
|
|
pSectionList = sect;
|
|
|
|
return sect;
|
|
#undef mask
|
|
}
|
|
|
|
/*
|
|
* Set the current section
|
|
*/
|
|
static void changeSection(void)
|
|
{
|
|
if (unionStack)
|
|
fatalerror("Cannot change the section within a UNION\n");
|
|
|
|
sym_SetCurrentSymbolScope(NULL);
|
|
}
|
|
|
|
/*
|
|
* Set the current section by name and type
|
|
*/
|
|
void out_NewSection(char const *name, uint32_t type, uint32_t org,
|
|
struct SectionSpec const *attribs, enum SectionModifier mod)
|
|
{
|
|
if (currentLoadSection)
|
|
fatalerror("Cannot change the section within a `LOAD` block\n");
|
|
|
|
struct Section *sect = getSection(name, type, org, attribs, mod);
|
|
|
|
changeSection();
|
|
curOffset = mod == SECTION_UNION ? 0 : sect->size;
|
|
pCurrentSection = sect;
|
|
}
|
|
|
|
/*
|
|
* Set the current section by name and type
|
|
*/
|
|
void out_SetLoadSection(char const *name, uint32_t type, uint32_t org,
|
|
struct SectionSpec const *attribs)
|
|
{
|
|
checkcodesection();
|
|
|
|
if (currentLoadSection)
|
|
fatalerror("`LOAD` blocks cannot be nested\n");
|
|
|
|
struct Section *sect = getSection(name, type, org, attribs, false);
|
|
|
|
loadOffset = curOffset;
|
|
curOffset = 0; /* curOffset -= loadOffset; */
|
|
changeSection();
|
|
currentLoadSection = sect;
|
|
}
|
|
|
|
void out_EndLoadSection(void)
|
|
{
|
|
if (!currentLoadSection)
|
|
error("Found `ENDL` outside of a `LOAD` block\n");
|
|
currentLoadSection = NULL;
|
|
|
|
changeSection();
|
|
curOffset += loadOffset;
|
|
loadOffset = 0;
|
|
}
|
|
|
|
struct Section *sect_GetSymbolSection(void)
|
|
{
|
|
return currentLoadSection ? currentLoadSection : pCurrentSection;
|
|
}
|
|
|
|
/*
|
|
* The offset into the section above
|
|
*/
|
|
uint32_t sect_GetSymbolOffset(void)
|
|
{
|
|
return curOffset;
|
|
}
|
|
|
|
uint32_t sect_GetOutputOffset(void)
|
|
{
|
|
return curOffset + loadOffset;
|
|
}
|
|
|
|
void sect_AlignPC(uint8_t alignment, uint16_t offset)
|
|
{
|
|
checksection();
|
|
struct Section *sect = sect_GetSymbolSection();
|
|
|
|
if (sect->org != -1) {
|
|
if ((sym_GetPCValue() - offset) % (1 << alignment))
|
|
error("Section's fixed address fails required alignment (PC = $%04"
|
|
PRIx32 ")\n", sym_GetPCValue());
|
|
} else if (sect->align != 0) {
|
|
if ((((sect->alignOfs + curOffset) % (1 << sect->align))
|
|
- offset) % (1 << alignment)) {
|
|
error("Section's alignment fails required alignment (offset from section start = $%04"
|
|
PRIx32 ")\n", curOffset);
|
|
} else if (alignment > sect->align) {
|
|
sect->align = alignment;
|
|
sect->alignOfs =
|
|
(offset - curOffset) % (1 << alignment);
|
|
}
|
|
} else {
|
|
sect->align = alignment;
|
|
sect->alignOfs = offset;
|
|
}
|
|
}
|
|
|
|
static inline void growSection(uint32_t growth)
|
|
{
|
|
curOffset += growth;
|
|
if (curOffset + loadOffset > pCurrentSection->size)
|
|
pCurrentSection->size = curOffset + loadOffset;
|
|
if (currentLoadSection && curOffset > currentLoadSection->size)
|
|
currentLoadSection->size = curOffset;
|
|
}
|
|
|
|
static inline void writebyte(uint8_t byte)
|
|
{
|
|
pCurrentSection->data[sect_GetOutputOffset()] = byte;
|
|
growSection(1);
|
|
}
|
|
|
|
static inline void writeword(uint16_t b)
|
|
{
|
|
writebyte(b & 0xFF);
|
|
writebyte(b >> 8);
|
|
}
|
|
|
|
static inline void writelong(uint32_t b)
|
|
{
|
|
writebyte(b & 0xFF);
|
|
writebyte(b >> 8);
|
|
writebyte(b >> 16);
|
|
writebyte(b >> 24);
|
|
}
|
|
|
|
static inline void createPatch(enum PatchType type,
|
|
struct Expression const *expr)
|
|
{
|
|
out_CreatePatch(type, expr, sect_GetOutputOffset());
|
|
}
|
|
|
|
void sect_StartUnion(void)
|
|
{
|
|
if (!pCurrentSection)
|
|
fatalerror("UNIONs must be inside a SECTION\n");
|
|
if (sect_HasData(pCurrentSection->type))
|
|
fatalerror("Cannot use UNION inside of ROM0 or ROMX sections\n");
|
|
struct UnionStackEntry *entry = malloc(sizeof(*entry));
|
|
|
|
if (!entry)
|
|
fatalerror("Failed to allocate new union stack entry: %s\n", strerror(errno));
|
|
entry->start = curOffset;
|
|
entry->size = 0;
|
|
entry->next = unionStack;
|
|
unionStack = entry;
|
|
}
|
|
|
|
static void endUnionMember(void)
|
|
{
|
|
uint32_t memberSize = curOffset - unionStack->start;
|
|
|
|
if (memberSize > unionStack->size)
|
|
unionStack->size = memberSize;
|
|
curOffset = unionStack->start;
|
|
}
|
|
|
|
void sect_NextUnionMember(void)
|
|
{
|
|
if (!unionStack)
|
|
fatalerror("Found NEXTU outside of a UNION construct\n");
|
|
endUnionMember();
|
|
}
|
|
|
|
void sect_EndUnion(void)
|
|
{
|
|
if (!unionStack)
|
|
fatalerror("Found ENDU outside of a UNION construct\n");
|
|
endUnionMember();
|
|
curOffset += unionStack->size;
|
|
struct UnionStackEntry *next = unionStack->next;
|
|
|
|
free(unionStack);
|
|
unionStack = next;
|
|
}
|
|
|
|
void sect_CheckUnionClosed(void)
|
|
{
|
|
if (unionStack)
|
|
fatalerror("Unterminated UNION construct!\n");
|
|
}
|
|
|
|
/*
|
|
* Output an absolute byte
|
|
*/
|
|
void out_AbsByte(uint8_t b)
|
|
{
|
|
checkcodesection();
|
|
reserveSpace(1);
|
|
|
|
writebyte(b);
|
|
}
|
|
|
|
void out_AbsByteGroup(uint8_t const *s, int32_t length)
|
|
{
|
|
checkcodesection();
|
|
reserveSpace(length);
|
|
|
|
while (length--)
|
|
writebyte(*s++);
|
|
}
|
|
|
|
/*
|
|
* Skip this many bytes
|
|
*/
|
|
void out_Skip(int32_t skip, bool ds)
|
|
{
|
|
checksection();
|
|
reserveSpace(skip);
|
|
|
|
if (!ds && sect_HasData(pCurrentSection->type))
|
|
warning(WARNING_EMPTY_DATA_DIRECTIVE, "db/dw/dl directive without data in ROM\n");
|
|
|
|
if (!sect_HasData(pCurrentSection->type)) {
|
|
growSection(skip);
|
|
} else {
|
|
checkcodesection();
|
|
while (skip--)
|
|
writebyte(CurrentOptions.fillchar);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Output a NULL terminated string (excluding the NULL-character)
|
|
*/
|
|
void out_String(char const *s)
|
|
{
|
|
checkcodesection();
|
|
reserveSpace(strlen(s));
|
|
|
|
while (*s)
|
|
writebyte(*s++);
|
|
}
|
|
|
|
/*
|
|
* Output a relocatable byte. Checking will be done to see if it
|
|
* is an absolute value in disguise.
|
|
*/
|
|
void out_RelByte(struct Expression *expr)
|
|
{
|
|
checkcodesection();
|
|
reserveSpace(1);
|
|
|
|
if (!rpn_isKnown(expr)) {
|
|
createPatch(PATCHTYPE_BYTE, expr);
|
|
writebyte(0);
|
|
} else {
|
|
writebyte(expr->nVal);
|
|
}
|
|
rpn_Free(expr);
|
|
}
|
|
|
|
/*
|
|
* Output several copies of a relocatable byte. Checking will be done to see if
|
|
* it is an absolute value in disguise.
|
|
*/
|
|
void out_RelBytes(struct Expression *expr, uint32_t n)
|
|
{
|
|
checkcodesection();
|
|
reserveSpace(n);
|
|
|
|
while (n--) {
|
|
if (!rpn_isKnown(expr)) {
|
|
createPatch(PATCHTYPE_BYTE, expr);
|
|
writebyte(0);
|
|
} else {
|
|
writebyte(expr->nVal);
|
|
}
|
|
}
|
|
rpn_Free(expr);
|
|
}
|
|
|
|
/*
|
|
* Output a relocatable word. Checking will be done to see if
|
|
* it's an absolute value in disguise.
|
|
*/
|
|
void out_RelWord(struct Expression *expr)
|
|
{
|
|
checkcodesection();
|
|
reserveSpace(2);
|
|
|
|
if (!rpn_isKnown(expr)) {
|
|
createPatch(PATCHTYPE_WORD, expr);
|
|
writeword(0);
|
|
} else {
|
|
writeword(expr->nVal);
|
|
}
|
|
rpn_Free(expr);
|
|
}
|
|
|
|
/*
|
|
* Output a relocatable longword. Checking will be done to see if
|
|
* is an absolute value in disguise.
|
|
*/
|
|
void out_RelLong(struct Expression *expr)
|
|
{
|
|
checkcodesection();
|
|
reserveSpace(2);
|
|
|
|
if (!rpn_isKnown(expr)) {
|
|
createPatch(PATCHTYPE_LONG, expr);
|
|
writelong(0);
|
|
} else {
|
|
writelong(expr->nVal);
|
|
}
|
|
rpn_Free(expr);
|
|
}
|
|
|
|
/*
|
|
* Output a PC-relative relocatable byte. Checking will be done to see if it
|
|
* is an absolute value in disguise.
|
|
*/
|
|
void out_PCRelByte(struct Expression *expr)
|
|
{
|
|
checkcodesection();
|
|
reserveSpace(1);
|
|
struct Symbol const *pc = sym_GetPC();
|
|
|
|
if (!rpn_IsDiffConstant(expr, pc)) {
|
|
createPatch(PATCHTYPE_JR, expr);
|
|
writebyte(0);
|
|
} else {
|
|
struct Symbol const *sym = rpn_SymbolOf(expr);
|
|
/* The offset wraps (jump from ROM to HRAM, for example) */
|
|
int16_t offset;
|
|
|
|
/* Offset is relative to the byte *after* the operand */
|
|
if (sym == pc)
|
|
offset = -2; /* PC as operand to `jr` is lower than reference PC by 2 */
|
|
else
|
|
offset = sym_GetValue(sym) - (sym_GetValue(pc) + 1);
|
|
|
|
if (offset < -128 || offset > 127) {
|
|
error("jr target out of reach (expected -129 < %" PRId16 " < 128)\n",
|
|
offset);
|
|
writebyte(0);
|
|
} else {
|
|
writebyte(offset);
|
|
}
|
|
}
|
|
rpn_Free(expr);
|
|
}
|
|
|
|
/*
|
|
* Output a binary file
|
|
*/
|
|
void out_BinaryFile(char const *s, int32_t startPos)
|
|
{
|
|
if (startPos < 0) {
|
|
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
|
|
startPos = 0;
|
|
}
|
|
|
|
char *fullPath = NULL;
|
|
size_t size = 0;
|
|
FILE *f = NULL;
|
|
|
|
if (fstk_FindFile(s, &fullPath, &size))
|
|
f = fopen(fullPath, "rb");
|
|
|
|
if (!f) {
|
|
free(fullPath);
|
|
if (oGeneratedMissingIncludes) {
|
|
oFailedOnMissingInclude = true;
|
|
return;
|
|
}
|
|
fatalerror("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
|
|
}
|
|
|
|
int32_t fsize = -1;
|
|
int byte;
|
|
|
|
checkcodesection();
|
|
if (fseek(f, 0, SEEK_END) != -1) {
|
|
fsize = ftell(f);
|
|
|
|
if (startPos >= fsize) {
|
|
error("Specified start position is greater than length of file\n");
|
|
return;
|
|
}
|
|
|
|
fseek(f, startPos, SEEK_SET);
|
|
reserveSpace(fsize - startPos);
|
|
} else {
|
|
if (errno != ESPIPE)
|
|
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);
|
|
}
|
|
|
|
while ((byte = fgetc(f)) != EOF) {
|
|
if (fsize == -1)
|
|
growSection(1);
|
|
writebyte(byte);
|
|
}
|
|
|
|
if (ferror(f))
|
|
error("Error reading INCBIN file '%s': %s\n", s, strerror(errno));
|
|
|
|
fclose(f);
|
|
free(fullPath);
|
|
}
|
|
|
|
void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
|
{
|
|
if (start_pos < 0) {
|
|
error("Start position cannot be negative (%" PRId32 ")\n", start_pos);
|
|
start_pos = 0;
|
|
}
|
|
|
|
if (length < 0) {
|
|
error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length);
|
|
length = 0;
|
|
}
|
|
if (length == 0) /* Don't even bother with 0-byte slices */
|
|
return;
|
|
|
|
char *fullPath = NULL;
|
|
size_t size = 0;
|
|
FILE *f = NULL;
|
|
|
|
if (fstk_FindFile(s, &fullPath, &size))
|
|
f = fopen(fullPath, "rb");
|
|
|
|
if (!f) {
|
|
free(fullPath);
|
|
if (oGeneratedMissingIncludes) {
|
|
oFailedOnMissingInclude = true;
|
|
return;
|
|
}
|
|
fatalerror("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
|
|
}
|
|
|
|
checkcodesection();
|
|
reserveSpace(length);
|
|
|
|
int32_t fsize;
|
|
|
|
if (fseek(f, 0, SEEK_END) != -1) {
|
|
fsize = ftell(f);
|
|
|
|
if (start_pos >= fsize) {
|
|
error("Specified start position is greater than length of file\n");
|
|
return;
|
|
}
|
|
|
|
if ((start_pos + length) > fsize)
|
|
fatalerror("Specified range in INCBIN is out of bounds\n");
|
|
|
|
fseek(f, start_pos, SEEK_SET);
|
|
} else {
|
|
if (errno != ESPIPE)
|
|
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);
|
|
}
|
|
|
|
int32_t todo = length;
|
|
|
|
while (todo--) {
|
|
int byte = fgetc(f);
|
|
|
|
if (byte != EOF) {
|
|
writebyte(byte);
|
|
} 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",
|
|
todo + 1);
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
free(fullPath);
|
|
}
|
|
|
|
/*
|
|
* Section stack routines
|
|
*/
|
|
void out_PushSection(void)
|
|
{
|
|
struct SectionStackEntry *sect = malloc(sizeof(*sect));
|
|
|
|
if (sect == NULL)
|
|
fatalerror("No memory for section stack: %s\n", strerror(errno));
|
|
sect->section = pCurrentSection;
|
|
sect->scope = sym_GetCurrentSymbolScope();
|
|
sect->offset = curOffset;
|
|
sect->next = sectionStack;
|
|
sectionStack = sect;
|
|
/* TODO: maybe set current section to NULL? */
|
|
}
|
|
|
|
void out_PopSection(void)
|
|
{
|
|
if (!sectionStack)
|
|
fatalerror("No entries in the section stack\n");
|
|
|
|
if (currentLoadSection)
|
|
fatalerror("Cannot change the section within a `LOAD` block!\n");
|
|
|
|
struct SectionStackEntry *sect;
|
|
|
|
sect = sectionStack;
|
|
changeSection();
|
|
pCurrentSection = sect->section;
|
|
sym_SetCurrentSymbolScope(sect->scope);
|
|
curOffset = sect->offset;
|
|
|
|
sectionStack = sect->next;
|
|
free(sect);
|
|
}
|