Appearance
aowlspt/il2cpp
Source: aowl/src/aowlspt/il2cpp.nim — 896 lines.
Bindings to the IL2CPP runtime C API, as exported by GameAssembly.dll.
This module is the reason aowlspt can reach a post-1.0 Tarkov client at all, so it is worth being explicit about why it exists and why it looks like this.
Post-1.0 Tarkov is IL2CPP: the C# was translated to C++ ahead of time and there are no managed assemblies left to load a plugin into. The usual answer is a stack that reconstructs the managed world -- BepInEx 6 IL2CPP runs a CoreCLR inside the process, Cpp2IL rebuilds proxy assemblies out of the metadata, and Il2CppInterop marshals between the two. It works, and it is an enormous amount of machinery to arrive back where you started.
None of it is necessary. Unity's IL2CPP runtime exports its own C API from GameAssembly.dll -- 242 functions, by name, unmangled -- and that API is the exact shape aowlspt's ABI already wanted: look a type up by name, look a method up on it, invoke it with an argument array. So the post-1.0 client host calls the runtime directly. No second runtime in the process, no generated assemblies, no interop layer, and the host is written in the same language as the mods it hosts.
Why the indirect calls go through C. nimony will not cast between a pointer and a proc type -- in either direction, and via an integer as well. That rules out the ordinary shape for this, which is a record of function pointers filled in by GetProcAddress. Rather than give up the runtime symbol resolution (which is not optional: inside the game the module is already loaded, and out of it the path is only known to the caller), the indirect call is done in abi/aowlspt_shim.h, one trampoline per distinct signature. There are eighteen of them and they are each one line. Compile with --passC:-I<repo>/abi.
What this module deliberately does not do: it does not read global-metadata.dat. BSG ship that file encrypted and the runtime decrypts it for itself during il2cpp_init. Going around that would be breaking a protection rather than using an interface, so everything here goes in through the runtime's own front door and sees exactly what the runtime chose to expose.
Types
Il2CppPtr
nim
Il2CppPtr* = nil pointeraowl/src/aowlspt/il2cpp.nim:44
Il2CppDomain
nim
Il2CppDomain* = Il2CppPtraowl/src/aowlspt/il2cpp.nim:46
Il2CppAssembly
nim
Il2CppAssembly* = Il2CppPtraowl/src/aowlspt/il2cpp.nim:47
Il2CppImage
nim
Il2CppImage* = Il2CppPtraowl/src/aowlspt/il2cpp.nim:48
Il2CppClass
nim
Il2CppClass* = Il2CppPtraowl/src/aowlspt/il2cpp.nim:49
Il2CppObject
nim
Il2CppObject* = Il2CppPtraowl/src/aowlspt/il2cpp.nim:50
Il2CppMethod
nim
Il2CppMethod* = Il2CppPtr ## `MethodInfo*`aowl/src/aowlspt/il2cpp.nim:51
Il2CppField
nim
Il2CppField* = Il2CppPtr ## `FieldInfo*`aowl/src/aowlspt/il2cpp.nim:52
Il2CppProperty
nim
Il2CppProperty* = Il2CppPtr ## `PropertyInfo*`aowl/src/aowlspt/il2cpp.nim:53
Il2CppType
nim
Il2CppType* = Il2CppPtraowl/src/aowlspt/il2cpp.nim:54
Il2CppString
nim
Il2CppString* = Il2CppPtraowl/src/aowlspt/il2cpp.nim:55
Il2CppThread
nim
Il2CppThread* = Il2CppPtraowl/src/aowlspt/il2cpp.nim:56
Il2CppException
nim
Il2CppException* = Il2CppPtraowl/src/aowlspt/il2cpp.nim:57
Il2CppIter
nim
Il2CppIter* = Il2CppPtraowl/src/aowlspt/il2cpp.nim:61
Entry
nim
Entry* = enum
eSetDataDir, eSetConfigDir, eInit, eShutdown,
eDomainGet, eDomainGetAssemblies, eAssemblyGetImage,
eImageGetName, eImageGetClassCount, eImageGetClass,
eClassFromName, eClassGetName, eClassGetNamespace, eClassGetParent,
eClassGetMethods, eClassGetFields, eClassGetProperties,
eClassGetMethodFromName, eClassGetFieldFromName, eClassGetPropertyFromName,
eClassGetType, eClassIsValuetype, eClassInstanceSize, eRuntimeClassInit,
eMethodGetName, eMethodGetParamCount, eMethodGetReturnType,
eMethodGetParam, eMethodGetClass,
eFieldGetName, eFieldGetType, eFieldGetOffset,
eFieldGetValue, eFieldSetValue, eFieldStaticGetValue, eFieldStaticSetValue,
ePropertyGetName, ePropertyGetGetMethod, ePropertyGetSetMethod,
eRuntimeInvoke, eObjectNew, eRuntimeObjectInit, eObjectGetClass,
eValueBox, eObjectUnbox,
eStringNew, eStringChars, eStringLength,
eTypeGetName, eTypeGetObject,
eThreadAttach, eThreadDetach, eThreadCurrent,
eGcHandleNew, eGcHandleGetTarget, eGcHandleFree,
eFree,
## Appended in one group, for the same reason as `eMethodGetFlags` below:
## the enum's order is `EntryNames`' order, and inserting renames every
## entry after the insertion point without touching a line of this file.
eClassFromType, eFieldGetFlags, eClassStaticFieldData, eGcWriteBarrier,
## Appended rather than inserted: the enum's order is the order of
## `EntryNames`, and moving an existing member renames every entry
## after it without changing a line of this file.
eMethodGetFlags,
## Managed-array construction, appended for the same append-only reason.
## `il2cpp_array_new(elementClass, length)` returns an `Il2CppArray*`; it is
## what lets a mod hand a `byte[]` to a managed method — e.g. building the
## replacement image bytes for `ImageConversion.LoadImage` (see
## `mods/textures`). Not `Essential`: a mod that never constructs an array
## does not need it, and a build that stripped it is still hostable.
eArrayNew,
## Appended, again, rather than inserted -- same append-only reason. These
## two are the runtime half of the codeGenModule code-pointer fallback (see
## `abi/aowlspt_codegen.h`): with them the walk from a `MethodInfo*` to its
## declaring image's NAME and to its metadata TOKEN needs no guessed struct
## offset for `MethodInfo`, `Il2CppClass` or `Il2CppImage`. Both were
## confirmed present in `GameAssembly.dll`'s export directory for build
## 1.1.0.1.46777 (386 exports) before being added here. Not `Essential`:
## a build without them simply loses the fallback.
eClassGetImage, eMethodGetTokenaowl/src/aowlspt/il2cpp.nim:110
Il2Cpp
nim
Il2Cpp* = object
handle*: Il2CppPtr
loaded*: bool
lastError*: int32
## Indexed by `ord(Entry)`. nimony has neither enum-indexed arrays nor
## iteration over an enum type, so the ordinal is used directly and
## `EntryNames` is kept in the same order by construction.
fns*: array[NumEntries, Il2CppPtr]
missing*: seq[string]aowl/src/aowlspt/il2cpp.nim:204
Routines
nullPtr
nim
proc nullPtr*(): Il2CppPtr {.inline.}aowl/src/aowlspt/il2cpp.nim:63
isNull
nim
proc isNull*(p: Il2CppPtr): bool {.inline.}aowl/src/aowlspt/il2cpp.nim:66
has
nim
proc has*(rt: Il2Cpp; e: Entry): bool {.inline.}aowl/src/aowlspt/il2cpp.nim:232
openIl2Cpp
nim
proc openIl2Cpp*(path: string = ""): Il2CppBinds to the IL2CPP runtime.
With no path the already-loaded GameAssembly.dll is used, which is the in-game case and the only correct one there. With a path the module is loaded, which is for out-of-process tooling.
aowl/src/aowlspt/il2cpp.nim:235
missingEssential
nim
proc missingEssential*(rt: Il2Cpp): seq[string]aowl/src/aowlspt/il2cpp.nim:271
readCString
nim
proc readCString*(p: Il2CppPtr): stringCopies a NUL-terminated ASCII string the runtime owns -- or REFUSES.
THE NIL CHECK BELOW CANNOT FIRE FOR THE CASE THAT KILLED THE CLIENT, AND THAT IS WHY THE REST OF THIS PROC EXISTS. Measured (fact #198, docs/IL2CPP_EXPORTS.md): 38 of the 241 il2cpp_* exports are TOKEN-GATED -- they take a trailing 32-byte token argument the stock signature does not have and memcmp it before doing any work. On mismatch they do not return NULL and do not abort: they tail-call a trap that seeds a per-thread MT19937-64 and returns a uniform random non-zero uint64. il2cpp_class_get_name is one of them. So p != nil passes, the first byteAt dereferences a random address, and the process dies with 0xC0000005 -- which is exactly what took the client down at 10:30:54 through fov's bindOnObject -> fullName -> className chain, faulting at +0x31 inside this loop.
A nil check is therefore not a check. What follows is: a VirtualQuery on the pointer (a random 64-bit address is overwhelmingly not committed readable memory) re-run at every page-sized step, and a printable-ASCII gate on every byte (a random address that IS mapped is overwhelmingly not an ASCII identifier). Both can fail on real input and both say so by returning "" and bumping gReadCStringRefusals, which every caller already treats as "the runtime did not answer" -- className, classNamespace, methodName, fieldName, propertyName, imageName and typeName all return "" when the export is simply absent.
This is a refusal, not a guarantee. It narrows a certain crash to an unlikely one; it does not make a gated export safe to call.
aowl/src/aowlspt/il2cpp.nim:294
setDataDir
nim
proc setDataDir*(rt: Il2Cpp; dir: string)aowl/src/aowlspt/il2cpp.nim:361
setConfigDir
nim
proc setConfigDir*(rt: Il2Cpp; dir: string)aowl/src/aowlspt/il2cpp.nim:366
init
nim
proc init*(rt: Il2Cpp; domainName: string): Il2CppDomainaowl/src/aowlspt/il2cpp.nim:371
shutdown
nim
proc shutdown*(rt: Il2Cpp)aowl/src/aowlspt/il2cpp.nim:376
domainGet
nim
proc domainGet*(rt: Il2Cpp): Il2CppDomainaowl/src/aowlspt/il2cpp.nim:380
domainGetAssemblies
nim
proc domainGetAssemblies*(rt: Il2Cpp; domain: Il2CppDomain; count: var int): Il2CppPtrReturns the raw Il2CppAssembly**; use assemblyAt to index it.
aowl/src/aowlspt/il2cpp.nim:384
assemblyAt
nim
proc assemblyAt*(assemblies: Il2CppPtr; index: int): Il2CppAssemblyIndexes the Il2CppAssembly** returned above. A pointer array, so the stride is one pointer.
aowl/src/aowlspt/il2cpp.nim:394
assemblyGetImage
nim
proc assemblyGetImage*(rt: Il2Cpp; a: Il2CppAssembly): Il2CppImageaowl/src/aowlspt/il2cpp.nim:404
imageGetName
nim
proc imageGetName*(rt: Il2Cpp; i: Il2CppImage): stringaowl/src/aowlspt/il2cpp.nim:408
imageGetClassCount
nim
proc imageGetClassCount*(rt: Il2Cpp; i: Il2CppImage): intaowl/src/aowlspt/il2cpp.nim:412
imageGetClass
nim
proc imageGetClass*(rt: Il2Cpp; i: Il2CppImage; index: int): Il2CppClassaowl/src/aowlspt/il2cpp.nim:416
methodClass
nim
proc methodClass*(rt: Il2Cpp; m: Il2CppMethod): Il2CppClassThe type a method was declared on. il2cpp_method_get_class is an exported entry point, so this is an interface call rather than a read of MethodInfo.klass at an assumed offset.
aowl/src/aowlspt/il2cpp.nim:420
classGetImage
nim
proc classGetImage*(rt: Il2Cpp; c: Il2CppClass): Il2CppImageThe assembly image a class was defined in. Exported by the runtime, so this is an interface call and not a struct-offset read -- which matters, because it is the hop the codeGenModule code-pointer fallback needs and a guessed Il2CppClass.image offset would be exactly the kind of coin flip this project refuses.
aowl/src/aowlspt/il2cpp.nim:428
methodToken
nim
proc methodToken*(rt: Il2Cpp; m: Il2CppMethod): uint32A method's metadata token. token and 0xFFFFFF is its RID within its own image, which is the index (minus one) into that image's Il2CppCodeGenModule.methodPointers. Zero means unavailable.
Called through a uint32-returning trampoline rather than the size_t one: the high half of RAX is unspecified for a uint32-returning callee, so reading it as a size_t can bring garbage back in the top 32 bits.
aowl/src/aowlspt/il2cpp.nim:437
classFromName
nim
proc classFromName*(rt: Il2Cpp; image: Il2CppImage; ns, name: string): Il2CppClassaowl/src/aowlspt/il2cpp.nim:449
className
nim
proc className*(rt: Il2Cpp; c: Il2CppClass): stringaowl/src/aowlspt/il2cpp.nim:457
classNameSafe
nim
proc classNameSafe*(rt: Il2Cpp; c: Il2CppClass): stringclassName that returns "" instead of faulting when c is a class handed back by a blind enumeration that is not safe to dereference. For scanning every class in an image to find one by name; see the host's key dump.
aowl/src/aowlspt/il2cpp.nim:462
classNamespace
nim
proc classNamespace*(rt: Il2Cpp; c: Il2CppClass): stringaowl/src/aowlspt/il2cpp.nim:471
classParent
nim
proc classParent*(rt: Il2Cpp; c: Il2CppClass): Il2CppClassaowl/src/aowlspt/il2cpp.nim:475
nextMethod
nim
proc nextMethod*(rt: Il2Cpp; c: Il2CppClass; iter: var Il2CppIter): Il2CppMethodaowl/src/aowlspt/il2cpp.nim:479
nextField
nim
proc nextField*(rt: Il2Cpp; c: Il2CppClass; iter: var Il2CppIter): Il2CppFieldaowl/src/aowlspt/il2cpp.nim:483
nextProperty
nim
proc nextProperty*(rt: Il2Cpp; c: Il2CppClass; iter: var Il2CppIter): Il2CppPropertyaowl/src/aowlspt/il2cpp.nim:487
findMethod
nim
proc findMethod*(rt: Il2Cpp; c: Il2CppClass; name: string; argc: int): Il2CppMethodaowl/src/aowlspt/il2cpp.nim:492
findField
nim
proc findField*(rt: Il2Cpp; c: Il2CppClass; name: string): Il2CppFieldaowl/src/aowlspt/il2cpp.nim:499
findProperty
nim
proc findProperty*(rt: Il2Cpp; c: Il2CppClass; name: string): Il2CppPropertyaowl/src/aowlspt/il2cpp.nim:505
classType
nim
proc classType*(rt: Il2Cpp; c: Il2CppClass): Il2CppTypeaowl/src/aowlspt/il2cpp.nim:511
classIsValueType
nim
proc classIsValueType*(rt: Il2Cpp; c: Il2CppClass): boolaowl/src/aowlspt/il2cpp.nim:515
classInstanceSize
nim
proc classInstanceSize*(rt: Il2Cpp; c: Il2CppClass): intaowl/src/aowlspt/il2cpp.nim:519
runtimeClassInit
nim
proc runtimeClassInit*(rt: Il2Cpp; c: Il2CppClass)aowl/src/aowlspt/il2cpp.nim:523
methodName
nim
proc methodName*(rt: Il2Cpp; m: Il2CppMethod): stringaowl/src/aowlspt/il2cpp.nim:527
methodParamCount
nim
proc methodParamCount*(rt: Il2Cpp; m: Il2CppMethod): intaowl/src/aowlspt/il2cpp.nim:531
methodReturnType
nim
proc methodReturnType*(rt: Il2Cpp; m: Il2CppMethod): Il2CppTypeaowl/src/aowlspt/il2cpp.nim:535
methodParam
nim
proc methodParam*(rt: Il2Cpp; m: Il2CppMethod; index: int): Il2CppTypeaowl/src/aowlspt/il2cpp.nim:539
fieldName
nim
proc fieldName*(rt: Il2Cpp; f: Il2CppField): stringaowl/src/aowlspt/il2cpp.nim:543
fieldType
nim
proc fieldType*(rt: Il2Cpp; f: Il2CppField): Il2CppTypeaowl/src/aowlspt/il2cpp.nim:547
fieldOffset
nim
proc fieldOffset*(rt: Il2Cpp; f: Il2CppField): intaowl/src/aowlspt/il2cpp.nim:551
fieldGetValue
nim
proc fieldGetValue*(rt: Il2Cpp; obj: Il2CppObject; f: Il2CppField; into: Il2CppPtr)aowl/src/aowlspt/il2cpp.nim:555
fieldSetValue
nim
proc fieldSetValue*(rt: Il2Cpp; obj: Il2CppObject; f: Il2CppField; value: Il2CppPtr)aowl/src/aowlspt/il2cpp.nim:560
fieldStaticGetValue
nim
proc fieldStaticGetValue*(rt: Il2Cpp; f: Il2CppField; into: Il2CppPtr)aowl/src/aowlspt/il2cpp.nim:565
fieldStaticSetValue
nim
proc fieldStaticSetValue*(rt: Il2Cpp; f: Il2CppField; value: Il2CppPtr)aowl/src/aowlspt/il2cpp.nim:569
propertyName
nim
proc propertyName*(rt: Il2Cpp; p: Il2CppProperty): stringaowl/src/aowlspt/il2cpp.nim:573
propertyGetter
nim
proc propertyGetter*(rt: Il2Cpp; p: Il2CppProperty): Il2CppMethodaowl/src/aowlspt/il2cpp.nim:577
propertySetter
nim
proc propertySetter*(rt: Il2Cpp; p: Il2CppProperty): Il2CppMethodaowl/src/aowlspt/il2cpp.nim:581
invoke
nim
proc invoke*(rt: Il2Cpp; m: Il2CppMethod; obj: Il2CppObject; args: Il2CppPtr; exc: var Il2CppException): Il2CppObjectargs is an Il2CppPtr to an array of pointers, or null for none.
The exception out-parameter is not optional. IL2CPP does not unwind through a C caller; a managed throw comes back here as a written-to pointer and an ignored one is a silently wrong result.
aowl/src/aowlspt/il2cpp.nim:585
objectNew
nim
proc objectNew*(rt: Il2Cpp; c: Il2CppClass): Il2CppObjectaowl/src/aowlspt/il2cpp.nim:597
objectClass
nim
proc objectClass*(rt: Il2Cpp; obj: Il2CppObject): Il2CppClassaowl/src/aowlspt/il2cpp.nim:601
arrayNew
nim
proc arrayNew*(rt: Il2Cpp; elementClass: Il2CppClass; count: int): Il2CppPtrAllocate a managed 1-D array of elementClass with count elements, e.g. a byte[] from the System.Byte class. Returns null if the runtime did not export il2cpp_array_new, if the class is null, or if count is negative — the caller then does nothing, which is the safe outcome.
The element data of the returned Il2CppArray* begins at the standard 64-bit IL2CPP array header size (32 bytes: Il2CppObject{klass, monitor} 16 + bounds 8 + max_length 8) past the pointer. A caller filling it writes count bytes from that offset, which stays inside the allocation.
aowl/src/aowlspt/il2cpp.nim:605
valueBox
nim
proc valueBox*(rt: Il2Cpp; c: Il2CppClass; data: Il2CppPtr): Il2CppObjectaowl/src/aowlspt/il2cpp.nim:619
objectUnbox
nim
proc objectUnbox*(rt: Il2Cpp; obj: Il2CppObject): Il2CppPtraowl/src/aowlspt/il2cpp.nim:623
newString
nim
proc newString*(rt: Il2Cpp; s: string): Il2CppStringaowl/src/aowlspt/il2cpp.nim:627
readString
nim
proc readString*(rt: Il2Cpp; s: Il2CppString): stringA managed System.String as UTF-8.
Only the BMP is decoded; a surrogate pair comes out as replacement characters rather than as one wrong character. Everything this is used for -- type names, member names, JSON -- is ASCII in practice, and quietly producing mojibake would be worse than visibly not handling it.
aowl/src/aowlspt/il2cpp.nim:632
typeName
nim
proc typeName*(rt: Il2Cpp; t: Il2CppType): stringThe runtime allocates this one and expects it back.
aowl/src/aowlspt/il2cpp.nim:662
threadAttach
nim
proc threadAttach*(rt: Il2Cpp; domain: Il2CppDomain): Il2CppThreadEvery thread that is not one the runtime created must attach before it calls in. Skipping this does not fail loudly -- it corrupts the GC's view of the stack and the process dies somewhere else, later.
aowl/src/aowlspt/il2cpp.nim:670
threadDetach
nim
proc threadDetach*(rt: Il2Cpp; t: Il2CppThread)aowl/src/aowlspt/il2cpp.nim:677
threadCurrent
nim
proc threadCurrent*(rt: Il2Cpp): Il2CppThreadaowl/src/aowlspt/il2cpp.nim:681
gcHandleNew
nim
proc gcHandleNew*(rt: Il2Cpp; obj: Il2CppObject; pinned: bool): uint32aowl/src/aowlspt/il2cpp.nim:685
gcHandleTarget
nim
proc gcHandleTarget*(rt: Il2Cpp; h: uint32): Il2CppObjectaowl/src/aowlspt/il2cpp.nim:689
gcHandleFree
nim
proc gcHandleFree*(rt: Il2Cpp; h: uint32)aowl/src/aowlspt/il2cpp.nim:693
splitTypeName
nim
proc splitTypeName*(qualified: string; ns, name: var string)Splits Namespace.Nested.Type at the last dot: IL2CPP keeps namespace and type name separate, and everything a mod author writes keeps them together.
aowl/src/aowlspt/il2cpp.nim:701
findClass
nim
proc findClass*(rt: Il2Cpp; qualified: string): Il2CppClassLooks a type up by qualified name across every loaded assembly.
Searching all images rather than requiring an assembly name is the point: Tarkov and SPT move types between assemblies across releases, and a mod that named the assembly would break on a release where nothing about the type itself changed.
aowl/src/aowlspt/il2cpp.nim:718
methodPointer
nim
proc methodPointer*(rt: Il2Cpp; m: Il2CppMethod): Il2CppPtrThe compiled function behind a method.
IL2CPP exposes no accessor for this, so it is read directly: it is the first field of MethodInfo and has been for every Unity version this targets. That is a layout assumption rather than an interface, so the result is checked before it is used -- a pointer that is not in an executable page means the assumption is wrong on this build, and the caller gets nothing rather than a patch aimed at the middle of a struct.
aowl/src/aowlspt/il2cpp.nim:749
fullName
nim
proc fullName*(rt: Il2Cpp; c: Il2CppClass): stringaowl/src/aowlspt/il2cpp.nim:775
methodFlags
nim
proc methodFlags*(rt: Il2Cpp; m: Il2CppMethod): uint32The method's attribute flags. Bit 0x10 is static, which is the one thing the detour path needs: a compiled instance method takes this in the first argument register and a static one does not, so getting this wrong shifts every argument by one and reports the wrong values with total confidence.
aowl/src/aowlspt/il2cpp.nim:785
methodIsStatic
nim
proc methodIsStatic*(rt: Il2Cpp; m: Il2CppMethod): boolaowl/src/aowlspt/il2cpp.nim:795
classFromType
nim
proc classFromType*(rt: Il2Cpp; t: Il2CppType): Il2CppClassThe class behind a type, exactly.
The alternative is to take the type's name and look the class up by it, which works for System.Int32 and falls apart on a generic, an array or a nested type -- the printed name is not always a name the resolver accepts. Classifying a parameter is the difference between binding a method on the fast path and refusing it, so it is worth an entry point.
aowl/src/aowlspt/il2cpp.nim:798
boxHeaderBytes
nim
proc boxHeaderBytes*(rt: Il2Cpp): intHow much of a value type's reported instance size is object header -- measured, not assumed.
il2cpp_class_get_instance_size on the game reports a value type's boxed size, header plus payload, because that is the only form the runtime allocates. That is not universal: a stand-in can just as reasonably report the payload, and both answers are self-consistent, so no single number in isolation tells them apart.
One number whose payload is known does. System.Int32 holds four bytes by definition, so whatever this runtime reports for it, minus four, is the header. System.Double only checks that answer: eight bytes of payload must report four more than Int32 does, and if it does not, this runtime is not measuring what it is assumed to be, and the fallback stands.
Getting it wrong subtracts a header that is not there, which turns every small value type into a negative width and refuses it -- an enum reported as unclassifiable, a shaped call quietly falling back to reflection, and the log saying the shape was checked. Two mods hit exactly that before this existed; the host has the same calculation in invoke.nim.
aowl/src/aowlspt/il2cpp.nim:811
valueWidth
nim
proc valueWidth*(rt: Il2Cpp; c: Il2CppClass): intThe payload width of a value type, or 0 if it is not one. See boxHeaderBytes for why the header is measured rather than named.
aowl/src/aowlspt/il2cpp.nim:847
fieldFlags
nim
proc fieldFlags*(rt: Il2Cpp; f: Il2CppField): uint32Field attributes. Bit 0x10 is static, which cannot be inferred reliably from the offset: a static field's offset is into the class's static data, not into an instance, and the two ranges can overlap.
aowl/src/aowlspt/il2cpp.nim:857
fieldIsStatic
nim
proc fieldIsStatic*(rt: Il2Cpp; f: Il2CppField): boolaowl/src/aowlspt/il2cpp.nim:864
staticFieldData
nim
proc staticFieldData*(rt: Il2Cpp; c: Il2CppClass): Il2CppPtrThe base address of a class's static storage, which is what a static field's offset is relative to.
aowl/src/aowlspt/il2cpp.nim:867
writeBarrier
nim
proc writeBarrier*(rt: Il2Cpp; obj, field, value: Il2CppPtr)Stores a reference into a field and tells the collector.
Writing the pointer directly is faster and is how an object gets collected while something still points at it: a generational collector needs to know that an old object now references a young one, and a plain store does not tell it. The crash arrives at the next collection, far from the write.
aowl/src/aowlspt/il2cpp.nim:873
hasWriteBarrier
nim
proc hasWriteBarrier*(rt: Il2Cpp): boolaowl/src/aowlspt/il2cpp.nim:884
writeBarrierFn
nim
proc writeBarrierFn*(rt: Il2Cpp): Il2CppPtrThe resolved entry itself, for the fast path.
writeBarrier above is the pleasant form and costs a has test and an Il2Cpp copy per call. A FieldBinding is bound once and written many times, so it keeps this pointer instead and branches on nil -- which is also how it records that a runtime without the entry was bound against, rather than silently deciding that question again at every write.
aowl/src/aowlspt/il2cpp.nim:886

