swh:1:snp:687ac8cdbfab3b78b7f301abee5f451127f135fc
Raw File
Tip revision: dbe4ddb10315479fc00086f08e25d968b4b43c49 authored by Travis Bradshaw on 31 January 2012, 19:41:34 UTC
The Quake III Arena sources as originally released under the GPL license on August 20, 2005.
Tip revision: dbe4ddb
q3asm.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 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;
}

back to top