Replace some gamedata lookups with VScript function bindings - #459
Conversation
|
I would've had concerns about VScript (or even just the CS2 bindings) being cleaned up from the game in the past. But given we're 3 years in to CS2 now, and it's all still there. I don't see any reason why we can't abuse them if they're sticking around, this would've been more stable than relying on signatures/offsets, anyway. Worst case we just revert back to signatures/offsets if Valve does ever go ahead with VScript cleanup. I would suggest sticking with "VScript" naming to avoid any confusion with cs_script though. I'll do a more thorough review once this is no longer marked as draft. |
|
The bindings no longer require waiting for |
| int ExtractTeleportIndexFromSetOrigin(void* pSetOrigin) | ||
| { | ||
| if (!pSetOrigin) | ||
| return -1; | ||
|
|
||
| constexpr int MaxScanSize = 0x40; | ||
|
|
||
| uint8* pCode = static_cast<uint8*>(pSetOrigin); | ||
|
|
||
| for (int i = 0; i + 9 <= MaxScanSize; i++) | ||
| { | ||
| int p = i; | ||
| uint8 rex = 0; | ||
|
|
||
| // Optional x86-64 REX prefix. | ||
| if ((pCode[p] & 0xF0) == 0x40) | ||
| rex = pCode[p++]; | ||
|
|
||
| // Pattern #1: | ||
| // | ||
| // mov rax, [this] | ||
| // ... | ||
| // call [rax + offset] | ||
| // | ||
| // or: | ||
| // | ||
| // jmp [rax + offset] | ||
| // | ||
| // FF /2 = CALL | ||
| // FF /4 = JMP | ||
| if (pCode[p] == 0xFF) | ||
| { | ||
| uint8 modrm = pCode[p + 1]; | ||
|
|
||
| int mod = modrm >> 6; | ||
| int op = (modrm >> 3) & 7; | ||
|
|
||
| // mod == 2 means [register + disp32]. | ||
| // rm == 4 would require a SIB byte. | ||
| if (mod == 2 && (op == 2 || op == 4) && (modrm & 7) != 4) | ||
| { | ||
| int32 disp = *reinterpret_cast<int32*>(pCode + p + 2); | ||
|
|
||
| if (disp >= 0 && (disp & 7) == 0) | ||
| return disp >> 3; | ||
| } | ||
| } | ||
|
|
||
| // Pattern #2: | ||
| // | ||
| // mov rax, [rax + offset] | ||
| // jmp rax | ||
| // | ||
| // or: | ||
| // | ||
| // mov rax, [rax + offset] | ||
| // call rax | ||
| // | ||
| // Require REX.W because we are loading a 64-bit function pointer. | ||
| if ((rex & 8) && pCode[p] == 0x8B) | ||
| { | ||
| uint8 modrm = pCode[p + 1]; | ||
|
|
||
| int mod = modrm >> 6; | ||
| int dst = ((modrm >> 3) & 7) | ((rex & 4) ? 8 : 0); | ||
| int base = (modrm & 7) | ((rex & 1) ? 8 : 0); | ||
|
|
||
| // mov reg, [same_reg + disp32] | ||
| if (mod == 2 && dst == base && (modrm & 7) != 4) | ||
| { | ||
| int32 disp = *reinterpret_cast<int32*>(pCode + p + 2); | ||
|
|
||
| if (disp < 0 || (disp & 7) != 0) | ||
| continue; | ||
|
|
||
| // The MOV instruction is 7 bytes: | ||
| // REX + 8B + ModRM + disp32 | ||
| int next = i + 7; | ||
|
|
||
| uint8 nextRex = 0; | ||
|
|
||
| if ((pCode[next] & 0xF0) == 0x40) | ||
| nextRex = pCode[next++]; | ||
|
|
||
| if (pCode[next] != 0xFF) | ||
| continue; | ||
|
|
||
| uint8 nextModrm = pCode[next + 1]; | ||
|
|
||
| int nextMod = nextModrm >> 6; | ||
| int nextOp = (nextModrm >> 3) & 7; | ||
| int nextReg = (nextModrm & 7) | ((nextRex & 1) ? 8 : 0); | ||
|
|
||
| // mod == 3 means CALL/JMP directly through a register. | ||
| // | ||
| // FF /2 = CALL reg | ||
| // FF /4 = JMP reg | ||
| if (nextMod == 3 && (nextOp == 2 || nextOp == 4) && nextReg == dst) | ||
| return disp >> 3; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return -1; | ||
| } |
There was a problem hiding this comment.
In my opinion, this is a lot to have replacing one simple vtable index offset, maintenance could be challenging. Think we'd just prefer keeping Teleport as a gamedata offset in this case.
Although VScript itself is disabled, its function bindings are still registered and can be accessed through
CGameEntitySystem.This PR replaces several gamedata signatures and offsets with runtime lookups for the following function bindings:
SetGravitySetEntityNameEmitSoundParamsSetTeam(ChangeTeam)IsPlayerPawnIsPlayerControllerSetModelcould also be resolved this way, but it is currently hooked, so it is intentionally left unchanged.Teleportcould potentially be obtained throughSetOrigin.Since the function bindings cannot be resolved before
StartupServeris called, the appropriate failure behavior still needs to be determined. For now, lookup failures are reported throughPanic, although it may be better to simply usePlat_FatalErrorinstead.