1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
|
/* THIS FILE SHOULD BE CALLED `con.c` BUT WINDOWS IS STUPID */
/*
* Copyright © Michael Smith <mikesmiffy128@gmail.com>
* Copyright © Hayden K <imaciidz@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stddef.h> // should be implied by stdlib but glibc is dumb (offsetof)
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "abi.h"
#include "con_.h"
#include "engineapi.h" // for factories and rgba - XXX: is this a bit circular?
#include "errmsg.h"
#include "extmalloc.h"
#include "gamedata.h"
#include "gametype.h"
#include "langext.h"
#include "mem.h"
#include "os.h"
#include "vcall.h"
#include "version.h"
#include "x86.h"
#include "x86util.h"
/******************************************************************************\
* Have you ever noticed that when someone comments "here be dragons" there's *
* no actual dragons? Turns out, that's because the dragons all migrated over *
* here, so that they could build multiple inheritance vtables in C, by hand. *
* *
* Also there's self-modifying code now. *
* *
* Don't get set on fire. *
\******************************************************************************/
static int dllid; // from AllocateDLLIdentifier(), lets us unregister in bulk
int con_cmdclient;
DECL_VFUNC_DYN(struct ICvar, int, AllocateDLLIdentifier)
DECL_VFUNC_DYN(struct ICvar, void, RegisterConCommand, /*ConCommandBase*/ void *)
DECL_VFUNC_DYN(struct ICvar, void, UnregisterConCommands, int)
DECL_VFUNC_DYN(struct ICvar, struct con_var *, FindVar, const char *)
//DECL_VFUNC(struct ICvar, const struct con_var *, FindVar_const, 13, const char *)
DECL_VFUNC_DYN(struct ICvar, struct con_cmd *, FindCommand, const char *)
DECL_VFUNC_DYN(struct ICvar, void, CallGlobalChangeCallbacks, struct con_var *,
const char *, float)
#ifdef _WIN32
DECL_VFUNC_DYN(struct ICvar, void, CallGlobalChangeCallbacks_OE,
struct con_var *, const char *)
// other OE stuff. TODO(compat): should this be in gamedata? fine for now?
DECL_VFUNC(struct ICvar, struct con_cmdbase *, GetCommands_OE, 9)
DECL_VFUNC(struct VEngineClient, void *, Cmd_Argv, 32)
#endif
// bootstrap things for con_detect(), not used after that
DECL_VFUNC(struct ICvar, void *, FindCommandBase_p2, 13, const char *)
DECL_VFUNC(struct ICvar, void *, FindCommand_nonp2, 14, const char *)
DECL_VFUNC(struct ICvar, void *, FindVar_nonp2, 12, const char *)
#ifdef _WIN32
DECL_VFUNC(struct ICvar, void *, FindVar_OE, 7, const char *)
#endif
static struct ICvar *coniface;
static void *colourmsgf;
#ifdef _WIN32
#pragma section("selfmod", execute)
__attribute((used, section("selfmod"), noinline))
#endif
asm_only void _con_colourmsg(void *dummy, const struct rgba *c,
const char *fmt, ...) {
// NE: ConsoleColorPrintf is virtual, so the dummy param is a carve-out for
// `this` (which is coniface).
__asm volatile (
"mov eax, %0\n"
"mov [esp + 4], eax\n" // put coniface in the empty stack slot
"jmp dword ptr %1\n" // jump to the real function
:
: "m" (coniface), "m" (colourmsgf)
: "eax", "memory"
);
}
#ifdef _WIN32
// this function is defined as data because we'll be using it to self-modify the
// main _con_colourmsg function!
__attribute((used, section("rdata")))
asm_only static void _con_colourmsg_OE(void *dummy, const struct rgba *c,
const char *fmt, ...) {
// OE: it's a global function, with no this param, so we have to fix up the
// stack a bit. This will be less efficient than NE, but that seems like a
// reasonable tradeoff considering most games are NE. We could in theory
// self-modify every single call site to avoid the fixups but haha are you
// out of your mind we're not doing that.
__asm volatile (
"pop ebx\n" // pop return address, store in callee-save (*see header!*)
"add esp, 4\n" // pop the dummy stack slot, it's only useful for NE
"call dword ptr %1\n" // jump to the real function
"sub esp, 4\n" // pad the stack back out for the caller
"jmp ebx\n" // return to saved address
:
: "m" (coniface), "m" (colourmsgf)
: "eax", "ebx", "memory"
);
}
#define SELFMOD_LEN 15 // above instructions assemble to this many bytes!
static bool selfmod() {
if (!os_mprot((void *)_con_colourmsg, SELFMOD_LEN, PAGE_EXECUTE_READWRITE)) {
errmsg_errorsys("couldn't make memory writable");
return false;
}
memcpy((void *)&_con_colourmsg, (void *)&_con_colourmsg_OE, SELFMOD_LEN);
if (!os_mprot((void *)_con_colourmsg, SELFMOD_LEN, PAGE_EXECUTE_READ)) {
errmsg_warnsys("couldn't restore self-modified page to read-only");
}
return true;
}
#endif
static void VCALLCONV dtor(void *_) {} // we don't use constructors/destructors
static bool VCALLCONV IsCommand_cmd(void *this) { return true; }
static bool VCALLCONV IsCommand_var(void *this) { return false; }
static bool VCALLCONV IsFlagSet(struct con_cmdbase *this, int flags) {
return !!(this->flags & flags);
}
static void VCALLCONV AddFlags(struct con_cmdbase *this, int flags) {
this->flags |= flags;
}
static void VCALLCONV RemoveFlags(struct con_cmdbase *this, int flags) {
this->flags &= ~flags;
}
static int VCALLCONV GetFlags(struct con_cmdbase *this) {
return this->flags;
}
static const char *VCALLCONV GetName(struct con_cmdbase *this) {
return this->name;
}
static const char *VCALLCONV GetHelpText(struct con_cmdbase *this) {
return this->help;
}
static bool VCALLCONV IsRegistered(struct con_cmdbase *this) {
return this->registered;
}
static int VCALLCONV GetDLLIdentifier(struct con_cmdbase *this) {
return dllid;
}
static void VCALLCONV Create_base(struct con_cmdbase *this, const char *name,
const char *help, int flags) {} // nop, we static init already
static void VCALLCONV Init(struct con_cmdbase *this) {} // ""
static bool ClampValue_common(struct con_var_common *this, float *f) {
if (this->hasmin && this->minval > *f) { *f = this->minval; return true; }
if (this->hasmax && this->maxval < *f) { *f = this->maxval; return true; }
return false;
}
static bool VCALLCONV ClampValue(struct con_var *this, float *f) {
return ClampValue_common(&this->v2, f);
}
#ifdef _WIN32
static bool VCALLCONV ClampValue_OE(struct con_var *this, float *f) {
return ClampValue_common(&this->v1, f);
}
#endif
// global argc/argv. also OE only. extern for use in sst.c plugin_unload hook
// as well as in DEF_CCMD_COMPAT_HOOK
int *_con_argc;
const char *(*_con_argv)[80];
static bool find_argcargv() {
const uchar *insns = (const uchar *)VFUNC(engclient, Cmd_Argv);
for (const uchar *p = insns; p - insns < 32;) {
if (p[0] == X86_CALL) { insns = p + 5 + mem_loads32(p + 1); goto _1; }
NEXT_INSN(p, "global Cmd_Argv function");
}
return false;
_1: for (const uchar *p = insns; p - insns < 32;) {
if (p[0] == X86_CMPRMW && p[1] == X86_MODRM(0, 0, 5)) {
_con_argc = mem_loadptr(p + 2);
}
else if (p[0] == X86_MOVRMW && p[1] == X86_MODRM(0, 0, 4) &&
p[2] == X86_TESTMRW) {
_con_argv = mem_loadptr(p + 3);
}
if (_con_argc && _con_argv) return true;
NEXT_INSN(p, "global argc and argv variables");
}
return false;
}
int VCALLCONV AutoCompleteSuggest(struct con_cmd *this, const char *partial,
/*CUtlVector*/ void *commands) {
// TODO(autocomplete): implement this if needed later
return 0;
}
bool VCALLCONV CanAutoComplete(struct con_cmd *this) {
return false;
}
void VCALLCONV Dispatch(struct con_cmd *this, const struct con_cmdargs *args) {
this->cb(args->argc, args->argv);
}
#ifdef _WIN32
void VCALLCONV Dispatch_OE(struct con_cmd *this) {
this->cb(*_con_argc, *_con_argv);
}
#endif
static void ChangeStringValue_common(struct con_var *this,
struct con_var_common *common, char *old, const char *s) {
memcpy(old, common->strval, common->strlen);
int len = strlen(s) + 1;
if (len > common->strlen) {
common->strval = extrealloc(common->strval, len);
common->strlen = len;
}
memcpy(common->strval, s, len);
// callbacks don't matter as far as ABI compat goes (and thank goodness
// because e.g. portal2 randomly adds a *list* of callbacks!?). however we
// do need callbacks for at least one feature, so do our own minimal thing
if (this->cb) this->cb(this);
}
static void VCALLCONV ChangeStringValue(struct con_var *this, const char *s,
float oldf) {
char *old = alloca(this->v2.strlen);
ChangeStringValue_common(this, &this->v2, old, s);
CallGlobalChangeCallbacks(coniface, this, old, oldf);
}
#ifdef _WIN32
static void VCALLCONV ChangeStringValue_OE(struct con_var *this, const char *s) {
char *old = alloca(this->v1.strlen);
ChangeStringValue_common(this, &this->v1, old, s);
CallGlobalChangeCallbacks_OE(coniface, this, old);
}
#endif
// NOTE: these Internal* functions are virtual in the engine, but nowadays we
// just call them directly since they're private to us. We still put them in the
// vtable just in case (see below), though arguably nothing in the engine
// *should* be calling these internal things anyway.
static void InternalSetValue_common(struct con_var *this,
struct con_var_common *common, const char *v) {
float newf = atof(v);
char tmp[32];
if (ClampValue_common(common, &newf)) {
snprintf(tmp, sizeof(tmp), "%f", newf);
v = tmp;
}
common->fval = newf;
common->ival = (int)newf;
}
static void VCALLCONV InternalSetValue(struct con_var *this, const char *v) {
float oldf = this->v2.fval;
InternalSetValue_common(this, &this->v2, v);
if (!(this->base.flags & CON_NOPRINT)) ChangeStringValue(this, v, oldf);
}
#ifdef _WIN32
static void VCALLCONV InternalSetValue_OE(struct con_var *this, const char *v) {
InternalSetValue_common(this, &this->v1, v);
if (!(this->base.flags & CON_NOPRINT)) ChangeStringValue_OE(this, v);
}
#endif
static void VCALLCONV InternalSetFloatValue(struct con_var *this, float v) {
if (v == this->v2.fval) return;
float old = this->v2.fval;
ClampValue_common(&this->v2, &v);
this->v2.fval = v; this->v2.ival = (int)v;
if (!(this->base.flags & CON_NOPRINT)) {
char tmp[32];
snprintf(tmp, sizeof(tmp), "%f", this->v2.fval);
ChangeStringValue(this, tmp, old);
}
}
#ifdef _WIN32
static void VCALLCONV InternalSetFloatValue_OE(struct con_var *this, float v) {
if (v == this->v1.fval) return;
ClampValue_common(&this->v1, &v);
this->v1.fval = v; this->v1.ival = (int)v;
if (!(this->base.flags & CON_NOPRINT)) {
char tmp[32];
snprintf(tmp, sizeof(tmp), "%f", this->v1.fval);
ChangeStringValue_OE(this, tmp);
}
}
#endif
static void InternalSetIntValue_impl(struct con_var *this,
struct con_var_common *common, int v) {
float f = (float)v;
if (ClampValue_common(common, &f)) v = (int)f;
common->fval = f; common->ival = v;
}
static void VCALLCONV InternalSetIntValue(struct con_var *this, int v) {
if (v == this->v2.ival) return;
float old = this->v2.fval;
InternalSetIntValue_impl(this, &this->v2, v);
if (!(this->base.flags & CON_NOPRINT)) {
char tmp[32];
snprintf(tmp, sizeof(tmp), "%f", this->v2.fval);
ChangeStringValue(this, tmp, old);
}
}
#ifdef _WIN32
static void VCALLCONV InternalSetIntValue_OE(struct con_var *this, int v) {
if (v == this->v1.ival) return;
InternalSetIntValue_impl(this, &this->v1, v);
if (!(this->base.flags & CON_NOPRINT)) {
char tmp[32];
snprintf(tmp, sizeof(tmp), "%f", this->v1.fval);
ChangeStringValue_OE(this, tmp);
}
}
#endif
// IConVar calls get this-adjusted pointers, so just subtract the offset
static void VCALLCONV SetValue_str_thunk(void *thisoff, const char *v) {
struct con_var *this = mem_offset(thisoff,
-offsetof(struct con_var, vtable_iconvar));
InternalSetValue(this, v);
}
static void VCALLCONV SetValue_f_thunk(void *thisoff, float v) {
struct con_var *this = mem_offset(thisoff,
-offsetof(struct con_var, vtable_iconvar));
InternalSetFloatValue(this, v);
}
static void VCALLCONV SetValue_i_thunk(void *thisoff, int v) {
struct con_var *this = mem_offset(thisoff,
-offsetof(struct con_var, vtable_iconvar));
InternalSetIntValue(this, v);
}
static void VCALLCONV SetValue_colour_thunk(void *thisoff, struct rgba v) {
struct con_var *this = mem_offset(thisoff,
-offsetof(struct con_var, vtable_iconvar));
InternalSetIntValue(this, v.val);
}
// more misc thunks for IConVar, hopefully these just compile to a lea and a jmp
static const char *VCALLCONV GetName_thunk(void *thisoff) {
struct con_var *this = mem_offset(thisoff,
-offsetof(struct con_var, vtable_iconvar));
return GetName(&this->base);
}
static bool VCALLCONV IsFlagSet_thunk(void *thisoff, int flags) {
struct con_var *this = mem_offset(thisoff,
-offsetof(struct con_var, vtable_iconvar));
return IsFlagSet(&this->base, flags);
}
// dunno what this is actually for...
static int VCALLCONV GetSplitScreenPlayerSlot(struct con_var *thisoff) {
return 0;
}
// aand yet another Create nop
static void VCALLCONV Create_var(void *thisoff, const char *name,
const char *defaultval, int flags, const char *helpstr, bool hasmin,
float min, bool hasmax, float max, void *cb) {}
// the first few members of ConCommandBase are the same between versions
void *_con_vtab_cmd[14 + NVDTOR] = {
(void *)&dtor,
#ifndef _WIN32
(void *)&dtor,
#endif
(void *)&IsCommand_cmd,
(void *)&IsFlagSet,
(void *)&AddFlags
};
// the engine does dynamic_casts on ConVar at some points so we have to fill out
// bare minimum rtti to prevent crashes. oh goody.
#ifdef _WIN32
DEF_MSVC_BASIC_RTTI(static, varrtti, _con_vtab_var, "sst_ConVar")
#else
DEF_ITANIUM_BASIC_RTTI(static, varrtti, "sst_ConVar")
#endif
struct _con_vtab_var_wrap _con_vtab_var_wrap = {
#ifndef _WIN32
0, // this *is* the top, no offset needed :)
#endif
&varrtti,
(void *)&dtor,
#ifndef _WIN32
(void *)&dtor,
#endif
(void *)&IsCommand_var,
(void *)&IsFlagSet,
(void *)&AddFlags
};
struct _con_vtab_iconvar_wrap _con_vtab_iconvar_wrap = {
#ifdef _WIN32
0 // because of crazy overload vtable order we can't prefill *anything*
#else
// RTTI members first on linux:
-offsetof(struct con_var, vtable_iconvar),
&varrtti,
// colour is the last of the 4 on linux so we can at least prefill these 3
(void *)&SetValue_str_thunk,
(void *)&SetValue_f_thunk,
(void *)&SetValue_i_thunk
#endif
};
#ifdef _WIN32
static int off_cvar_common = offsetof(struct con_var, v2);
#else
enum { off_cvar_common = offsetof(struct con_var, v2) };
#endif
struct con_var_common *con_getvarcommon(const struct con_var *v) {
return mem_offset(v, off_cvar_common);
}
void con_regvar(struct con_var *v) {
struct con_var_common *c = con_getvarcommon(v);
c->strval = extmalloc(c->strlen); // note: _DEF_CVAR() sets strlen member
memcpy(c->strval, c->defaultval, c->strlen);
RegisterConCommand(coniface, v);
}
void con_regcmd(struct con_cmd *c) {
RegisterConCommand(coniface, c);
}
// XXX: these should use vcall/gamedata stuff as they're only used for the
// setter API after everything is brought up. however that will require some
// kind of windows/linux conditionals in the gamedata system! this solution is
// just hacked in for now to get things working because it was broken before...
#ifdef _WIN32
static int vtidx_SetValue_str = 2, vtidx_SetValue_f = 1, vtidx_SetValue_i = 0;
static int off_setter_vtable = offsetof(struct con_var, vtable_iconvar);
#else
enum { vtidx_SetValue_str = 0, vtidx_SetValue_f = 1, vtidx_SetValue_i = 2 };
#endif
#ifdef _WIN32
struct con_cmdbase **linkedlist = 0; // indirect command list, OE only!
static bool find_linkedlist(const uchar *insns) {
// note: it's a jmp in the disasm I've seen but a call seems plausible too
if (insns[0] != X86_JMPIW && *insns != X86_CALL) return false;
insns += 5 + mem_loads32(insns + 1); // follow the call
if (insns[0] != X86_MOVEAXII || insns[5] != X86_RET) return false;
linkedlist = mem_loadptr(insns + 1);
return true;
}
static bool find_Con_ColorPrintf() {
typedef void *(*GetSpewOutputFunc_func)();
void *tier0 = os_dlhandle(L"tier0.dll");
if_cold (!tier0) {
errmsg_errorsys("couldn't get tier0.dll handle");
return false;
}
GetSpewOutputFunc_func GetSpewOutputFunc = (GetSpewOutputFunc_func)os_dlsym(
tier0, "GetSpewOutputFunc");
if_cold (!GetSpewOutputFunc) {
errmsg_errorx("couldn't find GetSpewOutputFunc symbol");
return false;
}
uchar *insns = (uchar *)GetSpewOutputFunc();
for (uchar *p = insns; p - insns < 320;) {
if (p[0] == X86_PUSHECX && p[1] == X86_PUSHIW && p[6] == X86_CALL &&
p[11] == X86_ALUMI8S && p[12] == X86_MODRM(3, 0, 4)) {
colourmsgf = p + 11 + mem_loads32(p + 7);
return true;
}
NEXT_INSN(p, "Con_ColorPrintf function");
}
return false;
}
#endif
static void helpuserhelpus(int pluginver, char ifaceverchar) {
con_msg("\n");
con_msg("-- Please include ALL of the following if asking for help:\n");
con_msg("-- plugin: " LONGNAME " v" VERSION "\n");
con_msg("-- interfaces: %d/%c\n", pluginver, ifaceverchar);
con_msg("\n");
}
// note: for now at least, not using errmsg_*() macros here because it doesn't
// really make sense for these messages to be coming from "con"
static void badver() {
con_warn("sst: error: this engine version is not yet supported\n");
}
bool con_detect(int pluginver) {
if (coniface = factory_engine("VEngineCvar007", 0)) {
// GENIUS HACK (BUT STILL BAD): Portal 2 has everything in ICvar shifted
// down 3 places due to the extra stuff in IAppSystem. This means that
// if we look up the Portal 2-specific cvar using FindCommandBase, it
// *actually* calls the const-overloaded FindVar on other branches,
// which just happens to still work fine. From there, we can figure out
// the actual ABI to use to avoid spectacular crashes.
if (FindCommandBase_p2(coniface, "portal2_square_portals")) {
_gametype_tag |= _gametype_tag_Portal2;
return true;
}
if (FindCommand_nonp2(coniface, "l4d2_snd_adrenaline")) {
// while we're here, also distinguish Survivors, the stupid Japanese
// arcade game a few people seem to care about for some reason
// (which for some other reason also has some vtable changes)
if (FindVar_nonp2(coniface, "avatarbasemodel")) {
_gametype_tag |= _gametype_tag_L4DS;
}
else {
_gametype_tag |= _gametype_tag_L4D2;
}
if (FindVar_nonp2(coniface, "sv_zombie_touch_trigger_delay")) {
_gametype_tag |= _gametype_tag_L4D2_2125plus;
}
if (FindVar_nonp2(coniface, "director_cs_weapon_spawn_chance")) {
_gametype_tag |= _gametype_tag_TheLastStand;
}
return true;
}
if (FindVar_nonp2(coniface, "z_difficulty")) {
_gametype_tag |= _gametype_tag_L4D1;
// Crash Course update
if (FindCommand_nonp2(coniface, "director_log_scavenge_items")) {
_gametype_tag |= _gametype_tag_L4D1_1015plus;
// seems there was some code shuffling in the Mac update (1022).
// this update came out like 2-3 weeks after The Sacrifice
if (con_findvar("tank_stasis_time_suicide")) {
_gametype_tag |= _gametype_tag_L4D1_1022plus;
}
}
return true;
}
con_warn("sst: error: game is unsupported (using VEngineCvar007)\n");
helpuserhelpus(pluginver, '7');
return false;
}
if (coniface = factory_engine("VEngineCvar004", 0)) {
// TODO(compat): are there any cases where 004 is incompatible? could
// this crash? find out!
if (pluginver == 3) _gametype_tag |= _gametype_tag_2013;
else _gametype_tag |= _gametype_tag_OrangeBox;
// detect Portal 1 versions while we're here...
if (FindCommand_nonp2(coniface, "upgrade_portalgun")) {
_gametype_tag |= _gametype_tag_Portal1;
if (!FindVar_nonp2(coniface, "tf_escort_score_rate")) {
_gametype_tag |= _gametype_tag_Portal1_3420;
}
}
else if (FindCommand_nonp2(coniface, "phys_swap")) {
_gametype_tag |= _gametype_tag_HL2series;
}
return true;
}
if (coniface = factory_engine("VEngineCvar003", 0)) {
#ifdef _WIN32 // there's no OE on linux!
_gametype_tag |= _gametype_tag_OE;
// for deletion/unlinking on unload, we need an indirect linked list
// pointer. calling GetCommands gives us a direct pointer. so we have to
// actually pull out the indirect pointer from the actual asm lol.
if (!find_linkedlist((uchar *)VFUNC(coniface, GetCommands_OE))) {
errmsg_errorx("couldn't find command list pointer");
return false;
}
if (!find_argcargv()) return false;
if (!find_Con_ColorPrintf()) return false;
if (!selfmod()) return false;
// NOTE: the default static struct layout is for NE; immediately after
// engineapi init finishes, the generated glue code will shunt
// everything along for OE if required, in shuntvars(). since all the
// gluegen code is currently hooked up in sst.c this is a little bit
// annoyingly removed from here. not sure how to do it better, sorry.
off_cvar_common = offsetof(struct con_var, v1);
if (FindVar_OE(coniface, "mm_ai_facehugger_enablehugeattack")) {
_gametype_tag |= _gametype_tag_DMoMM;
}
return true;
#else
badver();
helpuserhelpus(pluginver, '2');
#endif
}
// I don't suppose there's anything below 002 worth caring about? Shrug.
if (factory_engine("VEngineCvar002", 0)) {
badver();
helpuserhelpus(pluginver, '2');
return false;
}
con_warn("sst: error: couldn't find a supported console interface\n");
helpuserhelpus(pluginver, '?');
return false;
}
void con_init() {
if (!GAMETYPE_MATCHES(OE)) {
colourmsgf = coniface->vtable[vtidx_ConsoleColorPrintf];
dllid = AllocateDLLIdentifier(coniface);
}
void **pc = _con_vtab_cmd + 3 + NVDTOR, **pv = _con_vtab_var + 3 + NVDTOR,
#ifdef _WIN32
**pi = _con_vtab_iconvar;
#else
**pi = _con_vtab_iconvar + 3;
#endif
if (GAMETYPE_MATCHES(L4Dbased)) { // 007 base
*pc++ = (void *)&RemoveFlags;
*pc++ = (void *)&GetFlags;
*pv++ = (void *)&RemoveFlags;
*pv++ = (void *)&GetFlags;
}
// base stuff in cmd
*pc++ = (void *)&GetName;
*pc++ = (void *)&GetHelpText;
*pc++ = (void *)&IsRegistered;
if (!GAMETYPE_MATCHES(OE)) *pc++ = (void *)&GetDLLIdentifier;
*pc++ = (void *)&Create_base;
*pc++ = (void *)&Init;
// cmd-specific
*pc++ = (void *)&AutoCompleteSuggest;
*pc++ = (void *)&CanAutoComplete;
if (GAMETYPE_MATCHES(OE)) {
#ifdef _WIN32 // function only defined in windows
*pc++ = (void *)&Dispatch_OE;
#endif
}
else {
*pc++ = (void *)&Dispatch;
}
// base stuff in var
*pv++ = (void *)&GetName;
*pv++ = (void *)&GetHelpText;
*pv++ = (void *)&IsRegistered;
if (!GAMETYPE_MATCHES(OE)) *pv++ = (void *)&GetDLLIdentifier;
*pv++ = (void *)&Create_base;
*pv++ = (void *)&Init;
// var-specific
if (GAMETYPE_MATCHES(OE)) {
#ifdef _WIN32
// these there are for the SetValue overloads but we effectively inline
// them by putting in pointers to call the Internal ones directly. this
// specifically works now that we've opted not to bother with the parent
// pointer stuff, otherwise we'd still need wrappers here.
vtidx_SetValue_i = pv - _con_vtab_var;
*pv++ = (void *)&InternalSetIntValue_OE;
vtidx_SetValue_f = pv - _con_vtab_var;
*pv++ = (void *)&InternalSetFloatValue_OE;
vtidx_SetValue_str = pv - _con_vtab_var;
*pv++ = (void *)&InternalSetValue_OE;
off_setter_vtable = 0; // setters should use the single vtable (below)
*pv++ = (void *)&InternalSetValue_OE;
*pv++ = (void *)&InternalSetFloatValue_OE;
*pv++ = (void *)&InternalSetIntValue_OE;
*pv++ = (void *)&ClampValue_OE;
*pv++ = (void *)&ChangeStringValue_OE;
#endif
}
else {
*pv++ = (void *)&InternalSetValue;
*pv++ = (void *)&InternalSetFloatValue;
*pv++ = (void *)&InternalSetIntValue;
if (GAMETYPE_MATCHES(L4D2x) || GAMETYPE_MATCHES(Portal2)) { // ugh.
// InternalSetColorValue, exact same machine instructions as for int
*pv++ = (void *)&InternalSetIntValue;
}
*pv++ = (void *)&ClampValue;
*pv++ = (void *)&ChangeStringValue;
}
*pv++ = (void *)&Create_var;
if (GAMETYPE_MATCHES(OE)) return; // we can just skip the rest on OE!
if (GAMETYPE_MATCHES(L4D2x) || GAMETYPE_MATCHES(Portal2)) {
*pi++ = (void *)&SetValue_colour_thunk;
#ifdef _WIN32
// stupid hack for above mentioned crazy overload ordering
++vtidx_SetValue_str;
++vtidx_SetValue_i;
++vtidx_SetValue_f;
#endif
}
#ifdef _WIN32
// see above: these aren't prefilled due to the reverse order
*pi++ = (void *)&SetValue_i_thunk;
*pi++ = (void *)&SetValue_f_thunk;
*pi++ = (void *)&SetValue_str_thunk;
#endif
*pi++ = (void *)&GetName_thunk;
// GetBaseName (we just return actual name in all cases)
if (GAMETYPE_MATCHES(L4Dbased)) *pi++ = (void *)&GetName_thunk;
*pi++ = (void *)&IsFlagSet_thunk;
// last one: not in 004, but doesn't matter. one less branch!
*pi++ = (void *)&GetSplitScreenPlayerSlot;
}
void con_disconnect() {
#ifdef _WIN32
if (linkedlist) {
// there's no DLL identifier system in OE so we have to manually unlink
// our commands and variables from the global list.
for (struct con_cmdbase **pp = linkedlist; *pp; ) {
struct con_cmdbase **next = &(*pp)->next;
// HACK: easiest way to do this is by vtable. dumb, but whatever!
const struct con_cmdbase *p = *pp;
if (p->vtable == _con_vtab_cmd || p->vtable == _con_vtab_var) {
*pp = *next;
}
else {
pp = next;
}
}
return;
}
#endif
UnregisterConCommands(coniface, dllid);
}
struct con_var *con_findvar(const char *name) {
return FindVar(coniface, name);
}
struct con_cmd *con_findcmd(const char *name) {
#ifdef _WIN32
if (linkedlist) {
// OE has a FindVar but no FindCommand. interesting oversight...
for (struct con_cmdbase *p = *linkedlist; p; p = p->next) {
if (!_stricmp(name, p->name)) {
// FIXME: this'll get variables too! make the appropriate vcall!
return (struct con_cmd *)p;
}
}
return 0;
}
#endif
return FindCommand(coniface, name);
}
// NOTE: getters here still go through the parent pointer although we stopped
// doing that internally, just in case we run into parented cvars in the actual
// engine. a little less efficient, but safest and simplest for now.
#define GETTER(T, N, M) \
T N(const struct con_var *v) { \
return con_getvarcommon(con_getvarcommon(v)->parent)->M; \
}
GETTER(const char *, con_getvarstr, strval)
GETTER(float, con_getvarf, fval)
GETTER(int, con_getvari, ival)
#undef GETTER
#define SETTER(T, I, N) \
void N(struct con_var *v, T x) { \
void (***VCALLCONV vtp)(void *, T) = mem_offset(v, off_setter_vtable); \
(*vtp)[I](vtp, x); \
}
SETTER(const char *, vtidx_SetValue_str, con_setvarstr)
SETTER(float, vtidx_SetValue_f, con_setvarf)
SETTER(int, vtidx_SetValue_i, con_setvari)
#undef SETTER
con_cmdcbv2 con_getcmdcbv2(const struct con_cmd *cmd) {
return !cmd->use_newcmdiface && cmd->use_newcb ? cmd->cb_v2 : 0;
}
con_cmdcbv1 con_getcmdcbv1(const struct con_cmd *cmd) {
return !cmd->use_newcmdiface && !cmd->use_newcb ? cmd->cb_v1 : 0;
}
// vi: sw=4 ts=4 noet tw=80 cc=80
|