/*
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 .
-------------------------------------------------------------------------
Amstrad CPC tape output handler.
*/
#include
#include
#include
#include "global.h"
#include "codepage.h"
#include "cpcout.h"
#include "expr.h"
/* ---------------------------------------- MACROS & TYPES
*/
#define BLOCK_SIZE 2048
#define LO_BYTE(w) ((w) & 0xff)
#define HI_BYTE(w) (((w) & 0xff00)>>8)
enum option_t
{
OPT_START_ADDR,
OPT_LOADER
};
static const ValueTable option_set[] =
{
{"cpc-start", OPT_START_ADDR},
{"cpc-loader", OPT_LOADER},
{NULL}
};
typedef struct
{
int start_addr;
int loader;
} Options;
static Options options = {-1, 0};
typedef struct
{
Byte *stream;
size_t length;
} Stream;
/* ---------------------------------------- PRIVATE FUNCTIONS
*/
static void InitStream(Stream *s)
{
s->stream = NULL;
s->length = 0;
}
static void AddStreamByte(Stream *s, Byte b)
{
s->length++;
s->stream = Realloc(s->stream, s->length);
s->stream[s->length - 1] = b;
}
static void AddStreamMem(Stream *s, Byte *mem, size_t len)
{
while(len--)
{
AddStreamByte(s, *mem++);
}
}
static void AddStreamWord(Stream *s, int w)
{
AddStreamByte(s, LO_BYTE(w));
AddStreamByte(s, HI_BYTE(w));
}
static void FreeStream(Stream *s)
{
if (s->stream)
{
free(s->stream);
}
InitStream(s);
}
static Word CRC(const Byte *b, int size)
{
Word crc = 0xffff;
int f;
for(f = 0; f < 256; f++)
{
Word w;
int n;
if (f < size)
{
w = b[f];
}
else
{
w = 0;
}
crc ^= w << 8;
for(n = 0; n < 8; n++)
{
if (crc & 0x8000)
{
crc = ((crc << 1) ^ 0x1021) & 0xffff;
}
else
{
crc = (crc << 1) & 0xffff;
}
}
}
crc ^= 0xffff;
return crc;
}
static void WriteByte(FILE *fp, Byte b)
{
putc(b, fp);
}
static void WriteWord(FILE *fp, int w)
{
WriteByte(fp, LO_BYTE(w));
WriteByte(fp, HI_BYTE(w));
}
static void WriteDWord(FILE *fp, unsigned long w)
{
WriteWord(fp, w & 0xffff);
WriteWord(fp, (w & 0xffff0000) >> 16);
}
static void WriteMem(FILE *fp, const Byte *mem, int len)
{
fwrite(mem, 1, len, fp);
}
static void WriteWordMem(Byte *mem, int offset, int w)
{
mem[offset] = LO_BYTE(w);
mem[offset+1] = HI_BYTE(w);
}
static void Write3Word(FILE *fp, int w)
{
WriteByte(fp, LO_BYTE(w));
WriteByte(fp, HI_BYTE(w));
WriteByte(fp, (w & 0xff0000) >> 16);
}
static void WriteString(FILE *fp, const char *p, int len,
Byte fill, Codepage cp)
{
while(len--)
{
WriteByte(fp, *p ? CodeFromNative(cp, *p++) : CodeFromNative(cp, fill));
}
}
static void WriteStringMem(Byte *mem, int offset, const char *p, int len,
Byte fill, Codepage cp)
{
while(len--)
{
mem[offset++] =
*p ? CodeFromNative(cp, *p++) : CodeFromNative(cp, fill);
}
}
static void OutputTZXTurboBlock(FILE *fp, Stream *s)
{
WriteByte(fp, 0x11); /* Block type - Turbo block */
WriteWord(fp, 0x0b21); /* PILOT pulse len */
WriteWord(fp, 0x05ad); /* SYNC 1 len */
WriteWord(fp, 0x05ad); /* SYNC 2 len */
WriteWord(fp, 0x05ac); /* Zero len */
WriteWord(fp, 0x0af4); /* One len */
WriteWord(fp, 0x1002); /* PILOT tone */
WriteByte(fp, 8); /* Last byte used bits */
WriteWord(fp, 0x0011); /* Pause after block */
Write3Word(fp, s->length);
WriteMem(fp, s->stream, s->length);
}
/* ---------------------------------------- INTERFACES
*/
const ValueTable *CPCOutputOptions(void)
{
return option_set;
}
CommandStatus CPCOutputSetOption(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_START_ADDR:
CMD_EXPR(argv[0], options.start_addr);
break;
case OPT_LOADER:
options.loader = ParseTrueFalse(argv[0], FALSE);
break;
default:
break;
}
return stat;
}
int CPCOutput(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.start_addr == -1)
{
options.start_addr = bank[0]->min_address_used;
}
/* Output the binary files
*/
WriteString(fp, "ZXTape!", 7, 0, CP_ASCII);
WriteByte(fp, 0x1a);
WriteByte(fp, 1);
WriteByte(fp, 0x14);
/* Output the basic loader if defined
*/
if (options.loader)
{
Stream basic;
Stream stream;
Byte header[256] = {0};
Word crc;
InitStream(&basic);
/* Line 10 MEMORY &xxxx
*/
AddStreamWord(&basic, 2 + 2 + 1 + 3 + 1);
AddStreamWord(&basic, 10);
AddStreamByte(&basic, 0xaa);
AddStreamByte(&basic, 0x1c);
AddStreamWord(&basic, bank[0]->min_address_used);
AddStreamByte(&basic, 0);
/* Line 20 LOAD ""
*/
AddStreamWord(&basic, 2 + 2 + 1 + 3 + 1);
AddStreamWord(&basic, 20);
AddStreamByte(&basic, 0xa8);
AddStreamByte(&basic, CodeFromNative(CP_ASCII, '"'));
AddStreamByte(&basic, CodeFromNative(CP_ASCII, '!'));
AddStreamByte(&basic, CodeFromNative(CP_ASCII, '"'));
AddStreamByte(&basic, 0);
/* Line 30 CALL &xxxx
*/
AddStreamWord(&basic, 2 + 2 + 1 + 3 + 1);
AddStreamWord(&basic, 30);
AddStreamByte(&basic, 0x83);
AddStreamByte(&basic, 0x1c);
AddStreamWord(&basic, options.start_addr);
AddStreamByte(&basic, 0);
/* End of program
*/
AddStreamWord(&basic, 0);
/* Create header
*/
WriteStringMem(header, 0, "LOADER.BAS", 16, 0, CP_ASCII);
header[16] = 1;
header[17] = 255;
header[18] = 0;
WriteWordMem(header, 19, basic.length);
WriteWordMem(header, 21, 0x170);
header[23] = 255;
WriteWordMem(header, 24, basic.length);
crc = CRC(header, 256);
/* Output header block
*/
InitStream(&stream);
AddStreamByte(&stream, 0x2c);
AddStreamMem(&stream, header, 256);
AddStreamByte(&stream, HIBYTE(crc));
AddStreamByte(&stream, LOBYTE(crc));
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
OutputTZXTurboBlock(fp, &stream);
FreeStream(&stream);
/* Output basic data block
*/
InitStream(&stream);
memset(header, 0, 256);
memcpy(header, basic.stream, basic.length);
crc = CRC(header, 256);
AddStreamByte(&stream, 0x16);
AddStreamMem(&stream, header, 256);
AddStreamByte(&stream, HIBYTE(crc));
AddStreamByte(&stream, LOBYTE(crc));
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
OutputTZXTurboBlock(fp, &stream);
FreeStream(&stream);
FreeStream(&basic);
}
for(f = 0; f < count; f++)
{
const Byte *mem;
int min, max, len, blocks, addr;
int block, blocklen;
Stream stream;
mem = bank[f]->memory;
min = bank[f]->min_address_used;
addr = min;
max = bank[f]->max_address_used;
len = max - min + 1;
blocks = len / BLOCK_SIZE;
if ((len % BLOCK_SIZE) == 0)
{
blocks--;
}
for(block = 0; block <= blocks; block++)
{
Byte header[256] = {0};
Word crc;
int first, last;
int seg, segs;
InitStream(&stream);
first = 0;
last = 0;
/* Create the header
*/
if (f == 0)
{
WriteStringMem(header, 0, filename, 16, 0, CP_ASCII);
}
else
{
char fn[16];
snprintf(fn, sizeof fn, filename_bank, bank[f]->number);
WriteStringMem(header, 0, fn, 16, 0, CP_ASCII);
}
header[16] = block + 1;
if (block == 0)
{
first = 255;
if (len > BLOCK_SIZE)
{
blocklen = BLOCK_SIZE;
}
else
{
blocklen = len;
}
}
if (block == blocks)
{
last = 255;
blocklen = len % BLOCK_SIZE;
}
if (blocklen == BLOCK_SIZE)
{
segs = 7;
}
else
{
segs = blocklen / 256;
}
header[17] = last;
header[18] = 2;
WriteWordMem(header, 19, blocklen);
WriteWordMem(header, 21, addr);
header[23] = first;
WriteWordMem(header, 24, len);
WriteWordMem(header, 26, options.start_addr);
crc = CRC(header, 256);
/* Write header data
*/
AddStreamByte(&stream, 0x2c);
AddStreamMem(&stream, header, 256);
AddStreamByte(&stream, HIBYTE(crc));
AddStreamByte(&stream, LOBYTE(crc));
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
OutputTZXTurboBlock(fp, &stream);
FreeStream(&stream);
/* Loop round for the segments (up to 8)
*/
InitStream(&stream);
AddStreamByte(&stream, 0x16);
for(seg = 0; seg <= segs; seg++)
{
Byte segment[256] = {0};
int segi = 0;
int last_seg = 0;
last_seg = (seg == segs);
if (!last_seg || blocklen == BLOCK_SIZE)
{
addr += 256;
}
else
{
addr += blocklen % 256;
}
while(min < addr)
{
segment[segi++] = mem[min++];
}
/* Add segment data to stream
*/
AddStreamMem(&stream, segment, 256);
crc = CRC(segment, 256);
AddStreamByte(&stream, HIBYTE(crc));
AddStreamByte(&stream, LOBYTE(crc));
if (last_seg)
{
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
AddStreamByte(&stream, 0xff);
}
}
OutputTZXTurboBlock(fp, &stream);
FreeStream(&stream);
}
}
fclose(fp);
return TRUE;
}
/*
vim: ai sw=4 ts=8 expandtab
*/