Mercurial > hg > dmlib
view objlink.c @ 407:59244a7ae37f
Move c64 utilities to the engine lib, as we benefit from a common framework.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sat, 03 Nov 2012 02:19:51 +0200 |
parents | |
children | 81b92a0e754b |
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-2012 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #include <errno.h> #include "dmlib.h" #include "dmargs.h" #include "dmfile.h" #include "dmmutex.h" #define MAX_FILENAMES (128) #define MAX_MEMBLOCKS (128) /* Typedefs */ typedef struct { ssize_t start, end; // Start and end address int type; // Type char *name; // Name of the block ssize_t size; int placement; } DMMemBlock; typedef struct { char *name; // Description of memory model char *desc; ssize_t size; // Total addressable memory size ssize_t nmemBlocks; // Defined memory areas DMMemBlock memBlocks[MAX_MEMBLOCKS]; } DMMemModel; typedef struct { char *filename; int type; int placement; ssize_t 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 }; /* Memory models */ const DMMemModel memoryModels[] = { { "C64 unrestricted", "$01 = $34", (64*1024), 0, { { 0, 0, 0, NULL, 0, 0 } }}, { "C64 normal (IO+Basic+Kernal)", "$01 = $37", (64*1024), 3, { { 0xA000, 0xBFFF, MTYPE_ROM_WT, "Basic ROM", 0, PLACE_STATIC }, { 0xD000, 0xDFFF, MTYPE_IO, "I/O", 0, PLACE_STATIC }, { 0xE000, 0xFFFF, MTYPE_ROM_WT, "Kernal ROM", 0, PLACE_STATIC }, }}, { "C64 modified (IO+Kernal)", "$01 = $36", (64*1024), 2, { { 0xD000, 0xDFFF, MTYPE_IO, "I/O", 0, PLACE_STATIC }, { 0xE000, 0xFFFF, MTYPE_ROM_WT, "Kernal ROM", 0, PLACE_STATIC }, }}, { "C64 modified (IO only)", "$01 = $35", (64*1024), 1, { { 0xD000, 0xDFFF, MTYPE_IO, "I/O", 0, PLACE_STATIC }, }}, { "C64 modified (Char+Kernal+Basic)", "$01 = $33", (64*1024), 3, { { 0xA000, 0xBFFF, MTYPE_ROM_WT, "Basic ROM", 0, PLACE_STATIC }, { 0xD000, 0xDFFF, MTYPE_ROM, "Char ROM", 0, PLACE_STATIC }, { 0xE000, 0xFFFF, MTYPE_ROM_WT, "Kernal ROM", 0, 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[MAX_FILENAMES]; // Source file names int nmemBlocks = 0; DMMemBlock memBlocks[MAX_FILENAMES]; char *optLinkFileName = NULL; int optLinkFileFormat = FMT_GENERIC; BOOL optDescribe = FALSE, optAllowOverlap = FALSE; Uint32 optInitValue = 0; int optInitValueType = 1; int optMemModel = 0; const DMMemModel *memModel = NULL; Uint8 *memory = NULL; char *optDestName = NULL; /* Arguments */ static 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> or <start>:<len>", 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 }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp() { int i; dmPrintBanner(stdout, dmProgName, "[options]"); dmArgsPrintHelp(stdout, optList, optListN); 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 (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 */ void reserveMemBlock(ssize_t startAddr, ssize_t endAddr, const char *blockName, int blockType) { if (startAddr > endAddr) { dmError("ERROR! Block '%s' has startAddr=$%.4x > endAddr=$%.4x!\n", blockName, startAddr, endAddr); exit(4); } if (nmemBlocks < MAX_FILENAMES) { memBlocks[nmemBlocks].start = startAddr; memBlocks[nmemBlocks].end = endAddr; memBlocks[nmemBlocks].size = (endAddr - startAddr + 1); memBlocks[nmemBlocks].name = dm_strdup(blockName); memBlocks[nmemBlocks].type = blockType; nmemBlocks++; } else { dmError("Maximum number of memBlock definitions (%d) exceeded!\n", MAX_FILENAMES); exit(4); } } int compareMemBlock(const void *cva, const void *cvb) { const DMMemBlock *a = cva, *b = cvb; return a->start - b->start; } BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { char *p, *s; ssize_t tmpi; switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: // Add RAW if ((p = strrchr(optArg, ':')) != NULL) { *p = 0; if (!dmGetIntVal(p + 1, &tmpi)) { dmError("Invalid RAW address '%s' specified for '%s'.\n", p + 1, optArg); return FALSE; } } else { dmError("No RAW loading address specified for '%s'.\n", optArg); return FALSE; } srcFiles[nsrcFiles].filename = optArg; srcFiles[nsrcFiles].type = STYPE_RAW; srcFiles[nsrcFiles].addr = tmpi; nsrcFiles++; break; case 2: // Add PRG if ((p = strrchr(optArg, ':')) != NULL) { *p = 0; if (!dmGetIntVal(p + 1, &tmpi)) { dmError("Invalid PRG address '%s' specified for '%s'.\n", p + 1, optArg); return FALSE; } srcFiles[nsrcFiles].addr = tmpi; srcFiles[nsrcFiles].type = STYPE_PRGA; } else { srcFiles[nsrcFiles].type = STYPE_PRG; } srcFiles[nsrcFiles].filename = optArg; nsrcFiles++; break; case 5: // Set output file name optDestName = optArg; break; case 6: // Allow overlapping segments optAllowOverlap = TRUE; dmError("Warning, allowing overlapping data.\n"); break; case 7: // Set memory model optMemModel = atoi(optArg); if (optMemModel < 0 || optMemModel >= nmemoryModels) { dmError("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: dmError("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: dmError("Invalid init value type '%c' specified for '%s'.\n", p[1], optArg); return FALSE; } } if (!dmGetIntVal(optArg, &tmpi)) { dmError("Invalid initvalue '%s'.\n", optArg); return FALSE; } optInitValue = tmpi; break; case 11: // Set describe mode optDescribe = TRUE; break; case 12: { ssize_t sectStart, sectEnd, sectLen; char sectMode; // Define reserved section // Create a copy of the argument if ((s = dm_strdup(optArg)) == NULL) { dmError("Could not allocate temporary string!\n"); exit(128); } // Get start address if ((p = strchr(s, '-')) == NULL && (p = strchr(s, ':')) == NULL) { dmFree(s); dmError("Section definition '%s' invalid.\n", optArg); return FALSE; } sectMode = *p; *p = 0; // Get value if (!dmGetIntVal(s, §Start)) { dmError("Section start address '%s' in '%s' invalid.\n", s, optArg); dmFree(s); return FALSE; } // Get end address or length if (!dmGetIntVal(p + 1, &tmpi)) { dmError("Section %s '%s' in '%s' invalid.\n", sectMode == '-' ? "end address" : "length", p, optArg); dmFree(s); return FALSE; } dmFree(s); if (sectMode == ':') { sectEnd = sectStart + tmpi; sectLen = tmpi; } else { if (tmpi < sectStart) { dmError("Section start address > end address in '%s'.\n", optArg); return FALSE; } sectLen = tmpi - sectStart + 1; sectEnd = tmpi; } // Allocate memory block dmMsg(1, "Reserve $%.4x - $%.4x ($%x, %d bytes) as 'Clear'\n", sectStart, sectEnd, sectLen, sectLen); reserveMemBlock(sectStart, sectEnd, "Clear", MTYPE_RES); } break; default: dmError("Unknown argument '%s'.\n", currArg); return FALSE; } return TRUE; } int dmLoadPRG(const char *fname, BOOL forceAddr, int destAddr) { FILE *f; ssize_t a, b, s; // Open the input file if ((f = fopen(fname, "rb")) == NULL) { dmError("Error opening input file '%s' (%s).\n", fname, strerror(errno)); return 1; } // Get filesize if ((s = dmGetFileSize(f) - 2) < 0) { dmError("Error getting file size for '%s'.\n", fname); return 6; } // Get loading address if ((a = fgetc(f)) < 0) { dmError("Error reading input file '%s' (%s).\n", fname, strerror(errno)); return 2; } if ((b = fgetc(f)) < 0) { dmError("Error reading input file '%s' (%s).\n", fname, strerror(errno)); return 3; } // Show information if (forceAddr) a = destAddr; else a = a + (b * 0x100); dmPrint(1, "* Loading '%s', %s at $%.4x-$%.4x", fname, forceAddr ? "PRGA" : "PRG", a, (a + s - 1)); if (a + s >= memModel->size) { dmPrint(1, " .. Does not fit into the memory!\n"); return 5; } // Load data if (fread(&memory[a], s, 1, f) < 1) { dmPrint(1, " .. Error: %s.\n", strerror(errno)); return 4; } dmPrint(1, " .. OK\n"); // Add info to list reserveMemBlock(a, (a + s + 1), fname, MTYPE_RES); return 0; } int dmLoadRAW(const char *fname, int destAddr) { FILE *f; ssize_t s; // Open the input file if ((f = fopen(fname, "rb")) == NULL) { dmError("Error opening input file '%s' (%s).\n", fname, strerror(errno)); return 1; } // Get filesize s = dmGetFileSize(f); if (s < 0) { dmError("Error getting file size for '%s'.\n", fname); return 6; } // Show information dmPrint(1, "* Loading '%s', RAW at $%.4x-$%.4x", fname, destAddr, (destAddr + s - 1)); if (destAddr + s >= memModel->size) { dmPrint(1, " .. Does not fit into the memory!\n"); return 5; } // Load data if (fread(&memory[destAddr], s, 1, f) < 1) { dmPrint(1, " .. Error: %s.\n", strerror(errno)); return 4; } dmPrint(1, " .. OK\n"); // Add info to list reserveMemBlock(destAddr, (destAddr + s + 1), fname, MTYPE_RES); return 0; } 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) { dmError("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, ssize_t n) { ssize_t i; for (i = 0; i < n; i++) fprintf(f, " | |\n"); } void dmDescribeMemory(FILE *f) { int i; DMMemBlock *prev = NULL; memPrintLine(f); for (i = 0; i < nmemBlocks; i++) { DMMemBlock *curr = &memBlocks[i]; char desc[512], *s; ssize_t siz, kz; // Check for empty, unreserved areas if (prev != NULL && prev->start - 1 > curr->end + 1) { siz = (prev->start - 1) - (curr->end + 1); kz = siz / (1024 * 2); if (kz > 1) memPrintEmpty(f, kz); snprintf(desc, sizeof(desc), "EMPTY (%d)", siz); fprintf(f, "$%.4x - $%.4x | %-40s |\n", curr->end + 1, prev->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 i, j; ssize_t startAddr, endAddr, dataSize, totalSize; dmInitProg("objlink", "Simple file-linker", "0.80", NULL, NULL); dmVerbosity = 1; // Parse arguments if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, NULL, TRUE)) exit(1); if (nsrcFiles < 1) { dmError("Nothing to do. (try --help)\n"); exit(0); } // Allocate memory memModel = &memoryModels[optMemModel]; dmMsg(1, "Using memory model #%d '%s', %d bytes.\n", optMemModel, memModel->name, memModel->size); memory = (Uint8 *) dmMalloc(memModel->size + 32); if (memory == NULL) { dmError("Could not allocate memory.\n"); exit(2); } // 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 (i = memModel->size / sizeof(*mp); i; i--) { *mp++ = optInitValue; } } else { Uint32 *mp = (Uint32 *) memory; dmPrint(1, "DWORD 0x%.8x\n", optInitValue); for (i = memModel->size / sizeof(*mp); i; i--) { *mp++ = optInitValue; } } // Load the datafiles for (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 (i = 0; i < memModel->nmemBlocks; i++) { reserveMemBlock( memModel->memBlocks[i].start, memModel->memBlocks[i].end, memModel->memBlocks[i].name, memModel->memBlocks[i].type); } // Sort the blocks qsort(memBlocks, nmemBlocks, sizeof(DMMemBlock), compareMemBlock); // Check for overlapping conflicts hasOverlaps = FALSE; for (i = 0; i < nmemBlocks; i++) for (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) { dmError("Error occured, overlaps not allowed.\n"); exit(5); } // Find out start and end-addresses startAddr = memModel->size; totalSize = endAddr = 0; for (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) { dmError("Invalid saveblock addresses (start=$%.4x, end=$%.4x)!\n", startAddr, endAddr); exit(8); } // Output linkfile if (optLinkFileName) { dmMsg(1, "Writing linkfile to '%s'\n", optLinkFileName); if ((dfile = fopen(optLinkFileName, "wb")) == NULL) { dmError("Error creating file '%s' (%s).\n", optLinkFileName, strerror(errno)); exit(1); } switch (optLinkFileFormat) { case FMT_GENERIC: default: fprintf(dfile, "; Definitions generated by %s v%s\n", dmProgName, dmProgVersion); break; } for (i = 0; i < nmemBlocks; i++) { DMMemBlock *mbi = &memBlocks[i]; outputLinkData(dfile, mbi->name, mbi->start, mbi->end); } fclose(dfile); } // Show some information dataSize = endAddr - startAddr + 1; 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) { dmError("Error creating output file '%s' (%s).\n", optDestName, strerror(errno)); exit(1); } else dmPrint(1, "to '%s'\n", optDestName); // Save loading address dm_fwrite_le16(dfile, startAddr); // Save the data if (fwrite(&memory[startAddr], dataSize, 1, dfile) < 1) { dmError("Error writing to file (%s)\n", strerror(errno)); } fclose(dfile); // Describe if (optDescribe) dmDescribeMemory(stdout); exit(0); return 0; }