fteqw/q3asm2/q3asm2.c

1234 lines
26 KiB
C
Raw Permalink Normal View History

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of the fteqw tools.
FTEQW and the Quake III Arena source code are free software; you can redistribute them
and/or modify them under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
FTEQW and the Quake III Arena source code are 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 Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
/*
Copyright (C) 2007 David Walton
GPL.
This file is the core of an assembler/linker to generate q3-compatable qvm files. It is based on the version in the Q3 source code, but rewritten from the ground up.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include "../engine/qclib/hash.h"
//from qfiles.h
#define VM_MAGIC 0x12721444
struct vmheader {
int vmMagic;
int instructionCount;
int codeOffset;
int codeLength;
int dataOffset; // data then lit are here
int dataLength;
int litLength; // ( dataLength - litLength ) should be byteswapped on load
int bssLength; // zero filled memory appended to datalength
};
//end of qfiles.h
typedef enum {
OP_UNDEF,
OP_IGNORE,
OP_BREAK,
OP_ENTER,
OP_LEAVE,
OP_CALL,
OP_PUSH,
OP_POP,
OP_CONST,
OP_LOCAL,
OP_JUMP,
//-------------------
OP_EQ,
OP_NE,
OP_LTI,
OP_LEI,
OP_GTI,
OP_GEI,
OP_LTU,
OP_LEU,
OP_GTU,
OP_GEU,
OP_EQF,
OP_NEF,
OP_LTF,
OP_LEF,
OP_GTF,
OP_GEF,
//-------------------
OP_LOAD1,
OP_LOAD2,
OP_LOAD4,
OP_STORE1,
OP_STORE2,
OP_STORE4, // *(stack[top-1]) = stack[yop
OP_ARG,
OP_BLOCK_COPY,
//-------------------
OP_SEX8,
OP_SEX16,
OP_NEGI,
OP_ADD,
OP_SUB,
OP_DIVI,
OP_DIVU,
OP_MODI,
OP_MODU,
OP_MULI,
OP_MULU,
OP_BAND,
OP_BOR,
OP_BXOR,
OP_BCOM,
OP_LSH,
OP_RSHI,
OP_RSHU,
OP_NEGF,
OP_ADDF,
OP_SUBF,
OP_DIVF,
OP_MULF,
OP_CVIF,
OP_CVFI
} opcode_t;
typedef struct {
char *name;
int opcode;
} sourceOps_t;
sourceOps_t sourceOps[] = {
#include "opstrings.h"
};
enum segs {
SEG_CODE,
SEG_DATA,
SEG_LIT,
SEG_BSS,
SEG_MAX,
SEG_UNKNOWN
};
#define SYMBOLBUCKETS 4096
#define LOCALSYMBOLBUCKETS 1024
#define USEDATBLOCK 64
#define STACKSIZE 0x10000
#ifdef WIN32
#define snprintf _snprintf
#endif
struct usedat {
struct usedat *next;
int count;
char seg[USEDATBLOCK];
int offset[USEDATBLOCK];
};
struct symbol {
struct symbol *next;
int inseg; //this is where the actual value is defined
int atoffset;
struct usedat usedat; //this is where it is used
//to help with cpu cache, the first is embeded
bucket_t bucket; //for the hash tables
char name[1]; //a common C hack
};
struct segment {
void *data;
unsigned int base;
unsigned int length;
unsigned int available; //before it needs a realloc
int isdummy; //set if there's no real data assosiated with this segment
};
struct assembler {
struct segment segs[SEG_MAX];
struct segment *curseg;
struct symbol *symbols;
hashtable_t symboltable;
bucket_t symboltablebuckets[SYMBOLBUCKETS];
hashtable_t localsymboltable;
bucket_t localsymboltablebuckets[LOCALSYMBOLBUCKETS];
struct symbol *lastsym; //so we can move symbols that lcc put into the wrong place.
int numinstructions;
int errorcount;
int funclocals;
int funcargs;
int funcargoffset;
int verbose;
};
struct workload {
char output[256];
int numsrcfiles;
char **srcfilelist;
int verbose;
};
void EmitData(struct segment *seg, void *data, int bytes)
{
unsigned char *d, *s=data;
if (seg->isdummy)
{
seg->length += bytes;
return;
}
if (seg->length + bytes > seg->available)
{
unsigned int newlen = (seg->length + bytes + 0x10000) & ~0xffff;
void *newseg;
newseg = realloc(seg->data, newlen);
if (!newseg)
{
printf("out of memory\n");
seg->length += bytes;
seg->isdummy = 1;
return;
}
memset((char*)newseg+seg->available, 0, newlen - seg->available);
seg->data = newseg;
seg->available = newlen;
}
d = (unsigned char*)seg->data + seg->length;
seg->length += bytes;
while(bytes-- > 0)
*d++ = *s++;
}
void EmitByte(struct segment *seg, unsigned char data)
{
EmitData(seg, &data, 1);
}
void SkipData(struct segment *seg, int bytes)
{
seg->length += bytes;
}
void AlignSegment(struct segment *seg, int alignment)
{
//alignment must always be power of 2
alignment--;
seg->length = (seg->length + alignment) & ~(alignment);
}
void AssembleError(struct assembler *w, char *message, ...)
{
va_list va;
va_start(va, message);
vprintf(message, va);
va_end(va);
w->errorcount++;
}
void SegmentHack(struct assembler *w, int seg)
{
//like id, I don't see any way around this
//plus compatability. :/
if (w->curseg != &w->segs[seg])
{
w->curseg = &w->segs[seg];
if (w->lastsym)
{
// if (*w->lastsym->name != '$')
// printf("symbol %s segment-switch hack\n", w->lastsym->name);
w->lastsym->inseg = seg;
w->lastsym->atoffset = w->curseg->length;
}
else
printf("segment-switch hack\n");
}
}
void DoSegmentHack(struct assembler *w, int bytes)
{
if (bytes == 4)
{
SegmentHack(w, SEG_DATA);
}
else if (bytes == 1)
{
SegmentHack(w, SEG_LIT);
}
else
AssembleError(w, "%ibit variables are not supported\n", bytes*8);
}
int ParseToken(char *buffer, int bufferlen, char **input)
{
unsigned char *in = (unsigned char*)*input;
*buffer = 0;
while (*in <= ' ')
{
if (!*in++)
return 0;
}
if (*in == ';') //';' is taken as a comment
return 0;
do
{
if (!--bufferlen)
{
printf("name too long\n");
break;
}
*buffer++ = *in++;
} while (*in > ' ');
*buffer = 0;
*input = (char*)in;
return 1;
}
void AddReference(struct usedat *s, int segnum, int segofs)
{
if (s->count == USEDATBLOCK)
{ //we're getting a lot of references for this var
if (!s->next)
{
s->next = malloc(sizeof(*s));
if (!s->next)
{
printf("out of memory\n");
// AssembleError(w, "out of memory\n");
return;
}
s->next->next = NULL;
s->next->count = 0;
}
AddReference(s->next, segnum, segofs);
return;
}
s->seg[s->count] = segnum;
s->offset[s->count] = segofs;
s->count++;
}
struct symbol *GetSymbol(struct assembler *w, char *name)
{
struct symbol *s;
hashtable_t *t;
if (*name == '$')
t = &w->localsymboltable;
else
t = &w->symboltable;
s = Hash_Get(t, name);
if (!s)
{
s = malloc(sizeof(*s) + strlen(name));
if (!s)
{
AssembleError(w, "out of memory\n");
return NULL;
}
memset(s, 0, sizeof(*s));
s->next = w->symbols;
w->symbols = s;
strcpy(s->name, name);
Hash_Add(t, s->name, s, &s->bucket);
s->inseg = SEG_UNKNOWN;
s->atoffset = 0;
}
return s;
}
void DefineSymbol(struct assembler *w, char *symname, int segnum, int segofs)
{
struct symbol *s;
hashtable_t *t;
if (*symname == '$')
t = &w->localsymboltable;
else
t = &w->symboltable;
if (!*symname) //bail out
*(int*)NULL = -3;
s = Hash_Get(t, symname);
if (s)
{
if (s->inseg != SEG_UNKNOWN)
AssembleError(w, "symbol %s is already defined!\n", symname);
}
else
{
s = malloc(sizeof(*s) + strlen(symname));
if (!s)
{
printf ("out of memory\n");
w->errorcount++;
return;
}
memset(s, 0, sizeof(*s));
s->next = w->symbols;
w->symbols = s;
strcpy(s->name, symname);
Hash_Add(t, s->name, s, &s->bucket);
}
w->lastsym = s;
s->inseg = segnum;
s->atoffset = segofs;
}
int CalculateExpression(struct assembler *w, char *line)
{
char *o;
char valstr[64];
int val;
int segnum = w->curseg - w->segs;
int segoffs = w->curseg->length;
int isnum;
struct symbol *s;
//this is expressed as CONST+NAME-NAME
//many instructions need to add an additional base before emitting it
//so set the references to the place that we expect to be
val = 0;
while (*line)
{
o = valstr;
if (*line == '-' || *line == '+')
{
*o++ = *line++;
}
while (*(unsigned char*)line > ' ')
{
if (*line == '+' || *line == '-')
break;
*o++ = *line++;
}
*o = '\0';
if (*valstr == '-')
isnum = 1;
else if (*valstr == '+')
isnum = (valstr[1] >= '0' && valstr[1] <= '9');
else
isnum = (valstr[0] >= '0' && valstr[0] <= '9');
if (isnum)
val += atoi(valstr);
else
{
if (*valstr == '-' || *valstr == '+')
s = GetSymbol(w, valstr+1);
else
s = GetSymbol(w, valstr);
if (s) //yeah, doesn't make sense to have two symbols in one expression
AddReference(&s->usedat, segnum, segoffs); //but we do it anyway
}
}
return val;
}
void AssembleLine(struct assembler *w, char *line)
{
int i;
int opcode;
char instruction[64];
if (!ParseToken(instruction, sizeof(instruction), &line))
return; //nothing on this line
//try and get our special instructions first
switch (instruction[0])
{
case 'a':
if (!strcmp(instruction+1, "lign"))
{ //align
//theoretically, this should never make a difference. if it does, the segment hack stuff screwed something up.
if (ParseToken(instruction, sizeof(instruction), &line))
AlignSegment(w->curseg, atoi(instruction));
else
AssembleError(w, "align without argument\n");
return;
}
if (!strcmp(instruction+1, "ddress"))
{ //address
// w->lastsym = NULL;
if (ParseToken(instruction, sizeof(instruction), &line))
{
int expression;
SegmentHack(w, SEG_DATA);
expression = CalculateExpression(w, instruction);
EmitData(w->curseg, &expression, 4);
}
return;
}
break;
case 'b':
if (!strcmp(instruction+1, "ss"))
{ //bss
w->curseg = &w->segs[SEG_BSS];
return;
}
if (!strcmp(instruction+1, "yte"))
{ //byte
unsigned int value = 0;
unsigned int bytes = 0;
if (ParseToken(instruction, sizeof(instruction), &line))
bytes = atoi(instruction);
else
AssembleError(w, "byte without count\n");
if (ParseToken(instruction, sizeof(instruction), &line))
value = atoi(instruction);
else
AssembleError(w, "byte without value\n");
if (bytes == 4)
{
SegmentHack(w, SEG_DATA);
EmitData(w->curseg, &value, 4);
}
else if (bytes == 1)
{
SegmentHack(w, SEG_LIT);
EmitByte(w->curseg, value);
}
else
AssembleError(w, "%ibit variables are not supported\n", bytes*8);
return;
}
break;
case 'c':
if (!strcmp(instruction+1, "ode"))
{ //code
w->curseg = &w->segs[SEG_CODE];
return;
}
break;
case 'd':
if (!strcmp(instruction+1, "ata"))
{ //data
w->curseg = &w->segs[SEG_DATA];
return;
}
break;
case 'e':
if (!strcmp(instruction+1, "qu"))
{ //equ
char value[32];
if (ParseToken(instruction, sizeof(instruction), &line))
{
if (ParseToken(value, sizeof(value), &line))
DefineSymbol(w, instruction, w->curseg - w->segs, atoi(value));
else
AssembleError(w, "equ without value\n");
}
else
AssembleError(w, "equ without name\n");
return;
}
if (!strcmp(instruction+1, "xport"))
{ //export (ignored)
return;
}
if (!strcmp(instruction+1, "ndproc"))
{ //endproc
int locals = 0, args = 0;
if (ParseToken(instruction, sizeof(instruction), &line))
locals = atoi(instruction);
if (ParseToken(instruction, sizeof(instruction), &line))
args = atoi(instruction);
EmitByte(&w->segs[SEG_CODE], OP_PUSH); //we must return something
w->numinstructions++;
//disregard the two vars from above (matches q3asm...)
locals = 8 + w->funclocals + w->funcargs;
EmitByte(&w->segs[SEG_CODE], OP_LEAVE);
EmitData(&w->segs[SEG_CODE], &locals, 4);
w->numinstructions++;
return;
}
break;
case 'f':
if (!strcmp(instruction+1, "ile"))
{ //file (ignored)
return;
}
break;
case 'i':
if (!strcmp(instruction+1, "mport"))
{ //import (ignored)
return;
}
break;
case 'l':
if (!strcmp(instruction+1, "ine"))
{ //line (ignored)
return;
}
if (!strcmp(instruction+1, "it"))
{ //lit
w->curseg = &w->segs[SEG_LIT];
return;
}
break;
case 'p':
if (!strcmp(instruction+1, "roc"))
{ //proc
int v;
if (!ParseToken(instruction, sizeof(instruction), &line))
AssembleError(w, "unnamed function\n");
DefineSymbol(w, instruction, SEG_CODE, w->numinstructions);
if (ParseToken(instruction, sizeof(instruction), &line))
w->funclocals = (atoi(instruction) + 3) & ~3;
else
AssembleError(w, "proc without locals\n");
if (ParseToken(instruction, sizeof(instruction), &line))
w->funcargs = (atoi(instruction) + 3) & ~3;
else
AssembleError(w, "proc without args\n");
v = 8 + w->funclocals + w->funcargs;
if (v > 32767)
AssembleError(w, "function with an aweful lot of args\n");
EmitByte(&w->segs[SEG_CODE], OP_ENTER);
EmitData(&w->segs[SEG_CODE], &v, 4);
w->numinstructions++;
return;
}
if (!strcmp(instruction+1, "op"))
{ //pop
EmitByte(&w->segs[SEG_CODE], OP_POP);
w->numinstructions++;
return;
}
break;
case 's':
if (!strcmp(instruction+1, "kip"))
{ //skip
if (ParseToken(instruction, sizeof(instruction), &line))
SkipData(w->curseg, atoi(instruction));
else
AssembleError(w, "skip without argument\n");
return;
}
break;
case 'L':
if (!strncmp(instruction+1, "ABEL", 4))
{
if (!ParseToken(instruction, sizeof(instruction), &line))
{
printf("label with no name\n");
return;
}
//some evilness here (symbols in the instruction segment are instruction indexes not byte offsets)
if (w->curseg == &w->segs[SEG_CODE])
DefineSymbol(w, instruction, w->curseg - w->segs, w->numinstructions);
else
DefineSymbol(w, instruction, w->curseg - w->segs, w->curseg->length);
return;
}
break;
case 'A':
if (!strncmp(instruction+1, "RG", 2))
{ //ARG*
EmitByte(&w->segs[SEG_CODE], OP_ARG);
w->numinstructions++;
if (8 + w->funcargoffset > 255)
AssembleError(w, "too many arguments in fuction\n");
EmitByte(&w->segs[SEG_CODE], 8 + w->funcargoffset);
w->funcargoffset += 4; //reset by a following CALL instruction
return;
}
if (!strncmp(instruction+1, "DDRF", 4))
{ //ADDRF*
int exp = 0;
w->curseg = &w->segs[SEG_CODE]; //this differs, but not for lcc
EmitByte(&w->segs[SEG_CODE], OP_LOCAL);
if (ParseToken(instruction, sizeof(instruction), &line))
exp = CalculateExpression(w, instruction);
exp += 16 + w->funcargs + w->funclocals;
EmitData(&w->segs[SEG_CODE], &exp, 4);
w->numinstructions++;
return;
}
if (!strncmp(instruction+1, "DDRL", 4))
{ //ADDRL
int exp = 0;
w->curseg = &w->segs[SEG_CODE]; //this differs, but not for lcc
EmitByte(&w->segs[SEG_CODE], OP_LOCAL);
if (ParseToken(instruction, sizeof(instruction), &line))
exp = CalculateExpression(w, instruction);
exp += 8 + w->funcargs;
EmitData(&w->segs[SEG_CODE], &exp, 4);
w->numinstructions++;
return;
}
break;
case 'C':
if (!strncmp(instruction+1, "ALL", 3))
{ //CALL*
EmitByte(&w->segs[SEG_CODE], OP_CALL);
w->numinstructions++;
w->funcargoffset = 0;
return;
}
break;
case 'R':
if (!strncmp(instruction+1, "ET", 2))
{
int v = 8 + w->funclocals + w->funcargs;
EmitByte(&w->segs[SEG_CODE], OP_LEAVE);
EmitData(&w->segs[SEG_CODE], &v, 4);
w->numinstructions++;
return;
}
break;
default:
break;
}
//okay, we don't know what it is yet, try generic instructions
for (i = 0; i < sizeof(sourceOps) / sizeof(sourceOps[0]); i++)
{
if (*(unsigned int*)sourceOps[i].name == *(unsigned int*)instruction && !strcmp(sourceOps[i].name+4, instruction+4))
{
opcode = sourceOps[i].opcode;
if (opcode == OP_IGNORE)
return;
if (opcode == OP_SEX8)
{ //sex is special, apparently
if (ParseToken(instruction, sizeof(instruction), &line))
{
if (*instruction == '2')
opcode = OP_SEX16;
else if (*instruction != '1')
AssembleError(w, "bad sign extension\n");
}
}
if (opcode == OP_CVIF || opcode == OP_CVFI)
line = ""; //so we don't fall afoul looking for aguments (float/int conversions are always 4 anyway)
if (ParseToken(instruction, sizeof(instruction), &line))
{
int expression;
w->curseg = &w->segs[SEG_CODE]; //this differs, but not for lcc
EmitByte(&w->segs[SEG_CODE], opcode);
expression = CalculateExpression(w, instruction);
if (opcode == OP_BLOCK_COPY) //string initialisations are block copys too (this could still be wrong, but it matches)
expression = (expression+3)&~3;
EmitData(&w->segs[SEG_CODE], &expression, 4);
}
else
EmitByte(&w->segs[SEG_CODE], opcode);
w->numinstructions++;
return;
}
}
AssembleError(w, "Cannot handle %s\n", instruction);
}
void AssembleFile(struct assembler *w, int fnum, char *filename)
{
char linebuffer[1024];
FILE *f;
if (w->verbose)
{
if (fnum)
printf("file: %i %s\n", fnum, filename);
else
printf("file: %s\n", filename);
}
w->curseg = &w->segs[SEG_CODE];
f = fopen(filename, "rt");
if (!f)
{
snprintf(linebuffer, sizeof(linebuffer)-1, "%s.asm", filename);
f = fopen(linebuffer, "rt");
}
if (f)
{
while(fgets(linebuffer, sizeof(linebuffer), f))
AssembleLine(w, linebuffer);
fclose(f);
}
else
{
printf("couldn't open %s\n", filename);
w->errorcount++;
}
//reset the local symbol hash, so we don't find conflicting vars
memset(w->localsymboltablebuckets, 0, sizeof(w->localsymboltablebuckets));
}
int FixupSymbolReferencesOne(struct assembler *w, struct symbol *s, struct usedat *u)
{
int i;
int val;
unsigned int temp;
unsigned char *ptr;
val = w->segs[s->inseg].base + s->atoffset;
for (i = 0; i < u->count; i++)
{
ptr = (unsigned char*)w->segs[(int)u->seg[i]].data;
ptr += u->offset[i];
temp = (unsigned int)(ptr[0] | (ptr[1]<<8) | (ptr[2]<<16) | (ptr[3]<<24));
*(int*)&temp += val;
ptr[0] = (temp&0x000000ff)>>0;
ptr[1] = (temp&0x0000ff00)>>8;
ptr[2] = (temp&0x00ff0000)>>16;
ptr[3] = (temp&0xff000000)>>24;
}
if (u->next)
i += FixupSymbolReferencesOne(w, s, u->next);
return i;
}
void FixupSymbolReferences(struct assembler *w)
{ //this is our 'second pass'
struct symbol *s;
int numsyms = 0;
int numsymrefs = 0;
if (w->verbose)
printf("second 'pass'\n");
for (s = w->symbols; s; s = s->next)
{
if (s->inseg == SEG_UNKNOWN)
{
AssembleError(w, "Symbol %s was not defined\n", s->name);
continue;
}
if (w->verbose && !s->usedat.count && *s->name != '$') //don't ever mention the compiler generated 'static' vars
printf("%s was never used\n", s->name);
numsymrefs += FixupSymbolReferencesOne(w, s, &s->usedat);
numsyms++;
}
s = GetSymbol(w, "vmMain");
if (s)
{
if (s->atoffset != 0 || s->inseg != SEG_CODE)
AssembleError(w, "vmMain *MUST* be the first symbol in the qvm\nReorder your files\n");
}
if (w->verbose)
{
printf("Found %i symbols\n", numsyms);
printf("Found %i symbol references\n", numsymrefs);
}
}
void WriteMapFile(struct assembler *w, char *fname)
{
int segnum;
struct symbol *s;
FILE *f;
f = fopen(fname, "wt");
if (!f)
return;
//if we sorted these, we'd have better compatability with id's q3asm
//(their linker only defines variables in the second pass, their first pass is only to get table sizes)
//our symbols are in the order they were referenced, rather than used.
for (segnum = 0; segnum < SEG_MAX; segnum++)
{
for (s = w->symbols; s; s = s->next)
{
if (*s->name == '$') //don't show locals
continue;
if (s->inseg != segnum)
continue;
fprintf(f, "%i %8x %s\n", s->inseg, s->atoffset, s->name);
}
}
fclose(f);
}
void InitialiseAssembler(struct assembler *w)
{
int i;
memset(w, 0, sizeof(*w));
Hash_InitTable(&w->symboltable, SYMBOLBUCKETS, w->symboltablebuckets);
Hash_InitTable(&w->localsymboltable, LOCALSYMBOLBUCKETS, w->localsymboltablebuckets);
w->segs[SEG_BSS].isdummy = 1;
i = 0;
EmitData(&w->segs[SEG_DATA], &i, 4); //so NULL really is NULL
}
void WriteOutput(struct assembler *w, char *outputname)
{
FILE *f;
struct vmheader h;
int i;
for (i = 0; i < SEG_MAX; i++)
{ //align the segments (yes, even the code segment)
w->segs[i].length = (w->segs[i].length + 3) & ~3;
}
//add the stack to the end of the bss. I don't know if q3 even uses this
DefineSymbol(w, "_stackStart", SEG_BSS, w->segs[SEG_BSS].length);
w->segs[SEG_BSS].length += STACKSIZE;
DefineSymbol(w, "_stackEnd", SEG_BSS, w->segs[SEG_BSS].length);
w->segs[SEG_CODE].base = 0;
w->segs[SEG_DATA].base = 0;
w->segs[SEG_LIT].base = w->segs[SEG_DATA].base + w->segs[SEG_DATA].length;
w->segs[SEG_BSS].base = w->segs[SEG_LIT].base + w->segs[SEG_LIT].length;
FixupSymbolReferences(w);
if (w->segs[SEG_CODE].isdummy || w->segs[SEG_DATA].isdummy || w->segs[SEG_LIT].isdummy)
w->errorcount++; //one of the segments failed to allocate mem
printf("code bytes: %i\n", w->segs[SEG_CODE].length);
printf("data bytes: %i\n", w->segs[SEG_DATA].length);
printf(" lit bytes: %i\n", w->segs[SEG_LIT ].length);
printf(" bss bytes: %i\n", w->segs[SEG_BSS ].length);
printf("instruction count: %i\n", w->numinstructions);
if (w->errorcount)
{
printf("%i errors\n", w->errorcount);
return;
}
h.vmMagic = VM_MAGIC;
h.instructionCount = w->numinstructions;
h.codeLength = w->segs[SEG_CODE].length;
h.dataLength = w->segs[SEG_DATA].length;
h.litLength = w->segs[SEG_LIT].length;
h.bssLength = w->segs[SEG_BSS].length;
h.codeOffset = sizeof(h);
h.dataOffset = h.codeOffset + h.codeLength;
f = fopen(outputname, "wb");
if (f)
{
fwrite(&h, 1, sizeof(h), f);
fwrite(w->segs[SEG_CODE].data, 1, w->segs[SEG_CODE].length, f);
fwrite(w->segs[SEG_DATA].data, 1, w->segs[SEG_DATA].length, f);
fwrite(w->segs[SEG_LIT ].data, 1, w->segs[SEG_LIT ].length, f);
//logically the bss comes here (but doesn't, cos its bss)
fclose(f);
}
else
printf("error writing to file: %s\n", outputname);
WriteMapFile(w, "q3asm2.map");
}
void Assemble(struct workload *w)
{
int i;
struct assembler a;
InitialiseAssembler(&a);
a.verbose = w->verbose;
for (i = 0; i < w->numsrcfiles; i++)
AssembleFile(&a, i+1, w->srcfilelist[i]);
WriteOutput(&a, w->output);
}
int ParseCommandList(struct workload *w, int numcmds, char **cmds)
{
char **t;
FILE *f;
char *buffer, *pp, *pps;
unsigned int len, numextraargs;
char *suppargs[64];
while (numcmds)
{
if ((*cmds)[0] == '-')
{
if ((*cmds)[1] == 'f')
{
numcmds--;
cmds++;
f = fopen(*cmds, "rt");
if (!f)
{
char blah[256];
snprintf(blah, sizeof(blah)-1, "%s.q3asm", *cmds);
f = fopen(blah, "rt");
}
if (f)
{
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
buffer = malloc((len+9));
if (buffer)
{
if (fread(buffer, 1, len, f) == len)
{
buffer[len] = 0;
numextraargs = 0;
pp = buffer;
do
{
if (numextraargs == sizeof(suppargs)/sizeof(suppargs[0]))
break;
while (*pp == ' ' || *pp == '\r' || *pp == '\n')
{
pp++;
}
if (*pp == '\"')
{
pp++;
pps = pp;
while (*pp && *pp != '\"')
pp++;
}
else
{
pps = pp;
while (*pp && !(*pp == ' ' || *pp == '\r' || *pp == '\n'))
{
pp++;
}
}
if (*pp)
*pp++ = 0;
suppargs[numextraargs] = pps;
numextraargs++;
} while (*pp);
ParseCommandList(w, numextraargs, suppargs);
}
else
{
printf("failure reading source file\n");
}
free(buffer);
}
else
{
printf("out of memory\n");
}
fclose(f);
}
else
{
printf("couldn't open \"%s\"\n", *cmds);
}
}
else if ((*cmds)[1] == 'o')
{
numcmds--;
cmds++;
if (!strchr(*cmds, '.'))
{
snprintf(w->output, sizeof(w->output)-1, "%s.qvm", *cmds);
}
else
{
strncpy(w->output, *cmds, sizeof(w->output)-1);
w->output[sizeof(w->output)-1] = 0;
}
}
else if ((*cmds)[1] == 'v')
w->verbose = 1;
else
{
printf("Unrecognised command\n");
return 1;
}
}
else
{
//just a src file
t = realloc(w->srcfilelist, sizeof(char*)*(w->numsrcfiles+1));
if (!t)
{ //if realloc fails, the old pointer is still valid
printf("out of memory\n");
return 1;
}
w->srcfilelist = t;
w->srcfilelist[w->numsrcfiles] = malloc(strlen(*cmds)+1);
if (!w->srcfilelist[w->numsrcfiles])
{
printf("out of memory\n");
return 1;
}
strcpy(w->srcfilelist[w->numsrcfiles], *cmds);
w->numsrcfiles++;
}
numcmds--;
cmds++;
}
return 0;
}
int main(int argc, char **argv)
{
int i;
struct workload *w;
if (!(w = malloc(sizeof(*w))))
{
printf("out of memory (REALLY EARLY!!!)\n");
}
else
{
w->numsrcfiles = 0;
w->srcfilelist = NULL;
strcpy(w->output, "q3asm2.qvm"); //fill in the default options
if (ParseCommandList(w, argc-1, argv+1))
{
printf("Syntax is: q3asm2 [-o <output>] <files>\n");
printf(" or: q3asm2 -f <listfile>\n");
printf("or any crazy recursive mixture of the two\n");
}
else
{
printf("output file: %s\n", w->output);
printf("%i files\n", w->numsrcfiles);
Assemble(w);
}
for (i = 0; i < w->numsrcfiles; i++)
{
free(w->srcfilelist[i]);
}
if (w->srcfilelist)
free(w->srcfilelist);
free(w);
}
return 0;
}