summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rwxr-xr-xcompile12
-rw-r--r--compile.bat14
-rw-r--r--gamedata/engine.kv181
-rw-r--r--gamedata/engine.txt162
-rw-r--r--gamedata/entprops.kv9
-rw-r--r--gamedata/entprops.txt8
-rw-r--r--gamedata/gamelib.kv57
-rw-r--r--gamedata/gamelib.txt46
-rw-r--r--gamedata/inputsystem.kv17
-rw-r--r--gamedata/inputsystem.txt12
-rw-r--r--gamedata/matchmaking.kv11
-rw-r--r--gamedata/matchmaking.txt7
-rw-r--r--gamedata/vgui2.kv7
-rw-r--r--gamedata/vgui2.txt7
-rw-r--r--gamedata/vguimatsurface.kv69
-rw-r--r--gamedata/vguimatsurface.txt49
-rw-r--r--src/build/mkentprops.c447
-rw-r--r--src/build/mkgamedata.c345
-rw-r--r--src/build/vec.h2
-rw-r--r--src/engineapi.c3
-rw-r--r--src/engineapi.h11
-rw-r--r--src/kv.c290
-rw-r--r--src/kv.h95
-rw-r--r--src/l4dwarp.c4
-rw-r--r--src/mem.h2
-rw-r--r--test/kv.test.c55
26 files changed, 807 insertions, 1115 deletions
diff --git a/compile b/compile
index d9ab4f6..17a1ba4 100755
--- a/compile
+++ b/compile
@@ -89,15 +89,15 @@ if [ "$dbg" = 1 ]; then src="$src \
fi
$HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -include stdbool.h \
- -o .build/codegen src/build/codegen.c src/build/cmeta.c
+ -o .build/codegen src/build/codegen.c src/build/cmeta.c src/os.c
$HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -include stdbool.h \
- -o .build/mkgamedata src/build/mkgamedata.c src/kv.c
+ -o .build/mkgamedata src/build/mkgamedata.c src/os.c
$HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -include stdbool.h \
- -o .build/mkentprops src/build/mkentprops.c src/kv.c
+ -o .build/mkentprops src/build/mkentprops.c src/os.c
.build/codegen `for s in $src; do echo "src/$s"; done`
-.build/mkgamedata gamedata/engine.kv gamedata/gamelib.kv gamedata/inputsystem.kv \
-gamedata/matchmaking.kv gamedata/vgui2.kv gamedata/vguimatsurface.kv
-.build/mkentprops gamedata/entprops.kv
+.build/mkgamedata gamedata/engine.txt gamedata/gamelib.txt gamedata/inputsystem.txt \
+gamedata/matchmaking.txt gamedata/vgui2.txt gamedata/vguimatsurface.txt
+.build/mkentprops gamedata/entprops.txt
for s in $src; do cc "$s"; done
$CC -shared -fpic -fuse-ld=lld -O0 -w -o .build/libtier0.so src/stubs/tier0.c
$CC -shared -fpic -fuse-ld=lld -O0 -w -o .build/libvstdlib.so src/stubs/vstdlib.c
diff --git a/compile.bat b/compile.bat
index f36d17d..ca632ed 100644
--- a/compile.bat
+++ b/compile.bat
@@ -115,13 +115,13 @@ if %host64%==1 (
%HOSTCC% -fuse-ld=lld -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h ^
-L.build %lbcryptprimitives_host% -o .build/codegen.exe src/build/codegen.c src/build/cmeta.c src/os.c || goto :end
%HOSTCC% -fuse-ld=lld -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h ^
--L.build %lbcryptprimitives_host% -o .build/mkgamedata.exe src/build/mkgamedata.c src/kv.c src/os.c || goto :end
-%HOSTCC% -fuse-ld=lld -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h ^
--L.build %lbcryptprimitives_host% -o .build/mkentprops.exe src/build/mkentprops.c src/kv.c src/os.c || goto :end
+-L.build %lbcryptprimitives_host% -o .build/mkgamedata.exe src/build/mkgamedata.c src/os.c || goto :end
+%HOSTCC% -fuse-ld=lld -municode -O2 -g %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h ^
+-L.build %lbcryptprimitives_host% -o .build/mkentprops.exe src/build/mkentprops.c src/os.c || goto :end
.build\codegen.exe%src% || goto :end
-.build\mkgamedata.exe gamedata/engine.kv gamedata/gamelib.kv gamedata/inputsystem.kv ^
-gamedata/matchmaking.kv gamedata/vgui2.kv gamedata/vguimatsurface.kv || goto :end
-.build\mkentprops.exe gamedata/entprops.kv || goto :end
+.build\mkgamedata.exe gamedata/engine.txt gamedata/gamelib.txt gamedata/inputsystem.txt ^
+gamedata/matchmaking.txt gamedata/vgui2.txt gamedata/vguimatsurface.txt || goto :end
+.build\mkentprops.exe gamedata/entprops.txt || goto :end
llvm-rc /FO .build\dll.res src\dll.rc || goto :end
for %%b in (%src%) do ( call :cc %%b || goto :end )
:: we need different library names for debugging because Microsoft...
@@ -143,8 +143,6 @@ del .build\sst.lib
:: special case: test must be 32-bit
%HOSTCC% -fuse-ld=lld -m32 -O2 -g -L.build -lbcryptprimitives -include test/test.h -o .build/hook.test.exe test/hook.test.c || goto :end
.build\hook.test.exe || goto :end
-%HOSTCC% -fuse-ld=lld -O2 -g -include test/test.h -o .build/kv.test.exe test/kv.test.c || goto :end
-.build\kv.test.exe || goto :end
%HOSTCC% -fuse-ld=lld -O2 -g -include test/test.h -o .build/x86.test.exe test/x86.test.c || goto :end
.build\x86.test.exe || goto :end
diff --git a/gamedata/engine.kv b/gamedata/engine.kv
deleted file mode 100644
index 304b218..0000000
--- a/gamedata/engine.kv
+++ /dev/null
@@ -1,181 +0,0 @@
-// = engine library =
-
-// ICvar
-// XXX: const and non-const entries might be flipped here on Windows, not 100%
-// sure. kind of just choosing not to care thusfar, as it still works the same!
-vtidx_AllocateDLLIdentifier { default 5 Portal2 8 }
-vtidx_RegisterConCommand { default 6 Portal2 9 }
-vtidx_UnregisterConCommands { default 8 Portal2 11 }
-//vtidx_FindComandBase { default 10 Portal2 13 } // unused
-vtidx_FindVar { default 12 Portal2 15 }
-//vtidx_FindVar_const { default 13 Portal2 16 } // e.g. this might be wrong(???)
-vtidx_FindCommand { default 14 Portal2 17 }
-vtidx_CallGlobalChangeCallbacks { default 20 L4Dx 18 Portal2 21 }
-vtidx_ConsoleColorPrintf { OrangeBoxbased 23 L4Dx 21 Portal2 24 }
-
-// CDemoRecorder
-vtidx_StartRecording 2
-vtidx_SetSignonState 3
-vtidx_StopRecording 7
-vtidx_RecordPacket 11
-
-// VEngineClient
-vtidx_IsInGame {
- Client015 26
- Client014 {
- L4D2 28
- 2013 26
- }
- Client013 {
- L4D1 27
- default 26
- }
- // TODO(compat): unconfirmed, and OE support isn't really a thing yet anyway
- //Client012 28
-}
-vtidx_GetGameDirectory {
- Client015 35 // current portal 2
- Client014 {
- L4D2 73 // YES IT'S SEVENTY THREE ALL OF A SUDDEN.
- 2013 35
- }
- Client013 {
- L4Dx 36 // AND THEN THEY CHANGED IT BACK LATER! (on 2.0.4.1)
- default 35 // <- most things have this!
- }
- Client012 37 // dmomm, ep1, ...
-}
-vtidx_GetEngineBuildNumber {
- Client013 {
- L4D2 99
- Portal2 100
- }
- Client014 {
- L4D2 31
- 2013 98
- }
- Portal1_3420 91
- // TODO(compat): we'll need these... but need to detect first
- //Portal1_5135 102
- //L4D1_1005 99
- //L4D1_Steam 97
-}
-
-// IGameUIFuncs
-vtidx_GetDesktopResolution 5
-
-// IGame/CGame
-vtidx_DispatchAllStoredGameMessages 16
-
-// VEngineServer
-vtidx_PEntityOfEntIndex { OrangeBox 19 } // probably OE too but???
-vtidx_ServerCommand { OrangeBoxbased 36 }
-
-sz_edict {
- default 20
- L4Dbased 16 // see engineapi.h comment
-}
-
-// vgui::Panel
-vtidx_SetPaintEnabled {
- default 67
- Client013 {
- L4D1 68
- L4D2 {
- default 71
- L4D2_2147plus 72
- }
- }
- Client014 {
- L4D2 70
- }
-}
-vtidx_Paint {
- default 123
- Client014 { L4D2 126 } // 2000
- Client013 {
- L4D2 {
- default 127 // 2045
- L4D2_2147plus 128
- }
- }
-}
-
-// SendProp
-sz_SendProp {
- // wrapping all these in 005 for right now.
- // will need at least 009 as well at some point!
- SrvDLL005 {
- OrangeBox 76
- L4D1 80
- L4D2 84
- Portal2 84
- }
- //2013 80 // TODO(compat): not sure about 2013/009 yet
-}
-off_SP_varname {
- SrvDLL005 {
- OrangeBox 44
- //L4Dbased 48 // TODO(compat): haven't tested Survivors
- // for now do this instead:
- L4D 48
- Portal2 48
- }
- //2013 48 // TODO(compat): not sure about 2013/009 yet pt2
-}
-off_SP_offset {
- SrvDLL005 {
- OrangeBox 68
- L4D1 72
- L4D2 76
- Portal2 76
- }
- //2013 72 // TODO(compat): not sure about 2013/009 yet pt3
-}
-
-// CBaseServer/CGameServer
-vtidx_GetSpawnCount {
- //OrangeBox "13 + NVDTOR" // not used right now anyway
- L4D1 "13 + NVDTOR"
- L4D2 "14 + NVDTOR" // GetTimescale() added, pushed it down
- // rest untested, add later if/when actually needed for something
-}
-
-// IEngineVGuiInternal/CEngineVGui
-vtidx_GetPanel NVDTOR
-vtidx_VGuiConnect { // note: the actual name is Connect() but that's too generic
- default "3 + NVDTOR"
- L4Dbased {
- default "4 + NVDTOR" // ActivateGameUI added
- L4DS "5 + NVDTOR" // some other crap added, god knows
- }
-}
-vtidx_VGuiIsInitialized { // likewise, function is just called IsInitialized()
- default "6 + NVDTOR"
- L4Dbased {
- default "7 + NVDTOR"
- L4DS "8 + NVDTOR"
- }
-}
-
-// CDedicatedServerAPI
-vtidx_RunFrame 7
-
-// IEngine
-vtidx_Frame "4 + NVDTOR"
-
-// CEngineTool
-vtidx_GetRealTime {
- default 34 // HL2, P1, L4D1, BMS
- // OE, DMoMM 24
- L4D2 35
- Portal2 36
-}
-vtidx_HostFrameTime {
- default 35
- // OE, DMoMM 25
- L4D2 38
- Portal2 39
-}
-
-// vi: sw=4 ts=4 noet tw=80 cc=80 ft=text
diff --git a/gamedata/engine.txt b/gamedata/engine.txt
new file mode 100644
index 0000000..b5186a6
--- /dev/null
+++ b/gamedata/engine.txt
@@ -0,0 +1,162 @@
+# ICvar
+# XXX: const and non-const entries might be flipped here on Windows, not 100%
+# sure. kind of just choosing not to care thusfar, as it still works the same!
+vtidx_AllocateDLLIdentifier 5
+ Portal2 8
+vtidx_RegisterConCommand 6
+ Portal2 9
+vtidx_UnregisterConCommands 8
+ Portal2 11
+# unused:
+#vtidx_FindCommandBase 10
+# Portal2 13
+vtidx_FindVar 12
+ Portal2 15
+vtidx_FindCommand 14
+ Portal2 17
+vtidx_CallGlobalChangeCallbacks 20
+ L4Dx 18
+ Portal2 21
+vtidx_ConsoleColorPrintf
+ OrangeBoxbased 23
+ L4Dx 21
+ Portal2 24
+
+# CDemoRecorder
+vtidx_StartRecording 2
+vtidx_SetSignonState 3
+vtidx_StopRecording 7
+vtidx_RecordPacket 11
+
+# VEngineClient
+vtidx_IsInGame
+ Client015 26
+ Client014
+ L4D2 28
+ 2013 26
+ Client013 26
+ L4D1 27
+ # TODO(compat): unconfirmed, and OE support isn't really a thing yet anyway
+ #Client012 28
+vtidx_GetGameDirectory
+ Client015 35 # current Portal 2
+ Client014
+ L4D2 73 # YES IT'S SEVENTY THREE SOMEHOW ALL OF A SUDDEN.
+ 2013 35
+ Client013 35
+ L4Dx 36 # AND THEY ACTUALLY CHANGED IT BACK LATER! (on 2.0.4.1)
+ Client012 37 # dmomm, ep1, ...
+vtidx_GetEngineBuildNumber
+ Client013
+ L4D2 99
+ Portal2 100
+ Client014
+ L4D2 31
+ 2013 98
+ Portal1_3420 91
+ # TODO(compat): we'll need these... but need to detect first
+ #Portal1_5135 102
+ #L4D1_1005 99
+ #L4D1_Steam 97
+
+# IGameUIFuncs
+vtidx_GetDesktopResolution 5
+
+# IGame/CGame
+vtidx_DispatchAllStoredGameMessages 16
+
+# VEngineServer
+vtidx_PEntityOfEntIndex
+ OrangeBox 19 # probably OE too but ??
+vtidx_ServerCommand
+ OrangeBoxbased 36
+
+sz_edict 20
+ L4Dbased 16
+
+# vgui::Panel
+vtidx_SetPaintEnabled 67
+ Client013
+ L4D1 68
+ L4D2 71
+ L4D2_2147plus 72
+ Client014
+ L4D2 70
+vtidx_Paint 123
+ Client014
+ L4D2 126 # 2000
+ Client013
+ L4D2 127 # 2045
+ L4D2_2147plus 128
+
+# SendProp
+sz_SendProp
+ # wrapping all these in 005 for right now.
+ # will need at least 009 as well at some point!
+ SrvDLL005
+ OrangeBox 76
+ L4D1 80
+ L4D2 84
+ Portal2 84
+ #2013 80 # TODO(compat): not sure about 2013/009 yet
+off_SP_type 8
+off_SP_varname
+ SrvDLL005
+ OrangeBox 44
+ #L4Dbased 48 # TODO(compat): haven't tested Survivors
+ # for now do this instead:
+ L4D 48
+ Portal2 48
+ #2013 48 # TODO(compat): not sure about 2013/009 yet pt2
+off_SP_subtable
+ SrvDLL005
+ OrangeBox 64
+ L4D1 68
+ L4D2 72
+ Portal2 72
+ #2013 68 # TODO(compat): not sure about 2013/009 yet pt3
+off_SP_offset
+ SrvDLL005
+ OrangeBox 68
+ L4D1 72
+ L4D2 76
+ Portal2 76
+ #2013 72 # TODO(compat): not sure about 2013/009 yet pt4
+
+DT_DataTable 5 # constant value from SendPropType enum (XXX: name okay???)
+ L4Dbased 6
+
+# CBaseServer/CGameServer
+vtidx_GetSpawnCount
+ #OrangeBox 13 + NVDTOR # not used right now anyway
+ L4D1 13 + NVDTOR
+ L4D2 14 + NVDTOR # GetTimescale() added, pushed it down
+ # rest untested, add later if/when actually needed for something
+
+# IEngineVGuiInternal/CEngineVGui
+vtidx_GetPanel NVDTOR
+# note: actual name of this function is Connect() but that's too generic
+vtidx_VGuiConnect 3 + NVDTOR
+ L4Dbased 4 + NVDTOR # ActivateGameUI added
+ L4DS 5 + NVDTOR # some other crap added, god knows
+vtidx_VGuiIsInitialized 6 + NVDTOR # this is also just called IsInitialized()
+ L4Dbased 7 + NVDTOR
+ L4DS 8 + NVDTOR
+
+# CDedicatedServerAPI
+vtidx_RunFrame 7
+
+# IEngine
+vtidx_Frame 4 + NVDTOR
+
+# CEngineTool
+vtidx_GetRealTime 34 # HL2, P1, L4D1, BMS
+ # OE, DMoMM 24
+ L4D2 35
+ Portal2 36
+vtidx_HostFrameTime 35
+ # OE, DMoMM 25
+ L4D2 38
+ Portal2 39
+
+# vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/gamedata/entprops.kv b/gamedata/entprops.kv
deleted file mode 100644
index 942d9c9..0000000
--- a/gamedata/entprops.kv
+++ /dev/null
@@ -1,9 +0,0 @@
-// This follows a different format to the other gamedata files.
-// It simply assigns variable names to network table property name strings.
-// Network names are classname/propname, e.g. CBasePlayer/m_fWhatever
-
-off_entpos CBaseEntity/m_vecOrigin
-// look angles, currently just for L4D1/2, can add other games as needed
-off_eyeang "CCSPlayer/m_angEyeAngles[0]"
-
-// vi: sw=4 ts=4 noet tw=80 cc=80 ft=text
diff --git a/gamedata/entprops.txt b/gamedata/entprops.txt
new file mode 100644
index 0000000..5d6bd7c
--- /dev/null
+++ b/gamedata/entprops.txt
@@ -0,0 +1,8 @@
+# Format: variable sendtable[/subtable/...]/property
+# Example: off_coolvar CClassName/m_pSubTableThing/m_fCoolVariable
+
+off_entpos CBaseEntity/m_vecOrigin
+# look angles, currently just for L4D1/2, can add other games as needed
+off_eyeang CCSPlayer/m_angEyeAngles[0]
+
+# vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/gamedata/gamelib.kv b/gamedata/gamelib.kv
deleted file mode 100644
index 7f53383..0000000
--- a/gamedata/gamelib.kv
+++ /dev/null
@@ -1,57 +0,0 @@
-// = client and server libraries =
-
-// CGameMovement
-vtidx_CheckJumpButton {
- Portal1_3420 "22 + NVDTOR"
- 2013 "28 + NVDTOR"
- L4D "32 + NVDTOR"
- L4DS "33 + NVDTOR"
- Portal2 "35 + NVDTOR"
-}
-off_mv {
- default 8
- Portal1_3420 4
-}
-
-// IServerGameDLL
-vtidx_GetAllServerClasses {
- default 10
- 2013 11
- // TODO(compat): BMS 11
-}
-
-// I(Server|Client)Unknown
-vtidx_GetBaseEntity "4 + NVDTOR"
-
-// CBaseEntity or CBasePlayer or something
-off_netprop_statechanged { L4D 88 }
-off_simtime { L4D 128 }
-vtidx_Spawn {
- L4D2 {
- default "22 + NVDTOR"
- TheLastStand "23 + NVDTOR"
- }
-}
-vtidx_Teleport {
- L4D "104 + NVDTOR"
- L4D2 {
- default "116 + NVDTOR" // TODO(linux): might actually be 119!?!?
- TheLastStand "117 + NVDTOR" // I dunno why JAiZ changed this
- }
-}
-
-// CGlobalVars
-off_curtime 12
-off_edicts { L4D 88 }
-
-// IServerGameDLL
-vtidx_GameFrame 4
-vtidx_GameShutdown 7
-
-// CDirector
-vtidx_OnGameplayStart {
- L4D2 11 // note: just happens the same on linux!
- L4D1 11 // TODO(linux): unknown whether or not this is the same
-}
-
-// vi: sw=4 ts=4 noet tw=80 cc=80 ft=text
diff --git a/gamedata/gamelib.txt b/gamedata/gamelib.txt
new file mode 100644
index 0000000..35d5551
--- /dev/null
+++ b/gamedata/gamelib.txt
@@ -0,0 +1,46 @@
+# CGameMovement
+vtidx_CheckJumpButton
+ Portal1_3420 22 + NVDTOR
+ 2013 28 + NVDTOR
+ L4D 32 + NVDTOR
+ L4DS 33 + NVDTOR
+ Portal2 35 + NVDTOR
+off_mv 8
+ Portal1_3420 4
+
+# IServerGameDLL
+vtidx_GetAllServerClasses 10
+ 2013 11
+ # TODO(compat): BMS 11
+
+# I(Server|Client)Unknown
+vtidx_GetBaseEntity 4 + NVDTOR
+
+# CBaseEntity or CBasePlayer or something
+off_netprop_statechanged
+ L4D 88
+off_simtime
+ L4D 128
+vtidx_Spawn
+ L4D2 22 + NVDTOR
+ TheLastStand 23 + NVDTOR
+vtidx_Teleport
+ L4D 104 + NVDTOR
+ L4D2 116 + NVDTOR # TODO(linux): might actually be 119!?!?
+ TheLastStand 117 + NVDTOR # I dunno why JAiZ changed this
+
+# CGlobalVars
+off_curtime 12
+off_edicts
+ L4D 88
+
+# IServerGameDLL
+vtidx_GameFrame 4
+vtidx_GameShutdown 7
+
+# CDirector
+vtidx_OnGameplayStart
+ L4D2 11 # note: just happens the same on linux!
+ L4D1 11
+
+# vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/gamedata/inputsystem.kv b/gamedata/inputsystem.kv
deleted file mode 100644
index 3c13e09..0000000
--- a/gamedata/inputsystem.kv
+++ /dev/null
@@ -1,17 +0,0 @@
-// = inputsystem library =
-
-vtidx_SleepUntilInput {
- default 31 // TODO(compat): don't really know yet if this causes any issues
- Portal2 34 // IAppSystem changes
-}
-
-// XXX: This function won't always exist even when has_ is true.
-// It's only used by rinput.c after checking that m_rawinput exists.
-vtidx_GetRawMouseAccumulators {
- L4D1 39
- L4D2 37
- 2013 39
- Portal2 50
-}
-
-// vi: sw=4 ts=4 noet tw=80 cc=80 ft=text
diff --git a/gamedata/inputsystem.txt b/gamedata/inputsystem.txt
new file mode 100644
index 0000000..65bca21
--- /dev/null
+++ b/gamedata/inputsystem.txt
@@ -0,0 +1,12 @@
+vtidx_SleepUntilInput 31 # TODO(compat): unsure if this causes any issues yet
+ Portal2 34 # IAppSystem changes
+
+# XXX: This function won't always exist even when has_ is true.
+# It's only used by rinput.c after checking that m_rawinput exists.
+vtidx_GetRawMouseAccumulators
+ L4D1 39
+ L4D2 37
+ 2013 39
+ Portal2 50
+
+# vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/gamedata/matchmaking.kv b/gamedata/matchmaking.kv
deleted file mode 100644
index 8ee618d..0000000
--- a/gamedata/matchmaking.kv
+++ /dev/null
@@ -1,11 +0,0 @@
-// = matchmaking library =
-
-// IMatchFramework
-vtidx_GetMatchNetworkMsgController {
- L4D 10 // NOTE: probably same for aswarm or p2 except with IAppSystem shift
-}
-
-// IMatchNetworkMsgController
-vtidx_GetActiveGameServerDetails { L4D 1 }
-
-// vi: sw=4 ts=4 noet tw=80 cc=80 ft=text
diff --git a/gamedata/matchmaking.txt b/gamedata/matchmaking.txt
new file mode 100644
index 0000000..2c93120
--- /dev/null
+++ b/gamedata/matchmaking.txt
@@ -0,0 +1,7 @@
+# IMatchFramework
+vtidx_GetMatchNetworkMsgController
+ L4D 10 # NOTE: probably same for aswarm or p2 except with IAppSystem shift
+vtidx_GetActiveGameServerDetails
+ L4D 1
+
+# vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/gamedata/vgui2.kv b/gamedata/vgui2.kv
deleted file mode 100644
index 6fd55a3..0000000
--- a/gamedata/vgui2.kv
+++ /dev/null
@@ -1,7 +0,0 @@
-// = vgui2 library =
-
-// ISchemeManager
-vtidx_GetIScheme 8
-
-// IScheme
-vtidx_GetFont 3
diff --git a/gamedata/vgui2.txt b/gamedata/vgui2.txt
new file mode 100644
index 0000000..91aba01
--- /dev/null
+++ b/gamedata/vgui2.txt
@@ -0,0 +1,7 @@
+# ISchemeManager
+vtidx_GetIScheme 8
+
+# IScheme
+vtidx_GetFont 3
+
+# vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/gamedata/vguimatsurface.kv b/gamedata/vguimatsurface.kv
deleted file mode 100644
index 19857be..0000000
--- a/gamedata/vguimatsurface.kv
+++ /dev/null
@@ -1,69 +0,0 @@
-// = vguimatsurface library =
-
-// ISurface
-vtidx_DrawSetColor {
- OrangeBoxbased 10
- L4D 10
-}
-vtidx_DrawFilledRect {
- OrangeBoxbased 12
- L4D 12
-}
-vtidx_DrawOutlinedRect {
- OrangeBoxbased 14
- L4D 14
-}
-vtidx_DrawLine {
- OrangeBoxbased 15
- L4D 15
-}
-vtidx_DrawPolyLine {
- OrangeBoxbased 16
- L4D 16
-}
-vtidx_DrawSetTextFont {
- OrangeBoxbased 17
- L4D 17
-}
-vtidx_DrawSetTextColor {
- OrangeBoxbased 18
- L4D 18
-}
-vtidx_DrawSetTextPos {
- OrangeBoxbased 20
- L4D 20
-}
-vtidx_DrawPrintText {
- OrangeBoxbased 22
- L4D 22
-}
-vtidx_GetScreenSize {
- OrangeBoxbased 37
- L4D {
- default 37
- L4D2_2147plus 35
- }
-}
-// Unused: currently no good way to create custom fonts without leaking them
-//vtidx_CreateFont {
-// OrangeBoxbased 64
-// L4D {
-// default 64
-// L4D2_2147plus 63
-// }
-//}
-//vtidx_SetFontGlyphSet {
-// OrangeBoxbased 65
-// L4D {
-// default 65
-// L4D2_2147plus 64
-// }
-//}
-vtidx_GetFontTall {
- OrangeBoxbased 67
- L4D 67
-}
-vtidx_GetCharacterWidth {
- OrangeBoxbased 71
- L4D 71
-}
diff --git a/gamedata/vguimatsurface.txt b/gamedata/vguimatsurface.txt
new file mode 100644
index 0000000..a9dc3f2
--- /dev/null
+++ b/gamedata/vguimatsurface.txt
@@ -0,0 +1,49 @@
+# ISurface
+vtidx_DrawSetColor
+ OrangeBoxbased 10
+ L4D 10
+vtidx_DrawFilledRect
+ OrangeBoxbased 12
+ L4D 12
+vtidx_DrawOutlinedRect
+ OrangeBoxbased 14
+ L4D 14
+vtidx_DrawLine
+ OrangeBoxbased 15
+ L4D 15
+vtidx_DrawPolyLine
+ OrangeBoxbased 16
+ L4D 16
+vtidx_DrawSetTextFont
+ OrangeBoxbased 17
+ L4D 17
+vtidx_DrawSetTextColor
+ OrangeBoxbased 18
+ L4D 18
+vtidx_DrawSetTextPos
+ OrangeBoxbased 20
+ L4D 20
+vtidx_DrawPrintText
+ OrangeBoxbased 22
+ L4D 22
+vtidx_GetScreenSize
+ OrangeBoxbased 37
+ L4D 37
+ L4D2_2147plus 35
+# Unused: currently no good way to create custom fonts without leaking them
+#vtidx_CreateFont
+# OrangeBoxbased 64
+# L4D 64
+# L4D2_2147plus 63
+#vtidx_SetFontGlyphSet
+# OrangeBoxbased 65
+# L4D 65
+# L4D2_2147plus 64
+vtidx_GetFontTall
+ OrangeBoxbased 67
+ L4D 67
+vtidx_GetCharacterWidth
+ OrangeBoxbased 71
+ L4D 71
+
+# vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/build/mkentprops.c b/src/build/mkentprops.c
index fdbb8af..4806996 100644
--- a/src/build/mkentprops.c
+++ b/src/build/mkentprops.c
@@ -17,13 +17,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
#include "../intdefs.h"
-#include "../kv.h"
#include "../langext.h"
#include "../os.h"
-#include "skiplist.h"
-#include "vec.h"
#ifdef _WIN32
#define fS "S"
@@ -31,176 +29,349 @@
#define fS "s"
#endif
-static noreturn die(const char *s) {
+static noreturn die(int status, const char *s) {
fprintf(stderr, "mkentprops: %s\n", s);
- exit(100);
+ exit(status);
}
-
-struct prop {
- const char *varname; /* the C global name */
- const char *propname; /* the entity property name */
- struct prop *next;
-};
-struct vec_prop VEC(struct prop *);
-
-DECL_SKIPLIST(static, class, struct class, const char *, 4)
-struct class {
- const char *name; /* the entity class name */
- struct vec_prop props;
- struct skiplist_hdr_class hdr;
-};
-static inline int cmp_class(struct class *c, const char *s) {
- return strcmp(c->name, s);
+static noreturn dieparse(const os_char *file, int line, const char *s) {
+ fprintf(stderr, "mkentprops: %" fS ":%d: %s\n", file, line, s);
+ exit(2);
}
-static inline struct skiplist_hdr_class *hdr_class(struct class *c) {
- return &c->hdr;
+
+static char *sbase; // input file contents - string values are indices off this
+
+// custom data structure of the day: zero-alloc half-SoA adaptive radix trees(!)
+
+#define ART_MAXNODES 16384
+static uchar art_firstbytes[ART_MAXNODES];
+static struct art_core {
+ int soff; // string offset to entire key chunk (including firstbyte)
+ u16 slen; // number of bytes in key chunk (including firstbyte, >=1)
+ u16 next; // sibling node (same prefix, different firstbyte)
+} art_cores[ART_MAXNODES];
+// if node doesn't end in \0: child node (i.e. key appends more bytes)
+// if node DOES end in \0: offset into art_leaves (below)
+static u16 art_children[ART_MAXNODES];
+static int art_nnodes = 0;
+
+#define ART_NULL ((u16)-1)
+
+static inline int art_newnode(void) {
+ if (art_nnodes == ART_MAXNODES) die(2, "out of tree nodes");
+ return art_nnodes++;
}
-DEF_SKIPLIST(static, class, cmp_class, hdr_class)
-static struct skiplist_hdr_class classes = {0};
-static int nclasses = 0;
-
-struct parsestate {
- const os_char *filename;
- struct kv_parser *parser;
- char *lastvar;
-};
-
-static noreturn badparse(struct parsestate *state, const char *e) {
- fprintf(stderr, "mkentprops: %" fS ":%d:%d: parse error: %s",
- state->filename, state->parser->line, state->parser->col, e);
- exit(1);
+
+#define ART_MAXLEAVES 8192
+static struct art_leaf {
+ int varstr; // offset of string (generated variable), if any, or -1 if none
+ u16 subtree; // art index of subtree (nested SendTable), or -1 if none
+ u16 nsubs; // number of subtrees (used to short-circuit the runtime search)
+} art_leaves[ART_MAXLEAVES];
+static int art_nleaves = 0;
+static u16 art_root = ART_NULL; // ServerClasses (which point at SendTables)
+static u16 nclasses = 0; // similar short circuit for ServerClasses
+
+#define VAR_NONE -1
+
+// for quick iteration over var names to generate decls header without ART faff
+#define MAXDECLS 4096
+static int decls[MAXDECLS];
+static int ndecls = 0;
+
+static inline int art_newleaf(void) {
+ if (art_nleaves == ART_MAXLEAVES) die(2, "out of leaf nodes");
+ return art_nleaves++;
}
-static void kv_cb(enum kv_token type, const char *p, uint len, void *ctxt) {
- struct parsestate *state = ctxt;
- switch_exhaust_enum (kv_token, type) {
- case KV_IDENT: case KV_IDENT_QUOTED:
- state->lastvar = malloc(len + 1);
- if (!state->lastvar) die("couldn't allocate memory");
- memcpy(state->lastvar, p, len); state->lastvar[len] = '\0';
- break;
- case KV_NEST_START: badparse(state, "unexpected nested block");
- case KV_NEST_END: badparse(state, "unexpected closing brace");
- case KV_VAL: case KV_VAL_QUOTED:;
- struct prop *prop = malloc(sizeof(*prop));
- if (!prop) die("couldn't allocate memory");
- prop->varname = state->lastvar;
- char *classname = malloc(len + 1);
- if (!classname) die("couldn't allocate memory");
- memcpy(classname, p, len); classname[len] = '\0';
- char *propname = strchr(classname, '/');
- if (!propname) {
- badparse(state, "network name not in class/prop format");
+static struct art_lookup_ret {
+ bool isnew; // true if node created, false if found
+ u16 leafidx; // index of value (leaf) node
+} art_lookup(u16 *art, int soff, int len) {
+ assume(len > 0); // everything must be null-terminated!
+ for (;;) {
+ const uchar *p = (const uchar *)sbase + soff;
+ u16 cur = *art;
+ if (cur == ART_NULL) { // append
+ int new = art_newnode();
+ *art = new;
+ art_firstbytes[new] = *p;
+ art_cores[new].soff = soff;
+ art_cores[new].slen = len;
+ art_cores[new].next = ART_NULL;
+ int leaf = art_newleaf(); // N.B. firstbyte is already 0 here
+ art_children[new] = leaf;
+ return (struct art_lookup_ret){true, leaf};
+ }
+ while (art_firstbytes[cur] == *p) {
+ int nodelen = art_cores[cur].slen;
+ int matchlen = 1;
+ const uchar *q = (const uchar *)sbase + art_cores[cur].soff;
+ for (; matchlen < nodelen; ++matchlen) {
+ if (p[matchlen] != q[matchlen]) {
+ // node and key diverge: split into child nodes
+ // (left is new part of key string, right is existing tail)
+ art_cores[cur].slen = matchlen;
+ int l = art_newnode(), r = art_newnode();
+ art_firstbytes[l] = p[matchlen];
+ art_cores[l].soff = soff + matchlen;
+ art_cores[l].slen = len - matchlen;
+ art_cores[l].next = r;
+ art_firstbytes[r] = q[matchlen];
+ art_cores[r].soff = art_cores[cur].soff + matchlen;
+ art_cores[r].slen = nodelen - matchlen;
+ art_cores[r].next = ART_NULL;
+ art_children[r] = art_children[cur];
+ art_children[cur] = l;
+ int leaf = art_newleaf();
+ art_children[l] = leaf;
+ return (struct art_lookup_ret){true, leaf};
+ }
}
- *propname = '\0'; ++propname; // split!
- prop->propname = propname;
- struct class *class = skiplist_get_class(&classes, classname);
- if (!class) {
- class = malloc(sizeof(*class));
- if (!class) die("couldn't allocate memory");
- *class = (struct class){.name = classname};
- skiplist_insert_class(&classes, classname, class);
- ++nclasses;
+ if (matchlen == len) {
+ // node matches entire key: we have matched an existing entry
+ return (struct art_lookup_ret){false, art_children[cur]};
}
- // (if class is already there just leak classname, no point freeing)
- if (!vec_push(&class->props, prop)) die("couldn't append to array");
+ // node is substring of key: descend into child nodes
+ soff += matchlen;
+ len -= matchlen;
+ cur = art_children[cur]; // note: this can't be ART_NULL (thus loop)
+ p = (const uchar *)sbase + soff; // XXX: kinda silly dupe
+ }
+ // if we didn't match this node, try the next sibling node.
+ // if sibling is null, we'll hit the append case above.
+ art = &art_cores[cur].next;
+ }
+}
+
+static struct art_leaf *helpgetleaf(u16 *art, const char *s, int len,
+ const os_char *parsefile, int parseline, u16 *countvar) {
+ struct art_lookup_ret leaf = art_lookup(art, s - sbase, len);
+ if (leaf.isnew) {
+ art_leaves[leaf.leafidx].varstr = VAR_NONE;
+ art_leaves[leaf.leafidx].subtree = ART_NULL;
+ ++*countvar;
+ }
+ else if (parsefile) { // it's null if an existing entry is allowed
+ dieparse(parsefile, parseline, "duplicate property name");
+ }
+ return art_leaves + leaf.leafidx;
+}
+
+static inline void handleentry(char *k, char *v, int vlen,
+ const os_char *file, int line) {
+ if (ndecls == MAXDECLS) die(2, "out of declaration entries");
+ decls[ndecls++] = k - sbase;
+ char *propname = memchr(v, '/', vlen);
+ if (!propname) {
+ dieparse(file, line, "network name not in class/property format");
+ }
+ *propname++ = '\0';
+ int sublen = propname - v;
+ if (sublen > 65535) {
+ dieparse(file, line, "network class name is far too long");
+ }
+ vlen -= sublen;
+ struct art_leaf *leaf = helpgetleaf(&art_root, v, sublen, 0, 0, &nclasses);
+ u16 *subtree = &leaf->subtree;
+ for (;;) {
+ if (vlen > 65535) {
+ dieparse(file, line, "property (SendTable) name is far too long");
+ }
+ char *nextpart = memchr(propname, '/', vlen);
+ if (!nextpart) {
+ leaf = helpgetleaf(subtree, propname, vlen, file, line,
+ &leaf->nsubs);
+ leaf->varstr = k - sbase;
break;
- case KV_COND_PREFIX: case KV_COND_SUFFIX:
- badparse(state, "unexpected conditional");
+ }
+ *nextpart++ = '\0';
+ sublen = nextpart - propname;
+ leaf = helpgetleaf(subtree, propname, sublen, file, line, &leaf->nsubs);
+ subtree = &leaf->subtree;
+ vlen -= sublen;
+ propname = nextpart;
}
}
-static inline noreturn diewrite(void) { die("couldn't write to file"); }
+static void parse(const os_char *file, int len) {
+ char *s = sbase; // for convenience
+ if (s[len - 1] != '\n') dieparse(file, 0, "invalid text file (missing EOL)");
+ enum { BOL = 0, KEY = 4, KWS = 8, VAL = 12, COM = 16, ERR = -1 };
+ static const s8 statetrans[] = {
+ // layout: any, space|tab, #, \n
+ [BOL + 0] = KEY, [BOL + 1] = ERR, [BOL + 2] = COM, [BOL + 3] = BOL,
+ [KEY + 0] = KEY, [KEY + 1] = KWS, [KEY + 2] = ERR, [KEY + 3] = ERR,
+ [KWS + 0] = VAL, [KWS + 1] = KWS, [KWS + 2] = ERR, [KWS + 3] = ERR,
+ [VAL + 0] = VAL, [VAL + 1] = VAL, [VAL + 2] = COM, [VAL + 3] = BOL,
+ [COM + 0] = COM, [COM + 1] = COM, [COM + 2] = COM, [COM + 3] = BOL
+ };
+ char *key, *val;
+ for (int state = BOL, i = 0, line = 1; i < len; ++i) {
+ int transidx = state;
+ switch (s[i]) {
+ case '\0': dieparse(file, line, "unexpected null byte");
+ case ' ': case '\t': transidx += 1; break;
+ case '#': transidx += 2; break;
+ case '\n': transidx += 3;
+ }
+ int newstate = statetrans[transidx];
+ if_cold (newstate == ERR) {
+ if (state == BOL) dieparse(file, line, "unexpected indentation");
+ if (s[i] == '\n') dieparse(file, line, "unexpected end of line");
+ dieparse(file, line, "unexpected comment");
+ }
+ switch_exhaust (newstate) {
+ case KEY: if_cold (state != KEY) key = s + i; break;
+ case KWS: if_cold (state != KWS) s[i] = '\0'; break;
+ case VAL: if_cold (state == KWS) val = s + i; break;
+ case COM: case BOL:
+ if (state == VAL) {
+ int j = i;
+ while (s[j - 1] == ' ' || s[j - 1] == '\t') --j;
+ s[j] = '\0';
+ int vallen = j - (val - s) + 1;
+ handleentry(key, val, vallen, file, line);
+ }
+ }
+ line += state == BOL;
+ state = newstate;
+ }
+}
-#define _(x) \
- if (fprintf(out, "%s\n", x) < 0) diewrite();
-#define F(f, ...) \
- if (fprintf(out, f "\n", __VA_ARGS__) < 0) diewrite();
+static inline noreturn diewrite(void) { die(100, "couldn't write to file"); }
+#define _(x) if (fprintf(out, "%s\n", x) < 0) diewrite();
+#define _i(x) for (int i = 0; i < indent; ++i) fputc('\t', out); _(x)
+#define F(f, ...) if (fprintf(out, f "\n", __VA_ARGS__) < 0) diewrite();
+#define Fi(...) for (int i = 0; i < indent; ++i) fputc('\t', out); F(__VA_ARGS__)
#define H() \
_( "/* This file is autogenerated by src/build/mkentprops.c. DO NOT EDIT! */") \
_( "")
-static void decls(FILE *out) {
- for (struct class *c = classes.x[0]; c; c = c->hdr.x[0]) {
- for (struct prop **pp = c->props.data;
- pp - c->props.data < c->props.sz; ++pp) {
-F( "extern bool has_%s;", (*pp)->varname)
-F( "extern int %s;", (*pp)->varname)
+static void dosendtables(FILE *out, u16 art, int indent) {
+_i("switch (*p) {")
+ while (art != ART_NULL) {
+ if (art_cores[art].slen != 1) {
+ const char *tail = sbase + art_cores[art].soff + 1;
+ int len = art_cores[art].slen - 1;
+Fi(" case '%c': if (!strncmp(p + 1, \"%.*s\", %d)) {",
+art_firstbytes[art], len, tail, len)
+ }
+ else {
+Fi(" case '%c': {", art_firstbytes[art])
}
+ int idx = art_children[art];
+ // XXX: kind of a dumb and bad way to distinguish these. okay for now...
+ if (sbase[art_cores[art].soff + art_cores[art].slen - 1] != '\0') {
+ dosendtables(out, idx, indent + 1);
+ }
+ else {
+ if (art_leaves[idx].varstr != VAR_NONE) {
+_i(" if (mem_loads32(mem_offset(sp, off_SP_type)) != DT_DataTable) {")
+Fi(" %s = mem_loads32(mem_offset(sp, off_SP_offset));",
+sbase + art_leaves[idx].varstr);
+Fi(" --need;")
+_i(" }")
+ }
+ if (art_leaves[idx].subtree != ART_NULL) {
+_i(" if (mem_loads32(mem_offset(sp, off_SP_type)) == DT_DataTable) {")
+_i(" const struct SendTable *st = mem_loadptr(mem_offset(sp, off_SP_subtable));")
+_i(" // BEGIN SUBTABLE")
+Fi(" for (int i = 0, need = %d; i < st->nprops && need; ++i) {",
+art_leaves[idx].nsubs)
+_i(" const struct SendProp *sp = mem_offset(st->props, sz_SendProp * i);")
+_i(" const char *p = mem_loadptr(mem_offset(sp, off_SP_varname));")
+ dosendtables(out, art_leaves[idx].subtree, indent + 4);
+_i(" }")
+_i(" // END SUBTABLE")
+_i(" }")
+ }
+ }
+_i(" } break;")
+ art = art_cores[art].next;
}
+_i("}")
}
-static void defs(FILE *out) {
- for (struct class *c = classes.x[0]; c; c = c->hdr.x[0]) {
- for (struct prop **pp = c->props.data;
- pp - c->props.data < c->props.sz; ++pp) {
-F( "bool has_%s = false;", (*pp)->varname)
-F( "int %s;", (*pp)->varname)
+static void doclasses(FILE *out, u16 art, int indent) {
+_i("switch (*p) {")
+ for (; art != ART_NULL; art = art_cores[art].next) {
+ if (art_cores[art].slen != 1) {
+ const char *tail = sbase + art_cores[art].soff + 1;
+ int len = art_cores[art].slen - 1;
+Fi(" case '%c': if (!strncmp(p + 1, \"%.*s\", %d)) {",
+art_firstbytes[art], len, tail, len)
}
- }
-_( "")
-_( "static void initentprops(struct ServerClass *class) {")
-F( " for (int needclasses = %d; class; class = class->next) {", nclasses)
- char *else1 = "";
- for (struct class *c = classes.x[0]; c; c = c->hdr.x[0]) {
- // TODO(opt): some sort of PHF or trie instead of chained strcmp, if we
- // ever have more than a few classes/properties?
-F( " %sif (!strcmp(class->name, \"%s\")) {", else1, c->name)
-_( " struct SendTable *st = class->table;")
-F( " int needprops = %d;", c->props.sz)
-_( " for (struct SendProp *p = st->props;")
-_( " mem_diff(p, st->props) < st->nprops * sz_SendProp;")
-_( " p = mem_offset(p, sz_SendProp)) {")
-_( " const char *varname = mem_loadptr(mem_offset(p, off_SP_varname));")
- char *else2 = "";
- for (struct prop **pp = c->props.data;
- pp - c->props.data < c->props.sz; ++pp) {
-F( " %sif (!strcmp(varname, \"%s\")) {", else2, (*pp)->propname)
-F( " has_%s = true;", (*pp)->varname)
-F( " %s = mem_loads32(mem_offset(p, off_SP_offset));",
- (*pp)->varname)
-_( " if (!--needprops) break;")
-_( " }")
- else2 = "else ";
+ else {
+Fi(" case '%c': {", art_firstbytes[art])
}
-_( " }")
-_( " if (!--needclasses) break;")
-_( " }")
- else1 = "else ";
+ int idx = art_children[art];
+ // XXX: same dumb-and-bad-ness as above. there must be a better way!
+ if (sbase[art_cores[art].soff + art_cores[art].slen - 1] != '\0') {
+Fi(" p += %d;", art_cores[art].slen)
+ doclasses(out, art_children[art], indent + 2);
+ }
+ else {
+ assume(art_leaves[idx].varstr == VAR_NONE);
+ assume(art_leaves[idx].subtree != ART_NULL);
+_i(" const struct SendTable *st = class->table;")
+Fi(" for (int i = 0, need = %d; i < st->nprops && need; ++i) {",
+art_leaves[idx].nsubs)
+ // note: annoyingly long line here, but the generated code gets
+ // super nested anyway, so there's no point in caring really
+ // XXX: basically a dupe of dosendtables() - fold into above?
+_i(" const struct SendProp *sp = mem_offset(st->props, sz_SendProp * i);")
+_i(" const char *p = mem_loadptr(mem_offset(sp, off_SP_varname));")
+ dosendtables(out, art_leaves[idx].subtree, indent + 3);
+_i(" }")
+ }
+_i(" } break;")
+ }
+_i("}")
+}
+
+static void dodecls(FILE *out) {
+ for (int i = 0; i < ndecls; ++i) {
+ const char *s = sbase + decls[i];
+F( "extern int %s;", s);
+F( "#define has_%s (!!%s)", s, s); // offsets will NEVER be 0, due to vtable!
+ }
+}
+
+static void doinit(FILE *out) {
+ for (int i = 0; i < ndecls; ++i) {
+ const char *s = sbase + decls[i];
+F( "int %s = 0;", s);
}
+_( "")
+_( "static inline void initentprops(const struct ServerClass *class) {")
+_( " const char *p = class->name;")
+F( " for (int need = %d; need && class; class = class->next) {", nclasses)
+ doclasses(out, art_root, 2);
_( " }")
_( "}")
}
int OS_MAIN(int argc, os_char *argv[]) {
- for (++argv; *argv; ++argv) {
- int f = os_open_read(*argv);
- if (f == -1) die("couldn't open file");
- struct kv_parser kv = {0};
- struct parsestate state = {*argv, &kv};
- char buf[1024];
- int nread;
- while (nread = os_read(f, buf, sizeof(buf))) {
- if (nread == -1) die("couldn't read file");
- if (!kv_parser_feed(&kv, buf, nread, &kv_cb, &state)) goto ep;
- }
- if (!kv_parser_done(&kv)) {
-ep: fprintf(stderr, "mkentprops: %" fS ":%d:%d: bad syntax: %s\n",
- *argv, kv.line, kv.col, kv.errmsg);
- exit(1);
- }
- os_close(f);
- }
+ if (argc != 2) die(1, "wrong number of arguments");
+ int f = os_open_read(argv[1]);
+ if (f == -1) die(100, "couldn't open file");
+ vlong len = os_fsize(f);
+ if (len > 1u << 30 - 1) die(2, "input file is far too large");
+ sbase = malloc(len);
+ if (!sbase) die(100, "couldn't allocate memory");
+ if (os_read(f, sbase, len) != len) die(100, "couldn't read file");
+ os_close(f);
+ parse(argv[1], len);
FILE *out = fopen(".build/include/entprops.gen.h", "wb");
- if (!out) die("couldn't open entprops.gen.h");
+ if (!out) die(100, "couldn't open entprops.gen.h");
H();
- decls(out);
+ dodecls(out);
out = fopen(".build/include/entpropsinit.gen.h", "wb");
- if (!out) die("couldn't open entpropsinit.gen.h");
+ if (!out) die(100, "couldn't open entpropsinit.gen.h");
H();
- defs(out);
+ doinit(out);
return 0;
}
diff --git a/src/build/mkgamedata.c b/src/build/mkgamedata.c
index 266a411..1fce1cf 100644
--- a/src/build/mkgamedata.c
+++ b/src/build/mkgamedata.c
@@ -19,10 +19,8 @@
#include <string.h>
#include "../intdefs.h"
-#include "../kv.h"
#include "../langext.h"
#include "../os.h"
-#include "vec.h"
#ifdef _WIN32
#define fS "S"
@@ -30,214 +28,247 @@
#define fS "s"
#endif
-static noreturn die(const char *s) {
- fprintf(stderr, "mkgamedata: %s\n", s);
- exit(100);
+static noreturn die(int status, const char *s) {
+ fprintf(stderr, "mkentprops: %s\n", s);
+ exit(status);
+}
+
+// concatenated input file contents - string values are indices off this
+static char *sbase = 0;
+
+static const os_char *const *srcnames;
+static noreturn dieparse(int file, int line, const char *s) {
+ fprintf(stderr, "mkentprops: %" fS ":%d: %s\n", srcnames[file], line, s);
+ exit(2);
+}
+
+#define MAXENTS 32768
+static int tags[MAXENTS]; // varname/gametype
+static int exprs[MAXENTS];
+static uchar indents[MAXENTS]; // nesting level
+static schar srcfiles[MAXENTS];
+static int srclines[MAXENTS];
+static int nents = 0;
+
+static inline void handleentry(char *k, char *v, int indent,
+ int file, int line) {
+ int previndent = nents ? indents[nents - 1] : -1; // meh
+ if_cold (indent > previndent + 1) {
+ dieparse(file, line, "excessive indentation");
+ }
+ if_cold (indent == previndent && !exprs[nents - 1]) {
+ dieparse(file, line - 1, "missing a value and/or conditional(s)");
+ }
+ if_cold (nents == MAXENTS) die(2, "out of array indices");
+ tags[nents] = k - sbase;
+ exprs[nents] = v - sbase; // will produce garbage for null v. this is fine!
+ indents[nents] = indent;
+ srcfiles[nents] = file;
+ srclines[nents++] = line;
}
/*
- * We keep the gamedata KV format as simple as possible. Default values are
+ * -- Quick file format documentation! --
+ *
+ * We keep the gamedata format as simple as possible. Default values are
* specified as direct key-value pairs:
*
- * <varname> <expr>
+ * <varname> <expr>
*
- * Game- or engine-specific values are set using blocks:
+ * Game- or engine-specific values are set using indented blocks:
*
- * <varname> { <gametype> <expr> <gametype> <expr> ... [default <expr>] }
+ * <varname> <optional-default>
+ * <gametype1> <expr>
+ * <gametype2> <expr> # you can write EOL comments too!
+ * <some-other-nested-conditional-gametype> <expr>
*
* The most complicated it can get is if conditionals are nested, which
- * basically translates directly into nested ifs:
- * <varname> { <gametype> { <gametype> <expr> <gametype> <expr> } }
- * [however many entries...]
+ * basically translates directly into nested ifs.
*
- * If that doesn't make sense, just look at one of the existing data files and
- * then it should be obvious. :^)
- *
- * Note: if `default` isn't given in a conditional block, that piece of gamedata
- * is considered unavailable and modules that use it won't get initialised/used
- * unless all the conditions are met.
+ * Just be aware that whitespace is significant, and you have to use tabs.
+ * Any and all future complaints about that decision SHOULD - and MUST - be
+ * directed to the Python Software Foundation and the authors of the POSIX
+ * Makefile specification. In that order.
*/
-struct vec_ent VEC(struct ent *);
-struct ent {
- const char *name; // (or condition tag, in a child node)
- const char *defexpr;
- struct vec_ent subents;
- struct ent *parent; // to back up a level during parse
-};
-// root only contains subents list but it's easier to use the same struct
-static struct ent root = {0};
-
-struct parsestate {
- const os_char *filename;
- struct kv_parser *parser;
- struct ent *curent; // current ent lol
- bool haddefault; // blegh;
-};
-
-static noreturn badparse(struct parsestate *state, const char *e) {
- fprintf(stderr, "mkgamedata: %" fS ":%d:%d: parse error: %s",
- state->filename, state->parser->line, state->parser->col, e);
- exit(1);
-}
-static void kv_cb(enum kv_token type, const char *p, uint len, void *ctxt) {
- struct parsestate *state = ctxt;
- switch (type) {
- case KV_IDENT: case KV_IDENT_QUOTED:;
- if (len == 7 && !memcmp(p, "default", 7)) { // special case!
- if (state->curent == &root) {
- badparse(state, "unexpected default keyword at top level");
+static void parse(int file, char *s, int len) {
+ if (s[len - 1] != '\n') dieparse(file, 0, "invalid text file (missing EOL)");
+ enum { BOL = 0, KEY = 4, KWS = 8, VAL = 12, COM = 16, ERR = -1 };
+ static const s8 statetrans[] = {
+ // layout: any, space|tab, #, \n
+ [BOL + 0] = KEY, [BOL + 1] = BOL, [BOL + 2] = COM, [BOL + 3] = BOL,
+ [KEY + 0] = KEY, [KEY + 1] = KWS, [KEY + 2] = COM, [KEY + 3] = BOL,
+ [KWS + 0] = VAL, [KWS + 1] = KWS, [KWS + 2] = COM, [KWS + 3] = BOL,
+ [VAL + 0] = VAL, [VAL + 1] = VAL, [VAL + 2] = COM, [VAL + 3] = BOL,
+ [COM + 0] = COM, [COM + 1] = COM, [COM + 2] = COM, [COM + 3] = BOL
+ };
+ char *key, *val = sbase; // 0 index by default (invalid value works as null)
+ for (int state = BOL, i = 0, line = 1, indent = 0; i < len; ++i) {
+ int transidx = state;
+ char c = s[i];
+ switch (c) {
+ case '\0': dieparse(file, line, "unexpected null byte");
+ case ' ':
+ if_cold (state == BOL) {
+ dieparse(file, line, "unexpected space at start of line");
}
- struct ent *e = state->curent;
- if (e->defexpr) {
- badparse(state, "multiple default keywords");
- }
- state->haddefault = true;
+ case '\t':
+ transidx += 1;
break;
- }
- state->haddefault = false;
- char *k = malloc(len + 1);
- if (!k) die("couldn't allocate key string");
- // FIXME(?): should check and prevent duplicate keys probably!
- // need table.h or something to avoid O(n^2) :)
- memcpy(k, p, len); k[len] = '\0';
- struct ent *e = malloc(sizeof(*e));
- if (!e) die("couldn't allocate memory");
- e->name = k;
- e->defexpr = 0;
- e->subents = (struct vec_ent){0};
- if (!vec_push(&state->curent->subents, e)) {
- die("couldn't append to array");
- }
- e->parent = state->curent;
- state->curent = e;
- break;
- case KV_NEST_START:
- if (state->haddefault) badparse(state, "default cannot be a block");
- break;
- case KV_NEST_END:
- if (!state->curent->parent) {
- badparse(state, "unexpected closing brace");
- }
- state->curent = state->curent->parent;
- break;
- case KV_VAL: case KV_VAL_QUOTED:;
- char *s = malloc(len + 1);
- if (!s) die("couldn't allocate value string");
- memcpy(s, p, len); s[len] = '\0';
- state->curent->defexpr = s;
- if (!state->haddefault) {
- // a non-default value is just a node that itself only has a
- // default value.
- state->curent = state->curent->parent;
- }
- break;
- case KV_COND_PREFIX: case KV_COND_SUFFIX:
- badparse(state, "unexpected conditional");
+ case '#': transidx += 2; break;
+ case '\n': transidx += 3;
+ }
+ int newstate = statetrans[transidx];
+ switch_exhaust (newstate) {
+ case KEY: if_cold (state != KEY) key = s + i; break;
+ case KWS: if_cold (state != KWS) s[i] = '\0'; break;
+ case VAL: if_cold (state == KWS) val = s + i; break;
+ case BOL:
+ indent += state == BOL;
+ if_cold (indent > 255) { // this shouldn't happen if we're sober
+ dieparse(file, line, "exceeded max nesting level (255)");
+ }
+ case COM:
+ if_hot (state != BOL) {
+ if (state != COM) { // blegh!
+ int j = i;
+ while (s[j - 1] == ' ' || s[j - 1] == '\t') --j;
+ s[j] = '\0';
+ handleentry(key, val, indent, file, line);
+ }
+ val = sbase; // reset this again
+ }
+ }
+ if_cold (c == '\n') { // ugh, so much for state transitions.
+ indent = 0;
+ ++line;
+ }
+ state = newstate;
}
}
-static inline noreturn diewrite(void) { die("couldn't write to file"); }
-
-#define _doindent \
- for (int _indent = 0; _indent < indent; ++_indent) { \
- if (fputs("\t", out) == -1) diewrite(); \
- }
-#define _(x) \
- if (fprintf(out, "%s\n", x) < 0) diewrite();
-#define _i(x) _doindent _(x)
-#define F(f, ...) \
- if (fprintf(out, f "\n", __VA_ARGS__) < 0) diewrite();
-#define Fi(...) _doindent F(__VA_ARGS__)
+static inline noreturn diewrite(void) { die(100, "couldn't write to file"); }
+#define _(x) if (fprintf(out, "%s\n", x) < 0) diewrite();
+#define _i(x) for (int i = 0; i < indent; ++i) fputc('\t', out); _(x)
+#define F(f, ...) if (fprintf(out, f "\n", __VA_ARGS__) < 0) diewrite();
+#define Fi(...) for (int i = 0; i < indent; ++i) fputc('\t', out); F(__VA_ARGS__)
#define H() \
_( "/* This file is autogenerated by src/build/mkgamedata.c. DO NOT EDIT! */") \
_( "")
static void decls(FILE *out) {
- for (struct ent *const *pp = root.subents.data;
- pp - root.subents.data < root.subents.sz; ++pp) {
- if ((*pp)->defexpr) {
-F( "#define has_%s true", (*pp)->name)
- if ((*pp)->subents.sz) {
-F( "extern int %s;", (*pp)->name)
- }
- else {
-F( "enum { %s = %s };", (*pp)->name, (*pp)->defexpr)
- }
+ for (int i = 0; i < nents; ++i) {
+ if (indents[i] != 0) continue;
+F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]])
+ if (exprs[i]) { // default value is specified - entry always exists
+ // *technically* this case is redundant - the other has_ macro would
+ // still work. however, having a distinct case makes the generated
+ // header a little easier to read at a glance.
+F( "#define has_%s 1", sbase + tags[i])
}
- else {
-F( "extern bool has_%s;", (*pp)->name)
-F( "extern int %s;", (*pp)->name)
+ else { // entry is missing unless a tag is matched
+ // implementation detail: INT_MIN is reserved for missing gamedata!
+ // XXX: for max robustness, should probably check for this in input?
+F( "#define has_%s (%s != -2147483648)", sbase + tags[i], sbase + tags[i])
+ }
+F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]])
+ if_cold (i == nents - 1 || !indents[i + 1]) { // no tags - it's constant
+F( "enum { %s = (%s) };", sbase + tags[i], sbase + exprs[i])
+ }
+ else { // global variable intialised by gamedata_init() call
+F( "extern int %s;", sbase + tags[i]);
}
}
}
-static void inits(FILE *out, const char *var, struct vec_ent *v, bool needhas,
- int indent) {
- for (struct ent *const *pp = v->data; pp - v->data < v->sz; ++pp) {
-Fi("if (GAMETYPE_MATCHES(%s)) {", (*pp)->name)
- if ((*pp)->defexpr) {
- if (needhas) {
-Fi(" has_%s = true;", var);
+static void defs(FILE *out) {
+ for (int i = 0; i < nents; ++i) {
+ if (indents[i] != 0) continue;
+ if_hot (i < nents - 1 && indents[i + 1]) {
+F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]])
+ if (exprs[i]) {
+F( "int %s = (%s);", sbase + tags[i], sbase + exprs[i])
+ }
+ else {
+F( "int %s = -2147483648;", sbase + tags[i])
}
-Fi(" %s = %s;", var, (*pp)->defexpr);
}
- inits(out, var, &(*pp)->subents, needhas && !(*pp)->defexpr, indent + 1);
-_i("}")
}
}
-static void defs(FILE *out) {
- for (struct ent *const *pp = root.subents.data;
- pp - root.subents.data < root.subents.sz; ++pp) {
- if ((*pp)->defexpr) {
- if ((*pp)->subents.sz) {
-F( "int %s = %s;", (*pp)->name, (*pp)->defexpr);
+static void init(FILE *out) {
+_( "void gamedata_init(void) {")
+ int varidx;
+ int indent = 0;
+ for (int i = 0; i < nents; ++i) {
+ if (indents[i] < indents[i - 1]) {
+ for (; indent != indents[i]; --indent) {
+_i("}")
}
}
+ if (indents[i] == 0) {
+ varidx = i;
+ continue;
+ }
+F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]])
+ if (indents[i] > indents[i - 1]) {
+Fi(" if (GAMETYPE_MATCHES(%s)) {", sbase + tags[i])
+ ++indent;
+ }
else {
-F( "int %s;", (*pp)->name);
-F( "bool has_%s = false;", (*pp)->name);
+_i("}")
+F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]])
+Fi("else if (GAMETYPE_MATCHES(%s)) {", sbase + tags[i])
+ }
+ if (exprs[i]) {
+F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]])
+Fi(" %s = (%s);", sbase + tags[varidx], sbase + exprs[i])
}
}
-_( "")
-_( "void gamedata_init(void) {")
- for (struct ent *const *pp = root.subents.data;
- pp - root.subents.data < root.subents.sz; ++pp) {
- inits(out, (*pp)->name, &(*pp)->subents, !(*pp)->defexpr, 1);
+ for (; indent != 0; --indent) {
+_i("}")
}
_( "}")
}
int OS_MAIN(int argc, os_char *argv[]) {
- for (++argv; *argv; ++argv) {
- int fd = os_open_read(*argv);
- if (fd == -1) die("couldn't open file");
- struct kv_parser kv = {0};
- struct parsestate state = {*argv, &kv, &root};
- char buf[1024];
- int nread;
- while (nread = os_read(fd, buf, sizeof(buf))) {
- if (nread == -1) die("couldn't read file");
- if (!kv_parser_feed(&kv, buf, nread, &kv_cb, &state)) goto ep;
+ srcnames = (const os_char *const *)argv;
+ int sbase_len = 0, sbase_max = 65536;
+ sbase = malloc(sbase_max);
+ if (!sbase) die(100, "couldn't allocate memory");
+ int n = 1;
+ for (++argv; *argv; ++argv, ++n) {
+ int f = os_open_read(*argv);
+ if (f == -1) die(100, "couldn't open file");
+ vlong len = os_fsize(f);
+ if (sbase_len + len > 1u << 29) {
+ die(2, "combined input files are far too large");
}
- if (!kv_parser_done(&kv)) {
-ep: fprintf(stderr, "mkgamedata: %" fS ":%d:%d: bad syntax: %s\n",
- *argv, kv.line, kv.col, kv.errmsg);
- exit(1);
+ if (sbase_len + len > sbase_max) {
+ fprintf(stderr, "mkgamedata: warning: need to resize string. "
+ "increase sbase_max to avoid this extra work!\n");
+ sbase_max *= 4;
+ sbase = realloc(sbase, sbase_max);
+ if (!sbase) die(100, "couldn't grow memory allocation");
}
- os_close(fd);
+ char *s = sbase + sbase_len;
+ if (os_read(f, s, len) != len) die(100, "couldn't read file");
+ os_close(f);
+ parse(n, s, len);
+ sbase_len += len;
}
FILE *out = fopen(".build/include/gamedata.gen.h", "wb");
- if (!out) die("couldn't open gamedata.gen.h");
+ if (!out) die(100, "couldn't open gamedata.gen.h");
H();
decls(out);
out = fopen(".build/include/gamedatainit.gen.h", "wb");
- if (!out) die("couldn't open gamedatainit.gen.h");
+ if (!out) die(100, "couldn't open gamedatainit.gen.h");
H();
defs(out);
+ _("")
+ init(out);
return 0;
}
diff --git a/src/build/vec.h b/src/build/vec.h
index 6b4dffb..6dfa645 100644
--- a/src/build/vec.h
+++ b/src/build/vec.h
@@ -62,7 +62,7 @@ static bool _vec_make_room(struct _vec *v, uint tsize, uint addcnt) {
// internal: for reuse by vec0
#define _vec_push(v, val, slack) ( \
((v)->sz + (slack) < (v)->max || \
- _vec_make_room((struct _vec *)(v), sizeof(val), 1)) && \
+ _vec_make_room((struct _vec *)(v), sizeof(val), 1)) && /*NOLINT*/ \
((v)->data[(v)->sz++ - slack] = (val), true) \
)
diff --git a/src/engineapi.c b/src/engineapi.c
index 04e6a8c..b92de4d 100644
--- a/src/engineapi.c
+++ b/src/engineapi.c
@@ -115,7 +115,8 @@ void engineapi_lateinit(void) {
// > can detect that and set the SPROP_IS_VECTOR_ELEM flag.
// by doing this at the deferred stage, we avoid having to abs() everything
if (srvdll && has_vtidx_GetAllServerClasses && has_sz_SendProp &&
- has_off_SP_varname && has_off_SP_offset) {
+ has_off_SP_varname && has_off_SP_type && has_off_SP_offset &&
+ has_DT_DataTable) {
initentprops(GetAllServerClasses(srvdll));
}
}
diff --git a/src/engineapi.h b/src/engineapi.h
index 4489e21..4f96b73 100644
--- a/src/engineapi.h
+++ b/src/engineapi.h
@@ -1,5 +1,5 @@
/*
- * Copyright © 2023 Michael Smith <mikesmiffy128@gmail.com>
+ * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -96,15 +96,6 @@ struct CMoveData {
struct vec3f origin;
};
-#define SENDPROP_INT 0
-#define SENDPROP_FLOAT 1
-#define SENDPROP_VEC 2
-#define SENDPROP_VECXY 3
-#define SENDPROP_STR 4
-#define SENDPROP_ARRAY 5
-#define SENDPROP_DTABLE 6
-#define SENDPROP_INT64 7
-
// these have to be opaque because, naturally, they're unstable between
// branches - access stuff using gamedata offsets as usual
struct RecvProp;
diff --git a/src/kv.c b/src/kv.c
deleted file mode 100644
index 698aa8e..0000000
--- a/src/kv.c
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "intdefs.h"
-#include "kv.h"
-#include "langext.h"
-
-#define EOF -1
-
-// parser states, implemented by STATE() macros in kv_parser_feed() below.
-// needs to be kept in sync!
-enum {
- ok, ok_slash,
- ident, ident_slash, identq,
- sep, sep_slash, condsep, condsep_slash,
- cond_prefix,
- val, val_slash, valq, afterval, afterval_slash,
- cond_suffix
-};
-
-bool kv_parser_feed(struct kv_parser *this, const char *in, uint sz,
- kv_parser_cb cb, void *ctxt) {
- const char *p = in;
- short c;
-
- // slight hack, makes init more convenient (just {0})
- if (!this->line) this->line = 1;
- if (!this->outp) this->outp = this->tokbuf;
-
- // this is a big ol' blob of ugly state machine macro spaghetti - too bad!
- #define INCCOL() (*p == '\n' ? (++this->line, this->col = 0) : ++this->col)
- #define READ() (p == in + sz ? EOF : (INCCOL(), *p++))
- #define ERROR(s) do { \
- this->errmsg = s; \
- return false; \
- } while (0)
- #define OUT(c) do { \
- if (this->outp - this->tokbuf == KV_TOKEN_MAX) { \
- ERROR("token unreasonably large!"); \
- } \
- *this->outp++ = (c); \
- } while (0)
- #define CASE_WS case ' ': case '\t': case '\n': case '\r'
- // note: multi-eval
- #define IS_WS(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r')
- #define STATE(s) case s: s
- #define HANDLE_EOF() do { case EOF: return true; } while (0)
- #define SKIP_COMMENT(next) do { \
- this->state = next; \
- this->incomment = true; \
- goto start; \
- } while (0)
- #define GOTO(s) do { this->state = s; goto s; } while (0)
- #define CB(type) do { \
- cb(type, this->tokbuf, this->outp - this->tokbuf, ctxt); \
- this->outp = this->tokbuf; \
- } while (0)
- // prefix and suffix conditions are more or less the same, just in different
- // contexts, because very good syntax yes.
- #define CONDSTATE(name, type, next) do { \
- STATE(name): \
- switch (c = READ()) { \
- HANDLE_EOF(); \
- CASE_WS: ERROR("unexpected whitespace in conditional"); \
- case '[': ERROR("unexpected opening bracket in conditional"); \
- case '{': case '}': ERROR("unexpected brace in conditional"); \
- case '/': ERROR("unexpected slash in conditional"); \
- case ']': CB(type); GOTO(next); \
- default: OUT(c); goto name; \
- } \
- } while (0)
-
-start: // special spaghetti so we don't have a million different comment states
- if (this->incomment) while ((c = READ()) != '\n') if (c == EOF) return true;
- this->incomment = false;
-
-switch (this->state) {
-
-STATE(ok):
- c = READ();
-ident_postread:
- switch (c) {
- HANDLE_EOF();
- CASE_WS: goto ok;
- case '#': ERROR("kv macros not supported");
- case '{': ERROR("unexpected control character");
- case '}':
- if (!this->nestlvl) ERROR("too many closing braces");
- --this->nestlvl;
- char c_ = c;
- cb(KV_NEST_END, &c_, 1, ctxt);
- goto ok;
- case '"': GOTO(identq);
- case '/': GOTO(ok_slash);
- case '[': case ']': ERROR("unexpected conditional bracket");
- default: GOTO(ident);
- }
-
-STATE(ok_slash):
- switch (c = READ()) {
- HANDLE_EOF();
- case '/': SKIP_COMMENT(ok);
- default: GOTO(ident);
- }
-
-ident:
- OUT(c);
-case ident: // continue here
- switch (c = READ()) {
- HANDLE_EOF();
- case '{':
- CB(KV_IDENT);
- ++this->nestlvl;
- char c_ = c;
- cb(KV_NEST_START, &c_, 1, ctxt);
- GOTO(ok);
- // XXX: assuming [ is a token break; haven't checked Valve's code
- case '[': CB(KV_IDENT); GOTO(cond_prefix);
- case '}': ERROR("unexpected closing brace");
- case ']': ERROR("unexpected closing bracket");
- case '"': ERROR("unexpected quote mark");
- CASE_WS: CB(KV_IDENT); GOTO(sep);
- case '/': GOTO(ident_slash);
- default: goto ident;
- }
-
-STATE(ident_slash):
- switch (c = READ()) {
- HANDLE_EOF();
- case '/': CB(KV_IDENT); SKIP_COMMENT(sep);
- default: OUT('/'); GOTO(ident);
- }
-
-STATE(identq):
- switch (c = READ()) {
- HANDLE_EOF();
- case '"': CB(KV_IDENT_QUOTED); GOTO(sep);
- default: OUT(c); goto identq;
- }
-
-STATE(sep):
- do c = READ(); while (IS_WS(c));
- switch (c) {
- HANDLE_EOF();
- case '{':;
- char c_ = c;
- ++this->nestlvl;
- cb(KV_NEST_START, &c_, 1, ctxt);
- GOTO(ok);
- case '[': GOTO(cond_prefix);
- case '"': GOTO(valq);
- case '}': ERROR("unexpected closing brace");
- case ']': ERROR("unexpected closing bracket");
- case '/': GOTO(sep_slash);
- default: GOTO(val);
- }
-
-STATE(sep_slash):
- switch (c = READ()) {
- HANDLE_EOF();
- case '/': SKIP_COMMENT(sep);
- default: GOTO(val);
- }
-
-CONDSTATE(cond_prefix, KV_COND_PREFIX, condsep);
-
-STATE(condsep):
- do c = READ(); while (IS_WS(c));
- switch (c) {
- HANDLE_EOF();
- case '{':;
- char c_ = c;
- ++this->nestlvl;
- cb(KV_NEST_START, &c_, 1, ctxt);
- GOTO(ok);
- case '}': ERROR("unexpected closing brace");
- case '[': ERROR("unexpected opening bracket");
- case ']': ERROR("unexpected closing bracket");
- case '/': GOTO(condsep_slash);
- // these conditions only go before braces because very good syntax
- default: ERROR("unexpected string value after prefix condition");
- }
-
-STATE(condsep_slash):
- switch (c = READ()) {
- HANDLE_EOF();
- case '/': SKIP_COMMENT(condsep);
- default: ERROR("unexpected string value after prefix condition");
- }
-
-val:
- OUT(c);
-case val: // continue here
- switch (c = READ()) {
- HANDLE_EOF();
- case '{': ERROR("unexpected opening brace");
- case ']': ERROR("unexpected closing bracket");
- case '"': ERROR("unexpected quotation mark");
- // might get [ or } with no whitespace
- case '}':
- CB(KV_VAL);
- --this->nestlvl;
- char c_ = c;
- cb(KV_NEST_END, &c_, 1, ctxt);
- GOTO(afterval);
- case '[': CB(KV_VAL); GOTO(cond_suffix);
- CASE_WS: CB(KV_VAL); GOTO(afterval);
- case '/': GOTO(val_slash);
- default: goto val;
- }
-
-STATE(val_slash):
- switch (c = READ()) {
- HANDLE_EOF();
- case '/': CB(KV_VAL); SKIP_COMMENT(afterval);
- default: OUT('/'); GOTO(val);
- }
-
-STATE(valq):
- switch (c = READ()) {
- HANDLE_EOF();
- case '"': CB(KV_VAL_QUOTED); GOTO(afterval);
- default: OUT(c); goto valq;
- }
-
-STATE(afterval):
- switch (c = READ()) {
- HANDLE_EOF();
- CASE_WS: goto afterval;
- case '[': GOTO(cond_suffix);
- case '/': GOTO(afterval_slash);
- // mildly dumb hack: if no conditional, we can just use the regular
- // starting state handler to get next transition correct - just avoid
- // double-reading the character
- default: goto ident_postread;
- }
-
-STATE(afterval_slash):
- switch (c = READ()) {
- HANDLE_EOF();
- case '/': SKIP_COMMENT(afterval);
- default: GOTO(ident);
- }
-
-CONDSTATE(cond_suffix, KV_COND_SUFFIX, ok);
-
-}
-
- #undef CONDSTATE
- #undef CB
- #undef GOTO
- #undef SKIP_COMMENT
- #undef HANDLE_EOF
- #undef STATE
- #undef IS_WS
- #undef CASE_WS
- #undef OUT
- #undef ERROR
- #undef READ
- #undef INCCOL
-
- unreachable; // pretty sure!
-}
-
-bool kv_parser_done(struct kv_parser *this) {
- if (this->state != ok && this->state != afterval) {
- this->errmsg = "unexpected end of input";
- return false;
- }
- if (this->nestlvl != 0) {
- this->errmsg = "unterminated object (unbalanced braces)";
- return false;
- }
- return true;
-}
-
-// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/kv.h b/src/kv.h
deleted file mode 100644
index 63a9146..0000000
--- a/src/kv.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef INC_KV_H
-#define INC_KV_H
-
-#include "intdefs.h"
-
-/*
- * Maximum length of a single token. Since this code is trying to avoid dynamic
- * memory allocations, this arbitrary limit is chosen to accomodate all known
- * "reasonable" tokens likely to come in any real files, probably.
- */
-#define KV_TOKEN_MAX 512
-
-/*
- * Contains all the state associated with parsing (lexing?) a KeyValues file.
- * Should be zeroed out prior to the first call (initialise with `= {0};`).
- */
-struct kv_parser {
- ushort line, col; /* the current line and column in the text */
- char state : 7; /* internal, shouldn't usually be touched directly */
- bool incomment : 1; /* internal */
- ushort nestlvl; /* internal */
- const char *errmsg; /* the error message, *IF* parsing just failed */
-
- // trying to avoid dynamic allocations - valve's own parser seems to have
- // a similar limit as well and our use case doesn't really need to worry
- // about stupid massive values, so it's fine
- char *outp;
- char tokbuf[KV_TOKEN_MAX];
-};
-
-/*
- * These are the tokens that can be received by a kv_parser_cb (below).
- * The x-macro and string descriptions are given to allow for easy debug
- * stringification. Note that this "parser" is really just lexing out these
- * tokens - handling the actual structure of the file should be done in the
- * callback. This is so that data can be streamed rather than all read into
- * memory at once.
- */
-#define KV_TOKENS(X) \
- X(KV_IDENT, "ident") \
- X(KV_IDENT_QUOTED, "quoted-ident") \
- X(KV_VAL, "value") \
- X(KV_VAL_QUOTED, "quoted-value") \
- X(KV_COND_PREFIX, "cond-prefix") \
- X(KV_COND_SUFFIX, "cond-suffix") \
- X(KV_NEST_START, "object-start") \
- X(KV_NEST_END, "object-end")
-
-#define _ENUM(s, ignore) s,
-enum kv_token { KV_TOKENS(_ENUM) };
-#undef _ENUM
-
-typedef void (*kv_parser_cb)(enum kv_token type, const char *p, uint len,
- void *ctxt);
-
-/*
- * Feed a block of text into the lexer. This would usually be a block of data
- * read in from a file.
- *
- * The lexer is reentrant and can be fed arbitrarily sized blocks of data at a
- * time. The function may return early in the event of an error; a return value
- * of false indicates thaat this has happened, otherwise true is returned.
- *
- * In the event of an error, the errmsg, line and col fields of the parser
- * struct can be used for diagnostics.
- */
-bool kv_parser_feed(struct kv_parser *this, const char *in, uint sz,
- kv_parser_cb cb, void *ctxt);
-
-/*
- * This indicates that parsing is done; if this is called at an unexpected time,
- * a parsing error will result; this is indicated in the return value as with
- * kv_parser_feed.
- */
-bool kv_parser_done(struct kv_parser *this);
-
-#endif
-
-// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/l4dwarp.c b/src/l4dwarp.c
index d24e39c..c17c7b8 100644
--- a/src/l4dwarp.c
+++ b/src/l4dwarp.c
@@ -44,8 +44,8 @@ DEF_CCMD_HERE_UNREG(sst_l4d_testwarp, "Simulate a bot warping to you",
struct edict *ed = ent_getedict(con_cmdclient + 1);
if_cold (!ed) { errmsg_errorx("couldn't access player entity"); return; }
void *e = GetBaseEntity(ed->ent_unknown); // is this call required?
- struct vec3f *org = mem_offset(e, off_entpos);
- struct vec3f *ang = mem_offset(e, off_eyeang);
+ const struct vec3f *org = mem_offset(e, off_entpos);
+ const struct vec3f *ang = mem_offset(e, off_eyeang);
// L4D idle warps go up to 10 units behind yaw, lessening based on pitch.
float pitch = ang->x * M_PI / 180, yaw = ang->y * M_PI / 180;
float shift = -10 * cos(pitch);
diff --git a/src/mem.h b/src/mem.h
index 7b1a0b0..6acf018 100644
--- a/src/mem.h
+++ b/src/mem.h
@@ -67,7 +67,7 @@ static inline usize mem_loadusize(const void *p) {
}
/* Adds a byte count to a pointer and returns a freely-assignable pointer. */
-static inline void *mem_offset(void *p, int off) { return (char *)p + off; }
+static inline void *mem_offset(const void *p, int off) { return (char *)p + off; }
/* Returns the offset in bytes from one pointer to another (p - q). */
static inline ssize mem_diff(const void *p, const void *q) {
diff --git a/test/kv.test.c b/test/kv.test.c
deleted file mode 100644
index 1bd1784..0000000
--- a/test/kv.test.c
+++ /dev/null
@@ -1,55 +0,0 @@
-/* This file is dedicated to the public domain. */
-
-{.desc = "the KeyValues parser"};
-
-// undef conflicting macros
-#undef ERROR // windows.h
-#undef OUT // "
-#undef EOF // stdio.h
-#include "../src/kv.c"
-
-#include "../src/intdefs.h"
-#include "../src/langext.h"
-
-static noreturn die(const struct kv_parser *kvp) {
- fprintf(stderr, "parse error: %d:%d: %s\n", kvp->line, kvp->col,
- kvp->errmsg);
- exit(1);
-}
-
-static void tokcb(enum kv_token type, const char *p, uint len,
- void *ctxt) {
- // nop - we're just testing the tokeniser
-}
-
-static const char data[] =
-"KeyValues {\n\
- Key/1 Val1![conditional]\n\
- Key2\n\
-Val2// comment\n\
- \"String Key\" // also comment\n\
- Val3 Key4 [conditional!]{ Key5 \"Value Five\" } // one more\n\
-} \n\
-";
-static const int sz = sizeof(data) - 1;
-
-TEST("parsing should work with any buffer size") {
- for (int chunksz = 3; chunksz <= sz; ++chunksz) {
- struct kv_parser kvp = {0};
- // sending data in chunks to test reentrancy
- for (int chunk = 0; chunk * chunksz < sz; ++chunk) {
- int thischunk = chunksz;
- if (chunk * chunksz + thischunk > sz) {
- thischunk = sz - chunk * chunksz;
- }
- if (!kv_parser_feed(&kvp, data + chunk * chunksz, thischunk,
- tokcb, 0)) {
- die(&kvp);
- }
- }
- if (!kv_parser_done(&kvp)) die(&kvp);
- }
- return true;
-}
-
-// vi: sw=4 ts=4 noet tw=80 cc=80