Mercurial > hg > dmlib
view tools/objlink.c @ 2586:9807ae37ad69
Require stdbool.h, we require C11 now.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 08 Dec 2022 15:59:22 +0200 |
parents | d56a0e86067a |
children |
line wrap: on
line source
/* * objlink - Link files (RAW and PRG) into one PRG object * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2002-2022 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #include "dmtool.h" #include "dmlib.h" #include "dmargs.h" #include "dmfile.h" #define SET_MAX_FILENAMES (128) #define SET_MAX_MEMBLOCKS (128) /* Typedefs */ typedef struct { int start, end; // Start and end address int type; // Type char *name; // Name of the block int placement; } DMMemBlock; typedef struct { int noverlaps; int overlaps[SET_MAX_FILENAMES]; } DMMemBlockOverlap; typedef struct { char *name; // Description of memory model char *desc; int size; // Total addressable memory size int nmemBlocks; // Defined memory areas DMMemBlock memBlocks[SET_MAX_MEMBLOCKS]; } DMMemModel; typedef struct { char *filename; int type; int placement; int addr; } DMSourceFile; // Source file type enum { STYPE_RAW = 1, STYPE_PRG, STYPE_PRGA }; // How to determine block placement / address enum { PLACE_STATIC = 1, // Already known PLACE_ARGUMENT, // Commandline argument PLACE_FILE, // From file }; enum { FMT_GENERIC = 1, FMT_PLAIN, FMT_DECIMAL }; enum { MTYPE_NONE = 0, MTYPE_ROM, // ROM MTYPE_WT, // Write to RAM through ROM MTYPE_IO, // I/O lines MTYPE_RES // RESERVED }; enum { LA_NONE = -2, LA_AUTO = -1 }; /* Memory models */ const DMMemModel memoryModels[] = { { "C64 unrestricted", "$01 = $34", 64 * 1024, 0, { { 0x0000, 0x0000, 0 , NULL , 0 } } }, { "C64 normal (IO+Basic+Kernal)", "$01 = $37", 64 * 1024, 3, { { 0xA000, 0xBFFF, MTYPE_WT , "Basic ROM" , PLACE_STATIC }, { 0xD000, 0xDFFF, MTYPE_IO , "I/O" , PLACE_STATIC }, { 0xE000, 0xFFFF, MTYPE_WT , "Kernal ROM" , PLACE_STATIC }, } }, { "C64 modified (IO+Kernal)", "$01 = $36", 64 * 1024, 2, { { 0xD000, 0xDFFF, MTYPE_IO , "I/O" , PLACE_STATIC }, { 0xE000, 0xFFFF, MTYPE_WT , "Kernal ROM" , PLACE_STATIC }, } }, { "C64 modified (IO only)", "$01 = $35", 64 * 1024, 1, { { 0xD000, 0xDFFF, MTYPE_IO , "I/O" , PLACE_STATIC }, } }, { "C64 modified (Char+Kernal+Basic)", "$01 = $33", 64 * 1024, 3, { { 0xA000, 0xBFFF, MTYPE_WT , "Basic ROM" , PLACE_STATIC }, { 0xD000, 0xDFFF, MTYPE_ROM , "Char ROM" , PLACE_STATIC }, { 0xE000, 0xFFFF, MTYPE_WT , "Kernal ROM" , PLACE_STATIC }, } }, }; static const int nmemoryModels = sizeof(memoryModels) / sizeof(memoryModels[0]); /* Global variables */ int nsrcFiles = 0; // Number of source files DMSourceFile srcFiles[SET_MAX_FILENAMES]; // Source file names int nmemBlocks = 0; DMMemBlock memBlocks[SET_MAX_FILENAMES]; DMMemBlockOverlap memBlockOverlaps[SET_MAX_FILENAMES]; char *optDestName = NULL; char *optLinkFileName = NULL; int optLinkFileFormat = FMT_GENERIC; bool optDescribe = false, optAllowOverlap = false, optCropOutput = false; unsigned int optCropStart, optCropEnd; unsigned int optInitValue = 0; int optInitValueType = 1; int optLoadAddress = LA_AUTO; int optMemModel = 0; const DMMemModel *memModel = NULL; Uint8 *memory = NULL; /* Arguments */ static const DMOptArg optList[] = { { 0, '?', "help" , "Show this help", OPT_NONE }, { 1, 0, "license" , "Print out this program's license agreement", OPT_NONE }, { 2, 'v', "verbose" , "Be more verbose", OPT_NONE }, { 10, 'r', "input-raw" , "RAW input: -r <file>:<addr>", OPT_ARGREQ }, { 12, 'p', "input-prg" , "PRG input: -p <file>[:<addr>]", OPT_ARGREQ }, { 14, 's', "section" , "Reserved section: -s <start>-<end>[,name] or <start>:<len>[,name]", OPT_ARGREQ }, { 16, 'o', "output" , "Specify output file, -o <file>", OPT_ARGREQ }, { 18, 'O', "overlap" , "Allow overlapping memory areas", OPT_NONE }, { 20, 'm', "model" , "Set memory model", OPT_ARGREQ }, { 22, 'l', "link-file" , "Output addresses and labels into file", OPT_ARGREQ }, { 24, 'f', "format" , "Format of link-file: (g)eneric, (p)lain, (d)ecimal", OPT_ARGREQ }, { 26, 'i', "init-value" , "Initialize memory with: -i <byte/word/dword>:[bwd]", OPT_ARGREQ }, { 28, 'd', "describe" , "Output ASCII memory map description", OPT_NONE }, { 30, 'c', "crop" , "Crop output file to: -c <start>-<end> or <start>:<len>", OPT_ARGREQ }, { 32, 'L', "load-address" , "Set output file load address (or 'none' for 'raw' output)", OPT_ARGREQ }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp() { dmPrintBanner(stdout, dmProgName, "[options]"); dmArgsPrintHelp(stdout, optList, optListN, 0, 80 - 2); fprintf(stdout, "\n" "Each numeric argument can be prefixed with $ or 0x for hexadecimal values.\n" "NOTICE! -p filename:<addr> will ignore load address and use <addr> instead!\n" "\n" "Available memory models:\n"); for (int i = 0; i < nmemoryModels; i++) { const DMMemModel *m = &memoryModels[i]; fprintf(stdout, " %d = %-40s [%s] (%d kB)\n", i, m->name, m->desc, m->size / 1024); } fprintf(stdout, "\n" "Memory block types:\n" " NC = Not Connected\n" " RSVD = Reserved\n" " WT = RAM under 'write-through' ROM\n" "\n" ); } off_t dmGetFileSize(FILE *fh) { off_t len, pos = ftell(fh); fseeko(fh, 0, SEEK_END); len = ftell(fh); fseek(fh, pos, SEEK_SET); return len; } /* Memory block handling */ int dmReserveMemBlock(int startAddr, int endAddr, const char *blockName, int blockType) { if (startAddr > endAddr) { return dmError(DMERR_INVALID_ARGS, "ERROR! Block '%s' has startAddr=$%.4x > endAddr=$%.4x!\n", blockName, startAddr, endAddr); } if (nmemBlocks < SET_MAX_FILENAMES) { memBlocks[nmemBlocks].start = startAddr; memBlocks[nmemBlocks].end = endAddr; memBlocks[nmemBlocks].name = dm_strdup(blockName); memBlocks[nmemBlocks].type = blockType; nmemBlocks++; return DMERR_OK; } else { return dmError(DMERR_BOUNDS, "Maximum number of memBlock definitions (%d) exceeded!\n", SET_MAX_FILENAMES); } } int dmCompareMemBlock(const void *cva, const void *cvb) { const DMMemBlock *a = cva, *b = cvb; return a->start - b->start; } bool dmParseSection(const char *arg, unsigned int *sectStart, unsigned int *sectEnd, char **sectName, bool canHasName) { char sectMode, *sep, *str, *namesep; unsigned int tmpi; bool res = false; // Define reserved section // Create a copy of the argument if ((str = dm_strdup(arg)) == NULL) { dmErrorMsg("Could not allocate temporary string!\n"); goto out; } // Get start address if ((sep = strchr(str, '-')) == NULL && (sep = strchr(str, ':')) == NULL) { dmErrorMsg("Section definition '%s' invalid.\n", arg); goto out; } sectMode = *sep; *sep = 0; // Get value if (!dmGetIntVal(str, sectStart, NULL)) { dmErrorMsg("Section start address '%s' in '%s' invalid.\n", str, arg); goto out; } // Check for name namesep = strchr(sep + 1, ','); if (canHasName && namesep != NULL) { *namesep = 0; namesep++; if (*namesep == 0) { dmErrorMsg("Section definition '%s' name is empty. Either specify name or leave it out.\n", arg); goto out; } *sectName = dm_strdup(namesep); } else if (namesep != NULL) { dmErrorMsg("Section definition does not allow a name, syntax error in '%s' at '%s'.\n", arg, namesep); goto out; } // Get end address or length if (!dmGetIntVal(sep + 1, &tmpi, NULL)) { dmErrorMsg("Section %s '%s' in '%s' invalid.\n", sectMode == '-' ? "end address" : "length", sep + 1, arg); goto out; } if (sectMode == ':') { *sectEnd = *sectStart + tmpi - 1; } else { if (tmpi < *sectStart) { dmErrorMsg("Section start address > end address in '%s'.\n", arg); goto out; } *sectEnd = tmpi; } res = true; out: dmFree(str); return res; } bool dmParseInputFile(char *arg, const int type1, const int type2, const char *desc, bool requireAddr) { unsigned int tmpi = 0; bool hasAddr = false; char *sep; if ((sep = strrchr(arg, ':')) != NULL) { *sep = 0; if (!dmGetIntVal(sep + 1, &tmpi, NULL)) { dmErrorMsg("Invalid %s address '%s' specified for '%s'.\n", desc, sep + 1, arg); return false; } hasAddr = true; } else if (requireAddr) { dmErrorMsg("No %s loading address specified for '%s'.\n", desc, arg); return false; } srcFiles[nsrcFiles].filename = arg; srcFiles[nsrcFiles].type = hasAddr ? type1 : type2; srcFiles[nsrcFiles].addr = tmpi; nsrcFiles++; return true; } bool argHandleOpt(const int optN, char *optArg, char *currArg) { switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: dmPrintLicense(stdout); exit(0); break; case 2: dmVerbosity++; break; case 10: // Add RAW if (!dmParseInputFile(optArg, STYPE_RAW, STYPE_RAW, "RAW", true)) return false; break; case 12: // Add PRG if (!dmParseInputFile(optArg, STYPE_PRGA, STYPE_PRG, "PRG", false)) return false; break; case 14: { char *sectName = "Clear"; unsigned int sectStart, sectEnd, sectLen; if (!dmParseSection(optArg, §Start, §End, §Name, true)) return false; // Allocate memory block sectLen = sectEnd - sectStart + 1; dmMsg(1, "Reserve $%.4x - $%.4x ($%x, %d bytes) as '%s'\n", sectStart, sectEnd, sectLen, sectLen, sectName); if (dmReserveMemBlock(sectStart, sectEnd, sectName, MTYPE_RES) != DMERR_OK) return false; } break; case 16: // Set output file name optDestName = optArg; break; case 18: // Allow overlapping segments optAllowOverlap = true; break; case 20: // Set memory model optMemModel = atoi(optArg); if (optMemModel < 0 || optMemModel >= nmemoryModels) { dmErrorMsg("Invalid memory model number %i!\n", optMemModel); return false; } break; case 22: // Linker file optLinkFileName = optArg; break; case 24: // Linker file format switch (tolower(optArg[0])) { case 'g': optLinkFileFormat = FMT_GENERIC; break; case 'p': optLinkFileFormat = FMT_PLAIN; break; case 'd': optLinkFileFormat = FMT_DECIMAL; break; default: dmErrorMsg("Invalid/unknown linker file format '%s'!\n", optArg); return false; } break; case 26: // Initialization value { char *p; unsigned int tmpi; optInitValueType = 1; if ((p = strrchr(optArg, ':')) != NULL) { *p = 0; switch (tolower(p[1])) { case 'b': optInitValueType = 1; break; case 'w': optInitValueType = 2; break; case 'd': optInitValueType = 4; break; default: dmErrorMsg("Invalid init value type '%c' specified for '%s'.\n", p[1], optArg); return false; } } if (!dmGetIntVal(optArg, &tmpi, NULL)) { dmErrorMsg("Invalid initvalue '%s'.\n", optArg); return false; } optInitValue = tmpi; } break; case 28: // Set describe mode optDescribe = true; break; case 30: { size_t cropLen; if (!dmParseSection(optArg, &optCropStart, &optCropEnd, NULL, false)) return false; cropLen = optCropEnd - optCropStart + 1; dmMsg(1, "Cutting output to $%.4x - $%.4x " "($%" DM_PRIx_SIZE_T ", %" DM_PRIu_SIZE_T " bytes)\n", optCropStart, optCropEnd, cropLen, cropLen); optCropOutput = true; } break; case 32: // Set loading address if (strcasecmp(optArg, "none") == 0) { optLoadAddress = LA_NONE; } else { unsigned int tmpi; if (!dmGetIntVal(optArg, &tmpi, NULL)) { dmErrorMsg("Invalid loading address '%s'.\n", optArg); return false; } if (tmpi >= 64*1024) { dmErrorMsg("Invalid or insane loading address %d/$%x!\n", tmpi, tmpi); return false; } optLoadAddress = tmpi; } break; default: dmErrorMsg("Unimplemented option argument '%s'.\n", currArg); return false; } return true; } int dmLoadPRG(const char *filename, const bool forceAddr, const int destAddr) { FILE *fh; int dataSize, loadAddr, endAddr, res = DMERR_OK; Uint16 tmpAddr; // Open the input file if ((fh = fopen(filename, "rb")) == NULL) { res = dmGetErrno(); dmErrorMsg("Error opening input file '%s': %s.\n", filename, dmErrorStr(res)); goto out; } // Get filesize if ((dataSize = dmGetFileSize(fh) - 2) < 0) { res = dmGetErrno(); dmErrorMsg("Error getting file size for '%s': %s.\n", filename, dmErrorStr(res)); goto out; } // Get loading address if (!dm_fread_le16(fh, &tmpAddr)) { res = dmGetErrno(); dmErrorMsg("Error reading input file '%s': %s.\n", filename, dmErrorStr(res)); goto out; } // Show information loadAddr = forceAddr ? destAddr : tmpAddr; endAddr = loadAddr + dataSize - 1; dmPrint(1, "* Loading '%s', %s at $%.4x-$%.4x", filename, forceAddr ? "PRGA" : "PRG", loadAddr, endAddr); if (endAddr >= memModel->size) { res = DMERR_BOUNDS; dmPrint(1, " .. Does not fit into the memory!\n"); goto out; } // Load data if (fread(&memory[loadAddr], dataSize, 1, fh) < 1) { res = dmGetErrno(); dmPrint(1, " .. Error: %s.\n", dmErrorStr(res)); goto out; } dmPrint(1, " .. OK\n"); // Add to list of blocks if ((res = dmReserveMemBlock(loadAddr, endAddr, filename, MTYPE_RES)) != DMERR_OK) goto out; out: if (fh != NULL) fclose(fh); return res; } int dmLoadRAW(const char *filename, const int destAddr) { FILE *fh; int dataSize, endAddr, res = DMERR_OK; // Open the input file if ((fh = fopen(filename, "rb")) == NULL) { res = dmGetErrno(); dmErrorMsg("Error opening input file '%s': %s.\n", filename, dmErrorStr(res)); goto out; } // Get filesize if ((dataSize = dmGetFileSize(fh)) < 0) { res = dmError(DMERR_FREAD, "Error getting file size for '%s'.\n", filename); goto out; } // Show information endAddr = destAddr + dataSize - 1; dmPrint(1, "* Loading '%s', RAW at $%.4x-$%.4x", filename, destAddr, endAddr); if (endAddr >= memModel->size) { res = DMERR_BOUNDS; dmPrint(1, " .. Does not fit into the memory!\n"); goto out; } // Load data if (fread(&memory[destAddr], dataSize, 1, fh) < 1) { res = dmGetErrno(); dmPrint(1, " .. Error reading data: %s.\n", dmErrorStr(res)); goto out; } dmPrint(1, " .. OK\n"); // Add info to list if ((res = dmReserveMemBlock(destAddr, endAddr, filename, MTYPE_RES)) != DMERR_OK) goto out; out: if (fh != NULL) fclose(fh); return res; } int outputLinkData(FILE *dfile, const char *blockName, const int blockStart, const int blockEnd) { char *tmpStr, *s, *t; int blockSize; blockSize = (blockEnd - blockStart + 1); // Create label name from filename if ((tmpStr = dm_strdup(blockName)) == NULL) { return dmError(DMERR_MALLOC, "Could not allocate memory for string '%s'!\n", blockName); } if ((t = strrchr(tmpStr, '/'))) s = (t + 1); else if ((t = strrchr(tmpStr, '\\'))) s = (t + 1); else s = tmpStr; if ((t = strrchr(s, '.'))) *t = 0; for (t = s; *t; t++) { if (!isalnum(*t)) *t = '_'; } // Print the label line switch (optLinkFileFormat) { case FMT_PLAIN: fprintf(dfile, "%s = $%.4x\n", tmpStr, blockStart); break; case FMT_DECIMAL: fprintf(dfile, "%s = %d\n", tmpStr, blockStart); break; case FMT_GENERIC: default: fprintf(dfile, "; %s ($%.4x - $%.4x, %d/$%x bytes)\n", blockName, blockStart, blockEnd, blockSize, blockSize); fprintf(dfile, "%s = $%.4x\n", s, blockStart); break; } dmFree(tmpStr); return DMERR_OK; } /* Print out an ASCII presentation of memory map */ #define BOX_WIDTH 60 #define TO_STR1(x) #x #define TO_STR(x) TO_STR1(x) void memPrintLine(FILE *fh) { fputs(" +", fh); for (int i = 0; i < BOX_WIDTH + 2; i++) fputc('-', fh); fputs("+\n", fh); } void memPrintEmpty(FILE *fh, const int n) { for (int i = 0; i < n; i++) { fputs(" | ", fh); for (int i = 0; i < BOX_WIDTH; i++) fputc(' ', fh); fputs(" |\n", fh); } } void memPrintBox(FILE *fh, const int bsize, const int bstart, const int bend, const char *bdesc) { int bksize = bsize / (1024 * 2); if (bksize > 1) memPrintEmpty(fh, bksize); fprintf(fh, "$%.4x - $%.4x | %-" TO_STR(BOX_WIDTH) "s |\n", bstart, bend, bdesc); if (bksize > 1) memPrintEmpty(fh, bksize); memPrintLine(fh); } void dmDescribeMemory(FILE *fh) { DMMemBlock *bprev = NULL; memPrintLine(fh); for (int i = 0; i < nmemBlocks; i++) { DMMemBlock *bcurr = &memBlocks[i]; char bdesc[128]; const char *btype; int bsize; // Check for empty, unreserved areas if (bprev != NULL && (bsize = (bcurr->start - 1) - (bprev->end + 1) + 1) > 1) { snprintf(bdesc, sizeof(bdesc), "EMPTY (%d)", bsize); memPrintBox(fh, bsize, bprev->end + 1, bcurr->start - 1, bdesc); } bprev = bcurr; // Print current block switch (bcurr->type) { case MTYPE_NONE: btype = "N/A (NC)"; break; case MTYPE_ROM: btype = "ROM"; break; case MTYPE_WT: btype = "WT"; break; case MTYPE_IO: btype = "I/O"; break; case MTYPE_RES: btype = "RSVD"; break; default: btype = "????"; break; } bsize = bcurr->end - bcurr->start + 1; snprintf(bdesc, sizeof(bdesc), "%.40s (%s, %d)", bcurr->name, btype, bsize); memPrintBox(fh, bsize, bcurr->start, bcurr->end, bdesc); } } int dmAddOverlap(DMMemBlockOverlap *olp, const int index) { if (olp->noverlaps < SET_MAX_FILENAMES) { olp->overlaps[olp->noverlaps++] = index; return DMERR_OK; } else { return dmError(DMERR_BOUNDS, "Too many memory block overlaps (%d >= %d).\n", olp->noverlaps, SET_MAX_FILENAMES); } } /* * The main program */ int main(int argc, char *argv[]) { FILE *outFile = NULL; bool hasOverlaps; int res = DMERR_OK, startAddr, endAddr, dataSize, totalSize; dmInitProg("objlink", "Simple file-linker", "0.83", NULL, NULL); // Parse arguments if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, NULL, OPTH_BAILOUT)) goto out; if (nsrcFiles < 1) { argShowHelp(); res = dmError(DMERR_INVALID_ARGS, "No input file(s) specified.\n"); goto out; } // Warn about overlaps if enabled if (optAllowOverlap) dmMsg(-1, "WARNING: Overlapping data has been allowed!\n"); // Allocate memory memModel = &memoryModels[optMemModel]; dmMsg(1, "Using memory model #%d '%s', %d bytes.\n", optMemModel, memModel->name, memModel->size); if ((memory = (Uint8 *) dmMalloc(memModel->size + 32)) == NULL) { res = dmError(DMERR_MALLOC, "Could not allocate memory for memory model!\n"); goto out; } // Initialize memory dmMsg(1, "Initializing memory with "); if (optInitValueType == 1 || optInitValue <= 0xff) { dmPrint(1, "BYTE 0x%.2x\n", optInitValue); memset(memory, optInitValue, memModel->size); } else if (optInitValueType == 2 || optInitValue <= 0xffff) { uint16_t *mp = (uint16_t *) memory; dmPrint(1, "WORD 0x%.4x\n", optInitValue); for (int i = memModel->size / sizeof(*mp); i; i--) { *mp++ = optInitValue; } } else { Uint32 *mp = (Uint32 *) memory; dmPrint(1, "DWORD 0x%.8x\n", optInitValue); for (int i = memModel->size / sizeof(*mp); i; i--) { *mp++ = optInitValue; } } // Load the datafiles for (int i = 0; i < nsrcFiles; i++) switch (srcFiles[i].type) { case STYPE_RAW: dmLoadRAW(srcFiles[i].filename, srcFiles[i].addr); break; case STYPE_PRG: dmLoadPRG(srcFiles[i].filename, false, 0); break; case STYPE_PRGA: dmLoadPRG(srcFiles[i].filename, true, srcFiles[i].addr); break; } // Add memory model blocks dmMsg(1, "Applying memory model restrictions...\n"); for (int i = 0; i < memModel->nmemBlocks; i++) { if ((res = dmReserveMemBlock( memModel->memBlocks[i].start, memModel->memBlocks[i].end, memModel->memBlocks[i].name, memModel->memBlocks[i].type)) != DMERR_OK) goto out; } // Sort the blocks qsort(memBlocks, nmemBlocks, sizeof(DMMemBlock), dmCompareMemBlock); // Check for overlapping conflicts hasOverlaps = false; for (int bk1 = 0; bk1 < nmemBlocks; bk1++) for (int bk2 = 0; bk2 < nmemBlocks; bk2++) if (bk1 != bk2) { DMMemBlockOverlap *olp1 = &memBlockOverlaps[bk1], *olp2 = &memBlockOverlaps[bk2]; DMMemBlock *mb1 = &memBlocks[bk1], *mb2 = &memBlocks[bk2]; bool found = false; if (mb1->type != MTYPE_RES) continue; for (int no = 0; no < olp1->noverlaps; no++) { if (olp1->overlaps[no] == bk2) { found = true; break; } } if (found) continue; // Check for per-file conflicts if ((mb2->start >= mb1->start && mb2->start <= mb1->end) || (mb2->end >= mb1->start && mb2->end <= mb1->end)) { dmPrint(-1, "* '%s' and '%s' overlap ($%.4x-$%.4x vs $%.4x-$%.4x)\n", mb1->name, mb2->name, mb1->start, mb1->end, mb2->start, mb2->end); if ((res = dmAddOverlap(olp1, bk2)) != DMERR_OK || (res = dmAddOverlap(olp2, bk1)) != DMERR_OK) goto out; hasOverlaps = true; } } if (!optAllowOverlap && hasOverlaps) { res = dmError(DMERR_BOUNDS, "Error occured, overlaps not allowed.\n"); goto out; } // Find out start and end-addresses startAddr = memModel->size; totalSize = endAddr = 0; for (int i = 0; i < nmemBlocks; i++) { DMMemBlock *mbi = &memBlocks[i]; if (mbi->type == MTYPE_RES) { if (mbi->start < startAddr) startAddr = mbi->start; if (mbi->end > endAddr) endAddr = mbi->end; totalSize += (mbi->end - mbi->start + 1); } } if (startAddr >= memModel->size || endAddr < startAddr) { res = dmError(DMERR_BOUNDS, "Invalid saveblock addresses (start=$%.4x, end=$%.4x)!\n", startAddr, endAddr); goto out; } // Output linkfile if (optLinkFileName) { dmMsg(1, "Writing linkfile to '%s'\n", optLinkFileName); if ((outFile = fopen(optLinkFileName, "wb")) == NULL) { res = dmGetErrno(); dmErrorMsg("Error creating file '%s': %s.\n", optLinkFileName, dmErrorStr(res)); goto out; } switch (optLinkFileFormat) { case FMT_GENERIC: default: fprintf(outFile, "; Definitions generated by %s v%s\n", dmProgName, dmProgVersion); break; } for (int i = 0; i < nmemBlocks; i++) { DMMemBlock *mbi = &memBlocks[i]; outputLinkData(outFile, mbi->name, mbi->start, mbi->end); } fclose(outFile); } // Show some information if (optCropOutput) { startAddr = optCropStart; endAddr = optCropEnd; } dataSize = endAddr - startAddr + 1; if (dataSize - totalSize > 0) { dmMsg(1, "Total of %d/$%x bytes unused(?) areas.\n", dataSize - totalSize, dataSize - totalSize); } dmMsg(1, "Writing $%.4x - $%.4x (%d/$%x bytes) ", startAddr, endAddr, dataSize, dataSize); // Open the destination file if (optDestName == NULL) { outFile = stdout; dmPrint(1, "...\n"); } else if ((outFile = fopen(optDestName, "wb")) == NULL) { res = dmGetErrno(); dmErrorMsg("Error creating output file '%s': %s.\n", optDestName, dmErrorStr(res)); goto out; } else dmPrint(1, "to '%s'\n", optDestName); // Save loading address if (optLoadAddress >= 0) { dmMsg(1, "Using specified loading address $%.4x\n", optLoadAddress); dm_fwrite_le16(outFile, optLoadAddress); } else if (optLoadAddress == LA_AUTO) { dmMsg(1, "Using automatic loading address $%.4x\n", startAddr); dm_fwrite_le16(outFile, startAddr); } else { dmMsg(1, "Writing raw output, without loading address.\n"); } // Save the data if (fwrite(&memory[startAddr], dataSize, 1, outFile) < 1) { res = dmGetErrno(); dmErrorMsg( "Error writing to file: %s.\n", dmErrorStr(res)); goto out; } // Describe the memory layout if (optDescribe) dmDescribeMemory(outFile != stdout ? stdout : stderr); out: if (outFile != NULL && outFile != stdout) fclose(outFile); return res; }