Mercurial > hg > dmlib
view tools/objlink.c @ 2408:60e119262c67
Option index re-ordering cleanup work.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 13 Jan 2020 21:21:25 +0200 |
parents | b7cd5dd0b82e |
children | bc05bcfc4598 |
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-2018 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 { 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, // Hard ROM MTYPE_ROM_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, { { 0, 0, 0, NULL, 0 } }}, { "C64 normal (IO+Basic+Kernal)", "$01 = $37", (64*1024), 3, { { 0xA000, 0xBFFF, MTYPE_ROM_WT, "Basic ROM", PLACE_STATIC }, { 0xD000, 0xDFFF, MTYPE_IO, "I/O", PLACE_STATIC }, { 0xE000, 0xFFFF, MTYPE_ROM_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_ROM_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_ROM_WT, "Basic ROM", PLACE_STATIC }, { 0xD000, 0xDFFF, MTYPE_ROM, "Char ROM", PLACE_STATIC }, { 0xE000, 0xFFFF, MTYPE_ROM_WT, "Kernal ROM", PLACE_STATIC }, }}, /* { "C64 normal", "$01 = $37", (64*1024), 0, { { 0x0000, 0x0000, MTYPE_RAM, "" }, }}, */ }; 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]; char *optLinkFileName = NULL; int optLinkFileFormat = FMT_GENERIC; BOOL optDescribe = FALSE, optAllowOverlap = FALSE; Uint32 optInitValue = 0; int optInitValueType = 1; unsigned int optCropStart, optCropEnd; BOOL optCropOutput = FALSE; int optLoadAddress = LA_AUTO; int optMemModel = 0; const DMMemModel *memModel = NULL; Uint8 *memory = NULL; char *optDestName = NULL; /* Arguments */ static const DMOptArg optList[] = { { 0, '?', "help", "Show this help", OPT_NONE }, { 1, 'r', "input-raw", "RAW input: -r <file>:<addr>", OPT_ARGREQ }, { 2, 'p', "input-prg", "PRG input: -p <file>[:<addr>]", OPT_ARGREQ }, { 12, 's', "section", "Reserved section: -s <start>-<end>[,name] or <start>:<len>[,name]", OPT_ARGREQ }, { 5, 'o', "output", "Specify output file, -o <file>", OPT_ARGREQ }, { 6, 'O', "overlap", "Allow overlapping memory areas", OPT_NONE }, { 7, 'm', "model", "Set memory model", OPT_ARGREQ }, { 8, 'l', "link-file", "Output addresses and labels into file", OPT_ARGREQ }, { 9, 'f', "format", "Format of link-file: (g)eneric, (p)lain, (d)ecimal", OPT_ARGREQ }, { 10, 'i', "initvalue", "Initialize memory with: -i <byte/word/dword>:[bwd]", OPT_ARGREQ }, { 11, 'd', "describe", "Output ASCII memory map description", OPT_NONE }, { 13, 'c', "crop", "Crop output file to: -c <start>-<end> or <start>:<len>", OPT_ARGREQ }, { 14, '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); printf( "\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]; printf(" %d = %-40s [%s] (%d kB)\n", i, m->name, m->desc, m->size / 1024); } } off_t dmGetFileSize(FILE *f) { off_t len, pos = ftell(f); fseeko(f, 0, SEEK_END); len = ftell(f); fseek(f, pos, SEEK_SET); return len; } /* Memory block handling */ BOOL dmReserveMemBlock(int startAddr, int endAddr, const char *blockName, int blockType) { if (startAddr > endAddr) { dmErrorMsg("ERROR! Block '%s' has startAddr=$%.4x > endAddr=$%.4x!\n", blockName, startAddr, endAddr); return FALSE; } 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 TRUE; } else { dmErrorMsg("Maximum number of memBlock definitions (%d) exceeded!\n", SET_MAX_FILENAMES); return FALSE; } } 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) { char *p; unsigned int tmpi; switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: // Add RAW if (!dmParseInputFile(optArg, STYPE_RAW, STYPE_RAW, "RAW", TRUE)) return FALSE; break; case 2: // Add PRG if (!dmParseInputFile(optArg, STYPE_PRGA, STYPE_PRG, "PRG", FALSE)) return FALSE; break; case 5: // Set output file name optDestName = optArg; break; case 6: // Allow overlapping segments optAllowOverlap = TRUE; dmErrorMsg("Warning, allowing overlapping data.\n"); break; case 7: // Set memory model optMemModel = atoi(optArg); if (optMemModel < 0 || optMemModel >= nmemoryModels) { dmErrorMsg("Invalid memory model number %i!\n", optMemModel); return FALSE; } break; case 8: // Linker file optLinkFileName = optArg; break; case 9: // 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 10: // Initialization value 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 11: // Set describe mode optDescribe = TRUE; break; case 12: { 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)) return FALSE; } break; case 13: { 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 14: // Set loading address if (strcasecmp(optArg, "none") == 0) optLoadAddress = LA_NONE; else { 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' #%d: %s.\n", filename, res, dmErrorStr(res)); goto out; } // Get filesize if ((dataSize = dmGetFileSize(fh) - 2) < 0) { res = dmGetErrno(); dmErrorMsg("Error getting file size for '%s' #%d: %s.\n", filename, res, dmErrorStr(res)); goto out; } // Get loading address if (!dm_fread_le16(fh, &tmpAddr)) { res = dmGetErrno(); dmErrorMsg("Error reading input file '%s' #%d: %s.\n", filename, res, 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 #%d: %s.\n", res, dmErrorStr(res)); goto out; } dmPrint(1, " .. OK\n"); // Add to list of blocks if (!dmReserveMemBlock(loadAddr, endAddr, filename, MTYPE_RES)) res = DMERR_MALLOC; 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' #%d: %s.\n", filename, res, 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 #%d: %s.\n", res, dmErrorStr(res)); goto out; } dmPrint(1, " .. OK\n"); // Add info to list if (!dmReserveMemBlock(destAddr, endAddr, filename, MTYPE_RES)) res = DMERR_MALLOC; 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 tmpStr = dm_strdup(blockName); if (tmpStr == NULL) { dmErrorMsg("Could not allocate memory for string '%s'!\n", blockName); return -1; } 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 0; } /* Print out an ASCII presentation of memory map */ void memPrintLine(FILE *f) { fprintf(f, " +------------------------------------------+\n"); } void memPrintEmpty(FILE *f, int n) { for (int i = 0; i < n; i++) fprintf(f, " | |\n"); } void dmDescribeMemory(FILE *f) { DMMemBlock *prev = NULL; memPrintLine(f); for (int i = 0; i < nmemBlocks; i++) { DMMemBlock *curr = &memBlocks[i]; char desc[512], *s; int siz, kz; // Check for empty, unreserved areas if (prev != NULL) siz = (curr->start - 1) - (prev->end + 1) + 1; else siz = 0; if (prev != NULL && siz > 1) { kz = siz / (1024 * 2); if (kz > 1) memPrintEmpty(f, kz); snprintf(desc, sizeof(desc), "EMPTY (%d)", siz); fprintf(f, "$%.4x - $%.4x | %-40s |\n", prev->end + 1, curr->start - 1, desc); if (kz > 1) memPrintEmpty(f, kz); memPrintLine(f); } prev = curr; // Print current block switch (curr->type) { case MTYPE_NONE: s = "N/A (NC)"; break; case MTYPE_ROM: s = "ROM"; break; case MTYPE_ROM_WT: s = "ROM/WT"; break; case MTYPE_IO: s = "I/O"; break; case MTYPE_RES: s = "RSVD"; break; default: s = "????"; break; } siz = curr->end - curr->start + 1; kz = siz / (1024 * 2); if (kz > 1) memPrintEmpty(f, kz); snprintf(desc, sizeof(desc), "%s (%s, %d)", curr->name, s, siz); fprintf(f, "$%.4x - $%.4x | %-40s |\n", curr->start, curr->end, desc); if (kz > 1) memPrintEmpty(f, kz); memPrintLine(f); } fprintf(f, "\n" "NC = Not Connected\n" "RSVD = Reserved\n" "ROM/WT = RAM under 'write-through' ROM\n" "\n" ); } /* * The main program */ int main(int argc, char *argv[]) { FILE *dfile = NULL; BOOL hasOverlaps; int startAddr, endAddr, dataSize, totalSize; dmInitProg("objlink", "Simple file-linker", "0.82", NULL, NULL); dmVerbosity = 1; // Parse arguments if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, NULL, OPTH_BAILOUT)) goto out; if (nsrcFiles < 1) { dmErrorMsg("Nothing to do. (try --help)\n"); goto out; } // 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) { dmErrorMsg("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); dmMemset(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 (!dmReserveMemBlock( memModel->memBlocks[i].start, memModel->memBlocks[i].end, memModel->memBlocks[i].name, memModel->memBlocks[i].type)) goto out; } // Sort the blocks qsort(memBlocks, nmemBlocks, sizeof(DMMemBlock), dmCompareMemBlock); // Check for overlapping conflicts hasOverlaps = FALSE; for (int i = 0; i < nmemBlocks; i++) for (int j = 0; j < nmemBlocks; j++) if (j != i && memBlocks[i].type == MTYPE_RES) { DMMemBlock *mbi = &memBlocks[i], *mbj = &memBlocks[j]; // Check for per-file conflicts if ((mbj->start >= mbi->start && mbj->start <= mbi->end) || (mbj->end >= mbi->start && mbj->end <= mbi->end)) { dmPrint(1, "* '%s' and '%s' overlap ($%.4x-$%.4x vs $%.4x-$%.4x)\n", mbi->name, mbj->name, mbi->start, mbi->end, mbj->start, mbj->end); hasOverlaps = TRUE; } } if (!optAllowOverlap && hasOverlaps) { dmErrorMsg("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) { dmErrorMsg("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 ((dfile = fopen(optLinkFileName, "wb")) == NULL) { int err = dmGetErrno(); dmErrorMsg("Error creating file '%s' #%d: %s.\n", optLinkFileName, err, dmErrorStr(err)); goto out; } switch (optLinkFileFormat) { case FMT_GENERIC: default: fprintf(dfile, "; Definitions generated by %s v%s\n", dmProgName, dmProgVersion); break; } for (int i = 0; i < nmemBlocks; i++) { DMMemBlock *mbi = &memBlocks[i]; outputLinkData(dfile, mbi->name, mbi->start, mbi->end); } fclose(dfile); } // 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) { dfile = stdout; dmPrint(1, "...\n"); } else if ((dfile = fopen(optDestName, "wb")) == NULL) { int err = dmGetErrno(); dmErrorMsg("Error creating output file '%s' #%d: %s.\n", optDestName, err, dmErrorStr(err)); 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(dfile, optLoadAddress); } else if (optLoadAddress == LA_AUTO) { dmMsg(1, "Using automatic loading address $%.4x\n", startAddr); dm_fwrite_le16(dfile, startAddr); } else { dmMsg(1, "Writing raw output, without loading address.\n"); } // Save the data if (fwrite(&memory[startAddr], dataSize, 1, dfile) < 1) { int err = dmGetErrno(); dmErrorMsg("Error writing to file #%d: %s.\n", err, dmErrorStr(err)); goto out; } // Describe the memory layout if (optDescribe) dmDescribeMemory(dfile != stdout ? stdout : stderr); out: if (dfile != NULL && dfile != stdout) fclose(dfile); return 0; }