Mercurial > hg > dmlib
view tools/objlink.c @ 2208:90ec1ec89c56
Revamp the palette handling in lib64gfx somewhat, add helper functions to
lib64util for handling external palette file options and add support for
specifying one of the "internal" palettes or external (.act) palette file to
gfxconv and 64vw.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 14 Jun 2019 05:01:12 +0300 |
parents | e3f0eaf23f4f |
children | 36edd316184a |
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); 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 ($%x, %d 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); 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; }