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
|
/*
* Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com>
* Copyright © 2022 Willian Henrique <wsimanbrazil@yahoo.com.br>
*
* 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.
*/
// TODO(linux): theoretically, probably ifdef out the cvar-replacement stuff; we
// expect any game that's been ported to linux to already have fov_desired
#include "con_.h"
#include "engineapi.h"
#include "errmsg.h"
#include "ent.h"
#include "event.h"
#include "feature.h"
#include "gametype.h"
#include "hook.h"
#include "intdefs.h"
#include "langext.h"
#include "mem.h"
#include "sst.h"
#include "vcall.h"
#include "x86.h"
#include "x86util.h"
FEATURE("extended FOV range")
// could work for other games, but generally only portal 1 people want this (the
// rest of us consider this cheating and a problem for runs...)
GAMESPECIFIC(Portal1)
REQUEST(ent)
DEF_CVAR_MINMAX_UNREG(fov_desired,
"Set the base field of view (SST reimplementation)", 75, 75, 120,
CON_HIDDEN | CON_ARCHIVE)
static struct con_var *real_fov_desired; // engine's if it has it, or ours
typedef void (*VCALLCONV SetDefaultFOV_func)(void *, int);
static SetDefaultFOV_func orig_SetDefaultFOV;
static void VCALLCONV hook_SetDefaultFOV(void *this, int fov) {
// disregard server-side clamped value and force our own value instead
orig_SetDefaultFOV(this, con_getvari(real_fov_desired));
}
static bool find_SetDefaultFOV(struct con_cmd *fov) {
const uchar *insns = (const uchar *)fov->cb;
int callcnt = 0;
for (const uchar *p = insns; p - insns < 96;) {
// The fov command calls 4 functions, one of them virtual. Of the 3
// direct calls, SetDefaultFOV() is the third.
if (p[0] == X86_CALL && ++callcnt == 3) {
orig_SetDefaultFOV = (SetDefaultFOV_func)(p + 5 +
mem_loads32(p + 1));
return true;
}
NEXT_INSN(p, "SetDefaultFOV function");
}
return false;
}
// replacement cvar needs to actively set player fov if in a map
static void fovcb(struct con_var *v) {
void *player = ent_get(1); // NOTE: singleplayer only!
if_hot (player) orig_SetDefaultFOV(player, con_getvari(v));
}
// ensure FOV is applied on load, if the engine wouldn't do that itself
HANDLE_EVENT(ClientActive, struct edict *e) {
if (real_fov_desired == fov_desired) {
orig_SetDefaultFOV(e->ent_unknown, con_getvari(fov_desired));
}
}
static struct con_cmd *cmd_fov;
INIT {
cmd_fov = con_findcmd("fov");
if_cold (!cmd_fov) return FEAT_INCOMPAT; // shouldn't happen, but who knows!
if (real_fov_desired = con_findvar("fov_desired")) {
// latest steampipe already goes up to 120 fov
if (real_fov_desired->parent->maxval == 120) return FEAT_SKIP;
real_fov_desired->parent->maxval = 120;
}
else {
if (!has_ent) return FEAT_INCOMPAT;
con_regvar(fov_desired);
real_fov_desired = fov_desired;
}
if_cold (!find_SetDefaultFOV(cmd_fov)) {
errmsg_errorx("couldn't find SetDefaultFOV function");
return FEAT_INCOMPAT;
}
orig_SetDefaultFOV = (SetDefaultFOV_func)hook_inline(
(void *)orig_SetDefaultFOV, (void *)&hook_SetDefaultFOV);
if_cold (!orig_SetDefaultFOV) {
errmsg_errorsys("couldn't hook SetDefaultFOV function");
return FEAT_FAIL;
}
// we might not be using our cvar but simpler to do this unconditionally
fov_desired->cb = &fovcb;
fov_desired->parent->base.flags &= ~CON_HIDDEN;
// hide the original fov command since we've effectively broken it anyway :)
cmd_fov->base.flags |= CON_DEVONLY;
return FEAT_OK;
}
END {
if_hot (!sst_userunloaded) return;
if (real_fov_desired && real_fov_desired != fov_desired) {
real_fov_desired->parent->maxval = 90;
if (con_getvarf(real_fov_desired) > 90) {
con_setvarf(real_fov_desired, 90); // blegh.
}
}
else {
void *player = ent_get(1); // also singleplayer only
if (player) orig_SetDefaultFOV(player, 75);
}
unhook_inline((void *)orig_SetDefaultFOV);
cmd_fov->base.flags &= ~CON_DEVONLY;
}
// vi: sw=4 ts=4 noet tw=80 cc=80
|