SourceMod gamedata checker.

nosoop bf42d42fd4 Introduce memory indirection asserts 5 mēneši atpakaļ
src bf42d42fd4 Introduce memory indirection asserts 4 nedēļas atpakaļ
tests 094d53033e Support testing multiple versions of the game binary 4 nedēļas atpakaļ
.gitignore 2ca53b9168 Add TF2 server binary vtable test 8 mēneši atpakaļ
.justfile f6e4a2cd4b Add mypy checking to test dir and prioritize over pytest 8 mēneši atpakaļ
README.md bf42d42fd4 Introduce memory indirection asserts 4 nedēļas atpakaļ
pyproject.toml 48fa8ed5e7 Strongly enforce type annotations 4 nedēļas atpakaļ

README.md

smgdc

SourceMod gamedata checker.

Rewrite of a previous internal project. Faster. Stronger. More maintainable.

Usage

smgdc <specfile> --add-binary <binary> --add-binary <binary> ...

  • specfile is a specification file or directory (tree) containing specification files.
  • --add-binary specifies one or more binaries to add for checking.
    • This may be a os.pathsep-separated pair of values that map a physical file to one present in the specification file.

Specification file

A specification file is an INI configuration file used to check aspects of one or more binary (code) files.

Example:

; reports presence of a byte sequence in the Windows server binary, yielding signature
[CWeaponMedigun::SecondaryAttack()]
target = bin/tf/server.dll
type = bytesig
contents = 55 8B EC 56 8B 75 08 85 F6 0F 84 ?? ?? ?? ?? 53

; reports that a given immediate value in code is at a given location, yielding offsets, etc.
[CTFProjectile_Flare::Explode_Air()::SelfDamageRadius LINUX]
target = bin/tf/server_srv.so
type = value
symbol = _ZN19CTFProjectile_Flare11Explode_AirEP10CGameTraceib
offset = 0x468
struct = <f
assert = value == 100.0

; reports the presence of a symbol in a given vtable, yielding offset for Windows / Linux
[CBaseEntity::GetEnemy()]
target = bin/tf/server_srv.so
type = vfn
vtable = 11CBaseEntity
symbol = _ZN11CBaseEntity8GetEnemyEv

The entries specify a section name (must be unique within the file), a target binary's subpath to match against, a type of specification to check for, and additional information for a particular specification type.

This allows the validation process to be more flexible than a gameconf file is on its own, as that tells you nothing about the context (is the offset for a virtual file? a property?).

Types of specifications include:

  • value: reads out a value from a binary
  • bytesig: confirms the presence of a byte sequence
  • vfn: takes a virtual method symbol and gets the vtable index for it on Linux and Windows (guesstimate on the latter)

Assertions

Some entries accept an assert option, which allows the evaluation of single Python expressions. Returning False causes validation to fail, indicating that an entry requires review.

[!WARNING] Obviously, Python's eval is dangerous; this application was never designed to accomodate adversarial inputs. You should never blindly perform validations with user-submitted specification files.

Depending on the entry type, one or more of these variables will be made available:

  • addr: An object representing a position in the binary. This is set to the configuration's result address and provides the following methods:

    • read(offset: int = 0): Returns a new BinaryPosition, acting as if the position + offset was dereferenced as an absolute address.
    • value(struct, offset = 0): Extracts the current position using a struct.Struct-format string into a single result.
    • string_value(encoding = "utf-8", errors = "strict", offset = 0): Extracts a string from the current position.
    • This is designed to mimic SourceMod's gamedata API of using reads via method chaining:

      # process an indirection by dereferencing at position +3h
      addr.read(0x3).value("<I") == 0xDEADBEEF
      
  • value: In a value entry, this returns the value that will be inserted into the output.

Constraint file

[!IMPORTANT] The constraint file specification is not finalized. Please ensure that you are reading the documentation for the version of smgdc that you are working with.

Linux binaries are sometimes compiled with flags that cause a many-to-one mapping of symbols to function addresses (in other words: link time optimization). While the application does attempt to uniquely identify symbols using other markers, there's sometimes insufficient information to do so.

A constraint file is used as a last resort for end users to manually identify which symbols map to which positions for a given virtual table and its subclasses. This is done by specifying relative ordering constraints for each symbol, allowing for reuse between binary revisions (with the assumption that vtables aren't reordered across them).

Example:

[[_ZTV17CBaseCombatWeapon]]
constraint = "soft"
symbols = [
	"_ZN17CBaseCombatWeapon6DeleteEv",
	"_ZN17CBaseCombatWeapon4KillEv",
]

[[_ZTV17CBaseCombatWeapon]]
constraint = "consecutive"
symbols = [
	"_ZN17CBaseCombatWeapon27WeaponRangeAttack1ConditionEff",
	"_ZN17CBaseCombatWeapon27WeaponRangeAttack2ConditionEff",
	"_ZN17CBaseCombatWeapon27WeaponMeleeAttack1ConditionEff",
	"_ZN17CBaseCombatWeapon27WeaponMeleeAttack2ConditionEff",
]

In this CBaseCombatWeapon vtable, a soft order constraint is applied such that the offset of CBaseCombatWeapon::Delete is lower than CBaseCombatWeapon::Kill — both symbols point to the same address, and both are present at two offsets, so this ensures that once solved, each symbol will be assigned the correct offset.

A consecutive order constraint is also applied such that CBaseCombatWeapon::WeaponRangeAttack1Condition and subsequent symbols have monotonically increasing offsets.