/* casm - Simple, portable assembler Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.co.uk) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ------------------------------------------------------------------------- Spectrum TAP file output handler. */ #include #include #include #include "global.h" #include "specout.h" #include "expr.h" /* ---------------------------------------- PRIVATE TYPES */ enum option_t { OPT_LOADER, OPT_START_ADDR, }; static const ValueTable option_set[] = { {"spectrum-loader", OPT_LOADER}, {"spectrum-start", OPT_START_ADDR}, {NULL} }; typedef struct { int loader; int start_addr; } Options; /* ---------------------------------------- PRIVATE DATA */ /* Types for AddLine() */ #define TYPE_END 0 #define TYPE_TOKEN 1 #define TYPE_STRING 2 /* Constants for some tokens */ #define TOK_VAL 176 #define TOK_QUOTE 34 #define TOK_CODE 175 #define TOK_USR 192 #define TOK_LOAD 239 #define TOK_RAND 249 #define TOK_CLEAR 253 static Options options = { FALSE, -1 }; /* Buffers used for BASIC loader */ static unsigned char basic[0x10000]; static unsigned endptr = 0; static unsigned line_no = 10; /* ---------------------------------------- PRIVATE FUNCTIONS */ static void Poke(unsigned char b, int *len) { if (len) { (*len)++; } basic[endptr++]=b; } static void AddBASICLine(int type, ...) { va_list va; int len; unsigned char *len_ptr; Poke(line_no>>8, NULL); Poke(line_no&0xff, NULL); line_no += 10; len_ptr = basic + endptr; endptr += 2; len = 0; va_start(va,type); while(type!=TYPE_END) { char *str; switch(type) { case TYPE_TOKEN: Poke(va_arg(va,int), &len); break; case TYPE_STRING: str = va_arg(va,char *); Poke(TOK_QUOTE, &len); while(*str) { Poke(*str++, &len); } Poke(TOK_QUOTE, &len); break; default: break; } type = va_arg(va,int); } Poke(0x0d, &len); *len_ptr++ = len&0xff; *len_ptr = len>>8; } static Byte TapByte(FILE *fp, Byte b, Byte chk) { chk ^= b; putc(b, fp); return chk; } static Byte TapWord(FILE *fp, int w, Byte chk) { chk = TapByte(fp, w & 0xff, chk); chk = TapByte(fp, (w & 0xff00) >> 8, chk); return chk; } static Byte TapString(FILE *fp, const char *p, int len, Byte chk) { while(len--) { chk = TapByte(fp, *p ? *p++ : ' ', chk); } return chk; } static unsigned char TapStream(FILE *fp, const unsigned char *p, unsigned addr, unsigned len, unsigned char chk) { while(len--) { chk = TapByte(fp, p[addr], chk); addr = (addr + 1) & 0xffff; } return chk; } /* ---------------------------------------- INTERFACES */ const ValueTable *SpecTAPOutputOptions(void) { return option_set; } CommandStatus SpecTAPOutputSetOption(int opt, int argc, char *argv[], int quoted[], char *err, size_t errsize) { CommandStatus stat = CMD_OK; CMD_ARGC_CHECK(1); switch(opt) { case OPT_LOADER: options.loader = ParseTrueFalse(argv[0], FALSE); break; case OPT_START_ADDR: CMD_EXPR(argv[0], options.start_addr); break; default: break; } return stat; } int SpecTAPOutput(const char *filename, const char *filename_bank, MemoryBank **bank, int count, char *error, size_t error_size) { FILE *fp = fopen(filename, "wb"); int f; if (!fp) { snprintf(error, error_size, "Failed to create %s", filename); return FALSE; } /* First get the initial address is none defined */ if (options.loader && options.start_addr == -1) { options.start_addr = bank[0]->min_address_used; } /* Output the BASIC loader if set */ if (options.loader) { char no[64]; Byte chk = 0; snprintf(no, sizeof no, "%u", options.start_addr); /* CLEAR VAL "start_addr" */ AddBASICLine(TYPE_TOKEN, TOK_CLEAR, TYPE_TOKEN, TOK_VAL, TYPE_STRING, no, TYPE_END); /* LOAD "" CODE */ for(f = 0; f < count; f++) { AddBASICLine(TYPE_TOKEN, TOK_LOAD, TYPE_TOKEN, TOK_QUOTE, TYPE_TOKEN, TOK_QUOTE, TYPE_TOKEN, TOK_CODE, TYPE_END); } /* RANDOMIZE USR VAL "start_addr" */ AddBASICLine(TYPE_TOKEN, TOK_RAND, TYPE_TOKEN, TOK_USR, TYPE_TOKEN, TOK_VAL, TYPE_STRING, no, TYPE_END); TapWord(fp, 19, 0); chk = TapByte(fp, 0, chk); chk = TapByte(fp, 0, chk); chk = TapString(fp, "LOADER.BAS", 10, chk); chk = TapWord(fp, endptr, chk); chk = TapWord(fp, 10, chk); chk = TapWord(fp, endptr, chk); TapByte(fp, chk, 0); TapWord(fp, endptr + 2, 0); chk = 0; chk = TapByte(fp, 0xff, chk); chk = TapStream(fp, basic, 0, endptr, chk); TapByte(fp, chk, 0); } /* Output the binary files */ for(f = 0; f < count; f++) { Byte chk = 0; const Byte *mem; int min, max, len; mem = bank[f]->memory; min = bank[f]->min_address_used; max = bank[f]->max_address_used; len = max - min + 1; TapWord(fp, 19, 0); chk = TapByte(fp, 0, chk); chk = TapByte(fp, 3, chk); if (count == 1) { chk = TapString(fp, filename, 10, chk); } else { char fn[16]; snprintf(fn, sizeof fn, filename_bank, bank[f]->number); chk = TapString(fp, fn, 10, chk); } chk = TapWord(fp, len, chk); chk = TapWord(fp, min, chk); chk = TapWord(fp, 32768, chk); TapByte(fp, chk, 0); /* Output file data */ TapWord(fp, len + 2, 0); chk = 0; chk = TapByte(fp, 0xff, chk); while(min <= max) { chk = TapByte(fp, mem[min], chk); min++; } TapByte(fp, chk, 0); } fclose(fp); return TRUE; } /* vim: ai sw=4 ts=8 expandtab */