Skip to content

aowlspt_camera.h

Source: abi/aowlspt_camera.h — 455 lines, 22 file-scope functions.

What this header owns

Reproduced verbatim from the header's own banner comment — these notes are frequently the only written record of why the subsystem is shaped the way it is.

text
 aowlspt_camera.h -- the C half of the host CAMERA API.

===========================================================================
WHAT THIS IS
===========================================================================

A guarded, byte-verified surface for READING and WRITING the live camera:
position, rotation (quaternion and euler), field of view, near/far clip, and
the three basis vectors. It is the foundation the free camera in
`host/Aowlspt.Host.Il2Cpp/camera.nim` is built on, and it is deliberately a
plain API rather than one feature's private plumbing -- mods and the live
inspector call the same entry points.

===========================================================================
WHAT IS NOT HERE, AND WHY
===========================================================================

There is NO camera-acquisition target in this table. `UnityEngine.Camera::
get_main` @0x5260400 already lives in `aowlspt_debugui.h`'s verified table
(index AOWL_DU_CAMERA_MAIN) and is already the deploy latch's signal S5 in
`raidphase.nim`. A second acquisition path would be a second answer to the
question "which camera?", and the two would drift. `camera.nim` calls
`aowl_du_fn(AOWL_DU_CAMERA_MAIN)` / `aowl_du_call_p_v`, and reaches the
camera's Transform through `aowl_du_fn(AOWL_DU_GET_TRANSFORM)`
(`UnityEngine.Component::get_transform` @0x73B0F0) -- also already verified
there.

`EFT.CameraControl.CameraManager::SetFov` @0x1268D20 is NOT here and must not
be added. MEASURED by disassembly (and re-stated in `mods/fov/fov.nim`): it
opens `mov rdi,[rbx+0x70]; test rdi,rdi; je <epilogue>` and `<Camera>@0x70`
reads null on this build, so every call through it is a silent no-op that
still returns success. Writing `Camera::set_fieldOfView` on the camera we
actually hold is the only route that lands.

===========================================================================
THE ABI, MEASURED -- NOT ASSUMED
===========================================================================

Every claim below was read out of `GameAssembly.dll` with
`tools/il2cpp_resolve.py ... type UnityEngine.Camera|UnityEngine.Transform`
plus a 32-56 byte code dump at each RVA. The bytes are quoted in the comment
beside each row.

A Vector3 is 12 bytes -- neither 1/2/4/8 -- so Win64 cannot pass or return it
in a register. The build agrees, and says so twice:

  RETURN (hidden buffer / sret).  `Transform::get_position` @0x52B70E0 opens
      33 C0           xor  eax,eax
      48 8B FA        mov  rdi,rdx        <- `this` arrives in RDX
      48 89 01        mov  [rcx],rax      <- 8 bytes of the retbuf at RCX
      48 8B D9        mov  rbx,rcx
      89 41 08        mov  [rcx+8],eax    <- the 12th byte
  i.e. exactly 12 bytes zeroed through RCX before the icall. So the shape is
  (retbuf in RCX, this in RDX, MethodInfo* in R8) -- the SAME shape the
  already-live `RectTransform::get_rect` sret path uses. `get_eulerAngles`,
  `get_forward`, `get_right` and `get_up` open with the identical
  `33 C0 / 0F 57 C0 / 48 89 01 / 48 8B FA / 89 41 08`.

  ARGUMENT (by address).  `Transform::set_eulerAngles` @0x52B7320 opens
      F2 0F 10 02     movsd  xmm0,[rdx]     <- x,y read THROUGH rdx
      48 8B D9        mov    rbx,rcx        <- rcx is `this`
      ...
      F3 0F 10 4A 08  movss  xmm1,[rdx+8]   <- z
  RDX is dereferenced, so it is a POINTER to the Vector3, not the value.
  `set_position`/`set_rotation` carry RDX through untouched
  (`48 8B DA` ... `48 8B D3`) into the icall, which is the same conclusion.

A Quaternion is 16 bytes and takes the same two treatments: `get_rotation`
@0x52B7F60 zeroes the retbuf with `0F 57 C0 / 0F 11 01` (movups [rcx],xmm0 --
all 16 bytes at once), and `set_rotation` passes RDX through by address.

A float argument is ordinary: `Camera::set_fieldOfView` @0x525DD30 does
`0F 28 F1  movaps xmm6,xmm1`, so the value is in XMM1 with `this` in RCX.

This differs from the Vector2 case only because of size: a Vector2 is 8 bytes
and comes back PACKED IN RAX (`aowl_du_call_u_p`). Three sizes, three
conventions, all measured.

===========================================================================
SHAREDNESS
===========================================================================

`tools/il2cpp_resolve.py ... shared <rva>` over every row below reports
`unique` (owners=1) for ALL FIFTEEN. Nothing here is detoured in any case --
this file only CALLS -- but the check was run because 28.3% of by-name
lookups land on a shared RVA and "we only call it" is not a reason to skip a
measurement that is free.

For completeness and because it is the one exception worth stating:
`Component::get_transform` @0x73B0F0, which `camera.nim` reuses out of the
debugui table, is SHARED (15 owners). Calling a shared address is correct
code for the receiver passed; DETOURING one would not be. It is called here
and never patched.

===========================================================================
SAFETY
===========================================================================

`aowl_cam_fn` is a byte-for-byte copy of `aowl_du_fn`'s discipline:
GameAssembly.dll must be mapped (through `aowl_cam_ga`, the single
never-cached accessor -- and if it is absent the caller must RETRY rather
than record a verdict; see the comment on that function), the address must
land in COMMITTED
EXECUTABLE memory (checked before the memcmp, because a stale RVA on another
build can point at an uncommitted page and memcmp there faults), and the
prologue is compared against the STARTUP SNAPSHOT (`aowl_pro_verify`) rather
than live memory -- so a feature that binds second still verifies instead of
reading someone else's trampoline and self-rejecting.

Per-target state is recorded once (`aowl_cam_state`), so the verified/rejected
counters are counts of DISTINCT TARGETS and not of successful calls -- the
defect that once printed "36 of 28 managed targets verified".

Every thunk refuses a NULL function pointer and a NULL receiver rather than
calling through it, and every sret read rejects a NaN or an absurd magnitude
and leaves the output statics alone, because a garbage coordinate that still
looks like a number is the failure this whole file exists to stop.

This header opens NO `aowl_p_p_seh` guard of its own. `camera.nim` opens
exactly one around its whole body; that guard is not re-entrant, so a nested
one here would DISARM it.
------------------------------------------------------------------

Constants

  • AOWLSPT_CAMERA_H
  • AOWL_CAM_GET_EULER
  • AOWL_CAM_GET_FAR
  • AOWL_CAM_GET_FORWARD
  • AOWL_CAM_GET_FOV
  • AOWL_CAM_GET_NEAR
  • AOWL_CAM_GET_POSITION
  • AOWL_CAM_GET_RIGHT
  • AOWL_CAM_GET_ROTATION
  • AOWL_CAM_GET_UP
  • AOWL_CAM_SET_EULER
  • AOWL_CAM_SET_FAR
  • AOWL_CAM_SET_FOV
  • AOWL_CAM_SET_NEAR
  • AOWL_CAM_SET_POSITION
  • AOWL_CAM_SET_ROTATION
  • AOWL_CAM_TARGET_COUNT

Types

  • struct AowlCamTarget

Functions

SignatureLine
HMODULE aowl_cam_ga(void)279
int32_t aowl_cam_module_ready(void)282
int32_t aowl_cam_wait_count(void)283
void aowl_cam_fn(int32_t i)285
char aowl_cam_name(int32_t i)322
uint32_t aowl_cam_rva(int32_t i)326
int32_t aowl_cam_target_count(void)330
int32_t aowl_cam_ok_count(void)331
int32_t aowl_cam_bad_count(void)332
int32_t aowl_cam_reason(void)333
int32_t aowl_cam_profull_count(void)334
char aowl_cam_reason_text(void)337
double aowl_cam_get_f(void* fn, void* self)364
int32_t aowl_cam_get_f_ok(void* fn, void* self)375
int32_t aowl_cam_set_f(void* fn, void* self, double v)386
int32_t aowl_cam_get_v(void* fn, void* self, int32_t nfloats)400
double aowl_cam_v0(void)418
double aowl_cam_v1(void)419
double aowl_cam_v2(void)420
double aowl_cam_v3(void)421
int32_t aowl_cam_set_v(void* fn, void* self, double x, double y, double z, double w)427
int32_t aowl_cam_key_down(int32_t vk)449

aoughwl — self-hosted platform for things n stuff. Contact / Support on Discord for access to the private backends.