Mercurial > hg > dmlib
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/objlink.c Sat Nov 03 02:19:51 2012 +0200 @@ -0,0 +1,899 @@ +/* + * 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; +}