mirror of
https://github.com/DrBeef/ioq3quest.git
synced 2024-11-26 22:11:18 +00:00
1647 lines
34 KiB
C
1647 lines
34 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena source code 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 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "../../qcommon/q_platform.h"
|
|
#include "cmdlib.h"
|
|
#include "mathlib.h"
|
|
#include "../../qcommon/qfiles.h"
|
|
|
|
/* 19079 total symbols in FI, 2002 Jan 23 */
|
|
#define DEFAULT_HASHTABLE_SIZE 2048
|
|
|
|
char outputFilename[MAX_OS_PATH];
|
|
|
|
// the zero page size is just used for detecting run time faults
|
|
#define ZERO_PAGE_SIZE 0 // 256
|
|
|
|
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 {
|
|
int imageBytes; // after decompression
|
|
int entryPoint;
|
|
int stackBase;
|
|
int stackSize;
|
|
} executableHeader_t;
|
|
|
|
typedef enum {
|
|
CODESEG,
|
|
DATASEG, // initialized 32 bit data, will be byte swapped
|
|
LITSEG, // strings
|
|
BSSSEG, // 0 filled
|
|
JTRGSEG, // pseudo-segment that contains only jump table targets
|
|
NUM_SEGMENTS
|
|
} segmentName_t;
|
|
|
|
#define MAX_IMAGE 0x400000
|
|
|
|
typedef struct {
|
|
byte image[MAX_IMAGE];
|
|
int imageUsed;
|
|
int segmentBase; // only valid on second pass
|
|
} segment_t;
|
|
|
|
typedef struct symbol_s {
|
|
struct symbol_s *next;
|
|
int hash;
|
|
segment_t *segment;
|
|
char *name;
|
|
int value;
|
|
} symbol_t;
|
|
|
|
typedef struct hashchain_s {
|
|
void *data;
|
|
struct hashchain_s *next;
|
|
} hashchain_t;
|
|
|
|
typedef struct hashtable_s {
|
|
int buckets;
|
|
hashchain_t **table;
|
|
} hashtable_t;
|
|
|
|
int symtablelen = DEFAULT_HASHTABLE_SIZE;
|
|
hashtable_t *symtable;
|
|
hashtable_t *optable;
|
|
|
|
segment_t segment[NUM_SEGMENTS];
|
|
segment_t *currentSegment;
|
|
|
|
int passNumber;
|
|
|
|
int numSymbols;
|
|
int errorCount;
|
|
|
|
typedef struct options_s {
|
|
qboolean verbose;
|
|
qboolean writeMapFile;
|
|
qboolean vanillaQ3Compatibility;
|
|
} options_t;
|
|
|
|
options_t options = { 0 };
|
|
|
|
symbol_t *symbols;
|
|
symbol_t *lastSymbol = 0; /* Most recent symbol defined. */
|
|
|
|
|
|
#define MAX_ASM_FILES 256
|
|
int numAsmFiles;
|
|
char *asmFiles[MAX_ASM_FILES];
|
|
char *asmFileNames[MAX_ASM_FILES];
|
|
|
|
int currentFileIndex;
|
|
char *currentFileName;
|
|
int currentFileLine;
|
|
|
|
//int stackSize = 16384;
|
|
int stackSize = 0x10000;
|
|
|
|
// we need to convert arg and ret instructions to
|
|
// stores to the local stack frame, so we need to track the
|
|
// characteristics of the current functions stack frame
|
|
int currentLocals; // bytes of locals needed by this function
|
|
int currentArgs; // bytes of largest argument list called from this function
|
|
int currentArgOffset; // byte offset in currentArgs to store next arg, reset each call
|
|
|
|
#define MAX_LINE_LENGTH 1024
|
|
char lineBuffer[MAX_LINE_LENGTH];
|
|
int lineParseOffset;
|
|
char token[MAX_LINE_LENGTH];
|
|
|
|
int instructionCount;
|
|
|
|
typedef struct {
|
|
char *name;
|
|
int opcode;
|
|
} sourceOps_t;
|
|
|
|
sourceOps_t sourceOps[] = {
|
|
#include "opstrings.h"
|
|
};
|
|
|
|
#define NUM_SOURCE_OPS ( sizeof( sourceOps ) / sizeof( sourceOps[0] ) )
|
|
|
|
int opcodesHash[ NUM_SOURCE_OPS ];
|
|
|
|
|
|
|
|
static int vreport (const char* fmt, va_list vp)
|
|
{
|
|
if (options.verbose != qtrue)
|
|
return 0;
|
|
return vprintf(fmt, vp);
|
|
}
|
|
|
|
static int report (const char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
int retval;
|
|
|
|
va_start(va, fmt);
|
|
retval = vreport(fmt, va);
|
|
va_end(va);
|
|
return retval;
|
|
}
|
|
|
|
/* The chain-and-bucket hash table. -PH */
|
|
|
|
static void hashtable_init (hashtable_t *H, int buckets)
|
|
{
|
|
H->buckets = buckets;
|
|
H->table = calloc(H->buckets, sizeof(*(H->table)));
|
|
}
|
|
|
|
static hashtable_t *hashtable_new (int buckets)
|
|
{
|
|
hashtable_t *H;
|
|
|
|
H = malloc(sizeof(hashtable_t));
|
|
hashtable_init(H, buckets);
|
|
return H;
|
|
}
|
|
|
|
/* No destroy/destructor. No need. */
|
|
|
|
static void hashtable_add (hashtable_t *H, int hashvalue, void *datum)
|
|
{
|
|
hashchain_t *hc, **hb;
|
|
|
|
hashvalue = (abs(hashvalue) % H->buckets);
|
|
hb = &(H->table[hashvalue]);
|
|
if (*hb == 0)
|
|
{
|
|
/* Empty bucket. Create new one. */
|
|
*hb = calloc(1, sizeof(**hb));
|
|
hc = *hb;
|
|
}
|
|
else
|
|
{
|
|
/* Get hc to point to last node in chain. */
|
|
for (hc = *hb; hc && hc->next; hc = hc->next);
|
|
hc->next = calloc(1, sizeof(*hc));
|
|
hc = hc->next;
|
|
}
|
|
hc->data = datum;
|
|
hc->next = 0;
|
|
}
|
|
|
|
static hashchain_t *hashtable_get (hashtable_t *H, int hashvalue)
|
|
{
|
|
hashvalue = (abs(hashvalue) % H->buckets);
|
|
return (H->table[hashvalue]);
|
|
}
|
|
|
|
static void hashtable_stats (hashtable_t *H)
|
|
{
|
|
int len, empties, longest, nodes;
|
|
int i;
|
|
float meanlen;
|
|
hashchain_t *hc;
|
|
|
|
report("Stats for hashtable %08X", H);
|
|
empties = 0;
|
|
longest = 0;
|
|
nodes = 0;
|
|
for (i = 0; i < H->buckets; i++)
|
|
{
|
|
if (H->table[i] == 0)
|
|
{ empties++; continue; }
|
|
for (hc = H->table[i], len = 0; hc; hc = hc->next, len++);
|
|
if (len > longest) { longest = len; }
|
|
nodes += len;
|
|
}
|
|
meanlen = (float)(nodes) / (H->buckets - empties);
|
|
#if 0
|
|
/* Long stats display */
|
|
report(" Total buckets: %d\n", H->buckets);
|
|
report(" Total stored nodes: %d\n", nodes);
|
|
report(" Longest chain: %d\n", longest);
|
|
report(" Empty chains: %d\n", empties);
|
|
report(" Mean non-empty chain length: %f\n", meanlen);
|
|
#else //0
|
|
/* Short stats display */
|
|
report(", %d buckets, %d nodes", H->buckets, nodes);
|
|
report("\n");
|
|
report(" Longest chain: %d, empty chains: %d, mean non-empty: %f", longest, empties, meanlen);
|
|
#endif //0
|
|
report("\n");
|
|
}
|
|
|
|
|
|
/* Kludge. */
|
|
/* Check if symbol already exists. */
|
|
/* Returns 0 if symbol does NOT already exist, non-zero otherwise. */
|
|
static int hashtable_symbol_exists (hashtable_t *H, int hash, char *sym)
|
|
{
|
|
hashchain_t *hc;
|
|
symbol_t *s;
|
|
|
|
hash = (abs(hash) % H->buckets);
|
|
hc = H->table[hash];
|
|
if (hc == 0)
|
|
{
|
|
/* Empty chain means this symbol has not yet been defined. */
|
|
return 0;
|
|
}
|
|
for (; hc; hc = hc->next)
|
|
{
|
|
s = (symbol_t*)hc->data;
|
|
// if ((hash == s->hash) && (strcmp(sym, s->name) == 0))
|
|
/* We _already_ know the hash is the same. That's why we're probing! */
|
|
if (strcmp(sym, s->name) == 0)
|
|
{
|
|
/* Symbol collisions -- symbol already exists. */
|
|
return 1;
|
|
}
|
|
}
|
|
return 0; /* Can't find collision. */
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Comparator function for quicksorting. */
|
|
static int symlist_cmp (const void *e1, const void *e2)
|
|
{
|
|
const symbol_t *a, *b;
|
|
|
|
a = *(const symbol_t **)e1;
|
|
b = *(const symbol_t **)e2;
|
|
//crumb("Symbol comparison (1) %d to (2) %d\n", a->value, b->value);
|
|
return ( a->value - b->value);
|
|
}
|
|
|
|
/*
|
|
Sort the symbols list by using QuickSort (qsort()).
|
|
This may take a LOT of memory (a few megabytes?), but memory is cheap these days.
|
|
However, qsort(3) already exists, and I'm really lazy.
|
|
-PH
|
|
*/
|
|
static void sort_symbols ()
|
|
{
|
|
int i, elems;
|
|
symbol_t *s;
|
|
symbol_t **symlist;
|
|
|
|
if(!symbols)
|
|
return;
|
|
|
|
//crumb("sort_symbols: Constructing symlist array\n");
|
|
for (elems = 0, s = symbols; s; s = s->next, elems++) /* nop */ ;
|
|
|
|
symlist = malloc(elems * sizeof(symbol_t*));
|
|
for (i = 0, s = symbols; s; s = s->next, i++)
|
|
{
|
|
symlist[i] = s;
|
|
}
|
|
//crumbf("sort_symbols: Quick-sorting %d symbols\n", elems);
|
|
qsort(symlist, elems, sizeof(symbol_t*), symlist_cmp);
|
|
//crumbf("sort_symbols: Reconstructing symbols list\n");
|
|
s = symbols = symlist[0];
|
|
for (i = 1; i < elems; i++)
|
|
{
|
|
s->next = symlist[i];
|
|
s = s->next;
|
|
}
|
|
lastSymbol = s;
|
|
s->next = 0;
|
|
//crumbf("sort_symbols: verifying..."); fflush(stdout);
|
|
for (i = 0, s = symbols; s; s = s->next, i++) /*nop*/ ;
|
|
//crumbf(" %d elements\n", i);
|
|
free(symlist); /* d'oh. no gc. */
|
|
}
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
#define INT64 __int64
|
|
#define atoi64 _atoi64
|
|
#else
|
|
#define INT64 long long int
|
|
#define atoi64 atoll
|
|
#endif
|
|
|
|
/*
|
|
Problem:
|
|
BYTE values are specified as signed decimal string. A properly functional
|
|
atoip() will cap large signed values at 0x7FFFFFFF. Negative word values are
|
|
often specified as very large decimal values by lcc. Therefore, values that
|
|
should be between 0x7FFFFFFF and 0xFFFFFFFF come out as 0x7FFFFFFF when using
|
|
atoi(). Bad.
|
|
|
|
This function is one big evil hack to work around this problem.
|
|
*/
|
|
static int atoiNoCap (const char *s)
|
|
{
|
|
INT64 l;
|
|
union {
|
|
unsigned int u;
|
|
signed int i;
|
|
} retval;
|
|
|
|
l = atoi64(s);
|
|
/* Now smash to signed 32 bits accordingly. */
|
|
if (l < 0) {
|
|
retval.i = (int)l;
|
|
} else {
|
|
retval.u = (unsigned int)l;
|
|
}
|
|
return retval.i; /* <- union hackage. I feel dirty with this. -PH */
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=============
|
|
HashString
|
|
=============
|
|
*/
|
|
/* Default hash function of Kazlib 1.19, slightly modified. */
|
|
static unsigned int HashString (const char *key)
|
|
{
|
|
static unsigned long randbox[] = {
|
|
0x49848f1bU, 0xe6255dbaU, 0x36da5bdcU, 0x47bf94e9U,
|
|
0x8cbcce22U, 0x559fc06aU, 0xd268f536U, 0xe10af79aU,
|
|
0xc1af4d69U, 0x1d2917b5U, 0xec4c304dU, 0x9ee5016cU,
|
|
0x69232f74U, 0xfead7bb3U, 0xe9089ab6U, 0xf012f6aeU,
|
|
};
|
|
|
|
const char *str = key;
|
|
unsigned int acc = 0;
|
|
|
|
while (*str) {
|
|
acc ^= randbox[(*str + acc) & 0xf];
|
|
acc = (acc << 1) | (acc >> 31);
|
|
acc &= 0xffffffffU;
|
|
acc ^= randbox[((*str++ >> 4) + acc) & 0xf];
|
|
acc = (acc << 2) | (acc >> 30);
|
|
acc &= 0xffffffffU;
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
CodeError
|
|
============
|
|
*/
|
|
static void CodeError( char *fmt, ... ) {
|
|
va_list argptr;
|
|
|
|
errorCount++;
|
|
|
|
fprintf( stderr, "%s:%i ", currentFileName, currentFileLine );
|
|
|
|
va_start( argptr,fmt );
|
|
vfprintf( stderr, fmt, argptr );
|
|
va_end( argptr );
|
|
}
|
|
|
|
/*
|
|
============
|
|
EmitByte
|
|
============
|
|
*/
|
|
static void EmitByte( segment_t *seg, int v ) {
|
|
if ( seg->imageUsed >= MAX_IMAGE ) {
|
|
Error( "MAX_IMAGE" );
|
|
}
|
|
seg->image[ seg->imageUsed ] = v;
|
|
seg->imageUsed++;
|
|
}
|
|
|
|
/*
|
|
============
|
|
EmitInt
|
|
============
|
|
*/
|
|
static void EmitInt( segment_t *seg, int v ) {
|
|
if ( seg->imageUsed >= MAX_IMAGE - 4) {
|
|
Error( "MAX_IMAGE" );
|
|
}
|
|
seg->image[ seg->imageUsed ] = v & 255;
|
|
seg->image[ seg->imageUsed + 1 ] = ( v >> 8 ) & 255;
|
|
seg->image[ seg->imageUsed + 2 ] = ( v >> 16 ) & 255;
|
|
seg->image[ seg->imageUsed + 3 ] = ( v >> 24 ) & 255;
|
|
seg->imageUsed += 4;
|
|
}
|
|
|
|
/*
|
|
============
|
|
DefineSymbol
|
|
|
|
Symbols can only be defined on pass 0
|
|
============
|
|
*/
|
|
static void DefineSymbol( char *sym, int value ) {
|
|
/* Hand optimization by PhaethonH */
|
|
symbol_t *s;
|
|
char expanded[MAX_LINE_LENGTH];
|
|
int hash;
|
|
|
|
if ( passNumber == 1 ) {
|
|
return;
|
|
}
|
|
|
|
// add the file prefix to local symbols to guarantee unique
|
|
if ( sym[0] == '$' ) {
|
|
sprintf( expanded, "%s_%i", sym, currentFileIndex );
|
|
sym = expanded;
|
|
}
|
|
|
|
hash = HashString( sym );
|
|
|
|
if (hashtable_symbol_exists(symtable, hash, sym)) {
|
|
CodeError( "Multiple definitions for %s\n", sym );
|
|
return;
|
|
}
|
|
|
|
s = malloc( sizeof( *s ) );
|
|
s->next = NULL;
|
|
s->name = copystring( sym );
|
|
s->hash = hash;
|
|
s->value = value;
|
|
s->segment = currentSegment;
|
|
|
|
hashtable_add(symtable, hash, s);
|
|
|
|
/*
|
|
Hash table lookup already speeds up symbol lookup enormously.
|
|
We postpone sorting until end of pass 0.
|
|
Since we're not doing the insertion sort, lastSymbol should always
|
|
wind up pointing to the end of list.
|
|
This allows constant time for adding to the list.
|
|
-PH
|
|
*/
|
|
if (symbols == 0) {
|
|
lastSymbol = symbols = s;
|
|
} else {
|
|
lastSymbol->next = s;
|
|
lastSymbol = s;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
LookupSymbol
|
|
|
|
Symbols can only be evaluated on pass 1
|
|
============
|
|
*/
|
|
static int LookupSymbol( char *sym ) {
|
|
symbol_t *s;
|
|
char expanded[MAX_LINE_LENGTH];
|
|
int hash;
|
|
hashchain_t *hc;
|
|
|
|
if ( passNumber == 0 ) {
|
|
return 0;
|
|
}
|
|
|
|
// add the file prefix to local symbols to guarantee unique
|
|
if ( sym[0] == '$' ) {
|
|
sprintf( expanded, "%s_%i", sym, currentFileIndex );
|
|
sym = expanded;
|
|
}
|
|
|
|
hash = HashString( sym );
|
|
|
|
/*
|
|
Hand optimization by PhaethonH
|
|
|
|
Using a hash table with chain/bucket for lookups alone sped up q3asm by almost 3x for me.
|
|
-PH
|
|
*/
|
|
for (hc = hashtable_get(symtable, hash); hc; hc = hc->next) {
|
|
s = (symbol_t*)hc->data; /* ugly typecasting, but it's fast! */
|
|
if ( (hash == s->hash) && !strcmp(sym, s->name) ) {
|
|
return s->segment->segmentBase + s->value;
|
|
}
|
|
}
|
|
|
|
CodeError( "error: symbol %s undefined\n", sym );
|
|
passNumber = 0;
|
|
DefineSymbol( sym, 0 ); // so more errors aren't printed
|
|
passNumber = 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
ExtractLine
|
|
|
|
Extracts the next line from the given text block.
|
|
If a full line isn't parsed, returns NULL
|
|
Otherwise returns the updated parse pointer
|
|
===============
|
|
*/
|
|
static char *ExtractLine( char *data ) {
|
|
/* Goal:
|
|
Given a string `data', extract one text line into buffer `lineBuffer' that
|
|
is no longer than MAX_LINE_LENGTH characters long. Return value is
|
|
remainder of `data' that isn't part of `lineBuffer'.
|
|
-PH
|
|
*/
|
|
/* Hand-optimized by PhaethonH */
|
|
char *p, *q;
|
|
|
|
currentFileLine++;
|
|
|
|
lineParseOffset = 0;
|
|
token[0] = 0;
|
|
*lineBuffer = 0;
|
|
|
|
p = q = data;
|
|
if (!*q) {
|
|
return NULL;
|
|
}
|
|
|
|
for ( ; !((*p == 0) || (*p == '\n')); p++) /* nop */ ;
|
|
|
|
if ((p - q) >= MAX_LINE_LENGTH) {
|
|
CodeError( "MAX_LINE_LENGTH" );
|
|
return data;
|
|
}
|
|
|
|
memcpy( lineBuffer, data, (p - data) );
|
|
lineBuffer[(p - data)] = 0;
|
|
p += (*p == '\n') ? 1 : 0; /* Skip over final newline. */
|
|
return p;
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
Parse
|
|
|
|
Parse a token out of linebuffer
|
|
==============
|
|
*/
|
|
static qboolean Parse( void ) {
|
|
/* Hand-optimized by PhaethonH */
|
|
const char *p, *q;
|
|
|
|
/* Because lineParseOffset is only updated just before exit, this makes this code version somewhat harder to debug under a symbolic debugger. */
|
|
|
|
*token = 0; /* Clear token. */
|
|
|
|
// skip whitespace
|
|
for (p = lineBuffer + lineParseOffset; *p && (*p <= ' '); p++) /* nop */ ;
|
|
|
|
// skip ; comments
|
|
/* die on end-of-string */
|
|
if ((*p == ';') || (*p == 0)) {
|
|
lineParseOffset = p - lineBuffer;
|
|
return qfalse;
|
|
}
|
|
|
|
q = p; /* Mark the start of token. */
|
|
/* Find separator first. */
|
|
for ( ; *p > 32; p++) /* nop */ ; /* XXX: unsafe assumptions. */
|
|
/* *p now sits on separator. Mangle other values accordingly. */
|
|
strncpy(token, q, p - q);
|
|
token[p - q] = 0;
|
|
|
|
lineParseOffset = p - lineBuffer;
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
ParseValue
|
|
==============
|
|
*/
|
|
static int ParseValue( void ) {
|
|
Parse();
|
|
return atoiNoCap( token );
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
ParseExpression
|
|
==============
|
|
*/
|
|
static int ParseExpression(void) {
|
|
/* Hand optimization, PhaethonH */
|
|
int i, j;
|
|
char sym[MAX_LINE_LENGTH];
|
|
int v;
|
|
|
|
/* Skip over a leading minus. */
|
|
for ( i = ((token[0] == '-') ? 1 : 0) ; i < MAX_LINE_LENGTH ; i++ ) {
|
|
if ( token[i] == '+' || token[i] == '-' || token[i] == 0 ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
memcpy( sym, token, i );
|
|
sym[i] = 0;
|
|
|
|
switch (*sym) { /* Resolve depending on first character. */
|
|
/* Optimizing compilers can convert cases into "calculated jumps". I think these are faster. -PH */
|
|
case '-':
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
v = atoiNoCap(sym);
|
|
break;
|
|
default:
|
|
v = LookupSymbol(sym);
|
|
break;
|
|
}
|
|
|
|
// parse add / subtract offsets
|
|
while ( token[i] != 0 ) {
|
|
for ( j = i + 1 ; j < MAX_LINE_LENGTH ; j++ ) {
|
|
if ( token[j] == '+' || token[j] == '-' || token[j] == 0 ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
memcpy( sym, token+i+1, j-i-1 );
|
|
sym[j-i-1] = 0;
|
|
|
|
switch (token[i]) {
|
|
case '+':
|
|
v += atoiNoCap(sym);
|
|
break;
|
|
case '-':
|
|
v -= atoiNoCap(sym);
|
|
break;
|
|
}
|
|
|
|
i = j;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
HackToSegment
|
|
|
|
BIG HACK: I want to put all 32 bit values in the data
|
|
segment so they can be byte swapped, and all char data in the lit
|
|
segment, but switch jump tables are emitted in the lit segment and
|
|
initialized strng variables are put in the data segment.
|
|
|
|
I can change segments here, but I also need to fixup the
|
|
label that was just defined
|
|
|
|
Note that the lit segment is read-write in the VM, so strings
|
|
aren't read only as in some architectures.
|
|
==============
|
|
*/
|
|
static void HackToSegment( segmentName_t seg ) {
|
|
if ( currentSegment == &segment[seg] ) {
|
|
return;
|
|
}
|
|
|
|
currentSegment = &segment[seg];
|
|
if ( passNumber == 0 ) {
|
|
lastSymbol->segment = currentSegment;
|
|
lastSymbol->value = currentSegment->imageUsed;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//#define STAT(L) report("STAT " L "\n");
|
|
#define STAT(L)
|
|
#define ASM(O) int TryAssemble##O ()
|
|
|
|
|
|
/*
|
|
These clauses were moved out from AssembleLine() to allow reordering of if's.
|
|
An optimizing compiler should reconstruct these back into inline code.
|
|
-PH
|
|
*/
|
|
|
|
// call instructions reset currentArgOffset
|
|
ASM(CALL)
|
|
{
|
|
if ( !strncmp( token, "CALL", 4 ) ) {
|
|
STAT("CALL");
|
|
EmitByte( &segment[CODESEG], OP_CALL );
|
|
instructionCount++;
|
|
currentArgOffset = 0;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// arg is converted to a reversed store
|
|
ASM(ARG)
|
|
{
|
|
if ( !strncmp( token, "ARG", 3 ) ) {
|
|
STAT("ARG");
|
|
EmitByte( &segment[CODESEG], OP_ARG );
|
|
instructionCount++;
|
|
if ( 8 + currentArgOffset >= 256 ) {
|
|
CodeError( "currentArgOffset >= 256" );
|
|
return 1;
|
|
}
|
|
EmitByte( &segment[CODESEG], 8 + currentArgOffset );
|
|
currentArgOffset += 4;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// ret just leaves something on the op stack
|
|
ASM(RET)
|
|
{
|
|
if ( !strncmp( token, "RET", 3 ) ) {
|
|
STAT("RET");
|
|
EmitByte( &segment[CODESEG], OP_LEAVE );
|
|
instructionCount++;
|
|
EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs );
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// pop is needed to discard the return value of
|
|
// a function
|
|
ASM(POP)
|
|
{
|
|
if ( !strncmp( token, "pop", 3 ) ) {
|
|
STAT("POP");
|
|
EmitByte( &segment[CODESEG], OP_POP );
|
|
instructionCount++;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// address of a parameter is converted to OP_LOCAL
|
|
ASM(ADDRF)
|
|
{
|
|
int v;
|
|
if ( !strncmp( token, "ADDRF", 5 ) ) {
|
|
STAT("ADDRF");
|
|
instructionCount++;
|
|
Parse();
|
|
v = ParseExpression();
|
|
v = 16 + currentArgs + currentLocals + v;
|
|
EmitByte( &segment[CODESEG], OP_LOCAL );
|
|
EmitInt( &segment[CODESEG], v );
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// address of a local is converted to OP_LOCAL
|
|
ASM(ADDRL)
|
|
{
|
|
int v;
|
|
if ( !strncmp( token, "ADDRL", 5 ) ) {
|
|
STAT("ADDRL");
|
|
instructionCount++;
|
|
Parse();
|
|
v = ParseExpression();
|
|
v = 8 + currentArgs + v;
|
|
EmitByte( &segment[CODESEG], OP_LOCAL );
|
|
EmitInt( &segment[CODESEG], v );
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(PROC)
|
|
{
|
|
char name[1024];
|
|
if ( !strcmp( token, "proc" ) ) {
|
|
STAT("PROC");
|
|
Parse(); // function name
|
|
strcpy( name, token );
|
|
|
|
DefineSymbol( token, instructionCount ); // segment[CODESEG].imageUsed );
|
|
|
|
currentLocals = ParseValue(); // locals
|
|
currentLocals = ( currentLocals + 3 ) & ~3;
|
|
currentArgs = ParseValue(); // arg marshalling
|
|
currentArgs = ( currentArgs + 3 ) & ~3;
|
|
|
|
if ( 8 + currentLocals + currentArgs >= 32767 ) {
|
|
CodeError( "Locals > 32k in %s\n", name );
|
|
}
|
|
|
|
instructionCount++;
|
|
EmitByte( &segment[CODESEG], OP_ENTER );
|
|
EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs );
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
ASM(ENDPROC)
|
|
{
|
|
if ( !strcmp( token, "endproc" ) ) {
|
|
STAT("ENDPROC");
|
|
Parse(); // skip the function name
|
|
ParseValue(); // locals
|
|
ParseValue(); // arg marshalling
|
|
|
|
// all functions must leave something on the opstack
|
|
instructionCount++;
|
|
EmitByte( &segment[CODESEG], OP_PUSH );
|
|
|
|
instructionCount++;
|
|
EmitByte( &segment[CODESEG], OP_LEAVE );
|
|
EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs );
|
|
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
ASM(ADDRESS)
|
|
{
|
|
int v;
|
|
if ( !strcmp( token, "address" ) ) {
|
|
STAT("ADDRESS");
|
|
Parse();
|
|
v = ParseExpression();
|
|
|
|
/* Addresses are 32 bits wide, and therefore go into data segment. */
|
|
HackToSegment( DATASEG );
|
|
EmitInt( currentSegment, v );
|
|
if( passNumber == 1 && token[ 0 ] == '$' ) // crude test for labels
|
|
EmitInt( &segment[ JTRGSEG ], v );
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(EXPORT)
|
|
{
|
|
if ( !strcmp( token, "export" ) ) {
|
|
STAT("EXPORT");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(IMPORT)
|
|
{
|
|
if ( !strcmp( token, "import" ) ) {
|
|
STAT("IMPORT");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(CODE)
|
|
{
|
|
if ( !strcmp( token, "code" ) ) {
|
|
STAT("CODE");
|
|
currentSegment = &segment[CODESEG];
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(BSS)
|
|
{
|
|
if ( !strcmp( token, "bss" ) ) {
|
|
STAT("BSS");
|
|
currentSegment = &segment[BSSSEG];
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(DATA)
|
|
{
|
|
if ( !strcmp( token, "data" ) ) {
|
|
STAT("DATA");
|
|
currentSegment = &segment[DATASEG];
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(LIT)
|
|
{
|
|
if ( !strcmp( token, "lit" ) ) {
|
|
STAT("LIT");
|
|
currentSegment = &segment[LITSEG];
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(LINE)
|
|
{
|
|
if ( !strcmp( token, "line" ) ) {
|
|
STAT("LINE");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(FILE)
|
|
{
|
|
if ( !strcmp( token, "file" ) ) {
|
|
STAT("FILE");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(EQU)
|
|
{
|
|
char name[1024];
|
|
if ( !strcmp( token, "equ" ) ) {
|
|
STAT("EQU");
|
|
Parse();
|
|
strcpy( name, token );
|
|
Parse();
|
|
DefineSymbol( name, atoiNoCap(token) );
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(ALIGN)
|
|
{
|
|
int v;
|
|
if ( !strcmp( token, "align" ) ) {
|
|
STAT("ALIGN");
|
|
v = ParseValue();
|
|
currentSegment->imageUsed = (currentSegment->imageUsed + v - 1 ) & ~( v - 1 );
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(SKIP)
|
|
{
|
|
int v;
|
|
if ( !strcmp( token, "skip" ) ) {
|
|
STAT("SKIP");
|
|
v = ParseValue();
|
|
currentSegment->imageUsed += v;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ASM(BYTE)
|
|
{
|
|
int i, v, v2;
|
|
if ( !strcmp( token, "byte" ) ) {
|
|
STAT("BYTE");
|
|
v = ParseValue();
|
|
v2 = ParseValue();
|
|
|
|
if ( v == 1 ) {
|
|
/* Character (1-byte) values go into lit(eral) segment. */
|
|
HackToSegment( LITSEG );
|
|
} else if ( v == 4 ) {
|
|
/* 32-bit (4-byte) values go into data segment. */
|
|
HackToSegment( DATASEG );
|
|
} else if ( v == 2 ) {
|
|
/* and 16-bit (2-byte) values will cause q3asm to barf. */
|
|
CodeError( "16 bit initialized data not supported" );
|
|
}
|
|
|
|
// emit little endien
|
|
for ( i = 0 ; i < v ; i++ ) {
|
|
EmitByte( currentSegment, (v2 & 0xFF) ); /* paranoid ANDing -PH */
|
|
v2 >>= 8;
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// code labels are emitted as instruction counts, not byte offsets,
|
|
// because the physical size of the code will change with
|
|
// different run time compilers and we want to minimize the
|
|
// size of the required translation table
|
|
ASM(LABEL)
|
|
{
|
|
if ( !strncmp( token, "LABEL", 5 ) ) {
|
|
STAT("LABEL");
|
|
Parse();
|
|
if ( currentSegment == &segment[CODESEG] ) {
|
|
DefineSymbol( token, instructionCount );
|
|
} else {
|
|
DefineSymbol( token, currentSegment->imageUsed );
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
==============
|
|
AssembleLine
|
|
|
|
==============
|
|
*/
|
|
static void AssembleLine( void ) {
|
|
hashchain_t *hc;
|
|
sourceOps_t *op;
|
|
int i;
|
|
int hash;
|
|
|
|
Parse();
|
|
if ( !token[0] ) {
|
|
return;
|
|
}
|
|
|
|
hash = HashString( token );
|
|
|
|
/*
|
|
Opcode search using hash table.
|
|
Since the opcodes stays mostly fixed, this may benefit even more from a tree.
|
|
Always with the tree :)
|
|
-PH
|
|
*/
|
|
for (hc = hashtable_get(optable, hash); hc; hc = hc->next) {
|
|
op = (sourceOps_t*)(hc->data);
|
|
i = op - sourceOps;
|
|
if ((hash == opcodesHash[i]) && (!strcmp(token, op->name))) {
|
|
int opcode;
|
|
int expression;
|
|
|
|
if ( op->opcode == OP_UNDEF ) {
|
|
CodeError( "Undefined opcode: %s\n", token );
|
|
}
|
|
if ( op->opcode == OP_IGNORE ) {
|
|
return; // we ignore most conversions
|
|
}
|
|
|
|
// sign extensions need to check next parm
|
|
opcode = op->opcode;
|
|
if ( opcode == OP_SEX8 ) {
|
|
Parse();
|
|
if ( token[0] == '1' ) {
|
|
opcode = OP_SEX8;
|
|
} else if ( token[0] == '2' ) {
|
|
opcode = OP_SEX16;
|
|
} else {
|
|
CodeError( "Bad sign extension: %s\n", token );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check for expression
|
|
Parse();
|
|
if ( token[0] && op->opcode != OP_CVIF
|
|
&& op->opcode != OP_CVFI ) {
|
|
expression = ParseExpression();
|
|
|
|
// code like this can generate non-dword block copies:
|
|
// auto char buf[2] = " ";
|
|
// we are just going to round up. This might conceivably
|
|
// be incorrect if other initialized chars follow.
|
|
if ( opcode == OP_BLOCK_COPY ) {
|
|
expression = ( expression + 3 ) & ~3;
|
|
}
|
|
|
|
EmitByte( &segment[CODESEG], opcode );
|
|
EmitInt( &segment[CODESEG], expression );
|
|
} else {
|
|
EmitByte( &segment[CODESEG], opcode );
|
|
}
|
|
|
|
instructionCount++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* This falls through if an assembly opcode is not found. -PH */
|
|
|
|
/* The following should be sorted in sequence of statistical frequency, most frequent first. -PH */
|
|
/*
|
|
Empirical frequency statistics from FI 2001.01.23:
|
|
109892 STAT ADDRL
|
|
72188 STAT BYTE
|
|
51150 STAT LINE
|
|
50906 STAT ARG
|
|
43704 STAT IMPORT
|
|
34902 STAT LABEL
|
|
32066 STAT ADDRF
|
|
23704 STAT CALL
|
|
7720 STAT POP
|
|
7256 STAT RET
|
|
5198 STAT ALIGN
|
|
3292 STAT EXPORT
|
|
2878 STAT PROC
|
|
2878 STAT ENDPROC
|
|
2812 STAT ADDRESS
|
|
738 STAT SKIP
|
|
374 STAT EQU
|
|
280 STAT CODE
|
|
176 STAT LIT
|
|
102 STAT FILE
|
|
100 STAT BSS
|
|
68 STAT DATA
|
|
|
|
-PH
|
|
*/
|
|
|
|
#undef ASM
|
|
#define ASM(O) if (TryAssemble##O ()) return;
|
|
|
|
ASM(ADDRL)
|
|
ASM(BYTE)
|
|
ASM(LINE)
|
|
ASM(ARG)
|
|
ASM(IMPORT)
|
|
ASM(LABEL)
|
|
ASM(ADDRF)
|
|
ASM(CALL)
|
|
ASM(POP)
|
|
ASM(RET)
|
|
ASM(ALIGN)
|
|
ASM(EXPORT)
|
|
ASM(PROC)
|
|
ASM(ENDPROC)
|
|
ASM(ADDRESS)
|
|
ASM(SKIP)
|
|
ASM(EQU)
|
|
ASM(CODE)
|
|
ASM(LIT)
|
|
ASM(FILE)
|
|
ASM(BSS)
|
|
ASM(DATA)
|
|
|
|
CodeError( "Unknown token: %s\n", token );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
InitTables
|
|
==============
|
|
*/
|
|
void InitTables( void ) {
|
|
int i;
|
|
|
|
symtable = hashtable_new(symtablelen);
|
|
optable = hashtable_new(100); /* There's hardly 100 opcodes anyway. */
|
|
|
|
for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) {
|
|
opcodesHash[i] = HashString( sourceOps[i].name );
|
|
hashtable_add(optable, opcodesHash[i], sourceOps + i);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
WriteMapFile
|
|
==============
|
|
*/
|
|
static void WriteMapFile( void ) {
|
|
FILE *f;
|
|
symbol_t *s;
|
|
char imageName[MAX_OS_PATH];
|
|
int seg;
|
|
|
|
strcpy( imageName, outputFilename );
|
|
StripExtension( imageName );
|
|
strcat( imageName, ".map" );
|
|
|
|
report( "Writing %s...\n", imageName );
|
|
|
|
f = SafeOpenWrite( imageName );
|
|
for ( seg = CODESEG ; seg <= BSSSEG ; seg++ ) {
|
|
for ( s = symbols ; s ; s = s->next ) {
|
|
if ( s->name[0] == '$' ) {
|
|
continue; // skip locals
|
|
}
|
|
if ( &segment[seg] != s->segment ) {
|
|
continue;
|
|
}
|
|
fprintf( f, "%i %8x %s\n", seg, s->value, s->name );
|
|
}
|
|
}
|
|
fclose( f );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
WriteVmFile
|
|
===============
|
|
*/
|
|
static void WriteVmFile( void ) {
|
|
char imageName[MAX_OS_PATH];
|
|
vmHeader_t header;
|
|
FILE *f;
|
|
int headerSize;
|
|
|
|
report( "%i total errors\n", errorCount );
|
|
|
|
strcpy( imageName, outputFilename );
|
|
StripExtension( imageName );
|
|
strcat( imageName, ".qvm" );
|
|
|
|
remove( imageName );
|
|
|
|
report( "code segment: %7i\n", segment[CODESEG].imageUsed );
|
|
report( "data segment: %7i\n", segment[DATASEG].imageUsed );
|
|
report( "lit segment: %7i\n", segment[LITSEG].imageUsed );
|
|
report( "bss segment: %7i\n", segment[BSSSEG].imageUsed );
|
|
report( "instruction count: %i\n", instructionCount );
|
|
|
|
if ( errorCount != 0 ) {
|
|
report( "Not writing a file due to errors\n" );
|
|
return;
|
|
}
|
|
|
|
if( !options.vanillaQ3Compatibility ) {
|
|
header.vmMagic = VM_MAGIC_VER2;
|
|
headerSize = sizeof( header );
|
|
} else {
|
|
header.vmMagic = VM_MAGIC;
|
|
|
|
// Don't write the VM_MAGIC_VER2 bits when maintaining 1.32b compatibility.
|
|
// (I know this isn't strictly correct due to padding, but then platforms
|
|
// that pad wouldn't be able to write a correct header anyway). Note: if
|
|
// vmHeader_t changes, this needs to be adjusted too.
|
|
headerSize = sizeof( header ) - sizeof( header.jtrgLength );
|
|
}
|
|
|
|
header.instructionCount = instructionCount;
|
|
header.codeOffset = headerSize;
|
|
header.codeLength = segment[CODESEG].imageUsed;
|
|
header.dataOffset = header.codeOffset + segment[CODESEG].imageUsed;
|
|
header.dataLength = segment[DATASEG].imageUsed;
|
|
header.litLength = segment[LITSEG].imageUsed;
|
|
header.bssLength = segment[BSSSEG].imageUsed;
|
|
header.jtrgLength = segment[JTRGSEG].imageUsed;
|
|
|
|
report( "Writing to %s\n", imageName );
|
|
|
|
#ifdef Q3_BIG_ENDIAN
|
|
{
|
|
int i;
|
|
|
|
// byte swap the header
|
|
for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) {
|
|
((int *)&header)[i] = LittleLong( ((int *)&header)[i] );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
CreatePath( imageName );
|
|
f = SafeOpenWrite( imageName );
|
|
SafeWrite( f, &header, headerSize );
|
|
SafeWrite( f, &segment[CODESEG].image, segment[CODESEG].imageUsed );
|
|
SafeWrite( f, &segment[DATASEG].image, segment[DATASEG].imageUsed );
|
|
SafeWrite( f, &segment[LITSEG].image, segment[LITSEG].imageUsed );
|
|
|
|
if( !options.vanillaQ3Compatibility ) {
|
|
SafeWrite( f, &segment[JTRGSEG].image, segment[JTRGSEG].imageUsed );
|
|
}
|
|
|
|
fclose( f );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Assemble
|
|
===============
|
|
*/
|
|
static void Assemble( void ) {
|
|
int i;
|
|
char filename[MAX_OS_PATH];
|
|
char *ptr;
|
|
|
|
report( "outputFilename: %s\n", outputFilename );
|
|
|
|
for ( i = 0 ; i < numAsmFiles ; i++ ) {
|
|
strcpy( filename, asmFileNames[ i ] );
|
|
DefaultExtension( filename, ".asm" );
|
|
LoadFile( filename, (void **)&asmFiles[i] );
|
|
}
|
|
|
|
// assemble
|
|
for ( passNumber = 0 ; passNumber < 2 ; passNumber++ ) {
|
|
segment[LITSEG].segmentBase = segment[DATASEG].imageUsed;
|
|
segment[BSSSEG].segmentBase = segment[LITSEG].segmentBase + segment[LITSEG].imageUsed;
|
|
segment[JTRGSEG].segmentBase = segment[BSSSEG].segmentBase + segment[BSSSEG].imageUsed;
|
|
for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) {
|
|
segment[i].imageUsed = 0;
|
|
}
|
|
segment[DATASEG].imageUsed = 4; // skip the 0 byte, so NULL pointers are fixed up properly
|
|
instructionCount = 0;
|
|
|
|
for ( i = 0 ; i < numAsmFiles ; i++ ) {
|
|
currentFileIndex = i;
|
|
currentFileName = asmFileNames[ i ];
|
|
currentFileLine = 0;
|
|
report("pass %i: %s\n", passNumber, currentFileName );
|
|
fflush( NULL );
|
|
ptr = asmFiles[i];
|
|
while ( ptr ) {
|
|
ptr = ExtractLine( ptr );
|
|
AssembleLine();
|
|
}
|
|
}
|
|
|
|
// align all segment
|
|
for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) {
|
|
segment[i].imageUsed = (segment[i].imageUsed + 3) & ~3;
|
|
}
|
|
if (passNumber == 0) {
|
|
sort_symbols();
|
|
}
|
|
}
|
|
|
|
// reserve the stack in bss
|
|
DefineSymbol( "_stackStart", segment[BSSSEG].imageUsed );
|
|
segment[BSSSEG].imageUsed += stackSize;
|
|
DefineSymbol( "_stackEnd", segment[BSSSEG].imageUsed );
|
|
|
|
// write the image
|
|
WriteVmFile();
|
|
|
|
// write the map file even if there were errors
|
|
if( options.writeMapFile ) {
|
|
WriteMapFile();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
ParseOptionFile
|
|
|
|
=============
|
|
*/
|
|
static void ParseOptionFile( const char *filename ) {
|
|
char expanded[MAX_OS_PATH];
|
|
char *text, *text_p;
|
|
|
|
strcpy( expanded, filename );
|
|
DefaultExtension( expanded, ".q3asm" );
|
|
LoadFile( expanded, (void **)&text );
|
|
if ( !text ) {
|
|
return;
|
|
}
|
|
|
|
text_p = text;
|
|
|
|
while( ( text_p = COM_Parse( text_p ) ) != 0 ) {
|
|
if ( !strcmp( com_token, "-o" ) ) {
|
|
// allow output override in option file
|
|
text_p = COM_Parse( text_p );
|
|
if ( text_p ) {
|
|
strcpy( outputFilename, com_token );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
asmFileNames[ numAsmFiles ] = copystring( com_token );
|
|
numAsmFiles++;
|
|
}
|
|
}
|
|
|
|
static void ShowHelp( char *argv0 ) {
|
|
Error("Usage: %s [OPTION]... [FILES]...\n\
|
|
Assemble LCC bytecode assembly to Q3VM bytecode.\n\
|
|
\n\
|
|
-o OUTPUT Write assembled output to file OUTPUT.qvm\n\
|
|
-f LISTFILE Read options and list of files to assemble from LISTFILE.q3asm\n\
|
|
-b BUCKETS Set symbol hash table to BUCKETS buckets\n\
|
|
-m Generate a mapfile for each OUTPUT.qvm\n\
|
|
-v Verbose compilation report\n\
|
|
-vq3 Produce a qvm file compatible with Q3 1.32b\n\
|
|
-h --help -? Show this help\n\
|
|
", argv0);
|
|
}
|
|
|
|
/*
|
|
==============
|
|
main
|
|
==============
|
|
*/
|
|
int main( int argc, char **argv ) {
|
|
int i;
|
|
double start, end;
|
|
|
|
// _chdir( "/quake3/jccode/cgame/lccout" ); // hack for vc profiler
|
|
|
|
if ( argc < 2 ) {
|
|
ShowHelp( argv[0] );
|
|
}
|
|
|
|
start = I_FloatTime ();
|
|
|
|
// default filename is "q3asm"
|
|
strcpy( outputFilename, "q3asm" );
|
|
numAsmFiles = 0;
|
|
|
|
for ( i = 1 ; i < argc ; i++ ) {
|
|
if ( argv[i][0] != '-' ) {
|
|
break;
|
|
}
|
|
if( !strcmp( argv[ i ], "-h" ) ||
|
|
!strcmp( argv[ i ], "--help" ) ||
|
|
!strcmp( argv[ i ], "-?") ) {
|
|
ShowHelp( argv[0] );
|
|
}
|
|
|
|
if ( !strcmp( argv[i], "-o" ) ) {
|
|
if ( i == argc - 1 ) {
|
|
Error( "-o must precede a filename" );
|
|
}
|
|
/* Timbo of Tremulous pointed out -o not working; stock ID q3asm folded in the change. Yay. */
|
|
strcpy( outputFilename, argv[ i+1 ] );
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if ( !strcmp( argv[i], "-f" ) ) {
|
|
if ( i == argc - 1 ) {
|
|
Error( "-f must precede a filename" );
|
|
}
|
|
ParseOptionFile( argv[ i+1 ] );
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(argv[i], "-b")) {
|
|
if (i == argc - 1) {
|
|
Error("-b requires an argument");
|
|
}
|
|
i++;
|
|
symtablelen = atoiNoCap(argv[i]);
|
|
continue;
|
|
}
|
|
|
|
if( !strcmp( argv[ i ], "-v" ) ) {
|
|
/* Verbosity option added by Timbo, 2002.09.14.
|
|
By default (no -v option), q3asm remains silent except for critical errors.
|
|
Verbosity turns on all messages, error or not.
|
|
Motivation: not wanting to scrollback for pages to find asm error.
|
|
*/
|
|
options.verbose = qtrue;
|
|
continue;
|
|
}
|
|
|
|
if( !strcmp( argv[ i ], "-m" ) ) {
|
|
options.writeMapFile = qtrue;
|
|
continue;
|
|
}
|
|
|
|
if( !strcmp( argv[ i ], "-vq3" ) ) {
|
|
options.vanillaQ3Compatibility = qtrue;
|
|
continue;
|
|
}
|
|
|
|
Error( "Unknown option: %s", argv[i] );
|
|
}
|
|
|
|
// the rest of the command line args are asm files
|
|
for ( ; i < argc ; i++ ) {
|
|
asmFileNames[ numAsmFiles ] = copystring( argv[ i ] );
|
|
numAsmFiles++;
|
|
}
|
|
// In some case it Segfault without this check
|
|
if ( numAsmFiles == 0 ) {
|
|
Error( "No file to assemble" );
|
|
}
|
|
|
|
InitTables();
|
|
Assemble();
|
|
|
|
{
|
|
symbol_t *s;
|
|
|
|
for ( i = 0, s = symbols ; s ; s = s->next, i++ ) /* nop */ ;
|
|
|
|
if (options.verbose)
|
|
{
|
|
report("%d symbols defined\n", i);
|
|
hashtable_stats(symtable);
|
|
hashtable_stats(optable);
|
|
}
|
|
}
|
|
|
|
end = I_FloatTime ();
|
|
report ("%5.0f seconds elapsed\n", end-start);
|
|
|
|
return errorCount;
|
|
}
|
|
|