aboutsummaryrefslogtreecommitdiffstats
path: root/q3asm/q3asm.c
diff options
context:
space:
mode:
Diffstat (limited to 'q3asm/q3asm.c')
-rwxr-xr-xq3asm/q3asm.c1047
1 files changed, 1047 insertions, 0 deletions
diff --git a/q3asm/q3asm.c b/q3asm/q3asm.c
new file mode 100755
index 0000000..836e427
--- /dev/null
+++ b/q3asm/q3asm.c
@@ -0,0 +1,1047 @@
+/*
+===========================================================================
+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 Foobar; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "cmdlib.h"
+#include "mathlib.h"
+#include "qfiles.h"
+
+/* MSVC-ism fix. */
+#define atoi(s) strtoul(s,NULL,10)
+
+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
+ 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;
+
+
+segment_t segment[NUM_SEGMENTS];
+segment_t *currentSegment;
+
+int passNumber;
+
+int numSymbols;
+int errorCount;
+
+symbol_t *symbols;
+symbol_t *lastSymbol;
+
+
+#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 ];
+
+
+/*
+=============
+HashString
+=============
+*/
+int HashString( char *s ) {
+ int v = 0;
+
+ while ( *s ) {
+ v += *s;
+ s++;
+ }
+ return v;
+}
+
+
+/*
+============
+CodeError
+============
+*/
+void CodeError( char *fmt, ... ) {
+ va_list argptr;
+
+ errorCount++;
+
+ printf( "%s:%i ", currentFileName, currentFileLine );
+
+ va_start( argptr,fmt );
+ vprintf( fmt,argptr );
+ va_end( argptr );
+}
+
+/*
+============
+EmitByte
+============
+*/
+void EmitByte( segment_t *seg, int v ) {
+ if ( seg->imageUsed >= MAX_IMAGE ) {
+ Error( "MAX_IMAGE" );
+ }
+ seg->image[ seg->imageUsed ] = v;
+ seg->imageUsed++;
+}
+
+/*
+============
+EmitInt
+============
+*/
+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
+============
+*/
+void DefineSymbol( char *sym, int value ) {
+ symbol_t *s, *after;
+ char expanded[MAX_LINE_LENGTH];
+ int hash;
+
+ if ( passNumber == 1 ) {
+ return;
+ }
+
+ // TTimo
+ // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=381
+ // as a security, bail out if vmMain entry point is not first
+ if (!Q_stricmp(sym, "vmMain"))
+ if (value)
+ Error( "vmMain must be the first symbol in the qvm (got offset %d)\n", value );
+
+ // add the file prefix to local symbols to guarantee unique
+ if ( sym[0] == '$' ) {
+ sprintf( expanded, "%s_%i", sym, currentFileIndex );
+ sym = expanded;
+ }
+
+ hash = HashString( sym );
+
+ for ( s = symbols ; s ; s = s->next ) {
+ if ( hash == s->hash && !strcmp( sym, s->name ) ) {
+ CodeError( "Multiple definitions for %s\n", sym );
+ return;
+ }
+ }
+
+ s = malloc( sizeof( *s ) );
+ s->name = copystring( sym );
+ s->hash = hash;
+ s->value = value;
+ s->segment = currentSegment;
+
+ lastSymbol = s; /* for the move-to-lit-segment byteswap hack */
+
+ // insert it in order
+ if ( !symbols || s->value < symbols->value ) {
+ s->next = symbols;
+ symbols = s;
+ return;
+ }
+
+ for ( after = symbols ; after->next && after->next->value < value ; after = after->next ) {
+ }
+ s->next = after->next;
+ after->next = s;
+}
+
+
+/*
+============
+LookupSymbol
+
+Symbols can only be evaluated on pass 1
+============
+*/
+int LookupSymbol( char *sym ) {
+ symbol_t *s;
+ char expanded[MAX_LINE_LENGTH];
+ int hash;
+
+ 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 );
+ for ( s = symbols ; s ; s = s->next ) {
+ 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
+===============
+*/
+char *ExtractLine( char *data ) {
+ int i;
+
+ currentFileLine++;
+ lineParseOffset = 0;
+ token[0] = 0;
+
+ if ( data[0] == 0 ) {
+ lineBuffer[0] = 0;
+ return NULL;
+ }
+
+ for ( i = 0 ; i < MAX_LINE_LENGTH ; i++ ) {
+ if ( data[i] == 0 || data[i] == '\n' ) {
+ break;
+ }
+ }
+ if ( i == MAX_LINE_LENGTH ) {
+ CodeError( "MAX_LINE_LENGTH" );
+ return data;
+ }
+ memcpy( lineBuffer, data, i );
+ lineBuffer[i] = 0;
+ data += i;
+ if ( data[0] == '\n' ) {
+ data++;
+ }
+ return data;
+}
+
+
+/*
+==============
+Parse
+
+Parse a token out of linebuffer
+==============
+*/
+qboolean Parse( void ) {
+ int c;
+ int len;
+
+ len = 0;
+ token[0] = 0;
+
+ // skip whitespace
+ while ( lineBuffer[ lineParseOffset ] <= ' ' ) {
+ if ( lineBuffer[ lineParseOffset ] == 0 ) {
+ return qfalse;
+ }
+ lineParseOffset++;
+ }
+
+ // skip ; comments
+ c = lineBuffer[ lineParseOffset ];
+ if ( c == ';' ) {
+ return qfalse;
+ }
+
+
+ // parse a regular word
+ do {
+ token[len] = c;
+ len++;
+ lineParseOffset++;
+ c = lineBuffer[ lineParseOffset ];
+ } while (c>32);
+
+ token[len] = 0;
+ return qtrue;
+}
+
+
+/*
+==============
+ParseValue
+==============
+*/
+int ParseValue( void ) {
+ Parse();
+ return atoi( token );
+}
+
+
+/*
+==============
+ParseExpression
+==============
+*/
+int ParseExpression(void) {
+ int i, j;
+ char sym[MAX_LINE_LENGTH];
+ int v;
+
+ if ( token[0] == '-' ) {
+ i = 1;
+ } else {
+ i = 0;
+ }
+
+ for ( ; i < MAX_LINE_LENGTH ; i++ ) {
+ if ( token[i] == '+' || token[i] == '-' || token[i] == 0 ) {
+ break;
+ }
+ }
+
+ memcpy( sym, token, i );
+ sym[i] = 0;
+
+ if ( ( sym[0] >= '0' && sym[0] <= '9' ) || sym[0] == '-' ) {
+ v = atoi( sym );
+ } else {
+ v = LookupSymbol( sym );
+ }
+
+ // 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;
+
+ if ( token[i] == '+' ) {
+ v += atoi( sym );
+ }
+ if ( token[i] == '-' ) {
+ v -= atoi( sym );
+ }
+ 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 emited 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.
+==============
+*/
+void HackToSegment( segmentName_t seg ) {
+ if ( currentSegment == &segment[seg] ) {
+ return;
+ }
+
+ currentSegment = &segment[seg];
+ if ( passNumber == 0 ) {
+ lastSymbol->segment = currentSegment;
+ lastSymbol->value = currentSegment->imageUsed;
+ }
+}
+
+/*
+==============
+AssembleLine
+
+==============
+*/
+void AssembleLine( void ) {
+ int v, v2;
+ int i;
+ int hash;
+
+ Parse();
+ if ( !token[0] ) {
+ return;
+ }
+
+ hash = HashString( token );
+
+ for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) {
+ if ( hash == opcodesHash[i] && !strcmp( token, sourceOps[i].name ) ) {
+ int opcode;
+ int expression;
+
+ if ( sourceOps[i].opcode == OP_UNDEF ) {
+ CodeError( "Undefined opcode: %s\n", token );
+ }
+ if ( sourceOps[i].opcode == OP_IGNORE ) {
+ return; // we ignore most conversions
+ }
+
+ // sign extensions need to check next parm
+ opcode = sourceOps[i].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] && sourceOps[i].opcode != OP_CVIF
+ && sourceOps[i].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;
+ }
+ }
+
+ // call instructions reset currentArgOffset
+ if ( !strncmp( token, "CALL", 4 ) ) {
+ EmitByte( &segment[CODESEG], OP_CALL );
+ instructionCount++;
+ currentArgOffset = 0;
+ return;
+ }
+
+ // arg is converted to a reversed store
+ if ( !strncmp( token, "ARG", 3 ) ) {
+ EmitByte( &segment[CODESEG], OP_ARG );
+ instructionCount++;
+ if ( 8 + currentArgOffset >= 256 ) {
+ CodeError( "currentArgOffset >= 256" );
+ return;
+ }
+ EmitByte( &segment[CODESEG], 8 + currentArgOffset );
+ currentArgOffset += 4;
+ return;
+ }
+
+ // ret just leaves something on the op stack
+ if ( !strncmp( token, "RET", 3 ) ) {
+ EmitByte( &segment[CODESEG], OP_LEAVE );
+ instructionCount++;
+ EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs );
+ return;
+ }
+
+ // pop is needed to discard the return value of
+ // a function
+ if ( !strncmp( token, "pop", 3 ) ) {
+ EmitByte( &segment[CODESEG], OP_POP );
+ instructionCount++;
+ return;
+ }
+
+ // address of a parameter is converted to OP_LOCAL
+ if ( !strncmp( token, "ADDRF", 5 ) ) {
+ instructionCount++;
+ Parse();
+ v = ParseExpression();
+ v = 16 + currentArgs + currentLocals + v;
+ EmitByte( &segment[CODESEG], OP_LOCAL );
+ EmitInt( &segment[CODESEG], v );
+ return;
+ }
+
+ // address of a local is converted to OP_LOCAL
+ if ( !strncmp( token, "ADDRL", 5 ) ) {
+ instructionCount++;
+ Parse();
+ v = ParseExpression();
+ v = 8 + currentArgs + v;
+ EmitByte( &segment[CODESEG], OP_LOCAL );
+ EmitInt( &segment[CODESEG], v );
+ return;
+ }
+
+ if ( !strcmp( token, "proc" ) ) {
+ char name[1024];
+
+ 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;
+ }
+ if ( !strcmp( token, "endproc" ) ) {
+ Parse(); // skip the function name
+ v = ParseValue(); // locals
+ v2 = 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;
+ }
+
+
+ if ( !strcmp( token, "address" ) ) {
+ Parse();
+ v = ParseExpression();
+
+ HackToSegment( DATASEG );
+ EmitInt( currentSegment, v );
+ return;
+ }
+ if ( !strcmp( token, "export" ) ) {
+ return;
+ }
+ if ( !strcmp( token, "import" ) ) {
+ return;
+ }
+ if ( !strcmp( token, "code" ) ) {
+ currentSegment = &segment[CODESEG];
+ return;
+ }
+ if ( !strcmp( token, "bss" ) ) {
+ currentSegment = &segment[BSSSEG];
+ return;
+ }
+ if ( !strcmp( token, "data" ) ) {
+ currentSegment = &segment[DATASEG];
+ return;
+ }
+ if ( !strcmp( token, "lit" ) ) {
+ currentSegment = &segment[LITSEG];
+ return;
+ }
+ if ( !strcmp( token, "line" ) ) {
+ return;
+ }
+ if ( !strcmp( token, "file" ) ) {
+ return;
+ }
+
+ if ( !strcmp( token, "equ" ) ) {
+ char name[1024];
+
+ Parse();
+ strcpy( name, token );
+ Parse();
+ DefineSymbol( name, atoi(token) );
+ return;
+ }
+
+ if ( !strcmp( token, "align" ) ) {
+ v = ParseValue();
+ currentSegment->imageUsed = (currentSegment->imageUsed + v - 1 ) & ~( v - 1 );
+ return;
+ }
+
+ if ( !strcmp( token, "skip" ) ) {
+ v = ParseValue();
+ currentSegment->imageUsed += v;
+ return;
+ }
+
+ if ( !strcmp( token, "byte" ) ) {
+ v = ParseValue();
+ v2 = ParseValue();
+
+ if ( v == 1 ) {
+ HackToSegment( LITSEG );
+ } else if ( v == 4 ) {
+ HackToSegment( DATASEG );
+ } else if ( v == 2 ) {
+ CodeError( "16 bit initialized data not supported" );
+ }
+
+ // emit little endien
+ for ( i = 0 ; i < v ; i++ ) {
+ EmitByte( currentSegment, v2 );
+ v2 >>= 8;
+ }
+ return;
+ }
+
+ // code labels are emited 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
+ if ( !strncmp( token, "LABEL", 5 ) ) {
+ Parse();
+ if ( currentSegment == &segment[CODESEG] ) {
+ DefineSymbol( token, instructionCount );
+ } else {
+ DefineSymbol( token, currentSegment->imageUsed );
+ }
+ return;
+ }
+
+ CodeError( "Unknown token: %s\n", token );
+}
+
+/*
+==============
+InitTables
+==============
+*/
+void InitTables( void ) {
+ int i;
+
+ for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) {
+ opcodesHash[i] = HashString( sourceOps[i].name );
+ }
+}
+
+
+/*
+==============
+WriteMapFile
+==============
+*/
+void WriteMapFile( void ) {
+ FILE *f;
+ symbol_t *s;
+ char imageName[MAX_OS_PATH];
+ int seg;
+
+ strcpy( imageName, outputFilename );
+ StripExtension( imageName );
+ strcat( imageName, ".map" );
+
+ printf( "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
+===============
+*/
+void WriteVmFile( void ) {
+ char imageName[MAX_OS_PATH];
+ vmHeader_t header;
+ FILE *f;
+
+ printf( "%i total errors\n", errorCount );
+ strcpy( imageName, outputFilename );
+ StripExtension( imageName );
+ strcat( imageName, ".qvm" );
+
+ remove( imageName );
+
+ printf( "code segment: %7i\n", segment[CODESEG].imageUsed );
+ printf( "data segment: %7i\n", segment[DATASEG].imageUsed );
+ printf( "lit segment: %7i\n", segment[LITSEG].imageUsed );
+ printf( "bss segment: %7i\n", segment[BSSSEG].imageUsed );
+ printf( "instruction count: %i\n", instructionCount );
+ if ( errorCount != 0 ) {
+ printf( "Not writing a file due to errors\n" );
+ return;
+ }
+
+ header.vmMagic = VM_MAGIC;
+ header.instructionCount = instructionCount;
+ header.codeOffset = sizeof( header );
+ 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;
+
+ printf( "Writing to %s\n", imageName );
+
+ CreatePath( imageName );
+ f = SafeOpenWrite( imageName );
+ SafeWrite( f, &header, sizeof( header ) );
+ SafeWrite( f, &segment[CODESEG].image, segment[CODESEG].imageUsed );
+ SafeWrite( f, &segment[DATASEG].image, segment[DATASEG].imageUsed );
+ SafeWrite( f, &segment[LITSEG].image, segment[LITSEG].imageUsed );
+ fclose( f );
+}
+
+/*
+===============
+Assemble
+===============
+*/
+void Assemble( void ) {
+ int i;
+ char filename[MAX_OS_PATH];
+ char *ptr;
+
+ printf( "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;
+ 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;
+ printf("pass %i: %s\n", passNumber, currentFileName );
+ 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;
+ }
+ }
+
+ // 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
+ WriteMapFile();
+}
+
+
+/*
+=============
+ParseOptionFile
+
+=============
+*/
+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++;
+ }
+}
+
+/*
+==============
+main
+==============
+*/
+int main( int argc, char **argv ) {
+ int i;
+ double start, end;
+
+// _chdir( "/quake3/jccode/cgame/lccout" ); // hack for vc profiler
+
+ if ( argc < 2 ) {
+ Error( "usage: q3asm [-o output] <files> or q3asm -f <listfile>\n" );
+ }
+
+ start = I_FloatTime ();
+ InitTables();
+
+ // default filename is "q3asm"
+ strcpy( outputFilename, "q3asm" );
+ numAsmFiles = 0;
+
+ for ( i = 1 ; i < argc ; i++ ) {
+ if ( argv[i][0] != '-' ) {
+ break;
+ }
+ if ( !strcmp( argv[i], "-o" ) ) {
+ if ( i == argc - 1 ) {
+ Error( "-o must preceed a filename" );
+ }
+ strcpy( outputFilename, argv[ i+1 ] );
+ i++;
+ continue;
+ }
+
+ if ( !strcmp( argv[i], "-f" ) ) {
+ if ( i == argc - 1 ) {
+ Error( "-f must preceed a filename" );
+ }
+ ParseOptionFile( argv[ i+1 ] );
+ i++;
+ 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++;
+ }
+
+ Assemble();
+
+ end = I_FloatTime ();
+ printf ("%5.0f seconds elapsed\n", end-start);
+
+ return 0;
+}
+