├── notes.txt ├── lib.txt ├── readme.md ├── README.Id ├── Makefile ├── vs2005 ├── q3asm.sln └── q3asm.vcproj ├── qvm.h ├── ops.txt ├── cmdlib.h ├── opstrings.h ├── cmdlib.c ├── q3vm.c └── q3asm.c /notes.txt: -------------------------------------------------------------------------------- 1 | 2 | don't do any parameter conversion (double to float, etc) 3 | 4 | 5 | 6 | Why? 7 | 8 | Security. 9 | Portability. 10 | 11 | It may be more aproachable. 12 | 13 | can still use regular dlls for development purposes 14 | 15 | lcc 16 | q3asm 17 | -------------------------------------------------------------------------------- /lib.txt: -------------------------------------------------------------------------------- 1 | 2 | strlen 3 | strcasecmp 4 | tolower 5 | strcat 6 | strncpy 7 | strcmp 8 | strcpy 9 | strchr 10 | 11 | vsprintf 12 | 13 | memcpy 14 | memset 15 | rand 16 | 17 | atoi 18 | atof 19 | 20 | abs 21 | 22 | floor 23 | fabs 24 | tan 25 | atan 26 | sqrt 27 | log 28 | cos 29 | sin 30 | atan2 31 | 32 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | q3asm is a tool which compiles assembly files (created by q3lcc from game sources) to QVM bytecode 2 | 3 | Major features of this version: 4 | 5 | * fast processing (faster even than q3asm-turbo) 6 | * unreferenced code/data elimination 7 | * proper 'static' keyword support 8 | * external jump targets segment (*.jts) generation for quake3e engine -------------------------------------------------------------------------------- /README.Id: -------------------------------------------------------------------------------- 1 | 2002-10-25 Timothee Besset 2 | If you are looking for a faster version of the q3asm tool, try: 3 | http://www.icculus.org/~phaethon/q3/q3asm-turbo/q3asm-turbo.html 4 | 5 | 2001-10-31 Timothee Besset 6 | updated from the $/source/q3asm code 7 | modified for portability and use with >= 1.31 mod source release 8 | 9 | the cmdlib.c cmdlib.h mathlib.h qfiles.h have been copied from 10 | $/source/common 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # yeah, couldn't do more simple really 2 | 3 | ifeq ($(PLATFORM),mingw32) 4 | BINEXT=.exe 5 | else 6 | BINEXT= 7 | endif 8 | 9 | ifeq ($(PLATFORM),sunos) 10 | INSTALL=ginstall 11 | else 12 | INSTALL=install 13 | endif 14 | 15 | CC=gcc 16 | Q3ASM_CFLAGS=-O2 -Wall -Werror -fno-strict-aliasing 17 | 18 | ifeq ($(PLATFORM),darwin) 19 | LCC_CFLAGS += -DMACOS_X=1 20 | endif 21 | 22 | ifndef USE_CCACHE 23 | USE_CCACHE=0 24 | endif 25 | 26 | ifeq ($(USE_CCACHE),1) 27 | CC := ccache $(CC) 28 | CXX := ccache $(CXX) 29 | endif 30 | 31 | default: q3asm 32 | 33 | q3asm: q3asm.c q3vm.c cmdlib.c 34 | $(CC) $(Q3ASM_CFLAGS) -o $@ $^ 35 | 36 | clean: 37 | rm -f q3asm *~ *.o 38 | 39 | install: default 40 | $(INSTALL) -s -m 0755 q3asm$(BINEXT) ../ 41 | 42 | uninstall: 43 | rm -f ../q3asm$(BINEXT) 44 | -------------------------------------------------------------------------------- /vs2005/q3asm.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 9.00 2 | # Visual C++ Express 2005 3 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "q3asm", "q3asm.vcproj", "{E0D6B319-6F95-4ABA-9BE0-5454CAA5C8CD}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Win32 = Debug|Win32 8 | Release|Win32 = Release|Win32 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {E0D6B319-6F95-4ABA-9BE0-5454CAA5C8CD}.Debug|Win32.ActiveCfg = Debug|Win32 12 | {E0D6B319-6F95-4ABA-9BE0-5454CAA5C8CD}.Debug|Win32.Build.0 = Debug|Win32 13 | {E0D6B319-6F95-4ABA-9BE0-5454CAA5C8CD}.Release|Win32.ActiveCfg = Release|Win32 14 | {E0D6B319-6F95-4ABA-9BE0-5454CAA5C8CD}.Release|Win32.Build.0 = Release|Win32 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /qvm.h: -------------------------------------------------------------------------------- 1 | /* 2 | copied from qfiles.h, should be synced with host qvm format 3 | - made for standalone compilation 4 | */ 5 | 6 | #include "cmdlib.h" 7 | 8 | #define VM_MAGIC 0x12721444 9 | #define VM_MAGIC_VER2 0x12721445 10 | 11 | #define PROGRAM_STACK_SIZE 0x10000 12 | 13 | typedef struct { 14 | int vmMagic; 15 | 16 | int instructionCount; 17 | 18 | int codeOffset; 19 | int codeLength; 20 | 21 | int dataOffset; 22 | int dataLength; 23 | int litLength; // ( dataLength - litLength ) should be byteswapped on load 24 | int bssLength; // zero filled memory appended to datalength 25 | 26 | //!!! below here is VM_MAGIC_VER2 !!! 27 | int jtrgLength; // number of jump table targets 28 | } vmHeader_t; 29 | 30 | 31 | typedef enum { 32 | CODESEG, 33 | DATASEG, // initialized 32 bit data, will be byte swapped 34 | LITSEG, // strings 35 | BSSSEG, // 0 filled 36 | JTRGSEG, // pseudo-segment that contains only jump table targets 37 | NUM_SEGMENTS 38 | } segmentName_t; 39 | 40 | typedef struct { 41 | int value; 42 | byte op; 43 | byte opStack; 44 | unsigned jused:1; 45 | } instruction_t; 46 | 47 | const char *VM_LoadInstructions( const byte *code_pos, int codeLength, int instructionCount, instruction_t *buf ); 48 | const char *VM_CheckInstructions( instruction_t *buf, int instructionCount, int dataLength ); 49 | -------------------------------------------------------------------------------- /ops.txt: -------------------------------------------------------------------------------- 1 | CNSTF, 2 | CNSTI, 3 | CNSTP, 4 | CNSTU, 5 | 6 | ARGB, 7 | ARGF, 8 | ARGI, 9 | ARGP, 10 | ARGU, 11 | 12 | ASGNB, 13 | ASGNF, 14 | ASGNI, 15 | ASGNP, 16 | ASGNU, 17 | 18 | INDIRB, 19 | INDIRF, 20 | INDIRI, 21 | INDIRP, 22 | INDIRU, 23 | 24 | CVFF, 25 | CVFI, 26 | 27 | CVIF, 28 | CVII, 29 | CVIU, 30 | 31 | CVPU, 32 | 33 | CVUI, 34 | CVUP, 35 | CVUU, 36 | 37 | NEGF, 38 | NEGI, 39 | 40 | CALLB, 41 | CALLF, 42 | CALLI, 43 | CALLP, 44 | CALLU, 45 | CALLV, 46 | 47 | RETF, 48 | RETI, 49 | RETP, 50 | RETU, 51 | RETV, 52 | 53 | ADDRGP, 54 | 55 | ADDRFP, 56 | 57 | ADDRLP, 58 | 59 | ADDF, 60 | ADDI, 61 | ADDP, 62 | ADDU, 63 | 64 | SUBF, 65 | SUBI, 66 | SUBP, 67 | SUBU, 68 | 69 | LSHI, 70 | LSHU, 71 | 72 | MODI, 73 | MODU, 74 | 75 | RSHI, 76 | RSHU, 77 | 78 | BANDI, 79 | BANDU, 80 | 81 | BCOMI, 82 | BCOMU, 83 | 84 | BORI, 85 | BORU, 86 | 87 | BXORI, 88 | BXORU, 89 | 90 | DIVF, 91 | DIVI, 92 | DIVU, 93 | 94 | MULF, 95 | MULI, 96 | MULU, 97 | 98 | EQF, 99 | EQI, 100 | EQU, 101 | 102 | GEF, 103 | GEI, 104 | GEU, 105 | 106 | GTF, 107 | GTI, 108 | GTU, 109 | 110 | LEF, 111 | LEI, 112 | LEU, 113 | 114 | LTF, 115 | LTI, 116 | LTU, 117 | 118 | NEF, 119 | NEI, 120 | NEU, 121 | 122 | JUMPV, 123 | 124 | LABELV, 125 | 126 | LOADB, 127 | LOADF, 128 | LOADI, 129 | LOADP, 130 | LOADU, 131 | 132 | 133 | -------------------------------------------------------------------------------- /cmdlib.h: -------------------------------------------------------------------------------- 1 | /* 2 | =========================================================================== 3 | Copyright (C) 1999-2005 Id Software, Inc. 4 | 5 | This file is part of Quake III Arena source code. 6 | 7 | Quake III Arena source code is free software; you can redistribute it 8 | and/or modify it under the terms of the GNU General Public License as 9 | published by the Free Software Foundation; either version 2 of the License, 10 | or (at your option) any later version. 11 | 12 | Quake III Arena source code is distributed in the hope that it will be 13 | useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Quake III Arena source code; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | =========================================================================== 21 | */ 22 | // cmdlib.h 23 | 24 | #ifndef __CMDLIB__ 25 | #define __CMDLIB__ 26 | 27 | #ifdef _MSC_VER 28 | #pragma warning(disable : 4244) // MIPS 29 | #pragma warning(disable : 4136) // X86 30 | #pragma warning(disable : 4051) // ALPHA 31 | 32 | #pragma check_stack(off) 33 | 34 | #endif 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #ifdef _MSC_VER 45 | 46 | #pragma intrinsic( memset, memcpy ) 47 | 48 | #endif 49 | 50 | #ifndef __BYTEBOOL__ 51 | #define __BYTEBOOL__ 52 | typedef enum { qfalse, qtrue } qboolean; 53 | typedef unsigned char byte; 54 | #endif 55 | 56 | #ifdef _WIN32 57 | #define MAX_OS_PATH MAX_PATH 58 | #ifndef PATH_SEP 59 | #define PATH_SEP '\\' 60 | #endif 61 | #else 62 | #define MAX_OS_PATH 1024 63 | #ifndef PATH_SEP 64 | #define PATH_SEP '/' 65 | #endif 66 | #endif 67 | 68 | int Q_filelength( FILE *f ); 69 | 70 | void Q_mkdir( const char *path ); 71 | 72 | double I_FloatTime( void ); 73 | 74 | void Error( const char *error, ... ); 75 | 76 | FILE *SafeOpenWrite( const char *filename ); 77 | FILE *SafeOpenRead( const char *filename ); 78 | void SafeRead( FILE *f, void *buffer, int count ); 79 | void SafeWrite( FILE *f, const void *buffer, int count ); 80 | 81 | int LoadFile( const char *filename, void **bufferptr ); 82 | void SaveFile( const char *filename, const void *buffer, int count ); 83 | 84 | void DefaultExtension( char *path, const char *extension ); 85 | void StripExtension( char *path ); 86 | 87 | void CreatePath( const char *path ); 88 | 89 | void ExtractFileExtension( const char *path, char *dest ); 90 | 91 | char *COM_Parse ( char *data ); 92 | 93 | extern char com_token[1024]; 94 | 95 | char *copystring( const char *s ); 96 | 97 | int BigEndian( void ); 98 | int LongSwap( int l ); 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /opstrings.h: -------------------------------------------------------------------------------- 1 | /* 2 | =========================================================================== 3 | Copyright (C) 1999-2005 Id Software, Inc. 4 | 5 | This file is part of Quake III Arena source code. 6 | 7 | Quake III Arena source code is free software; you can redistribute it 8 | and/or modify it under the terms of the GNU General Public License as 9 | published by the Free Software Foundation; either version 2 of the License, 10 | or (at your option) any later version. 11 | 12 | Quake III Arena source code is distributed in the hope that it will be 13 | useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Quake III Arena source code; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | =========================================================================== 21 | */ 22 | { "BREAK", OP_BREAK }, 23 | 24 | { "CNSTF4", OP_CONST }, 25 | { "CNSTI4", OP_CONST }, 26 | { "CNSTP4", OP_CONST }, 27 | { "CNSTU4", OP_CONST }, 28 | 29 | { "CNSTI2", OP_CONST }, 30 | { "CNSTU2", OP_CONST }, 31 | 32 | { "CNSTI1", OP_CONST }, 33 | { "CNSTU1", OP_CONST }, 34 | 35 | { "ASGNB", OP_BLOCK_COPY }, 36 | { "ASGNF4", OP_STORE4 }, 37 | { "ASGNI4", OP_STORE4 }, 38 | { "ASGNP4", OP_STORE4 }, 39 | { "ASGNU4", OP_STORE4 }, 40 | 41 | { "ASGNI2", OP_STORE2 }, 42 | { "ASGNU2", OP_STORE2 }, 43 | 44 | { "ASGNI1", OP_STORE1 }, 45 | { "ASGNU1", OP_STORE1 }, 46 | 47 | { "INDIRB", OP_IGNORE }, // block copy deals with this 48 | { "INDIRF4", OP_LOAD4 }, 49 | { "INDIRI4", OP_LOAD4 }, 50 | { "INDIRP4", OP_LOAD4 }, 51 | { "INDIRU4", OP_LOAD4 }, 52 | 53 | { "INDIRI2", OP_LOAD2 }, 54 | { "INDIRU2", OP_LOAD2 }, 55 | 56 | { "INDIRI1", OP_LOAD1 }, 57 | { "INDIRU1", OP_LOAD1 }, 58 | 59 | { "CVFF4", OP_UNDEF }, 60 | { "CVFI4", OP_CVFI }, 61 | 62 | { "CVIF4", OP_CVIF }, 63 | { "CVII4", OP_SEX8 }, // will be either SEX8 or SEX16 64 | { "CVII1", OP_IGNORE }, 65 | { "CVII2", OP_IGNORE }, 66 | { "CVIU4", OP_IGNORE }, 67 | 68 | { "CVPU4", OP_IGNORE }, 69 | 70 | { "CVUI4", OP_IGNORE }, 71 | { "CVUP4", OP_IGNORE }, 72 | { "CVUU4", OP_IGNORE }, 73 | 74 | { "CVUU2", OP_IGNORE }, 75 | { "CVUU1", OP_IGNORE }, 76 | 77 | { "NEGF4", OP_NEGF }, 78 | { "NEGI4", OP_NEGI }, 79 | 80 | { "ADDRGP4", OP_CONST }, 81 | 82 | //{ "ADDRFP", OP_PARM, ASMP(ADDR) }, 83 | //{ "ADDRLP", OP_LOCAL,ASMP(ADDR) }, 84 | 85 | { "ADDF4", OP_ADDF }, 86 | { "ADDI4", OP_ADD }, 87 | { "ADDP4", OP_ADD }, 88 | { "ADDP", OP_ADD }, 89 | { "ADDU4", OP_ADD }, 90 | 91 | { "SUBF4", OP_SUBF }, 92 | { "SUBI4", OP_SUB }, 93 | { "SUBP4", OP_SUB }, 94 | { "SUBU4", OP_SUB }, 95 | 96 | { "LSHI4", OP_LSH }, 97 | { "LSHU4", OP_LSH }, 98 | 99 | { "MODI4", OP_MODI }, 100 | { "MODU4", OP_MODU }, 101 | 102 | { "RSHI4", OP_RSHI }, 103 | { "RSHU4", OP_RSHU }, 104 | 105 | { "BANDI4", OP_BAND }, 106 | { "BANDU4", OP_BAND }, 107 | 108 | { "BCOMI4", OP_BCOM }, 109 | { "BCOMU4", OP_BCOM }, 110 | 111 | { "BORI4", OP_BOR }, 112 | { "BORU4", OP_BOR }, 113 | 114 | { "BXORI4", OP_BXOR }, 115 | { "BXORU4", OP_BXOR }, 116 | 117 | { "DIVF4", OP_DIVF }, 118 | { "DIVI4", OP_DIVI }, 119 | { "DIVU4", OP_DIVU }, 120 | 121 | { "MULF4", OP_MULF }, 122 | { "MULI4", OP_MULI }, 123 | { "MULU4", OP_MULU }, 124 | 125 | { "EQF4", OP_EQF }, 126 | { "EQI4", OP_EQ }, 127 | { "EQU4", OP_EQ }, 128 | 129 | { "GEF4", OP_GEF }, 130 | { "GEI4", OP_GEI }, 131 | { "GEU4", OP_GEU }, 132 | 133 | { "GTF4", OP_GTF }, 134 | { "GTI4", OP_GTI }, 135 | { "GTU4", OP_GTU }, 136 | 137 | { "LEF4", OP_LEF }, 138 | { "LEI4", OP_LEI }, 139 | { "LEU4", OP_LEU }, 140 | 141 | { "LTF4", OP_LTF }, 142 | { "LTI4", OP_LTI }, 143 | { "LTU4", OP_LTU }, 144 | 145 | { "NEF4", OP_NEF }, 146 | { "NEI4", OP_NE }, 147 | { "NEU4", OP_NE }, 148 | 149 | { "JUMPV", OP_JUMP }, 150 | 151 | //{ "LOADB4", OP_UNDEF }, 152 | //{ "LOADF4", OP_UNDEF }, 153 | //{ "LOADI4", OP_UNDEF }, 154 | //{ "LOADP4", OP_UNDEF }, 155 | //{ "LOADU4", OP_UNDEF }, 156 | 157 | { "proc", DIR_PROC, ASMP(PROC) }, 158 | { "endproc", DIR_ENDPROC, ASMP(ENDPROC) }, 159 | { "address", DIR_ADDRESS, ASMP(ADDRESS) }, 160 | { "export", DIR_EXPORT, ASMP(EXPORT) }, 161 | { "import", DIR_IMPORT, ASMP(IMPORT) }, 162 | { "code", DIR_CODE, ASMP(CODE) }, 163 | { "data", DIR_DATA, ASMP(DATA) }, 164 | { "lit", DIR_LIT, ASMP(LIT) }, 165 | { "bss", DIR_BSS, ASMP(BSS) }, 166 | { "line", DIR_LINE, ASMP(LINE) }, 167 | { "file", DIR_FILE, ASMP(FILE) }, 168 | { "equ", DIR_EQU, ASMP(EQU) }, 169 | { "align", DIR_ALIGN, ASMP(ALIGN) }, 170 | { "byte", DIR_BYTE, ASMP(BYTE) }, 171 | { "skip", DIR_SKIP, ASMP(SKIP) }, 172 | 173 | { "pop", OP_IGNORE, ASMP(POP) }, 174 | 175 | { "ARGB", OP_IGNORE, ASMP(ARG) }, 176 | { "ARGF4", OP_IGNORE, ASMP(ARG) }, 177 | { "ARGI4", OP_IGNORE, ASMP(ARG) }, 178 | { "ARGP4", OP_IGNORE, ASMP(ARG) }, 179 | { "ARGU4", OP_IGNORE, ASMP(ARG) }, 180 | 181 | //{ "CALLB", OP_IGNORE, ASMP(CALL) }, 182 | { "CALLF4", OP_IGNORE, ASMP(CALL) }, 183 | { "CALLI4", OP_IGNORE, ASMP(CALL) }, 184 | { "CALLP4", OP_IGNORE, ASMP(CALL) }, 185 | { "CALLU4", OP_IGNORE, ASMP(CALL) }, 186 | { "CALLV", OP_IGNORE, ASMP(CALL) }, 187 | 188 | { "RETF4", OP_IGNORE, ASMP(RET) }, 189 | { "RETI4", OP_IGNORE, ASMP(RET) }, 190 | { "RETP4", OP_IGNORE, ASMP(RET) }, 191 | { "RETU4", OP_IGNORE, ASMP(RET) }, 192 | //{ "RETV", OP_IGNORE, ASMP(RET) }, 193 | 194 | { "ADDRFP4", OP_IGNORE, ASMP(ADDRF) }, 195 | { "ADDRLP4", OP_IGNORE, ASMP(ADDRL) }, 196 | 197 | { "LABELV", OP_IGNORE, ASMP(LABEL) }, 198 | 199 | -------------------------------------------------------------------------------- /vs2005/q3asm.vcproj: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 25 | 28 | 31 | 34 | 37 | 40 | 49 | 52 | 57 | 60 | 67 | 70 | 73 | 76 | 79 | 82 | 85 | 88 | 91 | 92 | 101 | 104 | 107 | 110 | 113 | 116 | 131 | 134 | 139 | 142 | 150 | 153 | 156 | 159 | 162 | 165 | 168 | 171 | 174 | 175 | 176 | 177 | 178 | 179 | 183 | 186 | 189 | 195 | 196 | 199 | 206 | 207 | 208 | 211 | 214 | 220 | 221 | 224 | 231 | 232 | 233 | 236 | 239 | 245 | 246 | 249 | 256 | 257 | 258 | 259 | 260 | 264 | 267 | 268 | 271 | 272 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /cmdlib.c: -------------------------------------------------------------------------------- 1 | /* 2 | =========================================================================== 3 | Copyright (C) 1999-2005 Id Software, Inc. 4 | 5 | This file is part of Quake III Arena source code. 6 | 7 | Quake III Arena source code is free software; you can redistribute it 8 | and/or modify it under the terms of the GNU General Public License as 9 | published by the Free Software Foundation; either version 2 of the License, 10 | or (at your option) any later version. 11 | 12 | Quake III Arena source code is distributed in the hope that it will be 13 | useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Quake III Arena source code; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | =========================================================================== 21 | */ 22 | // cmdlib.c 23 | 24 | #include "cmdlib.h" 25 | #include 26 | #include 27 | 28 | #ifdef _WIN32 29 | #include 30 | #include 31 | #elif defined(NeXT) 32 | #include 33 | #else 34 | #include 35 | #endif 36 | 37 | char com_token[1024]; 38 | qboolean com_eof; 39 | 40 | /* 41 | ================= 42 | Error 43 | 44 | For abnormal program terminations in console apps 45 | ================= 46 | */ 47 | void Error( const char *error, ...) 48 | { 49 | va_list argptr; 50 | 51 | printf( "\n************ ERROR ************\n" ); 52 | va_start( argptr, error ); 53 | vprintf( error, argptr ); 54 | va_end( argptr ); 55 | printf( "\n" ); 56 | exit( 1 ); 57 | } 58 | 59 | 60 | char *copystring( const char *s ) 61 | { 62 | int len; 63 | char *b; 64 | len = strlen( s ) + 1; 65 | b = malloc( len ); 66 | memcpy( b, s, len ); 67 | return b; 68 | } 69 | 70 | 71 | /* 72 | ================ 73 | I_FloatTime 74 | ================ 75 | */ 76 | double I_FloatTime (void) 77 | { 78 | #ifdef _WIN32 79 | DWORD count; 80 | count = GetTickCount(); 81 | return (double)count*0.001; 82 | #else 83 | time_t t; 84 | time (&t); 85 | return t; 86 | #endif 87 | 88 | #if 0 89 | // more precise, less portable 90 | struct timeval tp; 91 | struct timezone tzp; 92 | static int secbase; 93 | 94 | gettimeofday(&tp, &tzp); 95 | 96 | if (!secbase) 97 | { 98 | secbase = tp.tv_sec; 99 | return tp.tv_usec/1000000.0; 100 | } 101 | 102 | return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; 103 | #endif 104 | } 105 | 106 | 107 | void Q_mkdir (const char *path) 108 | { 109 | #ifdef _WIN32 110 | if (_mkdir (path) != -1) 111 | return; 112 | #else 113 | if (mkdir (path, 0777) != -1) 114 | return; 115 | #endif 116 | if (errno != EEXIST) 117 | Error ("mkdir %s: %s",path, strerror(errno)); 118 | } 119 | 120 | /* 121 | ============== 122 | COM_Parse 123 | 124 | Parse a token out of a string 125 | ============== 126 | */ 127 | char *COM_Parse (char *data) 128 | { 129 | int c; 130 | int len; 131 | 132 | len = 0; 133 | com_token[0] = 0; 134 | 135 | if (!data) 136 | return NULL; 137 | 138 | // skip whitespace 139 | skipwhite: 140 | while ( (c = *data) <= ' ') 141 | { 142 | if (c == 0) 143 | { 144 | com_eof = qtrue; 145 | return NULL; // end of file; 146 | } 147 | data++; 148 | } 149 | 150 | // skip // comments 151 | if (c=='/' && data[1] == '/') 152 | { 153 | while (*data && *data != '\n') 154 | data++; 155 | goto skipwhite; 156 | } 157 | 158 | 159 | // handle quoted strings specially 160 | if (c == '\"') 161 | { 162 | data++; 163 | for ( ;; ) { 164 | //do 165 | //{ 166 | c = *data++; 167 | if (c=='\"') 168 | { 169 | com_token[len] = 0; 170 | return data; 171 | } 172 | com_token[len] = c; 173 | len++; 174 | //} while (1); 175 | } 176 | } 177 | 178 | // parse single characters 179 | switch ( c ) { 180 | case '{': case '}': 181 | case '(': case ')': 182 | case '\'': case ':': 183 | { 184 | com_token[len++] = c; 185 | com_token[len] = '\0'; 186 | return data+1; 187 | } 188 | } 189 | 190 | // parse a regular word 191 | do 192 | { 193 | com_token[len] = c; 194 | data++; 195 | len++; 196 | c = *data; 197 | switch ( c ) { 198 | case '{': case '}': 199 | case '(': case ')': 200 | case '\'': case ':': 201 | { 202 | com_token[len] = '\0'; 203 | return data; 204 | } 205 | } 206 | } while ( c > ' ' ); 207 | 208 | com_token[len] = '\0'; 209 | 210 | return data; 211 | } 212 | 213 | 214 | /* 215 | ============================================================================= 216 | 217 | MISC FUNCTIONS 218 | 219 | ============================================================================= 220 | */ 221 | 222 | 223 | /* 224 | ================ 225 | Q_filelength 226 | ================ 227 | */ 228 | int Q_filelength (FILE *f) 229 | { 230 | int pos; 231 | int end; 232 | 233 | pos = ftell (f); 234 | fseek (f, 0, SEEK_END); 235 | end = ftell (f); 236 | fseek (f, pos, SEEK_SET); 237 | 238 | return end; 239 | } 240 | 241 | #ifdef MAX_PATH 242 | #undef MAX_PATH 243 | #endif 244 | #define MAX_PATH 4096 245 | 246 | static FILE* myfopen( const char* filename, const char* mode ) 247 | { 248 | char* p; 249 | char fn[MAX_PATH]; 250 | 251 | fn[0] = '\0'; 252 | strncat(fn, filename, sizeof(fn)-1); 253 | 254 | for(p=fn;*p;++p) if(*p == '\\') *p = '/'; 255 | 256 | return fopen(fn, mode); 257 | } 258 | 259 | 260 | FILE *SafeOpenWrite (const char *filename) 261 | { 262 | FILE *f; 263 | 264 | f = myfopen(filename, "wb"); 265 | 266 | if (!f) 267 | Error ("Error opening %s: %s",filename,strerror(errno)); 268 | 269 | return f; 270 | } 271 | 272 | FILE *SafeOpenRead (const char *filename) 273 | { 274 | FILE *f; 275 | 276 | f = myfopen(filename, "rb"); 277 | 278 | if (!f) 279 | Error ("Error opening %s: %s",filename,strerror(errno)); 280 | 281 | return f; 282 | } 283 | 284 | 285 | void SafeRead (FILE *f, void *buffer, int count) 286 | { 287 | if ( fread (buffer, 1, count, f) != (size_t)count) 288 | Error ("File read failure"); 289 | } 290 | 291 | 292 | void SafeWrite (FILE *f, const void *buffer, int count) 293 | { 294 | if (fwrite (buffer, 1, count, f) != (size_t)count) 295 | Error ("File write failure"); 296 | } 297 | 298 | 299 | /* 300 | ============== 301 | LoadFile 302 | ============== 303 | */ 304 | int LoadFile( const char *filename, void **bufferptr ) 305 | { 306 | FILE *f; 307 | int length; 308 | void *buffer; 309 | 310 | f = SafeOpenRead (filename); 311 | length = Q_filelength (f); 312 | buffer = malloc (length+1); 313 | ((char *)buffer)[length] = 0; 314 | SafeRead (f, buffer, length); 315 | fclose (f); 316 | 317 | *bufferptr = buffer; 318 | return length; 319 | } 320 | 321 | 322 | /* 323 | ============== 324 | SaveFile 325 | ============== 326 | */ 327 | void SaveFile ( const char *filename, const void *buffer, int count ) 328 | { 329 | FILE *f; 330 | 331 | f = SafeOpenWrite( filename ); 332 | SafeWrite( f, buffer, count ); 333 | fclose( f ); 334 | } 335 | 336 | 337 | 338 | void DefaultExtension ( char *path, const char *extension ) 339 | { 340 | char *src; 341 | // 342 | // if path doesnt have a .EXT, append extension 343 | // (extension should include the .) 344 | // 345 | src = path + strlen(path) - 1; 346 | 347 | while ( *src != '/' && *src != '\\' && src != path ) 348 | { 349 | if ( *src == '.' ) 350 | return; // it has an extension 351 | src--; 352 | } 353 | 354 | strcat( path, extension ); 355 | } 356 | 357 | 358 | void StripExtension ( char *path ) 359 | { 360 | int length; 361 | 362 | length = strlen(path)-1; 363 | while( length > 0 && path[length] != '.' ) 364 | { 365 | length--; 366 | if ( path[length] == '/' ) 367 | return; // no extension 368 | } 369 | if ( length ) 370 | path[length] = '\0'; 371 | } 372 | 373 | 374 | /* 375 | ============================================================================ 376 | 377 | BYTE ORDER FUNCTIONS 378 | 379 | ============================================================================ 380 | */ 381 | 382 | // detect endianess 383 | int BigEndian( void ) { 384 | union { 385 | int i; 386 | char s[4]; 387 | } u; 388 | strcpy( u.s, "123" ); 389 | if ( u.i == 0x00333231 ) 390 | return 0; 391 | else 392 | return 1; 393 | } 394 | 395 | int LongSwap( int l ) 396 | { 397 | byte b1,b2,b3,b4; 398 | 399 | b1 = l&255; 400 | b2 = (l>>8)&255; 401 | b3 = (l>>16)&255; 402 | b4 = (l>>24)&255; 403 | 404 | return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; 405 | } 406 | 407 | //======================================================= 408 | 409 | /* 410 | ============ 411 | CreatePath 412 | ============ 413 | */ 414 | void CreatePath (const char *path) 415 | { 416 | const char *ofs; 417 | char c; 418 | char dir[1024]; 419 | 420 | #ifdef _WIN32 421 | int olddrive = -1; 422 | 423 | if ( path[1] == ':' ) 424 | { 425 | olddrive = _getdrive(); 426 | _chdrive( toupper( path[0] ) - 'A' + 1 ); 427 | } 428 | #endif 429 | 430 | if (path[1] == ':') 431 | path += 2; 432 | 433 | for (ofs = path+1 ; *ofs ; ofs++) 434 | { 435 | c = *ofs; 436 | if (c == '/' || c == '\\') 437 | { // create the directory 438 | memcpy( dir, path, ofs - path ); 439 | dir[ ofs - path ] = 0; 440 | Q_mkdir( dir ); 441 | } 442 | } 443 | 444 | #ifdef _WIN32 445 | if ( olddrive != -1 ) 446 | { 447 | _chdrive( olddrive ); 448 | } 449 | #endif 450 | } 451 | -------------------------------------------------------------------------------- /q3vm.c: -------------------------------------------------------------------------------- 1 | /* 2 | =========================================================================== 3 | Copyright (C) 1999-2005 Id Software, Inc. 4 | 5 | This file is part of Quake III Arena source code. 6 | 7 | Quake III Arena source code is free software; you can redistribute it 8 | and/or modify it under the terms of the GNU General Public License as 9 | published by the Free Software Foundation; either version 2 of the License, 10 | or (at your option) any later version. 11 | 12 | Quake III Arena source code is distributed in the hope that it will be 13 | useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Quake III Arena source code; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | =========================================================================== 21 | */ 22 | 23 | #include "qvm.h" 24 | #include "cmdlib.h" 25 | 26 | typedef enum { 27 | OP_UNDEF, 28 | 29 | OP_IGNORE, 30 | 31 | OP_BREAK, 32 | 33 | OP_ENTER, 34 | OP_LEAVE, 35 | OP_CALL, 36 | OP_PUSH, 37 | OP_POP, 38 | 39 | OP_CONST, 40 | OP_LOCAL, 41 | 42 | OP_JUMP, 43 | 44 | //------------------- 45 | 46 | OP_EQ, 47 | OP_NE, 48 | 49 | OP_LTI, 50 | OP_LEI, 51 | OP_GTI, 52 | OP_GEI, 53 | 54 | OP_LTU, 55 | OP_LEU, 56 | OP_GTU, 57 | OP_GEU, 58 | 59 | OP_EQF, 60 | OP_NEF, 61 | 62 | OP_LTF, 63 | OP_LEF, 64 | OP_GTF, 65 | OP_GEF, 66 | 67 | //------------------- 68 | 69 | OP_LOAD1, 70 | OP_LOAD2, 71 | OP_LOAD4, 72 | OP_STORE1, 73 | OP_STORE2, 74 | OP_STORE4, // *(stack[top-1]) = stack[top] 75 | OP_ARG, 76 | 77 | OP_BLOCK_COPY, 78 | 79 | //------------------- 80 | 81 | OP_SEX8, 82 | OP_SEX16, 83 | 84 | OP_NEGI, 85 | OP_ADD, 86 | OP_SUB, 87 | OP_DIVI, 88 | OP_DIVU, 89 | OP_MODI, 90 | OP_MODU, 91 | OP_MULI, 92 | OP_MULU, 93 | 94 | OP_BAND, 95 | OP_BOR, 96 | OP_BXOR, 97 | OP_BCOM, 98 | 99 | OP_LSH, 100 | OP_RSHI, 101 | OP_RSHU, 102 | 103 | OP_NEGF, 104 | OP_ADDF, 105 | OP_SUBF, 106 | OP_DIVF, 107 | OP_MULF, 108 | 109 | OP_CVIF, 110 | OP_CVFI, 111 | 112 | OP_MAX 113 | } opcode_t; 114 | 115 | typedef struct opcode_info_s 116 | { 117 | int size; 118 | int stack; 119 | int flags; 120 | } opcode_info_t; 121 | 122 | #define JUMP (1<<0) 123 | #define PROC_OPSTACK_SIZE 30 124 | 125 | #define LittleLong 126 | 127 | static const opcode_info_t ops[ OP_MAX ] = 128 | { 129 | { 0, 0, 0 }, // undef 130 | { 0, 0, 0 }, // ignore 131 | { 0, 0, 0 }, // break 132 | 133 | { 4, 0, 0 }, // enter 134 | { 4,-4, 0 }, // leave 135 | { 0, 0, 0 }, // call 136 | { 0, 4, 0 }, // push 137 | { 0,-4, 0 }, // pop 138 | 139 | { 4, 4, 0 }, // const 140 | { 4, 4, 0 }, // local 141 | { 0,-4, 0 }, // jump 142 | 143 | { 4,-8, JUMP }, // eq 144 | { 4,-8, JUMP }, // ne 145 | 146 | { 4,-8, JUMP }, // lti 147 | { 4,-8, JUMP }, // lei 148 | { 4,-8, JUMP }, // gti 149 | { 4,-8, JUMP }, // gei 150 | 151 | { 4,-8, JUMP }, // ltu 152 | { 4,-8, JUMP }, // leu 153 | { 4,-8, JUMP }, // gtu 154 | { 4,-8, JUMP }, // geu 155 | 156 | { 4,-8, JUMP }, // eqf 157 | { 4,-8, JUMP }, // nef 158 | 159 | { 4,-8, JUMP }, // ltf 160 | { 4,-8, JUMP }, // lef 161 | { 4,-8, JUMP }, // gtf 162 | { 4,-8, JUMP }, // gef 163 | 164 | { 0, 0, 0 }, // load1 165 | { 0, 0, 0 }, // load2 166 | { 0, 0, 0 }, // load4 167 | { 0,-8, 0 }, // store1 168 | { 0,-8, 0 }, // store2 169 | { 0,-8, 0 }, // store4 170 | { 1,-4, 0 }, // arg 171 | { 4,-8, 0 }, // bcopy 172 | 173 | { 0, 0, 0 }, // sex8 174 | { 0, 0, 0 }, // sex16 175 | 176 | { 0, 0, 0 }, // negi 177 | { 0,-4, 0 }, // add 178 | { 0,-4, 0 }, // sub 179 | { 0,-4, 0 }, // divi 180 | { 0,-4, 0 }, // divu 181 | { 0,-4, 0 }, // modi 182 | { 0,-4, 0 }, // modu 183 | { 0,-4, 0 }, // muli 184 | { 0,-4, 0 }, // mulu 185 | 186 | { 0,-4, 0 }, // band 187 | { 0,-4, 0 }, // bor 188 | { 0,-4, 0 }, // bxor 189 | { 0, 0, 0 }, // bcom 190 | 191 | { 0,-4, 0 }, // lsh 192 | { 0,-4, 0 }, // rshi 193 | { 0,-4, 0 }, // rshu 194 | 195 | { 0, 0, 0 }, // negf 196 | { 0,-4, 0 }, // addf 197 | { 0,-4, 0 }, // subf 198 | { 0,-4, 0 }, // divf 199 | { 0,-4, 0 }, // mulf 200 | 201 | { 0, 0, 0 }, // cvif 202 | { 0, 0, 0 } // cvfi 203 | }; 204 | 205 | 206 | /* 207 | ================= 208 | VM_LoadInstructions 209 | 210 | loads instructions in structured format 211 | ================= 212 | */ 213 | const char *VM_LoadInstructions( const byte *code_pos, int codeLength, int instructionCount, instruction_t *buf ) 214 | { 215 | static char errBuf[ 128 ]; 216 | const byte *code_start, *code_end; 217 | int i, n, op0, op1, opStack; 218 | instruction_t *ci; 219 | 220 | code_start = code_pos; // for printing 221 | code_end = code_pos + codeLength; 222 | 223 | ci = buf; 224 | opStack = 0; 225 | op1 = OP_UNDEF; 226 | 227 | // load instructions and perform some initial calculations/checks 228 | //for ( i = 0; i < header->instructionCount; i++, ci++, op1 = op0 ) { 229 | for ( i = 0; i < instructionCount; i++, ci++, op1 = op0 ) { 230 | op0 = *code_pos; 231 | if ( op0 < 0 || op0 >= OP_MAX ) { 232 | sprintf( errBuf, "bad opcode %02X at offset %d", op0, (int)(code_pos - code_start) ); 233 | return errBuf; 234 | } 235 | n = ops[ op0 ].size; 236 | if ( code_pos + 1 + n > code_end ) { 237 | sprintf( errBuf, "code_pos > code_end" ); 238 | return errBuf; 239 | } 240 | code_pos++; 241 | ci->op = op0; 242 | if ( n == 4 ) { 243 | ci->value = LittleLong( *((int*)code_pos) ); 244 | code_pos += 4; 245 | } else if ( n == 1 ) { 246 | ci->value = *((unsigned char*)code_pos); 247 | code_pos += 1; 248 | } else { 249 | ci->value = 0; 250 | } 251 | 252 | // setup jump value from previous const 253 | if ( op0 == OP_JUMP && op1 == OP_CONST ) { 254 | ci->value = (ci-1)->value; 255 | } 256 | 257 | ci->opStack = opStack; 258 | opStack += ops[ op0 ].stack; 259 | } 260 | 261 | return NULL; 262 | } 263 | 264 | 265 | /* 266 | =============================== 267 | VM_CheckInstructions 268 | 269 | performs additional consistency and security checks 270 | =============================== 271 | */ 272 | const char *VM_CheckInstructions( instruction_t *buf, int instructionCount, int dataLength ) 273 | { 274 | static char errBuf[ 128 ]; 275 | int i, n, v, op0, op1, opStack, pstack; 276 | instruction_t *ci, *proc; 277 | int startp, endp; 278 | 279 | ci = buf; 280 | opStack = 0; 281 | 282 | // opstack checks 283 | for ( i = 0; i < instructionCount; i++, ci++ ) { 284 | opStack += ops[ ci->op ].stack; 285 | if ( opStack < 0 ) { 286 | sprintf( errBuf, "opStack underflow at %i", i ); 287 | return errBuf; 288 | } 289 | if ( opStack >= PROC_OPSTACK_SIZE * 4 ) { 290 | sprintf( errBuf, "opStack overflow at %i", i ); 291 | return errBuf; 292 | } 293 | } 294 | 295 | ci = buf; 296 | pstack = 0; 297 | op1 = OP_UNDEF; 298 | proc = NULL; 299 | 300 | startp = 0; 301 | endp = instructionCount - 1; 302 | 303 | // Additional security checks 304 | 305 | for ( i = 0; i < instructionCount; i++, ci++, op1 = op0 ) { 306 | op0 = ci->op; 307 | 308 | // function entry 309 | if ( op0 == OP_ENTER ) { 310 | // missing block end 311 | if ( proc || ( pstack && op1 != OP_LEAVE ) ) { 312 | sprintf( errBuf, "missing proc end before %i", i ); 313 | return errBuf; 314 | } 315 | if ( ci->opStack != 0 ) { 316 | v = ci->opStack; 317 | sprintf( errBuf, "bad entry opstack %i at %i", v, i ); 318 | return errBuf; 319 | } 320 | v = ci->value; 321 | if ( v < 0 || v >= PROGRAM_STACK_SIZE || (v & 3) ) { 322 | sprintf( errBuf, "bad entry programStack %i at %i", v, i ); 323 | return errBuf; 324 | } 325 | 326 | pstack = ci->value; 327 | 328 | // mark jump target 329 | ci->jused = 1; 330 | proc = ci; 331 | startp = i + 1; 332 | 333 | // locate endproc 334 | for ( endp = 0, n = i+1 ; n < instructionCount; n++ ) { 335 | if ( buf[n].op == OP_PUSH && buf[n+1].op == OP_LEAVE ) { 336 | endp = n; 337 | break; 338 | } 339 | } 340 | 341 | if ( endp == 0 ) { 342 | sprintf( errBuf, "missing end proc for %i", i ); 343 | return errBuf; 344 | } 345 | 346 | continue; 347 | } 348 | 349 | // proc opstack will carry max.possible opstack value 350 | if ( proc && ci->opStack > proc->opStack ) 351 | proc->opStack = ci->opStack; 352 | 353 | // function return 354 | if ( op0 == OP_LEAVE ) { 355 | // bad return programStack 356 | if ( pstack != ci->value ) { 357 | v = ci->value; 358 | sprintf( errBuf, "bad programStack %i at %i", v, i ); 359 | return errBuf; 360 | } 361 | // bad opStack before return 362 | if ( ci->opStack != 4 ) { 363 | v = ci->opStack; 364 | sprintf( errBuf, "bad opStack %i at %i", v, i ); 365 | return errBuf; 366 | } 367 | v = ci->value; 368 | if ( v < 0 || v >= PROGRAM_STACK_SIZE || (v & 3) ) { 369 | sprintf( errBuf, "bad return programStack %i at %i", v, i ); 370 | return errBuf; 371 | } 372 | if ( op1 == OP_PUSH ) { 373 | if ( proc == NULL ) { 374 | sprintf( errBuf, "unexpected proc end at %i", i ); 375 | return errBuf; 376 | } 377 | proc = NULL; 378 | startp = i + 1; // next instruction 379 | endp = instructionCount - 1; // end of the image 380 | } 381 | continue; 382 | } 383 | 384 | // conditional jumps 385 | if ( ops[ ci->op ].flags & JUMP ) { 386 | v = ci->value; 387 | // conditional jumps should have opStack == 8 388 | if ( ci->opStack != 8 ) { 389 | sprintf( errBuf, "bad jump opStack %i at %i", ci->opStack, i ); 390 | return errBuf; 391 | } 392 | //if ( v >= header->instructionCount ) { 393 | // allow only local proc jumps 394 | if ( v < startp || v > endp ) { 395 | sprintf( errBuf, "jump target %i at %i is out of range (%i,%i)", v, i-1, startp, endp ); 396 | return errBuf; 397 | } 398 | if ( buf[v].opStack != 0 ) { 399 | n = buf[v].opStack; 400 | sprintf( errBuf, "jump target %i has bad opStack %i", v, n ); 401 | return errBuf; 402 | } 403 | // mark jump target 404 | buf[v].jused = 1; 405 | continue; 406 | } 407 | 408 | // unconditional jumps 409 | if ( op0 == OP_JUMP ) { 410 | // jumps should have opStack == 4 411 | if ( ci->opStack != 4 ) { 412 | sprintf( errBuf, "bad jump opStack %i at %i", ci->opStack, i ); 413 | return errBuf; 414 | } 415 | if ( op1 == OP_CONST ) { 416 | v = buf[i-1].value; 417 | // allow only local jumps 418 | if ( v < startp || v > endp ) { 419 | sprintf( errBuf, "jump target %i at %i is out of range (%i,%i)", v, i-1, startp, endp ); 420 | return errBuf; 421 | } 422 | if ( buf[v].opStack != 0 ) { 423 | n = buf[v].opStack; 424 | sprintf( errBuf, "jump target %i has bad opStack %i", v, n ); 425 | return errBuf; 426 | } 427 | if ( buf[v].op == OP_ENTER ) { 428 | n = buf[v].op; 429 | sprintf( errBuf, "jump target %i has bad opcode %i", v, n ); 430 | return errBuf; 431 | } 432 | if ( v == (i-1) ) { 433 | sprintf( errBuf, "self loop at %i", v ); 434 | return errBuf; 435 | } 436 | // mark jump target 437 | buf[v].jused = 1; 438 | } 439 | continue; 440 | } 441 | 442 | if ( op0 == OP_CALL ) { 443 | if ( ci->opStack < 4 ) { 444 | sprintf( errBuf, "bad call opStack at %i", i ); 445 | return errBuf; 446 | } 447 | if ( op1 == OP_CONST ) { 448 | v = buf[i-1].value; 449 | // analyse only local function calls 450 | if ( v >= 0 ) { 451 | if ( v >= instructionCount ) { 452 | sprintf( errBuf, "call target %i is out of range", v ); 453 | return errBuf; 454 | } 455 | if ( buf[v].op != OP_ENTER ) { 456 | n = buf[v].op; 457 | sprintf( errBuf, "call target %i has bad opcode %i", v, n ); 458 | return errBuf; 459 | } 460 | if ( v == 0 ) { 461 | sprintf( errBuf, "explicit vmMain call inside VM" ); 462 | return errBuf; 463 | } 464 | // mark jump target 465 | buf[v].jused = 1; 466 | } 467 | } 468 | continue; 469 | } 470 | 471 | if ( ci->op == OP_ARG ) { 472 | v = ci->value & 255; 473 | // argument can't exceed programStack frame 474 | if ( v < 8 || v > pstack - 4 || (v & 3) ) { 475 | sprintf( errBuf, "bad argument address %i at %i", v, i ); 476 | return errBuf; 477 | } 478 | continue; 479 | } 480 | 481 | if ( ci->op == OP_LOCAL ) { 482 | v = ci->value; 483 | if ( proc == NULL ) { 484 | sprintf( errBuf, "missing proc frame for local %i at %i", v, i ); 485 | return errBuf; 486 | } 487 | if ( (ci+1)->op == OP_LOAD1 || (ci+1)->op == OP_LOAD2 || (ci+1)->op == OP_LOAD4 || (ci+1)->op == OP_ARG ) { 488 | // FIXME: alloc 256 bytes of programStack in VM_CallCompiled()? 489 | if ( v < 8 || v >= proc->value + 256 ) { 490 | sprintf( errBuf, "bad local address %i at %i", v, i ); 491 | return errBuf; 492 | } 493 | } 494 | } 495 | 496 | if ( ci->op == OP_LOAD4 && op1 == OP_CONST ) { 497 | v = (ci-1)->value; 498 | if ( v < 0 || v > dataLength - 4 ) { 499 | sprintf( errBuf, "bad load4 address %i at %i", v, i - 1 ); 500 | return errBuf; 501 | } 502 | } 503 | 504 | if ( ci->op == OP_LOAD2 && op1 == OP_CONST ) { 505 | v = (ci-1)->value; 506 | if ( v < 0 || v > dataLength - 2 ) { 507 | sprintf( errBuf, "bad load2 address %i at %i", v, i - 1 ); 508 | return errBuf; 509 | } 510 | } 511 | 512 | if ( ci->op == OP_LOAD1 && op1 == OP_CONST ) { 513 | v = (ci-1)->value; 514 | if ( v < 0 || v > dataLength - 1 ) { 515 | sprintf( errBuf, "bad load1 address %i at %i", v, i - 1 ); 516 | return errBuf; 517 | } 518 | } 519 | 520 | if ( ci->op == OP_BLOCK_COPY ) { 521 | v = ci->value; 522 | if ( v >= dataLength ) { 523 | sprintf( errBuf, "bad count %i for block copy at %i", v, i - 1 ); 524 | return errBuf; 525 | } 526 | } 527 | 528 | // op1 = op0; 529 | // ci++; 530 | } 531 | 532 | if ( op1 != OP_UNDEF && op1 != OP_LEAVE ) { 533 | sprintf( errBuf, "missing return instruction at the end of the image" ); 534 | return errBuf; 535 | } 536 | 537 | return NULL; 538 | } 539 | -------------------------------------------------------------------------------- /q3asm.c: -------------------------------------------------------------------------------- 1 | /* 2 | =========================================================================== 3 | Copyright (C) 1999-2005 Id Software, Inc. 4 | 5 | This file is part of Quake III Arena source code. 6 | 7 | Quake III Arena source code is free software; you can redistribute it 8 | and/or modify it under the terms of the GNU General Public License as 9 | published by the Free Software Foundation; either version 2 of the License, 10 | or (at your option) any later version. 11 | 12 | Quake III Arena source code is distributed in the hope that it will be 13 | useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Quake III Arena source code; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | =========================================================================== 21 | */ 22 | 23 | #ifdef _WIN32 24 | #include 25 | #include 26 | #endif 27 | 28 | #include "qvm.h" // <- that one should be synced with engine! 29 | #include "cmdlib.h" 30 | 31 | #define MYSORT 32 | #define EMITJTS 33 | 34 | // for symbols 35 | #define SYM_HASHTABLE_SIZE 2048 36 | 37 | // for opcodes/directives - minimize collisions 38 | #define OP_HASHTABLE_SIZE 512 39 | 40 | char outputFilename[MAX_OS_PATH]; 41 | 42 | typedef enum { 43 | OP_UNDEF, 44 | 45 | OP_IGNORE, 46 | 47 | OP_BREAK, 48 | 49 | OP_ENTER, 50 | OP_LEAVE, 51 | OP_CALL, 52 | OP_PUSH, 53 | OP_POP, 54 | 55 | OP_CONST, 56 | OP_LOCAL, 57 | 58 | OP_JUMP, 59 | 60 | //------------------- 61 | 62 | OP_EQ, 63 | OP_NE, 64 | 65 | OP_LTI, 66 | OP_LEI, 67 | OP_GTI, 68 | OP_GEI, 69 | 70 | OP_LTU, 71 | OP_LEU, 72 | OP_GTU, 73 | OP_GEU, 74 | 75 | OP_EQF, 76 | OP_NEF, 77 | 78 | OP_LTF, 79 | OP_LEF, 80 | OP_GTF, 81 | OP_GEF, 82 | 83 | //------------------- 84 | 85 | OP_LOAD1, 86 | OP_LOAD2, 87 | OP_LOAD4, 88 | OP_STORE1, 89 | OP_STORE2, 90 | OP_STORE4, // *(stack[top-1]) = stack[top] 91 | OP_ARG, 92 | OP_BLOCK_COPY, 93 | 94 | // ------------------- 95 | 96 | OP_SEX8, 97 | OP_SEX16, 98 | 99 | OP_NEGI, 100 | OP_ADD, 101 | OP_SUB, 102 | OP_DIVI, 103 | OP_DIVU, 104 | OP_MODI, 105 | OP_MODU, 106 | OP_MULI, 107 | OP_MULU, 108 | 109 | OP_BAND, 110 | OP_BOR, 111 | OP_BXOR, 112 | OP_BCOM, 113 | 114 | OP_LSH, 115 | OP_RSHI, 116 | OP_RSHU, 117 | 118 | OP_NEGF, 119 | OP_ADDF, 120 | OP_SUBF, 121 | OP_DIVF, 122 | OP_MULF, 123 | 124 | OP_CVIF, 125 | OP_CVFI, 126 | 127 | // additional directives 128 | 129 | DIR_PROC = 128, 130 | DIR_ENDPROC, 131 | DIR_ADDRESS, 132 | DIR_EXPORT, 133 | DIR_IMPORT, 134 | DIR_CODE, 135 | DIR_DATA, 136 | DIR_LIT, 137 | DIR_BSS, 138 | DIR_LINE, 139 | DIR_FILE, 140 | DIR_EQU, 141 | DIR_ALIGN, 142 | DIR_BYTE, 143 | DIR_SKIP 144 | 145 | } opcode_t; 146 | 147 | typedef enum { 148 | PASS_DEFINE, 149 | PASS_COMPILE, 150 | PASS_OPTIMIZE, 151 | PASS_COUNT 152 | } pass_t; 153 | 154 | 155 | typedef enum { 156 | ST_RESERVED, 157 | ST_FUNCTION, 158 | ST_LABEL 159 | } symtype_t; 160 | 161 | 162 | #define MAX_IMAGE 0x400000 163 | 164 | typedef struct segment_s { 165 | byte image[MAX_IMAGE]; 166 | int imageUsed; 167 | int segmentBase; // only valid after PASS_DEFINE 168 | } segment_t; 169 | 170 | 171 | typedef struct symbol_s { 172 | struct symbol_s *next; 173 | struct segment_s *segment; 174 | char *name; 175 | int value; 176 | 177 | symtype_t type; 178 | int refcnt; 179 | int ignore; 180 | 181 | struct { 182 | char *name; 183 | int line; 184 | } file; 185 | } symbol_t; 186 | 187 | 188 | typedef struct hashchain_s { 189 | void *data; 190 | struct hashchain_s *next; 191 | } hashchain_t; 192 | 193 | 194 | typedef struct hashtable_s { 195 | int buckets; 196 | int chains; 197 | int mask; 198 | hashchain_t **table; 199 | } hashtable_t; 200 | 201 | int symtablelen = SYM_HASHTABLE_SIZE; 202 | 203 | hashtable_t symtable; 204 | hashtable_t optable; 205 | 206 | byte jbitmap[MAX_IMAGE]; 207 | segment_t segment[NUM_SEGMENTS]; 208 | segment_t *currentSegment; 209 | 210 | pass_t passNumber; 211 | int passCount = 0; 212 | int ignoreFunc = 0; 213 | int ignoreLabel = 0; 214 | 215 | int errorCount; 216 | 217 | const char *passName[ PASS_COUNT ] = { "define", "compile", "optimize" }; 218 | 219 | typedef struct options_s { 220 | qboolean verbose; 221 | qboolean writeMapFile; 222 | qboolean vanillaQ3Compatibility; 223 | qboolean optimize; 224 | } options_t; 225 | 226 | options_t options = { 0 }; 227 | 228 | symbol_t *entry = NULL; // QVM entry point 229 | symbol_t *symbols = NULL; 230 | symbol_t *lastSymbol = NULL; // Most recent symbol defined 231 | 232 | #define MAX_ASM_FILES 256 233 | 234 | int numAsmFiles; 235 | char *asmFiles[MAX_ASM_FILES]; 236 | char *asmFileNames[MAX_ASM_FILES]; 237 | 238 | int currentFileIndex; 239 | char *currentFileName; 240 | int currentFileLine; 241 | 242 | int stackSize = PROGRAM_STACK_SIZE; // 65536 243 | 244 | // we need to convert arg and ret instructions to 245 | // stores to the local stack frame, so we need to track the 246 | // characteristics of the current functions stack frame 247 | 248 | int currentLocals; // bytes of locals needed by this function 249 | int currentArgs; // bytes of largest argument list called from this function 250 | int currentArgOffset; // byte offset in currentArgs to store next arg, reset each call 251 | 252 | #define MAX_LINE_LENGTH 1024 253 | 254 | char lineBuffer[MAX_LINE_LENGTH]; 255 | int lineParseOffset; 256 | char token[MAX_LINE_LENGTH]; 257 | 258 | int instructionCount; 259 | 260 | char symExport[MAX_LINE_LENGTH]; 261 | 262 | #define ASMP(O) TryAssemble_##O 263 | #define ASMF(O) int TryAssemble_##O( void ) 264 | 265 | typedef struct { 266 | char *name; 267 | int opcode; 268 | int (*func)(void); 269 | } sourceOps_t; 270 | 271 | // declare prototypes 272 | 273 | ASMF(PROC); 274 | ASMF(ENDPROC); 275 | ASMF(ADDRESS); 276 | ASMF(EXPORT); 277 | ASMF(IMPORT); 278 | ASMF(CODE); 279 | ASMF(DATA); 280 | ASMF(LIT); 281 | ASMF(BSS); 282 | ASMF(LINE); 283 | ASMF(FILE); 284 | ASMF(EQU); 285 | ASMF(ALIGN); 286 | ASMF(BYTE); 287 | ASMF(SKIP); 288 | 289 | ASMF(ARG); 290 | ASMF(POP); 291 | ASMF(RET); 292 | ASMF(CALL); 293 | ASMF(ADDRL); 294 | ASMF(ADDRF); 295 | ASMF(LABEL); 296 | 297 | sourceOps_t sourceOps[] = 298 | { 299 | #include "opstrings.h" 300 | }; 301 | 302 | #define NUM_SOURCE_OPS ( sizeof( sourceOps ) / sizeof( sourceOps[0] ) ) 303 | 304 | #define JUSED(v) jbitmap[(v)/8]|=1<<((v)&7) 305 | 306 | int vreport( const char* fmt, va_list vp ) 307 | { 308 | if ( options.verbose != qtrue ) 309 | return 0; 310 | return vprintf(fmt, vp); 311 | } 312 | 313 | int report( const char *fmt, ... ) 314 | { 315 | va_list va; 316 | int retval; 317 | 318 | va_start( va, fmt ); 319 | retval = vreport( fmt, va ); 320 | va_end( va ); 321 | return retval; 322 | } 323 | 324 | #ifdef EMITJTS 325 | 326 | unsigned int crc_table[256]; 327 | 328 | void _crc32_init( unsigned int *crc ) 329 | { 330 | unsigned int c; 331 | int i, j; 332 | for (i = 0; i < 256; i++) 333 | { 334 | c = i; 335 | for (j = 0; j < 8; j++) 336 | c = c & 1 ? (c >> 1) ^ 0xEDB88320UL : c >> 1; 337 | crc_table[i] = c; 338 | }; 339 | *crc = 0xFFFFFFFFUL; 340 | } 341 | 342 | void _crc32_update( unsigned int *crc, unsigned char *buf, unsigned int len ) 343 | { 344 | while (len--) 345 | *crc = crc_table[(*crc ^ *buf++) & 0xFF] ^ (*crc >> 8); 346 | } 347 | 348 | void _crc32_final( unsigned int *crc ) 349 | { 350 | *crc = *crc ^ 0xFFFFFFFFUL; 351 | } 352 | 353 | #endif 354 | 355 | int log2floor( int x ) 356 | { 357 | int r = 1; 358 | int v = x; 359 | while( v >>= 1 ) 360 | r <<= 1; 361 | if ( r < x ) 362 | r <<= 1; 363 | return r; 364 | } 365 | 366 | #define hashtable_get(H,hash) (H).table[(hash)&(H).mask] 367 | 368 | /* The chain-and-bucket hash table. -PH */ 369 | 370 | void hashtable_alloc( hashtable_t *H, int buckets ) 371 | { 372 | // round-up buckets so we can use mask correctly 373 | buckets = log2floor( buckets ); 374 | H->buckets = buckets; 375 | H->mask = buckets - 1; 376 | H->chains = 0; 377 | H->table = malloc( buckets * sizeof( *(H->table) ) ); 378 | memset( H->table, 0, buckets * sizeof( *(H->table) ) ); 379 | } 380 | 381 | 382 | // By Paul Larson 383 | unsigned int HashSymbol( const char *sym ) { 384 | unsigned int hash = 0; 385 | while( *sym ) 386 | hash = 101 * hash + *sym++; 387 | return hash ^ (hash >> 16); 388 | } 389 | 390 | 391 | // Modified version, perfect hash for our limited directive set 392 | unsigned int HashOpcode( const char *op ) { 393 | unsigned int hash = 0; 394 | while( *op ) 395 | hash = 2039545 * hash + *op++; 396 | return hash ^ (hash >> 14); 397 | // hash = 6720353 * hash + *op++; 398 | // return hash ^ (hash >> 18); 399 | } 400 | 401 | 402 | void hashtable_add( hashtable_t *H, int hashvalue, void *data ) 403 | { 404 | hashchain_t *hc, **hb; 405 | 406 | hb = &H->table[ hashvalue & H->mask ]; 407 | hc = malloc( sizeof( *hc ) ); 408 | hc->data = data; 409 | hc->next = *hb; 410 | *hb = hc; 411 | H->chains++; 412 | } 413 | 414 | /* 415 | There is no need in hashtable_remove routines or so since 416 | symbol/opcode tables is allocated once and used until program end 417 | */ 418 | 419 | 420 | /* 421 | ============== 422 | InitTables 423 | ============== 424 | */ 425 | void InitTables( void ) { 426 | int i, hash; 427 | sourceOps_t *op; 428 | 429 | hashtable_alloc( &symtable, symtablelen ); 430 | hashtable_alloc( &optable, OP_HASHTABLE_SIZE ); 431 | op = sourceOps; 432 | for ( i = 0 ; i < NUM_SOURCE_OPS ; i++, op++ ) { 433 | hash = HashOpcode( op->name ); 434 | hashtable_add( &optable, hash, op ); 435 | } 436 | } 437 | 438 | 439 | void hashtable_stats( hashtable_t *H ) 440 | { 441 | int len, empties, longest, nodes; 442 | int i; 443 | float meanlen; 444 | hashchain_t *hc; 445 | 446 | empties = 0; 447 | longest = 0; 448 | nodes = 0; 449 | for ( i = 0; i < H->buckets; i++ ) 450 | { 451 | if ( H->table[i] == NULL ) 452 | { empties++; continue; } 453 | for (hc = H->table[i], len = 0; hc; hc = hc->next, len++); 454 | if (len > longest) { longest = len; } 455 | nodes += len; 456 | } 457 | meanlen = (float)(nodes) / (H->buckets - empties); 458 | 459 | report( "Stats for %s hashtable:", (H==&symtable) ? "symbol" : "opcode" ); 460 | report( " %d buckets (%i empty), %d nodes\n", H->buckets, empties, nodes ); 461 | report( " longest chain: %d, mean non-empty: %f\n", longest, meanlen ); 462 | } 463 | 464 | 465 | /* 466 | Search for a symbol in corresponding hash table 467 | return NULL if not found 468 | */ 469 | symbol_t *hashtable_symbol_exists( int hash, const char *sym ) 470 | { 471 | hashchain_t *hc; 472 | symbol_t *s; 473 | hc = hashtable_get( symtable, hash ); 474 | while( hc != NULL ) { 475 | s = (symbol_t*)hc->data; 476 | if ( !strcmp( sym, s->name ) ) 477 | return s; 478 | hc = hc->next; 479 | } 480 | return NULL; /* no matches */ 481 | } 482 | 483 | 484 | #ifdef MYSORT 485 | /* 486 | Quick symbols by its values 487 | */ 488 | static void sym_sort_v( symbol_t **a, int n ) 489 | { 490 | symbol_t *temp; 491 | symbol_t *m; 492 | int i, j; 493 | 494 | i = 0; 495 | j = n; 496 | m = a[ n>>1 ]; 497 | 498 | do { 499 | // sort by values 500 | while ( a[i]->value < m->value ) i++; 501 | while ( a[j]->value > m->value ) j--; 502 | 503 | if ( i <= j ) { 504 | temp = a[i]; 505 | a[i] = a[j]; 506 | a[j] = temp; 507 | i++; 508 | j--; 509 | } 510 | } while ( i <= j ); 511 | 512 | if ( j > 0 ) sym_sort_v( a, j ); 513 | if ( n > i ) sym_sort_v( a+i, n-i ); 514 | } 515 | #else 516 | /* Comparator function for quicksorting. */ 517 | static int symlist_cmp( const void *e1, const void *e2 ) 518 | { 519 | const symbol_t *a, *b; 520 | 521 | a = *(const symbol_t **)e1; 522 | b = *(const symbol_t **)e2; 523 | 524 | return ( a->value - b->value ); 525 | } 526 | #endif 527 | 528 | 529 | void sort_symbols( void ) 530 | { 531 | int i, n; 532 | symbol_t *s; 533 | symbol_t **symlist; 534 | 535 | if ( symtable.chains <= 1 ) 536 | { 537 | return; // nothing to sort actually 538 | } 539 | 540 | // alloc max possible buffer for storing all symbol pointers 541 | symlist = malloc( symtable.chains * sizeof( symbol_t* ) ); 542 | 543 | // now count only used symbols 544 | n = 0; 545 | s = symbols; 546 | while ( s != NULL ) 547 | { 548 | if ( !s->ignore ) 549 | { 550 | symlist[n] = s; 551 | n++; 552 | } 553 | s = s->next; 554 | } 555 | 556 | // nothing to sort? 557 | if ( n <= 1 ) 558 | { 559 | free( symlist ); 560 | return; 561 | } 562 | 563 | #ifdef _DEBUG 564 | report( "Quick-sorting %d symbols\n", n ); 565 | #endif 566 | 567 | #ifdef MYSORT 568 | sym_sort_v( symlist, n-1 ); 569 | #else 570 | qsort( symlist, n, sizeof(symbol_t*), symlist_cmp ); 571 | #endif 572 | 573 | // re-link chains 574 | s = symbols = symlist[0]; 575 | for ( i = 1; i < n; i++ ) 576 | { 577 | s->next = symlist[i]; 578 | s = s->next; 579 | } 580 | 581 | lastSymbol = s; 582 | s->next = NULL; 583 | free( symlist ); 584 | } 585 | 586 | 587 | /* 588 | Problem: 589 | BYTE values are specified as signed decimal string. A properly functional 590 | atoip() will cap large signed values at 0x7FFFFFFF. Negative word values are 591 | often specified as very large decimal values by lcc. Therefore, values that 592 | should be between 0x7FFFFFFF and 0xFFFFFFFF come out as 0x7FFFFFFF when using 593 | atoi(). Bad. 594 | */ 595 | int atoiNoCap( const char *s ) 596 | { 597 | int sign; 598 | int c, v; 599 | 600 | if ( !s ) 601 | return 0; 602 | 603 | // skip spaces 604 | while ( ( c = (*s & 255) ) <= ' ' ) 605 | { 606 | if ( !c ) 607 | return 0; 608 | s++; 609 | } 610 | 611 | // check sign 612 | switch ( c ) 613 | { 614 | case '-': 615 | s++; 616 | sign = 1; 617 | break; 618 | case '+': 619 | s++; 620 | default: 621 | sign = 0; 622 | break; 623 | } 624 | 625 | v = 0; // resulting value 626 | 627 | for ( ;; ) 628 | { 629 | c = *s++ & 255; 630 | if ( c < '0' || c > '9' ) 631 | break; 632 | v = v * 10 + (c - '0'); 633 | } 634 | 635 | if ( sign ) 636 | v = -v; 637 | return v; 638 | } 639 | 640 | 641 | /* 642 | ============ 643 | CodeError 644 | ============ 645 | */ 646 | void CodeError( char *fmt, ... ) { 647 | va_list argptr; 648 | 649 | errorCount++; 650 | 651 | fprintf( stderr, "%s:%i ", currentFileName, currentFileLine ); 652 | 653 | va_start( argptr,fmt ); 654 | vfprintf( stderr, fmt, argptr ); 655 | va_end( argptr ); 656 | 657 | } 658 | 659 | 660 | /* 661 | ============ 662 | EmitByte 663 | ============ 664 | */ 665 | void EmitByte( segment_t *seg, int v ) { 666 | if ( seg->imageUsed >= MAX_IMAGE ) { 667 | Error( "MAX_IMAGE" ); 668 | } 669 | seg->image[ seg->imageUsed ] = v; 670 | seg->imageUsed++; 671 | } 672 | 673 | 674 | /* 675 | ============ 676 | EmitInt 677 | 678 | Emit integer to corresponding segment in little-endian representation 679 | ============ 680 | */ 681 | void EmitInt( segment_t *seg, int v ) { 682 | if ( seg->imageUsed >= MAX_IMAGE - 4) { 683 | Error( "MAX_IMAGE" ); 684 | } 685 | seg->image[ seg->imageUsed ] = v & 255; 686 | seg->image[ seg->imageUsed + 1 ] = ( v >> 8 ) & 255; 687 | seg->image[ seg->imageUsed + 2 ] = ( v >> 16 ) & 255; 688 | seg->image[ seg->imageUsed + 3 ] = ( v >> 24 ) & 255; 689 | seg->imageUsed += 4; 690 | } 691 | 692 | 693 | symbol_t* FindSymbol( const char *sym ) { 694 | symbol_t *s; 695 | hashchain_t *hc; 696 | hc = hashtable_get( symtable, HashSymbol( sym ) ); 697 | while ( hc ) { 698 | s = (symbol_t*)hc->data; 699 | if ( !strcmp( sym, s->name ) ) 700 | return s; 701 | hc = hc->next; 702 | } 703 | return NULL; 704 | } 705 | 706 | 707 | sourceOps_t *FindOpcode( const char *opcode ) { 708 | sourceOps_t *op; 709 | hashchain_t *hc; 710 | hc = hashtable_get( optable, HashOpcode( opcode ) ); 711 | while ( hc ) { 712 | op = (sourceOps_t*)hc->data; 713 | if ( !strcmp( opcode, op->name ) ) 714 | return op; 715 | hc = hc->next; 716 | } 717 | return NULL; 718 | } 719 | 720 | 721 | symbol_t* FindIgnoreSymbol( const char *sym ) { 722 | symbol_t *s; 723 | hashchain_t *hc; 724 | hc = hashtable_get( symtable, HashSymbol( sym ) ); 725 | while ( hc ) { 726 | s = (symbol_t*)hc->data; 727 | if ( s->ignore && !strcmp( sym, s->name ) 728 | // strict checks 729 | && s->file.name == currentFileName 730 | && s->file.line == currentFileLine ) 731 | return s; 732 | hc = hc->next; 733 | } 734 | return NULL; 735 | } 736 | 737 | 738 | /* 739 | ============ 740 | IsIgnoredSymbol 741 | 742 | returns non-NULL in case of ignored symbol 743 | ============ 744 | */ 745 | symbol_t *IsIgnoredSymbol( const char *sym ) { 746 | char buf[MAX_LINE_LENGTH]; 747 | symbol_t *s; 748 | 749 | if ( sym[0] == '$' ) { 750 | sprintf( buf, "%s_%i", sym, currentFileIndex ); 751 | s = FindIgnoreSymbol( buf ); // local 752 | if ( s ) 753 | return s; 754 | } else { 755 | sprintf( buf, "%s^%i", sym, currentFileIndex ); 756 | s = FindIgnoreSymbol( buf ); // static 757 | if ( s ) 758 | return s; 759 | s = FindIgnoreSymbol( sym ); // global 760 | if ( s ) 761 | return s; 762 | } 763 | return NULL; 764 | } 765 | 766 | 767 | /* 768 | ============ 769 | CheckIgnoredSymbols 770 | 771 | check and set ignore flags for non-referenced symbols 772 | ============ 773 | */ 774 | int CheckIgnoredSymbols( void ) { 775 | symbol_t *s; 776 | int count = 0; 777 | for ( s = symbols ; s ; s = s->next ) { 778 | if ( s->ignore || s->type == ST_RESERVED ) 779 | continue; 780 | if ( s->refcnt > 0 ) { 781 | s->refcnt = 0; // reset for further optimization passes 782 | continue; 783 | } 784 | s->ignore = 1; 785 | count++; 786 | } 787 | return count; 788 | } 789 | 790 | 791 | /* 792 | ============ 793 | DefineSymbol 794 | 795 | Symbols can only be defined on PASS_DEFINE 796 | ============ 797 | */ 798 | void DefineSymbol( const char *sym, int value, symtype_t type, qboolean allowStatic ) { 799 | char buf[MAX_LINE_LENGTH]; 800 | symbol_t *s; 801 | int hash; 802 | 803 | if ( passNumber != PASS_DEFINE || ignoreFunc ) 804 | return; // NULL 805 | 806 | // add the file suffix to local symbols to guarantee unique 807 | if ( sym[0] == '$' ) { 808 | sprintf( buf, "%s_%i", sym, currentFileIndex ); 809 | sym = buf; 810 | } else 811 | // special suffix for static symbols 812 | if ( allowStatic && ( *symExport == 0 || strcmp( sym, symExport ) != 0 ) ) { 813 | sprintf( buf, "%s^%i", sym, currentFileIndex ); 814 | sym = buf; 815 | } 816 | 817 | *symExport = '\0'; // reset export symbol 818 | 819 | hash = HashSymbol( sym ); // save as hash value can be used later 820 | 821 | s = hashtable_symbol_exists( hash, sym ); 822 | if ( s ) { 823 | // can happen on second PASS_DEFINE or PASS_OPTIMIZE 824 | if ( s->file.name != currentFileName || s->file.line != currentFileLine ) { 825 | // symbol exists but was defined elsewhere 826 | CodeError( "%s already defined (%s:%i)\n", sym, s->file.name, s->file.line ); 827 | } else { 828 | s->value = value; 829 | s->segment = currentSegment; 830 | lastSymbol = s; 831 | } 832 | return; 833 | } 834 | 835 | s = malloc( sizeof( *s ) ); 836 | s->next = NULL; 837 | s->name = copystring( sym ); 838 | s->value = value; 839 | s->segment = currentSegment; 840 | 841 | // extra information 842 | s->type = type; 843 | s->refcnt = 0; 844 | s->ignore = 0; 845 | s->file.name = currentFileName; 846 | s->file.line = currentFileLine; 847 | 848 | hashtable_add( &symtable, hash, s ); 849 | 850 | /* 851 | Hash table lookup already speeds up symbol lookup enormously. 852 | We postpone sorting until end of pass 0. 853 | Since we're not doing the insertion sort, lastSymbol should always 854 | wind up pointing to the end of list. 855 | This allows constant time for adding to the list. 856 | -PH 857 | */ 858 | if ( symbols == NULL ) { 859 | lastSymbol = symbols = s; 860 | } else { 861 | lastSymbol->next = s; 862 | lastSymbol = s; 863 | } 864 | 865 | return; //s 866 | } 867 | 868 | 869 | /* 870 | ============ 871 | LookupSymbol 872 | 873 | Perform lookup and return symbol value 874 | ============ 875 | */ 876 | int LookupSymbol( const char *sym ) { 877 | char buf[MAX_LINE_LENGTH]; 878 | symbol_t *s; 879 | 880 | // can't operate at this stage 881 | if ( passNumber == PASS_DEFINE ) 882 | return 0; 883 | 884 | // ignore all symbol lookups inside ignore scope 885 | if ( ignoreFunc ) 886 | return 0; 887 | 888 | // now perform lookup and set reference counters 889 | if ( sym[0] == '$' ) { 890 | sprintf( buf, "%s_%i", sym, currentFileIndex ); 891 | s = FindSymbol( buf ); // local 892 | if ( s ) { 893 | s->refcnt++; 894 | return (s->segment->segmentBase + s->value); 895 | } 896 | } else { 897 | // look for static first 898 | sprintf( buf, "%s^%i", sym, currentFileIndex ); 899 | s = FindSymbol( buf ); 900 | if ( s ) { 901 | s->refcnt++; 902 | return (s->segment->segmentBase + s->value); 903 | } 904 | // now search as global 905 | s = FindSymbol( sym ); 906 | if ( s ) { 907 | s->refcnt++; 908 | return (s->segment->segmentBase + s->value); 909 | } 910 | } 911 | 912 | CodeError( "error: symbol %s undefined\n", sym ); 913 | return 0; 914 | } 915 | 916 | 917 | 918 | /* 919 | ============== 920 | ExtractLine 921 | 922 | Extracts the next line from the given text block. 923 | If a full line isn't parsed, returns NULL 924 | Otherwise returns the updated parse pointer 925 | =============== 926 | */ 927 | char *ExtractLine( char *data ) { 928 | /* Goal: 929 | Given a string `data', extract one text line into buffer `lineBuffer' that 930 | is no longer than MAX_LINE_LENGTH characters long. Return value is 931 | remainder of `data' that isn't part of `lineBuffer'. 932 | -PH 933 | */ 934 | /* Hand-optimized by PhaethonH */ 935 | char *p, *q; 936 | 937 | currentFileLine++; 938 | 939 | lineParseOffset = 0; 940 | token[0] = 0; 941 | *lineBuffer = 0; 942 | 943 | p = q = data; 944 | if (!*q) { 945 | return NULL; 946 | } 947 | 948 | for ( ; !((*p == 0) || (*p == '\n')); p++) /* nop */ ; 949 | 950 | if ((p - q) >= MAX_LINE_LENGTH) { 951 | CodeError( "MAX_LINE_LENGTH" ); 952 | return data; 953 | } 954 | 955 | memcpy( lineBuffer, data, (p - data) ); 956 | lineBuffer[(p - data)] = 0; 957 | p += (*p == '\n') ? 1 : 0; /* Skip over final newline. */ 958 | return p; 959 | } 960 | 961 | /* 962 | ============== 963 | Parse 964 | 965 | Parse a token out of linebuffer 966 | ============== 967 | */ 968 | qboolean Parse( void ) { 969 | /* Hand-optimized by PhaethonH */ 970 | const char *p, *q; 971 | 972 | /* Because lineParseOffset is only updated just before exit, this makes this code version somewhat harder to debug under a symbolic debugger. */ 973 | 974 | *token = 0; /* Clear token. */ 975 | 976 | // skip whitespace 977 | for (p = lineBuffer + lineParseOffset; *p && (*p <= ' '); p++) /* nop */ ; 978 | 979 | // skip ; comments 980 | // die on end-of-string 981 | if ((*p == ';') || (*p == 0)) { 982 | lineParseOffset = p - lineBuffer; 983 | return qfalse; 984 | } 985 | 986 | q = p; /* Mark the start of token. */ 987 | /* Find separator first. */ 988 | for ( ; *p > 32; p++) /* nop */ ; /* XXX: unsafe assumptions. */ 989 | /* *p now sits on separator. Mangle other values accordingly. */ 990 | strncpy(token, q, p - q); 991 | token[p - q] = 0; 992 | 993 | lineParseOffset = p - lineBuffer; 994 | 995 | return qtrue; 996 | } 997 | 998 | 999 | /* 1000 | ============== 1001 | ParseValue 1002 | ============== 1003 | */ 1004 | int ParseValue( void ) { 1005 | Parse(); 1006 | return atoiNoCap( token ); 1007 | } 1008 | 1009 | 1010 | /* 1011 | ============== 1012 | ParseExpression 1013 | ============== 1014 | */ 1015 | int ParseExpression( void ) { 1016 | /* Hand optimization, PhaethonH */ 1017 | int i, j; 1018 | char sym[MAX_LINE_LENGTH]; 1019 | int v; 1020 | 1021 | /* Skip over a leading minus. */ 1022 | for ( i = ((token[0] == '-') ? 1 : 0) ; i < MAX_LINE_LENGTH ; i++ ) { 1023 | if ( token[i] == '+' || token[i] == '-' || token[i] == 0 ) { 1024 | break; 1025 | } 1026 | } 1027 | 1028 | memcpy( sym, token, i ); 1029 | sym[i] = 0; 1030 | 1031 | // resolve depending on first character 1032 | switch (*sym) { 1033 | /* Optimizing compilers can convert cases into "calculated jumps". I think these are faster. -PH */ 1034 | case '-': 1035 | case '0': case '1': case '2': case '3': case '4': 1036 | case '5': case '6': case '7': case '8': case '9': 1037 | v = atoiNoCap( sym ); 1038 | break; 1039 | default: 1040 | v = LookupSymbol( sym ); 1041 | break; 1042 | } 1043 | 1044 | // parse add / subtract offsets 1045 | while ( token[i] != 0 ) { 1046 | for ( j = i + 1 ; j < MAX_LINE_LENGTH ; j++ ) { 1047 | if ( token[j] == '+' || token[j] == '-' || token[j] == 0 ) { 1048 | break; 1049 | } 1050 | } 1051 | 1052 | memcpy( sym, token+i+1, j-i-1 ); 1053 | sym[j-i-1] = 0; 1054 | 1055 | switch ( token[i] ) { 1056 | case '+': 1057 | v += atoiNoCap( sym ); 1058 | break; 1059 | case '-': 1060 | v -= atoiNoCap( sym ); 1061 | break; 1062 | } 1063 | 1064 | i = j; 1065 | } 1066 | 1067 | return v; 1068 | } 1069 | 1070 | 1071 | /* 1072 | ============== 1073 | SwitchToSegment 1074 | 1075 | BIG HACK: I want to put all 32 bit values in the data 1076 | segment so they can be byte swapped, and all char data in the lit 1077 | segment, but switch jump tables are emitted in the lit segment and 1078 | initialized strng variables are put in the data segment. 1079 | 1080 | I can change segments here, but I also need to fixup the 1081 | label that was just defined 1082 | 1083 | Note that the lit segment is read-write in the VM, so strings 1084 | aren't read only as in some architectures. 1085 | ============== 1086 | */ 1087 | void SwitchToSegment( segmentName_t seg ) { 1088 | if ( currentSegment == &segment[seg] ) { 1089 | return; 1090 | } 1091 | report( "%s %i: SwitchToSerment %i\n", currentFileName, currentFileLine, seg ); 1092 | currentSegment = &segment[seg]; 1093 | if ( passNumber == PASS_DEFINE ) { 1094 | lastSymbol->segment = currentSegment; 1095 | lastSymbol->value = currentSegment->imageUsed; 1096 | } 1097 | } 1098 | 1099 | 1100 | // call instructions reset currentArgOffset 1101 | ASMF(CALL) 1102 | { 1103 | EmitByte( &segment[CODESEG], OP_CALL ); 1104 | instructionCount++; 1105 | currentArgOffset = 0; 1106 | return 1; 1107 | } 1108 | 1109 | 1110 | // arg is converted to a reversed store 1111 | ASMF(ARG) 1112 | { 1113 | EmitByte( &segment[CODESEG], OP_ARG ); 1114 | instructionCount++; 1115 | if ( 8 + currentArgOffset >= 256 ) { 1116 | CodeError( "currentArgOffset >= 256" ); 1117 | return 0; 1118 | } 1119 | EmitByte( &segment[CODESEG], 8 + currentArgOffset ); 1120 | currentArgOffset += 4; 1121 | return 1; 1122 | } 1123 | 1124 | 1125 | // ret just leaves something on the op stack 1126 | ASMF(RET) 1127 | { 1128 | EmitByte( &segment[CODESEG], OP_LEAVE ); 1129 | instructionCount++; 1130 | EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs ); 1131 | return 1; 1132 | } 1133 | 1134 | 1135 | // pop is needed to discard the return value of a function 1136 | ASMF(POP) 1137 | { 1138 | EmitByte( &segment[CODESEG], OP_POP ); 1139 | instructionCount++; 1140 | return 1; 1141 | } 1142 | 1143 | 1144 | // address of a parameter is converted to OP_LOCAL 1145 | ASMF(ADDRF) 1146 | { 1147 | int v; 1148 | Parse(); 1149 | v = ParseExpression(); 1150 | instructionCount++; 1151 | v = 16 + currentArgs + currentLocals + v; 1152 | EmitByte( &segment[CODESEG], OP_LOCAL ); 1153 | EmitInt( &segment[CODESEG], v ); 1154 | return 1; 1155 | } 1156 | 1157 | 1158 | // address of a local is converted to OP_LOCAL 1159 | ASMF(ADDRL) 1160 | { 1161 | int v; 1162 | Parse(); 1163 | v = ParseExpression(); 1164 | instructionCount++; 1165 | v = 8 + currentArgs + v; 1166 | EmitByte( &segment[CODESEG], OP_LOCAL ); 1167 | EmitInt( &segment[CODESEG], v ); 1168 | return 1; 1169 | } 1170 | 1171 | 1172 | ASMF(PROC) 1173 | { 1174 | char name[1024]; 1175 | 1176 | Parse(); // function name 1177 | strcpy( name, token ); 1178 | 1179 | // we are in optimizing stage(s) 1180 | if ( ignoreFunc == 0 ) { 1181 | if ( IsIgnoredSymbol( name ) ) { 1182 | ignoreFunc++; 1183 | } 1184 | } else { 1185 | // should never happen but anyway 1186 | ignoreFunc++; 1187 | } 1188 | 1189 | if ( ignoreFunc > 0 ) 1190 | return 1; 1191 | 1192 | if ( !entry ) { 1193 | if ( strcmp( token, "vmMain" ) ) 1194 | printf( "Warning: entry point should be 'vmMain' instead of '%s'\n", token ); 1195 | DefineSymbol( token, instructionCount, ST_RESERVED, qtrue ); 1196 | entry = symbols; 1197 | } else { 1198 | DefineSymbol( token, instructionCount, ST_FUNCTION, qtrue ); 1199 | } 1200 | 1201 | currentLocals = ParseValue(); // locals 1202 | currentLocals = ( currentLocals + 3 ) & ~3; 1203 | currentArgs = ParseValue(); // arg marshalling 1204 | currentArgs = ( currentArgs + 3 ) & ~3; 1205 | 1206 | if ( 8 + currentLocals + currentArgs >= 32767 ) { 1207 | CodeError( "Locals > 32k in %s\n", name ); 1208 | } 1209 | instructionCount++; 1210 | EmitByte( &segment[CODESEG], OP_ENTER ); 1211 | EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs ); 1212 | return 1; 1213 | } 1214 | 1215 | 1216 | ASMF(ENDPROC) 1217 | { 1218 | //int v, v2; 1219 | 1220 | if ( ignoreFunc > 0 ) { 1221 | ignoreFunc--; 1222 | return 1; 1223 | } 1224 | 1225 | //Parse(); // skip the function name 1226 | //v = ParseValue(); // locals 1227 | //v2 = ParseValue(); // arg marshalling 1228 | 1229 | // all functions must leave something on the opstack 1230 | instructionCount++; 1231 | EmitByte( &segment[CODESEG], OP_PUSH ); 1232 | 1233 | instructionCount++; 1234 | EmitByte( &segment[CODESEG], OP_LEAVE ); 1235 | EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs ); 1236 | 1237 | return 1; 1238 | } 1239 | 1240 | 1241 | ASMF(ADDRESS) 1242 | { 1243 | int v; 1244 | 1245 | Parse(); 1246 | v = ParseExpression(); 1247 | 1248 | /* Addresses are 32 bits wide, and therefore go into data segment. */ 1249 | SwitchToSegment( DATASEG ); 1250 | 1251 | EmitInt( currentSegment, v ); 1252 | #if 0 1253 | if ( passNumber == PASS_COMPILE && token[ 0 ] == '$' ) // crude test for labels 1254 | { 1255 | EmitInt( &segment[ JTRGSEG ], v ); 1256 | } 1257 | #endif 1258 | return 1; 1259 | } 1260 | 1261 | 1262 | ASMF(EXPORT) 1263 | { 1264 | Parse(); // symbol name 1265 | strcpy( symExport, token ); 1266 | return 1; 1267 | } 1268 | 1269 | 1270 | ASMF(IMPORT) 1271 | { 1272 | return 1; 1273 | } 1274 | 1275 | 1276 | ASMF(CODE) 1277 | { 1278 | currentSegment = &segment[CODESEG]; 1279 | return 1; 1280 | } 1281 | 1282 | 1283 | ASMF(BSS) 1284 | { 1285 | currentSegment = &segment[BSSSEG]; 1286 | return 1; 1287 | } 1288 | 1289 | 1290 | ASMF(DATA) 1291 | { 1292 | currentSegment = &segment[DATASEG]; 1293 | return 1; 1294 | } 1295 | 1296 | 1297 | ASMF(LIT) 1298 | { 1299 | currentSegment = &segment[LITSEG]; 1300 | return 1; 1301 | } 1302 | 1303 | 1304 | ASMF(LINE) 1305 | { 1306 | return 1; 1307 | } 1308 | 1309 | 1310 | ASMF(FILE) 1311 | { 1312 | return 1; 1313 | } 1314 | 1315 | 1316 | ASMF(EQU) 1317 | { 1318 | char name[1024]; 1319 | 1320 | Parse(); 1321 | strcpy( name, token ); 1322 | Parse(); 1323 | DefineSymbol( name, atoiNoCap( token ), ST_LABEL, qfalse ); 1324 | return 1; 1325 | } 1326 | 1327 | 1328 | ASMF(ALIGN) 1329 | { 1330 | int v; 1331 | v = ParseValue(); 1332 | currentSegment->imageUsed = (currentSegment->imageUsed + v - 1 ) & ~( v - 1 ); 1333 | return 1; 1334 | } 1335 | 1336 | 1337 | ASMF(SKIP) 1338 | { 1339 | int v; 1340 | v = ParseValue(); 1341 | 1342 | currentSegment->imageUsed += v; 1343 | return 1; 1344 | } 1345 | 1346 | 1347 | ASMF(BYTE) 1348 | { 1349 | int i, v, v2; 1350 | 1351 | v = ParseValue(); // size 1352 | v2 = ParseValue(); // value 1353 | 1354 | if ( v == 1 ) { 1355 | // Character (1-byte) values go into lit(eral) segment 1356 | SwitchToSegment( LITSEG ); 1357 | } else if ( v == 4 ) { 1358 | // 32-bit (4-byte) values go into data segment 1359 | SwitchToSegment( DATASEG ); 1360 | } else { 1361 | // and 16-bit (2-byte) values will cause q3asm to barf 1362 | CodeError( "%i bit initialized data not supported", v*8 ); 1363 | return 0; 1364 | } 1365 | 1366 | // emit little endian 1367 | for ( i = 0 ; i < v ; i++ ) { 1368 | EmitByte( currentSegment, (v2 & 255) ); /* paranoid ANDing -PH */ 1369 | v2 >>= 8; 1370 | } 1371 | return 1; 1372 | } 1373 | 1374 | 1375 | // code labels are emitted as instruction counts, not byte offsets, 1376 | // because the physical size of the code will change with 1377 | // different run time compilers and we want to minimize the 1378 | // size of the required translation table 1379 | ASMF(LABEL) 1380 | { 1381 | Parse(); 1382 | 1383 | // never ignore labels inside active code blocks 1384 | if ( currentSegment != &segment[CODESEG] && IsIgnoredSymbol( token ) ) { 1385 | ignoreLabel = 1; 1386 | return 1; 1387 | } else { 1388 | ignoreLabel = 0; 1389 | } 1390 | 1391 | if ( currentSegment == &segment[CODESEG] ) { 1392 | if ( passNumber == PASS_COMPILE ) 1393 | JUSED( instructionCount ); 1394 | DefineSymbol( token, instructionCount, ST_LABEL, qtrue ); 1395 | } else { 1396 | DefineSymbol( token, currentSegment->imageUsed, ST_LABEL, qtrue ); 1397 | } 1398 | 1399 | return 1; 1400 | } 1401 | 1402 | 1403 | /* 1404 | ============== 1405 | AssembleLine 1406 | ============== 1407 | */ 1408 | void AssembleLine( void ) { 1409 | int opcode; 1410 | int expression; 1411 | sourceOps_t *op; 1412 | 1413 | Parse(); 1414 | if ( !token[0] ) { 1415 | return; 1416 | } 1417 | 1418 | op = FindOpcode( token ); 1419 | 1420 | #if 0 1421 | hc = hashtable_get( optable, HashOpcode( token ) ); 1422 | while ( hc ) { 1423 | op = (sourceOps_t*)(hc->data); 1424 | if ( !strcmp( token, op->name ) ) 1425 | break; 1426 | hc = hc->next; 1427 | } 1428 | #endif 1429 | 1430 | if ( !op ) { 1431 | CodeError( "Unknown token: %s\n", token ); 1432 | return; 1433 | } 1434 | 1435 | opcode = op->opcode; 1436 | 1437 | if ( ignoreFunc ) { 1438 | if ( ignoreLabel ) { 1439 | CodeError( "ignore label in ignoreFunc scope" ); 1440 | ignoreLabel = 0; 1441 | } 1442 | switch( opcode ) { 1443 | case DIR_PROC: 1444 | case DIR_ENDPROC: 1445 | //case DIR_ALIGN: 1446 | case DIR_CODE: // NEVER ignore segment directives! 1447 | case DIR_LIT: 1448 | case DIR_BSS: 1449 | case DIR_DATA: 1450 | if ( op->func ) // execute only dedicated directives 1451 | op->func(); 1452 | default: 1453 | break; 1454 | } 1455 | return; // unconditionally return from this scope 1456 | } 1457 | 1458 | if ( ignoreLabel ) { 1459 | switch( opcode ) { 1460 | case DIR_ADDRESS: 1461 | case DIR_SKIP: 1462 | case DIR_BYTE: 1463 | return; 1464 | } 1465 | ignoreLabel = 0; 1466 | } 1467 | 1468 | // execute opcode(directive) function and exit 1469 | if ( op->func ) { 1470 | op->func(); 1471 | return; 1472 | } 1473 | 1474 | // we ignore most conversions 1475 | if ( op->opcode == OP_IGNORE ) { 1476 | return; 1477 | } 1478 | 1479 | // sign extensions need to check next parm 1480 | if ( opcode == OP_SEX8 ) { 1481 | Parse(); 1482 | if ( token[0] == '1' ) { 1483 | opcode = OP_SEX8; 1484 | } else if ( token[0] == '2' ) { 1485 | opcode = OP_SEX16; 1486 | } else { 1487 | CodeError( "Bad sign extension: %s\n", token ); 1488 | return; 1489 | } 1490 | } 1491 | 1492 | // check for expression 1493 | Parse(); 1494 | if ( token[0] && opcode != OP_CVIF && opcode != OP_CVFI ) { 1495 | expression = ParseExpression(); 1496 | // code like this can generate non-dword block copies: 1497 | // auto char buf[2] = " "; 1498 | // we are just going to round up. This might conceivably 1499 | // be incorrect if other initialized chars follow. 1500 | if ( opcode == OP_BLOCK_COPY ) { 1501 | expression = ( expression + 3 ) & ~3; 1502 | } 1503 | EmitByte( &segment[CODESEG], opcode ); 1504 | EmitInt( &segment[CODESEG], expression ); 1505 | } else { 1506 | EmitByte( &segment[CODESEG], opcode ); 1507 | } 1508 | 1509 | instructionCount++; 1510 | } 1511 | 1512 | 1513 | /* 1514 | ============== 1515 | WriteMapFile 1516 | ============== 1517 | */ 1518 | void WriteMapFile( void ) { 1519 | FILE *f; 1520 | symbol_t *s; 1521 | char imageName[MAX_OS_PATH]; 1522 | int seg; 1523 | 1524 | strcpy( imageName, outputFilename ); 1525 | StripExtension( imageName ); 1526 | strcat( imageName, ".map" ); 1527 | 1528 | // Symbols list may be shrunk after sort because of ignored symbols 1529 | // so we probably can't perform normal define/compile passes after that. 1530 | // However, as we already created qvm file before - we can do anything now 1531 | 1532 | sort_symbols(); 1533 | 1534 | report( "Writing %s...\n", imageName ); 1535 | 1536 | f = SafeOpenWrite( imageName ); 1537 | for ( seg = CODESEG ; seg <= BSSSEG ; seg++ ) { 1538 | for ( s = symbols ; s ; s = s->next ) { 1539 | if ( s->name[0] == '$' ) { 1540 | continue; // skip locals 1541 | } 1542 | if ( &segment[seg] != s->segment ) { 1543 | continue; 1544 | } 1545 | fprintf( f, "%i %8x %s\n", seg, s->value, s->name ); 1546 | } 1547 | } 1548 | fclose( f ); 1549 | } 1550 | 1551 | 1552 | /* 1553 | =============== 1554 | WriteVmFile 1555 | =============== 1556 | */ 1557 | void WriteVmFile( void ) { 1558 | char imageName[MAX_OS_PATH]; 1559 | char jtsName[MAX_OS_PATH]; 1560 | const char *errMsg; 1561 | vmHeader_t header; 1562 | FILE *f; 1563 | unsigned int crc; 1564 | int i, headerSize; 1565 | instruction_t *inst; 1566 | 1567 | strcpy( imageName, outputFilename ); 1568 | StripExtension( imageName ); 1569 | strcpy( jtsName, imageName ); 1570 | strcat( imageName, ".qvm" ); 1571 | strcat( jtsName, ".jts" ); 1572 | 1573 | remove( imageName ); 1574 | remove( jtsName ); 1575 | 1576 | if ( errorCount != 0 ) { 1577 | report( "Not writing a file due to errors\n" ); 1578 | return; 1579 | } 1580 | 1581 | report( "code segment: %7i\n", segment[CODESEG].imageUsed ); 1582 | report( "data segment: %7i\n", segment[DATASEG].imageUsed ); 1583 | report( "lit segment: %7i\n", segment[LITSEG].imageUsed ); 1584 | report( "bss segment: %7i\n", segment[BSSSEG].imageUsed ); 1585 | report( "instruction count: %i\n", instructionCount ); 1586 | 1587 | if( !options.vanillaQ3Compatibility ) { 1588 | header.vmMagic = VM_MAGIC_VER2; 1589 | headerSize = sizeof( header ); 1590 | } else { 1591 | header.vmMagic = VM_MAGIC; 1592 | 1593 | // Don't write the VM_MAGIC_VER2 bits when maintaining 1.32b compatibility. 1594 | // (I know this isn't strictly correct due to padding, but then platforms 1595 | // that pad wouldn't be able to write a correct header anyway). Note: if 1596 | // vmHeader_t changes, this needs to be adjusted too. 1597 | headerSize = sizeof( header ) - sizeof( header.jtrgLength ); 1598 | } 1599 | 1600 | // Build jump table targets segment 1601 | segment[JTRGSEG].imageUsed = 0; 1602 | 1603 | inst = ( instruction_t* ) malloc ( (instructionCount + 8) * sizeof( inst[0] ) ); 1604 | memset( inst, 0, (instructionCount + 8) * sizeof( inst[0] ) ); 1605 | 1606 | errMsg = VM_LoadInstructions( segment[CODESEG].image, segment[CODESEG].imageUsed, instructionCount, inst ); 1607 | if ( errMsg ) { 1608 | CodeError( "VM_LoadInstructions: %s\n", errMsg ); 1609 | exit(1); 1610 | } 1611 | 1612 | errMsg = VM_CheckInstructions( inst, instructionCount, 0x7FFFFFFF ); 1613 | if ( errMsg ) { 1614 | CodeError( "VM_CheckInstructions: %s\n", errMsg ); 1615 | exit(1); 1616 | } 1617 | 1618 | //segment[JTRGSEG].segmentBase = segment[BSSSEG].segmentBase + segment[BSSSEG].imageUsed; 1619 | for ( i = 0; i < instructionCount; i++ ) { 1620 | if ( inst[i].jused ) { 1621 | continue; 1622 | } 1623 | if ( jbitmap[i/8]&(1<<(i&7)) ) { 1624 | EmitInt( &segment[JTRGSEG], i ); 1625 | } 1626 | } 1627 | segment[JTRGSEG].imageUsed = (segment[JTRGSEG].imageUsed + 3) & ~3; 1628 | 1629 | header.instructionCount = instructionCount; 1630 | header.codeOffset = headerSize; 1631 | header.codeLength = segment[CODESEG].imageUsed; 1632 | header.dataOffset = header.codeOffset + segment[CODESEG].imageUsed; 1633 | header.dataLength = segment[DATASEG].imageUsed; 1634 | header.litLength = segment[LITSEG].imageUsed; 1635 | header.bssLength = segment[BSSSEG].imageUsed; 1636 | header.jtrgLength = segment[JTRGSEG].imageUsed; 1637 | 1638 | if ( BigEndian() ) { 1639 | // byte swap the header 1640 | for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) { 1641 | ((int *)&header)[i] = LongSwap( ((int *)&header)[i] ); 1642 | } 1643 | } 1644 | 1645 | #ifdef EMITJTS 1646 | 1647 | // Prepare data for storing jump targets segment in external jts file. 1648 | // With VM_MAGIC_VER2 we don't need that but at the same time 1649 | // VM_MAGIC_VER2 terminates Q3 SDK EULA which may be not acceptable. 1650 | // So we export jump targets into separate file while keeping main qvm 1651 | // vq3-compatible. Host engine should be modified to accept jts files. 1652 | // ----------------------------------------------------------------------- 1653 | // Jump targets is required by recent 1.32e engine builds to safely turn on 1654 | // bytecode optimizations which may provide significant performance boost 1655 | 1656 | _crc32_init( &crc ); 1657 | _crc32_update( &crc, (void*)&header, sizeof( header ) - sizeof( header.jtrgLength ) ); 1658 | _crc32_update( &crc, (void*)&segment[CODESEG].image, segment[CODESEG].imageUsed ); 1659 | _crc32_update( &crc, (void*)&segment[DATASEG].image, segment[DATASEG].imageUsed ); 1660 | _crc32_update( &crc, (void*)&segment[LITSEG].image, segment[LITSEG].imageUsed ); 1661 | _crc32_final( &crc ); 1662 | 1663 | // byte swap the sum 1664 | if ( BigEndian() ) { 1665 | for ( i = 0 ; i < sizeof( crc ) / 4 ; i++ ) { 1666 | ((int *)&crc)[i] = LongSwap( ((int *)&crc)[i] ); 1667 | } 1668 | } 1669 | #endif 1670 | 1671 | report( "Writing to %s\n", imageName ); 1672 | 1673 | CreatePath( imageName ); 1674 | f = SafeOpenWrite( imageName ); 1675 | SafeWrite( f, &header, headerSize ); 1676 | SafeWrite( f, &segment[CODESEG].image, segment[CODESEG].imageUsed ); 1677 | SafeWrite( f, &segment[DATASEG].image, segment[DATASEG].imageUsed ); 1678 | SafeWrite( f, &segment[LITSEG].image, segment[LITSEG].imageUsed ); 1679 | 1680 | if ( !options.vanillaQ3Compatibility ) { 1681 | SafeWrite( f, &segment[JTRGSEG].image, segment[JTRGSEG].imageUsed ); 1682 | } 1683 | 1684 | fclose( f ); 1685 | 1686 | #ifdef EMITJTS 1687 | // write jump targets to separate file 1688 | if ( options.vanillaQ3Compatibility ) 1689 | { 1690 | CreatePath( jtsName ); 1691 | f = SafeOpenWrite( jtsName ); 1692 | SafeWrite( f, &crc, sizeof( crc ) ); 1693 | SafeWrite( f, &header.jtrgLength, sizeof( header.jtrgLength ) ); 1694 | SafeWrite( f, &segment[JTRGSEG].image, segment[JTRGSEG].imageUsed ); 1695 | fclose( f ); 1696 | } 1697 | #endif 1698 | 1699 | } 1700 | 1701 | 1702 | void PassDefineCompile( void ) { 1703 | char *ptr; 1704 | int i; 1705 | 1706 | // clear potential garbage 1707 | for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) { 1708 | memset( segment[i].image, 0, MAX_IMAGE ); 1709 | } 1710 | memset( jbitmap, 0, sizeof( jbitmap ) ); 1711 | 1712 | for ( passNumber = PASS_DEFINE ; passNumber <= PASS_COMPILE ; passNumber++ ) { 1713 | segment[LITSEG].segmentBase = segment[DATASEG].imageUsed; 1714 | segment[BSSSEG].segmentBase = segment[LITSEG].segmentBase + segment[LITSEG].imageUsed; 1715 | segment[JTRGSEG].segmentBase = segment[BSSSEG].segmentBase + segment[BSSSEG].imageUsed; 1716 | 1717 | for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) { 1718 | segment[i].imageUsed = 0; 1719 | } 1720 | 1721 | segment[DATASEG].imageUsed = 4; // skip the 0 byte, so NULL pointers are fixed up properly 1722 | instructionCount = 0; 1723 | 1724 | report( "pass #%i: %s\n", ++passCount, passName[ passNumber ] ); 1725 | 1726 | for ( i = 0 ; i < numAsmFiles ; i++ ) { 1727 | currentFileIndex = i; 1728 | currentFileName = asmFileNames[ i ]; 1729 | currentFileLine = 0; 1730 | 1731 | fflush( NULL ); 1732 | ptr = asmFiles[i]; 1733 | ignoreFunc = 0; 1734 | ignoreLabel = 0; 1735 | while ( ptr ) { 1736 | ptr = ExtractLine( ptr ); 1737 | AssembleLine(); 1738 | } 1739 | if ( ignoreFunc ) { 1740 | CodeError( "ignore level %i\n", ignoreFunc ); 1741 | return; 1742 | } 1743 | if ( errorCount ) { 1744 | return; 1745 | } 1746 | } 1747 | // align all segments 1748 | for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) { 1749 | segment[i].imageUsed = (segment[i].imageUsed + 3) & ~3; 1750 | } 1751 | } 1752 | } 1753 | 1754 | 1755 | void PassOptimize( void ) { 1756 | char *ptr; 1757 | int i; 1758 | 1759 | passNumber = PASS_OPTIMIZE; 1760 | 1761 | if ( !CheckIgnoredSymbols() ) 1762 | return; 1763 | 1764 | do { 1765 | for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) { 1766 | segment[i].imageUsed = 0; 1767 | segment[i].segmentBase = 0; 1768 | } 1769 | 1770 | report( "pass #%i: %s\n", ++passCount, passName[ passNumber ] ); 1771 | 1772 | for ( i = 0 ; i < numAsmFiles ; i++ ) { 1773 | currentFileIndex = i; 1774 | currentFileName = asmFileNames[ i ]; 1775 | currentFileLine = 0; 1776 | fflush( NULL ); 1777 | ptr = asmFiles[i]; 1778 | ignoreFunc = 0; 1779 | ignoreLabel = 0; 1780 | while ( ptr ) { 1781 | ptr = ExtractLine( ptr ); 1782 | AssembleLine(); 1783 | } 1784 | if ( ignoreFunc ) { 1785 | CodeError( "ignore level is not zero: %i\n", ignoreFunc ); 1786 | return; 1787 | } 1788 | } 1789 | } while ( CheckIgnoredSymbols() > 0 ); 1790 | } 1791 | 1792 | 1793 | /* 1794 | =============== 1795 | Assemble 1796 | =============== 1797 | */ 1798 | void Assemble( void ) { 1799 | int i; 1800 | char filename[MAX_OS_PATH]; 1801 | 1802 | report( "Output filename: %s\n", outputFilename ); 1803 | 1804 | for ( i = 0 ; i < numAsmFiles ; i++ ) { 1805 | strcpy( filename, asmFileNames[ i ] ); 1806 | DefaultExtension( filename, ".asm" ); 1807 | LoadFile( filename, (void **)&asmFiles[i] ); 1808 | } 1809 | 1810 | PassDefineCompile(); 1811 | 1812 | if ( options.optimize ) { 1813 | // errors is unacceptable 1814 | if ( errorCount ) 1815 | return; 1816 | PassOptimize(); 1817 | if ( errorCount ) 1818 | return; 1819 | lastSymbol = NULL; 1820 | instructionCount = 0; 1821 | currentSegment = NULL; 1822 | for ( i = 0; i < NUM_SEGMENTS; i++ ) { 1823 | segment[i].imageUsed = 0; 1824 | segment[i].segmentBase = 0; 1825 | } 1826 | // perform final fixup 1827 | PassDefineCompile(); 1828 | } 1829 | 1830 | passNumber = PASS_DEFINE; 1831 | 1832 | // reserve the stack in bss 1833 | DefineSymbol( "_stackStart", segment[BSSSEG].imageUsed, ST_RESERVED, qfalse ); 1834 | segment[BSSSEG].imageUsed += stackSize; 1835 | DefineSymbol( "_stackEnd", segment[BSSSEG].imageUsed, ST_RESERVED, qfalse ); 1836 | 1837 | // write the image 1838 | WriteVmFile(); 1839 | 1840 | // write the map file even if there were errors 1841 | if( options.writeMapFile ) { 1842 | WriteMapFile(); 1843 | } 1844 | } 1845 | 1846 | 1847 | /* 1848 | ============= 1849 | ParseOptionFile 1850 | ============= 1851 | */ 1852 | void ParseOptionFile( const char *filename ) { 1853 | char expanded[MAX_OS_PATH]; 1854 | char *text, *text_p; 1855 | 1856 | strcpy( expanded, filename ); 1857 | DefaultExtension( expanded, ".q3asm" ); 1858 | LoadFile( expanded, (void **)&text ); 1859 | if ( !text ) { 1860 | return; 1861 | } 1862 | 1863 | text_p = text; 1864 | 1865 | while( ( text_p = COM_Parse( text_p ) ) != 0 ) { 1866 | if ( !strcmp( com_token, "-o" ) ) { 1867 | // allow output override in option file 1868 | text_p = COM_Parse( text_p ); 1869 | if ( text_p ) { 1870 | strcpy( outputFilename, com_token ); 1871 | } 1872 | continue; 1873 | } 1874 | 1875 | asmFileNames[ numAsmFiles ] = copystring( com_token ); 1876 | numAsmFiles++; 1877 | } 1878 | } 1879 | 1880 | 1881 | static const char *banner = { 1882 | "Usage: %s [OPTION]... [FILES]...\n" 1883 | "Assemble LCC bytecode assembly to Q3VM bytecode.\n" 1884 | "\n" 1885 | " -o OUTPUT Write assembled output to file OUTPUT.qvm\n" 1886 | " -f LISTFILE Read options and list of files to assemble from LISTFILE\n" 1887 | " -b BUCKETS Set symbol hash table to BUCKETS buckets\n" 1888 | " -r Remove unreferenced symbols/functions from image\n" 1889 | " -m Write map file\n" 1890 | " -v Verbose compilation report\n" 1891 | " -vq3 Produce a qvm file compatible with Q3 1.32b\n" 1892 | " -- Stop switches parsing\n" 1893 | }; 1894 | 1895 | 1896 | /* 1897 | ============== 1898 | main 1899 | ============== 1900 | */ 1901 | int main( int argc, const char *argv[] ) { 1902 | int i; 1903 | double start, end; 1904 | const char *bin; 1905 | 1906 | if ( argc < 2 ) { 1907 | // strip binary name 1908 | bin = strrchr( argv[0], PATH_SEP ); 1909 | if ( !bin || !bin[1] ) 1910 | bin = argv[0]; 1911 | else 1912 | bin++; 1913 | printf( banner, bin ); 1914 | return 0; 1915 | } 1916 | 1917 | start = I_FloatTime (); 1918 | 1919 | // default filename is "q3asm" 1920 | strcpy( outputFilename, "q3asm" ); 1921 | numAsmFiles = 0; 1922 | 1923 | for ( i = 1 ; i < argc ; i++ ) { 1924 | if ( argv[i][0] != '-' ) { 1925 | break; 1926 | } 1927 | if ( !strcmp( argv[i], "-o" ) ) { 1928 | if ( i == argc - 1 ) { 1929 | Error( "-o must precede a filename" ); 1930 | } 1931 | /* Timbo of Tremulous pointed out -o not working; stock ID q3asm folded in the change. Yay. */ 1932 | strcpy( outputFilename, argv[ i+1 ] ); 1933 | i++; 1934 | continue; 1935 | } 1936 | 1937 | if ( !strcmp( argv[i], "-f" ) ) { 1938 | if ( i == argc - 1 ) { 1939 | Error( "-f must precede a filename" ); 1940 | } 1941 | ParseOptionFile( argv[ i+1 ] ); 1942 | i++; 1943 | continue; 1944 | } 1945 | 1946 | if ( !strcmp( argv[i], "-b" ) ) { 1947 | if ( i == argc - 1 ) { 1948 | Error( "-b requires an argument" ); 1949 | } 1950 | i++; 1951 | symtablelen = atoiNoCap( argv[ i ] ); 1952 | continue; 1953 | } 1954 | 1955 | if ( !strcmp( argv[ i ], "-r" ) ) { 1956 | options.optimize = qtrue; 1957 | continue; 1958 | } 1959 | 1960 | /* 1961 | Verbosity option added by Timbo, 2002.09.14. 1962 | By default (no -v option), q3asm remains silent except for critical errors. 1963 | Verbosity turns on all messages, error or not. 1964 | Motivation: not wanting to scrollback for pages to find asm error. 1965 | */ 1966 | if( !strcmp( argv[ i ], "-v" ) ) { 1967 | options.verbose = qtrue; 1968 | continue; 1969 | } 1970 | 1971 | if( !strcmp( argv[ i ], "-m" ) ) { 1972 | options.writeMapFile = qtrue; 1973 | continue; 1974 | } 1975 | 1976 | if( !strcmp( argv[ i ], "-vq3" ) ) { 1977 | options.vanillaQ3Compatibility = qtrue; 1978 | continue; 1979 | } 1980 | 1981 | if( !strcmp( argv[ i ], "--" ) ) { 1982 | break; 1983 | } 1984 | 1985 | Error( "Unknown option: %s", argv[ i ] ); 1986 | } 1987 | 1988 | // the rest of the command line args are asm files 1989 | for ( ; i < argc ; i++ ) { 1990 | asmFileNames[ numAsmFiles ] = copystring( argv[ i ] ); 1991 | numAsmFiles++; 1992 | } 1993 | 1994 | InitTables(); 1995 | Assemble(); 1996 | 1997 | // do not print stats/timings on error 1998 | if ( errorCount ) { 1999 | return errorCount; 2000 | } 2001 | 2002 | if ( options.verbose ) { 2003 | hashtable_stats( &symtable ); 2004 | #ifdef _DEBUG 2005 | hashtable_stats( &optable ); 2006 | #endif 2007 | } 2008 | 2009 | end = I_FloatTime(); 2010 | report( "%5.3f seconds elapsed\n---------------------\n", end-start ); 2011 | 2012 | return 0; 2013 | } 2014 | 2015 | --------------------------------------------------------------------------------