--- q3asm.c.orig	2002-10-28 11:52:44.000000000 -0800
+++ q3asm.c	2006-09-17 12:30:25.000000000 -0700
@@ -6,6 +6,16 @@
 /* MSVC-ism fix. */
 #define atoi(s) strtoul(s,NULL,10)
 
+/* TODO:
+fails miserably on hash collision.
+*/
+
+/* 19079 total symbols in FI, 2002 Jan 23 */
+#define Q3ASM_TURBO
+#ifdef Q3ASM_TURBO
+#define MangleSymbol(dest,src) sprintf(dest, ":%d:_%s", currentFileIndex, src)
+#endif /* Q3ASM_TURBO */
+
 char	outputFilename[MAX_OS_PATH];
 
 // the zero page size is just used for detecting run time faults
@@ -128,6 +138,26 @@
 	int		value;
 } symbol_t;
 
+#ifdef Q3ASM_TURBO
+/* Tree. */
+typedef struct treenode_s {
+  int tag;  /* balancing info. */
+  void *data;  /* payload. */
+  struct treenode_s *left;
+  struct treenode_s *right;
+} treenode_t;
+
+treenode_t *symtree;
+treenode_t *optree;
+treenode_t *linktree;
+
+typedef struct linkentry_s {
+  int hash;
+  char *from_name;
+  char *to_name;
+} linkentry_t;
+
+#endif /* Q3ASM_TURBO */
 
 segment_t	segment[NUM_SEGMENTS];
 segment_t	*currentSegment;
@@ -136,9 +166,10 @@
 
 int		numSymbols;
 int		errorCount;
+qboolean  q3asmVerbose = qfalse;
 
 symbol_t	*symbols;
-symbol_t	*lastSymbol;
+symbol_t	*lastSymbol = 0;  /* Most recent symbol defined. */
 
 
 #define	MAX_ASM_FILES	256
@@ -170,6 +201,9 @@
 typedef struct {
 	char	*name;
 	int		opcode;
+#ifdef Q3ASM_TURBO
+	int		hash;
+#endif /* Q3ASM_TURBO */
 } sourceOps_t;
 
 sourceOps_t		sourceOps[] = {
@@ -181,11 +215,516 @@
 int		opcodesHash[ NUM_SOURCE_OPS ];
 
 
+#ifdef Q3ASM_TURBO
+
+int
+vreport (const char* fmt, va_list vp)
+{
+  if (q3asmVerbose != qtrue)
+      return 0;
+  return vprintf(fmt, vp);
+}
+
+int
+report (const char *fmt, ...)
+{
+  va_list va;
+  int retval;
+
+  va_start(va, fmt);
+  retval = vreport(fmt, va);
+  va_end(va);
+  return retval;
+}
+
+
+
+/* Comparator function for quicksorting. */
+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
+*/
+void
+sort_symbols ()
+{
+  int i, elems;
+  symbol_t *s;
+  symbol_t **symlist;
+
+//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. */
+}
+
+
+
+/* AA tree, aka Right-biased R-B tree */
+/* Implementation from "Algorithms, Data Structres, and Problem Solving with C++" by Mark Allen Weiss. */
+/* FFS, I hate C++. */
+
+/* Comparator function for aatree_insert() */
+int
+symtree_lessthan (void *x, void *y)
+{
+  symbol_t *a, *b;
+  a = (symbol_t*)x;
+  b = (symbol_t*)y;
+  if (a->hash < b->hash) return 1;
+  return 0;
+}
+
+/* Comparator function for aatree_insert() */
+int
+optree_lessthan (void *x, void *y)
+{
+  sourceOps_t *a, *b;
+//  int a0, b0;
+  a = (sourceOps_t*)x;
+  b = (sourceOps_t*)y;
+//  a0 = opcodesHash[a->opcode];
+//  b0 = opcodesHash[b->opcode];
+//  if (a->opcode < b->opcode) return 1;
+  if (a->hash < b->hash) return 1;
+//  if (a0 < b0) return 1;
+  return 0;
+}
+
+int
+linktree_lessthan (void *x, void *y)
+{
+  linkentry_t *a, *b;
+  a = (linkentry_t *)x;
+  b = (linkentry_t *)y;
+  if (a->hash < b->hash) return 1;
+  return 0;
+}
+
+/* Conjure up a new tree node. */
+treenode_t *
+treenode_new ()
+{
+  treenode_t *retval;
+
+  retval = malloc(sizeof(treenode_t));
+  memset(retval, 0, sizeof(retval));
+  retval->tag = 0;
+  retval->data = NULL;
+  retval->left = NULL;
+  retval->right = NULL;
+  return retval;
+}
+
+/* Count total nodes in tree. */
+int
+treenode_count (treenode_t *node)
+{
+  if (!node)
+      return 0;
+  return 1 + treenode_count(node->left) + treenode_count(node->right);
+}
+
+/* Count height of tree. */
+int
+treenode_height (treenode_t *node)
+{
+  int leftheight, rightheight;
+  if (!node) return 0;
+  leftheight = treenode_height(node->left);
+  rightheight = treenode_height(node->right);
+  if (leftheight > rightheight)
+      return (1 + leftheight);
+  else
+      return (1 + rightheight);
+}
+
+treenode_t *
+treenode_rotate_left (treenode_t *node)
+{
+  treenode_t *temp;
+
+  temp = node->left;
+  node->left = temp->right;
+  temp->right = node;
+  return temp;
+}
+
+treenode_t *
+treenode_rotate_right (treenode_t *node)
+{
+  treenode_t *temp;
+
+  temp = node->right;
+  node->right = temp->left;
+  temp->left = node;
+  return temp;
+}
+
+treenode_t *
+aatree_skew (treenode_t *node)
+{
+  if ((!node) || (!node->left))
+      return node;
+  if (node->left->tag == node->tag)
+    {
+      node = treenode_rotate_left(node);
+    }
+  return node;
+}
+
+treenode_t *
+aatree_split (treenode_t *node)
+{
+  if ((!node) || (!node->right) || (!node->right->right))
+      return node;
+  if (node->right->right->tag == node->tag)
+    {
+      node = treenode_rotate_right(node);
+      node->tag++;
+    }
+  return node;
+}
+
+void
+symtree_subdump (void *x)
+{
+  symbol_t *sym;
+  sym = (symbol_t*)x;
+  printf(" %08X/[%s] ", sym->hash, sym->name);
+}
+
+void
+optree_subdump (void *x)
+{
+  sourceOps_t *op;
+  op = (sourceOps_t*)x;
+//  printf(" %08X/(%d)[%s] ", opcodesHash[op->opcode], op->opcode, op->name);
+  printf(" %08X/(%d)[%s] ", op->hash, op->opcode, op->name);
+}
+
+void
+linktree_subdump (void *x)
+{
+  linkentry_t *link;
+  link = (linkentry_t*)x;
+  printf(" %08X/[%s]=>[%s]) ", link->hash, link->from_name, link->to_name);
+}
+
+void
+aatree_dump (treenode_t *node, void (*subdump)(void*))
+{
+  printf("(");
+  if (node->left)
+    {
+      printf("\n");
+      aatree_dump(node->left, subdump);
+    }
+  if (subdump)
+    {
+      subdump(node->data);
+    }
+  else
+    {
+      printf(" %p ", node->data);
+    }
+  if (node->right)
+    {
+      aatree_dump(node->right, subdump);
+    }
+}
+
+treenode_t *
+aatree_insert (treenode_t *node, void *data, int (lessthan)(void*, void*))
+{
+  treenode_t *retval;
+  if (node == NULL)
+    {
+      retval = treenode_new();
+      retval->data = data;
+      retval->tag = 1; /* is leaf node. */
+      return retval;
+    }
+  if (lessthan(data, node->data))
+    {
+      node->left = aatree_insert(node->left, data, lessthan);
+    }
+  else
+    {
+      node->right = aatree_insert(node->right, data, lessthan);
+    }
+  node = aatree_skew(node);
+  node = aatree_split(node);
+  return node;
+}
+
+void
+aatree_stats (treenode_t *node, char *treename)
+{
+  if (q3asmVerbose) {
+    report("Stats for %s tree: %d nodes, tree height of %d\n", treename, treenode_count(node), treenode_height(node));
+  }
+}
+
+
+
+
+treenode_t *
+symtree_insert (treenode_t *node, symbol_t *symbol)
+{
+//  report("Inserting symbol '%s'\n", symbol->name);
+  return aatree_insert(node, (void*)symbol, symtree_lessthan);
+}
+
+treenode_t *
+symtree_remove (treenode_t *node, symbol_t *symbol)
+{
+  /* No removal? */
+  return node;
+}
+
+symbol_t *
+symtree_find (treenode_t *node, int targethash, char *symname)
+{
+  symbol_t *sym;
+
+  if (!node) return NULL;
+  sym = ((symbol_t*)(node->data));
+
+
+#if 0
+  if (targethash < sym->hash)
+    {
+      return symtree_find (node->left, targethash, symname);
+    }
+  else if ((targethash == sym->hash) && (0 == strcmp(symname, sym->name)))
+    {
+      return sym;
+    }
+  else
+    {
+      return symtree_find (node->right, targethash, symname);
+    }
+#else /* 0 */
+  if (targethash == sym->hash)
+    {
+      //Exhaustive search either branch.
+      if (0 == strcmp(symname, sym->name))
+        {
+          return sym;
+        }
+      else
+        {
+          symbol_t *temp;
+          if ((temp = symtree_find(node->left, targethash, symname)))
+              return temp;
+          return symtree_find(node->right, targethash, symname);
+        }
+    }
+  else if (targethash < sym->hash)
+    {
+      return symtree_find (node->left, targethash, symname);
+    }
+  else if (targethash > sym->hash)
+    {
+      return symtree_find (node->right, targethash, symname);
+    }
+  return 0;
+#endif /* 0 */
+}
+
+void
+symtree_dump (treenode_t *node)
+{
+  aatree_dump(node, symtree_subdump);
+  printf("\n");
+}
+
+void
+symtree_stats (treenode_t *node)
+{
+  aatree_stats(node, "symbols");
+}
+
+
+treenode_t *
+optree_insert (treenode_t *node, sourceOps_t *op)
+{
+//printf("optree inserting (%d)%08X/[%s]\n", op->opcode, opcodesHash[op->opcode], op->name);
+  return aatree_insert(node, (void*)op, optree_lessthan);
+}
+
+sourceOps_t *
+optree_find (treenode_t *node, int targethash, char *opname)
+{
+  sourceOps_t *op;
+
+  if (!node) return NULL;
+  op = ((sourceOps_t *)(node->data));
+//printf("optree_find: looking for [%s/%08X] in [%s/%08X]\n", opname, targethash, op->name, opcodesHash[op->opcode]);
+#if 0
+//  if (targethash < opcodesHash[op->opcode])
+  if (targethash < op->hash)
+    {
+      return optree_find(node->left, targethash, opname);
+    }
+//  else if ((targethash == opcodesHash[op->opcode]) && (0 == strcmp(op->name, opname)))
+  else if ((targethash == op->hash) && (0 == strcmp(op->name, opname)))
+    {
+      return op;
+    }
+  else
+    {
+      return optree_find(node->right, targethash, opname);
+    }
+#else /* 0 */
+  if (targethash == op->hash)
+    {
+      if (0 == strcmp(op->name, opname))
+        {
+          return op;
+        }
+      else
+        {
+          sourceOps_t *temp;
+          if ((temp = optree_find(node->left, targethash, opname)))
+              return temp;
+          return optree_find(node->right, targethash, opname);
+        }
+    }
+  else if (targethash < op->hash)
+    {
+      return optree_find(node->left, targethash, opname);
+    }
+  else if (targethash > op->hash)
+    {
+      return optree_find(node->right, targethash, opname);
+    }
+  return 0;
+#endif /* 0 */
+}
+
+void
+optree_dump (treenode_t *node)
+{
+  aatree_dump(node, optree_subdump);
+  printf("\n");
+}
+
+void
+optree_stats (treenode_t *node)
+{
+  aatree_stats(node, "opcodes");
+}
+
+
+treenode_t *
+linktree_insert (treenode_t *node, linkentry_t *lent)
+{
+//  report("Exporting '%s'=>'%s'\n", lent->from_name, lent->to_name);
+  return aatree_insert(node, (void*)lent, linktree_lessthan);
+}
+
+linkentry_t *
+linktree_find (treenode_t *node, int targethash, char *name)
+{
+  linkentry_t *lent;
+
+  if (!node) return NULL;
+  lent = ((linkentry_t *)(node->data));
+#if 0
+  if (targethash < lent->hash)
+    {
+      return linktree_find(node->left, targethash, name);
+    }
+  else if ((targethash == lent->hash) && (0 == strcmp(lent->from_name, name)))
+    {
+      return lent;
+    }
+  else
+    {
+      return linktree_find(node->right, targethash, name);
+    }
+#else /* 0 */
+  if (targethash == lent->hash)
+    {
+      if (0 == strcmp(lent->from_name, name))
+        {
+          return lent;
+        }
+      else
+        {
+          linkentry_t *temp;
+          if ((temp = linktree_find(node->left, targethash, name)))
+              return temp;
+          return linktree_find(node->right, targethash, name);
+        }
+    }
+  else if (targethash < lent->hash)
+    {
+      return linktree_find(node->left, targethash, name);
+    }
+  else if (targethash > lent->hash)
+    {
+      return linktree_find(node->right, targethash, name);
+    }
+  return 0;
+#endif /* 0 */
+}
+
+void
+linktree_dump (treenode_t *node)
+{
+  aatree_dump(node, linktree_subdump);
+  printf("\n");
+}
+
+void
+linktree_stats (treenode_t *node)
+{
+  aatree_stats(node, "exports");
+}
+
+
+
+#endif /* Q3ASM_TURBO */
+
 /*
 =============
 HashString
 =============
 */
+#ifndef Q3ASM_TURBO
 int	HashString( char *s ) {
 	int		v = 0;
 
@@ -195,6 +734,31 @@
 	}
 	return v;
 }
+#else /* Q3ASM_TURBO */
+/* Default hash function of Kazlib 1.19, slightly modified. */
+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 abs(acc);
+}
+#endif /* Q3ASM_TURBO */
 
 
 /*
@@ -207,7 +771,7 @@
 
 	errorCount++;
 
-	printf( "%s:%i ", currentFileName, currentFileLine );
+	report( "%s:%i ", currentFileName, currentFileLine );
 
 	va_start( argptr,fmt );
 	vprintf( fmt,argptr );
@@ -251,6 +815,7 @@
 ============
 */
 void DefineSymbol( char *sym, int value ) {
+#ifndef Q3ASM_TURBO
 	symbol_t	*s, *after;
 	char		expanded[MAX_LINE_LENGTH];
 	int			hash;
@@ -258,7 +823,7 @@
 	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
@@ -300,6 +865,65 @@
 	}
 	s->next = after->next;
 	after->next = s;
+#else /* Q3ASM_TURBO */
+	/* Hand optimization by PhaethonH */
+	symbol_t	*s;
+	char		expanded[MAX_LINE_LENGTH];
+	int			hash;
+
+	if ( passNumber == 1 ) {
+		return;
+	}
+
+	if (!Q_stricmp(sym, "vmMain"))
+    	if (value)
+			Error( "vmMain must be the first symbol in the qvm (got offset %d)\n", value );
+
+#if 0
+	// add the file prefix to local symbols to guarantee unique
+	if ( sym[0] == '$' ) {
+		sprintf( expanded, "%s_%i", sym, currentFileIndex );
+		sym = expanded;
+	}
+#else /* 0 */
+	// localize everything, export selectively.
+	if (sym[0] != ':') {
+		MangleSymbol(expanded, sym);
+		sym = expanded;
+	}
+#endif /* 0 */
+
+	hash = HashString( sym );
+
+	if (symtree_find(symtree, 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;
+
+	symtree = symtree_insert(symtree, 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;
+	}
+#endif /* Q3ASM_TURBO */
 }
 
 
@@ -311,210 +935,776 @@
 ============
 */
 int LookupSymbol( char *sym ) {
+#ifndef Q3ASM_TURBO
+	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;
+#else /* Q3ASM_TURBO */
 	symbol_t	*s;
 	char		expanded[MAX_LINE_LENGTH];
 	int			hash;
+	linkentry_t	*lent;
 
 	if ( passNumber == 0 ) {
 		return 0;
 	}
 
+#if 0
 	// add the file prefix to local symbols to guarantee unique
 	if ( sym[0] == '$' ) {
 		sprintf( expanded, "%s_%i", sym, currentFileIndex );
 		sym = expanded;
 	}
+#else /* 0 */
+	if (sym[0] != ':') {
+//printf("Mangling global-looking name in %d: %s\n", currentFileIndex, sym);
+		MangleSymbol(expanded, sym);
+		sym = expanded;
+	}
+
+	/* Resolve for imported symbol. */
+	while ((lent = linktree_find(linktree, HashString(sym), sym))) {
+		sym = lent->to_name;
+//printf("Resolved imported name: %s\n", sym);
+	}
+#endif /* 0 */
+
+	hash = HashString( sym );
+
+/*
+  Hand optimization by PhaethonH
+ -PH
+*/
+	s = symtree_find(symtree, hash, sym);
+	if (s) {
+		return s->segment->segmentBase + s->value;
+	}
+
+	CodeError( "ERROR: symbol %s undefined (maybe static?)\n", sym );
+	passNumber = 0;
+	DefineSymbol( sym, 0 );	// so more errors aren't printed
+	passNumber = 1;
+	return 0;
+#endif /* Q3ASM_TURBO */
+}
+
+
+/*
+==============
+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 ) {
+#ifndef Q3ASM_TURBO
+	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;
+#else /* Q3ASM_TURBO */
+/* 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;
+#endif /* Q3ASM_TURBO */
+}
+
+
+/*
+==============
+Parse
+
+Parse a token out of linebuffer
+==============
+*/
+qboolean Parse( void ) {
+#ifndef Q3ASM_TURBO
+	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;
+#else /* Q3ASM_TURBO */
+	/* 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;
+#endif /* Q3ASM_TURBO */
+}
+
+
+/*
+==============
+ParseValue
+==============
+*/
+int	ParseValue( void ) {
+	Parse();
+	return atoi( token );
+}
+
+
+/*
+==============
+ParseExpression
+==============
+*/
+int	ParseExpression(void) {
+#ifndef Q3ASM_TURBO
+	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;
+#else /* Q3ASM_TURBO */
+	/* 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 = atoi(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 += atoi(sym);
+				break;
+			case '-':
+				v -= atoi(sym);
+				break;
+		}
+
+		i = j;
+	}
+
+	return v;
+#endif /* Q3ASM_TURBO */
+}
+
+
+/*
+==============
+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;
+	}
+}
+
+
+
+
+
+
+
+#ifdef Q3ASM_TURBO
+
+//#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)
+{
+	int		v, v2;
+	if ( !strcmp( token, "endproc" ) ) {
+STAT("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 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 );
+		return 1;
+	}
+	return 0;
+}
+
+ASM(EXPORT)
+{
+	linkentry_t *lent;
+	char name[1024];
+
+	if ( !strcmp( token, "export" ) ) {
+STAT("EXPORT");
+		if (passNumber > 0) return 1;
+		Parse();
+		strcpy(name, token);
+//printf("Exporting :%d:_%s as %s\n", currentFileIndex, name, name);
+		if ((lent = linktree_find(linktree, HashString(name), name))) {
+			CodeError("ERROR: Symbol %s already exported as %s\n", lent->from_name, lent->to_name);
+		} else {
+			lent = malloc(sizeof(linkentry_t));
+			lent->from_name = strdup(name);
+			MangleSymbol(name, lent->from_name);
+			lent->to_name = strdup(name);
+			lent->hash = HashString(lent->from_name);
+			linktree = linktree_insert(linktree, lent);
+		}
+		return 1;
+	}
+	return 0;
+}
+
+ASM(IMPORT)
+{
+	linkentry_t *lent;
+	char name[1024];
+
+	if ( !strcmp( token, "import" ) ) {
+STAT("IMPORT");
+		if (passNumber > 0) return 1;
+		Parse();
+		strcpy(name, token);
+//printf("Importing %s as :%d:_%s\n", name, currentFileIndex, name);
+		lent = malloc(sizeof(linkentry_t));
+		lent->to_name = strdup(name);
+		MangleSymbol(name, lent->to_name);
+		lent->from_name = strdup(name);
+		lent->hash = HashString(lent->from_name);
+		linktree = linktree_insert(linktree, lent);
+		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;
+}
 
-	hash = HashString( sym );
-	for ( s = symbols ; s ; s = s->next ) {
-		if ( hash == s->hash && !strcmp( sym, s->name ) ) {
-			return s->segment->segmentBase + s->value;
-		}
+ASM(LIT)
+{
+	if ( !strcmp( token, "lit" ) ) {
+STAT("LIT");
+		currentSegment = &segment[LITSEG];
+		return 1;
 	}
+	return 0;
+}
 
-	CodeError( "ERROR: symbol %s undefined\n", sym );
-	passNumber = 0;
-	DefineSymbol( sym, 0 );	// so more errors aren't printed
-	passNumber = 1;
+ASM(LINE)
+{
+	if ( !strcmp( token, "line" ) ) {
+STAT("LINE");
+		return 1;
+	}
 	return 0;
 }
 
+ASM(FILE)
+{
+	if ( !strcmp( token, "file" ) ) {
+STAT("FILE");
+		return 1;
+	}
+	return 0;
+}
 
-/*
-==============
-ExtractLine
+ASM(EQU)
+{
+	linkentry_t *lent;
+	char	name[1024];
 
-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;
+	if ( !strcmp( token, "equ" ) ) {
+STAT("EQU");
+		Parse();
+		strcpy( name, token );
+		Parse();
+		DefineSymbol( name, atoi(token) );
 
-	currentFileLine++;
-	lineParseOffset = 0;
-	token[0] = 0;
+		/* export. */
+		lent = malloc(sizeof(linkentry_t));
+		lent->from_name = strdup(name);
+		MangleSymbol(name, lent->from_name);
+		lent->to_name = strdup(name);
+		lent->hash = HashString(lent->from_name);
+		linktree = linktree_insert(linktree, lent);
 
-	if ( data[0] == 0 ) {
-		lineBuffer[0] = 0;
-		return NULL;
+		return 1;
 	}
+	return 0;
+}
 
-	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;
+ASM(ALIGN)
+{
+	int		v;
+	if ( !strcmp( token, "align" ) ) {
+STAT("ALIGN");
+		v = ParseValue();
+		currentSegment->imageUsed = (currentSegment->imageUsed + v - 1 ) & ~( v - 1 );
+		return 1;
 	}
-	memcpy( lineBuffer, data, i );
-	lineBuffer[i] = 0;
-	data += i;
-	if ( data[0] == '\n' ) {
-		data++;
+	return 0;
+}
+
+ASM(SKIP)
+{
+	int		v;
+	if ( !strcmp( token, "skip" ) ) {
+STAT("SKIP");
+		v = ParseValue();
+		currentSegment->imageUsed += v;
+		return 1;
 	}
-	return data;
+	return 0;
 }
 
+ASM(BYTE)
+{
+	int		i, v, v2;
+	if ( !strcmp( token, "byte" ) ) {
+STAT("BYTE");
+		v = ParseValue();
+		v2 = ParseValue();
 
-/*
-==============
-Parse
+		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" );
+		}
 
-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;
+		// emit little endien
+		for ( i = 0 ; i < v ; i++ ) {
+			EmitByte( currentSegment, (v2 & 0xFF) ); /* paranoid ANDing  -PH */
+			v2 >>= 8;
 		}
-		lineParseOffset++;
+		return 1;
 	}
+	return 0;
+}
 
-	// skip ; comments
-	c = lineBuffer[ lineParseOffset ];
-	if ( c == ';' ) {
-		return qfalse;
+	// 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
+ASM(LABEL)
+{
+	if ( !strncmp( token, "LABEL", 5 ) ) {
+STAT("LABEL");
+		Parse();
+		if ( currentSegment == &segment[CODESEG] ) {
+			DefineSymbol( token, instructionCount );
+		} else {
+			DefineSymbol( token, currentSegment->imageUsed );
+		}
+		return 1;
 	}
-	
-
-	// parse a regular word
-	do {
-		token[len] = c;
-		len++;
-		lineParseOffset++;
-		c = lineBuffer[ lineParseOffset ];
-	} while (c>32);
-	
-	token[len] = 0;
-	return qtrue;
+	return 0;
 }
 
 
-/*
-==============
-ParseValue
-==============
-*/
-int	ParseValue( void ) {
-	Parse();
-	return atoi( token );
-}
+
+#endif /* Q3ASM_TURBO */
+
 
 
-/*
-==============
-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;
-	}
-}
 
 /*
 ==============
@@ -523,7 +1713,11 @@
 ==============
 */
 void AssembleLine( void ) {
+#ifndef Q3ASM_TURBO
 	int		v, v2;
+#else /* Q3ASM_TURBO */
+	sourceOps_t *op;
+#endif /* Q3ASM_TURBO */
 	int		i;
 	int		hash;
 
@@ -534,6 +1728,7 @@
 
 	hash = HashString( token );
 
+#ifndef Q3ASM_TURBO
 	for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) {
 		if ( hash == opcodesHash[i] && !strcmp( token, sourceOps[i].name ) ) {
 			int		opcode;
@@ -775,6 +1970,122 @@
 		}
 		return;
 	}
+#else /* Q3ASM_TURBO */
+/*
+  Opcode search using hash table.
+  Since the opcodes stays mostly fixed, this may benefit even more from a tree.
+  Always with the tree :)
+ -PH
+*/
+	i = 0;  /* stops whining. */
+	op = optree_find(optree, hash, token);
+	if (op) {
+		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)
+
+#endif /* Q3ASM_TURBO */
 
 	CodeError( "Unknown token: %s\n", token );
 }
@@ -785,11 +2096,25 @@
 ==============
 */
 void InitTables( void ) {
+#ifndef Q3ASM_TURBO
 	int		i;
 
 	for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) {
 		opcodesHash[i] = HashString( sourceOps[i].name );
 	}
+#else /* Q3ASM_TURBO */
+	int i;
+
+	symtree = NULL;
+	optree = NULL;
+	linktree = NULL;
+
+	for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) {
+		opcodesHash[i] = HashString( sourceOps[i].name );
+		sourceOps[i].hash = opcodesHash[i];
+		optree = optree_insert(optree, sourceOps + i);
+	}
+#endif /* Q3ASM_TURBO */
 }
 
 
@@ -803,15 +2128,20 @@
 	symbol_t	*s;
 	char		imageName[MAX_OS_PATH];
 	int			seg;
+#ifdef Q3ASM_TURBO
+	char		*p;
+#endif /* Q3ASM_TURBO */
 
 	strcpy( imageName, outputFilename );
 	StripExtension( imageName );
 	strcat( imageName, ".map" );
 
-	printf( "Writing %s...\n", imageName );
+	report( "Writing %s...\n", imageName );
+
 	f = SafeOpenWrite( imageName );
 	for ( seg = CODESEG ; seg <= BSSSEG ; seg++ ) {
 		for ( s = symbols ; s ; s = s->next ) {
+#ifndef Q3ASM_TURBO
 			if ( s->name[0] == '$' ) {
 				continue;	// skip locals
 			}
@@ -819,6 +2149,21 @@
 				continue;
 			}
 			fprintf( f, "%i %8x %s\n", seg, s->value, s->name );
+#else /* Q3ASM_TURBO */
+			p = strchr(s->name, '_');
+			if (!p) {
+				p = s->name;
+			} else {
+				p++;
+			}
+			if (p[0] == '$') {
+				continue; /* local.  skip. */
+			}
+			if (&segment[seg] != s->segment) {
+				continue;  /* Belongs to another segment.  skip. */
+			}
+			fprintf(f, "%i %8x %s\n", seg, s->value, p);
+#endif /* Q3ASM_TURBO */
 		}
 	}
 	fclose( f );
@@ -834,20 +2179,22 @@
 	vmHeader_t	header;
 	FILE	*f;
 
-	printf( "%i total errors\n", errorCount );
+	report( "%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 );
+	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 ) {
-		printf( "Not writing a file due to errors\n" );
+		report( "Not writing a file due to errors\n" );
 		return;
 	}
 
@@ -860,7 +2207,7 @@
 	header.litLength = segment[LITSEG].imageUsed;
 	header.bssLength = segment[BSSSEG].imageUsed;
 
-	printf( "Writing to %s\n", imageName );
+	report( "Writing to %s\n", imageName );
 
 	CreatePath( imageName );
 	f = SafeOpenWrite( imageName );
@@ -881,7 +2228,7 @@
 	char	filename[MAX_OS_PATH];
 	char		*ptr;
 
-	printf( "outputFilename: %s\n", outputFilename );
+	report( "outputFilename: %s\n", outputFilename );
 
 	for ( i = 0 ; i < numAsmFiles ; i++ ) {
 		strcpy( filename, asmFileNames[ i ] );
@@ -903,7 +2250,7 @@
 			currentFileIndex = i;
 			currentFileName = asmFileNames[ i ];
 			currentFileLine = 0;
-			printf("pass %i: %s\n", passNumber, currentFileName );
+			report("pass %i: %s\n", passNumber, currentFileName );
 			ptr = asmFiles[i];
 			while ( ptr ) {
 				ptr = ExtractLine( ptr );
@@ -915,6 +2262,11 @@
 		for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) {
 			segment[i].imageUsed = (segment[i].imageUsed + 3) & ~3;
 		}
+#ifdef Q3ASM_TURBO
+		if (passNumber == 0) {
+			sort_symbols();
+		}
+#endif /* Q3ASM_TURBO */
 	}
 
 	// reserve the stack in bss
@@ -976,11 +2328,23 @@
 //	_chdir( "/quake3/jccode/cgame/lccout" );	// hack for vc profiler
 
 	if ( argc < 2 ) {
+#ifndef Q3ASM_TURBO
 		Error( "usage: q3asm [-o output] <files> or q3asm -f <listfile>\n" );
+#else /* Q3ASM_TURBO */
+		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\n\
+    -b BUCKETS     Set symbol hash table to BUCKETS buckets\n\
+", argv[0]);
+#endif /* Q3ASM_TURBO */
 	}
 
 	start = I_FloatTime ();
+#ifndef Q3ASM_TURBO
 	InitTables();
+#endif /* !Q3ASM_TURBO */
 
 	// default filename is "q3asm"
 	strcpy( outputFilename, "q3asm" );
@@ -994,6 +2358,7 @@
 			if ( i == argc - 1 ) {
 				Error( "-o must preceed 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;
@@ -1007,6 +2372,29 @@
 			i++;
 			continue;
 		}
+
+#ifdef Q3ASM_TURBO
+//		if (!strcmp(argv[i], "-b")) {
+//			if (i == argc - 1) {
+//				Error("-b requires an argument");
+//			}
+//			i++;
+//			symtablelen = atoi(argv[i]);
+//			continue;
+//		}
+#endif /* Q3ASM_TURBO */
+
+    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.
+*/
+      q3asmVerbose = qtrue;
+      continue;
+    }
+
 		Error( "Unknown option: %s", argv[i] );
 	}
 
@@ -1016,10 +2404,29 @@
 		numAsmFiles++;
 	}
 
+#ifdef Q3ASM_TURBO
+	InitTables();
+#endif /* Q3ASM_TURBO */
 	Assemble();
 
+#ifdef Q3ASM_TURBO
+	{
+		symbol_t *s;
+
+		for ( i = 0, s = symbols ; s ; s = s->next, i++ ) /* nop */ ;
+
+		if (q3asmVerbose)
+		{
+			report("%d symbols defined\n", i);
+			symtree_stats(symtree);
+			optree_stats(optree);
+			linktree_stats(linktree);
+		}
+	}
+#endif /* Q3ASM_TURBO */
+
 	end = I_FloatTime ();
-	printf ("%5.0f seconds elapsed\n", end-start);
+	report ("%5.0f seconds elapsed\n", end-start);
 
 	return 0;
 }
