> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spurdoverse.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Pointer

> An address in the game process, with arithmetic and typed reads.

An address in the game process. Immutable: arithmetic returns a new `Pointer`. Global class, no import.

<Note>
  `Pointer` is always game memory, read-only. Addresses in [ffi](/api/ffi) are plain `bigint`s.
</Note>

## Overview

<CardGroup cols={2}>
  <Card title="Creating" icon="plus" href="#creating">
    `new Pointer`, `Pointer.from`, `Pointer.null`.
  </Card>

  <Card title="Address and arithmetic" icon="calculator" href="#address-and-arithmetic">
    `address`, `isNull`, `add`, `sub`, `equals`.
  </Card>

  <Card title="Reading" icon="book-open" href="#reading">
    `deref`, `resolve`, `read`, the `…Raw` variants.
  </Card>

  <Card title="Scalar types" icon="list" href="#scalar-types">
    `int32`, `float`, `ptr`, `vector`…
  </Card>

  <Card title="Snapshot" icon="camera" href="#snapshot">
    `read`, `bytes`, `as`.
  </Card>
</CardGroup>

***

## Creating

### new Pointer

<br />

```ts theme={null}
new Pointer(address: PointerLike)
Pointer.from(address: PointerLike): Pointer
Pointer.null: Pointer
```

`PointerLike` is a `Pointer`, a `bigint`, a non-negative integer or a `"0x…"` string. `Pointer.null` is the null pointer.

```ts theme={null}
const a = new Pointer(0x7FF6_1234_0000n);
const b = Pointer.from("0x7FF612340000");
a.equals(b);   // true
```

Most pointers come from [memory.module](/api/memory#module), [memory.scan](/api/memory#scan) or `entity.address`.

***

## Address and arithmetic

| Member | |
| :- | :- |
| `address: bigint` | the address, unsigned 64-bit |
| `isNull: boolean` | `address === 0n` |
| `add(offset: number \| bigint): Pointer` | `offset` bytes further, negative goes back |
| `sub(offset: number \| bigint): Pointer` | `offset` bytes back |
| `equals(other: PointerLike): boolean` | same address |
| `toString(): string` | `"Pointer(0x7FF612340000)"` |
| `toJSON(): string` | `"0x7FF612340000"`, accepted back by `new Pointer()` |

***

## Reading

A read of unmapped memory, or of a null pointer, throws `Error` with `code: "EREAD"`.

### deref

<br />

```ts theme={null}
ptr.deref(): Pointer
```

The pointer stored at this address.

```ts theme={null}
const local = client.base.add(0x1A2B3C0).deref();
```

### resolve

<br />

```ts theme={null}
ptr.resolve(...offsets: (number | bigint)[]): Pointer
```

Follows a pointer chain: every offset but the last is added and dereferenced, the last is only added. No offsets returns a copy.

```ts theme={null}
ptr.resolve(0x10, 0x20, 0x30);
// same as ptr.add(0x10).deref().add(0x20).deref().add(0x30)
```

### read

<br />

```ts theme={null}
ptr.read(type: FixedScalarType): value
ptr.read(type: FixedScalarType, count: number): TypedArray | value[]
ptr.read(type: "string" | "wstring", maxLength?: number): string
ptr.read(struct: StructType): GameStruct
ptr.read(struct: StructType, count: number): GameStruct[]
ptr.read(byteCount: number): Snapshot
```

Reads by [scalar type](#scalar-types), by struct, or copies raw bytes into a [Snapshot](#snapshot).

| Call | Returns |
| :- | :- |
| `read("float")` | one value |
| `read("float", 16)` | `count` values: a typed array for numbers (`Float32Array` here), a plain array otherwise |
| `read("string")` | inline text up to its NUL; `maxLength` is bytes for `"string"`, UTF-16 units for `"wstring"` |
| `read(Struct)` | one struct copied out, as a view |
| `read(Struct, 64)` | `count` structs sharing one copied buffer |
| `read(0x400)` | a `Snapshot` of that many bytes |

```ts theme={null}
const health = pawn.add(0x34C).read("int32");
const matrix = viewMatrix.read("float", 16);
const name = pawn.resolve(0x6E8, 0x10).read("string");
```

Struct types come from [memory.struct](/api/memory#struct). `ptr` fields read as `Pointer`s. Writing a field changes only the local copy.

### Cached and raw reads

<br />

```ts theme={null}
ptr.derefRaw(): Pointer
ptr.resolveRaw(...offsets: (number | bigint)[]): Pointer
ptr.readRaw(...): same as read
```

`read`, `deref` and `resolve` can return a value up to one frame old. The `…Raw` variants take the same arguments and always read fresh.

```ts theme={null}
const cached = ptr.read("int32");
const fresh = ptr.readRaw("int32");
```

***

## Scalar types

The type names every memory read uses: `Pointer.read`, `Snapshot.read`, `entity.read` and struct fields.

| Type | Size | Value |
| :- | :- | :- |
| `int8` `uint8` `int16` `uint16` `int32` `uint32` | 1–4 | `number` |
| `int64` `uint64` | 8 | `bigint` |
| `float` `double` | 4 / 8 | `number` |
| `bool` | 1 | `boolean` |
| `bool32` | 4 | `boolean` (Win32 `BOOL`) |
| `ptr` | 8 | `Pointer` |
| `string` `wstring` | to NUL | `string`, inline UTF-8 / UTF-16 |
| `vector2` `vector` `qangle` | 8 / 12 / 12 | [Vector2](/api/types/vector2), [Vector](/api/types/vector), [QAngle](/api/types/qangle) |
| `quaternion` | 16 | [Quaternion](/api/types/quaternion) |
| `color` | 4 | [Color](/api/types/color), RGBA bytes |

***

## Snapshot

What `read(byteCount)` returns: bytes copied out of the game once. Reading past its end throws `RangeError`.

| Member | |
| :- | :- |
| `base: Pointer` | where the bytes came from |
| `size: number` | byte count |
| `bytes: Uint8Array` | the copied bytes |
| `read(type, offset?)` | one value at `offset` (default 0) |
| `as(struct, offset?)` | a struct view over the bytes |
| `as(struct, offset, count)` | `count` consecutive views |
| `toString()` | `"Snapshot(0x…, N bytes)"` |

```ts theme={null}
const snap = pawn.read(0x1000);
const health = snap.read("int32", 0x34C);
const origin = snap.read("vector", 0x1224);
const name = snap.read("string", 0x6E8);
```

One snapshot and many reads is cheaper than many small reads.

***

## Example

Reads a list of player records behind a signature.

```ts theme={null}
import memory from "@native/memory";

const Player = memory.struct({
    name: "char[32]",
    pawn: "ptr",
    health: "int32",
    team: "uint8",
});

const hit = memory.scan("client.dll", "48 8B 0D ? ? ? ? 8B 41 10");
const list = hit && hit.add(7).add(hit.add(3).read("int32"));

on("tick", () => {
    if (!list) return;
    try {
        const base = list.deref();
        const count = base.add(0x10).read("int32");
        const players = base.add(0x18).deref().read(Player, Math.min(count, 64));
        for (const p of players)
            if (!p.pawn.isNull) console.log(p.name, p.health, p.team, p.pawn);
    } catch (e) {
        if ((e as { code?: string }).code !== "EREAD") throw e;
    }
});
```
