├── .gitignore ├── LICENSE ├── README.md ├── g_file.cpp ├── g_file.h ├── g_main.c ├── g_main.h ├── g_save.c ├── g_save.h ├── g_thread.cpp ├── g_thread.h ├── g_time.cpp ├── g_time.h ├── game.def ├── game.h ├── gamex86.sln ├── gamex86.vcxproj ├── gamex86.vcxproj.filters ├── meson.build ├── meson_options.txt ├── shared ├── api.h ├── client.h ├── entity.h ├── platform.h ├── shared.c └── shared.h ├── vm.c ├── vm.h ├── vm_debug.c ├── vm_debug.h ├── vm_ext.c ├── vm_ext.h ├── vm_file.c ├── vm_file.h ├── vm_game.c ├── vm_game.h ├── vm_gi.c ├── vm_gi.h ├── vm_hash.c ├── vm_hash.h ├── vm_heap.c ├── vm_heap.h ├── vm_list.c ├── vm_list.h ├── vm_math.c ├── vm_math.h ├── vm_mem.c ├── vm_mem.h ├── vm_opcodes.c.h ├── vm_opcodes.h ├── vm_string.c ├── vm_string.h ├── vm_string_list.c ├── vm_structlist.c ├── vm_structlist.h └── windows └── unistd.h /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | /progs/*.asm 290 | /progs/*.bak 291 | /*.exe 292 | /KMQuake2 Debug 293 | /KMQuake2 Release 294 | /Vanilla Debug 295 | /Vanilla Release 296 | /Lexilla.dll 297 | /SciLexer.dll 298 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is this? 2 | It's 1996; Quake II is deep in development. They come to the realization that QC is quite limited and slow, and they consider moving to a native binary library to handle their game mods... except, they don't. They instead decide to improve QC and continue to build their next big game using QC as the game code handler. 3 | 4 | That's the alternate reality I have constructed here. An alternate reality where Carmack & Co have switched to C++20 (somehow). Take a peek into a dimension full of VMs! 5 | 6 | # Oh.. but why? 7 | This started as a joke in the Quake Mapping & Quake Legacy Discords. I had an idea to construct a VM inside of a Q2 game DLL and natively load Quake 1 progs.dat's to be able to run Quake 1 mods inside of Quake 2. Sadly that idea never made it beyond initial testing (although it might still be mostly possible!!), but I then decided to simply convert Quake 2's code to QC and load that instead. 8 | 9 | Those more familiar with QC can jump on in and create mods for Quake II without needing to touch any C code at all. 10 | 11 | # I wanna make a progs - how do? 12 | See the https://github.com/Paril/quake2c-progs repo for the actual progs source - this is just the game DLL! 13 | -------------------------------------------------------------------------------- /g_file.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #define QCVM_INTERNAL 4 | #include "shared/shared.h" 5 | #include "vm.h" 6 | #include "g_file.h" 7 | #include "vm_string.h" 8 | } 9 | 10 | #include 11 | #include 12 | #include 13 | namespace fs = std::filesystem; 14 | 15 | const char *qcvm_cpp_absolute_path(const qcvm_t *vm, const char *relative) 16 | { 17 | return qcvm_temp_format(vm, "%s", fs::absolute(relative).string().c_str()); 18 | } 19 | 20 | char **qcvm_get_file_list(const qcvm_t *vm, const char *path, const char *ext, int32_t *num) 21 | { 22 | std::vector paths; 23 | 24 | // filtered search 25 | if (strchr(path, '*') || strchr(path, '?') || strchr(path, '[') || strchr(path, ']')) 26 | { 27 | // convert to regex; 28 | // keep \ escapes intact 29 | // convert * to [^/\\]* 30 | // convert ? to . 31 | // convert [! to [^ 32 | // escape . outside of character classes with \. 33 | std::string raw_regex(path); 34 | bool inside_class = false; 35 | 36 | for (size_t i = 0; i < raw_regex.size(); i++) 37 | { 38 | switch (raw_regex[i]) 39 | { 40 | case '.': 41 | if (!inside_class) 42 | { 43 | raw_regex.insert(i, "\\"); 44 | i++; 45 | } 46 | continue; 47 | case '\\': 48 | i++; 49 | continue; 50 | case '*': 51 | raw_regex.insert(i, "[^/\\\\]"); 52 | i += 6; 53 | continue; 54 | case '[': 55 | inside_class = true; 56 | if (raw_regex[i + 1] == '!') 57 | raw_regex[i + 1] = '^'; 58 | continue; 59 | case ']': 60 | inside_class = false; 61 | continue; 62 | } 63 | } 64 | 65 | std::regex reg(raw_regex); 66 | fs::path base_path = qcvm_temp_format(vm, "%s", vm->path); 67 | 68 | for (auto &p : fs::recursive_directory_iterator(base_path)) 69 | if (p.is_regular_file()) 70 | { 71 | std::string str = p.path().string().substr(strlen(vm->path)); 72 | std::replace(str.begin(), str.end(), '\\', '/'); 73 | bool matched = std::regex_match(str, reg); 74 | 75 | if (matched) 76 | paths.push_back(str); 77 | } 78 | } 79 | // basic search 80 | else 81 | { 82 | fs::path base_path = qcvm_temp_format(vm, "%s%s", vm->path, path); 83 | 84 | for (auto &p : fs::directory_iterator(base_path)) 85 | if (p.is_regular_file() && p.path().has_extension() && p.path().extension().string().substr(1).compare(ext) == 0) 86 | { 87 | std::string str = p.path().string().substr(strlen(vm->path)); 88 | std::replace(str.begin(), str.end(), '\\', '/'); 89 | paths.push_back(str); 90 | } 91 | } 92 | 93 | char **results = (char **)qcvm_alloc(vm, sizeof(char *) * paths.size()); 94 | 95 | for (size_t i = 0; i < paths.size(); i++) 96 | { 97 | results[i] = (char *)qcvm_alloc(vm, sizeof(char) * paths[i].size() + 1); 98 | memcpy(results[i], paths[i].data(), paths[i].size()); 99 | } 100 | 101 | *num = (int32_t)paths.size(); 102 | return results; 103 | } -------------------------------------------------------------------------------- /g_file.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const char *qcvm_cpp_absolute_path(const qcvm_t *vm, const char *relative); 4 | 5 | char **qcvm_get_file_list(const qcvm_t *vm, const char *path, const char *ext, int32_t *num); 6 | -------------------------------------------------------------------------------- /g_main.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 1997-2001 Id Software, Inc. 3 | 4 | This program is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License 6 | as published by the Free Software Foundation; either version 2 7 | of the License, or (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | 13 | See the GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | 19 | */ 20 | #include "shared/shared.h" 21 | #include "vm.h" 22 | #include "vm_debug.h" 23 | #include "vm_string.h" 24 | #include "vm_gi.h" 25 | 26 | #if ALLOW_DEBUGGING 27 | #include "g_thread.h" 28 | #endif 29 | 30 | #include "game.h" 31 | #include "vm_game.h" 32 | 33 | #include "g_main.h" 34 | #include "g_save.h" 35 | 36 | qcvm_t *qvm; 37 | 38 | static void FieldCoord2Short(void *out, const void *in) 39 | { 40 | *(int16_t *)(out) = (*(const vec_t *)(in) * coord2short); 41 | } 42 | 43 | #ifdef KMQUAKE2_ENGINE_MOD 44 | static void FieldCoord2Int(void *out, const void *in) 45 | { 46 | *(int32_t *)(out) = (*(const vec_t *)(in) * coord2short); 47 | } 48 | #endif 49 | 50 | static void FieldCoord2Angle(void *out, const void *in) 51 | { 52 | *(int16_t *)(out) = (*(const vec_t *)(in) * angle2short); 53 | } 54 | 55 | #define qcvm_field_wrap_to_type(name, T) \ 56 | static void name(void *out, const void *in) \ 57 | { \ 58 | *(T *)(out) = *(const int32_t *)(in); \ 59 | } 60 | 61 | qcvm_field_wrap_to_type(qcvm_field_wrap_to_int16, int16_t) 62 | qcvm_field_wrap_to_type(qcvm_field_wrap_to_uint8, uint8_t) 63 | 64 | static void FieldEnt2Entity(void *out, const void *in) 65 | { 66 | *(edict_t **)(out) = qcvm_ent_to_entity(qvm, (*(const qcvm_ent_t *)(in)), false); 67 | } 68 | 69 | static void InitFieldWraps(void) 70 | { 71 | #define RegisterSingle(field_name, strct, name) \ 72 | qcvm_field_wrap_list_register(qvm, field_name, 0, offsetof(strct, name), NULL) 73 | 74 | #define RegisterSingleWrapped(field_name, strct, name, wrap) \ 75 | qcvm_field_wrap_list_register(qvm, field_name, 0, offsetof(strct, name), wrap) 76 | 77 | #define RegisterArray(field_name, strct, name, fofs, sofs) \ 78 | qcvm_field_wrap_list_register(qvm, field_name "[" #fofs "]", 0, offsetof(strct, name) + sofs, NULL) 79 | 80 | #define RegisterVector(field_name, strct, name) \ 81 | qcvm_field_wrap_list_register(qvm, field_name, 0, offsetof(strct, name), NULL); \ 82 | qcvm_field_wrap_list_register(qvm, field_name, 1, offsetof(strct, name) + 4, NULL); \ 83 | qcvm_field_wrap_list_register(qvm, field_name, 2, offsetof(strct, name) + 8, NULL) 84 | 85 | #define RegisterVectorCoord2Short(field_name, strct, name) \ 86 | qcvm_field_wrap_list_register(qvm, field_name, 0, offsetof(strct, name), FieldCoord2Short); \ 87 | qcvm_field_wrap_list_register(qvm, field_name, 1, offsetof(strct, name) + 2, FieldCoord2Short); \ 88 | qcvm_field_wrap_list_register(qvm, field_name, 2, offsetof(strct, name) + 4, FieldCoord2Short) 89 | 90 | #ifdef KMQUAKE2_ENGINE_MOD 91 | #define RegisterVectorCoord2Int(field_name, strct, name) \ 92 | qcvm_field_wrap_list_register(qvm, field_name, 0, offsetof(strct, name), FieldCoord2Int); \ 93 | qcvm_field_wrap_list_register(qvm, field_name, 1, offsetof(strct, name) + 4, FieldCoord2Int); \ 94 | qcvm_field_wrap_list_register(qvm, field_name, 2, offsetof(strct, name) + 8, FieldCoord2Int) 95 | #endif 96 | 97 | #define RegisterVectorCoord2Angle(field_name, strct, name) \ 98 | qcvm_field_wrap_list_register(qvm, field_name, 0, offsetof(strct, name), FieldCoord2Angle); \ 99 | qcvm_field_wrap_list_register(qvm, field_name, 1, offsetof(strct, name) + 2, FieldCoord2Angle); \ 100 | qcvm_field_wrap_list_register(qvm, field_name, 2, offsetof(strct, name) + 4, FieldCoord2Angle) 101 | 102 | // edict_t wraps 103 | RegisterSingleWrapped("owner", edict_t, owner, FieldEnt2Entity); 104 | 105 | // gclient_t wraps 106 | // gclient_t::ps 107 | RegisterVector("client.ps.viewangles", gclient_t, ps.viewangles); 108 | RegisterVector("client.ps.viewoffset", gclient_t, ps.viewoffset); 109 | RegisterVector("client.ps.kick_angles", gclient_t, ps.kick_angles); 110 | RegisterVector("client.ps.gunangles", gclient_t, ps.gunangles); 111 | RegisterVector("client.ps.gunoffset", gclient_t, ps.gunoffset); 112 | RegisterSingle("client.ps.gunindex", gclient_t, ps.gunindex); 113 | RegisterSingle("client.ps.gunframe", gclient_t, ps.gunframe); 114 | RegisterArray("client.ps.blend", gclient_t, ps.blend, 0, 0); 115 | RegisterArray("client.ps.blend", gclient_t, ps.blend, 1, 4); 116 | RegisterArray("client.ps.blend", gclient_t, ps.blend, 2, 8); 117 | RegisterArray("client.ps.blend", gclient_t, ps.blend, 3, 12); 118 | RegisterSingle("client.ps.fov", gclient_t, ps.fov); 119 | RegisterSingle("client.ps.rdflags", gclient_t, ps.rdflags); 120 | 121 | for (int32_t i = 0; i < MAX_STATS; i++) 122 | qcvm_field_wrap_list_register(qvm, qcvm_temp_format(qvm, "client.ps.stats[%i]", i), 0, offsetof(gclient_t, ps.stats) + (sizeof(player_stat_t) * i), qcvm_field_wrap_to_int16); 123 | 124 | // gclient_t::ps::pmove 125 | RegisterSingle("client.ps.pmove.pm_type", gclient_t, ps.pmove.pm_type); 126 | 127 | #ifdef KMQUAKE2_ENGINE_MOD 128 | RegisterVectorCoord2Int("client.ps.pmove.origin", gclient_t, ps.pmove.origin); 129 | #else 130 | RegisterVectorCoord2Short("client.ps.pmove.origin", gclient_t, ps.pmove.origin); 131 | #endif 132 | RegisterVectorCoord2Short("client.ps.pmove.velocity", gclient_t, ps.pmove.velocity); 133 | 134 | RegisterSingleWrapped("client.ps.pmove.pm_flags", gclient_t, ps.pmove.pm_flags, qcvm_field_wrap_to_uint8); 135 | RegisterSingleWrapped("client.ps.pmove.pm_time", gclient_t, ps.pmove.pm_time, qcvm_field_wrap_to_uint8); 136 | RegisterSingleWrapped("client.ps.pmove.gravity", gclient_t, ps.pmove.gravity, qcvm_field_wrap_to_int16); 137 | 138 | RegisterVectorCoord2Angle("client.ps.pmove.delta_angles", gclient_t, ps.pmove.delta_angles); 139 | } 140 | 141 | qc_export_t qce; 142 | 143 | #include 144 | 145 | static const char *GetProgsName(void) 146 | { 147 | cvar_t *game_var = gi.cvar("game", "", 0); 148 | 149 | #ifdef KMQUAKE2_ENGINE_MOD 150 | const char *kmq2_progs = qcvm_temp_format(qvm, "%s/kmq2progs.dat", game_var->string); 151 | 152 | if (access(kmq2_progs, F_OK) != -1) 153 | return kmq2_progs; 154 | #endif 155 | 156 | return qcvm_temp_format(qvm, "%s/progs.dat", game_var->string); 157 | } 158 | 159 | #define sizeof_member(type, member) sizeof(((type *)0)->member) 160 | 161 | // register system fields 162 | static void InitFields(void) 163 | { 164 | #define RegisterField(name) \ 165 | qcvm_register_system_field(qvm, #name, offsetof(edict_t, name) / sizeof(qcvm_global_t), sizeof_member(edict_t, name) / sizeof(qcvm_global_t)) 166 | 167 | RegisterField(s.number); 168 | RegisterField(s.origin); 169 | RegisterField(s.angles); 170 | RegisterField(s.old_origin); 171 | RegisterField(s.modelindex); 172 | RegisterField(s.modelindex2); 173 | RegisterField(s.modelindex3); 174 | RegisterField(s.modelindex4); 175 | RegisterField(s.frame); 176 | RegisterField(s.skinnum); 177 | RegisterField(s.effects); 178 | RegisterField(s.renderfx); 179 | RegisterField(s.solid); 180 | RegisterField(s.sound); 181 | RegisterField(s.event); 182 | RegisterField(inuse); 183 | RegisterField(linkcount); 184 | RegisterField(areanum); 185 | RegisterField(areanum2); 186 | RegisterField(svflags); 187 | RegisterField(mins); 188 | RegisterField(maxs); 189 | RegisterField(absmin); 190 | RegisterField(absmax); 191 | RegisterField(size); 192 | RegisterField(solid); 193 | RegisterField(clipmask); 194 | 195 | #ifdef KMQUAKE2_ENGINE_MOD 196 | RegisterField(s.modelindex5); 197 | RegisterField(s.modelindex6); 198 | RegisterField(s.alpha); 199 | RegisterField(s.attenuation); 200 | #endif 201 | } 202 | 203 | #ifdef KMQUAKE2_ENGINE_MOD 204 | static void InitKMQ2Constants(void) 205 | { 206 | #define SetConstant(name, value) \ 207 | { \ 208 | qcvm_definition_t *def = qcvm_find_definition(qvm, name, TYPE_INTEGER); \ 209 | \ 210 | if (def) \ 211 | { \ 212 | const int32_t val = value; \ 213 | qcvm_set_global_typed_value(vec_t, qvm, def->global_index, val); \ 214 | } \ 215 | } 216 | 217 | SetConstant("MAX_EDICTS", MAX_EDICTS); 218 | SetConstant("MAX_MODELS", 8192); 219 | SetConstant("MAX_SOUNDS", 8192); 220 | SetConstant("MAX_IMAGES", 2048); 221 | } 222 | #endif 223 | 224 | void AssignClientPointer(edict_t *e, const bool assign) 225 | { 226 | if (assign) 227 | e->client = &game.clients[e->s.number - 1]; 228 | else 229 | e->client = NULL; 230 | 231 | ((int32_t *)e)[game.fields.is_client] = true; 232 | } 233 | 234 | void WipeClientPointers(void) 235 | { 236 | for (uint32_t i = 0; i < game.num_clients; i++) 237 | AssignClientPointer((edict_t *)qcvm_itoe(qvm, i + 1), false); 238 | } 239 | 240 | void WipeEntities(void) 241 | { 242 | memset(globals.edicts, 0, globals.max_edicts * globals.edict_size); 243 | 244 | for (int32_t i = 0; i < globals.max_edicts; i++) 245 | ((edict_t *)qcvm_itoe(qvm, i))->s.number = i; 246 | 247 | WipeClientPointers(); 248 | } 249 | 250 | static qcvm_noreturn void qvm_error(const char *str) 251 | { 252 | gi.error("%s", str); 253 | } 254 | 255 | static void qvm_debug(const char *str) 256 | { 257 | gi.dprintf("%s\n", str); 258 | } 259 | 260 | static void *qvm_alloc(const size_t sz) 261 | { 262 | return gi.TagMalloc((uint32_t)sz, TAG_GAME); 263 | } 264 | 265 | static void InitGameField(qcvm_global_t *field_ptr, const char *name) 266 | { 267 | const qcvm_definition_t *field = qcvm_find_field(qvm, name); 268 | 269 | if (!field) 270 | qcvm_error(qvm, "missing required field: \"%s\"", name); 271 | 272 | *field_ptr = (qcvm_global_t)field->global_index; 273 | } 274 | 275 | static void InitGameFields(void) 276 | { 277 | InitGameField(&game.fields.is_client, "is_client"); 278 | InitGameField(&game.fields.is_linked, "is_linked"); 279 | InitGameField(&game.fields.owner, "owner"); 280 | 281 | game.funcs.ClientConnect = qcvm_get_function(qvm, qce.ClientConnect); 282 | game.funcs.ClientBegin = qcvm_get_function(qvm, qce.ClientBegin); 283 | game.funcs.ClientUserinfoChanged = qcvm_get_function(qvm, qce.ClientUserinfoChanged); 284 | game.funcs.ClientDisconnect = qcvm_get_function(qvm, qce.ClientDisconnect); 285 | game.funcs.ClientCommand = qcvm_get_function(qvm, qce.ClientCommand); 286 | game.funcs.ClientThink = qcvm_get_function(qvm, qce.ClientThink); 287 | game.funcs.RunFrame = qcvm_get_function(qvm, qce.RunFrame); 288 | game.funcs.ServerCommand = qcvm_get_function(qvm, qce.ServerCommand); 289 | } 290 | 291 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 292 | #include 293 | #endif 294 | 295 | /* 296 | ============ 297 | InitGame 298 | 299 | This will be called when the dll is first loaded, which 300 | only happens when a new game is started or a save game 301 | is loaded. 302 | ============ 303 | */ 304 | static void InitGame (void) 305 | { 306 | qvm = (qcvm_t *)gi.TagMalloc(sizeof(qcvm_t), TAG_GAME); 307 | 308 | qvm->warning = gi.dprintf; 309 | qvm->error = qvm_error; 310 | qvm->debug_print = qvm_debug; 311 | qvm->alloc = qvm_alloc; 312 | qvm->free = gi.TagFree; 313 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 314 | qvm->profiling.mark = MARK_INIT; 315 | #endif 316 | 317 | qvm->max_edicts = globals.max_edicts = MAX_EDICTS; 318 | qvm->system_edict_size = sizeof(edict_t); 319 | 320 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 321 | qvm->profiling.flags = (int32_t)gi.cvar("qc_profile_flags", "0", CVAR_LATCH)->value; 322 | 323 | if (qvm->profiling.flags & PROFILE_CONTINUOUS) 324 | { 325 | const cvar_t *qc_profile_name = gi.cvar("qc_profile_filename", qcvm_temp_format(qvm, "%u", time(NULL)), CVAR_NOSET); 326 | qvm->profiling.filename = qc_profile_name->string; 327 | } 328 | #endif 329 | 330 | #if ALLOW_PROFILING 331 | qvm->profiling.sampling.rate = (uint32_t)gi.cvar("qc_sample_rate", "32", CVAR_LATCH)->value; 332 | qvm->profiling.sampling.id = qvm->profiling.sampling.function_id = qvm->profiling.sampling.rate; 333 | #endif 334 | 335 | qcvm_load(qvm, "Quake2C DLL", GetProgsName()); 336 | 337 | #ifdef KMQUAKE2_ENGINE_MOD 338 | // adjust constants in progs that KMQ2 change 339 | InitKMQ2Constants(); 340 | #endif 341 | 342 | qcvm_init_all_builtins(qvm); 343 | qcvm_init_gi_builtins(qvm); 344 | 345 | InitFields(); 346 | 347 | qcvm_check(qvm); 348 | 349 | #if ALLOW_INSTRUMENTING 350 | const cvar_t *qc_profile_func = gi.cvar("qc_profile_func", "", CVAR_LATCH); 351 | 352 | if (*qc_profile_func->string) 353 | qvm->profiling.instrumentation.func = qcvm_find_function(qvm, qc_profile_func->string); 354 | #endif 355 | 356 | InitFieldWraps(); 357 | 358 | #if ALLOW_DEBUGGING 359 | qvm->debug.create_mutex = qcvm_cpp_create_mutex; 360 | qvm->debug.free_mutex = qcvm_cpp_free_mutex; 361 | qvm->debug.lock_mutex = qcvm_cpp_lock_mutex; 362 | qvm->debug.unlock_mutex = qcvm_cpp_unlock_mutex; 363 | qvm->debug.create_thread = qcvm_cpp_create_thread; 364 | qvm->debug.thread_sleep = qcvm_cpp_thread_sleep; 365 | 366 | if (gi.cvar("qcvm_debugger", "", CVAR_NONE)->value) 367 | { 368 | qcvm_init_debugger(qvm); 369 | qcvm_check_debugger_commands(qvm); 370 | } 371 | #endif 372 | 373 | // Call GetGameAPI 374 | qcvm_function_t *func = qcvm_find_function(qvm, "GetGameAPI"); 375 | qcvm_execute(qvm, func); 376 | qce = *qcvm_get_global_typed(qc_export_t, qvm, GLOBAL_PARM0); 377 | 378 | InitGameFields(); 379 | 380 | // initialize all clients for this game 381 | const cvar_t *maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); 382 | game.num_clients = (uint32_t)minsz(MAX_CLIENTS, (size_t)maxclients->value); 383 | game.clients = (gclient_t *)gi.TagMalloc(sizeof(gclient_t) * game.num_clients, TAG_GAME); 384 | 385 | // initialize all entities for this game 386 | qvm->edict_size = qvm->field_real_size * 4; 387 | globals.edict_size = (int32_t)qvm->edict_size; 388 | 389 | qcvm_debug(qvm, "Field size: %u bytes\n", globals.edict_size); 390 | 391 | globals.num_edicts = game.num_clients + 1; 392 | qvm->edicts = globals.edicts = (edict_t *)gi.TagMalloc(globals.max_edicts * globals.edict_size, TAG_GAME); 393 | 394 | WipeEntities(); 395 | 396 | func = qcvm_get_function(qvm, qce.InitGame); 397 | qcvm_execute(qvm, func); 398 | } 399 | 400 | static void ShutdownGame(void) 401 | { 402 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 403 | qvm->profiling.mark = MARK_SHUTDOWN; 404 | #endif 405 | 406 | #if ALLOW_DEBUGGING 407 | qcvm_check_debugger_commands(qvm); 408 | #endif 409 | 410 | qcvm_function_t *func = qcvm_get_function(qvm, qce.ShutdownGame); 411 | qcvm_execute(qvm, func); 412 | 413 | qcvm_shutdown(qvm); 414 | 415 | gi.FreeTags (TAG_GAME); 416 | } 417 | 418 | void BackupClientData(void) 419 | { 420 | // in Q2, gclient_t was stored in a separate pointer, but in Q2QC they're fields 421 | // and as such wiped with the entity structure. We have to mimic the original Q2 behavior of backing up 422 | // the gclient_t structures. 423 | edict_t *first_player = (edict_t *)qcvm_itoe(qvm, 1); 424 | 425 | game.client_load_data = (uint8_t *)gi.TagMalloc(globals.edict_size * game.num_clients, TAG_GAME); 426 | memcpy(game.client_load_data, first_player, globals.edict_size * game.num_clients); 427 | qcvm_string_list_mark_refs_copied(qvm, first_player, game.client_load_data, (globals.edict_size * game.num_clients) / sizeof(qcvm_global_t)); 428 | } 429 | 430 | void RestoreClientData(void) 431 | { 432 | // copy over any client-specific data back into the clients and re-sync 433 | for (uint32_t i = 0; i < game.num_clients; i++) 434 | { 435 | edict_t *ent = (edict_t *)qcvm_itoe(qvm, i + 1); 436 | #if defined(__GNU__) || defined(__clang__) 437 | #pragma GCC diagnostic push 438 | #pragma GCC diagnostic ignored "-Wcast-align" 439 | #endif 440 | edict_t *backup = (edict_t *)((uint8_t *)game.client_load_data + (globals.edict_size * i)); 441 | #if defined(__GNU__) || defined(__clang__) 442 | #pragma GCC diagnostic pop 443 | #endif 444 | 445 | AssignClientPointer(ent, true); 446 | 447 | // restore client structs 448 | for (qcvm_definition_t *def = qvm->fields; def < qvm->fields + qvm->fields_size; def++) 449 | { 450 | const char *name = qcvm_get_string(qvm, def->name_index); 451 | 452 | if (def->name_index == STRING_EMPTY || strnicmp(name, "client.", 6)) 453 | continue; 454 | 455 | const size_t len = qcvm_type_span(def->id); 456 | void *dst; 457 | 458 | if (!qcvm_resolve_pointer(qvm, qcvm_get_entity_field_pointer(qvm, ent, def->global_index), false, len * sizeof(qcvm_global_t), &dst)) 459 | qcvm_error(qvm, "invalid pointer"); 460 | 461 | void *src = (int32_t *)backup + def->global_index; 462 | 463 | memcpy(dst, src, sizeof(qcvm_global_t) * len); 464 | } 465 | } 466 | 467 | qcvm_string_list_mark_refs_copied(qvm, game.client_load_data, qcvm_itoe(qvm, 1), (globals.edict_size * game.num_clients) / sizeof(qcvm_global_t)); 468 | qcvm_field_wrap_list_check_set(qvm, qcvm_itoe(qvm, 1), (globals.edict_size * game.num_clients) / sizeof(qcvm_global_t)); 469 | qcvm_string_list_check_ref_unset(qvm, game.client_load_data, (globals.edict_size * game.num_clients) / sizeof(qcvm_global_t), true); 470 | 471 | gi.TagFree(game.client_load_data); 472 | game.client_load_data = NULL; 473 | } 474 | 475 | static void SpawnEntities(const char *mapname, const char *entities, const char *spawnpoint) 476 | { 477 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 478 | qvm->profiling.mark = MARK_SPAWNENTITIES; 479 | #endif 480 | 481 | #if ALLOW_DEBUGGING 482 | qcvm_check_debugger_commands(qvm); 483 | #endif 484 | 485 | qcvm_function_t *func = qcvm_get_function(qvm, qce.PreSpawnEntities); 486 | qcvm_execute(qvm, func); 487 | 488 | BackupClientData(); 489 | 490 | WipeEntities(); 491 | 492 | func = qcvm_get_function(qvm, qce.SpawnEntities); 493 | qcvm_set_global_str(qvm, GLOBAL_PARM0, mapname, strlen(mapname), true); 494 | qcvm_set_global_str(qvm, GLOBAL_PARM1, entities, strlen(entities), true); 495 | qcvm_set_global_str(qvm, GLOBAL_PARM2, spawnpoint, strlen(spawnpoint), true); 496 | qcvm_execute(qvm, func); 497 | 498 | func = qcvm_get_function(qvm, qce.PostSpawnEntities); 499 | qcvm_execute(qvm, func); 500 | 501 | RestoreClientData(); 502 | } 503 | 504 | static qboolean ClientConnect(edict_t *e, char *userinfo) 505 | { 506 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 507 | qvm->profiling.mark = MARK_CLIENTCONNECT; 508 | #endif 509 | 510 | #if ALLOW_DEBUGGING 511 | qcvm_check_debugger_commands(qvm); 512 | #endif 513 | 514 | AssignClientPointer(e, true); 515 | 516 | const qcvm_ent_t ent = qcvm_entity_to_ent(qvm, e); 517 | qcvm_set_global_typed_value(qcvm_ent_t, qvm, GLOBAL_PARM0, ent); 518 | qcvm_set_global_str(qvm, GLOBAL_PARM1, userinfo, strlen(userinfo), true); 519 | qcvm_execute(qvm, game.funcs.ClientConnect); 520 | 521 | Q_strlcpy(userinfo, qcvm_get_string(qvm, *qcvm_get_global_typed(qcvm_string_t, qvm, GLOBAL_PARM1)), MAX_INFO_STRING); 522 | 523 | const qboolean succeed = *qcvm_get_global_typed(qboolean, qvm, GLOBAL_RETURN); 524 | 525 | if (!succeed) 526 | AssignClientPointer(e, false); 527 | 528 | return succeed; 529 | } 530 | 531 | static void ClientBegin(edict_t *e) 532 | { 533 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 534 | qvm->profiling.mark = MARK_CLIENTBEGIN; 535 | #endif 536 | 537 | #if ALLOW_DEBUGGING 538 | qcvm_check_debugger_commands(qvm); 539 | #endif 540 | 541 | AssignClientPointer(e, true); 542 | 543 | const qcvm_ent_t ent = qcvm_entity_to_ent(qvm, e); 544 | qcvm_set_global_typed_value(qcvm_ent_t, qvm, GLOBAL_PARM0, ent); 545 | qcvm_execute(qvm, game.funcs.ClientBegin); 546 | } 547 | 548 | static void ClientUserinfoChanged(edict_t *e, char *userinfo) 549 | { 550 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 551 | qvm->profiling.mark = MARK_CLIENTUSERINFOCHANGED; 552 | #endif 553 | 554 | #if ALLOW_DEBUGGING 555 | qcvm_check_debugger_commands(qvm); 556 | #endif 557 | 558 | const qcvm_ent_t ent = qcvm_entity_to_ent(qvm, e); 559 | qcvm_set_global_typed_value(qcvm_ent_t, qvm, GLOBAL_PARM0, ent); 560 | qcvm_set_global_str(qvm, GLOBAL_PARM1, userinfo, strlen(userinfo), true); 561 | qcvm_execute(qvm, game.funcs.ClientUserinfoChanged); 562 | } 563 | 564 | static void ClientDisconnect(edict_t *e) 565 | { 566 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 567 | qvm->profiling.mark = MARK_CLIENTDISCONNECT; 568 | #endif 569 | 570 | #if ALLOW_DEBUGGING 571 | qcvm_check_debugger_commands(qvm); 572 | #endif 573 | 574 | const qcvm_ent_t ent = qcvm_entity_to_ent(qvm, e); 575 | qcvm_set_global_typed_value(qcvm_ent_t, qvm, GLOBAL_PARM0, ent); 576 | qcvm_execute(qvm, game.funcs.ClientDisconnect); 577 | } 578 | 579 | static void ClientCommand(edict_t *e) 580 | { 581 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 582 | qvm->profiling.mark = MARK_CLIENTCOMMAND; 583 | #endif 584 | 585 | #if ALLOW_DEBUGGING 586 | qcvm_check_debugger_commands(qvm); 587 | #endif 588 | 589 | const qcvm_ent_t ent = qcvm_entity_to_ent(qvm, e); 590 | qcvm_set_global_typed_value(qcvm_ent_t, qvm, GLOBAL_PARM0, ent); 591 | qcvm_execute(qvm, game.funcs.ClientCommand); 592 | } 593 | 594 | static void ClientThink(edict_t *e, usercmd_t *ucmd) 595 | { 596 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 597 | qvm->profiling.mark = MARK_CLIENTTHINK; 598 | #endif 599 | 600 | #if ALLOW_DEBUGGING 601 | qcvm_check_debugger_commands(qvm); 602 | #endif 603 | 604 | const qcvm_ent_t ent = qcvm_entity_to_ent(qvm, e); 605 | qcvm_set_global_typed_value(qcvm_ent_t, qvm, GLOBAL_PARM0, ent); 606 | 607 | QC_usercmd_t cmd = { 608 | ucmd->msec, 609 | ucmd->buttons, 610 | { ucmd->angles[0] * short2angle, ucmd->angles[1] * short2angle, ucmd->angles[2] * short2angle }, 611 | ucmd->forwardmove, 612 | ucmd->sidemove, 613 | ucmd->upmove, 614 | ucmd->impulse, 615 | ucmd->lightlevel 616 | }; 617 | 618 | qcvm_set_global_typed_value(QC_usercmd_t, qvm, GLOBAL_PARM1, cmd); 619 | qcvm_execute(qvm, game.funcs.ClientThink); 620 | } 621 | 622 | static void RunFrame(void) 623 | { 624 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 625 | qvm->profiling.mark = MARK_RUNFRAME; 626 | #endif 627 | 628 | #if ALLOW_DEBUGGING 629 | qcvm_check_debugger_commands(qvm); 630 | #endif 631 | 632 | qcvm_execute(qvm, game.funcs.RunFrame); 633 | } 634 | 635 | #define OPCODES_ONLY 636 | #include "vm_opcodes.h" 637 | #undef OPCODES_ONLY 638 | 639 | static void ServerCommand(void) 640 | { 641 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 642 | qvm->profiling.mark = MARK_SERVERCOMMAND; 643 | #endif 644 | 645 | #ifdef _DEBUG 646 | const char *cmd = gi.argv(1); 647 | 648 | if (strcmp(cmd, "qc_dump_strings") == 0) 649 | { 650 | FILE *fp = fopen(qcvm_temp_format(qvm, "%sstrings.txt", qvm->path), "w"); 651 | qcvm_string_list_dump_refs(fp, qvm); 652 | fclose(fp); 653 | return; 654 | } 655 | else if (strcmp(cmd, "qc_dump_function") == 0) 656 | { 657 | const char *func = gi.argv(2); 658 | qcvm_function_t *function; 659 | 660 | if ((function = qcvm_find_function(qvm, func))) 661 | { 662 | FILE *fp = fopen(qcvm_temp_format(qvm, "%s%s.txt", qvm->path, func), "w"); 663 | fprintf(fp, "%s (locals: %i -> %i)\n", func, function->first_arg, function->first_arg + function->num_args_and_locals); 664 | const qcvm_statement_t *statement = &qvm->statements[function->id]; 665 | 666 | while (statement->opcode != OP_DONE) 667 | { 668 | fprintf(fp, "%s\t%i\t%i\t%i\n", opcode_names[statement->opcode], statement->args.a, statement->args.b, statement->args.c); 669 | statement++; 670 | } 671 | 672 | fclose(fp); 673 | return; 674 | } 675 | } 676 | #endif 677 | 678 | #if ALLOW_DEBUGGING 679 | qcvm_check_debugger_commands(qvm); 680 | #endif 681 | 682 | qcvm_execute(qvm, game.funcs.ServerCommand); 683 | } 684 | 685 | game_import_t gi; 686 | 687 | game_t game; 688 | 689 | game_export_t globals = { 690 | .apiversion = 3, 691 | 692 | .Init = InitGame, 693 | .Shutdown = ShutdownGame, 694 | 695 | .SpawnEntities = SpawnEntities, 696 | 697 | .WriteGame = WriteGame, 698 | .ReadGame = ReadGame, 699 | 700 | .WriteLevel = WriteLevel, 701 | .ReadLevel = ReadLevel, 702 | 703 | .ClientConnect = ClientConnect, 704 | .ClientBegin = ClientBegin, 705 | .ClientUserinfoChanged = ClientUserinfoChanged, 706 | .ClientDisconnect = ClientDisconnect, 707 | .ClientCommand = ClientCommand, 708 | .ClientThink = ClientThink, 709 | 710 | .RunFrame = RunFrame, 711 | 712 | .ServerCommand = ServerCommand 713 | }; 714 | 715 | /* 716 | ================= 717 | GetGameAPI 718 | 719 | Returns a pointer to the structure with all entry points 720 | and global variables 721 | ================= 722 | */ 723 | game_export_t *GetGameAPI (game_import_t *import) 724 | { 725 | gi = *import; 726 | return &globals; 727 | } 728 | -------------------------------------------------------------------------------- /g_main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern qcvm_t *qvm; 4 | 5 | // exported from QC 6 | typedef struct 7 | { 8 | int32_t apiversion; 9 | int32_t clientsize; 10 | 11 | qcvm_func_t InitGame; 12 | qcvm_func_t ShutdownGame; 13 | 14 | qcvm_func_t PreSpawnEntities; 15 | qcvm_func_t SpawnEntities; 16 | qcvm_func_t PostSpawnEntities; 17 | 18 | qcvm_func_t ClientConnect; 19 | qcvm_func_t ClientBegin; 20 | qcvm_func_t ClientUserinfoChanged; 21 | qcvm_func_t ClientDisconnect; 22 | qcvm_func_t ClientCommand; 23 | qcvm_func_t ClientThink; 24 | 25 | qcvm_func_t RunFrame; 26 | 27 | qcvm_func_t ServerCommand; 28 | 29 | qcvm_func_t PreWriteGame; 30 | qcvm_func_t PostWriteGame; 31 | 32 | qcvm_func_t PreReadGame; 33 | qcvm_func_t PostReadGame; 34 | 35 | qcvm_func_t PreWriteLevel; 36 | qcvm_func_t PostWriteLevel; 37 | 38 | qcvm_func_t PreReadLevel; 39 | qcvm_func_t PostReadLevel; 40 | } qc_export_t; 41 | 42 | extern qc_export_t qce; 43 | 44 | void RestoreClientData(void); 45 | void AssignClientPointer(edict_t *e, const bool assign); 46 | void WipeClientPointers(void); 47 | void WipeEntities(void); 48 | void BackupClientData(void); 49 | 50 | // exported, but here to prevent warnings 51 | game_export_t *GetGameAPI (game_import_t *import); -------------------------------------------------------------------------------- /g_save.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 1997-2001 Id Software, Inc. 3 | 4 | This program is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License 6 | as published by the Free Software Foundation; either version 2 7 | of the License, or (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | 13 | See the GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | 19 | */ 20 | #include "shared/shared.h" 21 | #include "vm.h" 22 | #include "game.h" 23 | #include "g_main.h" 24 | #include "vm_string.h" 25 | 26 | //========================================================= 27 | 28 | static const uint32_t SAVE_MAGIC1 = (('V'<<24)|('S'<<16)|('C'<<8)|'Q'); // "QCSV" 29 | static const uint32_t SAVE_MAGIC2 = (('A'<<24)|('S'<<16)|('C'<<8)|'Q'); // "QCSA" 30 | static const uint32_t SAVE_VERSION = 666; 31 | 32 | static void WriteDefinitionData(FILE *fp, const qcvm_definition_t *def, const qcvm_global_t *value) 33 | { 34 | const qcvm_deftype_t type = def->id & ~TYPE_GLOBAL; 35 | 36 | if (type == TYPE_STRING) 37 | { 38 | const qcvm_string_t strid = (qcvm_string_t)*value; 39 | const size_t strlength = qcvm_get_string_length(qvm, strid); 40 | const char *str = qcvm_get_string(qvm, strid); 41 | fwrite(&strlength, sizeof(strlength), 1, fp); 42 | fwrite(str, sizeof(char), strlength, fp); 43 | return; 44 | } 45 | else if (type == TYPE_FUNCTION) 46 | { 47 | const qcvm_func_t func = (qcvm_func_t)*value; 48 | const qcvm_function_t *func_ptr = &qvm->functions[(size_t)func]; 49 | const char *str = qcvm_get_string(qvm, func_ptr->name_index); 50 | const size_t strlength = qcvm_get_string_length(qvm, func_ptr->name_index); 51 | 52 | fwrite(&strlength, sizeof(strlength), 1, fp); 53 | fwrite(str, sizeof(char), strlength, fp); 54 | return; 55 | } 56 | else if (type == TYPE_ENTITY) 57 | { 58 | const edict_t *ent = qcvm_ent_to_entity(qvm, (qcvm_ent_t)*value, false); 59 | int32_t number = -1; 60 | 61 | if (ent != NULL) 62 | number = ent->s.number; 63 | 64 | fwrite(&number, sizeof(number), 1, fp); 65 | return; 66 | } 67 | 68 | fwrite(value, sizeof(qcvm_global_t), qcvm_type_span(def->id), fp); 69 | } 70 | 71 | static void WriteEntityFieldData(FILE *fp, edict_t *ent, const qcvm_definition_t *def) 72 | { 73 | int32_t *field; 74 | 75 | if (!qcvm_resolve_pointer(qvm, qcvm_get_entity_field_pointer(qvm, ent, (int32_t)def->global_index), false, qcvm_type_size(def->id), (void**)&field)) 76 | qcvm_error(qvm, "bad pointer"); 77 | 78 | WriteDefinitionData(fp, def, (const qcvm_global_t *)field); 79 | } 80 | 81 | static void ReadDefinitionData(qcvm_t *vm, FILE *fp, const qcvm_definition_t *def, qcvm_global_t *value) 82 | { 83 | const qcvm_deftype_t type = def->id & ~TYPE_GLOBAL; 84 | 85 | if (type == TYPE_STRING) 86 | { 87 | size_t def_len; 88 | fread(&def_len, sizeof(def_len), 1, fp); 89 | char *def_value = qcvm_temp_buffer(vm, def_len); 90 | fread(def_value, sizeof(char), def_len, fp); 91 | def_value[def_len] = 0; 92 | qcvm_set_string_ptr(qvm, value, def_value, def_len, true); 93 | return; 94 | } 95 | else if (type == TYPE_FUNCTION) 96 | { 97 | size_t func_len; 98 | fread(&func_len, sizeof(func_len), 1, fp); 99 | 100 | if (!func_len) 101 | *value = GLOBAL_NULL; 102 | else 103 | { 104 | char *func_name = qcvm_temp_buffer(vm, func_len); 105 | fread(func_name, sizeof(char), func_len, fp); 106 | func_name[func_len] = 0; 107 | 108 | *value = (qcvm_global_t)qcvm_find_function_id(qvm, func_name); 109 | 110 | if (*value == GLOBAL_NULL) 111 | qcvm_error(qvm, "can't find func %s", func_name); 112 | } 113 | return; 114 | } 115 | else if (type == TYPE_ENTITY) 116 | { 117 | int32_t number; 118 | fread(&number, sizeof(number), 1, fp); 119 | *value = (qcvm_global_t)qcvm_entity_to_ent(qvm, qcvm_itoe(qvm, number)); 120 | return; 121 | } 122 | 123 | fread(value, sizeof(qcvm_global_t), qcvm_type_span(def->id), fp); 124 | } 125 | 126 | static void ReadEntityFieldData(qcvm_t *vm, FILE *fp, edict_t *ent, const qcvm_definition_t *def) 127 | { 128 | int32_t *field; 129 | 130 | if (!qcvm_resolve_pointer(qvm, qcvm_get_entity_field_pointer(qvm, ent, (int32_t)def->global_index), false, qcvm_type_size(def->id), (void**)&field)) 131 | qcvm_error(vm, "bad pointer"); 132 | 133 | ReadDefinitionData(vm, fp, def, (qcvm_global_t *)field); 134 | } 135 | 136 | /* 137 | ============ 138 | WriteGame 139 | 140 | This will be called whenever the game goes to a new level, 141 | and when the user explicitly saves the game. 142 | 143 | Game information include cross level data, like multi level 144 | triggers, help computer info, and all client states. 145 | 146 | A single player death will automatically restore from the 147 | last save position. 148 | ============ 149 | */ 150 | void WriteGame(const char *filename, qboolean autosave) 151 | { 152 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 153 | qvm->profiling.mark = MARK_WRITEGAME; 154 | #endif 155 | 156 | FILE *fp = fopen(filename, "wb"); 157 | 158 | qcvm_function_t *func = qcvm_get_function(qvm, qce.PreWriteGame); 159 | qcvm_set_global_typed_value(qboolean, qvm, GLOBAL_PARM0, autosave); 160 | qcvm_execute(qvm, func); 161 | 162 | fwrite(&SAVE_MAGIC1, sizeof(SAVE_MAGIC1), 1, fp); 163 | fwrite(&SAVE_VERSION, sizeof(SAVE_VERSION), 1, fp); 164 | fwrite(&globals.edict_size, sizeof(globals.edict_size), 1, fp); 165 | fwrite(&game.num_clients, sizeof(game.num_clients), 1, fp); 166 | 167 | // save "game." values 168 | size_t name_len; 169 | 170 | for (qcvm_definition_t *def = qvm->definitions; def < qvm->definitions + qvm->definitions_size; def++) 171 | { 172 | if (!(def->id & TYPE_GLOBAL)) 173 | continue; 174 | 175 | const char *name = qcvm_get_string(qvm, def->name_index); 176 | 177 | if (strnicmp(name, "game.", 5)) 178 | continue; 179 | 180 | name_len = strlen(name); 181 | fwrite(&name_len, sizeof(name_len), 1, fp); 182 | fwrite(name, sizeof(char), name_len, fp); 183 | 184 | WriteDefinitionData(fp, def, qcvm_get_global(qvm, def->global_index)); 185 | } 186 | 187 | name_len = 0; 188 | fwrite(&name_len, sizeof(name_len), 1, fp); 189 | 190 | // save client fields 191 | for (qcvm_definition_t *def = qvm->fields; def < qvm->fields + qvm->fields_size; def++) 192 | { 193 | const char *name = qcvm_get_string(qvm, def->name_index); 194 | 195 | if (strnicmp(name, "client.", 6)) 196 | continue; 197 | 198 | name_len = strlen(name); 199 | fwrite(&name_len, sizeof(name_len), 1, fp); 200 | fwrite(name, sizeof(char), name_len, fp); 201 | 202 | for (uint32_t i = 0; i < game.num_clients; i++) 203 | WriteEntityFieldData(fp, qcvm_itoe(qvm, i + 1), def); 204 | } 205 | 206 | name_len = 0; 207 | fwrite(&name_len, sizeof(name_len), 1, fp); 208 | 209 | func = qcvm_get_function(qvm, qce.PostWriteGame); 210 | qcvm_set_global_typed_value(qboolean, qvm, GLOBAL_PARM0, autosave); 211 | qcvm_execute(qvm, func); 212 | 213 | fclose(fp); 214 | } 215 | 216 | void ReadGame(const char *filename) 217 | { 218 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 219 | qvm->profiling.mark = MARK_READGAME; 220 | #endif 221 | 222 | FILE *fp = fopen(filename, "rb"); 223 | 224 | qcvm_function_t *func = qcvm_get_function(qvm, qce.PreReadGame); 225 | qcvm_execute(qvm, func); 226 | 227 | uint32_t magic, version, maxclients; 228 | int32_t edict_size; 229 | 230 | fread(&magic, sizeof(magic), 1, fp); 231 | 232 | if (magic != SAVE_MAGIC1) 233 | qcvm_error(qvm, "Not a save game"); 234 | 235 | fread(&version, sizeof(version), 1, fp); 236 | 237 | if (version != SAVE_VERSION) 238 | qcvm_error(qvm, "Savegame from different version (got %d, expected %d)", version, SAVE_VERSION); 239 | 240 | fread(&edict_size, sizeof(edict_size), 1, fp); 241 | 242 | if (globals.edict_size != edict_size) 243 | qcvm_error(qvm, "Savegame has bad fields (%i vs %i)", globals.edict_size, edict_size); 244 | 245 | fread(&maxclients, sizeof(maxclients), 1, fp); 246 | 247 | // should agree with server's version 248 | if (game.num_clients != maxclients) 249 | qcvm_error(qvm, "Savegame has bad maxclients"); 250 | 251 | // setup entities 252 | WipeEntities(); 253 | 254 | // free any string refs inside of the entity structure 255 | qcvm_string_list_check_ref_unset(qvm, globals.edicts, (globals.edict_size * globals.max_edicts) / sizeof(qcvm_global_t), false); 256 | 257 | // load game globals 258 | size_t len; 259 | 260 | while (true) 261 | { 262 | fread(&len, sizeof(len), 1, fp); 263 | 264 | if (!len) 265 | break; 266 | 267 | char *def_name = qcvm_temp_buffer(qvm, len); 268 | fread(def_name, sizeof(char), len, fp); 269 | def_name[len] = 0; 270 | 271 | qcvm_definition_hash_t *hashed = qvm->definition_hashes[Q_hash_string(def_name, qvm->definitions_size)]; 272 | 273 | for (; hashed; hashed = hashed->hash_next) 274 | if (!strcmp(qcvm_get_string(qvm, hashed->def->name_index), def_name)) 275 | break; 276 | 277 | if (!hashed) 278 | qcvm_error(qvm, "Bad definition %s", def_name); 279 | 280 | qcvm_definition_t *def = hashed->def; 281 | ReadDefinitionData(qvm, fp, def, qcvm_get_global(qvm, def->global_index)); 282 | } 283 | 284 | for (uint32_t i = 0; i < game.num_clients; i++) 285 | AssignClientPointer(qcvm_itoe(qvm, i + 1), true); 286 | 287 | // load client fields 288 | while (true) 289 | { 290 | fread(&len, sizeof(len), 1, fp); 291 | 292 | if (!len) 293 | break; 294 | 295 | char *def_name = qcvm_temp_buffer(qvm, len); 296 | fread(def_name, sizeof(char), len, fp); 297 | def_name[len] = 0; 298 | 299 | qcvm_string_t str; 300 | 301 | if (!qcvm_find_string(qvm, def_name, &str) || qcvm_string_list_is_ref_counted(qvm, str)) 302 | qcvm_error(qvm, "Bad string in save file"); 303 | 304 | qcvm_definition_hash_t *hashed = qvm->field_hashes[Q_hash_string(def_name, qvm->fields_size)]; 305 | 306 | for (; hashed; hashed = hashed->hash_next) 307 | if (!strcmp(qcvm_get_string(qvm, hashed->def->name_index), def_name)) 308 | break; 309 | 310 | if (!hashed) 311 | qcvm_error(qvm, "Bad field %s", def_name); 312 | 313 | qcvm_definition_t *field = hashed->def; 314 | 315 | for (uint32_t i = 0; i < game.num_clients; i++) 316 | ReadEntityFieldData(qvm, fp, qcvm_itoe(qvm, i + 1), field); 317 | } 318 | 319 | func = qcvm_get_function(qvm, qce.PostReadGame); 320 | qcvm_execute(qvm, func); 321 | 322 | qcvm_field_wrap_list_check_set(qvm, qcvm_itoe(qvm, 1), (globals.edict_size * game.num_clients) / sizeof(qcvm_global_t)); 323 | 324 | fclose(fp); 325 | } 326 | 327 | //========================================================== 328 | 329 | /* 330 | ================= 331 | WriteLevel 332 | 333 | ================= 334 | */ 335 | void WriteLevel(const char *filename) 336 | { 337 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 338 | qvm->profiling.mark = MARK_WRITELEVEL; 339 | #endif 340 | 341 | FILE *fp = fopen(filename, "wb"); 342 | 343 | qcvm_function_t *func = qcvm_get_function(qvm, qce.PreWriteLevel); 344 | qcvm_execute(qvm, func); 345 | 346 | fwrite(&SAVE_MAGIC2, sizeof(SAVE_MAGIC2), 1, fp); 347 | fwrite(&SAVE_VERSION, sizeof(SAVE_VERSION), 1, fp); 348 | fwrite(&globals.edict_size, sizeof(globals.edict_size), 1, fp); 349 | fwrite(&game.num_clients, sizeof(game.num_clients), 1, fp); 350 | 351 | // save "level." values 352 | size_t name_len; 353 | 354 | for (qcvm_definition_t *def = qvm->definitions; def < qvm->definitions + qvm->definitions_size; def++) 355 | { 356 | if (!(def->id & TYPE_GLOBAL)) 357 | continue; 358 | 359 | const char *name = qcvm_get_string(qvm, def->name_index); 360 | 361 | if (strnicmp(name, "level.", 5)) 362 | continue; 363 | 364 | name_len = strlen(name); 365 | fwrite(&name_len, sizeof(name_len), 1, fp); 366 | fwrite(name, sizeof(char), name_len, fp); 367 | 368 | WriteDefinitionData(fp, def, qcvm_get_global(qvm, def->global_index)); 369 | } 370 | 371 | name_len = 0; 372 | fwrite(&name_len, sizeof(name_len), 1, fp); 373 | 374 | // save non-client structs 375 | for (qcvm_definition_t *def = qvm->fields; def < qvm->fields + qvm->fields_size; def++) 376 | { 377 | const char *name = qcvm_get_string(qvm, def->name_index); 378 | 379 | if (def->name_index == STRING_EMPTY || strnicmp(name, "client.", 6) == 0) 380 | continue; 381 | 382 | name_len = strlen(name); 383 | fwrite(&name_len, sizeof(name_len), 1, fp); 384 | fwrite(name, sizeof(char), name_len, fp); 385 | 386 | for (uint32_t i = 0; i < globals.num_edicts; i++) 387 | { 388 | edict_t *ent = qcvm_itoe(qvm, i); 389 | 390 | if (!ent->inuse) 391 | continue; 392 | 393 | fwrite(&i, sizeof(i), 1, fp); 394 | WriteEntityFieldData(fp, ent, def); 395 | } 396 | 397 | uint32_t i = (uint32_t) -1; 398 | fwrite(&i, sizeof(i), 1, fp); 399 | } 400 | 401 | name_len = 0; 402 | fwrite(&name_len, sizeof(name_len), 1, fp); 403 | 404 | func = qcvm_get_function(qvm, qce.PostWriteLevel); 405 | qcvm_execute(qvm, func); 406 | 407 | fclose(fp); 408 | } 409 | 410 | /* 411 | ================= 412 | ReadLevel 413 | 414 | SpawnEntities will allready have been called on the 415 | level the same way it was when the level was saved. 416 | 417 | That is necessary to get the baselines 418 | set up identically. 419 | 420 | The server will have cleared all of the world links before 421 | calling ReadLevel. 422 | 423 | No clients are connected yet. 424 | ================= 425 | */ 426 | void ReadLevel(const char *filename) 427 | { 428 | #if ALLOW_INSTRUMENTING || ALLOW_PROFILING 429 | qvm->profiling.mark = MARK_READLEVEL; 430 | #endif 431 | 432 | FILE *fp = fopen(filename, "rb"); 433 | 434 | qcvm_function_t *func = qcvm_get_function(qvm, qce.PreReadLevel); 435 | qcvm_execute(qvm, func); 436 | 437 | uint32_t magic, version, maxclients; 438 | int32_t edict_size; 439 | 440 | fread(&magic, sizeof(magic), 1, fp); 441 | 442 | if (magic != SAVE_MAGIC2) 443 | qcvm_error(qvm, "Not a save game"); 444 | 445 | fread(&version, sizeof(version), 1, fp); 446 | 447 | if (version != SAVE_VERSION) 448 | qcvm_error(qvm, "Savegame from different version (got %d, expected %d)", version, SAVE_VERSION); 449 | 450 | fread(&edict_size, sizeof(edict_size), 1, fp); 451 | 452 | if (globals.edict_size != edict_size) 453 | qcvm_error(qvm, "Savegame has bad fields"); 454 | 455 | fread(&maxclients, sizeof(maxclients), 1, fp); 456 | 457 | // should agree with server's version 458 | if (game.num_clients != maxclients) 459 | qcvm_error(qvm, "Savegame has bad maxclients"); 460 | 461 | // setup entities 462 | BackupClientData(); 463 | 464 | WipeEntities(); 465 | 466 | globals.num_edicts = game.num_clients + 1; 467 | 468 | // free any string refs inside of the entity structure 469 | qcvm_string_list_check_ref_unset(qvm, globals.edicts, (globals.edict_size * globals.max_edicts) / sizeof(qcvm_global_t), false); 470 | 471 | // load level globals 472 | while (true) 473 | { 474 | size_t len; 475 | fread(&len, sizeof(len), 1, fp); 476 | 477 | if (!len) 478 | break; 479 | 480 | char *def_name = qcvm_temp_buffer(qvm, len); 481 | fread(def_name, sizeof(char), len, fp); 482 | def_name[len] = 0; 483 | 484 | qcvm_definition_hash_t *hashed = qvm->definition_hashes[Q_hash_string(def_name, qvm->definitions_size)]; 485 | 486 | for (; hashed; hashed = hashed->hash_next) 487 | if (!strcmp(qcvm_get_string(qvm, hashed->def->name_index), def_name)) 488 | break; 489 | 490 | if (!hashed) 491 | qcvm_error(qvm, "Bad definition %s", def_name); 492 | 493 | qcvm_definition_t *def = hashed->def; 494 | ReadDefinitionData(qvm, fp, def, qcvm_get_global(qvm, def->global_index)); 495 | } 496 | 497 | // load entity fields 498 | while (true) 499 | { 500 | size_t len; 501 | fread(&len, sizeof(len), 1, fp); 502 | 503 | if (!len) 504 | break; 505 | 506 | char *def_name = qcvm_temp_buffer(qvm, len); 507 | fread(def_name, sizeof(char), len, fp); 508 | def_name[len] = 0; 509 | 510 | qcvm_string_t str; 511 | 512 | if (!qcvm_find_string(qvm, def_name, &str) || qcvm_string_list_is_ref_counted(qvm, str)) 513 | qcvm_error(qvm, "Bad string in save file"); 514 | 515 | qcvm_definition_hash_t *hashed = qvm->field_hashes[Q_hash_string(def_name, qvm->fields_size)]; 516 | 517 | for (; hashed; hashed = hashed->hash_next) 518 | if (!strcmp(qcvm_get_string(qvm, hashed->def->name_index), def_name)) 519 | break; 520 | 521 | if (!hashed) 522 | qcvm_error(qvm, "Bad field %s", def_name); 523 | 524 | qcvm_definition_t *field = hashed->def; 525 | 526 | while (true) 527 | { 528 | uint32_t ent_id; 529 | fread(&ent_id, sizeof(ent_id), 1, fp); 530 | 531 | if (ent_id == -1u) 532 | break; 533 | 534 | if (ent_id >= globals.max_edicts) 535 | qcvm_error(qvm, "%s: bad entity number", __func__); 536 | 537 | if (ent_id >= globals.num_edicts) 538 | globals.num_edicts = ent_id + 1; 539 | 540 | edict_t *ent = qcvm_itoe(qvm, ent_id); 541 | ReadEntityFieldData(qvm, fp, ent, field); 542 | } 543 | } 544 | 545 | WipeClientPointers(); 546 | 547 | RestoreClientData(); 548 | 549 | for (uint32_t i = 0; i < globals.num_edicts; i++) 550 | { 551 | edict_t *ent = qcvm_itoe(qvm, i); 552 | 553 | // let the server rebuild world links for this ent 554 | ent->area = (list_t) { NULL, NULL }; 555 | gi.linkentity(ent); 556 | } 557 | 558 | func = qcvm_get_function(qvm, qce.PostReadLevel); 559 | qcvm_set_global_typed_value(int32_t, qvm, GLOBAL_PARM0, globals.num_edicts); 560 | qcvm_execute(qvm, func); 561 | 562 | fclose(fp); 563 | } 564 | -------------------------------------------------------------------------------- /g_save.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void WriteGame(const char *filename, qboolean autosave); 4 | void ReadGame(const char *filename); 5 | void WriteLevel(const char *filename); 6 | void ReadLevel(const char *filename); 7 | -------------------------------------------------------------------------------- /g_thread.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #define QCVM_INTERNAL 4 | #include "shared/shared.h" 5 | #include "vm.h" 6 | #include "g_thread.h" 7 | }; 8 | 9 | #if ALLOW_DEBUGGING 10 | #include 11 | #include 12 | #include 13 | 14 | qcvm_mutex_t qcvm_cpp_create_mutex(void) 15 | { 16 | return reinterpret_cast(new std::mutex); 17 | } 18 | 19 | void qcvm_cpp_free_mutex(qcvm_mutex_t mutex) 20 | { 21 | delete reinterpret_cast(mutex); 22 | } 23 | 24 | void qcvm_cpp_lock_mutex(qcvm_mutex_t mutex) 25 | { 26 | reinterpret_cast(mutex)->lock(); 27 | } 28 | 29 | void qcvm_cpp_unlock_mutex(qcvm_mutex_t mutex) 30 | { 31 | reinterpret_cast(mutex)->unlock(); 32 | } 33 | 34 | qcvm_thread_t qcvm_cpp_create_thread(qcvm_thread_func_t func) 35 | { 36 | return reinterpret_cast(new std::thread(func)); 37 | } 38 | 39 | void qcvm_cpp_thread_sleep(const uint32_t ms) 40 | { 41 | std::this_thread::sleep_for(std::chrono::milliseconds(ms)); 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /g_thread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if ALLOW_DEBUGGING 4 | // Wrapper for C++ threads. Blek. 5 | #ifdef __cplusplus 6 | extern "C" 7 | { 8 | #endif 9 | qcvm_mutex_t qcvm_cpp_create_mutex(void); 10 | void qcvm_cpp_free_mutex(qcvm_mutex_t); 11 | void qcvm_cpp_lock_mutex(qcvm_mutex_t); 12 | void qcvm_cpp_unlock_mutex(qcvm_mutex_t); 13 | qcvm_thread_t qcvm_cpp_create_thread(qcvm_thread_func_t); 14 | void qcvm_cpp_thread_sleep(const uint32_t); 15 | #ifdef __cplusplus 16 | }; 17 | #endif 18 | #endif -------------------------------------------------------------------------------- /g_time.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include "shared/shared.h" 4 | #include "g_time.h" 5 | }; 6 | 7 | #include 8 | 9 | using clk = std::chrono::high_resolution_clock; 10 | 11 | vec_t qcvm_cpp_now(void) 12 | { 13 | static auto start = clk::now(); 14 | return std::chrono::duration(clk::now() - start).count(); 15 | } -------------------------------------------------------------------------------- /g_time.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" 5 | { 6 | #endif 7 | vec_t qcvm_cpp_now(void); 8 | #ifdef __cplusplus 9 | }; 10 | #endif -------------------------------------------------------------------------------- /game.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | GetGameAPI 3 | -------------------------------------------------------------------------------- /game.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // memory tags to allow dynamic memory to be cleaned up. 4 | // need to use the value 765 because the engine clears this 5 | // on fatal errors. 6 | enum { TAG_GAME = 765 }; 7 | 8 | extern game_import_t gi; 9 | extern game_export_t globals; 10 | 11 | typedef struct 12 | { 13 | gclient_t *clients; 14 | uint32_t num_clients; 15 | 16 | void *client_load_data; 17 | 18 | struct { 19 | uint32_t is_client; 20 | uint32_t is_linked; 21 | uint32_t owner; 22 | } fields; 23 | 24 | struct { 25 | qcvm_function_t *ClientConnect; 26 | qcvm_function_t *ClientBegin; 27 | qcvm_function_t *ClientUserinfoChanged; 28 | qcvm_function_t *ClientDisconnect; 29 | qcvm_function_t *ClientCommand; 30 | qcvm_function_t *ClientThink; 31 | 32 | qcvm_function_t *RunFrame; 33 | 34 | qcvm_function_t *ServerCommand; 35 | } funcs; 36 | } game_t; 37 | 38 | extern game_t game; -------------------------------------------------------------------------------- /gamex86.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30204.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gamex86", "gamex86.vcxproj", "{D86BA707-9BEF-408E-90A1-2985B50FB996}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | KMQuake2 Debug|x64 = KMQuake2 Debug|x64 11 | KMQuake2 Debug|x86 = KMQuake2 Debug|x86 12 | KMQuake2 Release|x64 = KMQuake2 Release|x64 13 | KMQuake2 Release|x86 = KMQuake2 Release|x86 14 | Vanilla Debug|x64 = Vanilla Debug|x64 15 | Vanilla Debug|x86 = Vanilla Debug|x86 16 | Vanilla Release|x64 = Vanilla Release|x64 17 | Vanilla Release|x86 = Vanilla Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.KMQuake2 Debug|x64.ActiveCfg = KMQuake2 Debug|x64 21 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.KMQuake2 Debug|x64.Build.0 = KMQuake2 Debug|x64 22 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.KMQuake2 Debug|x86.ActiveCfg = KMQuake2 Debug|Win32 23 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.KMQuake2 Debug|x86.Build.0 = KMQuake2 Debug|Win32 24 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.KMQuake2 Release|x64.ActiveCfg = KMQuake2 Release|x64 25 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.KMQuake2 Release|x64.Build.0 = KMQuake2 Release|x64 26 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.KMQuake2 Release|x86.ActiveCfg = KMQuake2 Release|Win32 27 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.KMQuake2 Release|x86.Build.0 = KMQuake2 Release|Win32 28 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.Vanilla Debug|x64.ActiveCfg = Vanilla Debug|x64 29 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.Vanilla Debug|x64.Build.0 = Vanilla Debug|x64 30 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.Vanilla Debug|x86.ActiveCfg = Vanilla Debug|Win32 31 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.Vanilla Debug|x86.Build.0 = Vanilla Debug|Win32 32 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.Vanilla Release|x64.ActiveCfg = Vanilla Release|x64 33 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.Vanilla Release|x64.Build.0 = Vanilla Release|x64 34 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.Vanilla Release|x86.ActiveCfg = Vanilla Release|Win32 35 | {D86BA707-9BEF-408E-90A1-2985B50FB996}.Vanilla Release|x86.Build.0 = Vanilla Release|Win32 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {E9766741-32FE-427C-A046-A281A9B1199D} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /gamex86.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | {2b4115b2-12de-4fe1-8346-227ef381fc8d} 9 | 10 | 11 | {e1810386-02c0-4fff-8105-8a6b8bdd92a7} 12 | 13 | 14 | {6f2022d4-528a-4247-8843-58553860cf6f} 15 | 16 | 17 | 18 | 19 | src 20 | 21 | 22 | src 23 | 24 | 25 | src 26 | 27 | 28 | src 29 | 30 | 31 | src 32 | 33 | 34 | src 35 | 36 | 37 | src 38 | 39 | 40 | src 41 | 42 | 43 | src 44 | 45 | 46 | src 47 | 48 | 49 | src 50 | 51 | 52 | src 53 | 54 | 55 | src 56 | 57 | 58 | src 59 | 60 | 61 | src 62 | 63 | 64 | src 65 | 66 | 67 | src 68 | 69 | 70 | src 71 | 72 | 73 | src 74 | 75 | 76 | src 77 | 78 | 79 | 80 | 81 | inc\shared 82 | 83 | 84 | inc\shared 85 | 86 | 87 | inc\shared 88 | 89 | 90 | inc\shared 91 | 92 | 93 | inc\shared 94 | 95 | 96 | inc 97 | 98 | 99 | inc 100 | 101 | 102 | inc 103 | 104 | 105 | inc 106 | 107 | 108 | inc 109 | 110 | 111 | inc 112 | 113 | 114 | inc 115 | 116 | 117 | inc 118 | 119 | 120 | inc 121 | 122 | 123 | inc 124 | 125 | 126 | inc 127 | 128 | 129 | inc 130 | 131 | 132 | inc 133 | 134 | 135 | inc 136 | 137 | 138 | inc 139 | 140 | 141 | inc 142 | 143 | 144 | 145 | inc 146 | 147 | 148 | inc 149 | 150 | 151 | inc 152 | 153 | 154 | inc 155 | 156 | 157 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('quake2c', 'c', 'cpp', 2 | default_options : ['buildtype=debugoptimized', 3 | 'c_std=c17', 4 | 'cpp_std=c++2a', 5 | 'warning_level=0'], 6 | license: 'GPL-3.0-or-later') 7 | 8 | if get_option('KMQUAKE2') 9 | add_project_arguments('-DKMQUAKE2_ENGINE_MOD', language: ['c','cpp']) 10 | endif 11 | 12 | if get_option('X32') 13 | add_project_arguments('-m32', language: ['c', 'cpp']) 14 | add_project_link_arguments('-m32', language: ['c', 'cpp']) 15 | endif 16 | 17 | if get_option('ALLOW_DEBUGGING') 18 | add_project_arguments('-DALLOW_DEBUGGING=1', language: ['c','cpp']) 19 | else 20 | add_project_arguments('-DALLOW_DEBUGGING=0', language: ['c','cpp']) 21 | endif 22 | 23 | if get_option('ALLOW_INSTRUMENTING') 24 | add_project_arguments('-DALLOW_INSTRUMENTING=1', language: ['c', 'cpp']) 25 | endif 26 | 27 | if get_option('ALLOW_PROFILING') 28 | add_project_arguments('-DALLOW_PROFILING=1', language: ['c', 'cpp']) 29 | else 30 | add_project_arguments('-DALLOW_PROFILING=0', language: ['c', 'cpp']) 31 | endif 32 | 33 | if get_option('USE_GNU_OPCODE_JUMPING') 34 | add_project_arguments('-DUSE_GNU_OPCODE_JUMPING=1', language: ['c', 'cpp']) 35 | else 36 | add_project_arguments('-DUSE_GNU_OPCODE_JUMPING=0', language: ['c', 'cpp']) 37 | endif 38 | 39 | inc_dirs = [] 40 | if host_machine.system() == 'windows' 41 | add_project_arguments('-DWINDOWS', language: ['c','cpp']) 42 | inc_dirs += 'windows' 43 | else #extend above here for non-unix porting. This is unlikely to happen... 44 | add_project_arguments('-DUNIX', language: ['c','cpp']) 45 | endif 46 | 47 | 48 | sources = ['shared/shared.c', 49 | 'g_file.cpp', 50 | 'g_main.c', 51 | 'g_save.c', 52 | 'g_thread.cpp', 53 | 'g_time.cpp', 54 | 'vm.c', 55 | 'vm_debug.c', 56 | 'vm_ext.c', 57 | 'vm_file.c', 58 | 'vm_game.c', 59 | 'vm_gi.c', 60 | 'vm_hash.c', 61 | 'vm_heap.c', 62 | 'vm_list.c', 63 | 'vm_math.c', 64 | 'vm_mem.c', 65 | 'vm_string.c', 66 | 'vm_string_list.c', 67 | 'vm_structlist.c'] 68 | 69 | shared_library('game', sources, 70 | name_prefix: '', 71 | include_directories: inc_dirs, 72 | dependencies: dependency('threads')) 73 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('KMQUAKE2', type: 'boolean', value: false, 2 | description: 'build as kmquake2 mod (as opposed to vanilla)') 3 | option('X32', type: 'boolean', value: false, 4 | description: 'force 32bit build (only of relevance on x86...)') 5 | option('ALLOW_DEBUGGING', type: 'boolean', value: true, 6 | description: 'Allow FTEQCC debugging') 7 | option('ALLOW_INSTRUMENTING', type: 'boolean', value: false, 8 | description: 'Allow instrumentation (may hurt performance)') 9 | option('ALLOW_PROFILING', type: 'boolean', value: true, 10 | description: 'Allow profiling (minimal performance impact)') 11 | option('USE_GNU_OPCODE_JUMPING', type: 'boolean', value: true, 12 | description: 'Use GNUC address-of-label jumps.') 13 | #TODO: test for compiler support rather than asking the user to toggle this 14 | -------------------------------------------------------------------------------- /shared/api.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 1997-2001 Id Software, Inc. 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License along 15 | with this program; if not, write to the Free Software Foundation, Inc., 16 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #pragma once 20 | 21 | #define BASEDIR "baseq2" 22 | 23 | // 24 | // game.h -- game dll information visible to server 25 | // 26 | typedef struct edict_s edict_t; 27 | typedef struct gclient_s gclient_t; 28 | 29 | //=============================================================== 30 | 31 | // 32 | // per-level limits 33 | // 34 | enum { MAX_CLIENTS = 256 }; // absolute limit 35 | #ifdef KMQUAKE2_ENGINE_MOD 36 | enum { MAX_EDICTS = 8192 }; // must change protocol to increase more 37 | #else 38 | enum { MAX_EDICTS = 1024 }; // must change protocol to increase more 39 | #endif 40 | 41 | typedef int print_level_t; 42 | 43 | typedef int multicast_t; 44 | 45 | typedef int sound_channel_t; 46 | 47 | typedef vec_t sound_attn_t; 48 | 49 | typedef int config_string_t; 50 | 51 | /* 52 | ========================================================== 53 | 54 | CVARS (console variables) 55 | 56 | ========================================================== 57 | */ 58 | 59 | enum 60 | { 61 | CVAR_NONE = 0, 62 | CVAR_ARCHIVE = 1, // set to cause it to be saved to vars.rc 63 | CVAR_USERINFO = 2, // added to userinfo when changed 64 | CVAR_SERVERINFO = 4, // added to serverinfo when changed 65 | CVAR_NOSET = 8, // don't allow change from console at all, 66 | // but can be set from the command line 67 | CVAR_LATCH = 16 // save changes until server restart 68 | }; 69 | 70 | typedef int cvar_flags_t; 71 | 72 | // nothing outside the cvar.*() functions should modify these fields! 73 | typedef struct 74 | { 75 | char *name; 76 | char *string; 77 | char *latched_string; // for CVAR_LATCH vars 78 | cvar_flags_t flags; 79 | qboolean modified; // set each time the cvar is changed 80 | float value; 81 | } cvar_t; 82 | 83 | /* 84 | ============================================================== 85 | 86 | COLLISION DETECTION 87 | 88 | ============================================================== 89 | */ 90 | 91 | // lower bits are stronger, and will eat weaker brushes completely 92 | typedef int content_flags_t; 93 | 94 | typedef int surface_flags_t; 95 | 96 | typedef int box_edicts_area_t; 97 | 98 | typedef uint8_t plane_type_t; 99 | 100 | // plane_t structure 101 | typedef struct 102 | { 103 | vec3_t normal; 104 | float dist; 105 | plane_type_t type; // for fast side tests 106 | uint8_t signbits; // signx + (signy<<1) + (signz<<1) 107 | uint8_t pad[2]; 108 | } cplane_t; 109 | 110 | typedef struct 111 | { 112 | char name[16]; 113 | surface_flags_t flags; 114 | int value; 115 | } csurface_t; 116 | 117 | // a trace is returned when a box is swept through the world 118 | typedef struct 119 | { 120 | qboolean allsolid; // if true, plane is not valid 121 | qboolean startsolid; // if true, the initial point was in a solid area 122 | float fraction; // time completed, 1.0 = didn't hit anything 123 | vec3_t endpos; // final position 124 | cplane_t plane; // surface normal at impact 125 | csurface_t *surface; // surface hit 126 | content_flags_t contents; // contents on other side of surface hit 127 | edict_t *ent; // not set by CM_*() functions 128 | } trace_t; 129 | 130 | // 131 | // button bits 132 | // 133 | enum 134 | { 135 | BUTTON_ATTACK = 1, 136 | BUTTON_USE = 2, 137 | BUTTON_ANY = 128 // any key whatsoever 138 | }; 139 | 140 | typedef uint8_t button_bits_t; 141 | 142 | // usercmd_t is sent to the server each client frame 143 | typedef struct 144 | { 145 | uint8_t msec; 146 | button_bits_t buttons; 147 | int16_t angles[3]; 148 | int16_t forwardmove, sidemove, upmove; 149 | uint8_t impulse; // remove? 150 | uint8_t lightlevel; // light level the player is standing on 151 | } usercmd_t; 152 | 153 | // pmove_state_t is the information necessary for client side movement 154 | // prediction 155 | enum 156 | { 157 | // can accelerate and turn 158 | PM_NORMAL, 159 | PM_SPECTATOR, 160 | // no acceleration or turning 161 | PM_DEAD, 162 | PM_GIB, // different bounding box 163 | PM_FREEZE 164 | }; 165 | 166 | typedef int pmtype_t; 167 | 168 | // pmove->pm_flags 169 | enum 170 | { 171 | PMF_DUCKED = 1, 172 | PMF_JUMP_HELD = 2, 173 | PMF_ON_GROUND = 4, 174 | PMF_TIME_WATERJUMP = 8, // pm_time is waterjump 175 | PMF_TIME_LAND = 16, // pm_time is time before rejump 176 | PMF_TIME_TELEPORT = 32, // pm_time is non-moving time 177 | PMF_NO_PREDICTION = 64, // temporarily disables prediction (used for grappling hook) 178 | PMF_TELEPORT_BIT = 128 // used by q2pro 179 | }; 180 | 181 | typedef uint8_t pmflags_t; 182 | 183 | // this structure needs to be communicated bit-accurate 184 | // from the server to the client to guarantee that 185 | // prediction stays in sync, so no floats are used. 186 | // if any part of the game code modifies this struct, it 187 | // will result in a prediction error of some degree. 188 | typedef struct 189 | { 190 | pmtype_t pm_type; 191 | 192 | #ifdef KMQUAKE2_ENGINE_MOD 193 | int32_t origin[3]; // 12.3 194 | #else 195 | int16_t origin[3]; // 12.3 196 | #endif 197 | int16_t velocity[3]; // 12.3 198 | pmflags_t pm_flags; // ducked, jump_held, etc 199 | uint8_t pm_time; // each unit = 8 ms 200 | int16_t gravity; 201 | int16_t delta_angles[3]; // add to command angles to get view direction 202 | // changed by spawns, rotating objects, and teleporters 203 | } pmove_state_t; 204 | 205 | enum { MAX_TOUCH = 32 }; 206 | 207 | typedef struct 208 | { 209 | // state (in / out) 210 | pmove_state_t s; 211 | 212 | // command (in) 213 | usercmd_t cmd; 214 | qboolean snapinitial; // if s has been changed outside pmove 215 | 216 | // results (out) 217 | int32_t numtouch; 218 | edict_t *touchents[MAX_TOUCH]; 219 | 220 | vec3_t viewangles; // clamped 221 | vec_t viewheight; 222 | 223 | vec3_t mins, maxs; // bounding box size 224 | 225 | edict_t *groundentity; 226 | int32_t watertype; 227 | int32_t waterlevel; 228 | 229 | // callbacks to test the world 230 | trace_t (*trace)(const vec3_t *start, const vec3_t *mins, const vec3_t *maxs, const vec3_t *end); 231 | content_flags_t (*pointcontents)(const vec3_t *point); 232 | } pmove_t; 233 | 234 | #ifdef KMQUAKE2_ENGINE_MOD 235 | typedef int32_t fileHandle_t; 236 | 237 | typedef int32_t fsMode_t; 238 | #endif 239 | 240 | //=============================================================== 241 | 242 | // 243 | // functions provided by the main engine 244 | // 245 | typedef struct 246 | { 247 | // special messages 248 | void (*bprintf)(print_level_t printlevel, const char *fmt, ...); 249 | void (*dprintf)(const char *fmt, ...); 250 | void (*cprintf)(edict_t *ent, print_level_t printlevel, const char *fmt, ...); 251 | void (*centerprintf)(edict_t *ent, const char *fmt, ...); 252 | void (*sound)(edict_t *ent, sound_channel_t channel, int soundindex, vec_t volume, sound_attn_t attenuation, vec_t timeofs); 253 | void (*positioned_sound)(const vec3_t *origin, edict_t *ent, sound_channel_t channel, int soundindex, vec_t volume, sound_attn_t attenuation, vec_t timeofs); 254 | 255 | // config strings hold all the index strings, the lightstyles, 256 | // and misc data like the sky definition and cdtrack. 257 | // All of the current configstrings are sent to clients when 258 | // they connect, and changes are sent to all connected clients. 259 | void (*configstring)(config_string_t num, const char *string); 260 | 261 | void (* qcvm_noreturn error)(const char *fmt, ...); 262 | 263 | // the *index functions create configstrings and some internal server state 264 | int (*modelindex)(const char *name); 265 | int (*soundindex)(const char *name); 266 | int (*imageindex)(const char *name); 267 | 268 | void (*setmodel)(edict_t *ent, const char *name); 269 | 270 | // collision detection 271 | trace_t (*trace)(const vec3_t *start, const vec3_t *mins, const vec3_t *maxs, const vec3_t *end, edict_t *passent, content_flags_t contentmask); 272 | content_flags_t (*pointcontents)(const vec3_t *point); 273 | qboolean (*inPVS)(const vec3_t *p1, const vec3_t *p2); 274 | qboolean (*inPHS)(const vec3_t *p1, const vec3_t *p2); 275 | void (*SetAreaPortalState)(int portalnum, qboolean open); 276 | qboolean (*AreasConnected)(int area1, int area2); 277 | 278 | // an entity will never be sent to a client or used for collision 279 | // if it is not passed to linkentity. If the size, position, or 280 | // solidity changes, it must be relinked. 281 | void (*linkentity)(edict_t *ent); 282 | void (*unlinkentity)(edict_t *ent); // call before removing an interactive edict 283 | int (*BoxEdicts)(const vec3_t *mins, const vec3_t *maxs, edict_t **list, int maxcount, box_edicts_area_t areatype); 284 | void (*Pmove)(pmove_t *pmove); // player movement code common with client prediction 285 | 286 | // network messaging 287 | void (*multicast)(const vec3_t *origin, multicast_t to); 288 | void (*unicast)(edict_t *ent, qboolean reliable); 289 | void (*WriteChar)(int c); 290 | void (*WriteByte)(int c); 291 | void (*WriteShort)(int c); 292 | void (*WriteLong)(int c); 293 | void (*WriteFloat)(vec_t f); 294 | void (*WriteString)(const char *s); 295 | void (*WritePosition)(const vec3_t *pos); // some fractional bits 296 | void (*WriteDir)(const vec3_t *pos); // single byte encoded, very coarse 297 | void (*WriteAngle)(vec_t f); 298 | 299 | // managed memory allocation 300 | void *(*TagMalloc)(unsigned size, unsigned tag); 301 | void (*TagFree)(void *block); 302 | void (*FreeTags)(unsigned tag); 303 | 304 | // console variable interaction 305 | cvar_t *(*cvar)(const char *var_name, const char *value, cvar_flags_t flags); 306 | cvar_t *(*cvar_set)(const char *var_name, const char *value); 307 | cvar_t *(*cvar_forceset)(const char *var_name, const char *value); 308 | 309 | // ClientCommand and ServerCommand parameter access 310 | int (*argc)(void); 311 | char *(*argv)(int n); 312 | char *(*args)(void); // concatenation of all argv >= 1 313 | 314 | // add commands to the server console as if they were typed in 315 | // for map changing, etc 316 | void (*AddCommandString)(const char *text); 317 | 318 | void (*DebugGraph)(vec_t value, int color); 319 | 320 | // Knightmare- support game DLL loading from pak files thru engine 321 | // This can be used to load script files, etc 322 | #ifdef KMQUAKE2_ENGINE_MOD 323 | char **(*ListPak) (char *find, int *num); // Deprecated- DO NOT USE! 324 | int (*LoadFile) (char *name, void **buf); 325 | void (*FreeFile) (void *buf); 326 | void (*FreeFileList) (char **list, int n); 327 | int (*OpenFile) (const char *name, fileHandle_t *f, fsMode_t mode); 328 | int (*OpenCompressedFile) (const char *zipName, const char *fileName, fileHandle_t *f, fsMode_t mode); 329 | void (*CloseFile) (fileHandle_t f); 330 | int (*FRead) (void *buffer, int size, fileHandle_t f); 331 | int (*FWrite) (const void *buffer, int size, fileHandle_t f); 332 | char *(*FS_GameDir) (void); 333 | char *(*FS_SaveGameDir) (void); 334 | void (*CreatePath) (char *path); 335 | char **(*GetFileList) (const char *path, const char *extension, int *num); 336 | #endif 337 | } game_import_t; 338 | 339 | // 340 | // functions exported by the game subsystem 341 | // 342 | typedef struct 343 | { 344 | int apiversion; 345 | 346 | // the init function will only be called when a game starts, 347 | // not each time a level is loaded. Persistant data for clients 348 | // and the server can be allocated in init 349 | void (*Init)(void); 350 | void (*Shutdown)(void); 351 | 352 | // each new level entered will cause a call to SpawnEntities 353 | void (*SpawnEntities)(const char *mapname, const char *entstring, const char *spawnpoint); 354 | 355 | // Read/Write Game is for storing persistant cross level information 356 | // about the world state and the clients. 357 | // WriteGame is called every time a level is exited. 358 | // ReadGame is called on a loadgame. 359 | void (*WriteGame)(const char *filename, qboolean autosave); 360 | void (*ReadGame)(const char *filename); 361 | 362 | // ReadLevel is called after the default map information has been 363 | // loaded with SpawnEntities 364 | void (*WriteLevel)(const char *filename); 365 | void (*ReadLevel)(const char *filename); 366 | 367 | qboolean (*ClientConnect)(edict_t *ent, char *userinfo); 368 | void (*ClientBegin)(edict_t *ent); 369 | void (*ClientUserinfoChanged)(edict_t *ent, char *userinfo); 370 | void (*ClientDisconnect)(edict_t *ent); 371 | void (*ClientCommand)(edict_t *ent); 372 | void (*ClientThink)(edict_t *ent, usercmd_t *cmd); 373 | 374 | void (*RunFrame)(void); 375 | 376 | // ServerCommand will be called when an "sv " command is issued on the 377 | // server console. 378 | // The game can issue gi.argc() / gi.argv() commands to get the rest 379 | // of the parameters 380 | void (*ServerCommand)(void); 381 | 382 | // 383 | // global variables shared between game and server 384 | // 385 | 386 | // The edict array is allocated in the game dll so it 387 | // can vary in size from one game to another. 388 | // 389 | // The size will be fixed when ge->Init() is called 390 | edict_t *edicts; 391 | int edict_size; 392 | int num_edicts; // current number, <= max_edicts 393 | int max_edicts; 394 | } game_export_t; -------------------------------------------------------------------------------- /shared/client.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 1997-2001 Id Software, Inc. 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License along 15 | with this program; if not, write to the Free Software Foundation, Inc., 16 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #pragma once 20 | 21 | // player_state_t->refdef flags 22 | typedef enum 23 | { 24 | RDF_UNDERWATER = 1, // warp the screen as apropriate 25 | RDF_NOWORLDMODEL = 2, // used for player configuration screen 26 | 27 | //ROGUE 28 | RDF_IRGOGGLES = 4, 29 | RDF_UVGOGGLES = 8 30 | //ROGUE 31 | } refdef_flags_t; 32 | 33 | // player_state->stats[] indexes 34 | enum 35 | { 36 | // For engine compatibility, these 18 IDs should remain the same 37 | // and keep their described usage 38 | STAT_HEALTH_ICON, 39 | STAT_HEALTH, 40 | STAT_AMMO_ICON, 41 | STAT_AMMO, 42 | STAT_ARMOR_ICON, 43 | STAT_ARMOR, 44 | STAT_SELECTED_ICON, 45 | STAT_PICKUP_ICON, 46 | STAT_PICKUP_STRING, 47 | STAT_TIMER_ICON, 48 | STAT_TIMER, 49 | STAT_HELPICON, 50 | STAT_SELECTED_ITEM, 51 | STAT_LAYOUTS, 52 | STAT_FRAGS, 53 | STAT_FLASHES, // cleared each frame, 1 = health, 2 = armor 54 | STAT_CHASE, 55 | STAT_SPECTATOR, 56 | // Bits from here to 31 are free for mods 57 | 58 | #ifdef KMQUAKE2_ENGINE_MOD 59 | MAX_STATS = 256 60 | #else 61 | MAX_STATS = 32 62 | #endif 63 | }; 64 | 65 | typedef int16_t player_stat_t; 66 | 67 | // player_state_t is the information needed in addition to pmove_state_t 68 | // to rendered a view. There will only be 10 player_state_t sent each second, 69 | // but the number of pmove_state_t changes will be reletive to client 70 | // frame rates 71 | typedef struct 72 | { 73 | pmove_state_t pmove; // for prediction 74 | 75 | // these fields do not need to be communicated bit-precise 76 | 77 | vec3_t viewangles; // for fixed views 78 | vec3_t viewoffset; // add to pmovestate->origin 79 | vec3_t kick_angles; // add to view direction to get render angles 80 | // set by weapon kicks, pain effects, etc 81 | 82 | vec3_t gunangles; 83 | vec3_t gunoffset; 84 | int gunindex; 85 | int gunframe; 86 | 87 | #ifdef KMQUAKE2_ENGINE_MOD //Knightmare added 88 | int gunskin; // for animated weapon skins 89 | int gunindex2; // for a second weapon model (boot) 90 | int gunframe2; 91 | int gunskin2; 92 | 93 | // server-side speed control! 94 | int maxspeed; 95 | int duckspeed; 96 | int waterspeed; 97 | int accel; 98 | int stopspeed; 99 | #endif 100 | 101 | float blend[4]; // rgba full screen effect 102 | 103 | float fov; // horizontal field of view 104 | 105 | refdef_flags_t rdflags; // refdef flags 106 | 107 | player_stat_t stats[MAX_STATS]; // fast status bar updates 108 | } player_state_t; 109 | 110 | 111 | struct gclient_s 112 | { 113 | player_state_t ps; // communicated by server to clients 114 | int ping; 115 | 116 | // the game dll can add anything it wants after 117 | // this point in the structure 118 | int clientNum; 119 | }; -------------------------------------------------------------------------------- /shared/entity.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef uint32_t entity_effects_t; 4 | 5 | typedef uint32_t render_effects_t; 6 | 7 | typedef int32_t entity_event_t; 8 | 9 | // entity_state_t is the information conveyed from the server 10 | // in an update message about entities that the client will 11 | // need to render in some way 12 | typedef struct 13 | { 14 | int number; // edict index 15 | 16 | vec3_t origin; 17 | vec3_t angles; 18 | vec3_t old_origin; // for lerping 19 | int modelindex; 20 | int modelindex2, modelindex3, modelindex4; // weapons, CTF flags, etc 21 | #ifdef KMQUAKE2_ENGINE_MOD //Knightmare- Privater wanted this 22 | int modelindex5, modelindex6; //more attached models 23 | #endif 24 | int frame; 25 | int skinnum; 26 | #ifdef KMQUAKE2_ENGINE_MOD //Knightmare- allow the server to set this 27 | float alpha; //entity transparency 28 | #endif 29 | entity_effects_t effects; 30 | render_effects_t renderfx; 31 | int solid; // for client side prediction, 8*(bits 0-4) is x/y radius 32 | // 8*(bits 5-9) is z down distance, 8(bits10-15) is z up 33 | // gi.linkentity sets this properly 34 | int sound; // for looping sounds, to guarantee shutoff 35 | #ifdef KMQUAKE2_ENGINE_MOD // Knightmare- added sound attenuation 36 | float attenuation; 37 | #endif 38 | entity_event_t event; // impulse events -- muzzle flashes, footsteps, etc 39 | // events only go out for a single frame, they 40 | // are automatically cleared each frame 41 | } entity_state_t; 42 | 43 | typedef struct list_s 44 | { 45 | struct list_s *next, *prev; 46 | } list_t; 47 | 48 | // edict->svflags 49 | typedef int32_t svflags_t; 50 | 51 | // edict->solid values 52 | typedef int32_t solid_t; 53 | 54 | enum { MAX_ENT_CLUSTERS = 16 }; 55 | 56 | typedef struct edict_s edict_t; 57 | 58 | typedef struct edict_s 59 | { 60 | entity_state_t s; 61 | gclient_t *client; 62 | qboolean inuse; 63 | int linkcount; 64 | 65 | list_t area; // linked to a division node or leaf 66 | 67 | int num_clusters; // if -1, use headnode instead 68 | int clusternums[MAX_ENT_CLUSTERS]; 69 | int headnode; // unused if num_clusters != -1 70 | int areanum, areanum2; 71 | 72 | //================================ 73 | 74 | svflags_t svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc 75 | vec3_t mins, maxs; 76 | vec3_t absmin, absmax, size; 77 | solid_t solid; 78 | content_flags_t clipmask; 79 | edict_t *owner; 80 | } edict_t; -------------------------------------------------------------------------------- /shared/platform.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2012 Andrey Nazarov 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License along 15 | with this program; if not, write to the Free Software Foundation, Inc., 16 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | // 20 | // platform.h -- platform-specific definitions 21 | // 22 | 23 | #include 24 | 25 | #ifdef _WIN32 26 | #include 27 | #else 28 | #include 29 | #endif 30 | 31 | #include 32 | 33 | #ifdef _WIN32 34 | #define mkdir _mkdir 35 | #endif 36 | -------------------------------------------------------------------------------- /shared/shared.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 1997-2001 Id Software, Inc. 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License along 15 | with this program; if not, write to the Free Software Foundation, Inc., 16 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #include "shared.h" 20 | 21 | /* 22 | =============== 23 | Q_strlcpy 24 | 25 | Returns length of the source string. 26 | =============== 27 | */ 28 | size_t Q_strlcpy(char *dst, const char *src, size_t size) 29 | { 30 | size_t ret = strlen(src); 31 | 32 | if (size) { 33 | size_t len = size - 1; 34 | 35 | if (ret < len) 36 | len = ret; 37 | 38 | memcpy(dst, src, len); 39 | dst[len] = 0; 40 | } 41 | 42 | return ret; 43 | } 44 | 45 | /* 46 | ================ 47 | Q_hash_string 48 | ================ 49 | */ 50 | uint32_t Q_hash_string (const char *string, const size_t hash_size) 51 | { 52 | if (!hash_size) 53 | return 0; 54 | 55 | uint32_t hashValue = 0; 56 | 57 | for (size_t i = 0; *string; i++) 58 | { 59 | const char ch = *(string++); 60 | hashValue = hashValue * 33 + ch; 61 | } 62 | 63 | return (hashValue + (hashValue >> 5)) % hash_size; 64 | } 65 | 66 | /* 67 | ================ 68 | Q_hash_pointer 69 | ================ 70 | */ 71 | uint32_t Q_hash_pointer(uint32_t a, const size_t hash_size) 72 | { 73 | if (!hash_size) 74 | return 0; 75 | 76 | a = (a ^ 61) ^ (a >> 16); 77 | a = a + (a << 3); 78 | a = a ^ (a >> 4); 79 | a = a * 0x27d4eb2d; 80 | a = a ^ (a >> 15); 81 | return a & (hash_size - 1); 82 | } 83 | 84 | /* 85 | ================ 86 | Q_next_pow2 87 | ================ 88 | */ 89 | uint64_t Q_next_pow2(uint64_t x) 90 | { 91 | #ifdef __GNU__ 92 | return x == 1 ? 1 : 1 << (64 - __builtin_clzl((uint32_t)(x - 1))); 93 | #else 94 | x--; 95 | x |= x >> 1; 96 | x |= x >> 2; 97 | x |= x >> 4; 98 | x |= x >> 8; 99 | x |= x >> 16; 100 | x |= x >> 32; 101 | return x + 1; 102 | #endif 103 | } 104 | -------------------------------------------------------------------------------- /shared/shared.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 1997-2001 Id Software, Inc. 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License along 15 | with this program; if not, write to the Free Software Foundation, Inc., 16 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | */ 18 | 19 | #pragma once 20 | 21 | // 22 | // shared.h -- included first by ALL program modules 23 | // 24 | 25 | #if HAVE_CONFIG_H 26 | #include "config.h" 27 | #endif 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "platform.h" 40 | 41 | #if defined(__clang__) || defined(__GNUC__) 42 | #define qcvm_always_inline __attribute__((always_inline)) inline 43 | #define qcvm_noreturn __attribute__((noreturn)) 44 | #elif defined(_MSC_VER) 45 | #define qcvm_always_inline __forceinline 46 | #ifndef __cplusplus 47 | #define inline __inline 48 | #endif 49 | #define qcvm_noreturn 50 | #endif 51 | 52 | #ifdef UNIX 53 | //shim stricmp/strnicmp 54 | #include 55 | #define stricmp strcasecmp 56 | #define strnicmp strncasecmp 57 | #endif 58 | 59 | // ABI compat only, don't use 60 | typedef enum 61 | { 62 | qfalse, 63 | qtrue 64 | } qboolean; 65 | 66 | /* 67 | ============================================================== 68 | 69 | MATHLIB 70 | 71 | ============================================================== 72 | */ 73 | 74 | typedef float vec_t; 75 | 76 | typedef struct 77 | { 78 | vec_t x, y, z; 79 | } vec3_t; 80 | 81 | inline vec_t DotProduct(const vec3_t l, const vec3_t r) 82 | { 83 | return l.x * r.x + l.y * r.y + l.z * r.z; 84 | } 85 | 86 | #ifdef __cplusplus 87 | #define VEC3 88 | #else 89 | #define VEC3 (vec3_t) 90 | #endif 91 | 92 | inline vec3_t VectorAdd(const vec3_t l, const vec3_t r) 93 | { 94 | return VEC3 { 95 | l.x + r.x, 96 | l.y + r.y, 97 | l.z + r.z 98 | }; 99 | } 100 | 101 | inline vec3_t VectorSubtract(const vec3_t l, const vec3_t r) 102 | { 103 | return VEC3 { 104 | l.x - r.x, 105 | l.y - r.y, 106 | l.z - r.z 107 | }; 108 | } 109 | 110 | inline bool VectorEmpty(const vec3_t v) 111 | { 112 | return !v.x && !v.y && !v.z; 113 | } 114 | 115 | inline bool VectorEquals(const vec3_t a, const vec3_t b) 116 | { 117 | return a.x == b.x && a.y == b.y && a.z == b.z; 118 | } 119 | 120 | inline vec3_t VectorScaleF(const vec3_t v, const vec_t r) 121 | { 122 | return VEC3 { 123 | v.x * r, 124 | v.y * r, 125 | v.z * r 126 | }; 127 | } 128 | 129 | inline vec3_t VectorScaleI(const vec3_t v, const int32_t r) 130 | { 131 | return VEC3 { 132 | v.x * (vec_t) r, 133 | v.y * (vec_t) r, 134 | v.z * (vec_t) r 135 | }; 136 | } 137 | 138 | inline vec3_t VectorDivideF(const vec3_t v, const vec_t r) 139 | { 140 | return VEC3 { 141 | v.x / r, 142 | v.y / r, 143 | v.z / r 144 | }; 145 | } 146 | 147 | inline vec3_t VectorDivideI(const vec3_t v, const int32_t r) 148 | { 149 | return VEC3 { 150 | v.x / (vec_t) r, 151 | v.y / (vec_t) r, 152 | v.z / (vec_t) r 153 | }; 154 | } 155 | 156 | /* 157 | ============================================================== 158 | 159 | MATH 160 | 161 | ============================================================== 162 | */ 163 | 164 | #define coord2short (8.f) 165 | #define angle2short (65536.f / 360.f) 166 | #define short2coord (1.0f / 8) 167 | #define short2angle (360.0f / 65536) 168 | 169 | inline vec_t maxf(const vec_t a, const vec_t b) { return a > b ? a : b; } 170 | inline vec_t minf(const vec_t a, const vec_t b) { return a < b ? a : b; } 171 | 172 | inline int32_t maxi(const int32_t a, const int32_t b) { return a > b ? a : b; } 173 | inline int32_t mini(const int32_t a, const int32_t b) { return a < b ? a : b; } 174 | 175 | inline size_t maxsz(const size_t a, const size_t b) { return a > b ? a : b; } 176 | inline size_t minsz(const size_t a, const size_t b) { return a < b ? a : b; } 177 | 178 | /* 179 | ============================================================== 180 | 181 | STRING 182 | 183 | ============================================================== 184 | */ 185 | 186 | enum { MAX_QPATH = 64 }; // max length of a quake game pathname 187 | 188 | // buffer safe operations 189 | size_t Q_strlcpy(char *dst, const char *src, size_t size); 190 | 191 | enum { MAX_INFO_STRING = 512 }; 192 | 193 | uint32_t Q_hash_string(const char *string, const size_t hash_size); 194 | 195 | uint32_t Q_hash_pointer(uint32_t a, const size_t hash_size); 196 | 197 | uint64_t Q_next_pow2(uint64_t x); 198 | 199 | /* 200 | ========================================================== 201 | 202 | ELEMENTS COMMUNICATED ACROSS THE NET 203 | 204 | ========================================================== 205 | */ 206 | 207 | #include "api.h" 208 | #include "client.h" 209 | #include "entity.h" 210 | -------------------------------------------------------------------------------- /vm_debug.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | #include "vm_game.h" 4 | #include "vm_debug.h" 5 | #include "vm_string.h" 6 | 7 | static void QC_stacktrace(qcvm_t *vm) 8 | { 9 | qcvm_return_string(vm, qcvm_stack_trace(vm, qcvm_argv_int32(vm, 0))); 10 | } 11 | 12 | static void QC_debugbreak(qcvm_t *vm) 13 | { 14 | #if ALLOW_DEBUGGING 15 | qcvm_break_on_current_statement(vm); 16 | #endif 17 | } 18 | 19 | static void QC_dumpentity(qcvm_t *vm) 20 | { 21 | FILE *fp = fopen(qcvm_temp_format(vm, "%sdumpentity.text", vm->path), "a"); 22 | edict_t *ent = qcvm_argv_entity(vm, 0); 23 | 24 | for (qcvm_definition_t *f = vm->fields; f < vm->fields + vm->fields_size; f++) 25 | { 26 | fprintf(fp, "%s: ", qcvm_get_string(vm, f->name_index)); 27 | 28 | ptrdiff_t val; 29 | 30 | if (!qcvm_resolve_pointer(vm, qcvm_get_entity_field_pointer(vm, ent, (int32_t)f->global_index), true, qcvm_type_size(f->id), (void **)&val)) 31 | { 32 | fclose(fp); 33 | qcvm_error(vm, "invalid pointer"); 34 | } 35 | 36 | switch (f->id) 37 | { 38 | case TYPE_FLOAT: 39 | fprintf(fp, "%f", *(vec_t *)val); 40 | break; 41 | case TYPE_VECTOR: { 42 | vec_t *v = (vec_t *)val; 43 | fprintf(fp, "%f %f %f", v[0], v[1], v[2]); 44 | break; } 45 | default: 46 | fprintf(fp, "%i", *(int32_t *)val); 47 | break; 48 | } 49 | 50 | fprintf(fp, "\r\n"); 51 | } 52 | 53 | fclose(fp); 54 | } 55 | 56 | void qcvm_init_debug_builtins(qcvm_t *vm) 57 | { 58 | qcvm_register_builtin(stacktrace); 59 | qcvm_register_builtin(debugbreak); 60 | qcvm_register_builtin(dumpentity); 61 | } 62 | 63 | #if ALLOW_DEBUGGING 64 | static size_t debuggerwnd; 65 | static char *debugger_command; 66 | static qcvm_mutex_t input_mutex; 67 | static bool awaiting_steal = false; 68 | static qcvm_thread_t input_thread; 69 | static bool running_thread = false; 70 | static qcvm_t *thread_vm; 71 | 72 | static void qcvm_debugger_thread(void) 73 | { 74 | static char debug_string[MAX_INFO_STRING * 4]; 75 | static size_t debug_pos = 0; 76 | 77 | debugger_command = debug_string; 78 | 79 | while (true) 80 | { 81 | while (true) 82 | { 83 | char c = getc(stdin); 84 | 85 | if (c == 0) 86 | return; 87 | 88 | if (debug_pos >= sizeof(debug_string)) 89 | { 90 | debug_pos = 0; 91 | debug_string[0] = 0; 92 | thread_vm->debug_print("DEBUGGER STRING OVERFLOW :("); 93 | } 94 | 95 | debug_string[debug_pos] = c; 96 | 97 | if (debug_string[debug_pos] == '\n') 98 | { 99 | debug_string[debug_pos] = 0; 100 | break; 101 | } 102 | 103 | debug_pos++; 104 | } 105 | 106 | thread_vm->debug.lock_mutex(input_mutex); 107 | debug_pos = 0; 108 | awaiting_steal = true; 109 | thread_vm->debug.unlock_mutex(input_mutex); 110 | 111 | while (true) 112 | { 113 | thread_vm->debug.thread_sleep(10); 114 | 115 | thread_vm->debug.lock_mutex(input_mutex); 116 | bool can_break = !awaiting_steal; 117 | thread_vm->debug.unlock_mutex(input_mutex); 118 | 119 | if (can_break) 120 | break; 121 | } 122 | } 123 | } 124 | 125 | void qcvm_wait_for_debugger_commands(qcvm_t *vm); 126 | 127 | void qcvm_init_debugger(qcvm_t *vm) 128 | { 129 | thread_vm = vm; 130 | input_mutex = thread_vm->debug.create_mutex(); 131 | thread_vm->debug.thread_sleep(5000); 132 | 133 | input_thread = thread_vm->debug.create_thread(qcvm_debugger_thread); 134 | running_thread = true; 135 | vm->debug.state = DEBUG_BROKE; 136 | 137 | qcvm_wait_for_debugger_commands(vm); 138 | } 139 | 140 | 141 | void qcvm_send_debugger_command(const qcvm_t *vm, const char *cmd) 142 | { 143 | printf("%s\n", cmd); 144 | fflush(stdout); 145 | } 146 | 147 | static const char *strtok_emulate(qcvm_t *vm, qcvm_function_t *func, const char *string, int *start) 148 | { 149 | qcvm_set_global_str(vm, GLOBAL_PARM0, string, strlen(string), true); 150 | qcvm_set_global_typed_ptr(int32_t, vm, GLOBAL_PARM1, start); 151 | qcvm_execute(vm, func); 152 | 153 | *start = *qcvm_get_global_typed(int32_t, vm, GLOBAL_PARM1); 154 | return qcvm_get_string(vm, *qcvm_get_global_typed(qcvm_string_t, vm, GLOBAL_RETURN)); 155 | } 156 | 157 | void qcvm_check_debugger_commands(qcvm_t *vm) 158 | { 159 | if (!thread_vm) 160 | return; 161 | 162 | thread_vm->debug.lock_mutex(input_mutex); 163 | 164 | if (!awaiting_steal) 165 | { 166 | thread_vm->debug.unlock_mutex(input_mutex); 167 | return; 168 | } 169 | 170 | while (*debugger_command && debugger_command[strlen(debugger_command) - 1] == '\n') 171 | debugger_command[strlen(debugger_command) - 1] = 0; 172 | 173 | qcvm_function_t *qc_strtok = qcvm_find_function(vm, "strtok"); 174 | 175 | if (!qc_strtok) 176 | qcvm_error(vm, "Can't find strtok :("); 177 | else if (strncmp(debugger_command, "debuggerwnd ", 12) == 0) 178 | { 179 | vm->debug.attached = true; 180 | debuggerwnd = strtoul(debugger_command + 12, NULL, 0); 181 | qcvm_send_debugger_command(vm, qcvm_temp_format(vm, "qcreloaded \"%s\" \"%s\"\n", vm->engine_name, vm->path)); 182 | } 183 | else if (strncmp(debugger_command, "qcbreakpoint ", 13) == 0) 184 | { 185 | int start = 13; 186 | int mode = strtol(strtok_emulate(vm, qc_strtok, debugger_command, &start), NULL, 10); 187 | const char *file = strtok_emulate(vm, qc_strtok, debugger_command, &start); 188 | int line = strtol(strtok_emulate(vm, qc_strtok, debugger_command, &start), NULL, 10); 189 | 190 | qcvm_set_breakpoint(vm, mode, file, line); 191 | } 192 | else if (strcmp(debugger_command, "qcresume") == 0) 193 | { 194 | vm->debug.state = DEBUG_NONE; 195 | } 196 | else if (strncmp(debugger_command, "qcstep ", 7) == 0) 197 | { 198 | int start = 7; 199 | const char *mode = strtok_emulate(vm, qc_strtok, debugger_command, &start); 200 | 201 | if (!strcmp(mode, "into")) 202 | vm->debug.state = DEBUG_STEP_INTO; 203 | else if (!strcmp(mode, "out")) 204 | vm->debug.state = DEBUG_STEP_OUT; 205 | else 206 | vm->debug.state = DEBUG_STEP_OVER; 207 | } 208 | else if (strncmp(debugger_command, "qcinspect ", 10) == 0) 209 | { 210 | int start = 10; 211 | const char *variable = strtok_emulate(vm, qc_strtok, debugger_command, &start); 212 | 213 | qcvm_evaluated_t result = qcvm_evaluate(vm, variable); 214 | const char *value; 215 | char *slashed = NULL; 216 | 217 | switch (result.variant.type) 218 | { 219 | case TYPE_INTEGER: 220 | value = qcvm_temp_format(vm, "[%i]_%i", result.global, result.variant.value.itg); 221 | break; 222 | case TYPE_FLOAT: 223 | value = qcvm_temp_format(vm, "[%i]_%g", result.global, result.variant.value.flt); 224 | break; 225 | case TYPE_VECTOR: 226 | value = qcvm_temp_format(vm, "[%i]_%f_%f_%f", result.global, result.variant.value.vec.x, result.variant.value.vec.y, result.variant.value.vec.z); 227 | break; 228 | case TYPE_STRING: 229 | value = qcvm_get_string(vm, result.variant.value.str); 230 | 231 | if (strchr(value, '"') || strchr(value, '\\')) 232 | { 233 | size_t newlen = strlen(value); 234 | 235 | for (const char *c = value; *c; c++) 236 | if (*c == '"' || *c == '\\') 237 | newlen++; 238 | 239 | slashed = (char *)qcvm_alloc(vm, newlen + 2 + 1); 240 | *slashed = '"'; 241 | strcpy(slashed + 1, value); 242 | 243 | const char *end = slashed + newlen + 1; 244 | char *c; 245 | 246 | for (c = slashed + 1; *c; c++) 247 | { 248 | if (*c != '"' && *c != '\\') 249 | continue; 250 | 251 | memmove(c + 1, c, end - c); 252 | *c = '\\'; 253 | c++; 254 | } 255 | 256 | *c++ = '"'; 257 | *c = 0; 258 | 259 | value = slashed; 260 | } 261 | 262 | value = qcvm_temp_format(vm, "[%i]_%s", result.global, value); 263 | break; 264 | case TYPE_ENTITY: 265 | if (result.variant.value.ent == ENT_INVALID) 266 | value = qcvm_temp_format(vm, "[%i]_invalid/null_entity", result.global); 267 | else 268 | value = qcvm_temp_format(vm, "[%i]_entity_%i", result.global, qcvm_ent_to_entity(vm, result.variant.value.ent, false)->s.number); 269 | break; 270 | case TYPE_FUNCTION: 271 | if (result.variant.value.fnc == FUNC_VOID) 272 | value = qcvm_temp_format(vm, "[%i]_invalid/null_function", result.global); 273 | else 274 | { 275 | qcvm_function_t *func = qcvm_get_function(vm, result.variant.value.fnc); 276 | 277 | if (!func || func->name_index == STRING_EMPTY) 278 | value = qcvm_temp_format(vm, "[%i]_can't_resolve_function_%i", result.global, result.variant.value.fnc); 279 | else 280 | value = qcvm_temp_format(vm, "[%i]_%s", result.global, qcvm_get_string(vm, func->name_index)); 281 | } 282 | break; 283 | default: 284 | value = "unable_to_evaluate"; 285 | break; 286 | } 287 | 288 | qcvm_send_debugger_command(vm, qcvm_temp_format(vm, "qcvalue \"%s\" %s\n", variable, value)); 289 | 290 | if (slashed) 291 | qcvm_mem_free(vm, slashed); 292 | } 293 | 294 | awaiting_steal = false; 295 | thread_vm->debug.unlock_mutex(input_mutex); 296 | } 297 | 298 | void qcvm_wait_for_debugger_commands(qcvm_t *vm) 299 | { 300 | while (vm->debug.state == DEBUG_BROKE) 301 | { 302 | qcvm_check_debugger_commands(vm); 303 | vm->debug.thread_sleep(1); 304 | } 305 | } 306 | #endif -------------------------------------------------------------------------------- /vm_debug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if ALLOW_DEBUGGING 4 | typedef struct 5 | { 6 | qcvm_global_t global; 7 | qcvm_variant_t variant; 8 | } qcvm_evaluated_t; 9 | 10 | qcvm_evaluated_t qcvm_evaluate(qcvm_t *vm, const char *variable); 11 | void qcvm_break_on_current_statement(qcvm_t *vm); 12 | void qcvm_set_breakpoint(qcvm_t *vm, const bool is_set, const char *file, const int line); 13 | void qcvm_check_debugger_commands(qcvm_t *vm); 14 | void qcvm_send_debugger_command(const qcvm_t *vm, const char *cmd); 15 | void qcvm_wait_for_debugger_commands(qcvm_t *vm); 16 | void qcvm_init_debugger(qcvm_t *vm); 17 | #endif 18 | 19 | void qcvm_init_debug_builtins(qcvm_t *vm); -------------------------------------------------------------------------------- /vm_ext.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | #include "vm_ext.h" 4 | 5 | static void QC_ModInt(qcvm_t *vm) 6 | { 7 | const int a = qcvm_argv_int32(vm, 0); 8 | const int b = qcvm_argv_int32(vm, 1); 9 | 10 | qcvm_return_int32(vm, a % b); 11 | } 12 | 13 | static void QC_func_get(qcvm_t *vm) 14 | { 15 | const char *s = qcvm_argv_string(vm, 0); 16 | qcvm_func_t func = qcvm_find_function_id(vm, s); 17 | qcvm_return_func(vm, func); 18 | } 19 | 20 | static void QC_handle_free(qcvm_t *vm) 21 | { 22 | const int32_t id = qcvm_argv_int32(vm, 0); 23 | qcvm_handle_free(vm, qcvm_fetch_handle(vm, id)); 24 | } 25 | 26 | typedef struct 27 | { 28 | qcvm_t *vm; 29 | qcvm_pointer_t elements; 30 | qcvm_pointer_t ctx; 31 | qcvm_function_t *func; 32 | } qsort_context_t; 33 | 34 | #if defined(_WIN32) 35 | static int QC_qsort_callback(void *ctx, const void *a, const void *b) 36 | #else 37 | static int QC_qsort_callback(const void *a, const void *b, void *ctx) 38 | #endif 39 | { 40 | qsort_context_t *context = (qsort_context_t *)ctx; 41 | qcvm_pointer_t a_ptr = qcvm_make_pointer(context->vm, context->elements.raw.type, a); 42 | qcvm_pointer_t b_ptr = qcvm_make_pointer(context->vm, context->elements.raw.type, b); 43 | 44 | qcvm_set_global_typed_value(qcvm_pointer_t, context->vm, GLOBAL_PARM0, a_ptr); 45 | qcvm_set_global_typed_value(qcvm_pointer_t, context->vm, GLOBAL_PARM1, b_ptr); 46 | qcvm_set_global_typed_value(qcvm_pointer_t, context->vm, GLOBAL_PARM2, context->ctx); 47 | qcvm_execute(context->vm, context->func); 48 | 49 | return *qcvm_get_global_typed(int32_t, context->vm, GLOBAL_RETURN); 50 | } 51 | 52 | #if !defined(__STDC_LIB_EXT1__) && !defined(_WIN32) 53 | static int (*qsort_s_callback)(const void *, const void *, void *); 54 | static void *qsort_s_ctx; 55 | 56 | static int qsort_s_wrapper(const void *a, const void *b) 57 | { 58 | return qsort_s_callback(a, b, qsort_s_ctx); 59 | } 60 | 61 | static void qsort_s(const void *base, size_t count, size_t size, int (*callback)(const void *, const void *, void *), void *context) 62 | { 63 | qsort_s_callback = callback; 64 | qsort_s_ctx = context; 65 | 66 | qsort((void *)base, count, size, qsort_s_wrapper); 67 | } 68 | #endif 69 | 70 | static void QC_qsort(qcvm_t *vm) 71 | { 72 | const qcvm_pointer_t elements = qcvm_argv_pointer(vm, 0); 73 | const int32_t num = qcvm_argv_int32(vm, 1); 74 | const int32_t size_of_element = qcvm_argv_int32(vm, 2); 75 | void *address; 76 | 77 | if (elements.raw.type == QCVM_POINTER_HANDLE || !qcvm_resolve_pointer(vm, elements, false, num * size_of_element, &address)) 78 | qcvm_error(vm, "bad pointer"); 79 | 80 | qcvm_function_t *comparator_func = qcvm_get_function(vm, qcvm_argv_int32(vm, 3)); 81 | 82 | qsort_context_t context = { vm, elements, { 0 }, comparator_func }; 83 | 84 | if (vm->state.argc > 4) 85 | context.ctx = qcvm_argv_pointer(vm, 4); 86 | 87 | qsort_s(address, num, size_of_element, QC_qsort_callback, &context); 88 | } 89 | 90 | void qcvm_init_ext_builtins(qcvm_t *vm) 91 | { 92 | qcvm_register_builtin(ModInt); 93 | qcvm_register_builtin(func_get); 94 | qcvm_register_builtin(handle_free); 95 | qcvm_register_builtin(qsort); 96 | } -------------------------------------------------------------------------------- /vm_ext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void qcvm_init_ext_builtins(qcvm_t *vm); -------------------------------------------------------------------------------- /vm_file.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | #include "vm_string.h" 4 | #include "vm_file.h" 5 | #include "g_file.h" 6 | 7 | #include "game.h" 8 | 9 | #ifndef KMQUAKE2_ENGINE_MOD 10 | typedef size_t fileHandle_t; 11 | 12 | typedef enum 13 | { 14 | FS_READ, 15 | FS_WRITE, 16 | FS_APPEND 17 | } fsMode_t; 18 | 19 | static FILE *OpenFile(qcvm_t *vm, const char *name, const fsMode_t mode) 20 | { 21 | return fopen(qcvm_temp_format(vm, "%s%s", vm->path, name), mode == FS_READ ? "rb" : mode == FS_APPEND ? "ab" : "wb"); 22 | } 23 | #endif 24 | 25 | static void QC_LoadFile(qcvm_t *vm) 26 | { 27 | const char *name = qcvm_argv_string(vm, 0); 28 | void *buffer = NULL; 29 | int32_t len = 0; 30 | 31 | #ifdef KMQUAKE2_ENGINE_MOD 32 | len = gi.LoadFile((char *)name, &buffer); 33 | #else 34 | FILE *handle = OpenFile(vm, name, FS_READ); 35 | 36 | if (handle) 37 | { 38 | fseek(handle, 0, SEEK_END); 39 | len = ftell(handle); 40 | fseek(handle, 0, SEEK_SET); 41 | buffer = qcvm_alloc(vm, len); 42 | fread(buffer, sizeof(char), len, handle); 43 | fclose(handle); 44 | } 45 | else 46 | len = -1; 47 | #endif 48 | 49 | if (len >= 1) 50 | qcvm_set_global_str(vm, GLOBAL_PARM1, (const char *)buffer, len, true); 51 | else 52 | qcvm_set_global_str(vm, GLOBAL_PARM1, "", 0, false); 53 | qcvm_return_int32(vm, len); 54 | 55 | #ifdef KMQUAKE2_ENGINE_MOD 56 | if (buffer) 57 | gi.FreeFile(buffer); 58 | #else 59 | if (buffer) 60 | qcvm_mem_free(vm, buffer); 61 | #endif 62 | } 63 | 64 | static void CloseFile(qcvm_t *vm, void *ptr) 65 | { 66 | #ifdef KMQUAKE2_ENGINE_MOD 67 | gi.CloseFile((fileHandle_t)ptr); 68 | #else 69 | fclose((FILE *)ptr); 70 | #endif 71 | } 72 | 73 | static qcvm_handle_descriptor_t fileHandle_descriptor = 74 | { 75 | .free = CloseFile 76 | }; 77 | 78 | static void QC_OpenFile(qcvm_t *vm) 79 | { 80 | const char *name = qcvm_argv_string(vm, 0); 81 | const fsMode_t mode = qcvm_argv_int32(vm, 2); 82 | int32_t len; 83 | int32_t qhandle; 84 | 85 | #ifdef KMQUAKE2_ENGINE_MOD 86 | fileHandle_t handle; 87 | len = gi.OpenFile(name, &handle, mode); 88 | 89 | if (len != -1) 90 | qhandle = qcvm_handle_alloc(vm, (void *)(size_t)handle, &fileHandle_descriptor); 91 | #else 92 | FILE *handle = OpenFile(vm, name, mode); 93 | 94 | if (handle) 95 | { 96 | fseek(handle, 0, SEEK_END); 97 | len = ftell(handle); 98 | fseek(handle, 0, SEEK_SET); 99 | qhandle = qcvm_handle_alloc(vm, handle, &fileHandle_descriptor); 100 | } 101 | else 102 | len = -1; 103 | #endif 104 | 105 | if (len != -1) 106 | qcvm_set_global_typed_value(int32_t, vm, GLOBAL_PARM1, qhandle); 107 | qcvm_return_int32(vm, len); 108 | } 109 | 110 | static void QC_OpenCompressedFile(qcvm_t *vm) 111 | { 112 | #ifdef KMQUAKE2_ENGINE_MOD 113 | const char *zipName = qcvm_argv_string(vm, 0); 114 | const char *name = qcvm_argv_string(vm, 1); 115 | const fsMode_t mode = qcvm_argv_int32(vm, 3); 116 | int32_t len; 117 | fileHandle_t handle; 118 | 119 | len = gi.OpenCompressedFile(zipName, name, &handle, mode); 120 | 121 | if (len != -1) 122 | { 123 | int32_t qhandle = qcvm_handle_alloc(vm, (void *)(size_t)handle, &fileHandle_descriptor); 124 | qcvm_set_global_typed_value(fileHandle_t, vm, GLOBAL_PARM1, qhandle); 125 | } 126 | 127 | qcvm_return_int32(vm, len); 128 | #else 129 | qcvm_return_int32(vm, -1); 130 | #endif 131 | } 132 | 133 | static void QC_FRead(qcvm_t *vm) 134 | { 135 | const qcvm_pointer_t pointer = qcvm_argv_pointer(vm, 0); 136 | const int32_t size = qcvm_argv_int32(vm, 1); 137 | const fileHandle_t *handle = qcvm_argv_handle(fileHandle_t, vm, 2); 138 | int32_t read; 139 | void *address; 140 | 141 | if (!qcvm_resolve_pointer(vm, pointer, false, size, &address)) 142 | qcvm_error(vm, "bad pointer"); 143 | 144 | #ifdef KMQUAKE2_ENGINE_MOD 145 | read = gi.FRead(address, size, (fileHandle_t)handle); 146 | #else 147 | read = fread(address, size, 1, (FILE *)handle); 148 | #endif 149 | 150 | qcvm_return_int32(vm, read); 151 | } 152 | 153 | static void QC_FReadString(qcvm_t *vm) 154 | { 155 | int32_t size = qcvm_argv_int32(vm, 1); 156 | const fileHandle_t *handle = qcvm_argv_handle(fileHandle_t, vm, 2); 157 | int32_t read = 0; 158 | 159 | if (size == -1) 160 | { 161 | 162 | } 163 | else if (size) 164 | { 165 | char *buffer = qcvm_temp_buffer(vm, size); 166 | 167 | #ifdef KMQUAKE2_ENGINE_MOD 168 | read = gi.FRead(buffer, size, (fileHandle_t)handle); 169 | #else 170 | read = fread(buffer, size, 1, (FILE *)handle); 171 | #endif 172 | 173 | // trim trailing zeroes 174 | while (size && !buffer[size - 1]) 175 | size--; 176 | 177 | qcvm_set_global_str(vm, GLOBAL_PARM0, buffer, size, true); 178 | } 179 | 180 | qcvm_return_int32(vm, read); 181 | } 182 | 183 | static void QC_FWrite(qcvm_t *vm) 184 | { 185 | const qcvm_pointer_t pointer = qcvm_argv_pointer(vm, 0); 186 | const int32_t size = qcvm_argv_int32(vm, 1); 187 | const fileHandle_t *handle = qcvm_argv_handle(fileHandle_t, vm, 2); 188 | int32_t written; 189 | void *address; 190 | 191 | if (!qcvm_resolve_pointer(vm, pointer, false, size, &address)) 192 | qcvm_error(vm, "bad pointer"); 193 | 194 | #ifdef KMQUAKE2_ENGINE_MOD 195 | written = gi.FWrite(address, size, (fileHandle_t)handle); 196 | #else 197 | written = fwrite(address, size, 1, (FILE *)handle); 198 | #endif 199 | 200 | qcvm_return_int32(vm, written); 201 | } 202 | 203 | static void QC_CreatePath(qcvm_t *vm) 204 | { 205 | const char *name = qcvm_argv_string(vm, 0); 206 | 207 | name = qcvm_cpp_absolute_path(vm, name); 208 | 209 | #ifdef KMQUAKE2_ENGINE_MOD 210 | gi.CreatePath((char *)name); 211 | #else 212 | mkdir(name); 213 | #endif 214 | } 215 | 216 | static void QC_GameDir(qcvm_t *vm) 217 | { 218 | #ifdef KMQUAKE2_ENGINE_MOD 219 | qcvm_return_string(vm, gi.FS_GameDir()); 220 | #else 221 | const cvar_t *game_var = gi.cvar("game", "", 0); 222 | qcvm_return_string(vm, qcvm_temp_format(vm, "./%s", *game_var->string ? game_var->string : BASEDIR)); 223 | #endif 224 | } 225 | 226 | static void QC_SaveGameDir(qcvm_t *vm) 227 | { 228 | #ifdef KMQUAKE2_ENGINE_MOD 229 | qcvm_return_string(vm, gi.FS_SaveGameDir()); 230 | #else 231 | QC_GameDir(vm); 232 | #endif 233 | } 234 | 235 | typedef struct 236 | { 237 | char **entries; 238 | int32_t num; 239 | } qc_file_list_t; 240 | 241 | static void QC_file_list_get(qcvm_t *vm) 242 | { 243 | const qc_file_list_t *list = qcvm_argv_handle(qc_file_list_t, vm, 0); 244 | const int32_t index = qcvm_argv_int32(vm, 1); 245 | 246 | if (index < 0 || index >= list->num) 247 | qcvm_error(vm, "overrun file list"); 248 | 249 | qcvm_return_string(vm, list->entries[index]); 250 | } 251 | 252 | static void QC_file_list_length(qcvm_t *vm) 253 | { 254 | const qc_file_list_t *list = qcvm_argv_handle(qc_file_list_t, vm, 0); 255 | qcvm_return_int32(vm, list->num); 256 | } 257 | 258 | static void QC_file_list_free(qcvm_t *vm, void *ptr) 259 | { 260 | qc_file_list_t *list = (qc_file_list_t *)ptr; 261 | 262 | #ifdef KMQUAKE2_ENGINE_MOD 263 | gi.FreeFileList(list->entries, list->num); 264 | #else 265 | for (int32_t i = 0; i < list->num; i++) 266 | qcvm_mem_free(vm, list->entries[i]); 267 | #endif 268 | 269 | qcvm_mem_free(vm, list); 270 | } 271 | 272 | static qcvm_handle_descriptor_t file_list_descriptor = 273 | { 274 | .free = QC_file_list_free 275 | }; 276 | 277 | static void QC_GetFileList(qcvm_t *vm) 278 | { 279 | const char *path = qcvm_argv_string(vm, 0); 280 | const char *ext = qcvm_argv_string(vm, 1); 281 | qc_file_list_t *list = (qc_file_list_t *)qcvm_alloc(vm, sizeof(qc_file_list_t)); 282 | 283 | #ifdef KMQUAKE2_ENGINE_MOD 284 | list->entries = gi.GetFileList(path, ext, &list->num); 285 | #else 286 | list->entries = qcvm_get_file_list(vm, path, ext, &list->num); 287 | #endif 288 | 289 | qcvm_return_handle(vm, list, &file_list_descriptor); 290 | } 291 | 292 | void qcvm_init_file_builtins(qcvm_t *vm) 293 | { 294 | qcvm_register_builtin(LoadFile); 295 | qcvm_register_builtin(OpenFile); 296 | qcvm_register_builtin(OpenCompressedFile); 297 | qcvm_register_builtin(FRead); 298 | qcvm_register_builtin(FWrite); 299 | qcvm_register_builtin(FReadString); 300 | //qcvm_register_builtin(FWriteString); 301 | qcvm_register_builtin(CreatePath); 302 | qcvm_register_builtin(GameDir); 303 | qcvm_register_builtin(SaveGameDir); 304 | qcvm_register_builtin(GetFileList); 305 | qcvm_register_builtin(file_list_get); 306 | qcvm_register_builtin(file_list_length); 307 | } -------------------------------------------------------------------------------- /vm_file.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void qcvm_init_file_builtins(qcvm_t *vm); -------------------------------------------------------------------------------- /vm_game.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | #include "vm_string.h" 4 | 5 | #include "game.h" 6 | #include "vm_game.h" 7 | 8 | static void QC_SetNumEdicts(qcvm_t *vm) 9 | { 10 | globals.num_edicts = qcvm_argv_int32(vm, 0); 11 | } 12 | 13 | static void QC_ClearEntity(qcvm_t *vm) 14 | { 15 | edict_t *entity = qcvm_argv_entity(vm, 0); 16 | const int32_t number = entity->s.number; 17 | 18 | memset(entity, 0, globals.edict_size); 19 | 20 | entity->s.number = number; 21 | 22 | qcvm_string_list_check_ref_unset(vm, entity, globals.edict_size / sizeof(qcvm_global_t), true); 23 | qcvm_field_wrap_list_check_set(vm, entity, globals.edict_size / sizeof(qcvm_global_t)); 24 | } 25 | 26 | const char *ParseSlashes(const char *value) 27 | { 28 | // no slashes to parse 29 | if (!strchr(value, '\\')) 30 | return value; 31 | 32 | // in Q2 this was 128, but just in case... 33 | // TODO: honestly I'd prefer to make this dynamic in future 34 | static char slashless_string[MAX_INFO_STRING]; 35 | 36 | if (strlen(value) >= sizeof(slashless_string)) 37 | gi.error("string overflow :((((((("); 38 | 39 | const char *src = value; 40 | char *dst = slashless_string; 41 | 42 | while (*src) 43 | {\ 44 | const char c = *src; 45 | 46 | if (c == '\\') 47 | { 48 | const char next = *(src + 1); 49 | 50 | if (!next) 51 | gi.error("bad string"); 52 | else if (next == '\\' || next == 'n') 53 | { 54 | *dst = (next == 'n') ? '\n' : '\\'; 55 | src += 2; 56 | dst++; 57 | 58 | continue; 59 | } 60 | } 61 | 62 | *dst = c; 63 | src++; 64 | dst++; 65 | } 66 | 67 | *dst = 0; 68 | return slashless_string; 69 | } 70 | 71 | static inline void QC_parse_value_into_ptr(qcvm_t *vm, const qcvm_deftype_t type, const char *value, void *ptr) 72 | { 73 | size_t data_span = 1; 74 | 75 | switch (type) 76 | { 77 | case TYPE_STRING: { 78 | value = ParseSlashes(value); 79 | *(qcvm_string_t *)ptr = qcvm_store_or_find_string(vm, value, strlen(value), true); 80 | break; } 81 | case TYPE_FLOAT: 82 | *(vec_t *)ptr = strtof(value, NULL); 83 | break; 84 | case TYPE_VECTOR: 85 | data_span = 3; 86 | sscanf(value, "%f %f %f", (vec_t *)ptr, (vec_t *)ptr + 1, (vec_t *)ptr + 2); 87 | break; 88 | case TYPE_INTEGER: 89 | *(int32_t *)ptr = strtol(value, NULL, 10); 90 | break; 91 | default: 92 | qcvm_error(vm, "Couldn't parse field, bad type %i", type); 93 | } 94 | 95 | qcvm_string_list_check_ref_unset(vm, ptr, data_span, false); 96 | qcvm_field_wrap_list_check_set(vm, ptr, data_span); 97 | 98 | if (type == TYPE_STRING && qcvm_string_list_is_ref_counted(vm, *(qcvm_string_t *)ptr)) 99 | qcvm_string_list_mark_ref_copy(vm, *(qcvm_string_t *)ptr, ptr); 100 | } 101 | 102 | static void QC_entity_key_parse(qcvm_t *vm) 103 | { 104 | edict_t *ent = qcvm_argv_entity(vm, 0); 105 | const char *key = qcvm_argv_string(vm, 1); 106 | const char *value = qcvm_argv_string(vm, 2); 107 | 108 | qcvm_definition_hash_t *hashed = vm->definition_hashes[Q_hash_string(key, vm->definitions_size)]; 109 | 110 | for (; hashed; hashed = hashed->hash_next) 111 | if ((hashed->def->id & ~TYPE_GLOBAL) == TYPE_FIELD && !strcmp(qcvm_get_string(vm, hashed->def->name_index), key)) 112 | break; 113 | 114 | if (!hashed) 115 | qcvm_error(vm, "Bad field %s", key); 116 | 117 | const qcvm_global_t field = vm->global_data[hashed->def->global_index]; 118 | 119 | qcvm_definition_t *f = vm->field_map_by_id[field]; 120 | 121 | if (!f || strcmp(key, qcvm_get_string(vm, f->name_index)) || f->global_index != field) 122 | qcvm_error(vm, "Couldn't match field %u", field); 123 | 124 | void *ptr; 125 | 126 | if (!qcvm_resolve_pointer(vm, qcvm_get_entity_field_pointer(vm, ent, field), false, qcvm_type_size(f->id), &ptr)) 127 | qcvm_error(vm, "bad pointer"); 128 | 129 | QC_parse_value_into_ptr(vm, f->id, value, ptr); 130 | } 131 | 132 | static void QC_struct_key_parse(qcvm_t *vm) 133 | { 134 | const char *struct_name = qcvm_argv_string(vm, 0); 135 | const char *key_name = qcvm_argv_string(vm, 1); 136 | const char *value = qcvm_argv_string(vm, 2); 137 | 138 | const char *full_name = qcvm_temp_format(vm, "%s.%s", struct_name, key_name); 139 | qcvm_definition_hash_t *hashed = vm->definition_hashes[Q_hash_string(full_name, vm->definitions_size)]; 140 | 141 | for (; hashed; hashed = hashed->hash_next) 142 | if (!strcmp(qcvm_get_string(vm, hashed->def->name_index), full_name)) 143 | break; 144 | 145 | if (!hashed) 146 | { 147 | qcvm_return_int32(vm, 0); 148 | return; 149 | } 150 | 151 | qcvm_definition_t *g = hashed->def; 152 | void *global = qcvm_get_global(vm, g->global_index); 153 | QC_parse_value_into_ptr(vm, g->id & ~TYPE_GLOBAL, value, global); 154 | qcvm_return_int32(vm, 1); 155 | } 156 | 157 | void qcvm_init_game_builtins(qcvm_t *vm) 158 | { 159 | qcvm_register_builtin(SetNumEdicts); 160 | qcvm_register_builtin(ClearEntity); 161 | 162 | qcvm_register_builtin(entity_key_parse); 163 | qcvm_register_builtin(struct_key_parse); 164 | } -------------------------------------------------------------------------------- /vm_game.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void qcvm_init_game_builtins(qcvm_t *vm); 4 | -------------------------------------------------------------------------------- /vm_gi.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | #include "vm_gi.h" 4 | 5 | #include "game.h" 6 | #include "vm_game.h" 7 | #include "vm_hash.h" 8 | 9 | static void QC_sound(qcvm_t *vm) 10 | { 11 | edict_t *entity = qcvm_argv_entity(vm, 0); 12 | const sound_channel_t channel = qcvm_argv_int32(vm, 1); 13 | const int32_t soundindex = qcvm_argv_int32(vm, 2); 14 | const vec_t volume = qcvm_argv_float(vm, 3); 15 | const vec_t attenuation = qcvm_argv_float(vm, 4); 16 | const vec_t timeofs = qcvm_argv_float(vm, 5); 17 | 18 | gi.sound(entity, channel, soundindex, volume, attenuation, timeofs); 19 | } 20 | 21 | static void QC_positioned_sound(qcvm_t *vm) 22 | { 23 | const vec3_t position = qcvm_argv_vector(vm, 0); 24 | edict_t *entity = qcvm_argv_entity(vm, 1); 25 | const sound_channel_t channel = qcvm_argv_int32(vm, 2); 26 | const int32_t soundindex = qcvm_argv_int32(vm, 3); 27 | const vec_t volume = qcvm_argv_float(vm, 4); 28 | const vec_t attenuation = qcvm_argv_float(vm, 5); 29 | const vec_t timeofs = qcvm_argv_float(vm, 6); 30 | 31 | gi.positioned_sound(&position, entity, channel, soundindex, volume, attenuation, timeofs); 32 | } 33 | 34 | static void QC_cvar_get_name(qcvm_t *vm) 35 | { 36 | const cvar_t *cvar = qcvm_argv_handle(cvar_t, vm, 0); 37 | qcvm_return_string(vm, cvar->name); 38 | } 39 | 40 | static void QC_cvar_get_string(qcvm_t *vm) 41 | { 42 | const cvar_t *cvar = qcvm_argv_handle(cvar_t, vm, 0); 43 | qcvm_return_string(vm, cvar->string); 44 | } 45 | 46 | static void QC_cvar_get_latched_string(qcvm_t *vm) 47 | { 48 | const cvar_t *cvar = qcvm_argv_handle(cvar_t, vm, 0); 49 | qcvm_return_string(vm, cvar->latched_string); 50 | } 51 | 52 | static void QC_cvar_get_modified(qcvm_t *vm) 53 | { 54 | const cvar_t *cvar = qcvm_argv_handle(cvar_t, vm, 0); 55 | qcvm_return_int32(vm, cvar->modified); 56 | } 57 | static void QC_cvar_get_flags(qcvm_t *vm) 58 | { 59 | const cvar_t *cvar = qcvm_argv_handle(cvar_t, vm, 0); 60 | qcvm_return_int32(vm, cvar->flags); 61 | } 62 | 63 | static void QC_cvar_set_modified(qcvm_t *vm) 64 | { 65 | cvar_t *cvar = qcvm_argv_handle(cvar_t, vm, 0); 66 | const qboolean value = (qboolean)(qcvm_argv_int32(vm, 1)); 67 | 68 | cvar->modified = value; 69 | } 70 | 71 | static void QC_cvar_get_floatVal(qcvm_t *vm) 72 | { 73 | const cvar_t *cvar = qcvm_argv_handle(cvar_t, vm, 0); 74 | qcvm_return_float(vm, cvar->value); 75 | } 76 | 77 | static void QC_cvar_get_intVal(qcvm_t *vm) 78 | { 79 | const cvar_t *cvar = qcvm_argv_handle(cvar_t, vm, 0); 80 | qcvm_return_int32(vm, (int32_t)cvar->value); 81 | } 82 | 83 | static void QC_cvar(qcvm_t *vm) 84 | { 85 | const char *name = qcvm_argv_string(vm, 0); 86 | const char *value = qcvm_argv_string(vm, 1); 87 | const cvar_flags_t flags = qcvm_argv_int32(vm, 2); 88 | 89 | qcvm_return_handle(vm, gi.cvar(name, value, flags), NULL); 90 | } 91 | 92 | static void QC_cvar_set(qcvm_t *vm) 93 | { 94 | const char *name = qcvm_argv_string(vm, 0); 95 | const char *value = qcvm_argv_string(vm, 1); 96 | gi.cvar_set(name, value); 97 | } 98 | 99 | static void QC_cvar_forceset(qcvm_t *vm) 100 | { 101 | const char *name = qcvm_argv_string(vm, 0); 102 | const char *value = qcvm_argv_string(vm, 1); 103 | gi.cvar_forceset(name, value); 104 | } 105 | 106 | static void QC_configstring(qcvm_t *vm) 107 | { 108 | const config_string_t id = qcvm_argv_int32(vm, 0); 109 | const char *str = qcvm_argv_string(vm, 1); 110 | gi.configstring(id, str); 111 | } 112 | 113 | static qcvm_noreturn void QC_error(qcvm_t *vm) 114 | { 115 | const qcvm_string_t fmtid = qcvm_argv_string_id(vm, 0); 116 | qcvm_error(vm, qcvm_parse_format(fmtid, vm, 1)); 117 | } 118 | 119 | static void QC_modelindex(qcvm_t *vm) 120 | { 121 | const char *str = qcvm_argv_string(vm, 0); 122 | qcvm_return_int32(vm, gi.modelindex(str)); 123 | } 124 | 125 | static void QC_soundindex(qcvm_t *vm) 126 | { 127 | const char *str = qcvm_argv_string(vm, 0); 128 | qcvm_return_int32(vm, gi.soundindex(str)); 129 | } 130 | 131 | static void QC_imageindex(qcvm_t *vm) 132 | { 133 | const char *str = qcvm_argv_string(vm, 0); 134 | qcvm_return_int32(vm, gi.imageindex(str)); 135 | } 136 | 137 | static void QC_setmodel(qcvm_t *vm) 138 | { 139 | edict_t *ent = qcvm_argv_entity(vm, 0); 140 | const char *str = qcvm_argv_string(vm, 1); 141 | 142 | gi.setmodel(ent, str); 143 | } 144 | 145 | typedef struct 146 | { 147 | qcvm_string_t name; 148 | surface_flags_t flags; 149 | int value; 150 | } QC_csurface_t; 151 | 152 | typedef struct 153 | { 154 | int allsolid; 155 | int startsolid; 156 | float fraction; 157 | vec3_t endpos; 158 | vec3_t normal; 159 | QC_csurface_t surface; 160 | int contents; 161 | qcvm_ent_t ent; 162 | } QC_trace_t; 163 | 164 | static void QC_trace(qcvm_t *vm) 165 | { 166 | QC_trace_t *trace = qcvm_get_global_ptr_typed(QC_trace_t, vm, GLOBAL_PARM0); 167 | const vec3_t start = qcvm_argv_vector(vm, 1); 168 | const vec3_t mins = qcvm_argv_vector(vm, 2); 169 | const vec3_t maxs = qcvm_argv_vector(vm, 3); 170 | const vec3_t end = qcvm_argv_vector(vm, 4); 171 | edict_t *ent = qcvm_argv_entity(vm, 5); 172 | const content_flags_t contents = qcvm_argv_int32(vm, 6); 173 | 174 | trace_t trace_result = gi.trace(&start, &mins, &maxs, &end, ent, contents); 175 | 176 | if (trace_result.fraction == 1.0f && trace_result.surface == NULL) 177 | { 178 | gi.dprintf("Q2PRO runaway trace trapped, re-tracing...\n"); 179 | trace_result = gi.trace(&start, &mins, &maxs, &end, ent, contents); 180 | } 181 | 182 | trace->allsolid = trace_result.allsolid; 183 | trace->startsolid = trace_result.startsolid; 184 | trace->fraction = trace_result.fraction; 185 | trace->endpos = trace_result.endpos; 186 | trace->normal = trace_result.plane.normal; 187 | if (trace_result.surface) 188 | { 189 | trace->surface.flags = trace_result.surface->flags; 190 | trace->surface.value = trace_result.surface->value; 191 | trace->surface.name = qcvm_store_or_find_string(vm, trace_result.surface->name, strlen(trace_result.surface->name), true); 192 | } 193 | else 194 | trace->surface = (QC_csurface_t) { STRING_EMPTY, 0, 0 }; 195 | 196 | if (qcvm_string_list_is_ref_counted(vm, trace->surface.name)) 197 | qcvm_string_list_mark_ref_copy(vm, trace->surface.name, &trace->surface.name); 198 | 199 | trace->contents = trace_result.contents; 200 | trace->ent = qcvm_entity_to_ent(vm, trace_result.ent); 201 | qcvm_string_list_check_ref_unset(vm, trace, sizeof(*trace) / sizeof(qcvm_global_t), false); 202 | } 203 | 204 | static void QC_pointcontents(qcvm_t *vm) 205 | { 206 | const vec3_t pos = qcvm_argv_vector(vm, 0); 207 | qcvm_return_int32(vm, gi.pointcontents(&pos)); 208 | } 209 | 210 | static void QC_inPVS(qcvm_t *vm) 211 | { 212 | const vec3_t a = qcvm_argv_vector(vm, 0); 213 | const vec3_t b = qcvm_argv_vector(vm, 1); 214 | qcvm_return_int32(vm, gi.inPVS(&a, &b)); 215 | } 216 | 217 | static void QC_inPHS(qcvm_t *vm) 218 | { 219 | const vec3_t a = qcvm_argv_vector(vm, 0); 220 | const vec3_t b = qcvm_argv_vector(vm, 1); 221 | qcvm_return_int32(vm, gi.inPHS(&a, &b)); 222 | } 223 | 224 | static void QC_SetAreaPortalState(qcvm_t *vm) 225 | { 226 | const int32_t num = qcvm_argv_int32(vm, 0); 227 | const qboolean state = (qboolean)(qcvm_argv_int32(vm, 1)); 228 | 229 | gi.SetAreaPortalState(num, state); 230 | } 231 | 232 | static void QC_AreasConnected(qcvm_t *vm) 233 | { 234 | const int32_t a = qcvm_argv_int32(vm, 0); 235 | const int32_t b = qcvm_argv_int32(vm, 1); 236 | qcvm_return_int32(vm, gi.AreasConnected(a, b)); 237 | } 238 | 239 | static void QC_linkentity(qcvm_t *vm) 240 | { 241 | edict_t *ent = qcvm_argv_entity(vm, 0); 242 | gi.linkentity(ent); 243 | 244 | ((int32_t *)ent)[game.fields.is_linked] = true; 245 | } 246 | 247 | static void QC_unlinkentity(qcvm_t *vm) 248 | { 249 | edict_t *ent = qcvm_argv_entity(vm, 0); 250 | gi.unlinkentity(ent); 251 | 252 | ((int32_t *)ent)[game.fields.is_linked] = false; 253 | } 254 | 255 | static void QC_BoxEdicts(qcvm_t *vm) 256 | { 257 | qcvm_hashset_t *set = qcvm_argv_handle(qcvm_hashset_t, vm, 0); 258 | const vec3_t mins = qcvm_argv_vector(vm, 1); 259 | const vec3_t maxs = qcvm_argv_vector(vm, 2); 260 | const int32_t maxcount = qcvm_argv_int32(vm, 3); 261 | const box_edicts_area_t areatype = qcvm_argv_int32(vm, 4); 262 | static edict_t *raw_entities[MAX_EDICTS]; 263 | 264 | const int32_t count = gi.BoxEdicts(&mins, &maxs, raw_entities, maxcount, areatype); 265 | 266 | hashset_clear(vm, set); 267 | 268 | for (int32_t i = 0; i < count; i++) 269 | hashset_add(vm, set, (const qcvm_variant_t) { .type = TYPE_ENTITY, .value.ent = qcvm_entity_to_ent(vm, raw_entities[i]) }); 270 | 271 | qcvm_return_int32(vm, (int32_t)set->size); 272 | } 273 | 274 | typedef struct 275 | { 276 | pmtype_t pm_type; 277 | 278 | vec3_t origin; // 12.3 279 | vec3_t velocity; // 12.3 280 | int pm_flags; // ducked, jump_held, etc 281 | int pm_time; // each unit = 8 ms 282 | int gravity; 283 | vec3_t delta_angles; // add to command angles to get view direction 284 | // changed by spawns, rotating objects, and teleporters 285 | } QC_pmove_state_t; 286 | 287 | typedef qcvm_handle_id_t QC_entity_set_t; 288 | 289 | typedef struct 290 | { 291 | // inout 292 | QC_pmove_state_t s; 293 | 294 | // in 295 | QC_usercmd_t cmd; 296 | bool snapinitial; 297 | qcvm_ent_t passent; 298 | content_flags_t mask; 299 | 300 | // out 301 | QC_entity_set_t touchents; 302 | 303 | vec3_t viewangles; 304 | float viewheight; 305 | 306 | vec3_t mins, maxs; 307 | 308 | qcvm_ent_t groundentity; 309 | int watertype; 310 | int waterlevel; 311 | 312 | // in (callbacks) 313 | qcvm_func_t pointcontents; 314 | } QC_pmove_t; 315 | 316 | static qcvm_hashset_t touchents_memory; 317 | static int32_t touchents_handle = 0; 318 | 319 | static qcvm_func_t QC_pm_pointcontents_func; 320 | static qcvm_t *pmove_vm; 321 | 322 | static content_flags_t QC_pm_pointcontents(const vec3_t *position) 323 | { 324 | qcvm_function_t *func = qcvm_get_function(pmove_vm, QC_pm_pointcontents_func); 325 | qcvm_set_global_typed_ptr(vec3_t, pmove_vm, GLOBAL_PARM0, position); 326 | qcvm_execute(pmove_vm, func); 327 | return *qcvm_get_global_typed(content_flags_t, pmove_vm, GLOBAL_RETURN); 328 | } 329 | 330 | static edict_t *QC_pm_passent; 331 | static content_flags_t QC_pm_mask; 332 | 333 | static trace_t QC_pm_trace(const vec3_t *start, const vec3_t *mins, const vec3_t *maxs, const vec3_t *end) 334 | { 335 | return gi.trace(start, mins, maxs, end, QC_pm_passent, QC_pm_mask); 336 | } 337 | 338 | static void QC_Pmove(qcvm_t *vm) 339 | { 340 | pmove_vm = vm; 341 | 342 | QC_pmove_t *qc_pm = qcvm_get_global_ptr_typed(QC_pmove_t, vm, GLOBAL_PARM0); 343 | 344 | pmove_t pm; 345 | 346 | // set in parameters 347 | pm.s.pm_type = qc_pm->s.pm_type; 348 | 349 | pm.s.origin[0] = qc_pm->s.origin.x * coord2short; 350 | pm.s.origin[1] = qc_pm->s.origin.y * coord2short; 351 | pm.s.origin[2] = qc_pm->s.origin.z * coord2short; 352 | pm.s.velocity[0] = qc_pm->s.velocity.x * coord2short; 353 | pm.s.velocity[1] = qc_pm->s.velocity.y * coord2short; 354 | pm.s.velocity[2] = qc_pm->s.velocity.z * coord2short; 355 | pm.s.delta_angles[0] = qc_pm->s.delta_angles.x * angle2short; 356 | pm.s.delta_angles[1] = qc_pm->s.delta_angles.y * angle2short; 357 | pm.s.delta_angles[2] = qc_pm->s.delta_angles.z * angle2short; 358 | pm.cmd.angles[0] = qc_pm->cmd.angles.x * angle2short; 359 | pm.cmd.angles[1] = qc_pm->cmd.angles.y * angle2short; 360 | pm.cmd.angles[2] = qc_pm->cmd.angles.z * angle2short; 361 | 362 | pm.s.pm_flags = (pmflags_t)qc_pm->s.pm_flags; 363 | pm.s.pm_time = qc_pm->s.pm_time; 364 | pm.s.gravity = qc_pm->s.gravity; 365 | pm.cmd.buttons = (button_bits_t)qc_pm->cmd.buttons; 366 | pm.cmd.forwardmove = qc_pm->cmd.forwardmove; 367 | pm.cmd.impulse = qc_pm->cmd.impulse; 368 | pm.cmd.lightlevel = qc_pm->cmd.lightlevel; 369 | pm.cmd.msec = qc_pm->cmd.msec; 370 | pm.cmd.sidemove = qc_pm->cmd.sidemove; 371 | pm.cmd.upmove = qc_pm->cmd.upmove; 372 | 373 | if (qc_pm->pointcontents) 374 | { 375 | QC_pm_pointcontents_func = qc_pm->pointcontents; 376 | pm.pointcontents = QC_pm_pointcontents; 377 | } 378 | else 379 | pm.pointcontents = gi.pointcontents; 380 | 381 | QC_pm_passent = qcvm_ent_to_entity(vm, qc_pm->passent, true); 382 | QC_pm_mask = qc_pm->mask; 383 | pm.trace = QC_pm_trace; 384 | 385 | gi.Pmove(&pm); 386 | 387 | // copy out parameters 388 | qc_pm->s.pm_type = pm.s.pm_type; 389 | 390 | qc_pm->s.origin.x = pm.s.origin[0] * short2coord; 391 | qc_pm->s.origin.y = pm.s.origin[1] * short2coord; 392 | qc_pm->s.origin.z = pm.s.origin[2] * short2coord; 393 | qc_pm->s.velocity.x = pm.s.velocity[0] * short2coord; 394 | qc_pm->s.velocity.y = pm.s.velocity[1] * short2coord; 395 | qc_pm->s.velocity.z = pm.s.velocity[2] * short2coord; 396 | qc_pm->s.delta_angles.x = pm.s.delta_angles[0] * short2angle; 397 | qc_pm->s.delta_angles.y = pm.s.delta_angles[1] * short2angle; 398 | qc_pm->s.delta_angles.z = pm.s.delta_angles[2] * short2angle; 399 | 400 | qc_pm->s.pm_flags = pm.s.pm_flags; 401 | qc_pm->s.pm_time = pm.s.pm_time; 402 | qc_pm->s.gravity = pm.s.gravity; 403 | 404 | if (!touchents_handle) 405 | touchents_handle = qcvm_handle_alloc(vm, &touchents_memory, &hashset_descriptor); 406 | 407 | qc_pm->touchents = touchents_handle; 408 | hashset_clear(vm, &touchents_memory); 409 | 410 | for (int32_t i = 0; i < pm.numtouch; i++) 411 | hashset_add(vm, &touchents_memory, (const qcvm_variant_t) { .type = TYPE_ENTITY, .value.ent = qcvm_entity_to_ent(vm, pm.touchents[i]) }); 412 | 413 | qc_pm->viewangles = pm.viewangles; 414 | qc_pm->viewheight = pm.viewheight; 415 | 416 | qc_pm->mins = pm.mins; 417 | qc_pm->maxs = pm.maxs; 418 | 419 | qc_pm->groundentity = qcvm_entity_to_ent(vm, pm.groundentity); 420 | qc_pm->watertype = pm.watertype; 421 | qc_pm->waterlevel = pm.waterlevel; 422 | qcvm_string_list_check_ref_unset(vm, qc_pm, sizeof(*qc_pm) / sizeof(qcvm_global_t), false); 423 | } 424 | 425 | static void QC_multicast(qcvm_t *vm) 426 | { 427 | const vec3_t pos = qcvm_argv_vector(vm, 0); 428 | const multicast_t type = qcvm_argv_int32(vm, 1); 429 | 430 | gi.multicast(&pos, type); 431 | } 432 | 433 | static void QC_unicast(qcvm_t *vm) 434 | { 435 | edict_t *ent = qcvm_argv_entity(vm, 0); 436 | const qboolean reliable = (qboolean)(qcvm_argv_int32(vm, 1)); 437 | 438 | gi.unicast(ent, reliable); 439 | } 440 | 441 | static void QC_WriteChar(qcvm_t *vm) 442 | { 443 | const int32_t val = qcvm_argv_int32(vm, 0); 444 | 445 | gi.WriteChar(val); 446 | } 447 | 448 | static void QC_WriteByte(qcvm_t *vm) 449 | { 450 | const int32_t val = qcvm_argv_int32(vm, 0); 451 | 452 | gi.WriteByte(val); 453 | } 454 | 455 | static void QC_WriteShort(qcvm_t *vm) 456 | { 457 | const int32_t val = qcvm_argv_int32(vm, 0); 458 | 459 | gi.WriteShort(val); 460 | } 461 | 462 | static void QC_WriteLong(qcvm_t *vm) 463 | { 464 | const int32_t val = qcvm_argv_int32(vm, 0); 465 | 466 | gi.WriteLong(val); 467 | } 468 | 469 | static void QC_WriteFloat(qcvm_t *vm) 470 | { 471 | const vec_t val = qcvm_argv_float(vm, 0); 472 | 473 | gi.WriteFloat(val); 474 | } 475 | 476 | static void QC_WriteString(qcvm_t *vm) 477 | { 478 | const char *val = qcvm_argv_string(vm, 0); 479 | 480 | gi.WriteString(val); 481 | } 482 | 483 | static void QC_WritePosition(qcvm_t *vm) 484 | { 485 | const vec3_t val = qcvm_argv_vector(vm, 0); 486 | 487 | gi.WritePosition(&val); 488 | } 489 | 490 | static void QC_WriteDir(qcvm_t *vm) 491 | { 492 | const vec3_t val = qcvm_argv_vector(vm, 0); 493 | 494 | gi.WriteDir(&val); 495 | } 496 | 497 | static void QC_WriteAngle(qcvm_t *vm) 498 | { 499 | const vec_t val = qcvm_argv_float(vm, 0); 500 | 501 | gi.WriteAngle(val); 502 | } 503 | 504 | static void QC_argv(qcvm_t *vm) 505 | { 506 | const int32_t n = qcvm_argv_int32(vm, 0); 507 | qcvm_return_string(vm, gi.argv(n)); 508 | } 509 | 510 | static void QC_argc(qcvm_t *vm) 511 | { 512 | qcvm_return_int32(vm, gi.argc()); 513 | } 514 | 515 | static void QC_args(qcvm_t *vm) 516 | { 517 | qcvm_return_string(vm, gi.args()); 518 | } 519 | 520 | static void QC_AddCommandString(qcvm_t *vm) 521 | { 522 | gi.AddCommandString(qcvm_argv_string(vm, 0)); 523 | } 524 | 525 | static void QC_bprintf(qcvm_t *vm) 526 | { 527 | const print_level_t level = qcvm_argv_int32(vm, 0); 528 | const qcvm_string_t fmtid = qcvm_argv_string_id(vm, 1); 529 | gi.bprintf(level, "%s", qcvm_parse_format(fmtid, vm, 2)); 530 | } 531 | 532 | static void QC_dprintf(qcvm_t *vm) 533 | { 534 | const qcvm_string_t fmtid = qcvm_argv_string_id(vm, 0); 535 | gi.dprintf("%s", qcvm_parse_format(fmtid, vm, 1)); 536 | } 537 | 538 | static void QC_cprintf(qcvm_t *vm) 539 | { 540 | edict_t *ent = qcvm_argv_entity(vm, 0); 541 | const print_level_t level = qcvm_argv_int32(vm, 1); 542 | const qcvm_string_t fmtid = qcvm_argv_string_id(vm, 2); 543 | gi.cprintf(ent, level, "%s", qcvm_parse_format(fmtid, vm, 3)); 544 | } 545 | 546 | static void QC_centerprintf(qcvm_t *vm) 547 | { 548 | edict_t *ent = qcvm_argv_entity(vm, 0); 549 | const qcvm_string_t fmtid = qcvm_argv_string_id(vm, 1); 550 | gi.centerprintf(ent, "%s", qcvm_parse_format(fmtid, vm, 2)); 551 | } 552 | 553 | static void QC_DebugGraph(qcvm_t *vm) 554 | { 555 | const vec_t a = qcvm_argv_float(vm, 0); 556 | const int32_t b = qcvm_argv_int32(vm, 1); 557 | gi.DebugGraph(a, b); 558 | } 559 | 560 | void qcvm_init_gi_builtins(qcvm_t *vm) 561 | { 562 | qcvm_register_builtin(bprintf); 563 | qcvm_register_builtin(dprintf); 564 | qcvm_register_builtin(cprintf); 565 | qcvm_register_builtin(centerprintf); 566 | qcvm_register_builtin(sound); 567 | qcvm_register_builtin(positioned_sound); 568 | qcvm_register_builtin(cvar); 569 | qcvm_register_builtin(cvar_set); 570 | qcvm_register_builtin(cvar_forceset); 571 | 572 | qcvm_register_builtin(configstring); 573 | 574 | qcvm_register_builtin(error); 575 | 576 | qcvm_register_builtin(modelindex); 577 | qcvm_register_builtin(soundindex); 578 | qcvm_register_builtin(imageindex); 579 | 580 | qcvm_register_builtin(setmodel); 581 | 582 | qcvm_register_builtin(trace); 583 | qcvm_register_builtin(pointcontents); 584 | qcvm_register_builtin(inPVS); 585 | qcvm_register_builtin(inPHS); 586 | qcvm_register_builtin(SetAreaPortalState); 587 | qcvm_register_builtin(AreasConnected); 588 | 589 | qcvm_register_builtin(linkentity); 590 | qcvm_register_builtin(unlinkentity); 591 | qcvm_register_builtin(BoxEdicts); 592 | qcvm_register_builtin(Pmove); 593 | 594 | qcvm_register_builtin(multicast); 595 | qcvm_register_builtin(unicast); 596 | qcvm_register_builtin(WriteChar); 597 | qcvm_register_builtin(WriteByte); 598 | qcvm_register_builtin(WriteShort); 599 | qcvm_register_builtin(WriteLong); 600 | qcvm_register_builtin(WriteFloat); 601 | qcvm_register_builtin(WriteString); 602 | qcvm_register_builtin(WritePosition); 603 | qcvm_register_builtin(WriteDir); 604 | qcvm_register_builtin(WriteAngle); 605 | 606 | qcvm_register_builtin(argv); 607 | qcvm_register_builtin(argc); 608 | qcvm_register_builtin(args); 609 | 610 | qcvm_register_builtin(AddCommandString); 611 | 612 | qcvm_register_builtin(DebugGraph); 613 | 614 | qcvm_register_builtin(cvar_get_name); 615 | qcvm_register_builtin(cvar_get_string); 616 | qcvm_register_builtin(cvar_get_latched_string); 617 | qcvm_register_builtin(cvar_get_modified); 618 | qcvm_register_builtin(cvar_set_modified); 619 | qcvm_register_builtin(cvar_get_flags); 620 | qcvm_register_builtin(cvar_get_floatVal); 621 | qcvm_register_builtin(cvar_get_intVal); 622 | } -------------------------------------------------------------------------------- /vm_gi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct 4 | { 5 | int msec; 6 | int buttons; 7 | vec3_t angles; 8 | int forwardmove, sidemove, upmove; 9 | int impulse; // remove? 10 | int lightlevel; // light level the player is standing on 11 | } QC_usercmd_t; 12 | 13 | void qcvm_init_gi_builtins(qcvm_t *vm); -------------------------------------------------------------------------------- /vm_hash.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | #include "vm_hash.h" 4 | #include "vm_math.h" 5 | 6 | qcvm_hashset_t *hashset_alloc(const qcvm_t *vm, const size_t reserve) 7 | { 8 | qcvm_hashset_t *set = (qcvm_hashset_t *)qcvm_alloc(vm, sizeof(qcvm_hashset_t)); 9 | 10 | set->allocated = reserve; 11 | 12 | if (set->allocated) 13 | { 14 | set->values = (qcvm_hash_value_t *)qcvm_alloc(vm, sizeof(qcvm_hash_value_t) * set->allocated); 15 | set->hashed = (qcvm_hash_value_t **)qcvm_alloc(vm, sizeof(qcvm_hash_value_t *) * set->allocated); 16 | set->indexed = (qcvm_hash_value_t **)qcvm_alloc(vm, sizeof(qcvm_hash_value_t *) * set->allocated); 17 | 18 | for (qcvm_hash_value_t *v = set->values; v < set->values + set->allocated; v++) 19 | { 20 | v->hash_next = set->free; 21 | set->free = v; 22 | } 23 | } 24 | 25 | return set; 26 | } 27 | 28 | void hashset_free(const qcvm_t *vm, qcvm_hashset_t *set) 29 | { 30 | if (set->allocated) 31 | { 32 | qcvm_mem_free(vm, set->values); 33 | qcvm_mem_free(vm, set->hashed); 34 | } 35 | 36 | qcvm_mem_free(vm, set); 37 | } 38 | 39 | bool hashset_add(qcvm_t *vm, qcvm_hashset_t *set, const qcvm_variant_t variant) 40 | { 41 | uint32_t hash = 0; 42 | 43 | if (set->allocated) 44 | { 45 | hash = Q_hash_variant(variant, set->allocated); 46 | 47 | for (qcvm_hash_value_t *v = set->hashed[hash]; v; v = v->hash_next) 48 | if (qcvm_variant_equals(v->value, variant)) 49 | return false; 50 | } 51 | 52 | if (set->size == set->allocated) 53 | { 54 | const size_t old_size = set->allocated; 55 | 56 | if (!set->allocated) 57 | set->allocated = HASH_RESERVE; 58 | else 59 | set->allocated *= 2; 60 | 61 | qcvm_hash_value_t *old_values = set->values; 62 | 63 | if (old_values) 64 | { 65 | qcvm_mem_free(vm, set->hashed); 66 | qcvm_mem_free(vm, set->indexed); 67 | } 68 | 69 | set->values = (qcvm_hash_value_t *)qcvm_alloc(vm, sizeof(qcvm_hash_value_t) * set->allocated); 70 | set->hashed = (qcvm_hash_value_t **)qcvm_alloc(vm, sizeof(qcvm_hash_value_t *) * set->allocated); 71 | set->indexed = (qcvm_hash_value_t **)qcvm_alloc(vm, sizeof(qcvm_hash_value_t *) * set->allocated); 72 | 73 | if (old_values) 74 | { 75 | memcpy(set->values, old_values, sizeof(qcvm_hash_value_t) * old_size); 76 | qcvm_mem_free(vm, old_values); 77 | } 78 | 79 | set->free = NULL; 80 | 81 | size_t i = 0; 82 | 83 | // re-hash since hashs changed 84 | for (qcvm_hash_value_t *v = set->values; v < set->values + set->allocated; v++) 85 | { 86 | // we're free, so ignore us 87 | if (v->value.type == TYPE_VOID) 88 | { 89 | v->hash_next = set->free; 90 | set->free = v; 91 | continue; 92 | } 93 | 94 | v->hash_value = Q_hash_variant(v->value, set->allocated); 95 | v->hash_next = set->hashed[v->hash_value]; 96 | set->hashed[v->hash_value] = v; 97 | set->indexed[i++] = v; 98 | } 99 | 100 | hash = Q_hash_variant(variant, set->allocated); 101 | } 102 | 103 | qcvm_hash_value_t *v = set->free; 104 | set->free = v->hash_next; 105 | 106 | assert(v->value.type == TYPE_VOID); 107 | 108 | v->value = variant; 109 | v->hash_value = hash; 110 | v->hash_next = set->hashed[v->hash_value]; 111 | set->hashed[v->hash_value] = v; 112 | 113 | if (variant.type == TYPE_STRING) 114 | qcvm_string_list_acquire(vm, variant.value.str); 115 | 116 | set->indexed[set->size] = v; 117 | v->index = set->size; 118 | set->size++; 119 | return true; 120 | } 121 | 122 | bool hashset_remove(qcvm_t *vm, qcvm_hashset_t *set, const qcvm_variant_t variant) 123 | { 124 | if (!set->allocated) 125 | return false; 126 | 127 | const uint32_t hash = Q_hash_variant(variant, set->allocated); 128 | 129 | for (qcvm_hash_value_t **v = &set->hashed[hash], **p = NULL; *v; p = v, v = &(*v)->hash_next) 130 | { 131 | if (!qcvm_variant_equals((*v)->value, variant)) 132 | continue; 133 | 134 | qcvm_hash_value_t *old_v = (*v); 135 | 136 | old_v->value.type = TYPE_VOID; 137 | 138 | if ((*v)->index < set->size - 1) 139 | { 140 | memmove(set->indexed + (*v)->index, set->indexed + ((*v)->index + 1), sizeof(qcvm_hash_value_t *) * ((set->size - 1) - (*v)->index)); 141 | 142 | for (size_t i = (*v)->index; i < set->size - 1; i++) 143 | set->indexed[i]->index = i; 144 | } 145 | 146 | *v = (*v)->hash_next; 147 | 148 | if (p) 149 | (*p)->hash_next = *v; 150 | 151 | old_v->hash_next = set->free; 152 | set->free = old_v; 153 | 154 | if (variant.type == TYPE_STRING) 155 | qcvm_string_list_release(vm, variant.value.str); 156 | 157 | set->size--; 158 | return true; 159 | } 160 | 161 | return false; 162 | } 163 | 164 | bool hashset_contains(qcvm_t *vm, qcvm_hashset_t *set, const qcvm_variant_t variant) 165 | { 166 | if (!set->allocated) 167 | return false; 168 | 169 | const uint32_t hash = Q_hash_variant(variant, set->allocated); 170 | 171 | for (qcvm_hash_value_t *v = set->hashed[hash]; v; v = v->hash_next) 172 | if (qcvm_variant_equals(v->value, variant)) 173 | return true; 174 | 175 | return false; 176 | } 177 | 178 | void hashset_clear(qcvm_t *vm, qcvm_hashset_t *set) 179 | { 180 | if (!set->size) 181 | return; 182 | 183 | for (qcvm_hash_value_t *v = set->values; v < set->values + set->size; v++) 184 | if (v->value.type == TYPE_STRING) 185 | qcvm_string_list_release(vm, v->value.value.str); 186 | 187 | memset(set->values, 0, sizeof(qcvm_hash_value_t) * set->allocated); 188 | memset(set->hashed, 0, sizeof(qcvm_hash_value_t *) * set->allocated); 189 | set->size = 0; 190 | set->free = NULL; 191 | 192 | for (qcvm_hash_value_t *v = set->values; v < set->values + set->allocated; v++) 193 | { 194 | v->hash_next = set->free; 195 | set->free = v; 196 | } 197 | } 198 | 199 | static void qcvm_hashset_free(qcvm_t *vm, void *handle) 200 | { 201 | qcvm_hashset_t *set = (qcvm_hashset_t *)handle; 202 | hashset_free(vm, set); 203 | } 204 | 205 | const qcvm_handle_descriptor_t hashset_descriptor = 206 | { 207 | .free = qcvm_hashset_free 208 | }; 209 | 210 | static void QC_hashset_alloc(qcvm_t *vm) 211 | { 212 | const size_t reserve = vm->state.argc ? qcvm_argv_int32(vm, 0) : HASH_RESERVE; 213 | qcvm_hashset_t *set = hashset_alloc(vm, reserve); 214 | qcvm_return_handle(vm, set, &hashset_descriptor); 215 | } 216 | 217 | static qcvm_always_inline void QC_hashset_func(qcvm_t *vm, bool (*func)(qcvm_t *vm, qcvm_hashset_t *set, const qcvm_variant_t variant)) 218 | { 219 | qcvm_hashset_t *set = qcvm_argv_handle(qcvm_hashset_t, vm, 0); 220 | qcvm_variant_t variant = { 221 | .type = qcvm_argv_int32(vm, 1) 222 | }; 223 | 224 | assert(variant.type > TYPE_VOID && variant.type <= TYPE_INTEGER); 225 | 226 | if (variant.type == TYPE_VECTOR) 227 | variant.value.vec = qcvm_argv_vector(vm, 2); 228 | else 229 | variant.value.itg = qcvm_argv_int32(vm, 2); 230 | 231 | const bool added = func(vm, set, variant); 232 | qcvm_return_int32(vm, added); 233 | } 234 | 235 | static void QC_hashset_add(qcvm_t *vm) 236 | { 237 | QC_hashset_func(vm, hashset_add); 238 | } 239 | 240 | static void QC_hashset_remove(qcvm_t *vm) 241 | { 242 | QC_hashset_func(vm, hashset_remove); 243 | } 244 | 245 | static void QC_hashset_contains(qcvm_t *vm) 246 | { 247 | QC_hashset_func(vm, hashset_contains); 248 | } 249 | 250 | static void QC_hashset_get_length(qcvm_t *vm) 251 | { 252 | qcvm_hashset_t *set = qcvm_argv_handle(qcvm_hashset_t, vm, 0); 253 | qcvm_return_int32(vm, (int32_t)set->size); 254 | } 255 | 256 | static void QC_hashset_clear(qcvm_t *vm) 257 | { 258 | qcvm_hashset_t *set = qcvm_argv_handle(qcvm_hashset_t, vm, 0); 259 | hashset_clear(vm, set); 260 | } 261 | 262 | static void QC_hashset_at(qcvm_t *vm) 263 | { 264 | qcvm_hashset_t *set = qcvm_argv_handle(qcvm_hashset_t, vm, 0); 265 | int32_t index = qcvm_argv_int32(vm, 1); 266 | 267 | if (index < 0 || index >= set->size) 268 | qcvm_error(vm, "set index overrun"); 269 | 270 | qcvm_hash_value_t *v = set->indexed[index]; 271 | 272 | if (v->value.type == TYPE_VECTOR) 273 | qcvm_return_vector(vm, v->value.value.vec); 274 | else 275 | qcvm_return_int32(vm, v->value.value.itg); 276 | 277 | if (vm->state.argc > 2) 278 | qcvm_set_global_typed_value(int32_t, vm, GLOBAL_PARM2, v->value.type); 279 | } 280 | 281 | void qcvm_init_hash_builtins(qcvm_t *vm) 282 | { 283 | qcvm_register_builtin(hashset_alloc); 284 | qcvm_register_builtin(hashset_add); 285 | qcvm_register_builtin(hashset_remove); 286 | qcvm_register_builtin(hashset_contains); 287 | qcvm_register_builtin(hashset_get_length); 288 | qcvm_register_builtin(hashset_clear); 289 | qcvm_register_builtin(hashset_at); 290 | } -------------------------------------------------------------------------------- /vm_hash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct qcvm_hash_value_s 4 | { 5 | qcvm_variant_t value; 6 | 7 | size_t index; 8 | uint32_t hash_value; 9 | struct qcvm_hash_value_s *hash_next; 10 | } qcvm_hash_value_t; 11 | 12 | typedef struct 13 | { 14 | qcvm_hash_value_t *values; 15 | size_t size, allocated; 16 | qcvm_hash_value_t **hashed; 17 | qcvm_hash_value_t *free; 18 | qcvm_hash_value_t **indexed; 19 | } qcvm_hashset_t; 20 | 21 | inline bool qcvm_variant_equals(const qcvm_variant_t a, const qcvm_variant_t b) 22 | { 23 | if (a.type != b.type) 24 | return false; 25 | if (a.type == TYPE_VECTOR) 26 | return VectorEquals(a.value.vec, b.value.vec); 27 | return a.value.itg == b.value.itg; 28 | } 29 | 30 | inline uint32_t Q_hash_variant(const qcvm_variant_t variant, const size_t size) 31 | { 32 | uint32_t hash; 33 | 34 | if (variant.type == TYPE_VECTOR) 35 | hash = (int32_t)(variant.value.vec.x * 73856093) ^ (int32_t)(variant.value.vec.y * 19349663) ^ (int32_t)(variant.value.vec.z * 83492791); 36 | else 37 | hash = (variant.value.itg ^ variant.type); 38 | 39 | return hash % size; 40 | } 41 | 42 | #define HASH_RESERVE 64 43 | 44 | extern const qcvm_handle_descriptor_t hashset_descriptor; 45 | 46 | qcvm_hashset_t *hashset_alloc(const qcvm_t *vm, const size_t reserve); 47 | void hashset_free(const qcvm_t *vm, qcvm_hashset_t *set); 48 | bool hashset_add(qcvm_t *vm, qcvm_hashset_t *set, const qcvm_variant_t variant); 49 | bool hashset_remove(qcvm_t *vm, qcvm_hashset_t *set, const qcvm_variant_t variant); 50 | bool hashset_contains(qcvm_t *vm, qcvm_hashset_t *set, const qcvm_variant_t variant); 51 | void hashset_clear(qcvm_t *vm, qcvm_hashset_t *set); 52 | 53 | void qcvm_init_hash_builtins(qcvm_t *vm); -------------------------------------------------------------------------------- /vm_heap.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | #include "vm_heap.h" 4 | #include "vm_math.h" 5 | 6 | typedef struct 7 | { 8 | void *ptr; 9 | size_t size; 10 | } qcvm_heap_t; 11 | 12 | static void qcvm_heap_free(qcvm_t *vm, void *handle) 13 | { 14 | qcvm_heap_t *list = (qcvm_heap_t *)handle; 15 | if (list->ptr) 16 | qcvm_mem_free(vm, list->ptr); 17 | } 18 | 19 | static bool qcvm_heap_resolve_pointer(const qcvm_t *vm, void *handle, const size_t offset, const size_t len, void **address) 20 | { 21 | qcvm_heap_t *list = (qcvm_heap_t *)handle; 22 | 23 | if ((offset + len) <= list->size) 24 | { 25 | if (address) 26 | *address = (uint8_t *)list->ptr + offset; 27 | return true; 28 | } 29 | return false; 30 | } 31 | 32 | static const qcvm_handle_descriptor_t list_descriptor = 33 | { 34 | .free = qcvm_heap_free, 35 | .resolve_pointer = qcvm_heap_resolve_pointer 36 | }; 37 | 38 | static void QC_heap_alloc(qcvm_t *vm) 39 | { 40 | const int32_t size = qcvm_argv_int32(vm, 0); 41 | 42 | if (size < 0) 43 | qcvm_error(vm, "bad heap size"); 44 | 45 | qcvm_heap_t *list = (qcvm_heap_t *)qcvm_alloc(vm, sizeof(qcvm_heap_t)); 46 | 47 | list->ptr = qcvm_alloc(vm, size); 48 | list->size = size; 49 | 50 | int32_t handle = qcvm_handle_alloc(vm, list, &list_descriptor); 51 | 52 | qcvm_return_pointer(vm, (qcvm_pointer_t) { .handle = { .type = QCVM_POINTER_HANDLE, .index = handle, .offset = 0 } }); 53 | } 54 | 55 | void qcvm_init_heap_builtins(qcvm_t *vm) 56 | { 57 | qcvm_register_builtin(heap_alloc); 58 | } -------------------------------------------------------------------------------- /vm_heap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void qcvm_init_heap_builtins(qcvm_t *vm); -------------------------------------------------------------------------------- /vm_list.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | #include "vm_list.h" 4 | #include "vm_math.h" 5 | 6 | #define STRUCTLIST_RESERVE 32 7 | 8 | typedef struct 9 | { 10 | qcvm_variant_t *values; 11 | size_t size, allocated; 12 | } qcvm_list_t; 13 | 14 | static void qcvm_list_free(qcvm_t *vm, void *handle) 15 | { 16 | qcvm_list_t *list = (qcvm_list_t *)handle; 17 | if (list->values) 18 | qcvm_mem_free(vm, list->values); 19 | } 20 | 21 | static bool qcvm_list_resolve_pointer(const qcvm_t *vm, void *handle, const size_t offset, const size_t len, void **address) 22 | { 23 | qcvm_list_t *list = (qcvm_list_t *)handle; 24 | 25 | if ((offset + len) <= list->size * sizeof(qcvm_variant_t)) 26 | { 27 | if (address) 28 | *address = (uint8_t *)list->values + offset; 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | static const qcvm_handle_descriptor_t list_descriptor = 35 | { 36 | .free = qcvm_list_free, 37 | .resolve_pointer = qcvm_list_resolve_pointer 38 | }; 39 | 40 | static void QC_list_alloc(qcvm_t *vm) 41 | { 42 | qcvm_list_t *list = (qcvm_list_t *)qcvm_alloc(vm, sizeof(qcvm_list_t)); 43 | 44 | list->allocated = vm->state.argc > 0 ? qcvm_argv_int32(vm, 0) : STRUCTLIST_RESERVE; 45 | 46 | if (list->allocated) 47 | list->values = qcvm_alloc(vm, sizeof(qcvm_variant_t) * list->allocated); 48 | 49 | qcvm_return_handle(vm, list, &list_descriptor); 50 | } 51 | 52 | static inline void list_insert(qcvm_t *vm, qcvm_list_t *list, const qcvm_variant_t value, const size_t index) 53 | { 54 | if (index > list->size) 55 | qcvm_error(vm, "bad insert index"); 56 | 57 | // re-allocate 58 | if (list->size == list->allocated) 59 | { 60 | void *old_values = list->values; 61 | 62 | if (!list->allocated) 63 | list->allocated = STRUCTLIST_RESERVE; 64 | else 65 | list->allocated *= 2; 66 | 67 | list->values = qcvm_alloc(vm, sizeof(qcvm_variant_t) * list->allocated); 68 | 69 | if (old_values) 70 | { 71 | memcpy(list->values, old_values, sizeof(qcvm_variant_t) * list->size); 72 | qcvm_mem_free(vm, old_values); 73 | } 74 | } 75 | 76 | // shift the list to accomodate new entry 77 | if (index < list->size) 78 | { 79 | const size_t shift_size = (list->size - index) * sizeof(qcvm_variant_t); 80 | memmove((uint8_t *)list->values + (sizeof(qcvm_variant_t) * (index + 1)), (uint8_t *)list->values + (sizeof(qcvm_variant_t) * index), shift_size); 81 | } 82 | 83 | memcpy((uint8_t *)list->values + (sizeof(qcvm_variant_t) * index), &value, sizeof(qcvm_variant_t)); 84 | list->size++; 85 | } 86 | 87 | static void QC_list_insert(qcvm_t *vm) 88 | { 89 | qcvm_list_t *list = qcvm_argv_handle(qcvm_list_t, vm, 0); 90 | const qcvm_variant_t value = qcvm_argv_variant(vm, 1); 91 | const size_t index = qcvm_argv_int32(vm, 2); 92 | list_insert(vm, list, value, index); 93 | } 94 | 95 | static void QC_list_push(qcvm_t *vm) 96 | { 97 | qcvm_list_t *list = qcvm_argv_handle(qcvm_list_t, vm, 0); 98 | const qcvm_variant_t value = qcvm_argv_variant(vm, 1); 99 | list_insert(vm, list, value, list->size); 100 | } 101 | 102 | static void QC_list_unshift(qcvm_t *vm) 103 | { 104 | qcvm_list_t *list = qcvm_argv_handle(qcvm_list_t, vm, 0); 105 | const qcvm_variant_t value = qcvm_argv_variant(vm, 1); 106 | list_insert(vm, list, value, 0); 107 | } 108 | 109 | static inline void list_delete(qcvm_t *vm, qcvm_list_t *list, const size_t index) 110 | { 111 | if (index >= list->size) 112 | qcvm_error(vm, "bad delete index"); 113 | 114 | // shift if we have more than 1 115 | if (list->size > 1) 116 | { 117 | const size_t shift_size = (list->size - index - 1) * sizeof(qcvm_variant_t); 118 | memmove((uint8_t *)list->values + (sizeof(qcvm_variant_t) * index), (uint8_t *)list->values + (sizeof(qcvm_variant_t) * index + 1), shift_size); 119 | } 120 | 121 | list->size--; 122 | } 123 | 124 | static void QC_list_delete(qcvm_t *vm) 125 | { 126 | qcvm_list_t *list = qcvm_argv_handle(qcvm_list_t, vm, 0); 127 | const size_t index = qcvm_argv_int32(vm, 1); 128 | 129 | if (vm->state.argc > 2) 130 | qcvm_set_global_typed_ptr(qcvm_variant_t, vm, GLOBAL_PARM2, list->values + index); 131 | 132 | list_delete(vm, list, index); 133 | } 134 | 135 | static void QC_list_pop(qcvm_t *vm) 136 | { 137 | qcvm_list_t *list = qcvm_argv_handle(qcvm_list_t, vm, 0); 138 | 139 | if (vm->state.argc > 1) 140 | qcvm_set_global_typed_ptr(qcvm_variant_t, vm, GLOBAL_PARM1, list->values + (list->size - 1)); 141 | 142 | list_delete(vm, list, list->size - 1); 143 | } 144 | 145 | static void QC_list_shift(qcvm_t *vm) 146 | { 147 | qcvm_list_t *list = qcvm_argv_handle(qcvm_list_t, vm, 0); 148 | 149 | if (vm->state.argc > 1) 150 | qcvm_set_global_typed_ptr(qcvm_variant_t, vm, GLOBAL_PARM1, list->values); 151 | 152 | list_delete(vm, list, 0); 153 | } 154 | 155 | static void QC_list_get_length(qcvm_t *vm) 156 | { 157 | qcvm_list_t *list = qcvm_argv_handle(qcvm_list_t, vm, 0); 158 | qcvm_return_int32(vm, list->size); 159 | } 160 | 161 | static void QC_list_clear(qcvm_t *vm) 162 | { 163 | qcvm_list_t *list = qcvm_argv_handle(qcvm_list_t, vm, 0); 164 | list->size = 0; 165 | } 166 | 167 | static void QC_list_at(qcvm_t *vm) 168 | { 169 | qcvm_handle_id_t handleid = qcvm_argv_int32(vm, 0); 170 | qcvm_list_t *list = qcvm_fetch_handle_typed(qcvm_list_t, vm, handleid); 171 | const size_t index = qcvm_argv_int32(vm, 1); 172 | 173 | if (index >= list->size) 174 | qcvm_error(vm, "bad index"); 175 | 176 | qcvm_return_variant(vm, *(list->values + index)); 177 | } 178 | 179 | static void QC_list_set(qcvm_t *vm) 180 | { 181 | qcvm_list_t *list = qcvm_argv_handle(qcvm_list_t, vm, 0); 182 | const size_t index = qcvm_argv_int32(vm, 1); 183 | const qcvm_variant_t value = qcvm_argv_variant(vm, 2); 184 | 185 | if (index >= list->size) 186 | qcvm_error(vm, "bad index"); 187 | 188 | *(list->values + index) = value; 189 | } 190 | 191 | void qcvm_init_list_builtins(qcvm_t *vm) 192 | { 193 | qcvm_register_builtin(list_alloc); 194 | qcvm_register_builtin(list_insert); 195 | qcvm_register_builtin(list_push); 196 | qcvm_register_builtin(list_unshift); 197 | qcvm_register_builtin(list_delete); 198 | qcvm_register_builtin(list_pop); 199 | qcvm_register_builtin(list_shift); 200 | qcvm_register_builtin(list_get_length); 201 | qcvm_register_builtin(list_clear); 202 | qcvm_register_builtin(list_at); 203 | qcvm_register_builtin(list_set); 204 | } -------------------------------------------------------------------------------- /vm_list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void qcvm_init_list_builtins(qcvm_t *vm); -------------------------------------------------------------------------------- /vm_math.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | #include "vm_math.h" 4 | #include "g_time.h" 5 | #include 6 | 7 | // float(float) 8 | #define MATH_FUNC_RF_AF(name) \ 9 | static void QC_##name(qcvm_t *vm) \ 10 | { \ 11 | const vec_t v = qcvm_argv_float(vm, 0); \ 12 | qcvm_return_float(vm, name(v)); \ 13 | } 14 | 15 | // float(float, float) 16 | #define MATH_FUNC_RF_AFF(name) \ 17 | static void QC_##name(qcvm_t *vm) \ 18 | { \ 19 | const vec_t v1 = qcvm_argv_float(vm, 0); \ 20 | const vec_t v2 = qcvm_argv_float(vm, 1); \ 21 | qcvm_return_float(vm, name(v1, v2)); \ 22 | } 23 | 24 | // float(float, __out int) 25 | #define MATH_FUNC_RF_AFOI(name) \ 26 | static void QC_##name(qcvm_t *vm) \ 27 | { \ 28 | const vec_t v1 = qcvm_argv_float(vm, 0); \ 29 | int32_t v2; \ 30 | const vec_t result = name(v1, &v2); \ 31 | qcvm_return_float(vm, result); \ 32 | qcvm_set_global_typed_value(int32_t, vm, GLOBAL_PARM1, v2); \ 33 | } 34 | 35 | // float(float, __out float) 36 | #define MATH_FUNC_RF_AFOF(name) \ 37 | static void QC_##name(qcvm_t *vm) \ 38 | { \ 39 | const vec_t v1 = qcvm_argv_float(vm, 0); \ 40 | double v2_; \ 41 | const vec_t result = name(v1, &v2_); \ 42 | vec_t v2 = (vec_t)v2_; \ 43 | qcvm_return_float(vm, result); \ 44 | qcvm_set_global_typed_value(vec_t, vm, GLOBAL_PARM1, v2); \ 45 | } 46 | 47 | // float(float, float) 48 | #define MATH_FUNC_RF_AFI(name) \ 49 | static void QC_##name(qcvm_t *vm) \ 50 | { \ 51 | const vec_t v1 = qcvm_argv_float(vm, 0); \ 52 | const int32_t v2 = qcvm_argv_int32(vm, 1); \ 53 | qcvm_return_float(vm, name(v1, v2)); \ 54 | } 55 | 56 | // int(float) 57 | #define MATH_FUNC_RI_AF(name) \ 58 | static void QC_##name(qcvm_t *vm) \ 59 | { \ 60 | const vec_t v = qcvm_argv_float(vm, 0); \ 61 | qcvm_return_int32(vm, name(v)); \ 62 | } 63 | 64 | // float(float, float, __out int) 65 | #define MATH_FUNC_RF_AFFOI(name) \ 66 | static void QC_##name(qcvm_t *vm) \ 67 | { \ 68 | const vec_t v1 = qcvm_argv_float(vm, 0); \ 69 | const vec_t v2 = qcvm_argv_float(vm, 1); \ 70 | int32_t v3; \ 71 | const vec_t result = name(v1, v2, &v3); \ 72 | qcvm_return_float(vm, result); \ 73 | qcvm_set_global_typed_value(int32_t, vm, GLOBAL_PARM2, v2); \ 74 | } 75 | 76 | // float(string) 77 | #define MATH_FUNC_RF_AS(name) \ 78 | static void QC_##name(qcvm_t *vm) \ 79 | { \ 80 | const char *v = qcvm_argv_string(vm, 0); \ 81 | qcvm_return_float(vm, name(v)); \ 82 | } 83 | 84 | // int(int) 85 | #define MATH_FUNC_RI_AI(name) \ 86 | static void QC_##name(qcvm_t *vm) \ 87 | { \ 88 | const int32_t v = qcvm_argv_int32(vm, 0); \ 89 | qcvm_return_int32(vm, name(v)); \ 90 | } 91 | 92 | // float(float, float, float) 93 | #define MATH_FUNC_RF_AFFF(name) \ 94 | static void QC_##name(qcvm_t *vm) \ 95 | { \ 96 | const vec_t v1 = qcvm_argv_float(vm, 0); \ 97 | const vec_t v2 = qcvm_argv_float(vm, 1); \ 98 | const vec_t v3 = qcvm_argv_float(vm, 2); \ 99 | qcvm_return_float(vm, name(v1, v2, v3)); \ 100 | } 101 | 102 | // trig 103 | MATH_FUNC_RF_AF(cos); 104 | MATH_FUNC_RF_AF(sin); 105 | MATH_FUNC_RF_AF(tan); 106 | MATH_FUNC_RF_AF(acos); 107 | MATH_FUNC_RF_AF(asin); 108 | MATH_FUNC_RF_AF(atan); 109 | MATH_FUNC_RF_AFF(atan2); 110 | 111 | // hyperbolic 112 | MATH_FUNC_RF_AF(cosh); 113 | MATH_FUNC_RF_AF(sinh); 114 | MATH_FUNC_RF_AF(tanh); 115 | MATH_FUNC_RF_AF(acosh); 116 | MATH_FUNC_RF_AF(asinh); 117 | MATH_FUNC_RF_AF(atanh); 118 | 119 | // exp/logarithmic 120 | MATH_FUNC_RF_AF(exp); 121 | MATH_FUNC_RF_AFOI(frexp); 122 | MATH_FUNC_RF_AFI(ldexp); 123 | MATH_FUNC_RF_AF(log); 124 | MATH_FUNC_RF_AF(log10); 125 | MATH_FUNC_RF_AFOF(modf); 126 | MATH_FUNC_RF_AF(exp2); 127 | MATH_FUNC_RF_AF(expm1); 128 | MATH_FUNC_RI_AF(ilogb); 129 | MATH_FUNC_RF_AF(log1p); 130 | MATH_FUNC_RF_AF(log2); 131 | MATH_FUNC_RF_AF(logb); 132 | MATH_FUNC_RF_AFI(scalbn); 133 | 134 | // pow 135 | MATH_FUNC_RF_AFF(pow); 136 | MATH_FUNC_RF_AF(sqrt); 137 | MATH_FUNC_RF_AF(cbrt); 138 | MATH_FUNC_RF_AFF(hypot); 139 | 140 | // error and gamma 141 | MATH_FUNC_RF_AF(erf); 142 | MATH_FUNC_RF_AF(erfc); 143 | MATH_FUNC_RF_AF(tgamma); 144 | MATH_FUNC_RF_AF(lgamma); 145 | 146 | // rounding/remainder 147 | MATH_FUNC_RF_AF(ceil); 148 | MATH_FUNC_RF_AF(floor); 149 | MATH_FUNC_RF_AFF(fmod); 150 | MATH_FUNC_RF_AF(trunc); 151 | MATH_FUNC_RF_AF(round); 152 | MATH_FUNC_RI_AF(lround); 153 | MATH_FUNC_RF_AF(rint); 154 | MATH_FUNC_RI_AF(lrint); 155 | MATH_FUNC_RF_AF(nearbyint); 156 | MATH_FUNC_RF_AFF(remainder); 157 | MATH_FUNC_RF_AFFOI(remquo); 158 | 159 | // floating point 160 | MATH_FUNC_RF_AFF(copysign); 161 | MATH_FUNC_RF_AS(nan); 162 | MATH_FUNC_RF_AFF(nextafter); 163 | MATH_FUNC_RF_AFF(nexttoward); 164 | 165 | // other 166 | MATH_FUNC_RF_AF(fabs); 167 | MATH_FUNC_RI_AI(abs); 168 | MATH_FUNC_RF_AFFF(fma); 169 | 170 | // classifications 171 | MATH_FUNC_RI_AF(isfinite); 172 | MATH_FUNC_RI_AF(isinf); 173 | MATH_FUNC_RI_AF(isnan); 174 | MATH_FUNC_RI_AF(isnormal); 175 | MATH_FUNC_RI_AF(signbit); 176 | 177 | /* 178 | ===================================================================== 179 | 180 | MT19337 PRNG 181 | 182 | ===================================================================== 183 | */ 184 | 185 | enum { STATE_VECTOR_LENGTH = 624 }; 186 | enum { STATE_VECTOR_M = 397 }; /* changes to STATE_VECTOR_LENGTH also require changes to this */ 187 | 188 | static struct 189 | { 190 | uint32_t mt[STATE_VECTOR_LENGTH]; 191 | int32_t index; 192 | } qcvm_mt; 193 | 194 | enum { UPPER_MASK = (int32_t)0x80000000 }; 195 | enum { LOWER_MASK = 0x7fffffff }; 196 | enum { TEMPERING_MASK_B = (int32_t)0x9d2c5680 }; 197 | enum { TEMPERING_MASK_C = (int32_t)0xefc60000 }; 198 | 199 | void Q_srand(const uint32_t seed) 200 | { 201 | qcvm_mt.mt[0] = seed & 0xffffffff; 202 | 203 | for (qcvm_mt.index = 1; qcvm_mt.index < STATE_VECTOR_LENGTH; qcvm_mt.index++) 204 | qcvm_mt.mt[qcvm_mt.index] = (6069 * qcvm_mt.mt[qcvm_mt.index - 1]) & 0xffffffff; 205 | } 206 | 207 | static uint32_t Q_rand(void) 208 | { 209 | unsigned long y; 210 | static unsigned long mag[2] = { 0x0, 0x9908b0df }; 211 | 212 | if (qcvm_mt.index >= STATE_VECTOR_LENGTH || qcvm_mt.index < 0) 213 | { 214 | int kk; 215 | 216 | if (qcvm_mt.index >= STATE_VECTOR_LENGTH+1 || qcvm_mt.index < 0) 217 | Q_srand(4357); 218 | 219 | for (kk = 0; kk < STATE_VECTOR_LENGTH - STATE_VECTOR_M; kk++) 220 | { 221 | y = (qcvm_mt.mt[kk] & UPPER_MASK) | (qcvm_mt.mt[kk+1] & LOWER_MASK); 222 | qcvm_mt.mt[kk] = qcvm_mt.mt[kk+STATE_VECTOR_M] ^ (y >> 1) ^ mag[y & 0x1]; 223 | } 224 | 225 | for (; kk < STATE_VECTOR_LENGTH - 1; kk++) 226 | { 227 | y = (qcvm_mt.mt[kk] & UPPER_MASK) | (qcvm_mt.mt[kk+1] & LOWER_MASK); 228 | qcvm_mt.mt[kk] = qcvm_mt.mt[kk+(STATE_VECTOR_M-STATE_VECTOR_LENGTH)] ^ (y >> 1) ^ mag[y & 0x1]; 229 | } 230 | 231 | y = (qcvm_mt.mt[STATE_VECTOR_LENGTH-1] & UPPER_MASK) | (qcvm_mt.mt[0] & LOWER_MASK); 232 | qcvm_mt.mt[STATE_VECTOR_LENGTH-1] = qcvm_mt.mt[STATE_VECTOR_M-1] ^ (y >> 1) ^ mag[y & 0x1]; 233 | qcvm_mt.index = 0; 234 | } 235 | 236 | y = qcvm_mt.mt[qcvm_mt.index++]; 237 | y ^= (y >> 11); 238 | y ^= (y << 7) & TEMPERING_MASK_B; 239 | y ^= (y << 15) & TEMPERING_MASK_C; 240 | y ^= (y >> 18); 241 | return y; 242 | } 243 | 244 | static uint32_t Q_rand_uniform(uint32_t n) 245 | { 246 | uint32_t x, r; 247 | 248 | do 249 | { 250 | x = Q_rand(); 251 | r = x % n; 252 | } while (x - r > (-n)); 253 | 254 | return r; 255 | } 256 | 257 | vec_t frand(void) 258 | { 259 | return (vec_t)(Q_rand()) / 0xffffffffu; 260 | } 261 | 262 | vec_t frand_m(const vec_t max) 263 | { 264 | return frand() * max; 265 | } 266 | 267 | vec_t frand_mm(const vec_t min, const vec_t max) 268 | { 269 | return frand() * (max - min) + min; 270 | } 271 | 272 | static void QC_Q_rand(qcvm_t *vm) 273 | { 274 | qcvm_return_int32(vm, Q_rand() & 0x7FFFFFFF); 275 | } 276 | 277 | static void QC_Q_rand_uniform(qcvm_t *vm) 278 | { 279 | qcvm_return_int32(vm, Q_rand_uniform(qcvm_argv_int32(vm, 0))); 280 | } 281 | 282 | static void QC_now(qcvm_t *vm) 283 | { 284 | qcvm_return_float(vm, qcvm_cpp_now()); 285 | } 286 | 287 | void qcvm_init_math_builtins(qcvm_t *vm) 288 | { 289 | // trig 290 | qcvm_register_builtin(cos); 291 | qcvm_register_builtin(sin); 292 | qcvm_register_builtin(tan); 293 | qcvm_register_builtin(acos); 294 | qcvm_register_builtin(asin); 295 | qcvm_register_builtin(atan); 296 | qcvm_register_builtin(atan2); 297 | 298 | // hyperbolic 299 | qcvm_register_builtin(cosh); 300 | qcvm_register_builtin(sinh); 301 | qcvm_register_builtin(tanh); 302 | qcvm_register_builtin(acosh); 303 | qcvm_register_builtin(asinh); 304 | qcvm_register_builtin(atanh); 305 | 306 | // exp/logarithmic 307 | qcvm_register_builtin(exp); 308 | qcvm_register_builtin(frexp); 309 | qcvm_register_builtin(ldexp); 310 | qcvm_register_builtin(log); 311 | qcvm_register_builtin(log10); 312 | qcvm_register_builtin(modf); 313 | qcvm_register_builtin(exp2); 314 | qcvm_register_builtin(expm1); 315 | qcvm_register_builtin(ilogb); 316 | qcvm_register_builtin(log1p); 317 | qcvm_register_builtin(log2); 318 | qcvm_register_builtin(logb); 319 | qcvm_register_builtin(scalbn); 320 | 321 | // pow 322 | qcvm_register_builtin(pow); 323 | qcvm_register_builtin(sqrt); 324 | qcvm_register_builtin(cbrt); 325 | qcvm_register_builtin(hypot); 326 | 327 | // error and gamma 328 | qcvm_register_builtin(erf); 329 | qcvm_register_builtin(erfc); 330 | qcvm_register_builtin(tgamma); 331 | qcvm_register_builtin(lgamma); 332 | 333 | // rounding/remainder 334 | qcvm_register_builtin(ceil); 335 | qcvm_register_builtin(floor); 336 | qcvm_register_builtin(fmod); 337 | qcvm_register_builtin(trunc); 338 | qcvm_register_builtin(round); 339 | qcvm_register_builtin(lround); 340 | qcvm_register_builtin(rint); 341 | qcvm_register_builtin(lrint); 342 | qcvm_register_builtin(nearbyint); 343 | qcvm_register_builtin(remainder); 344 | qcvm_register_builtin(remquo); 345 | 346 | // floating point 347 | qcvm_register_builtin(copysign); 348 | qcvm_register_builtin(nan); 349 | qcvm_register_builtin(nextafter); 350 | qcvm_register_builtin(nexttoward); 351 | 352 | // other functions 353 | qcvm_register_builtin(fabs); 354 | qcvm_register_builtin(abs); 355 | qcvm_register_builtin(fma); 356 | 357 | // classifications 358 | qcvm_register_builtin(isfinite); 359 | qcvm_register_builtin(isinf); 360 | qcvm_register_builtin(isnan); 361 | qcvm_register_builtin(isnormal); 362 | qcvm_register_builtin(signbit); 363 | 364 | // set the FLT_ constants 365 | qcvm_definition_t *def = qcvm_find_definition(vm, "FLT_MAX", TYPE_FLOAT); 366 | 367 | if (def) 368 | { 369 | const vec_t val = FLT_MAX; 370 | qcvm_set_global_typed_value(vec_t, vm, def->global_index, val); 371 | } 372 | 373 | def = qcvm_find_definition(vm, "FLT_EPSILON", TYPE_FLOAT); 374 | 375 | if (def) 376 | { 377 | const vec_t val = FLT_EPSILON; 378 | qcvm_set_global_typed_value(vec_t, vm, def->global_index, val); 379 | } 380 | 381 | def = qcvm_find_definition(vm, "FLT_MIN", TYPE_FLOAT); 382 | 383 | if (def) 384 | { 385 | const vec_t val = FLT_MIN; 386 | qcvm_set_global_typed_value(vec_t, vm, def->global_index, val); 387 | } 388 | 389 | // randomness 390 | qcvm_register_builtin(Q_rand); 391 | qcvm_register_builtin(Q_rand_uniform); 392 | 393 | // time 394 | qcvm_register_builtin(now); 395 | } -------------------------------------------------------------------------------- /vm_math.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void qcvm_init_math_builtins(qcvm_t *vm); 4 | 5 | void Q_srand(const uint32_t seed); 6 | vec_t frand(void); 7 | vec_t frand_m(const vec_t max); 8 | vec_t frand_mm(const vec_t min, const vec_t max); 9 | -------------------------------------------------------------------------------- /vm_mem.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | 4 | static void QC_memcpy(qcvm_t *vm) 5 | { 6 | const qcvm_pointer_t dst = qcvm_argv_pointer(vm, 0); 7 | const qcvm_pointer_t src = qcvm_argv_pointer(vm, 1); 8 | const int32_t size = qcvm_argv_int32(vm, 2); 9 | void *dst_address, *src_address; 10 | 11 | if (size < 0 || !qcvm_resolve_pointer(vm, dst, false, size, &dst_address) || !qcvm_resolve_pointer(vm, src, false, size, &src_address)) 12 | qcvm_error(vm, "invalid pointer"); 13 | else if (!size) 14 | return; 15 | 16 | memcpy(dst_address, src_address, size); 17 | 18 | const size_t span = size / sizeof(qcvm_global_t); 19 | qcvm_string_list_mark_refs_copied(vm, src_address, dst_address, span); 20 | qcvm_field_wrap_list_check_set(vm, dst_address, span); 21 | } 22 | 23 | static void QC_memmove(qcvm_t *vm) 24 | { 25 | const qcvm_pointer_t dst = qcvm_argv_pointer(vm, 0); 26 | const qcvm_pointer_t src = qcvm_argv_pointer(vm, 1); 27 | const int32_t size = qcvm_argv_int32(vm, 2); 28 | void *dst_address, *src_address; 29 | 30 | if (size < 0 || !qcvm_resolve_pointer(vm, dst, false, size, &dst_address) || !qcvm_resolve_pointer(vm, src, false, size, &src_address)) 31 | qcvm_error(vm, "invalid pointer"); 32 | else if (!size) 33 | return; 34 | 35 | memmove(dst_address, src_address, size); 36 | 37 | const size_t span = size / sizeof(qcvm_global_t); 38 | qcvm_string_list_mark_refs_copied(vm, src_address, dst_address, span); 39 | qcvm_field_wrap_list_check_set(vm, dst_address, span); 40 | } 41 | 42 | static void QC_memset(qcvm_t *vm) 43 | { 44 | const qcvm_pointer_t dst = qcvm_argv_pointer(vm, 0); 45 | const int32_t val = qcvm_argv_int32(vm, 1); 46 | const int32_t size = qcvm_argv_int32(vm, 2); 47 | void *dst_address; 48 | 49 | if (size < 0 || !qcvm_resolve_pointer(vm, dst, false, size, &dst_address)) 50 | qcvm_error(vm, "invalid pointer"); 51 | else if (!size) 52 | return; 53 | 54 | memset(dst_address, val, size); 55 | 56 | const size_t span = size / sizeof(qcvm_global_t); 57 | qcvm_string_list_check_ref_unset(vm, dst_address, span, true); 58 | qcvm_field_wrap_list_check_set(vm, dst_address, span); 59 | } 60 | 61 | static void QC_memcmp(qcvm_t *vm) 62 | { 63 | const qcvm_pointer_t dst = qcvm_argv_pointer(vm, 0); 64 | const qcvm_pointer_t src = qcvm_argv_pointer(vm, 1); 65 | const int32_t size = qcvm_argv_int32(vm, 2); 66 | void *dst_address, *src_address; 67 | 68 | if (size < 0 || !qcvm_resolve_pointer(vm, dst, false, size, &dst_address) || !qcvm_resolve_pointer(vm, src, false, size, &src_address)) 69 | qcvm_error(vm, "invalid pointer"); 70 | 71 | qcvm_return_int32(vm, memcmp(dst_address, src_address, size)); 72 | } 73 | 74 | void qcvm_init_mem_builtins(qcvm_t *vm) 75 | { 76 | qcvm_register_builtin(memcpy); 77 | qcvm_register_builtin(memmove); 78 | qcvm_register_builtin(memset); 79 | qcvm_register_builtin(memcmp); 80 | } -------------------------------------------------------------------------------- /vm_mem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void qcvm_init_mem_builtins(qcvm_t *vm); -------------------------------------------------------------------------------- /vm_opcodes.h: -------------------------------------------------------------------------------- 1 | #ifndef OPLIST 2 | #define OPLIST(f) \ 3 | f(OP_DONE), \ 4 | f(OP_MUL_F), \ 5 | f(OP_MUL_V), \ 6 | f(OP_MUL_FV), \ 7 | f(OP_MUL_VF), \ 8 | f(OP_DIV_F), \ 9 | f(OP_ADD_F), \ 10 | f(OP_ADD_V), \ 11 | f(OP_SUB_F), \ 12 | f(OP_SUB_V), \ 13 | \ 14 | f(OP_EQ_F), \ 15 | f(OP_EQ_V), \ 16 | f(OP_EQ_S), \ 17 | f(OP_EQ_E), \ 18 | f(OP_EQ_FNC), \ 19 | \ 20 | f(OP_NE_F), \ 21 | f(OP_NE_V), \ 22 | f(OP_NE_S), \ 23 | f(OP_NE_E), \ 24 | f(OP_NE_FNC), \ 25 | \ 26 | f(OP_LE_F), \ 27 | f(OP_GE_F), \ 28 | f(OP_LT_F), \ 29 | f(OP_GT_F), \ 30 | \ 31 | f(OP_LOAD_F), \ 32 | f(OP_LOAD_V), \ 33 | f(OP_LOAD_S), \ 34 | f(OP_LOAD_ENT), \ 35 | f(OP_LOAD_FLD), \ 36 | f(OP_LOAD_FNC), \ 37 | \ 38 | f(OP_ADDRESS), \ 39 | \ 40 | f(OP_STORE_F), \ 41 | f(OP_STORE_V), \ 42 | f(OP_STORE_S), \ 43 | f(OP_STORE_ENT), \ 44 | f(OP_STORE_FLD), \ 45 | f(OP_STORE_FNC), \ 46 | \ 47 | f(OP_STOREP_F), \ 48 | f(OP_STOREP_V), \ 49 | f(OP_STOREP_S), \ 50 | f(OP_STOREP_ENT), \ 51 | f(OP_STOREP_FLD), \ 52 | f(OP_STOREP_FNC), \ 53 | \ 54 | f(OP_RETURN), \ 55 | f(OP_NOT_F), \ 56 | f(OP_NOT_V), \ 57 | f(OP_NOT_S), \ 58 | f(OP_NOT_ENT), \ 59 | f(OP_NOT_FNC), \ 60 | f(OP_IF_I), \ 61 | f(OP_IFNOT_I), \ 62 | f(OP_CALL0), \ 63 | f(OP_CALL1), \ 64 | f(OP_CALL2), \ 65 | f(OP_CALL3), \ 66 | f(OP_CALL4), \ 67 | f(OP_CALL5), \ 68 | f(OP_CALL6), \ 69 | f(OP_CALL7), \ 70 | f(OP_CALL8), \ 71 | f(OP_STATE), \ 72 | f(OP_GOTO), \ 73 | f(OP_AND_F), \ 74 | f(OP_OR_F), \ 75 | \ 76 | f(OP_BITAND_F), \ 77 | f(OP_BITOR_F), \ 78 | \ 79 | f(OP_MULSTORE_F), \ 80 | f(OP_MULSTORE_VF), \ 81 | f(OP_MULSTOREP_F), \ 82 | f(OP_MULSTOREP_VF), \ 83 | \ 84 | f(OP_DIVSTORE_F), \ 85 | f(OP_DIVSTOREP_F), \ 86 | \ 87 | f(OP_ADDSTORE_F), \ 88 | f(OP_ADDSTORE_V), \ 89 | f(OP_ADDSTOREP_F), \ 90 | f(OP_ADDSTOREP_V), \ 91 | \ 92 | f(OP_SUBSTORE_F), \ 93 | f(OP_SUBSTORE_V), \ 94 | f(OP_SUBSTOREP_F), \ 95 | f(OP_SUBSTOREP_V), \ 96 | \ 97 | f(OP_FETCH_GBL_F), \ 98 | f(OP_FETCH_GBL_V), \ 99 | f(OP_FETCH_GBL_S), \ 100 | f(OP_FETCH_GBL_E), \ 101 | f(OP_FETCH_GBL_FNC), \ 102 | \ 103 | f(OP_CSTATE), \ 104 | f(OP_CWSTATE), \ 105 | \ 106 | f(OP_THINKTIME), \ 107 | \ 108 | f(OP_BITSETSTORE_F), \ 109 | f(OP_BITSETSTOREP_F), \ 110 | f(OP_BITCLRSTORE_F), \ 111 | f(OP_BITCLRSTOREP_F), \ 112 | \ 113 | f(OP_RAND0), \ 114 | f(OP_RAND1), \ 115 | f(OP_RAND2), \ 116 | f(OP_RANDV0), \ 117 | f(OP_RANDV1), \ 118 | f(OP_RANDV2), \ 119 | \ 120 | f(OP_SWITCH_F), \ 121 | f(OP_SWITCH_V), \ 122 | f(OP_SWITCH_S), \ 123 | f(OP_SWITCH_E), \ 124 | f(OP_SWITCH_FNC), \ 125 | \ 126 | f(OP_CASE), \ 127 | f(OP_CASERANGE), \ 128 | \ 129 | f(OP_CALL1H),\ 130 | f(OP_CALL2H),\ 131 | f(OP_CALL3H),\ 132 | f(OP_CALL4H),\ 133 | f(OP_CALL5H),\ 134 | f(OP_CALL6H),\ 135 | f(OP_CALL7H),\ 136 | f(OP_CALL8H),\ 137 | \ 138 | f(OP_STORE_I), \ 139 | f(OP_STORE_IF), \ 140 | f(OP_STORE_FI), \ 141 | \ 142 | f(OP_ADD_I), \ 143 | f(OP_ADD_FI), \ 144 | f(OP_ADD_IF), \ 145 | \ 146 | f(OP_SUB_I), \ 147 | f(OP_SUB_FI), \ 148 | f(OP_SUB_IF), \ 149 | \ 150 | f(OP_CONV_ITOF), \ 151 | f(OP_CONV_FTOI), \ 152 | f(OP_CP_ITOF), \ 153 | f(OP_CP_FTOI), \ 154 | f(OP_LOAD_I), \ 155 | f(OP_STOREP_I), \ 156 | f(OP_STOREP_IF), \ 157 | f(OP_STOREP_FI), \ 158 | \ 159 | f(OP_BITAND_I), \ 160 | f(OP_BITOR_I), \ 161 | \ 162 | f(OP_MUL_I), \ 163 | f(OP_DIV_I), \ 164 | f(OP_EQ_I), \ 165 | f(OP_NE_I), \ 166 | \ 167 | f(OP_IFNOT_S), \ 168 | f(OP_IF_S), \ 169 | \ 170 | f(OP_NOT_I), \ 171 | \ 172 | f(OP_DIV_VF), \ 173 | \ 174 | f(OP_BITXOR_I), \ 175 | f(OP_RSHIFT_I), \ 176 | f(OP_LSHIFT_I), \ 177 | \ 178 | f(OP_GLOBALADDRESS), \ 179 | f(OP_ADD_PIW), \ 180 | \ 181 | f(OP_LOADA_F), \ 182 | f(OP_LOADA_V), \ 183 | f(OP_LOADA_S), \ 184 | f(OP_LOADA_ENT), \ 185 | f(OP_LOADA_FLD), \ 186 | f(OP_LOADA_FNC), \ 187 | f(OP_LOADA_I), \ 188 | \ 189 | f(OP_STORE_P), \ 190 | f(OP_LOAD_P), \ 191 | \ 192 | f(OP_LOADP_F), \ 193 | f(OP_LOADP_V), \ 194 | f(OP_LOADP_S), \ 195 | f(OP_LOADP_ENT), \ 196 | f(OP_LOADP_FLD), \ 197 | f(OP_LOADP_FNC), \ 198 | f(OP_LOADP_I), \ 199 | \ 200 | f(OP_LE_I), \ 201 | f(OP_GE_I), \ 202 | f(OP_LT_I), \ 203 | f(OP_GT_I), \ 204 | \ 205 | f(OP_LE_IF), \ 206 | f(OP_GE_IF), \ 207 | f(OP_LT_IF), \ 208 | f(OP_GT_IF), \ 209 | \ 210 | f(OP_LE_FI), \ 211 | f(OP_GE_FI), \ 212 | f(OP_LT_FI), \ 213 | f(OP_GT_FI), \ 214 | \ 215 | f(OP_EQ_IF), \ 216 | f(OP_EQ_FI), \ 217 | \ 218 | f(OP_ADD_SF), \ 219 | f(OP_SUB_S), \ 220 | f(OP_STOREP_C), \ 221 | f(OP_LOADP_C), \ 222 | \ 223 | f(OP_MUL_IF), \ 224 | f(OP_MUL_FI), \ 225 | f(OP_MUL_VI), \ 226 | f(OP_MUL_IV), \ 227 | f(OP_DIV_IF), \ 228 | f(OP_DIV_FI), \ 229 | f(OP_BITAND_IF), \ 230 | f(OP_BITOR_IF), \ 231 | f(OP_BITAND_FI), \ 232 | f(OP_BITOR_FI), \ 233 | f(OP_AND_I), \ 234 | f(OP_OR_I), \ 235 | f(OP_AND_IF), \ 236 | f(OP_OR_IF), \ 237 | f(OP_AND_FI), \ 238 | f(OP_OR_FI), \ 239 | f(OP_NE_IF), \ 240 | f(OP_NE_FI), \ 241 | \ 242 | f(OP_GSTOREP_I), \ 243 | f(OP_GSTOREP_F), \ 244 | f(OP_GSTOREP_ENT), \ 245 | f(OP_GSTOREP_FLD), \ 246 | f(OP_GSTOREP_S), \ 247 | f(OP_GSTOREP_FNC), \ 248 | f(OP_GSTOREP_V), \ 249 | f(OP_GADDRESS), \ 250 | f(OP_GLOAD_I), \ 251 | f(OP_GLOAD_F), \ 252 | f(OP_GLOAD_FLD), \ 253 | f(OP_GLOAD_ENT), \ 254 | f(OP_GLOAD_S), \ 255 | f(OP_GLOAD_FNC), \ 256 | \ 257 | f(OP_BOUNDCHECK), \ 258 | f(OP_UNUSED), \ 259 | f(OP_PUSH), \ 260 | f(OP_POP), \ 261 | \ 262 | f(OP_SWITCH_I), \ 263 | f(OP_GLOAD_V), \ 264 | f(OP_IF_F), \ 265 | f(OP_IFNOT_F), \ 266 | \ 267 | f(OP_STOREF_V), \ 268 | f(OP_STOREF_F), \ 269 | f(OP_STOREF_S), \ 270 | f(OP_STOREF_I), \ 271 | \ 272 | f(OP_STOREP_B), \ 273 | f(OP_LOADP_B), \ 274 | \ 275 | f(OP_INTRIN_SQRT), \ 276 | f(OP_INTRIN_SIN), \ 277 | f(OP_INTRIN_COS), \ 278 | \ 279 | f(OP_NUMOPS) 280 | 281 | enum 282 | { 283 | #define U(n) n 284 | OPLIST(U) 285 | #undef U 286 | }; 287 | 288 | static const char *opcode_names[] = 289 | { 290 | #define U(n) #n 291 | OPLIST(U) 292 | #undef U 293 | }; 294 | 295 | #define OP_BREAKPOINT 0x10000000 296 | #endif 297 | 298 | #ifndef OPCODES_ONLY 299 | #include "vm_opcodes.c.h" 300 | #endif -------------------------------------------------------------------------------- /vm_string.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | #include "vm_string.h" 4 | 5 | enum { NUM_ROTATING_BUFFERS = 4 }; 6 | 7 | typedef struct 8 | { 9 | char *buffer; 10 | size_t length; 11 | } qcvm_temp_buffer_t; 12 | 13 | static qcvm_temp_buffer_t qcvm_buffers[NUM_ROTATING_BUFFERS]; 14 | static int32_t buffer_index = 0; 15 | 16 | char *qcvm_temp_buffer(const qcvm_t *vm, const size_t len) 17 | { 18 | qcvm_temp_buffer_t *buf = &qcvm_buffers[buffer_index]; 19 | buffer_index = (buffer_index + 1) % NUM_ROTATING_BUFFERS; 20 | 21 | if (len > buf->length) 22 | { 23 | buf->length = len; 24 | if (buf->buffer) 25 | qcvm_mem_free(vm, buf->buffer); 26 | qcvm_debug(vm, "Temp buffer %i expanded to %u\n", buffer_index, buf->length); 27 | buf->buffer = (char *)qcvm_alloc(vm, buf->length + 1); 28 | } 29 | 30 | return buf->buffer; 31 | } 32 | 33 | /* 34 | ============ 35 | va 36 | 37 | does a varargs printf into a temp buffer, so I don't need to have 38 | varargs versions of all text functions. 39 | ============ 40 | */ 41 | char *qcvm_temp_format(const qcvm_t *vm, const char *format, ...) 42 | { 43 | va_list argptr; 44 | 45 | va_start(argptr, format); 46 | const int32_t my_index = buffer_index; 47 | qcvm_temp_buffer_t *buf = &qcvm_buffers[my_index]; 48 | char *buffer = qcvm_temp_buffer(vm, MAX_INFO_STRING); 49 | const size_t needed_to_write = vsnprintf(buffer, buf->length, format, argptr) + 1; 50 | 51 | if (needed_to_write > buf->length) 52 | { 53 | buffer_index = my_index; 54 | buffer = qcvm_temp_buffer(vm, needed_to_write - 1); 55 | vsnprintf(buffer, needed_to_write, format, argptr); 56 | } 57 | va_end(argptr); 58 | return buffer; 59 | } 60 | 61 | static void QC_va(qcvm_t *vm) 62 | { 63 | const qcvm_string_t fmtid = qcvm_argv_string_id(vm, 0); 64 | qcvm_return_string(vm, qcvm_parse_format(fmtid, vm, 1)); 65 | } 66 | 67 | static void QC_stoi(qcvm_t *vm) 68 | { 69 | const char *a = qcvm_argv_string(vm, 0); 70 | qcvm_return_int32(vm, strtol(a, NULL, 10)); 71 | } 72 | 73 | static void QC_stof(qcvm_t *vm) 74 | { 75 | const char *a = qcvm_argv_string(vm, 0); 76 | qcvm_return_float(vm, strtof(a, NULL)); 77 | } 78 | 79 | static void QC_strcmp(qcvm_t *vm) 80 | { 81 | const char *a = qcvm_argv_string(vm, 0); 82 | const char *b = qcvm_argv_string(vm, 1); 83 | qcvm_return_int32(vm, qcvm_strings_case_sensitive(vm) ? strcmp(a, b) : stricmp(a, b)); 84 | } 85 | 86 | static void QC_strncmp(qcvm_t *vm) 87 | { 88 | const char *a = qcvm_argv_string(vm, 0); 89 | const char *b = qcvm_argv_string(vm, 1); 90 | const int32_t c = qcvm_argv_int32(vm, 2); 91 | 92 | qcvm_return_int32(vm, qcvm_strings_case_sensitive(vm) ? strncmp(a, b, c) : strnicmp(a, b, c)); 93 | } 94 | 95 | static void QC_strlen(qcvm_t *vm) 96 | { 97 | const qcvm_string_t a = qcvm_argv_string_id(vm, 0); 98 | qcvm_return_int32(vm, (int32_t)qcvm_get_string_length(vm, a)); 99 | } 100 | 101 | static void QC_substr(qcvm_t *vm) 102 | { 103 | const qcvm_string_t strid = qcvm_argv_string_id(vm, 0); 104 | const int32_t start = qcvm_argv_int32(vm, 1); 105 | const size_t str_len = qcvm_get_string_length(vm, strid); 106 | size_t length = SIZE_MAX; 107 | 108 | if (start < 0 || start >= str_len) 109 | qcvm_error(vm, "invalid start to substr"); 110 | 111 | if (vm->state.argc >= 3) 112 | length = qcvm_argv_int32(vm, 2); 113 | 114 | length = minsz(str_len - start, length); 115 | 116 | char *buffer = qcvm_temp_buffer(vm, length); 117 | strncpy(buffer, qcvm_get_string(vm, strid) + start, length); 118 | buffer[length] = 0; 119 | qcvm_return_string(vm, buffer); 120 | } 121 | 122 | static void QC_strconcat(qcvm_t *vm) 123 | { 124 | if (vm->state.argc == 0) 125 | { 126 | qcvm_return_string_id(vm, STRING_EMPTY); 127 | return; 128 | } 129 | else if (vm->state.argc == 1) 130 | { 131 | qcvm_return_string_id(vm, qcvm_argv_string_id(vm, 0)); 132 | return; 133 | } 134 | 135 | const char *str = ""; 136 | 137 | for (int32_t i = 0; i < vm->state.argc; i++) 138 | str = qcvm_temp_format(vm, "%s%s", str, qcvm_argv_string(vm, i)); 139 | 140 | qcvm_return_string(vm, str); 141 | } 142 | 143 | static void QC_strstr(qcvm_t *vm) 144 | { 145 | const char *a = qcvm_argv_string(vm, 0); 146 | const char *b = qcvm_argv_string(vm, 1); 147 | const char *c = strstr(a, b); 148 | 149 | qcvm_return_int32(vm, c == NULL ? -1 : (int32_t)(c - a)); 150 | } 151 | 152 | static void QC_strchr(qcvm_t *vm) 153 | { 154 | const char *a = qcvm_argv_string(vm, 0); 155 | const int32_t b = qcvm_argv_int32(vm, 1); 156 | const char *c = strchr(a, b); 157 | 158 | qcvm_return_int32(vm, c == NULL ? -1 : (int32_t)(c - a)); 159 | } 160 | 161 | static void QC_chrlwr(qcvm_t *vm) 162 | { 163 | char a = qcvm_argv_int32(vm, 0); 164 | qcvm_return_int32(vm, tolower(a)); 165 | } 166 | 167 | static void QC_chrupr(qcvm_t *vm) 168 | { 169 | char a = qcvm_argv_int32(vm, 0); 170 | qcvm_return_int32(vm, toupper(a)); 171 | } 172 | 173 | static void QC_strlwr(qcvm_t *vm) 174 | { 175 | const qcvm_string_t a = qcvm_argv_string_id(vm, 0); 176 | const size_t length = qcvm_get_string_length(vm, a); 177 | 178 | char *buffer = qcvm_temp_buffer(vm, length); 179 | strncpy(buffer, qcvm_get_string(vm, a), length); 180 | buffer[length] = 0; 181 | 182 | for (size_t i = 0; i < length; i++) 183 | buffer[i] = tolower(buffer[i]); 184 | 185 | qcvm_return_string(vm, buffer); 186 | } 187 | 188 | static void QC_strupr(qcvm_t *vm) 189 | { 190 | const qcvm_string_t a = qcvm_argv_string_id(vm, 0); 191 | const size_t length = qcvm_get_string_length(vm, a); 192 | 193 | char *buffer = qcvm_temp_buffer(vm, length); 194 | strncpy(buffer, qcvm_get_string(vm, a), length); 195 | buffer[length] = 0; 196 | 197 | for (size_t i = 0; i < length; i++) 198 | buffer[i] = toupper(buffer[i]); 199 | 200 | qcvm_return_string(vm, buffer); 201 | } 202 | 203 | #include 204 | 205 | static void QC_localtime(qcvm_t *vm) 206 | { 207 | static struct tm empty_ltime; 208 | time_t gmtime = time(NULL); 209 | const struct tm *ltime = localtime(&gmtime); 210 | 211 | if (!ltime) 212 | ltime = &empty_ltime; 213 | 214 | qcvm_set_global_typed_ptr(struct tm, vm, GLOBAL_PARM0, ltime); 215 | } 216 | 217 | void qcvm_init_string_builtins(qcvm_t *vm) 218 | { 219 | qcvm_register_builtin(va); 220 | 221 | qcvm_register_builtin(stoi); 222 | qcvm_register_builtin(stof); 223 | 224 | qcvm_register_builtin(strcmp); 225 | qcvm_register_builtin(strlen); 226 | qcvm_register_builtin(substr); 227 | qcvm_register_builtin(strncmp); 228 | qcvm_register_builtin(strconcat); 229 | qcvm_register_builtin(strstr); 230 | qcvm_register_builtin(strchr); 231 | qcvm_register_builtin(chrlwr); 232 | qcvm_register_builtin(chrupr); 233 | qcvm_register_builtin(strlwr); 234 | qcvm_register_builtin(strupr); 235 | 236 | qcvm_register_builtin(localtime); 237 | } -------------------------------------------------------------------------------- /vm_string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | char *qcvm_temp_buffer(const qcvm_t *vm, const size_t len); 4 | char *qcvm_temp_format(const qcvm_t *vm, const char *format, ...); 5 | 6 | void qcvm_init_string_builtins(qcvm_t *vm); -------------------------------------------------------------------------------- /vm_string_list.c: -------------------------------------------------------------------------------- 1 | #define QCVM_INTERNAL 2 | #include "shared/shared.h" 3 | #include "vm.h" 4 | #include "vm_string.h" 5 | 6 | qcvm_string_t qcvm_string_list_store(qcvm_t *vm, const char *str, const size_t len) 7 | { 8 | qcvm_string_list_t *list = &vm->dynamic_strings; 9 | int32_t index; 10 | 11 | if (list->free_indices_size) 12 | index = (-list->free_indices[--list->free_indices_size]) - 1; 13 | else 14 | { 15 | if (list->strings_size == list->strings_allocated) 16 | { 17 | list->strings_allocated += REF_STRING_RESERVE; 18 | qcvm_ref_counted_string_t *old_strings = list->strings; 19 | list->strings = (qcvm_ref_counted_string_t *)qcvm_alloc(vm, sizeof(qcvm_ref_counted_string_t) * list->strings_allocated); 20 | if (old_strings) 21 | { 22 | memcpy(list->strings, old_strings, sizeof(qcvm_ref_counted_string_t) * list->strings_size); 23 | qcvm_mem_free(vm, old_strings); 24 | } 25 | qcvm_debug(vm, "Increased ref string storage to %u due to \"%s\"\n", list->strings_allocated, str); 26 | } 27 | 28 | index = (int32_t)list->strings_size++; 29 | } 30 | 31 | list->strings[index] = (qcvm_ref_counted_string_t) { 32 | str, 33 | len, 34 | 0 35 | }; 36 | 37 | return (qcvm_string_t)(-(index + 1)); 38 | } 39 | 40 | void qcvm_string_list_unstore(qcvm_t *vm, const qcvm_string_t id) 41 | { 42 | qcvm_string_list_t *list = &vm->dynamic_strings; 43 | const int32_t index = (int32_t)(-id) - 1; 44 | 45 | assert(index >= 0 && index < list->strings_size); 46 | 47 | qcvm_ref_counted_string_t *str = &list->strings[index]; 48 | 49 | assert(!str->ref_count); 50 | 51 | qcvm_mem_free(vm, (void *)str->str); 52 | 53 | *str = (qcvm_ref_counted_string_t) { NULL, 0, 0 }; 54 | 55 | if (list->free_indices_size == list->free_indices_allocated) 56 | { 57 | list->free_indices_allocated += FREE_STRING_RESERVE; 58 | qcvm_string_t *old_free_indices = list->free_indices; 59 | list->free_indices = (qcvm_string_t *)qcvm_alloc(vm, sizeof(qcvm_string_t) * list->free_indices_allocated); 60 | if (old_free_indices) 61 | { 62 | memcpy(list->free_indices, old_free_indices, sizeof(qcvm_string_t) * list->free_indices_size); 63 | qcvm_mem_free(vm, old_free_indices); 64 | } 65 | qcvm_debug(vm, "Increased free string storage to %u\n", list->free_indices_allocated); 66 | } 67 | 68 | list->free_indices[list->free_indices_size++] = id; 69 | } 70 | 71 | static size_t qcvm_string_list_get_length(const qcvm_t *vm, const qcvm_string_t id) 72 | { 73 | const qcvm_string_list_t *list = &vm->dynamic_strings; 74 | const int32_t index = (int32_t)(-id) - 1; 75 | assert(index >= 0 && index < list->strings_size); 76 | return list->strings[index].length; 77 | } 78 | 79 | const char *qcvm_string_list_get(const qcvm_t *vm, const qcvm_string_t id) 80 | { 81 | const qcvm_string_list_t *list = &vm->dynamic_strings; 82 | const int32_t index = (int32_t)(-id) - 1; 83 | assert(index >= 0 && index < list->strings_size); 84 | assert(list->strings[index].str); 85 | return list->strings[index].str; 86 | } 87 | 88 | void qcvm_string_list_acquire(qcvm_t *vm, const qcvm_string_t id) 89 | { 90 | qcvm_string_list_t *list = &vm->dynamic_strings; 91 | START_TIMER(vm, StringAcquire); 92 | 93 | const int32_t index = (int32_t)(-id) - 1; 94 | 95 | assert(index >= 0 && index < list->strings_size); 96 | 97 | list->strings[index].ref_count++; 98 | 99 | END_TIMER(vm, PROFILE_TIMERS); 100 | } 101 | 102 | void qcvm_string_list_release(qcvm_t *vm, const qcvm_string_t id) 103 | { 104 | qcvm_string_list_t *list = &vm->dynamic_strings; 105 | START_TIMER(vm, StringRelease); 106 | 107 | const int32_t index = (int32_t)(-id) - 1; 108 | 109 | assert(index >= 0 && index < list->strings_size); 110 | 111 | qcvm_ref_counted_string_t *str = &list->strings[index]; 112 | 113 | assert(str->ref_count); 114 | 115 | str->ref_count--; 116 | 117 | if (!str->ref_count) 118 | qcvm_string_list_unstore(vm, id); 119 | 120 | END_TIMER(vm, PROFILE_TIMERS); 121 | } 122 | 123 | static void qcvm_string_list_ref_link(qcvm_t *vm, uint32_t hash, const qcvm_string_t id, const void *ptr) 124 | { 125 | qcvm_string_list_t *list = &vm->dynamic_strings; 126 | 127 | // see if we need to expand the hashes list 128 | if (!list->ref_storage_free) 129 | { 130 | const size_t old_size = list->ref_storage_allocated; 131 | list->ref_storage_allocated = Q_next_pow2(list->ref_storage_allocated + (REF_STRING_RESERVE * 2)); 132 | 133 | qcvm_debug(vm, "Increased ref string pointer storage to %u due to \"%s\"\n", list->ref_storage_allocated, qcvm_get_string(vm, id)); 134 | 135 | qcvm_ref_storage_hash_t *old_ref_storage_data = list->ref_storage_data; 136 | list->ref_storage_data = (qcvm_ref_storage_hash_t*)qcvm_alloc(vm, sizeof(qcvm_ref_storage_hash_t) * list->ref_storage_allocated); 137 | 138 | list->ref_storage_free = NULL; 139 | 140 | if (old_ref_storage_data) 141 | { 142 | memcpy(list->ref_storage_data, old_ref_storage_data, sizeof(qcvm_ref_storage_hash_t) * old_size); 143 | qcvm_mem_free(vm, old_ref_storage_data); 144 | } 145 | 146 | if (list->ref_storage_hashes) 147 | qcvm_mem_free(vm, list->ref_storage_hashes); 148 | 149 | list->ref_storage_hashes = (qcvm_ref_storage_hash_t**)qcvm_alloc(vm, sizeof(qcvm_ref_storage_hash_t*) * list->ref_storage_allocated); 150 | 151 | // re-hash since hashs changed 152 | for (qcvm_ref_storage_hash_t *h = list->ref_storage_data; h < list->ref_storage_data + list->ref_storage_allocated; h++) 153 | { 154 | // this is a free pointer, so link us into the free ptr list 155 | if (!h->ptr) 156 | { 157 | h->hash_next = list->ref_storage_free; 158 | 159 | if (list->ref_storage_free) 160 | list->ref_storage_free->hash_prev = h; 161 | 162 | list->ref_storage_free = h; 163 | continue; 164 | } 165 | 166 | h->hash_value = Q_hash_pointer((uint32_t)h->ptr, list->ref_storage_allocated); 167 | 168 | // we have to set these up next pass since we can't really tell if 169 | // the pointer is good or not 170 | h->hash_next = h->hash_prev = NULL; 171 | } 172 | 173 | // re-link hash buckets 174 | for (qcvm_ref_storage_hash_t *h = list->ref_storage_data; h < list->ref_storage_data + list->ref_storage_allocated; h++) 175 | { 176 | if (!h->ptr) 177 | continue; 178 | 179 | h->hash_next = list->ref_storage_hashes[h->hash_value]; 180 | 181 | if (h->hash_next) 182 | h->hash_next->hash_prev = h; 183 | 184 | list->ref_storage_hashes[h->hash_value] = h; 185 | } 186 | 187 | // re-hash, because wrap changes 188 | hash = Q_hash_pointer((uint32_t)ptr, list->ref_storage_allocated); 189 | } 190 | 191 | // pop a free pointer off the free list 192 | qcvm_ref_storage_hash_t *hashed = list->ref_storage_free; 193 | 194 | list->ref_storage_free = list->ref_storage_free->hash_next; 195 | 196 | if (list->ref_storage_free) 197 | list->ref_storage_free->hash_prev = NULL; 198 | 199 | hashed->hash_next = hashed->hash_prev = NULL; 200 | 201 | // hash us in 202 | hashed->ptr = ptr; 203 | hashed->id = id; 204 | #ifdef _DEBUG 205 | if (vm->state.current >= 0) 206 | hashed->stack = vm->state.stack[vm->state.current]; 207 | #endif 208 | hashed->hash_value = hash; 209 | qcvm_ref_storage_hash_t *old_head = list->ref_storage_hashes[hash]; 210 | hashed->hash_next = old_head; 211 | if (old_head) 212 | old_head->hash_prev = hashed; 213 | list->ref_storage_hashes[hash] = hashed; 214 | 215 | list->ref_storage_stored++; 216 | } 217 | 218 | #ifdef _DEBUG 219 | void qcvm_string_list_dump_refs(FILE *fp, qcvm_t *vm) 220 | { 221 | qcvm_string_list_t *list = &vm->dynamic_strings; 222 | 223 | fprintf(fp, "strings: %" PRIuPTR " / %" PRIuPTR " (%" PRIuPTR " / %" PRIuPTR " free indices)\n", list->strings_size, list->strings_allocated, list->free_indices_size, list->free_indices_allocated); 224 | fprintf(fp, "ref pointers: %" PRIuPTR " stored / %" PRIuPTR "\n", list->ref_storage_stored, list->ref_storage_allocated); 225 | 226 | for (qcvm_ref_counted_string_t *str = list->strings; str < list->strings + list->strings_size; str++) 227 | { 228 | if (!str->str) 229 | continue; 230 | 231 | const qcvm_string_t id = (qcvm_string_t) -((str - list->strings) + 1); 232 | 233 | fprintf(fp, "%i\t%s\t%" PRIuPTR "\n", id, str->str, str->ref_count); 234 | 235 | for (qcvm_ref_storage_hash_t *hashed = list->ref_storage_data; hashed < list->ref_storage_data + list->ref_storage_allocated; hashed++) 236 | { 237 | if (!hashed->ptr) 238 | continue; 239 | else if (hashed->id != id) 240 | continue; 241 | 242 | const qcvm_string_t current_id = *(qcvm_string_t *)(hashed->ptr); 243 | const bool still_has_string = current_id == hashed->id; 244 | 245 | fprintf(fp, "\t%s\t%s\t%s (%u)\n", qcvm_dump_pointer(vm, (const qcvm_global_t *)hashed->ptr), qcvm_stack_entry(vm, &hashed->stack, false), still_has_string ? "valid" : "invalid", current_id); 246 | } 247 | } 248 | } 249 | #endif 250 | 251 | static void qcvm_string_list_ref_unlink(qcvm_t *vm, qcvm_ref_storage_hash_t *hashed) 252 | { 253 | qcvm_string_list_t *list = &vm->dynamic_strings; 254 | 255 | // if we were the head, swap us out first 256 | if (list->ref_storage_hashes[hashed->hash_value] == hashed) 257 | list->ref_storage_hashes[hashed->hash_value] = hashed->hash_next; 258 | 259 | // unlink hashed 260 | if (hashed->hash_prev) 261 | hashed->hash_prev->hash_next = hashed->hash_next; 262 | 263 | if (hashed->hash_next) 264 | hashed->hash_next->hash_prev = hashed->hash_prev; 265 | 266 | // put into free list 267 | hashed->ptr = NULL; 268 | hashed->id = 0; 269 | hashed->hash_next = list->ref_storage_free; 270 | hashed->hash_prev = NULL; 271 | if (list->ref_storage_free) 272 | list->ref_storage_free->hash_prev = hashed; 273 | list->ref_storage_free = hashed; 274 | 275 | list->ref_storage_stored--; 276 | } 277 | 278 | static inline qcvm_ref_storage_hash_t *qcvm_string_list_get_storage_hash(qcvm_t *vm, const uint32_t hash) 279 | { 280 | qcvm_string_list_t *list = &vm->dynamic_strings; 281 | 282 | if (!list->ref_storage_stored) 283 | return NULL; 284 | 285 | return list->ref_storage_hashes[hash]; 286 | } 287 | 288 | void qcvm_string_list_mark_ref_copy(qcvm_t *vm, const qcvm_string_t id, const void *ptr) 289 | { 290 | qcvm_string_list_t *list = &vm->dynamic_strings; 291 | START_TIMER(vm, StringMark); 292 | 293 | uint32_t hash = Q_hash_pointer((uint32_t)ptr, list->ref_storage_allocated); 294 | qcvm_ref_storage_hash_t *hashed = qcvm_string_list_get_storage_hash(vm, hash); 295 | 296 | for (; hashed; hashed = hashed->hash_next) 297 | { 298 | if (hashed->ptr == ptr) 299 | { 300 | // it's *possible* for a seemingly no-op to occur in some cases 301 | // (for instance, a call into function which copies PARM0 into locals+0, then 302 | // copies locals+0 back into PARM0 for calling a function). because PARM0 303 | // doesn't release its ref until its value changes, we treat this as a no-op. 304 | // if we released every time the value changes (even to the same value it already 305 | // had) this would effectively be the same behavior. 306 | if (id == hashed->id) 307 | { 308 | END_TIMER(vm, PROFILE_TIMERS); 309 | return; 310 | } 311 | 312 | // we're stomping over another string, so unlink us 313 | qcvm_string_list_ref_unlink(vm, hashed); 314 | break; 315 | } 316 | } 317 | 318 | // increase ref count 319 | qcvm_string_list_acquire(vm, id); 320 | 321 | // link! 322 | qcvm_string_list_ref_link(vm, hash, id, ptr); 323 | 324 | END_TIMER(vm, PROFILE_TIMERS); 325 | } 326 | 327 | bool qcvm_string_list_check_ref_unset(qcvm_t *vm, const void *ptr, const size_t span, const bool assume_changed) 328 | { 329 | qcvm_string_list_t *list = &vm->dynamic_strings; 330 | START_TIMER(vm, StringCheckUnset); 331 | 332 | bool any_unset = false; 333 | 334 | for (size_t i = 0; i < span; i++) 335 | { 336 | const qcvm_global_t *gptr = (const qcvm_global_t *)ptr + i; 337 | 338 | qcvm_ref_storage_hash_t *hashed = qcvm_string_list_get_storage_hash(vm, Q_hash_pointer((uint32_t)gptr, list->ref_storage_allocated)); 339 | 340 | for (; hashed; hashed = hashed->hash_next) 341 | if (hashed->ptr == gptr) 342 | break; 343 | 344 | if (!hashed) 345 | continue; 346 | 347 | const qcvm_string_t old = hashed->id; 348 | 349 | if (!assume_changed) 350 | { 351 | const qcvm_string_t newstr = *(const qcvm_string_t *)gptr; 352 | 353 | // still here, so we probably just copied to ourselves or something 354 | if (newstr == old) 355 | continue; 356 | } 357 | 358 | // not here! release and unmark 359 | qcvm_string_list_release(vm, old); 360 | 361 | // unlink 362 | qcvm_string_list_ref_unlink(vm, hashed); 363 | 364 | any_unset = true; 365 | } 366 | 367 | END_TIMER(vm, PROFILE_TIMERS); 368 | 369 | return any_unset; 370 | } 371 | 372 | qcvm_string_t *qcvm_string_list_has_ref(qcvm_t *vm, const void *ptr, qcvm_ref_storage_hash_t **hashed_ptr) 373 | { 374 | qcvm_string_list_t *list = &vm->dynamic_strings; 375 | START_TIMER(vm, StringHasRef); 376 | 377 | qcvm_ref_storage_hash_t *hashed = qcvm_string_list_get_storage_hash(vm, Q_hash_pointer((uint32_t)ptr, list->ref_storage_allocated)), *next; 378 | 379 | for (; hashed; hashed = next) 380 | { 381 | next = hashed->hash_next; 382 | 383 | if (hashed->ptr == ptr) 384 | { 385 | qcvm_string_t *rv = &hashed->id; 386 | 387 | END_TIMER(vm, PROFILE_TIMERS); 388 | 389 | if (hashed_ptr) 390 | *hashed_ptr = hashed; 391 | 392 | return rv; 393 | } 394 | } 395 | 396 | END_TIMER(vm, PROFILE_TIMERS); 397 | return NULL; 398 | } 399 | 400 | void qcvm_string_list_mark_refs_copied(qcvm_t *vm, const void *src, const void *dst, const size_t span) 401 | { 402 | START_TIMER(vm, StringMarkRefsCopied); 403 | 404 | // grab list of fields that have strings 405 | for (size_t i = 0; i < span; i++) 406 | { 407 | const qcvm_global_t *sptr = (const qcvm_global_t *)src + i; 408 | qcvm_string_t *sstr = qcvm_string_list_has_ref(vm, sptr, NULL); 409 | 410 | const qcvm_global_t *dptr = (const qcvm_global_t *)dst + i; 411 | qcvm_ref_storage_hash_t *hashed; 412 | qcvm_string_t *dstr = qcvm_string_list_has_ref(vm, dptr, &hashed); 413 | 414 | // dst already has a string, check if it's the same ID 415 | if (dstr) 416 | { 417 | // we're copying same string, so just skip 418 | if (sstr && *sstr == *dstr) 419 | continue; 420 | 421 | // different strings, unref us 422 | qcvm_string_list_release(vm, *dstr); 423 | qcvm_string_list_ref_unlink(vm, hashed); 424 | } 425 | 426 | // no new string, so keep going 427 | if (!sstr) 428 | continue; 429 | 430 | // mark them as being inside of src as well now 431 | qcvm_string_list_mark_ref_copy(vm, *sstr, dptr); 432 | } 433 | 434 | END_TIMER(vm, PROFILE_TIMERS); 435 | } 436 | 437 | bool qcvm_string_list_is_ref_counted(qcvm_t *vm, const qcvm_string_t id) 438 | { 439 | qcvm_string_list_t *list = &vm->dynamic_strings; 440 | const int32_t index = (int32_t)(-id) - 1; 441 | return index < list->strings_size && list->strings[index].str; 442 | } 443 | 444 | qcvm_string_backup_t qcvm_string_list_pop_ref(qcvm_t *vm, const void *ptr) 445 | { 446 | qcvm_string_list_t *list = &vm->dynamic_strings; 447 | START_TIMER(vm, StringPopRef); 448 | 449 | qcvm_ref_storage_hash_t *hashed = qcvm_string_list_get_storage_hash(vm, Q_hash_pointer((uint32_t)ptr, list->ref_storage_allocated)); 450 | 451 | for (; hashed; hashed = hashed->hash_next) 452 | if (hashed->ptr == ptr) 453 | break; 454 | 455 | assert(hashed); 456 | 457 | const qcvm_string_t id = hashed->id; 458 | 459 | const qcvm_string_backup_t popped_ref = (qcvm_string_backup_t) { ptr, id }; 460 | 461 | qcvm_string_list_ref_unlink(vm, hashed); 462 | 463 | END_TIMER(vm, PROFILE_TIMERS); 464 | 465 | return popped_ref; 466 | } 467 | 468 | void qcvm_string_list_push_ref(qcvm_t *vm, const qcvm_string_backup_t *backup) 469 | { 470 | qcvm_string_list_t *list = &vm->dynamic_strings; 471 | START_TIMER(vm, StringPushRef); 472 | 473 | const uint32_t hash = Q_hash_pointer((uint32_t)backup->ptr, list->ref_storage_allocated); 474 | qcvm_ref_storage_hash_t *hashed = qcvm_string_list_get_storage_hash(vm, hash); 475 | 476 | for (; hashed; hashed = hashed->hash_next) 477 | { 478 | if (hashed->ptr == backup->ptr) 479 | { 480 | // somebody stole our ptr >:( 481 | if (backup->id == hashed->id) 482 | { 483 | // ..oh maybe it was us. no-op! 484 | END_TIMER(vm, PROFILE_TIMERS); 485 | return; 486 | } 487 | 488 | qcvm_string_list_release(vm, hashed->id); 489 | qcvm_string_list_ref_unlink(vm, hashed); 490 | break; 491 | } 492 | } 493 | 494 | const int32_t index = (int32_t)(-backup->id) - 1; 495 | 496 | // simple restore 497 | if ((index >= 0 && index < list->strings_size) && list->strings[index].str) 498 | { 499 | qcvm_string_list_ref_link(vm, hash, backup->id, backup->ptr); 500 | END_TIMER(vm, PROFILE_TIMERS); 501 | return; 502 | } 503 | 504 | qcvm_error(vm, "unable to push string backup"); 505 | } 506 | 507 | void qcvm_string_list_write_state(qcvm_t *vm, FILE *fp) 508 | { 509 | qcvm_string_list_t *list = &vm->dynamic_strings; 510 | 511 | for (qcvm_ref_counted_string_t *s = list->strings; s < list->strings + list->strings_size; s++) 512 | { 513 | if (!s->str) 514 | continue; 515 | 516 | fwrite(&s->length, sizeof(s->length), 1, fp); 517 | fwrite(s->str, sizeof(char), s->length, fp); 518 | } 519 | 520 | const size_t len = 0; 521 | fwrite(&len, sizeof(len), 1, fp); 522 | } 523 | 524 | void qcvm_string_list_read_state(qcvm_t *vm, FILE *fp) 525 | { 526 | while (true) 527 | { 528 | size_t len; 529 | 530 | fread(&len, sizeof(len), 1, fp); 531 | 532 | if (!len) 533 | break; 534 | 535 | char *s = qcvm_temp_buffer(vm, len); 536 | fread(s, sizeof(char), len, fp); 537 | s[len] = 0; 538 | 539 | // does not acquire, since entity/game state does that itself 540 | qcvm_store_or_find_string(vm, s, len, true); 541 | } 542 | } 543 | 544 | qcvm_string_t qcvm_set_string_ptr(qcvm_t *vm, void *ptr, const char *value, const size_t len, const bool copy) 545 | { 546 | qcvm_string_t str = qcvm_store_or_find_string(vm, value, len, copy); 547 | *(qcvm_string_t *)ptr = str; 548 | qcvm_string_list_check_ref_unset(vm, ptr, sizeof(qcvm_string_t) / sizeof(qcvm_global_t), false); 549 | qcvm_field_wrap_list_check_set(vm, ptr, sizeof(qcvm_string_t) / sizeof(qcvm_global_t)); 550 | 551 | if (qcvm_string_list_is_ref_counted(vm, str)) 552 | qcvm_string_list_mark_ref_copy(vm, str, ptr); 553 | 554 | return str; 555 | } 556 | 557 | bool qcvm_find_string(qcvm_t *vm, const char *value, qcvm_string_t *rstr) 558 | { 559 | START_TIMER(vm, StringFind); 560 | 561 | *rstr = STRING_EMPTY; 562 | 563 | if (!value || !*value) 564 | { 565 | END_TIMER(vm, PROFILE_TIMERS); 566 | return true; 567 | } 568 | 569 | // check built-ins 570 | const uint32_t hash = Q_hash_string(value, vm->string_size); 571 | 572 | for (qcvm_string_hash_t *hashed = vm->string_hashes[hash]; hashed; hashed = hashed->hash_next) 573 | { 574 | if (!strcmp(hashed->str, value)) 575 | { 576 | *rstr = (qcvm_string_t)(hashed->str - vm->string_data); 577 | END_TIMER(vm, PROFILE_TIMERS); 578 | return true; 579 | } 580 | } 581 | 582 | // check dynamic strings. 583 | // Note that this search is not hashed... yet. 584 | for (qcvm_ref_counted_string_t *s = vm->dynamic_strings.strings; s < vm->dynamic_strings.strings + vm->dynamic_strings.strings_size; s++) 585 | { 586 | if (!s->str) 587 | continue; 588 | 589 | const char *str = s->str; 590 | 591 | if (str && strcmp(value, str) == 0) 592 | { 593 | *rstr = (qcvm_string_t) -((s - vm->dynamic_strings.strings) + 1); 594 | END_TIMER(vm, PROFILE_TIMERS); 595 | return true; 596 | } 597 | } 598 | 599 | END_TIMER(vm, PROFILE_TIMERS); 600 | return false; 601 | } 602 | 603 | qcvm_string_t qcvm_store_or_find_string(qcvm_t *vm, const char *value, const size_t len, const bool copy) 604 | { 605 | if (!len) 606 | return 0; 607 | 608 | // check built-ins 609 | qcvm_string_t str; 610 | 611 | if (qcvm_find_string(vm, value, &str)) 612 | return str; 613 | 614 | if (copy) 615 | { 616 | char *strcopy = (char *)qcvm_alloc(vm, (sizeof(char) * len) + 1); 617 | memcpy(strcopy, value, sizeof(char) * len); 618 | strcopy[len] = 0; 619 | value = strcopy; 620 | } 621 | 622 | return qcvm_string_list_store(vm, value, len); 623 | } 624 | 625 | const char *qcvm_get_string(const qcvm_t *vm, const qcvm_string_t str) 626 | { 627 | if (str < 0) 628 | return qcvm_string_list_get(vm, str); 629 | else if ((size_t)str >= vm->string_size) 630 | qcvm_error(vm, "bad string"); 631 | 632 | return vm->string_data + (size_t)str; 633 | } 634 | 635 | size_t qcvm_get_string_length(const qcvm_t *vm, const qcvm_string_t str) 636 | { 637 | if (str < 0) 638 | return qcvm_string_list_get_length(vm, str); 639 | else if ((size_t)str >= vm->string_size) 640 | qcvm_error(vm, "bad string"); 641 | 642 | return vm->string_lengths[(size_t)str]; 643 | } -------------------------------------------------------------------------------- /vm_structlist.c: -------------------------------------------------------------------------------- 1 | #include "shared/shared.h" 2 | #include "vm.h" 3 | #include "vm_structlist.h" 4 | #include "vm_math.h" 5 | 6 | #define STRUCTLIST_RESERVE 32 7 | 8 | typedef struct 9 | { 10 | void *values; 11 | size_t element_size; 12 | size_t size, allocated; 13 | } qcvm_structlist_t; 14 | 15 | static void qcvm_structlist_free(qcvm_t *vm, void *handle) 16 | { 17 | qcvm_structlist_t *list = (qcvm_structlist_t *)handle; 18 | if (list->values) 19 | qcvm_mem_free(vm, list->values); 20 | } 21 | 22 | static bool qcvm_structlist_resolve_pointer(const qcvm_t *vm, void *handle, const size_t offset, const size_t len, void **address) 23 | { 24 | qcvm_structlist_t *list = (qcvm_structlist_t *)handle; 25 | 26 | if ((offset + len) <= list->size * list->element_size) 27 | { 28 | if (address) 29 | *address = (uint8_t *)list->values + offset; 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | static const qcvm_handle_descriptor_t structlist_descriptor = 36 | { 37 | .free = qcvm_structlist_free, 38 | .resolve_pointer = qcvm_structlist_resolve_pointer 39 | }; 40 | 41 | static void QC_structlist_alloc(qcvm_t *vm) 42 | { 43 | qcvm_structlist_t *list = (qcvm_structlist_t *)qcvm_alloc(vm, sizeof(qcvm_structlist_t)); 44 | list->element_size = qcvm_argv_int32(vm, 0); 45 | 46 | if (!list->element_size) 47 | qcvm_error(vm, "bad element size"); 48 | 49 | list->allocated = vm->state.argc > 1 ? qcvm_argv_int32(vm, 1) : STRUCTLIST_RESERVE; 50 | 51 | if (list->allocated) 52 | list->values = qcvm_alloc(vm, list->element_size * list->allocated); 53 | 54 | qcvm_return_handle(vm, list, &structlist_descriptor); 55 | } 56 | 57 | static inline void structlist_insert(qcvm_t *vm, qcvm_structlist_t *list, const qcvm_pointer_t value, const size_t index) 58 | { 59 | void *address; 60 | 61 | if (!qcvm_resolve_pointer(vm, value, false, list->element_size, &address)) 62 | qcvm_error(vm, "bad pointer"); 63 | 64 | if (index > list->size) 65 | qcvm_error(vm, "bad insert index"); 66 | 67 | // re-allocate 68 | if (list->size == list->allocated) 69 | { 70 | void *old_values = list->values; 71 | 72 | if (!list->allocated) 73 | list->allocated = STRUCTLIST_RESERVE; 74 | else 75 | list->allocated *= 2; 76 | 77 | list->values = qcvm_alloc(vm, list->element_size * list->allocated); 78 | 79 | if (old_values) 80 | { 81 | memcpy(list->values, old_values, list->element_size * list->size); 82 | qcvm_mem_free(vm, old_values); 83 | } 84 | } 85 | 86 | // shift the list to accomodate new entry 87 | if (index < list->size) 88 | { 89 | const size_t shift_size = (list->size - index) * list->element_size; 90 | memmove((uint8_t *)list->values + (list->element_size * (index + 1)), (uint8_t *)list->values + (list->element_size * index), shift_size); 91 | } 92 | 93 | memcpy((uint8_t *)list->values + (list->element_size * index), address, list->element_size); 94 | list->size++; 95 | } 96 | 97 | static void QC_structlist_insert(qcvm_t *vm) 98 | { 99 | qcvm_structlist_t *list = qcvm_argv_handle(qcvm_structlist_t, vm, 0); 100 | const qcvm_pointer_t value = qcvm_argv_pointer(vm, 1); 101 | const size_t index = qcvm_argv_int32(vm, 2); 102 | structlist_insert(vm, list, value, index); 103 | } 104 | 105 | static void QC_structlist_push(qcvm_t *vm) 106 | { 107 | qcvm_structlist_t *list = qcvm_argv_handle(qcvm_structlist_t, vm, 0); 108 | const qcvm_pointer_t value = qcvm_argv_pointer(vm, 1); 109 | structlist_insert(vm, list, value, list->size); 110 | } 111 | 112 | static void QC_structlist_unshift(qcvm_t *vm) 113 | { 114 | qcvm_structlist_t *list = qcvm_argv_handle(qcvm_structlist_t, vm, 0); 115 | const qcvm_pointer_t value = qcvm_argv_pointer(vm, 1); 116 | structlist_insert(vm, list, value, 0); 117 | } 118 | 119 | static inline void structlist_delete(qcvm_t *vm, qcvm_structlist_t *list, const size_t index, void *store) 120 | { 121 | if (index >= list->size) 122 | qcvm_error(vm, "bad delete index"); 123 | 124 | if (store) 125 | memcpy(store, (uint8_t *)list->values + (list->element_size * index), list->element_size); 126 | 127 | // shift if we have more than 1 128 | if (list->size > 1) 129 | { 130 | const size_t shift_size = (list->size - index - 1) * list->element_size; 131 | memmove((uint8_t *)list->values + (list->element_size * index), (uint8_t *)list->values + (list->element_size * index + 1), shift_size); 132 | } 133 | 134 | list->size--; 135 | } 136 | 137 | static void QC_structlist_delete(qcvm_t *vm) 138 | { 139 | qcvm_structlist_t *list = qcvm_argv_handle(qcvm_structlist_t, vm, 0); 140 | const size_t index = qcvm_argv_int32(vm, 1); 141 | void *store = NULL; 142 | 143 | if (vm->state.argc > 2) 144 | if (!qcvm_resolve_pointer(vm, qcvm_argv_pointer(vm, 2), false, list->element_size, &store)) 145 | qcvm_error(vm, "bad pointer"); 146 | 147 | structlist_delete(vm, list, index, store); 148 | } 149 | 150 | static void QC_structlist_pop(qcvm_t *vm) 151 | { 152 | qcvm_structlist_t *list = qcvm_argv_handle(qcvm_structlist_t, vm, 0); 153 | void *store = NULL; 154 | 155 | if (vm->state.argc > 1) 156 | if (!qcvm_resolve_pointer(vm, qcvm_argv_pointer(vm, 1), false, list->element_size, &store)) 157 | qcvm_error(vm, "bad pointer"); 158 | 159 | structlist_delete(vm, list, list->size - 1, store); 160 | } 161 | 162 | static void QC_structlist_shift(qcvm_t *vm) 163 | { 164 | qcvm_structlist_t *list = qcvm_argv_handle(qcvm_structlist_t, vm, 0); 165 | void *store = NULL; 166 | 167 | if (vm->state.argc > 1) 168 | if (!qcvm_resolve_pointer(vm, qcvm_argv_pointer(vm, 1), false, list->element_size, &store)) 169 | qcvm_error(vm, "bad pointer"); 170 | 171 | structlist_delete(vm, list, 0, store); 172 | } 173 | 174 | static void QC_structlist_get_length(qcvm_t *vm) 175 | { 176 | qcvm_structlist_t *list = qcvm_argv_handle(qcvm_structlist_t, vm, 0); 177 | qcvm_return_int32(vm, list->size); 178 | } 179 | 180 | static void QC_structlist_clear(qcvm_t *vm) 181 | { 182 | qcvm_structlist_t *list = qcvm_argv_handle(qcvm_structlist_t, vm, 0); 183 | list->size = 0; 184 | } 185 | 186 | static void QC_structlist_at(qcvm_t *vm) 187 | { 188 | qcvm_handle_id_t handleid = qcvm_argv_int32(vm, 0); 189 | qcvm_structlist_t *list = qcvm_fetch_handle_typed(qcvm_structlist_t, vm, handleid); 190 | const size_t index = qcvm_argv_int32(vm, 1); 191 | 192 | if (index >= list->size) 193 | qcvm_error(vm, "bad index"); 194 | 195 | qcvm_return_pointer(vm, (qcvm_pointer_t) { .handle = { .type = QCVM_POINTER_HANDLE, .index = handleid, .offset = list->element_size * index } }); 196 | } 197 | 198 | static void QC_structlist_value_at(qcvm_t *vm) 199 | { 200 | qcvm_handle_id_t handleid = qcvm_argv_int32(vm, 0); 201 | qcvm_structlist_t *list = qcvm_fetch_handle_typed(qcvm_structlist_t, vm, handleid); 202 | const size_t index = qcvm_argv_int32(vm, 1); 203 | 204 | if (index >= list->size) 205 | qcvm_error(vm, "bad index"); 206 | 207 | void *store; 208 | 209 | if (!qcvm_resolve_pointer(vm, qcvm_argv_pointer(vm, 2), false, list->element_size, &store)) 210 | qcvm_error(vm, "bad pointer"); 211 | 212 | memcpy(store, (uint8_t *)list->values + (list->element_size * index), list->element_size); 213 | } 214 | 215 | static void QC_structlist_resize(qcvm_t *vm) 216 | { 217 | qcvm_structlist_t *list = qcvm_argv_handle(qcvm_structlist_t, vm, 0); 218 | const size_t index = qcvm_argv_int32(vm, 1); 219 | 220 | if (index <= list->allocated) 221 | list->size = index; 222 | else 223 | { 224 | void *old_values = list->values; 225 | 226 | if (!list->allocated) 227 | list->allocated = STRUCTLIST_RESERVE; 228 | else while (index > list->allocated) 229 | list->allocated *= 2; 230 | 231 | list->values = qcvm_alloc(vm, list->element_size * list->allocated); 232 | 233 | if (old_values) 234 | { 235 | memcpy(list->values, old_values, list->element_size * list->size); 236 | qcvm_mem_free(vm, old_values); 237 | } 238 | } 239 | } 240 | 241 | void qcvm_init_structlist_builtins(qcvm_t *vm) 242 | { 243 | qcvm_register_builtin(structlist_alloc); 244 | qcvm_register_builtin(structlist_insert); 245 | qcvm_register_builtin(structlist_push); 246 | qcvm_register_builtin(structlist_unshift); 247 | qcvm_register_builtin(structlist_delete); 248 | qcvm_register_builtin(structlist_pop); 249 | qcvm_register_builtin(structlist_shift); 250 | qcvm_register_builtin(structlist_get_length); 251 | qcvm_register_builtin(structlist_clear); 252 | qcvm_register_builtin(structlist_at); 253 | qcvm_register_builtin(structlist_value_at); 254 | qcvm_register_builtin(structlist_resize); 255 | } -------------------------------------------------------------------------------- /vm_structlist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void qcvm_init_structlist_builtins(qcvm_t *vm); -------------------------------------------------------------------------------- /windows/unistd.h: -------------------------------------------------------------------------------- 1 | #ifndef _UNISTD_H 2 | #define _UNISTD_H 1 3 | 4 | /* This is intended as a drop-in replacement for unistd.h on Windows. 5 | * Please add functionality as neeeded. 6 | * https://stackoverflow.com/a/826027/1202830 7 | */ 8 | 9 | #include 10 | #include 11 | #include /* for getpid() and the exec..() family */ 12 | #include /* for _getcwd() and _chdir() */ 13 | 14 | #define srandom srand 15 | #define random rand 16 | 17 | /* Values for the second argument to access. 18 | These may be OR'd together. */ 19 | #define R_OK 4 /* Test for read permission. */ 20 | #define W_OK 2 /* Test for write permission. */ 21 | //#define X_OK 1 /* execute permission - unsupported in windows*/ 22 | #define F_OK 0 /* Test for existence. */ 23 | 24 | #define access _access 25 | #define dup2 _dup2 26 | #define execve _execve 27 | #define ftruncate _chsize 28 | #define unlink _unlink 29 | #define fileno _fileno 30 | #define getcwd _getcwd 31 | #define chdir _chdir 32 | #define isatty _isatty 33 | #define lseek _lseek 34 | /* read, write, and close are NOT being #defined here, because while there are file handle specific versions for Windows, they probably don't work for sockets. You need to look at your app and consider whether to call e.g. closesocket(). */ 35 | 36 | #ifdef _WIN64 37 | #define ssize_t int64_t 38 | #else 39 | #define ssize_t int32_t 40 | #endif 41 | 42 | #define STDIN_FILENO 0 43 | #define STDOUT_FILENO 1 44 | #define STDERR_FILENO 2 45 | 46 | #endif /* unistd.h */ --------------------------------------------------------------------------------