diff options
-rwxr-xr-x | compile | 12 | ||||
-rw-r--r-- | compile.bat | 14 | ||||
-rw-r--r-- | gamedata/engine.kv | 181 | ||||
-rw-r--r-- | gamedata/engine.txt | 162 | ||||
-rw-r--r-- | gamedata/entprops.kv | 9 | ||||
-rw-r--r-- | gamedata/entprops.txt | 8 | ||||
-rw-r--r-- | gamedata/gamelib.kv | 57 | ||||
-rw-r--r-- | gamedata/gamelib.txt | 46 | ||||
-rw-r--r-- | gamedata/inputsystem.kv | 17 | ||||
-rw-r--r-- | gamedata/inputsystem.txt | 12 | ||||
-rw-r--r-- | gamedata/matchmaking.kv | 11 | ||||
-rw-r--r-- | gamedata/matchmaking.txt | 7 | ||||
-rw-r--r-- | gamedata/vgui2.kv | 7 | ||||
-rw-r--r-- | gamedata/vgui2.txt | 7 | ||||
-rw-r--r-- | gamedata/vguimatsurface.kv | 69 | ||||
-rw-r--r-- | gamedata/vguimatsurface.txt | 49 | ||||
-rw-r--r-- | src/build/mkentprops.c | 447 | ||||
-rw-r--r-- | src/build/mkgamedata.c | 345 | ||||
-rw-r--r-- | src/build/vec.h | 2 | ||||
-rw-r--r-- | src/engineapi.c | 3 | ||||
-rw-r--r-- | src/engineapi.h | 11 | ||||
-rw-r--r-- | src/kv.c | 290 | ||||
-rw-r--r-- | src/kv.h | 95 | ||||
-rw-r--r-- | src/l4dwarp.c | 4 | ||||
-rw-r--r-- | src/mem.h | 2 | ||||
-rw-r--r-- | test/kv.test.c | 55 |
26 files changed, 807 insertions, 1115 deletions
@@ -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); @@ -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 |