modwright 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -4
- package/bridges/bg3-se/README.md +3 -2
- package/bridges/bg3-se/bridge.json +1 -1
- package/bridges/cp2077-cet/ModWrightBridge/probes.lua +228 -1
- package/bridges/cp2077-cet/README.md +6 -3
- package/bridges/cp2077-cet/bridge.json +2 -2
- package/dist/core/bridge/remove.js +48 -0
- package/dist/core/build/stage.js +3 -2
- package/dist/core/ini.js +26 -0
- package/dist/core/knowledge/facts.js +0 -12
- package/dist/core/logs/registry.js +19 -0
- package/dist/core/logs/scan.js +44 -5
- package/dist/core/logs/triage.js +79 -2
- package/dist/core/managers/mo2.js +121 -0
- package/dist/core/managers/vortex.js +52 -0
- package/dist/core/ownership/index.js +364 -0
- package/dist/core/project/index.js +1 -1
- package/dist/core/project/load.js +5 -0
- package/dist/core/testplan/registry.js +84 -2
- package/dist/core/writes/index.js +206 -0
- package/dist/index.js +0 -2
- package/dist/server.js +180 -15
- package/dist/surfaces/baldursgate3/surface.js +3 -0
- package/dist/surfaces/baldursgate3/validators/story.js +3 -3
- package/dist/surfaces/baldursgate3/writes.js +152 -0
- package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +2 -2
- package/dist/surfaces/cyberpunk2077/logs.js +82 -3
- package/dist/surfaces/cyberpunk2077/surface.js +11 -0
- package/dist/surfaces/cyberpunk2077/writes.js +67 -0
- package/dist/surfaces/skyrimse/loadorder.js +2 -26
- package/dist/surfaces/skyrimse/surface.js +1 -0
- package/dist/surfaces/stardewvalley/logs.js +41 -0
- package/dist/surfaces/stardewvalley/surface.js +2 -0
- package/dist/surfaces/stardewvalley/writes.js +136 -0
- package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +29 -0
- package/knowledge/baldursgate3/osiris.tags.yaml +78 -0
- package/knowledge/cyberpunk2077/cet.natives.yaml +48 -0
- package/knowledge/cyberpunk2077/cet.rtti-binding.yaml +227 -0
- package/knowledge/cyberpunk2077/cet.sandbox.yaml +22 -0
- package/knowledge/cyberpunk2077/codeware.overview.yaml +63 -16
- package/knowledge/cyberpunk2077/codeware.systems.yaml +152 -33
- package/knowledge/cyberpunk2077/entities.queries.yaml +275 -0
- package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +30 -4
- package/knowledge/cyberpunk2077/rtti.game-systems.yaml +224 -0
- package/package.json +1 -1
|
@@ -384,3 +384,32 @@ facts:
|
|
|
384
384
|
Whether the journal .lsx under Mods/<Mod>/Story/Journals loads as loose .lsx was not separated
|
|
385
385
|
from the missing compile; the RootTemplates rule (loose .lsx not loaded, .lsf needed) suggests
|
|
386
386
|
it does not.
|
|
387
|
+
- id: bg3.dialogue.osiris-goals.prefix-convention-is-a-compiler-warning-in-lslib
|
|
388
|
+
claim: In LSLib's Osiris story compiler a database whose name does not start with "DB", a PROC whose
|
|
389
|
+
name does not start with "PROC" or a query whose name does not start with "QRY" produces a
|
|
390
|
+
warning (W26 DbNamingStyle, W23 RuleNamingStyle), never an error, and W23 is switched off by
|
|
391
|
+
default; the story still compiles.
|
|
392
|
+
status: inferred
|
|
393
|
+
source: Norbyte/lslib@6f5f698 LSLib/LS/Story/Compiler/Compiler.cs VerifyIRRule ("Name of PROC ...
|
|
394
|
+
should start with the prefix PROC", "Name of Query ... should start with the prefix QRY"),
|
|
395
|
+
VerifyDatabases ("Name of database ... should start with the prefix DB");
|
|
396
|
+
CompilationContext.cs DiagnosticCode RuleNamingStyle = "W23", DbNamingStyle = "W26",
|
|
397
|
+
CompilationLog() WarningSwitches[RuleNamingStyle] = false
|
|
398
|
+
tags:
|
|
399
|
+
- osiris
|
|
400
|
+
- naming
|
|
401
|
+
- db-prefix
|
|
402
|
+
- proc-prefix
|
|
403
|
+
- qry-prefix
|
|
404
|
+
- compiler
|
|
405
|
+
- warning
|
|
406
|
+
detail: "This is the community compiler's behaviour, not Larian's in-engine one: there the prefix is
|
|
407
|
+
a style check. Nothing read in that source supports the claim, attributed to a Nexus article,
|
|
408
|
+
that a missing prefix silently disables the whole story component; until the game's own
|
|
409
|
+
compiler is shown to behave differently, the bg3.story.osiris-naming-convention validator rule
|
|
410
|
+
stays informational. The checks compare the first 2-4 characters case-insensitively
|
|
411
|
+
(\"PROC\"/\"QRY\"/\"DB\"), and the PROC and QRY checks skip names no longer than the prefix,
|
|
412
|
+
so \"Proc_X\" and \"dbFoo\" pass them."
|
|
413
|
+
related:
|
|
414
|
+
- id: bg3.dialogue.osiris-goals.qry-proc-color-coding
|
|
415
|
+
detail: Agrees on the convention itself; adds what the compiler does when it is broken.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
game: baldursgate3
|
|
2
|
+
topic: osiris.tags
|
|
3
|
+
title: "Osiris entity tags: SetTag/IsTagged/ClearTag and the tag events, from Larian's modding wiki
|
|
4
|
+
and the generated stubs"
|
|
5
|
+
facts:
|
|
6
|
+
- id: bg3.osiris.tags.settag-istagged-cleartag-signatures
|
|
7
|
+
claim: Osiris tags an entity with the call SetTag((GUIDSTRING)_Target, (TAG)_Tag), tests it with the
|
|
8
|
+
query IsTagged([in](GUIDSTRING)_Target, [in](TAG)_Tag, [out](INTEGER)_Bool), and removes it
|
|
9
|
+
with the call ClearTag((GUIDSTRING)_Source, (TAG)_Tag). In the Script Extender's Osi table
|
|
10
|
+
these are Osi.SetTag(target, tag), Osi.IsTagged(target, tag) -> integer, and
|
|
11
|
+
Osi.ClearTag(target, tag).
|
|
12
|
+
status: verified
|
|
13
|
+
source: wiki pages SetTag ("call SetTag((GUIDSTRING)_Target, (TAG)_Tag)"), IsTagged ("query
|
|
14
|
+
IsTagged([in](GUIDSTRING)_Target, [in](TAG)_Tag, [out](INTEGER)_Bool)"), ClearTag
|
|
15
|
+
("ClearTag((GUIDSTRING)_Source, (TAG)_Tag)"); LaughingLeader/BG3ModdingTools@ab343b6
|
|
16
|
+
generated/ Osi.lua:4273-4275 (SetTag), :1840-1843 (IsTagged, @return integer bool), :2570-2572
|
|
17
|
+
(ClearTag).
|
|
18
|
+
tags:
|
|
19
|
+
- osiris
|
|
20
|
+
- tag
|
|
21
|
+
- settag
|
|
22
|
+
- istagged
|
|
23
|
+
- cleartag
|
|
24
|
+
- signature
|
|
25
|
+
detail: "Verified as declared signatures only; what the calls do at runtime was not observed. The
|
|
26
|
+
wiki's own examples: SetTag(_ParentA, (TAG)_SHADOW_CURSE_IMMUNE) inside a PROC;
|
|
27
|
+
IsTagged(_Player, (TAG)_DARK_URGE, 1) as an AND condition in a QRY (the trailing 1 binds the
|
|
28
|
+
out-param to \"true\"). A TAG is a UUID-keyed resource, referenced in story scripts by a named
|
|
29
|
+
constant; the wiki examples use (TAG)_NAME constants, which the story compiler resolves."
|
|
30
|
+
- id: bg3.osiris.tags.cleartag-only-clears-osiris-set-tags
|
|
31
|
+
claim: ClearTag removes only tags that Osiris itself set. Tags placed via the editor sidebar on a
|
|
32
|
+
root template or on an object instance cannot be cleared this way (a polymorph status replaces
|
|
33
|
+
them with the target template's, but plain Transform calls leave them), and tags set from
|
|
34
|
+
Anubis or from dialogs can only be cleared from those same systems (dialog tags via
|
|
35
|
+
ClearDialogTag).
|
|
36
|
+
status: community
|
|
37
|
+
source: wiki page ClearTag, "Further Information" section (last edited 2024-12-16); ClearDialogTag
|
|
38
|
+
is Osi.ClearDialogTag(target, tag) at LaughingLeader/BG3ModdingTools@ab343b6
|
|
39
|
+
generated/Osi.lua:2522-2524.
|
|
40
|
+
tags:
|
|
41
|
+
- osiris
|
|
42
|
+
- tag
|
|
43
|
+
- cleartag
|
|
44
|
+
- gotcha
|
|
45
|
+
- roottemplate
|
|
46
|
+
- dialog
|
|
47
|
+
related:
|
|
48
|
+
- bg3.osiris.tags.settag-istagged-cleartag-signatures
|
|
49
|
+
detail: "Practical consequence for a probe or test row: IsTagged answers for every tag source, so a
|
|
50
|
+
tag that \"will not clear\" is usually one that was never Osiris-owned — check the root
|
|
51
|
+
template's sidebar tags before treating a failed ClearTag as a scripting bug."
|
|
52
|
+
- id: bg3.osiris.tags.tag-events-and-inventory-tag-queries
|
|
53
|
+
claim: "Osiris declares tag-related events — CharacterTagEvent(character, tag, event),
|
|
54
|
+
TagCleared(target, tag), TagEvent(tag, event), TagSet(target, tag) — and inventory queries
|
|
55
|
+
that take their tags as a string rather than a TAG: GetByTagInInventory(tags, inventoryHolder)
|
|
56
|
+
-> GUIDSTRING, GetItemByTagInInventory(tags, inventoryHolder) -> ITEM, and
|
|
57
|
+
TaggedItemsGetCountInMagicPockets(tags, source) -> integer. HasAppearanceVisualTag(character,
|
|
58
|
+
tag) -> integer tests visual tags separately."
|
|
59
|
+
status: verified
|
|
60
|
+
source: LaughingLeader/BG3ModdingTools@ab343b6 generated/Osi.Events.lua:237
|
|
61
|
+
CharacterTagEvent(character, tag, event); Osi.Events.lua:1197 TagCleared(target, tag);
|
|
62
|
+
Osi.Events.lua:1201 TagEvent(tag, event); Osi.Events.lua:1205 TagSet(target, tag);
|
|
63
|
+
generated/Osi.lua:796-799 (GetByTagInInventory), :1006-1009 (GetItemByTagInInventory),
|
|
64
|
+
:2136-2139 (TaggedItemsGetCountInMagicPockets), :1347-1350 (HasAppearanceVisualTag).
|
|
65
|
+
tags:
|
|
66
|
+
- osiris
|
|
67
|
+
- tag
|
|
68
|
+
- event
|
|
69
|
+
- inventory
|
|
70
|
+
- tag-expression
|
|
71
|
+
related:
|
|
72
|
+
- bg3.osiris.tags.settag-istagged-cleartag-signatures
|
|
73
|
+
detail: Verified as declared signatures only; when the events fire and what the queries return at
|
|
74
|
+
runtime was not observed. The wiki's search-result snippets describe the string argument as a
|
|
75
|
+
tag expression, but its grammar (how several tags combine) is not established by anything read
|
|
76
|
+
here; test it before relying on more than a single tag name. Recorded so a future BG3 probe
|
|
77
|
+
kind for "is the scaffolded entity tagged" has its call shapes on file; none is registered yet
|
|
78
|
+
(bg3.entity.* probes go through the Script Extender's Ext.Entity, not Osiris tags).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
game: cyberpunk2077
|
|
2
|
+
topic: cet.natives
|
|
3
|
+
title: CET-added native functions used to set up a test (spawn, teleport, level)
|
|
4
|
+
facts:
|
|
5
|
+
- id: cp2077.cet.natives.exentityspawner-spawn-despawn
|
|
6
|
+
claim: "`exEntitySpawner` is a native that CET adds. `exEntitySpawner.Spawn(entPath, transform,
|
|
7
|
+
appearance)` spawns an entity from a resource path (backslash form, e.g.
|
|
8
|
+
`base\\characters\\entities\\gang\\gang__wraith_ma.ent`) at the given WorldTransform and
|
|
9
|
+
returns an entity id handle (userdata); `Game.FindEntityByID(handle)` resolves it to the
|
|
10
|
+
entity object. `exEntitySpawner.Despawn(entity)` despawns it, and takes the resolved object,
|
|
11
|
+
not the id handle."
|
|
12
|
+
status: verified
|
|
13
|
+
verified_on: "2.31"
|
|
14
|
+
source: ModWright field verification
|
|
15
|
+
tags:
|
|
16
|
+
- cet
|
|
17
|
+
- native
|
|
18
|
+
- spawn
|
|
19
|
+
- despawn
|
|
20
|
+
- workbench
|
|
21
|
+
- exentityspawner
|
|
22
|
+
detail: "Backs the probe cp2077.world.spawn. Two caveats from the 2026-09-12 run: spawning at the
|
|
23
|
+
player's own transform puts the player inside the spawned entity until they separate; and the
|
|
24
|
+
id handle does not survive across bridge calls (each cp2077.lua.eval is a fresh state), so a
|
|
25
|
+
despawn or respawn has to resolve the entity within the same call. No call is known that turns
|
|
26
|
+
a stored id string back into a live EntityID. CET's WorldFunctionalTests
|
|
27
|
+
(SpawnEntity/DespawnEntity) is the other spawn native it adds; this fact is about
|
|
28
|
+
exEntitySpawner, the one exercised. On 2026-10-04 the spawned entity appeared, but
|
|
29
|
+
`Game.FindEntityByID(handle)` in the same call returned nothing: the entity arrives after
|
|
30
|
+
`Spawn` returns, so resolve it in a later call."
|
|
31
|
+
- id: cp2077.cet.natives.teleport-and-setlevel-work-from-cet
|
|
32
|
+
claim: From CET Lua, `Game.GetTeleportationFacility():Teleport(Game.GetPlayer(), Vector4.new(x, y,
|
|
33
|
+
z, 1), EulerAngles.new(0, 0, yaw))` moves the player to that position, and
|
|
34
|
+
`Game.SetLevel("Level", n, 1)` sets the player level; both take effect at once. The level
|
|
35
|
+
reads back as `Game.GetStatsSystem():GetStatValue(Game.GetPlayer():GetEntityID(),
|
|
36
|
+
gamedataStatType.Level)`.
|
|
37
|
+
status: verified
|
|
38
|
+
verified_on: "2.31"
|
|
39
|
+
source: ModWright field verification
|
|
40
|
+
tags:
|
|
41
|
+
- cet
|
|
42
|
+
- native
|
|
43
|
+
- teleport
|
|
44
|
+
- level
|
|
45
|
+
- workbench
|
|
46
|
+
detail: Backs the probes cp2077.player.teleport and cp2077.player.level. The teleport's z settled
|
|
47
|
+
0.01 above the target, which is the ground under the player. A save that has been teleported
|
|
48
|
+
or levelled this way should not be kept.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
game: cyberpunk2077
|
|
2
|
+
topic: cet.rtti-binding
|
|
3
|
+
title: "How Cyber Engine Tweaks binds the game's RTTI to Lua: globals, the Game table, member calls,
|
|
4
|
+
out-params, structs, arrays"
|
|
5
|
+
facts:
|
|
6
|
+
- id: cp2077.cet.rtti-global-functions-reachable-bare-and-via-game-table
|
|
7
|
+
claim: "CET exposes every non-member, non-exec RTTI global function to Lua under three names: the
|
|
8
|
+
bare short name as a sandbox global (`TSF_NPC()`), `Game.TSF_NPC()` (short-name lookup, with
|
|
9
|
+
an overload dispatcher when several arities exist), and `Game[\"TSF_NPC;\"]()` (exact RTTI
|
|
10
|
+
full name). So the TSF_*/TSQ_* target-search constructors listed in the RTTI dump's
|
|
11
|
+
globals.json are callable from a CET console or mod without any wrapper."
|
|
12
|
+
status: inferred
|
|
13
|
+
source: 'src/reverse/RTTIMapper.cpp:186-213 (RegisterDirectGlobals walks apRtti->funcs and sets
|
|
14
|
+
aLuaGlobal[shortName] = RTTIHelper::Get().ResolveFunction(shortName) for every function whose
|
|
15
|
+
full name has no "::" and whose short name has no ";", skipping flags.isExec; the target table
|
|
16
|
+
is the sandbox globals, RTTIMapper.cpp:34, which is the fallback environment of every mod and
|
|
17
|
+
of the console, src/scripting/Sandbox.cpp:6-11, Scripting.cpp:644);
|
|
18
|
+
src/scripting/Scripting.cpp:487 (globals["Game"] = this) and :675-686 (Scripting::Index ->
|
|
19
|
+
RTTIHelper::ResolveFunction); src/reverse/RTTIHelper.cpp:316-343 (branch on ";" in the name:
|
|
20
|
+
full-name GetFunction vs short-name FindFunctions, RTTIHelper.cpp:238-259, with
|
|
21
|
+
MakeInvokableOverload at :444).'
|
|
22
|
+
tags:
|
|
23
|
+
- cet
|
|
24
|
+
- lua
|
|
25
|
+
- rtti
|
|
26
|
+
- globals
|
|
27
|
+
- game-table
|
|
28
|
+
- tsf
|
|
29
|
+
- binding
|
|
30
|
+
related:
|
|
31
|
+
- cp2077.entities.gameobject-getentitiesaroundobject-filter-constructors-exist
|
|
32
|
+
detail: 'One residual the source cannot settle: the bare-global registration is skipped when the
|
|
33
|
+
game marks a function isExec. Nothing in CET says whether TSF_* carry that flag, so the bare
|
|
34
|
+
form is very likely but not proven; the two Game[...] forms do not depend on it. A single
|
|
35
|
+
console line settles all three at once: print(type(TSF_NPC), type(Game.TSF_NPC),
|
|
36
|
+
type(Game["TSF_NPC;"])).'
|
|
37
|
+
- id: cp2077.cet.game-table-resolves-globals-then-scriptgameinstance-statics-and-throws-on-miss
|
|
38
|
+
claim: "`Game.Foo` resolves first against RTTI global functions and then against
|
|
39
|
+
ScriptGameInstance's static functions, including script-added ones (a redscript
|
|
40
|
+
`@addMethod(GameInstance)` registers a global named \"ScriptGameInstance::Foo;...\" which CET
|
|
41
|
+
indexes per class), with the GameInstance parameter supplied automatically. A name that
|
|
42
|
+
resolves nowhere raises a Lua error rather than returning nil, and a successful resolution is
|
|
43
|
+
memoized on the Game table."
|
|
44
|
+
status: inferred
|
|
45
|
+
source: src/reverse/RTTIHelper.cpp:56 (m_pGameInstanceType = GetClass("ScriptGameInstance"));
|
|
46
|
+
:316-343 (globals first, then FindFunctions(m_pGameInstanceType, hash, false)); :59-80
|
|
47
|
+
(ParseGlobalStatics indexes every "Class::Func" global into m_extendedFunctions) and :283-294
|
|
48
|
+
(the static branch searches that map alongside staticFuncs); :598 (GameInstance and out params
|
|
49
|
+
excluded from the arity check) and :657-671 (GameInstance auto-supplied, erroring "can't be
|
|
50
|
+
used yet because game instance is not ready" before the engine is up);
|
|
51
|
+
src/scripting/Scripting.cpp:686 ("Function Foo is not a GameInstance member and is not a
|
|
52
|
+
global.") with s_cThrowLuaErrors = true at RTTIHelper.cpp:17; Scripting.cpp:677-702
|
|
53
|
+
(memoization into m_properties on success only).
|
|
54
|
+
tags:
|
|
55
|
+
- cet
|
|
56
|
+
- lua
|
|
57
|
+
- game-table
|
|
58
|
+
- scriptgameinstance
|
|
59
|
+
- codeware
|
|
60
|
+
- addmethod
|
|
61
|
+
- error-handling
|
|
62
|
+
related:
|
|
63
|
+
- cp2077.rtti.scriptgameinstance-getxxx-system-getters
|
|
64
|
+
- cp2077.entities.gettaggedids-is-a-dynamic-entity-registry-not-a-node-lookup
|
|
65
|
+
detail: "This is why Game.GetDynamicEntitySystem() (Codeware's @addMethod addition, attested in a
|
|
66
|
+
shipped CET mod's script) and Game.GetGameTagSystem() (a native ScriptGameInstance static) go
|
|
67
|
+
through the same door. Probe-design consequence: a bare-global existence check (type(TSF_NPC))
|
|
68
|
+
is nil on a miss, but Game.Missing() throws — every handler in probes.lua that touches Game.*
|
|
69
|
+
must stay pcall-wrapped, which they are. The \"how redscript names an @addMethod(GameInstance)
|
|
70
|
+
function\" half is one reading step beyond CET's code (CET only shows it handles Class::Func
|
|
71
|
+
globals); the attested Game.GetDynamicEntitySystem() call is the evidence that the step holds
|
|
72
|
+
in practice."
|
|
73
|
+
- id: cp2077.cet.member-calls-via-colon-and-out-params-as-extra-returns
|
|
74
|
+
claim: "An RTTI object reaches Lua as a StrongReference/WeakReference/ClassReference userdata whose
|
|
75
|
+
__index resolves properties first, then member functions, so `obj:Method(args)` is the
|
|
76
|
+
supported call form. Out-parameters are never taken from Lua: CET allocates them and appends
|
|
77
|
+
their values after the return value, in declaration order, so a function declared `Bool
|
|
78
|
+
F(CName tag, out array<ref<entEntity>> entities)` is called `local ok, entities = obj:F(tag)`.
|
|
79
|
+
A static function can also be called through an instance."
|
|
80
|
+
status: inferred
|
|
81
|
+
source: "src/scripting/Scripting.cpp:186-200 (usertype __index -> ClassType::Index);
|
|
82
|
+
src/reverse/Type.cpp:229-256 (Index_Impl: properties, then ResolveFunction(class, name,
|
|
83
|
+
hasHandle), cached on the object; :238 a same-named property wins over a method);
|
|
84
|
+
src/reverse/RTTIHelper.cpp:514-566 (ResolveHandle pops the colon-call self as the RED context,
|
|
85
|
+
type-checked against the function's class at :551-553); :673-681 (out params get zeroed
|
|
86
|
+
placeholders) and :742-757 (results = return value, then every isOut param); :598 (out params
|
|
87
|
+
excluded from arity); :386-395 (instance call falls back to static scope)."
|
|
88
|
+
tags:
|
|
89
|
+
- cet
|
|
90
|
+
- lua
|
|
91
|
+
- rtti
|
|
92
|
+
- member-call
|
|
93
|
+
- out-params
|
|
94
|
+
- binding
|
|
95
|
+
related:
|
|
96
|
+
- cp2077.entities.native-gametagsystem-finds-entities-by-tag
|
|
97
|
+
detail: "Direct consequence for the native tag lookup: gameGameTagSystem::GetAllMatchingEntities
|
|
98
|
+
returns two Lua values, `ok, entities`, not the array alone. GetAnyMatchingEntity has no out
|
|
99
|
+
param and returns the handle directly."
|
|
100
|
+
- id: cp2077.cet.native-struct-values-are-classreference-copies-that-round-trip
|
|
101
|
+
claim: A non-handle RTTI class/struct value returned to Lua (for example the gameTargetSearchFilter
|
|
102
|
+
a TSF_* constructor returns) becomes a ClassReference userdata holding its own copy of the
|
|
103
|
+
struct, and passing that userdata back as a struct-typed parameter hands the copy's pointer
|
|
104
|
+
through unchanged. Building such a struct from a Lua table is disabled in CET, so the
|
|
105
|
+
constructor functions are the only way to obtain one.
|
|
106
|
+
status: inferred
|
|
107
|
+
source: "src/reverse/Converter.h:192-247 (ClassConverter: ToLua wraps in ClassReference(type,
|
|
108
|
+
value); ToRED takes aObject.as<ClassReference*>()->GetHandle(), else nullptr; the Lua-table
|
|
109
|
+
path is commented out at :213-227; Is() at :238-247 matches any Class type);
|
|
110
|
+
src/reverse/ClassReference.cpp:9-11 (constructor allocates and Assigns a copy)."
|
|
111
|
+
tags:
|
|
112
|
+
- cet
|
|
113
|
+
- lua
|
|
114
|
+
- rtti
|
|
115
|
+
- struct
|
|
116
|
+
- classreference
|
|
117
|
+
- tsf
|
|
118
|
+
- binding
|
|
119
|
+
related:
|
|
120
|
+
- cp2077.entities.gameobject-getentitiesaroundobject-filter-constructors-exist
|
|
121
|
+
detail: "This removes the \"guessing a native struct layout\" worry recorded earlier for
|
|
122
|
+
GetEntitiesAroundObject(range, searchFilter): the filter is obtained from TSF_NPC()/
|
|
123
|
+
TSF_All(mask)/... and passed straight back. Caveat from the same code: ToRED does not check
|
|
124
|
+
that the ClassReference's type matches the parameter type, so passing the wrong struct is not
|
|
125
|
+
caught by CET. The struct has no listed properties in the RTTI dump, so indexing it from Lua
|
|
126
|
+
yields nothing."
|
|
127
|
+
- id: cp2077.cet.float-params-from-numbers-and-array-returns-as-tables-with-nil-holes
|
|
128
|
+
claim: A Float parameter accepts a plain Lua number (20 and 20.0 alike). An array return value
|
|
129
|
+
becomes a fresh 1-based Lua table; elements that are handles become StrongReference userdata,
|
|
130
|
+
but a null handle inside the array becomes nil, so `#t` and ipairs can stop early — count with
|
|
131
|
+
a pairs loop when the array may contain empty slots.
|
|
132
|
+
status: inferred
|
|
133
|
+
source: src/reverse/LuaRED.h:56-71 (arithmetic types converted from sol::type::number, cdata via
|
|
134
|
+
tostring/tonumber); src/scripting/Scripting.cpp:767-782 (array -> sol::table, result[i+1] =
|
|
135
|
+
ToLua(el)); :748-757 (a null handle returns sol::nil).
|
|
136
|
+
tags:
|
|
137
|
+
- cet
|
|
138
|
+
- lua
|
|
139
|
+
- rtti
|
|
140
|
+
- float
|
|
141
|
+
- array
|
|
142
|
+
- nil-hole
|
|
143
|
+
- binding
|
|
144
|
+
related:
|
|
145
|
+
- cp2077.entities.gameobject-getnpcsaroundobject-exists-in-rtti
|
|
146
|
+
detail: "probes.lua's existing handlers use ipairs over GetTaggedIDs results (EntityID values, not
|
|
147
|
+
handles, so no holes there). The nearby handler iterates handle arrays and should count with
|
|
148
|
+
pairs rather than #; the nil-hole consequence is one reading step from the nil return, hence
|
|
149
|
+
inferred like the rest."
|
|
150
|
+
- id: cp2077.cet.enum-params-accept-number-string-or-enum-but-undeclared-values-become-zero
|
|
151
|
+
claim: "An RTTI enum parameter accepts a Lua number, a member-name string, or an Enum userdata
|
|
152
|
+
(Enum.new(typeName, nameOrValue)), and every RTTI enum is also reachable as a global table
|
|
153
|
+
(TSFMV.Obj_Device). But a number or name that is not a declared member is replaced by 0
|
|
154
|
+
silently: only Enum.new with a misspelled TYPE name raises a Lua error. So an ORed combination
|
|
155
|
+
of members the game did not itself declare (e.g. 31 for every Obj_* bit of
|
|
156
|
+
gametargetingSystemSearchFilterMaskValue) reaches the engine as 0."
|
|
157
|
+
status: inferred
|
|
158
|
+
source: 'src/reverse/Converter.h:67-111 (EnumConverter::ToRED: Enum userdata with type check; nil ->
|
|
159
|
+
0; sol::type::number -> Enum(type, uint32); string -> Enum(type, name));
|
|
160
|
+
src/reverse/Enum.cpp:42-52 (SetValueSafe assigns only when the value matches a declared entry;
|
|
161
|
+
m_value defaults to 0 at Enum.h:35) and :105-120 (SetValueByName, same for names);
|
|
162
|
+
src/reverse/RTTIHelper.cpp:711-714 (the "parameter N must be X" error only fires on a null
|
|
163
|
+
result, which these paths never produce); src/scripting/Scripting.cpp:219-221 (Enum.new
|
|
164
|
+
constructors) and :223-234 (EnumInt); src/reverse/RTTIMapper.cpp:164-171 and :215-219
|
|
165
|
+
(EnumStatic globals and the TSFMV alias); src/reverse/EnumStatic.cpp:15-33 (any member name
|
|
166
|
+
indexes, typos included). Dump: enums.json lists exactly 20 declared TSFMV values, 31 not
|
|
167
|
+
among them.'
|
|
168
|
+
tags:
|
|
169
|
+
- cet
|
|
170
|
+
- lua
|
|
171
|
+
- rtti
|
|
172
|
+
- enum
|
|
173
|
+
- tsfmv
|
|
174
|
+
- silent-failure
|
|
175
|
+
- binding
|
|
176
|
+
related:
|
|
177
|
+
- cp2077.entities.gameobject-getentitiesaroundobject-filter-constructors-exist
|
|
178
|
+
detail: "Probe-design consequence: never pass a computed mask. Build filters from declared members
|
|
179
|
+
one at a time — TSF_Any(Enum.new(\"gametargetingSystemSearchFilterMaskValue\",
|
|
180
|
+
\"Obj_Device\")) — and combine with TSF_Or/TSF_And (2 to 4 filters; the dump marks tsf3/tsf4
|
|
181
|
+
optional and CET honours optional params, RTTIHelper.cpp:784-788). Use the Enum.new form so a
|
|
182
|
+
wrong type name is a loud error. Which member a sector-placed prop matches (Obj_Device,
|
|
183
|
+
Obj_Other, or none if it has no targeting component) is exactly what a live run must observe;
|
|
184
|
+
print(EnumInt(Enum.new(\"gametargetingSystemSearchFilterMaskValue\", 31))) printing 0 confirms
|
|
185
|
+
the silent-zero path live in one line. Native and script-defined globals register and resolve
|
|
186
|
+
by the same code (RTTIMapper.cpp:188-210; RTTIHelper.cpp:316); CET reads no native/script
|
|
187
|
+
flag. Which is which, per NativeDB's decoder (next fact): TSF_All/Any/Not/ And/Or (plain full
|
|
188
|
+
names) are native statics; TSF_NPC/TSF_EnemyNPC/TSF_Quickhackable (mangled \"Name;\" full
|
|
189
|
+
names) are script-side wrappers — an earlier draft of this fact had that backwards."
|
|
190
|
+
- id: cp2077.cet.nativedb-lua-generator-and-flag-layout-corroborate-the-binding
|
|
191
|
+
claim: "NativeDB, the community viewer built on the same RTTI dump, independently encodes the same
|
|
192
|
+
calling conventions: its \"Copy call\" generator for Lua · CET renders a global function as
|
|
193
|
+
Game.Name(...), a static member as Class.Name(...) with GameInstance aliased to Game, an
|
|
194
|
+
instance member as obj:Name(...), and types a CName parameter as `string | CName`. Its flag
|
|
195
|
+
decoder reads the dump's per-function flag word as bits isPrivate, isProtected, isNative,
|
|
196
|
+
isStatic, isFinal, isThreadSafe, isEvent, isConst, isQuest, isTimer — no exec bit — so the
|
|
197
|
+
dump cannot say whether a function is exec-only."
|
|
198
|
+
status: inferred
|
|
199
|
+
source: "rayshader/cp2077-nativedb at b5d29af: src/shared/formatters/lua.formatter.ts
|
|
200
|
+
(formatStaticCall -> `Game.${func.name}`, formatMemberStaticCall -> `${Class}.${name}` with
|
|
201
|
+
formatAlias mapping GameInstance to Game, formatMemberCall -> `${self}:${name}`,
|
|
202
|
+
LuaPrimitiveDef.CName = 'string | CName'); src/shared/red-ast/red-function.ast.ts:85-105
|
|
203
|
+
(fromJson decodes json.d) and :145-156 (enum RedFunctionFlags). Decoded flag words from
|
|
204
|
+
classes.json/globals.json: TSF_All/Any/Not/And/Or = 12 (isNative, isStatic);
|
|
205
|
+
TSF_NPC/EnemyNPC/Quickhackable = 40 (isStatic, isThreadSafe — script-defined);
|
|
206
|
+
ScriptGameInstance::GetGameTagSystem and GetTargetingSystem = 12 (native static);
|
|
207
|
+
gameGameTagSystem::GetAnyMatchingEntity/GetAllMatchingEntities and
|
|
208
|
+
gametargetingTargetingSystem::GetLookAtObject = 4 (native instance);
|
|
209
|
+
gameObject::GetNPCsAroundObject = 48 (isFinal, isThreadSafe — script-defined);
|
|
210
|
+
entEntity::GetCurrentAppearanceName = 132 (native, const)."
|
|
211
|
+
tags:
|
|
212
|
+
- cet
|
|
213
|
+
- lua
|
|
214
|
+
- rtti
|
|
215
|
+
- nativedb
|
|
216
|
+
- flags
|
|
217
|
+
- corroboration
|
|
218
|
+
related:
|
|
219
|
+
- cp2077.cet.rtti-global-functions-reachable-bare-and-via-game-table
|
|
220
|
+
- cp2077.cet.game-table-resolves-globals-then-scriptgameinstance-statics-and-throws-on-miss
|
|
221
|
+
detail: "Two consequences. First, the `string | CName` typing is a second, independent source for
|
|
222
|
+
passing plain Lua strings where a CName is expected, which probes.lua already relies on
|
|
223
|
+
(GetTaggedIDs(<tag>) with a plain string is attested live). Second, the exec question stays
|
|
224
|
+
open but is now bounded: CET skips bare-global registration only for functions the engine
|
|
225
|
+
flags isExec, that flag is not in the dump's word at all, and exec functions are console cheat
|
|
226
|
+
commands by design, so a TSF_* constructor carrying it would be surprising. The Game.TSF_X
|
|
227
|
+
form is unaffected either way and is what NativeDB itself generates."
|
|
@@ -217,3 +217,25 @@ facts:
|
|
|
217
217
|
- hook
|
|
218
218
|
- debugging
|
|
219
219
|
- bridge
|
|
220
|
+
- id: cp2077.cet.sandbox.exec-func-vs-addmethod-reachability
|
|
221
|
+
claim: "From a CET mod's Lua environment, a module-less redscript `public static exec func Foo(gi:
|
|
222
|
+
GameInstance, ...)` is not reachable as `Game.Foo(...)`: the call returns with no Lua error,
|
|
223
|
+
but the function body never runs. An `@addMethod(<Class>)` instance method is reachable the
|
|
224
|
+
way a vanilla method is: `@addMethod(PlayerPuppet) public func Foo() -> String` is called as
|
|
225
|
+
`Game.GetPlayer():Foo()`, and its return value comes back to Lua. To read a value back from
|
|
226
|
+
redscript, add a method to a live game class and return the value; do not rely on an exec func
|
|
227
|
+
writing a log."
|
|
228
|
+
status: verified
|
|
229
|
+
verified_on: "2.31"
|
|
230
|
+
source: ModWright field verification
|
|
231
|
+
tags:
|
|
232
|
+
- cet
|
|
233
|
+
- redscript
|
|
234
|
+
- addmethod
|
|
235
|
+
- exec-func
|
|
236
|
+
- bridge
|
|
237
|
+
- reachability
|
|
238
|
+
- lua-eval
|
|
239
|
+
detail: "This covers calls from a mod's Lua environment (where a bridge evaluates code). The CET
|
|
240
|
+
console, where a person types `Game.Foo()`, was not retested and may still reach exec funcs.
|
|
241
|
+
RELATED: cp2077.redhottools.hotreload.reloadscripts-recompiles-cleanly-storm-crash-1.3.0."
|
|
@@ -6,7 +6,10 @@ facts:
|
|
|
6
6
|
claim: "Codeware's own documentation groups its API surface under nine headings: Lifecycle, World,
|
|
7
7
|
Entities, Player, User Interface, Resources, Localization, Reflection and Utilities."
|
|
8
8
|
status: community
|
|
9
|
-
source: psiberx/cp2077-codeware/README.md, section "Documentation" (list)
|
|
9
|
+
source: 'psiberx/cp2077-codeware/README.md, section "Documentation" (list). Checked against commit
|
|
10
|
+
3a43182 (2026-08-28): README.md still lists exactly these nine headings in this order; this is
|
|
11
|
+
a documentation table of contents with no corresponding code to confirm or contradict it
|
|
12
|
+
against.'
|
|
10
13
|
tags:
|
|
11
14
|
- codeware
|
|
12
15
|
- overview
|
|
@@ -22,8 +25,12 @@ facts:
|
|
|
22
25
|
or a specific resource inside any mounted archive is present — the documented uses are
|
|
23
26
|
verifying that the mod's own archive is actually enabled, and detecting whether another mod
|
|
24
27
|
(and therefore a resource it ships) is installed."
|
|
25
|
-
status:
|
|
26
|
-
source: psiberx/cp2077-codeware/wiki/Home.md, section "Resources > Checking resource existence"
|
|
28
|
+
status: inferred
|
|
29
|
+
source: 'psiberx/cp2077-codeware/wiki/Home.md, section "Resources > Checking resource existence";
|
|
30
|
+
confirmed against commit 3a43182 in scripts/Depot/ResourceDepot.reds: `public native func
|
|
31
|
+
ArchiveExists(name: String) -> Bool` and `public native func ResourceExists(path: ResRef) ->
|
|
32
|
+
Bool` on `public native class ResourceDepot`, reached via `@addMethod(GameInstance) public
|
|
33
|
+
static native func GetResourceDepot() -> ref<ResourceDepot>` in the same file.'
|
|
27
34
|
tags:
|
|
28
35
|
- codeware
|
|
29
36
|
- resourcedepot
|
|
@@ -36,8 +43,14 @@ facts:
|
|
|
36
43
|
caller registers a callback (`token.RegisterCallback(this, n"OnResourceReady")`) and only
|
|
37
44
|
accesses `token.GetResource()` inside that callback — resource loading through this API is
|
|
38
45
|
asynchronous, not a direct return value.
|
|
39
|
-
status:
|
|
40
|
-
source: psiberx/cp2077-codeware/wiki/Home.md, section "Resources > Reading resources"
|
|
46
|
+
status: inferred
|
|
47
|
+
source: 'psiberx/cp2077-codeware/wiki/Home.md, section "Resources > Reading resources"; confirmed
|
|
48
|
+
against commit 3a43182 in scripts/Depot/ResourceDepot.reds: `public native func
|
|
49
|
+
LoadResource(path: ResRef) -> ref<ResourceToken>`, and scripts/Depot/ResourceToken.reds:
|
|
50
|
+
`public native class ResourceToken` declares `GetResource() -> ref<CResource>`,
|
|
51
|
+
`RegisterCallback(target: ref<IScriptable>, function: CName)`, plus
|
|
52
|
+
`IsFinished`/`IsLoaded`/`IsFailed` — there is no synchronous accessor, only the
|
|
53
|
+
callback-registration path the wiki describes.'
|
|
41
54
|
tags:
|
|
42
55
|
- codeware
|
|
43
56
|
- resourcedepot
|
|
@@ -49,8 +62,14 @@ facts:
|
|
|
49
62
|
assigning a resource depot path to it with the `*=` operator (the wiki's example is
|
|
50
63
|
`weapon.effect *= r"base\gameplay\game_effects\strongmelee.es";`); `*=` is the only
|
|
51
64
|
initialization form the page shows.
|
|
52
|
-
status:
|
|
53
|
-
source: psiberx/cp2077-codeware/wiki/Home.md, section "Resources > Resource references"
|
|
65
|
+
status: inferred
|
|
66
|
+
source: "psiberx/cp2077-codeware/wiki/Home.md, section \"Resources > Resource references\";
|
|
67
|
+
confirmed against commit 3a43182 in scripts/Base/Addons/WeaponObject.reds:
|
|
68
|
+
`@addField(WeaponObject) public native let effect: ResourceRef; // rRef<gameEffectSet>` — the
|
|
69
|
+
exact field the wiki's `weapon.effect *= r\"...\"` example assigns to. The `ResourceRef` type
|
|
70
|
+
itself is Codeware's own wrapper, registered as `RTTI_DEFINE_CLASS(App::ResourceWrapper,
|
|
71
|
+
\"ResourceRef\", {...})` in src/App/Depot/ResourceReference.hpp, whose `LoadPath` method
|
|
72
|
+
resolves and starts loading the assigned path — the backing for `*=`'s effect."
|
|
54
73
|
tags:
|
|
55
74
|
- codeware
|
|
56
75
|
- resourceref
|
|
@@ -65,26 +84,48 @@ facts:
|
|
|
65
84
|
`DefineTexts()`/`DefineSubtitles()` using `Text`/`TextM`/`TextF` (and
|
|
66
85
|
`Subtitle`/`SubtitleM`/`SubtitleF`) calls, with gendered variants set either as two separate
|
|
67
86
|
calls or one three-argument `Text(key, female, male)` call."
|
|
68
|
-
status:
|
|
69
|
-
source: psiberx/cp2077-codeware/wiki/Home.md, sections "Localization > Localization providers" and
|
|
70
|
-
"Localization > Localization packages"
|
|
87
|
+
status: inferred
|
|
88
|
+
source: 'psiberx/cp2077-codeware/wiki/Home.md, sections "Localization > Localization providers" and
|
|
89
|
+
"Localization > Localization packages"; confirmed against commit 3a43182 in
|
|
90
|
+
scripts/Localization/Module/ModLocalizationProvider.reds (`public abstract class
|
|
91
|
+
ModLocalizationProvider extends ScriptableSystem` with `GetPackage(language: CName) ->
|
|
92
|
+
ref<ModLocalizationPackage>` and `GetFallback() -> CName`) and
|
|
93
|
+
scripts/Localization/Module/ModLocalizationPackage.reds (`public abstract class
|
|
94
|
+
ModLocalizationPackage` with `DefineTexts`/ `DefineSubtitles` and the
|
|
95
|
+
`Text`/`TextF`/`TextM`/`Subtitle`/ `SubtitleF`/`SubtitleM` overloads, including the two-arg
|
|
96
|
+
and three-arg `Text(key, valueF, valueM)` form).'
|
|
71
97
|
tags:
|
|
72
98
|
- codeware
|
|
73
99
|
- localization
|
|
74
100
|
- modlocalizationprovider
|
|
75
101
|
- modlocalizationpackage
|
|
76
|
-
detail: Texts registered this way are also pushed into the native localization system, so they
|
|
102
|
+
detail: "Texts registered this way are also pushed into the native localization system, so they
|
|
77
103
|
remain reachable via `GetLocalizedTextByKey`/`GetLocalizedText` and from TweakDB records — not
|
|
78
|
-
only from the mod's own redscript.
|
|
104
|
+
only from the mod's own redscript. Confirmed for interface texts specifically:
|
|
105
|
+
src/App/Localization/LocalizationService.cpp hooks the native `LoadTexts` raw function and, in
|
|
106
|
+
`OnLoadTexts`, walks every non-abstract `ModLocalizationProvider` subclass via RTTI, calls its
|
|
107
|
+
`GetOnScreenEntries`, and merges the results into the native
|
|
108
|
+
`localizationPersistenceOnScreenEntries` list by primary/secondary key. Subtitle texts
|
|
109
|
+
(`EntryType.Subtitle`) are not part of that native merge — they stay served through
|
|
110
|
+
`LocalizationSystem.GetSubtitle` at runtime instead."
|
|
79
111
|
- id: cp2077.codeware.overview.reflection-get-class-and-call
|
|
80
112
|
claim: "Codeware's `Reflection` API lets a mod resolve a class by name
|
|
81
113
|
(`Reflection.GetClass(n\"...\")` or `Reflection.GetTypeOf(instance)`), enumerate its
|
|
82
114
|
properties/functions, and read/write/call them dynamically: `prop.GetValue(instance)`,
|
|
83
115
|
`getter.Call(instance)`, `setter.Call(instance, [args])`, with a `status`-returning overload
|
|
84
116
|
of `Call` available to detect argument-count/signature mismatches instead of throwing."
|
|
85
|
-
status:
|
|
86
|
-
source: psiberx/cp2077-codeware/wiki/Home.md, sections "Reflection > Inspecting types and values"
|
|
87
|
-
and "Reflection > Custom callbacks"
|
|
117
|
+
status: inferred
|
|
118
|
+
source: 'psiberx/cp2077-codeware/wiki/Home.md, sections "Reflection > Inspecting types and values"
|
|
119
|
+
and "Reflection > Custom callbacks"; confirmed against commit 3a43182 in
|
|
120
|
+
scripts/Reflection/Reflection.reds (`public native struct Reflection` with `GetClass(name:
|
|
121
|
+
CName) -> ref<ReflectionClass>` and `GetTypeOf(value: Variant) -> ref<ReflectionType>`),
|
|
122
|
+
scripts/Reflection/ReflectionProp.reds (`GetValue(owner: Variant) -> Variant` /
|
|
123
|
+
`SetValue(owner: Variant, value: Variant)`), and scripts/Reflection/ReflectionFunc.reds
|
|
124
|
+
(`public native class ReflectionMemberFunc extends ReflectionFunc { public native func
|
|
125
|
+
Call(self: ref<IScriptable>, opt args: array<Variant>, opt status: script_ref<Bool>) ->
|
|
126
|
+
Variant }`, mirrored on `ReflectionStaticFunc`). Note: the "status-returning overload" is a
|
|
127
|
+
single `Call` with an optional `script_ref<Bool>` out-parameter, not a second,
|
|
128
|
+
separately-named overload.'
|
|
88
129
|
tags:
|
|
89
130
|
- codeware
|
|
90
131
|
- reflection
|
|
@@ -102,7 +143,13 @@ facts:
|
|
|
102
143
|
Reflection to check whether a particular class, property or function exists at runtime before
|
|
103
144
|
touching it, rather than assuming it does.
|
|
104
145
|
status: community
|
|
105
|
-
source: psiberx/cp2077-codeware/wiki/Home.md, section "Reflection > Mods and patches
|
|
146
|
+
source: "psiberx/cp2077-codeware/wiki/Home.md, section \"Reflection > Mods and patches
|
|
147
|
+
compatibility\". Checked against commit 3a43182: this is guidance prose (\"In cases where you
|
|
148
|
+
can't use conditional compilation with `ModuleExists()`, you can use reflection...\") with no
|
|
149
|
+
corresponding API of its own — `ModuleExists()` is redscript's own conditional-compilation
|
|
150
|
+
builtin, not something Codeware's source defines, and the Reflection API it recommends is
|
|
151
|
+
confirmed separately by cp2077.codeware.overview.reflection-get-class-and-call. Left community
|
|
152
|
+
rather than inferred since no code confirms or contradicts this specific piece of advice."
|
|
106
153
|
tags:
|
|
107
154
|
- codeware
|
|
108
155
|
- moduleexists
|