From 952c5c128f9efaea89d41d882c4ea3ade7df4591 Mon Sep 17 00:00:00 2001 From: zakk Date: Fri, 26 Aug 2005 04:48:05 +0000 Subject: Itsa me, quake3io! git-svn-id: svn://svn.icculus.org/quake3/trunk@2 edf5b092-35ff-0310-97b2-ce42778d08ea --- q3asm/q3asm.c | 1047 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1047 insertions(+) create mode 100755 q3asm/q3asm.c (limited to 'q3asm/q3asm.c') 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] or q3asm -f \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; +} + -- cgit v1.2.3