diff --git a/.gitignore b/.gitignore index 958ac4a9..2ac30bb9 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ ipch *.idb *.vcxproj *.sln +*.vcxproj.FileListAbsolute.txt +*.pdb # OSX/Linux build products *.mak @@ -49,6 +51,8 @@ client.dylib.dSYM/ server.dylib server.dylib.dSYM/ +sp/game/bin + # files generated by running a mod config.cfg @@ -61,6 +65,7 @@ config.cfg *.filters *.vpc_crc *.sentinel +*.recipe # Backup files *.bak @@ -90,4 +95,4 @@ sp/src/vgui2/*.dll sp/src/vgui2/*.pdb # Build support files -sp/src/devtools/gcc9+support.o +sp/src/devtools/gcc9+support.o \ No newline at end of file diff --git a/sp/src/game/client/c_baseanimating.cpp b/sp/src/game/client/c_baseanimating.cpp index edde6282..7e63a6bb 100644 --- a/sp/src/game/client/c_baseanimating.cpp +++ b/sp/src/game/client/c_baseanimating.cpp @@ -1531,35 +1531,32 @@ float C_BaseAnimating::ClampCycle( float flCycle, bool isLooping ) //----------------------------------------------------------------------------- const Vector& C_BaseAnimating::ScriptGetAttachmentOrigin( int iAttachment ) { - static Vector absOrigin; - static QAngle qa; + QAngle qa; C_BaseAnimating::GetAttachment( iAttachment, absOrigin, qa ); return absOrigin; } -const Vector& C_BaseAnimating::ScriptGetAttachmentAngles( int iAttachment ) +const QAngle& C_BaseAnimating::ScriptGetAttachmentAngles( int iAttachment ) { - - static Vector absOrigin; - static Vector absAngles; static QAngle qa; + Vector absOrigin; C_BaseAnimating::GetAttachment( iAttachment, absOrigin, qa ); - absAngles.x = qa.x; - absAngles.y = qa.y; - absAngles.z = qa.z; - return absAngles; + return qa; } -HSCRIPT C_BaseAnimating::ScriptGetAttachmentMatrix( int iAttachment ) +HSCRIPT_RC C_BaseAnimating::ScriptGetAttachmentMatrix( int iAttachment ) { - static matrix3x4_t matrix; + matrix3x4_t *matrix = new matrix3x4_t; - C_BaseAnimating::GetAttachment( iAttachment, matrix ); - return g_pScriptVM->RegisterInstance( &matrix ); + if ( C_BaseAnimating::GetAttachment( iAttachment, *matrix ) ) + return g_pScriptVM->RegisterInstance( matrix, true ); + + delete matrix; + return NULL; } void C_BaseAnimating::ScriptGetBoneTransform( int iBone, HSCRIPT hTransform ) diff --git a/sp/src/game/client/c_baseanimating.h b/sp/src/game/client/c_baseanimating.h index 24e33a3b..25dbfb33 100644 --- a/sp/src/game/client/c_baseanimating.h +++ b/sp/src/game/client/c_baseanimating.h @@ -464,8 +464,8 @@ public: #ifdef MAPBASE_VSCRIPT int ScriptLookupAttachment( const char *pAttachmentName ) { return LookupAttachment( pAttachmentName ); } const Vector& ScriptGetAttachmentOrigin(int iAttachment); - const Vector& ScriptGetAttachmentAngles(int iAttachment); - HSCRIPT ScriptGetAttachmentMatrix(int iAttachment); + const QAngle& ScriptGetAttachmentAngles(int iAttachment); + HSCRIPT_RC ScriptGetAttachmentMatrix(int iAttachment); void ScriptGetBoneTransform( int iBone, HSCRIPT hTransform ); void ScriptSetBoneTransform( int iBone, HSCRIPT hTransform ); diff --git a/sp/src/game/client/c_basecombatweapon.cpp b/sp/src/game/client/c_basecombatweapon.cpp index 3ae13cde..ff8e6d64 100644 --- a/sp/src/game/client/c_basecombatweapon.cpp +++ b/sp/src/game/client/c_basecombatweapon.cpp @@ -90,11 +90,10 @@ static inline bool ShouldDrawLocalPlayerViewModel( void ) C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer(); if (localplayer) { - if (localplayer->m_bDrawPlayerModelExternally) + if (localplayer->DrawingPlayerModelExternally() && localplayer->InFirstPersonView()) { // If this isn't the main view, draw the weapon. - view_id_t viewID = CurrentViewID(); - if (viewID != VIEW_MAIN && viewID != VIEW_INTRO_CAMERA) + if (!localplayer->InPerspectiveView()) return false; } @@ -224,8 +223,16 @@ ShadowType_t C_BaseCombatWeapon::ShadowCastType() if (!IsBeingCarried()) return SHADOWS_RENDER_TO_TEXTURE; - if (IsCarriedByLocalPlayer() && !C_BasePlayer::ShouldDrawLocalPlayer()) - return SHADOWS_NONE; + if (IsCarriedByLocalPlayer()) + { + if (!C_BasePlayer::ShouldDrawLocalPlayer()) + return SHADOWS_NONE; + +#ifdef MAPBASE + if (C_BasePlayer::GetLocalPlayer() && C_BasePlayer::GetLocalPlayer()->ShadowCastType() == SHADOWS_NONE) + return SHADOWS_NONE; +#endif + } return SHADOWS_RENDER_TO_TEXTURE; } @@ -458,7 +465,7 @@ bool C_BaseCombatWeapon::ShouldDraw( void ) #ifdef MAPBASE // We're drawing this in non-main views, handle it in DrawModel() - if ( pLocalPlayer->m_bDrawPlayerModelExternally ) + if ( pLocalPlayer->DrawingPlayerModelExternally() ) return true; #endif @@ -511,11 +518,10 @@ int C_BaseCombatWeapon::DrawModel( int flags ) if ( localplayer ) { #ifdef MAPBASE - if (localplayer->m_bDrawPlayerModelExternally) + if (GetOwner() == localplayer && localplayer->DrawingPlayerModelExternally()) { // If this isn't the main view, draw the weapon. - view_id_t viewID = CurrentViewID(); - if ( (!localplayer->InFirstPersonView() || (viewID != VIEW_MAIN && viewID != VIEW_INTRO_CAMERA)) && (viewID != VIEW_SHADOW_DEPTH_TEXTURE || !localplayer->IsEffectActive(EF_DIMLIGHT)) ) + if ( (!localplayer->InPerspectiveView() || !localplayer->InFirstPersonView()) && (CurrentViewID() != VIEW_SHADOW_DEPTH_TEXTURE || !localplayer->IsEffectActive(EF_DIMLIGHT))) { // TODO: Is this inefficient? int nModelIndex = GetModelIndex(); @@ -534,6 +540,10 @@ int C_BaseCombatWeapon::DrawModel( int flags ) return iDraw; } + else + { + return 0; + } } #endif if ( localplayer->IsObserver() && GetOwner() ) @@ -551,6 +561,24 @@ int C_BaseCombatWeapon::DrawModel( int flags ) return BaseClass::DrawModel( flags ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseCombatWeapon::DispatchMuzzleEffect( const char *options, bool isFirstPerson ) +{ + // Don't show muzzle flashes in first-person + C_BasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if (pPlayer) + { + if (pPlayer->DrawingPlayerModelExternally() && pPlayer->InFirstPersonView()) + return false; + } + + return BaseClass::DispatchMuzzleEffect( options, isFirstPerson ); +} +#endif + //----------------------------------------------------------------------------- // Allows the client-side entity to override what the network tells it to use for diff --git a/sp/src/game/client/c_baseentity.cpp b/sp/src/game/client/c_baseentity.cpp index 4a72bdb7..8b13c94b 100644 --- a/sp/src/game/client/c_baseentity.cpp +++ b/sp/src/game/client/c_baseentity.cpp @@ -433,8 +433,7 @@ ScriptHook_t C_BaseEntity::g_Hook_UpdateOnRemove; ScriptHook_t C_BaseEntity::g_Hook_ModifyEmitSoundParams; #endif -BEGIN_ENT_SCRIPTDESC_ROOT( C_BaseEntity, "Root class of all client-side entities" ) - DEFINE_SCRIPT_INSTANCE_HELPER( &g_BaseEntityScriptInstanceHelper ) +BEGIN_ENT_SCRIPTDESC_ROOT_WITH_HELPER( C_BaseEntity, "Root class of all client-side entities", &g_BaseEntityScriptInstanceHelper ) DEFINE_SCRIPTFUNC_NAMED( GetAbsOrigin, "GetOrigin", "" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetForward, "GetForwardVector", "Get the forward vector of the entity" ) #ifdef MAPBASE_VSCRIPT diff --git a/sp/src/game/client/c_baseplayer.cpp b/sp/src/game/client/c_baseplayer.cpp index 73d593c5..40127a98 100644 --- a/sp/src/game/client/c_baseplayer.cpp +++ b/sp/src/game/client/c_baseplayer.cpp @@ -125,6 +125,16 @@ ConVar demo_fov_override( "demo_fov_override", "0", FCVAR_CLIENTDLL | FCVAR_DONT // This value is found by hand, and a good value depends more on the in-game models than on actual human shapes. ConVar cl_meathook_neck_pivot_ingame_up( "cl_meathook_neck_pivot_ingame_up", "7.0" ); ConVar cl_meathook_neck_pivot_ingame_fwd( "cl_meathook_neck_pivot_ingame_fwd", "3.0" ); +#ifdef MAPBASE +ConVar cl_meathook_neck_pivot_override( "cl_meathook_neck_pivot_override", "0", FCVAR_NONE, "Overrides playermodel values for meathook and uses cvars only" ); + +//------------------------------------------------------------------------------------- + +ConVar cl_playermodel_draw_externally_override( "cl_playermodel_draw_externally_override", "-1", FCVAR_ARCHIVE, "Overrides developer-placed options to draw the player's model externally." ); + +ConVar cl_playermodel_legs_override( "cl_playermodel_legs_override", "-1", FCVAR_ARCHIVE, "Overrides developer-placed options to draw the player's model below the camera." ); +ConVar cl_playermodel_legs_scale_bones( "cl_playermodel_legs_scale_bones", "1" ); +#endif void RecvProxy_LocalVelocityX( const CRecvProxyData *pData, void *pStruct, void *pOut ); void RecvProxy_LocalVelocityY( const CRecvProxyData *pData, void *pStruct, void *pOut ); @@ -280,6 +290,7 @@ END_RECV_TABLE() // See baseplayer_shared.h for more details. RecvPropInt ( RECVINFO( m_spawnflags ), 0, RecvProxy_ShiftPlayerSpawnflags ), + RecvPropBool ( RECVINFO( m_bDrawPlayerLegs ) ), RecvPropBool ( RECVINFO( m_bDrawPlayerModelExternally ) ), RecvPropBool ( RECVINFO( m_bInTriggerFall ) ), #endif @@ -1486,13 +1497,186 @@ bool C_BasePlayer::ShouldInterpolate() } +#ifdef MAPBASE +bool C_BasePlayer::InPerspectiveView() const +{ + // VIEW_NONE is used by the water intersection view, see CAboveWaterView::CIntersectionView::Draw() + // (TODO: Consider changing the view ID at the source to VIEW_REFRACTION? VIEW_NONE could be an oversight) + view_id_t viewID = CurrentViewID(); + return (viewID == VIEW_MAIN || viewID == VIEW_INTRO_CAMERA || viewID == VIEW_REFRACTION || viewID == VIEW_NONE); +} + +bool C_BasePlayer::DrawingPlayerModelExternally() const +{ + if (cl_playermodel_draw_externally_override.GetInt() > -1) + return cl_playermodel_draw_externally_override.GetBool(); + + return m_bDrawPlayerModelExternally; +} + +bool C_BasePlayer::DrawingLegs() const +{ + if (cl_playermodel_legs_override.GetInt() > -1) + return cl_playermodel_legs_override.GetBool(); + + // For now, don't draw legs if looking up in any way + // (fixes issues with some animations causing clipping with chest) + if (GetAbsAngles().x < 0.0f) + return false; + + return m_bDrawPlayerLegs; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStudioHdr *C_BasePlayer::OnNewModel( void ) +{ + CStudioHdr *hdr = BaseClass::OnNewModel(); + if (!hdr) + return NULL; + + KeyValues *modelKeyValues = new KeyValues( "" ); + CUtlBuffer buf( 1024, 0, CUtlBuffer::TEXT_BUFFER ); + + // Init values + m_FirstPersonModelData.Reset(); + + if (!modelinfo->GetModelKeyValue( GetModel(), buf )) + { + modelKeyValues->deleteThis(); + return hdr; + } + + if (modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), buf )) + { + CUtlVector iszUsedNames; + for (KeyValues *pkvModelBlock = modelKeyValues; pkvModelBlock != nullptr; pkvModelBlock = pkvModelBlock->GetNextKey()) + { + KeyValues *pkvPlayerModelData = pkvModelBlock->FindKey( "playermodel_data" ); + if (pkvPlayerModelData) + { + m_FirstPersonModelData.ParseModelData( this, pkvPlayerModelData ); + break; + } + } + } + + modelKeyValues->deleteThis(); + + return hdr; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BasePlayer::FirstPersonModelData_t::ParseModelData( C_BasePlayer *pPlayer, KeyValues *pkvPlayerModelData ) +{ + m_flFirstPersonNeckPivotUp = pkvPlayerModelData->GetFloat( "neck_pivot_up", FLT_MAX ); + m_flFirstPersonNeckPivotFwd = pkvPlayerModelData->GetFloat( "neck_pivot_fwd", FLT_MAX ); + + m_flFirstPersonNeckPivotDuckUp = pkvPlayerModelData->GetFloat( "neck_pivot_duck_up", FLT_MAX ); + m_flFirstPersonNeckPivotDuckFwd = pkvPlayerModelData->GetFloat( "neck_pivot_duck_fwd", FLT_MAX ); + + KeyValues *pkvBoneScales = pkvPlayerModelData->FindKey( "bone_transforms" ); + if (pkvBoneScales) + { + KeyValues *pkvSpineTransforms = pkvBoneScales->FindKey( "spine" ); + if (pkvSpineTransforms) + { + for (KeyValues *pkvBone = pkvSpineTransforms->GetFirstSubKey(); pkvBone != nullptr; pkvBone = pkvBone->GetNextKey()) + { + int nBone = pPlayer->LookupBone( pkvBone->GetName() ); + if (nBone == -1) + continue; + + m_FirstPersonBoneScales[BoneScales_Spine].Insert(nBone, pkvBone->GetFloat()); + } + } + + KeyValues *pkvArmsTransforms = pkvBoneScales->FindKey( "arms" ); + if (pkvArmsTransforms) + { + for (KeyValues *pkvBone = pkvArmsTransforms->GetFirstSubKey(); pkvBone != nullptr; pkvBone = pkvBone->GetNextKey()) + { + int nBone = pPlayer->LookupBone( pkvBone->GetName() ); + if (nBone == -1) + continue; + + m_FirstPersonBoneScales[BoneScales_Arms].Insert( nBone, pkvBone->GetFloat() ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: move position and rotation transforms into global matrices +//----------------------------------------------------------------------------- +void C_BasePlayer::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion *q, const matrix3x4_t &cameraTransform, int boneMask, CBoneBitList &boneComputed ) +{ + BaseClass::BuildTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed ); + + if (DrawingLegs() && InPerspectiveView() && InFirstPersonView()) + { + //BuildFirstPersonMeathookTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed, "ValveBiped.Bip01_Head1" ); + + if (cl_playermodel_legs_scale_bones.GetBool()) + { + // For now, only do transforms when we have an active weapon + // (since we typically just cull stuff influenced by viewmodels and upper-body weapon posture, like arms) + if ((GetActiveWeapon() && !GetActiveWeapon()->IsEffectActive( EF_NODRAW )) || GetUseEntity()) + { + matrix3x4_t *pFirstZero = NULL; + + for (int nMap = 0; nMap < FirstPersonModelData_t::BoneScales_Max; nMap++) + { + const CUtlMap &scaleMap = m_FirstPersonModelData.m_FirstPersonBoneScales[nMap]; + FOR_EACH_MAP( scaleMap, i ) + { + int nBone = scaleMap.Key(i); + if (nBone == -1) + continue; + + if (!(hdr->boneFlags( nBone ) & boneMask)) + continue; + + float flScale = scaleMap.Element(i); + + matrix3x4_t &mTransform = GetBoneForWrite( nBone ); + + if (flScale == 0.0f) + { + if (!pFirstZero) + { + MatrixScaleByZero( mTransform ); + pFirstZero = &mTransform; + } + else + { + // Keep zeroes in one place + MatrixCopy( *pFirstZero, mTransform ); + } + } + else + { + MatrixScaleBy( flScale, mTransform ); + } + } + } + } + } + } +} +#endif + + bool C_BasePlayer::ShouldDraw() { #ifdef MAPBASE // We have to "always draw" a player with m_bDrawPlayerModelExternally in order to show up in whatever rendering list all of the views use, // but we can't put this in ShouldDrawThisPlayer() because we would have no way of knowing if it stomps the other checks that draw the player model anyway. // As a result, we have to put it here in the central ShouldDraw() function. DrawModel() makes sure we only draw in non-main views and nothing's drawing the model anyway. - return (ShouldDrawThisPlayer() || m_bDrawPlayerModelExternally) && BaseClass::ShouldDraw(); + return (ShouldDrawThisPlayer() || DrawingPlayerModelExternally() || DrawingLegs()) && BaseClass::ShouldDraw(); #else return ShouldDrawThisPlayer() && BaseClass::ShouldDraw(); #endif @@ -1501,12 +1685,16 @@ bool C_BasePlayer::ShouldDraw() int C_BasePlayer::DrawModel( int flags ) { #ifdef MAPBASE - if (m_bDrawPlayerModelExternally) + if (DrawingLegs() && InFirstPersonView() && InPerspectiveView()) + { + return BaseClass::DrawModel( flags ); + } + + if (DrawingPlayerModelExternally()) { // Draw the player in any view except the main or "intro" view, both of which are default first-person views. // HACKHACK: Also don't draw in shadow depth textures if the player's flashlight is on, as that causes the playermodel to block it. - view_id_t viewID = CurrentViewID(); - if (viewID == VIEW_MAIN || viewID == VIEW_INTRO_CAMERA || (viewID == VIEW_SHADOW_DEPTH_TEXTURE && IsEffectActive(EF_DIMLIGHT))) + if (InPerspectiveView() || (CurrentViewID() == VIEW_SHADOW_DEPTH_TEXTURE && IsEffectActive(EF_DIMLIGHT))) { // Make sure the player model wouldn't draw anyway... if (!ShouldDrawThisPlayer()) @@ -3057,13 +3245,21 @@ void C_BasePlayer::BuildFirstPersonMeathookTransformations( CStudioHdr *hdr, Vec return; } +#ifdef MAPBASE + if ( !InPerspectiveView() ) +#else if ( !DrawingMainView() ) +#endif { return; } // If we aren't drawing the player anyway, don't mess with the bones. This can happen in Portal. +#ifdef MAPBASE + if ( !ShouldDrawThisPlayer() && !DrawingPlayerModelExternally() && !DrawingLegs() ) +#else if( !ShouldDrawThisPlayer() ) +#endif { return; } @@ -3084,6 +3280,63 @@ void C_BasePlayer::BuildFirstPersonMeathookTransformations( CStudioHdr *hdr, Vec Vector vHeadTransformTranslation ( mHeadTransform[0][3], mHeadTransform[1][3], mHeadTransform[2][3] ); + float flNeckPivotUp = cl_meathook_neck_pivot_ingame_up.GetFloat(); + float flNeckPivotFwd = cl_meathook_neck_pivot_ingame_fwd.GetFloat(); +#ifdef MAPBASE + if (DrawingLegs() && !cl_meathook_neck_pivot_override.GetBool()) + { + if (m_FirstPersonModelData.m_flFirstPersonNeckPivotUp != FLT_MAX || m_FirstPersonModelData.m_flFirstPersonNeckPivotFwd != FLT_MAX) + { + if (m_FirstPersonModelData.m_flFirstPersonNeckPivotUp != FLT_MAX) + flNeckPivotUp = m_FirstPersonModelData.m_flFirstPersonNeckPivotUp; + if (m_FirstPersonModelData.m_flFirstPersonNeckPivotFwd != FLT_MAX) + flNeckPivotFwd = m_FirstPersonModelData.m_flFirstPersonNeckPivotFwd; + + if (GetFlags() & FL_DUCKING || m_Local.m_flDucktime > 0.0f) + { + if (!IsLocalPlayer() || m_Local.m_flDucktime <= 0.0f) + { + if (m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckUp != FLT_MAX) + flNeckPivotUp = m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckUp; + if (m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckFwd != FLT_MAX) + flNeckPivotFwd = m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckFwd; + } + else + { + bool bDucking; + if (IsLocalPlayer()) + bDucking = input->GetButtonBits(0) & IN_DUCK; + else + bDucking = GetCurrentUserCommand()->buttons & IN_DUCK; + + // HACKHACK using constants from game movement + float flPerc = SimpleSpline( RemapValClamped( m_Local.m_flDucktime, bDucking ? 600.0f : 800.0f, 1000.0f, 0.0f, 1.0f ) ); + + if (bDucking) + { + // Ducking + //Msg( "Ducking with perc %f (%f)\n", flPerc, m_Local.m_flDucktime ); + if (m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckUp != FLT_MAX) + flNeckPivotUp = FLerp( m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckUp, flNeckPivotUp, flPerc ); + if (m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckFwd != FLT_MAX) + flNeckPivotFwd = FLerp( m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckFwd, flNeckPivotFwd, flPerc ); + } + else + { + // Unducking + //Msg( "Unducking with perc %f (%f)\n", flPerc, m_Local.m_flDucktime ); + if (m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckUp != FLT_MAX) + flNeckPivotUp = FLerp( flNeckPivotUp, m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckUp, flPerc ); + if (m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckFwd != FLT_MAX) + flNeckPivotFwd = FLerp( flNeckPivotFwd, m_FirstPersonModelData.m_flFirstPersonNeckPivotDuckFwd, flPerc ); + } + } + } + } + } +#endif + + // Find out where the player's head (driven by the HMD) is in the world. // We can't move this with animations or effects without causing nausea, so we need to move // the whole body so that the animated head is in the right place to match the player-controlled head. @@ -3100,7 +3353,7 @@ void C_BasePlayer::BuildFirstPersonMeathookTransformations( CStudioHdr *hdr, Vec // The head bone is the neck pivot point of the in-game character. Vector vRealMidEyePos = mWorldFromMideye.GetTranslation(); - vRealPivotPoint = vRealMidEyePos - ( mWorldFromMideye.GetUp() * cl_meathook_neck_pivot_ingame_up.GetFloat() ) - ( mWorldFromMideye.GetForward() * cl_meathook_neck_pivot_ingame_fwd.GetFloat() ); + vRealPivotPoint = vRealMidEyePos - ( mWorldFromMideye.GetUp() * flNeckPivotUp ) - ( mWorldFromMideye.GetForward() * flNeckPivotFwd ); } else { @@ -3108,7 +3361,7 @@ void C_BasePlayer::BuildFirstPersonMeathookTransformations( CStudioHdr *hdr, Vec Vector vForward, vRight, vUp; AngleVectors( MainViewAngles(), &vForward, &vRight, &vUp ); - vRealPivotPoint = MainViewOrigin() - ( vUp * cl_meathook_neck_pivot_ingame_up.GetFloat() ) - ( vForward * cl_meathook_neck_pivot_ingame_fwd.GetFloat() ); + vRealPivotPoint = MainViewOrigin() - ( vUp * flNeckPivotUp ) - ( vForward * flNeckPivotFwd ); } Vector vDeltaToAdd = vRealPivotPoint - vHeadTransformTranslation; diff --git a/sp/src/game/client/c_baseplayer.h b/sp/src/game/client/c_baseplayer.h index bc8ca654..e9851586 100644 --- a/sp/src/game/client/c_baseplayer.h +++ b/sp/src/game/client/c_baseplayer.h @@ -293,6 +293,15 @@ public: virtual bool ShouldInterpolate(); +#ifdef MAPBASE + bool InPerspectiveView() const; // In a view that renders directly from the player's perspective (and may, for example, render the playermodel) + bool DrawingPlayerModelExternally() const; + bool DrawingLegs() const; + + virtual CStudioHdr *OnNewModel( void ); + virtual void BuildTransformations( CStudioHdr *pStudioHdr, Vector *pos, Quaternion q[], const matrix3x4_t &cameraTransform, int boneMask, CBoneBitList &boneComputed ); +#endif + virtual bool ShouldDraw(); virtual int DrawModel( int flags ); @@ -468,6 +477,9 @@ public: inline void RemoveSpawnFlags( int flags ) { m_spawnflags &= ~flags; } inline void AddSpawnFlags( int flags ) { m_spawnflags |= flags; } + // Draws the player's model below the camera, visible when the player looks down. + bool m_bDrawPlayerLegs; + // Allows the player's model to draw on non-main views, like monitors or mirrors. bool m_bDrawPlayerModelExternally; @@ -502,6 +514,10 @@ protected: virtual void FireGameEvent( IGameEvent *event ); +#ifdef MAPBASE + inline CUtlMap &GetFirstPersonArmScales() { return m_FirstPersonModelData.m_FirstPersonBoneScales[FirstPersonModelData_t::BoneScales_Arms]; } +#endif + protected: // Did we just enter a vehicle this frame? bool JustEnteredVehicle(); @@ -556,6 +572,41 @@ private: bool m_bFiredWeapon; +#ifdef MAPBASE + struct FirstPersonModelData_t + { + void Reset() + { + m_flFirstPersonNeckPivotUp = m_flFirstPersonNeckPivotFwd = FLT_MAX; + m_flFirstPersonNeckPivotDuckUp = m_flFirstPersonNeckPivotDuckFwd = FLT_MAX; + + for (int i = 0; i < BoneScales_Max; i++) + { + m_FirstPersonBoneScales[i].RemoveAll(); + m_FirstPersonBoneScales[i].SetLessFunc( DefLessFunc( int ) ); + } + } + + void ParseModelData( C_BasePlayer *pPlayer, KeyValues *pkvPlayerModelData ); + + enum + { + BoneScales_Spine, + BoneScales_Arms, + + BoneScales_Max + }; + + // Values to scale bones by when drawing playermodel in first person + CUtlMap m_FirstPersonBoneScales[BoneScales_Max]; + + float m_flFirstPersonNeckPivotUp, m_flFirstPersonNeckPivotFwd = FLT_MAX; + float m_flFirstPersonNeckPivotDuckUp, m_flFirstPersonNeckPivotDuckFwd = FLT_MAX; + }; + + FirstPersonModelData_t m_FirstPersonModelData; +#endif + // Player flashlight dynamic light pointers CFlashlightEffect *m_pFlashlight; diff --git a/sp/src/game/client/c_sprite.cpp b/sp/src/game/client/c_sprite.cpp index 9146eefc..466e350b 100644 --- a/sp/src/game/client/c_sprite.cpp +++ b/sp/src/game/client/c_sprite.cpp @@ -399,10 +399,38 @@ int C_SpriteRenderer::DrawSprite( if ( group == RENDER_GROUP_VIEW_MODEL_TRANSLUCENT || group == RENDER_GROUP_VIEW_MODEL_OPAQUE ) return 0; } +#ifdef MAPBASE + if (ent->m_iViewHideFlags > 0) + { + // Hide this entity if it's not supposed to be drawn in this view. + if (ent->m_iViewHideFlags & (1 << CurrentViewID())) + { + return 0; + } + } +#endif QAngle temp; ent->GetAttachment( attachmentindex, effect_origin, temp ); } } + +#ifdef MAPBASE + if ( entity ) + { + C_BaseEntity *ent = entity->GetBaseEntity(); + if ( ent ) + { + if (ent->m_iViewHideFlags > 0) + { + // Hide this entity if it's not supposed to be drawn in this view. + if (ent->m_iViewHideFlags & (1 << CurrentViewID())) + { + return 0; + } + } + } + } +#endif if ( rendermode != kRenderNormal ) { diff --git a/sp/src/game/client/client_mapbase.vpc b/sp/src/game/client/client_mapbase.vpc index fb2b98cd..5aef63ce 100644 --- a/sp/src/game/client/client_mapbase.vpc +++ b/sp/src/game/client/client_mapbase.vpc @@ -47,8 +47,8 @@ $Project $File "$SRCDIR\game\shared\mapbase\MapEdit.h" $File "$SRCDIR\game\shared\mapbase\matchers.cpp" $File "$SRCDIR\game\shared\mapbase\matchers.h" - $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.cpp" - $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.h" + $File "$SRCDIR\game\shared\mapbase\mapbase_playeranimstate.cpp" + $File "$SRCDIR\game\shared\mapbase\mapbase_playeranimstate.h" $File "$SRCDIR\game\shared\mapbase\protagonist_system.cpp" $File "$SRCDIR\game\shared\mapbase\protagonist_system.h" $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.cpp" [$MAPBASE_VSCRIPT] diff --git a/sp/src/game/client/colorcorrectionmgr.cpp b/sp/src/game/client/colorcorrectionmgr.cpp index cf1210ac..f04438d6 100644 --- a/sp/src/game/client/colorcorrectionmgr.cpp +++ b/sp/src/game/client/colorcorrectionmgr.cpp @@ -157,6 +157,22 @@ void CColorCorrectionMgr::CommitColorCorrectionWeights() } m_colorCorrectionWeights.RemoveAll(); } + +void CColorCorrectionMgr::LevelShutdownPreEntity() +{ + //Clean up the vectors when shuting down a level + //will keep dangling pointers inside of the vector causing a nullptr crash + if (g_ColorCorrectionVolumeList.Base()) + { + g_ColorCorrectionVolumeList.Purge(); + } + + if (g_ColorCorrectionList.Base()) + { + g_ColorCorrectionList.Purge(); + } +} + #else void CColorCorrectionMgr::SetColorCorrectionWeight( ClientCCHandle_t h, float flWeight ) { diff --git a/sp/src/game/client/colorcorrectionmgr.h b/sp/src/game/client/colorcorrectionmgr.h index 3eba0f8c..8492ac20 100644 --- a/sp/src/game/client/colorcorrectionmgr.h +++ b/sp/src/game/client/colorcorrectionmgr.h @@ -76,6 +76,8 @@ private: CUtlVector< SetWeightParams_t > m_colorCorrectionWeights; void CommitColorCorrectionWeights(); + + void LevelShutdownPreEntity(); #endif }; diff --git a/sp/src/game/client/hl2/c_basehlplayer.cpp b/sp/src/game/client/hl2/c_basehlplayer.cpp index daa4438a..461990c0 100644 --- a/sp/src/game/client/hl2/c_basehlplayer.cpp +++ b/sp/src/game/client/hl2/c_basehlplayer.cpp @@ -36,6 +36,7 @@ IMPLEMENT_CLIENTCLASS_DT(C_BaseHLPlayer, DT_HL2_Player, CHL2_Player) #endif #ifdef SP_ANIM_STATE RecvPropFloat( RECVINFO( m_flAnimRenderYaw ) ), + RecvPropFloat( RECVINFO( m_flAnimRenderZ ) ), #endif END_RECV_TABLE() @@ -108,6 +109,22 @@ void C_BaseHLPlayer::OnDataChanged( DataUpdateType_t updateType ) BaseClass::OnDataChanged( updateType ); } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_BaseHLPlayer::AddEntity( void ) +{ + BaseClass::AddEntity(); + +#ifdef MAPBASE_MP + if (m_pPlayerAnimState) + { + QAngle angEyeAngles = EyeAngles(); + m_pPlayerAnimState->Update( angEyeAngles.y, angEyeAngles.x ); + } +#endif +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -669,24 +686,76 @@ bool C_BaseHLPlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd ) void C_BaseHLPlayer::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed ) { BaseClass::BuildTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed ); +/*#ifdef MAPBASE + // BuildFirstPersonMeathookTransformations is used prior to this when drawing legs + if (!DrawingLegs() || !InPerspectiveView() || !InFirstPersonView()) +#endif*/ BuildFirstPersonMeathookTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed, "ValveBiped.Bip01_Head1" ); } #ifdef SP_ANIM_STATE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const Vector &C_BaseHLPlayer::GetRenderOrigin() +{ + if (m_flAnimRenderZ != 0.0f) + { + static Vector vecRender; + vecRender = BaseClass::GetRenderOrigin(); + vecRender.z += m_flAnimRenderZ; + return vecRender; + } + + return BaseClass::GetRenderOrigin(); +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const QAngle& C_BaseHLPlayer::GetRenderAngles( void ) { +#ifdef MAPBASE_MP + if ( m_pPlayerAnimState ) + { + return m_pPlayerAnimState->GetRenderAngles(); + } +#else if ( m_flAnimRenderYaw != FLT_MAX ) { return m_angAnimRender; } +#endif else { return BaseClass::GetRenderAngles(); } } + +//----------------------------------------------------------------------------- +// Purpose: model-change notification. Fires on dynamic load completion as well +//----------------------------------------------------------------------------- +CStudioHdr *C_BaseHLPlayer::OnNewModel() +{ + CStudioHdr *hdr = BaseClass::OnNewModel(); + +#ifdef MAPBASE_MP + // Clears the animation state if we already have one. + if ( m_pPlayerAnimState != NULL ) + { + m_pPlayerAnimState->Release(); + m_pPlayerAnimState = NULL; + } + + if ( hdr && hdr->HaveSequenceForActivity(ACT_HL2MP_IDLE) /*&& hl2_use_sp_animstate.GetBool()*/ ) + { + // Here we create and init the player animation state. + m_pPlayerAnimState = CreatePlayerAnimationState(this); + } +#endif + + return hdr; +} #endif diff --git a/sp/src/game/client/hl2/c_basehlplayer.h b/sp/src/game/client/hl2/c_basehlplayer.h index 8cce42a4..3bdc7015 100644 --- a/sp/src/game/client/hl2/c_basehlplayer.h +++ b/sp/src/game/client/hl2/c_basehlplayer.h @@ -16,7 +16,7 @@ #include "c_hl2_playerlocaldata.h" #if !defined( HL2MP ) && defined ( MAPBASE ) -#include "mapbase/singleplayer_animstate.h" +#include "mapbase/mapbase_playeranimstate.h" #endif class C_BaseHLPlayer : public C_BasePlayer @@ -29,6 +29,7 @@ public: C_BaseHLPlayer(); virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void AddEntity( void ); void Weapon_DropPrimary( void ); @@ -67,7 +68,9 @@ public: #endif #ifdef SP_ANIM_STATE + virtual const Vector& GetRenderOrigin(); virtual const QAngle& GetRenderAngles( void ); + virtual CStudioHdr *OnNewModel(); #endif public: @@ -95,10 +98,13 @@ private: int m_nProtagonistIndex; #endif -#ifdef SP_ANIM_STATE +#ifdef MAPBASE_MP + CSinglePlayerAnimState *m_pPlayerAnimState; +#elif MAPBASE // At the moment, we network the render angles since almost none of the player anim stuff is done on the client in SP. // If any of this is ever adapted for MP, this method should be replaced with replicating/moving the anim state to the client. float m_flAnimRenderYaw; + float m_flAnimRenderZ; QAngle m_angAnimRender; #endif diff --git a/sp/src/game/client/hl2/hud_suitpower.cpp b/sp/src/game/client/hl2/hud_suitpower.cpp index f1af00ac..e3e73aaa 100644 --- a/sp/src/game/client/hl2/hud_suitpower.cpp +++ b/sp/src/game/client/hl2/hud_suitpower.cpp @@ -100,7 +100,11 @@ void CHudSuitPower::OnThink( void ) } bool flashlightActive = pPlayer->IsFlashlightActive(); +#ifdef MAPBASE + bool sprintActive = pPlayer->IsSprintActive(); +#else bool sprintActive = pPlayer->IsSprinting(); +#endif bool breatherActive = pPlayer->IsBreatherActive(); int activeDevices = (int)flashlightActive + (int)sprintActive + (int)breatherActive; @@ -250,7 +254,11 @@ void CHudSuitPower::Paint() ypos += text2_gap; } +#ifdef MAPBASE + if (pPlayer->IsSprintActive()) +#else if (pPlayer->IsSprinting()) +#endif { tempString = g_pVGuiLocalize->Find("#Valve_Hud_SPRINT"); diff --git a/sp/src/game/client/mapbase/vscript_vgui.cpp b/sp/src/game/client/mapbase/vscript_vgui.cpp index 805d3305..9b2c3af5 100644 --- a/sp/src/game/client/mapbase/vscript_vgui.cpp +++ b/sp/src/game/client/mapbase/vscript_vgui.cpp @@ -1031,7 +1031,7 @@ void CScriptSurface::DrawColoredTextRect( int font, int x, int y, int w, int h, #define __base() this->_base #define BEGIN_SCRIPTDESC_VGUI( panelClass )\ - BEGIN_SCRIPTDESC_NAMED( CScript_##panelClass##_Helper, IScriptVGUIObject, #panelClass, "" )\ + BEGIN_SCRIPTDESC_NAMED_WITH_HELPER( CScript_##panelClass##_Helper, IScriptVGUIObject, #panelClass, "", VGUI_SCRIPT_INSTANCE_HELPER )\ DEFINE_VGUI_SCRIPTFUNC_##panelClass() // @@ -1235,9 +1235,9 @@ class CScriptVGUIScriptInstanceHelper : public IScriptInstanceHelper static CScriptVGUIScriptInstanceHelper g_ScriptVGUIScriptInstanceHelper; -#define DEFINE_VGUI_SCRIPT_INSTANCE_HELPER() DEFINE_SCRIPT_INSTANCE_HELPER( &g_ScriptVGUIScriptInstanceHelper ) +#define VGUI_SCRIPT_INSTANCE_HELPER &g_ScriptVGUIScriptInstanceHelper #else -#define DEFINE_VGUI_SCRIPT_INSTANCE_HELPER() +#define VGUI_SCRIPT_INSTANCE_HELPER NULL #endif @@ -1866,8 +1866,6 @@ public: }; #define DEFINE_VGUI_SCRIPTFUNC_Panel()\ - DEFINE_VGUI_SCRIPT_INSTANCE_HELPER()\ -\ DEFINE_SCRIPTFUNC( Destroy, "" )\ DEFINE_SCRIPTFUNC( MakeReadyForUse, "" )\ DEFINE_SCRIPTFUNC( GetName, "" )\ @@ -2612,9 +2610,17 @@ public: static inline void SetHScript( HSCRIPT &var, HSCRIPT val ) { - if ( var && g_pScriptVM ) - g_pScriptVM->ReleaseScript( var ); - var = val; + if ( g_pScriptVM ) + { + if ( var ) + g_pScriptVM->ReleaseScript( var ); + + var = g_pScriptVM->CopyObject( val ); + } + else + { + var = NULL; + } } #define CheckCallback(s)\ diff --git a/sp/src/game/client/prediction.cpp b/sp/src/game/client/prediction.cpp index 6d3a0439..d7dfbff7 100644 --- a/sp/src/game/client/prediction.cpp +++ b/sp/src/game/client/prediction.cpp @@ -855,7 +855,7 @@ void CPrediction::RunCommand( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper C_BaseCombatWeapon *weapon = dynamic_cast< C_BaseCombatWeapon * >( CBaseEntity::Instance( ucmd->weaponselect ) ); if ( weapon ) { - player->SelectItem( weapon->GetClassname(), ucmd->weaponsubtype ); + player->SelectItem( weapon->GetName(), ucmd->weaponsubtype ); } } diff --git a/sp/src/game/client/view.cpp b/sp/src/game/client/view.cpp index 8cd5293d..a930c4a7 100644 --- a/sp/src/game/client/view.cpp +++ b/sp/src/game/client/view.cpp @@ -129,6 +129,7 @@ ConVar gl_clear_randomcolor( "gl_clear_randomcolor", "0", FCVAR_CHEAT, "Clear th static ConVar r_farz( "r_farz", "-1", FCVAR_CHEAT, "Override the far clipping plane. -1 means to use the value in env_fog_controller." ); #ifdef MAPBASE static ConVar r_nearz( "r_nearz", "-1", FCVAR_CHEAT, "Override the near clipping plane. -1 means to use the default value (usually 7)." ); +static ConVar cl_camera_anim_intensity("cl_camera_anim_intensity", "1.0", FCVAR_ARCHIVE, "Intensity of cambone animations"); #endif static ConVar cl_demoviewoverride( "cl_demoviewoverride", "0", 0, "Override view during demo playback" ); @@ -1306,6 +1307,37 @@ void CViewRender::Render( vrect_t *rect ) g_ClientVirtualReality.OverlayHUDQuadWithUndistort( view, bDoUndistort, g_pClientMode->ShouldBlackoutAroundHUD(), bTranslucent ); } } + +#ifdef MAPBASE + //-------------------------------- + // Handle camera anims + //-------------------------------- + if (!UseVR() && pPlayer && cl_camera_anim_intensity.GetFloat() > 0) + { + if (pPlayer->GetViewModel(0)) + { + int attachment = pPlayer->GetViewModel(0)->LookupAttachment("camera"); + if (attachment != -1) + { + int rootBone = pPlayer->GetViewModel(0)->LookupAttachment("camera_root"); + Vector cameraOrigin = Vector(0, 0, 0); + QAngle cameraAngles = QAngle(0, 0, 0); + Vector rootOrigin = Vector(0, 0, 0); + QAngle rootAngles = QAngle(0, 0, 0); + + pPlayer->GetViewModel(0)->GetAttachmentLocal(attachment, cameraOrigin, cameraAngles); + if (rootBone != -1) + { + pPlayer->GetViewModel(0)->GetAttachmentLocal(rootBone, rootOrigin, rootAngles); + cameraOrigin -= rootOrigin; + cameraAngles -= rootAngles; + } + view.angles += cameraAngles * cl_camera_anim_intensity.GetFloat(); + view.origin += cameraOrigin * cl_camera_anim_intensity.GetFloat(); + } + } + } +#endif // MAPBASE } diff --git a/sp/src/game/client/vscript_client.cpp b/sp/src/game/client/vscript_client.cpp index 2312d6ca..cb3fcd72 100644 --- a/sp/src/game/client/vscript_client.cpp +++ b/sp/src/game/client/vscript_client.cpp @@ -28,6 +28,10 @@ extern IScriptManager *scriptmanager; extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * ); +#ifdef MAPBASE_VSCRIPT +extern int vscript_debugger_port; +#endif + // #define VMPROFILE 1 #ifdef VMPROFILE @@ -234,8 +238,7 @@ class CMaterialProxyScriptInstanceHelper : public IScriptInstanceHelper CMaterialProxyScriptInstanceHelper g_MaterialProxyScriptInstanceHelper; -BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptMaterialProxy, "CScriptMaterialProxy", "Material proxy for VScript" ) - DEFINE_SCRIPT_INSTANCE_HELPER( &g_MaterialProxyScriptInstanceHelper ) +BEGIN_SCRIPTDESC_ROOT_NAMED_WITH_HELPER( CScriptMaterialProxy, "CScriptMaterialProxy", "Material proxy for VScript", &g_MaterialProxyScriptInstanceHelper ) DEFINE_SCRIPTFUNC( GetVarString, "Gets a material var's string value" ) DEFINE_SCRIPTFUNC( GetVarInt, "Gets a material var's int value" ) DEFINE_SCRIPTFUNC( GetVarFloat, "Gets a material var's float value" ) @@ -683,6 +686,14 @@ bool VScriptClientInit() //g_pScriptVM->RegisterInstance( &g_ScriptEntityIterator, "Entities" ); #endif +#ifdef MAPBASE_VSCRIPT + if ( vscript_debugger_port ) + { + g_pScriptVM->ConnectDebugger( vscript_debugger_port ); + vscript_debugger_port = 0; + } +#endif + if (scriptLanguage == SL_SQUIRREL) { g_pScriptVM->Run( g_Script_vscript_client ); @@ -771,11 +782,19 @@ public: VScriptClientTerm(); } - virtual void FrameUpdatePostEntityThink() +#ifdef MAPBASE_VSCRIPT + virtual void Update( float frametime ) + { + if ( g_pScriptVM ) + g_pScriptVM->Frame( frametime ); + } +#else + virtual void FrameUpdatePostEntityThink() { if ( g_pScriptVM ) g_pScriptVM->Frame( gpGlobals->frametime ); } +#endif bool m_bAllowEntityCreationInScripts; }; diff --git a/sp/src/game/server/BaseAnimatingOverlay.cpp b/sp/src/game/server/BaseAnimatingOverlay.cpp index 06bf690e..d399d511 100644 --- a/sp/src/game/server/BaseAnimatingOverlay.cpp +++ b/sp/src/game/server/BaseAnimatingOverlay.cpp @@ -16,6 +16,10 @@ #include "saverestore_utlvector.h" #include "dt_utlvector_send.h" +#ifdef MAPBASE_VSCRIPT +#include "mapbase/vscript_funcs_shared.h" +#endif + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -471,7 +475,8 @@ void CAnimationLayer::DispatchAnimEvents( CBaseAnimating *eventHandler, CBaseAni } #ifdef MAPBASE_VSCRIPT - if (eventHandler->m_ScriptScope.IsInitialized() && eventHandler->ScriptHookHandleAnimEvent( &event ) == false) + scriptanimevent_t wrapper( event ); + if (eventHandler->m_ScriptScope.IsInitialized() && !eventHandler->ScriptHookHandleAnimEvent( wrapper )) continue; #endif diff --git a/sp/src/game/server/ai_activity.cpp b/sp/src/game/server/ai_activity.cpp index e20bfc48..6c10d7ea 100644 --- a/sp/src/game/server/ai_activity.cpp +++ b/sp/src/game/server/ai_activity.cpp @@ -2594,6 +2594,18 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_CROSSBOW ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SLAM ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_SMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_AR2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_PHYSGUN ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_GRENADE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_RPG ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_MELEE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_SLAM ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_REVOLVER ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_REVOLVER ); @@ -2603,6 +2615,7 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_REVOLVER ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_REVOLVER ); #if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES @@ -2614,6 +2627,7 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_AR1 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR1 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_AR1 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_AR1 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_AR3 ); @@ -2624,6 +2638,7 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_AR3 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR3 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_AR3 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_AR3 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_SMG2 ); @@ -2634,6 +2649,7 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG2 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG2 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_SMG2 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_SMG2 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_SMG3 ); @@ -2644,6 +2660,7 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG3 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG3 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_SMG3 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_SMG3 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_HMG1 ); @@ -2654,6 +2671,7 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_HMG1 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_HMG1 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_HMG1 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_HMG1 ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_SNIPER_RIFLE ); @@ -2664,6 +2682,7 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_SNIPER_RIFLE ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SNIPER_RIFLE ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_SNIPER_RIFLE ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_SNIPER_RIFLE ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_DUAL_PISTOLS ); @@ -2674,6 +2693,7 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_DUAL_PISTOLS ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_DUAL_PISTOLS ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELAX_DUAL_PISTOLS ); ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_DUAL_PISTOLS ); #endif diff --git a/sp/src/game/server/ai_basenpc.cpp b/sp/src/game/server/ai_basenpc.cpp index f087f8a4..bdface8c 100644 --- a/sp/src/game/server/ai_basenpc.cpp +++ b/sp/src/game/server/ai_basenpc.cpp @@ -318,6 +318,14 @@ ScriptHook_t CAI_BaseNPC::g_Hook_TranslateSchedule; ScriptHook_t CAI_BaseNPC::g_Hook_GetActualShootPosition; ScriptHook_t CAI_BaseNPC::g_Hook_OverrideMove; ScriptHook_t CAI_BaseNPC::g_Hook_ShouldPlayFakeSequenceGesture; +ScriptHook_t CAI_BaseNPC::g_Hook_IsValidEnemy; +ScriptHook_t CAI_BaseNPC::g_Hook_CanBeAnEnemyOf; +ScriptHook_t CAI_BaseNPC::g_Hook_UpdateEnemyMemory; +ScriptHook_t CAI_BaseNPC::g_Hook_OnSeeEntity; +ScriptHook_t CAI_BaseNPC::g_Hook_OnListened; +ScriptHook_t CAI_BaseNPC::g_Hook_BuildScheduleTestBits; +ScriptHook_t CAI_BaseNPC::g_Hook_StartTask; +ScriptHook_t CAI_BaseNPC::g_Hook_RunTask; #endif // @@ -746,10 +754,19 @@ Vector CAI_BaseNPC::VScriptGetEnemyLKP() //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -HSCRIPT CAI_BaseNPC::VScriptFindEnemyMemory( HSCRIPT pEnemy ) +int CAI_BaseNPC::VScriptNumEnemies() +{ + return GetEnemies()->NumEnemies(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CAI_BaseNPC::VScriptGetFirstEnemyMemory() { HSCRIPT hScript = NULL; - AI_EnemyInfo_t *info = GetEnemies()->Find( ToEnt(pEnemy) ); + + AIEnemiesIter_t iter; + AI_EnemyInfo_t *info = GetEnemies()->GetFirst( &iter ); if (info) { hScript = g_pScriptVM->RegisterInstance( reinterpret_cast(info) ); @@ -758,6 +775,70 @@ HSCRIPT CAI_BaseNPC::VScriptFindEnemyMemory( HSCRIPT pEnemy ) return hScript; } +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CAI_BaseNPC::VScriptGetNextEnemyMemory( HSCRIPT hMemory ) +{ + Script_AI_EnemyInfo_t *pCurEMemory = HScriptToClass( hMemory ); + if (!pCurEMemory) + return NULL; + + HSCRIPT hScript = NULL; + + AIEnemiesIter_t iter = (AIEnemiesIter_t)GetEnemies()->FindIndex( pCurEMemory->hEnemy ); + AI_EnemyInfo_t *pEMemory = GetEnemies()->GetNext( &iter ); + if (pEMemory) + { + hScript = g_pScriptVM->RegisterInstance( reinterpret_cast(pEMemory) ); + } + + return hScript; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CAI_BaseNPC::VScriptFindEnemyMemory( HSCRIPT hEnemy ) +{ + HSCRIPT hScript = NULL; + AI_EnemyInfo_t *info = GetEnemies()->Find( ToEnt(hEnemy) ); + if (info) + { + hScript = g_pScriptVM->RegisterInstance( reinterpret_cast(info) ); + } + + return hScript; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::VScriptUpdateEnemyMemory( HSCRIPT hEnemy, const Vector &position, HSCRIPT hInformer ) +{ + return UpdateEnemyMemory( ToEnt( hEnemy ), position, ToEnt( hInformer ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::VScriptClearEnemyMemory( HSCRIPT hEnemy ) +{ + CBaseEntity *pEnemy = ToEnt( hEnemy ); + if (!pEnemy) + return; + + GetEnemies()->ClearMemory( pEnemy ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::VScriptSetFreeKnowledgeDuration( float flDuration ) +{ + GetEnemies()->SetFreeKnowledgeDuration( flDuration ); +} + +void CAI_BaseNPC::VScriptSetEnemyDiscardTime( float flDuration ) +{ + GetEnemies()->SetEnemyDiscardTime( flDuration ); +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CAI_BaseNPC::VScriptGetState() @@ -765,6 +846,34 @@ int CAI_BaseNPC::VScriptGetState() return (int)GetState(); } +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CAI_BaseNPC::VScriptGetIdealState() +{ + return (int)GetIdealState(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::VScriptSetIdealState( int nNPCState ) +{ + SetIdealState( (NPC_STATE)nNPCState ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CAI_BaseNPC::VScriptGetTarget() +{ + return ToHScript( GetTarget() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::VScriptSetTarget( HSCRIPT hTarget ) +{ + SetTarget( ToEnt( hTarget ) ); +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- HSCRIPT CAI_BaseNPC::VScriptGetHintNode() @@ -832,7 +941,7 @@ int CAI_BaseNPC::VScriptGetTaskID() const Task_t *pTask = GetTask(); int iID = -1; if (pTask) - iID = GetTaskID( TaskName( pTask->iTask ) ); + iID = AI_RemapFromGlobal( GetTaskID( TaskName( pTask->iTask ) ) ); return iID; } @@ -867,6 +976,70 @@ HSCRIPT CAI_BaseNPC::VScriptGetSquad() return hScript; } + +HSCRIPT CAI_BaseNPC::VScriptGetBestSound( int validTypes ) +{ + HSCRIPT hScript = NULL; + CSound *pSound = GetBestSound( validTypes ); + if (pSound) + { + hScript = g_pScriptVM->RegisterInstance( pSound ); + } + + return hScript; +} + +HSCRIPT CAI_BaseNPC::VScriptGetFirstHeardSound() +{ + HSCRIPT hScript = NULL; + + AISoundIter_t iter; + CSound *pSound = GetSenses()->GetFirstHeardSound( &iter ); + if (pSound) + { + hScript = g_pScriptVM->RegisterInstance( pSound ); + } + + return hScript; +} +HSCRIPT CAI_BaseNPC::VScriptGetNextHeardSound( HSCRIPT hSound ) +{ + CSound *pCurSound = HScriptToClass( hSound ); + if (!pCurSound) + return NULL; + + int iCurrent = pCurSound->m_iNextAudible; + if ( iCurrent == SOUNDLIST_EMPTY ) + return NULL; + + HSCRIPT hScript = NULL; + + CSound *pNextSound = CSoundEnt::SoundPointerForIndex( iCurrent ); + if (pNextSound) + { + hScript = g_pScriptVM->RegisterInstance( pNextSound ); + } + + return hScript; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CAI_BaseNPC::VScriptGetFirstSeenEntity( int nSeenType ) +{ + AISightIter_t iter; + return ToHScript( GetSenses()->GetFirstSeenEntity( &iter, (seentype_t)nSeenType ) ); +} + +HSCRIPT CAI_BaseNPC::VScriptGetNextSeenEntity( HSCRIPT hEnt, int nSeenType ) +{ + CBaseEntity *pEnt = ToEnt( hEnt ); + + AISightIter_t iter; + GetSenses()->GetSeenEntityIndex( &iter, pEnt, (seentype_t)nSeenType ); + + return ToHScript( GetSenses()->GetNextSeenEntity( &iter ) ); +} #endif bool CAI_BaseNPC::PassesDamageFilter( const CTakeDamageInfo &info ) @@ -2595,6 +2768,29 @@ void CAI_BaseNPC::OnListened() { m_OnHearCombat.FireOutput(this, this); } + +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_OnListened.CanRunInScope( m_ScriptScope )) + { + ScriptVariant_t functionReturn; + g_Hook_OnListened.Call( m_ScriptScope, &functionReturn, NULL ); + } +#endif +} + +//----------------------------------------------------------------------------- + +void CAI_BaseNPC::OnSeeEntity( CBaseEntity *pEntity ) +{ +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_OnSeeEntity.CanRunInScope( m_ScriptScope )) + { + // entity + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pEntity ) }; + g_Hook_OnSeeEntity.Call( m_ScriptScope, &functionReturn, args ); + } +#endif } //========================================================= @@ -6107,6 +6303,22 @@ bool CAI_BaseNPC::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position if ( GetEnemies() ) { +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_UpdateEnemyMemory.CanRunInScope( m_ScriptScope )) + { + // enemy, position, informer + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pEnemy ), position, ToHScript( pInformer ) }; + if (g_Hook_UpdateEnemyMemory.Call( m_ScriptScope, &functionReturn, args )) + { + // Returning false normally indicates this is a known enemy + // Most uses of that functionality involve checking for new enemies, so this is acceptable + if (functionReturn.m_bool == false) + return false; + } + } +#endif + // If the was eluding me and allow the NPC to play a sound if (GetEnemies()->HasEludedMe(pEnemy)) { @@ -8979,6 +9191,20 @@ bool CAI_BaseNPC::IsValidEnemy( CBaseEntity *pEnemy ) if ( m_hEnemyFilter.Get()!= NULL && m_hEnemyFilter->PassesFilter( this, pEnemy ) == false ) return false; +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_IsValidEnemy.CanRunInScope(m_ScriptScope)) + { + // enemy + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pEnemy ) }; + if (g_Hook_IsValidEnemy.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.m_bool == false) + return false; + } + } +#endif + return true; } @@ -8988,6 +9214,20 @@ bool CAI_BaseNPC::CanBeAnEnemyOf( CBaseEntity *pEnemy ) if ( GetSleepState() > AISS_WAITING_FOR_THREAT ) return false; +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_CanBeAnEnemyOf.CanRunInScope(m_ScriptScope)) + { + // enemy + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pEnemy ) }; + if (g_Hook_CanBeAnEnemyOf.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.m_bool == false) + return false; + } + } +#endif + return true; } @@ -11529,6 +11769,13 @@ float CAI_BaseNPC::GetEnemyLastTimeSeen() const void CAI_BaseNPC::MarkEnemyAsEluded() { GetEnemies()->MarkAsEluded( GetEnemy() ); + +#ifdef MAPBASE + if (m_pSquad) + { + m_pSquad->MarkEnemyAsEluded( this, GetEnemy() ); + } +#endif } void CAI_BaseNPC::ClearEnemyMemory() @@ -12307,7 +12554,17 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC DEFINE_SCRIPTFUNC_NAMED( VScriptSetEnemy, "SetEnemy", "Set the NPC's current enemy." ) DEFINE_SCRIPTFUNC_NAMED( VScriptGetEnemyLKP, "GetEnemyLKP", "Get the last known position of the NPC's current enemy." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptNumEnemies, "NumEnemies", "Get the number of enemies this NPC knows about." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetFirstEnemyMemory, "GetFirstEnemyMemory", "Get information about the NPC's first enemy." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetNextEnemyMemory, "GetNextEnemyMemory", "Get information about the NPC's next enemy." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptFindEnemyMemory, "FindEnemyMemory", "Get information about the NPC's current enemy." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptUpdateEnemyMemory, "UpdateEnemyMemory", "Update information on this enemy. First parameter is the enemy, second is the position we now know the enemy is at, third parameter is the informer (e.g. squadmate who sees enemy, null if I see it myself). Returns true if this is a new enemy." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptClearEnemyMemory, "ClearEnemyMemory", "Makes the NPC forget about the specified enemy." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptSetFreeKnowledgeDuration, "SetFreeKnowledgeDuration", "Sets the amount of time the NPC can always know an enemy's location after losing sight." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptSetEnemyDiscardTime, "SetEnemyDiscardTime", "Sets the amount of time needed before the NPC discards an unseen enemy's memory." ) DEFINE_SCRIPTFUNC( GetLastAttackTime, "Get the last time the NPC has used an attack (e.g. fired a bullet from a gun)." ) DEFINE_SCRIPTFUNC( GetLastDamageTime, "Get the last time the NPC has been damaged." ) @@ -12315,6 +12572,11 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC DEFINE_SCRIPTFUNC( GetLastEnemyTime, "Get the last time the NPC has seen an enemy." ) DEFINE_SCRIPTFUNC_NAMED( VScriptGetState, "GetNPCState", "Get the NPC's current state." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetIdealState, "GetIdealNPCState", "Get the NPC's ideal state." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptSetIdealState, "SetIdealNPCState", "Set the NPC's ideal state." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetTarget, "GetNPCTarget", "Get the NPC's AI target." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptSetTarget, "SetNPCTarget", "Set the NPC's AI target." ) DEFINE_SCRIPTFUNC_NAMED( VScriptWake, "Wake", "Awakens the NPC if it is currently asleep." ) DEFINE_SCRIPTFUNC_NAMED( VScriptSleep, "Sleep", "Puts the NPC into a sleeping state." ) @@ -12362,6 +12624,16 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC DEFINE_SCRIPTFUNC_NAMED( VScriptClearCondition, "ClearCondition", "Clear a condition on the NPC." ) DEFINE_SCRIPTFUNC_NAMED( ClearCondition, "ClearConditionID", "Clear a condition on the NPC by ID." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptSetCustomInterruptCondition, "SetCustomInterruptCondition", "Use with BuildScheduleTestBits to define conditions which should interrupt the schedule." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptIsCustomInterruptConditionSet, "IsCustomInterruptConditionSet", "Use with BuildScheduleTestBits to define conditions which should interrupt the schedule." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptClearCustomInterruptCondition, "ClearCustomInterruptCondition", "Use with BuildScheduleTestBits to define conditions which should interrupt the schedule." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptChainStartTask, "ChainStartTask", "Use with StartTask to redirect to the specified task." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptChainRunTask, "ChainRunTask", "Use with RunTask to redirect to the specified task." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptFailTask, "FailTask", "Fails the currently running task with the specified error message." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptCompleteTask, "CompleteTask", "Completes the currently running task." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetTaskStatus, "GetTaskStatus", "Gets the current task's status." ) + DEFINE_SCRIPTFUNC( IsMoving, "Check if the NPC is moving." ) DEFINE_SCRIPTFUNC_NAMED( VScriptGetExpresser, "GetExpresser", "Get a handle for this NPC's expresser." ) @@ -12380,6 +12652,13 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC DEFINE_SCRIPTFUNC( IsCrouching, "Returns true if the NPC is crouching." ) DEFINE_SCRIPTFUNC( Crouch, "Tells the NPC to crouch." ) DEFINE_SCRIPTFUNC( Stand, "Tells the NPC to stand if it is crouching." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetBestSound, "GetBestSound", "Get the NPC's best sound of the specified type(s). Use 'ALL_SOUNDS' to get any sound." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetFirstHeardSound, "GetFirstHeardSound", "Get the NPC's first heard sound." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetNextHeardSound, "GetNextHeardSound", "Get the NPC's next heard sound." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetFirstSeenEntity, "GetFirstSeenEntity", "Get the NPC's first seen entity in the specified 'SEEN_' list." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetNextSeenEntity, "GetNextSeenEntity", "Get the NPC's next seen entity in the specified 'SEEN_' list." ) // // Hooks @@ -12409,6 +12688,32 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC DEFINE_SCRIPTHOOK_PARAM( "activity", FIELD_CSTRING ) DEFINE_SCRIPTHOOK_PARAM( "translatedActivity", FIELD_CSTRING ) END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_IsValidEnemy, "IsValidEnemy", FIELD_BOOLEAN, "Whether or not the specified enemy should be considered valid." ) + DEFINE_SCRIPTHOOK_PARAM( "enemy", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_CanBeAnEnemyOf, "CanBeAnEnemyOf", FIELD_BOOLEAN, "Whether or not this NPC can be an enemy of another NPC." ) + DEFINE_SCRIPTHOOK_PARAM( "enemy", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_UpdateEnemyMemory, "UpdateEnemyMemory", FIELD_BOOLEAN, "Whether or not this NPC can be an enemy of another NPC." ) + DEFINE_SCRIPTHOOK_PARAM( "enemy", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "position", FIELD_VECTOR ) + DEFINE_SCRIPTHOOK_PARAM( "informer", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_OnSeeEntity, "OnSeeEntity", FIELD_VOID, "Called when the NPC sees an entity." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + DEFINE_SIMPLE_SCRIPTHOOK( CAI_BaseNPC::g_Hook_OnListened, "OnListened", FIELD_VOID, "Called when the NPC assigns sound conditions after checking for sounds it hears." ) + DEFINE_SIMPLE_SCRIPTHOOK( CAI_BaseNPC::g_Hook_BuildScheduleTestBits, "BuildScheduleTestBits", FIELD_VOID, "Called when the NPC is determining which conditions can interrupt the current schedule." ) + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_StartTask, "StartTask", FIELD_VOID, "Called when a task is starting. The task is provided in both string and ID form. Return false to override actual task functionality." ) + DEFINE_SCRIPTHOOK_PARAM( "task", FIELD_CSTRING ) + DEFINE_SCRIPTHOOK_PARAM( "task_id", FIELD_INTEGER ) + DEFINE_SCRIPTHOOK_PARAM( "task_data", FIELD_FLOAT ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_RunTask, "RunTask", FIELD_VOID, "Called every think while the task is running. The task is provided in both string and ID form. Return false to override actual task functionality." ) + DEFINE_SCRIPTHOOK_PARAM( "task", FIELD_CSTRING ) + DEFINE_SCRIPTHOOK_PARAM( "task_id", FIELD_INTEGER ) + DEFINE_SCRIPTHOOK_PARAM( "task_data", FIELD_FLOAT ) + END_SCRIPTHOOK() END_SCRIPTDESC(); #endif diff --git a/sp/src/game/server/ai_basenpc.h b/sp/src/game/server/ai_basenpc.h index 7db67d33..97d74a1e 100644 --- a/sp/src/game/server/ai_basenpc.h +++ b/sp/src/game/server/ai_basenpc.h @@ -1104,7 +1104,7 @@ public: virtual void OnLooked( int iDistance ); virtual void OnListened(); - virtual void OnSeeEntity( CBaseEntity *pEntity ) {} + virtual void OnSeeEntity( CBaseEntity *pEntity ); // If true, AI will try to see this entity regardless of distance. virtual bool ShouldNotDistanceCull() { return false; } @@ -1271,9 +1271,24 @@ private: void VScriptSetEnemy( HSCRIPT pEnemy ); Vector VScriptGetEnemyLKP(); - HSCRIPT VScriptFindEnemyMemory( HSCRIPT pEnemy ); + int VScriptNumEnemies(); + + HSCRIPT VScriptGetFirstEnemyMemory(); + HSCRIPT VScriptGetNextEnemyMemory( HSCRIPT hMemory ); + + HSCRIPT VScriptFindEnemyMemory( HSCRIPT hEnemy ); + bool VScriptUpdateEnemyMemory( HSCRIPT hEnemy, const Vector &position, HSCRIPT hInformer ); + void VScriptClearEnemyMemory( HSCRIPT hEnemy ); + + void VScriptSetFreeKnowledgeDuration( float flDuration ); + void VScriptSetEnemyDiscardTime( float flDuration ); int VScriptGetState(); + int VScriptGetIdealState(); + void VScriptSetIdealState( int nNPCState ); + + HSCRIPT VScriptGetTarget(); + void VScriptSetTarget( HSCRIPT hTarget ); void VScriptWake( HSCRIPT hActivator ) { Wake( ToEnt(hActivator) ); } void VScriptSleep() { Sleep(); } @@ -1308,12 +1323,29 @@ private: void VScriptSetCondition( const char *szCondition ) { SetCondition( GetConditionID( szCondition ) ); } void VScriptClearCondition( const char *szCondition ) { ClearCondition( GetConditionID( szCondition ) ); } + void VScriptSetCustomInterruptCondition( const char *szCondition ) { SetCustomInterruptCondition( GetConditionID( szCondition ) ); } + bool VScriptIsCustomInterruptConditionSet( const char *szCondition ) { return IsCustomInterruptConditionSet( GetConditionID( szCondition ) ); } + void VScriptClearCustomInterruptCondition( const char *szCondition ) { ClearCustomInterruptCondition( GetConditionID( szCondition ) ); } + + void VScriptChainStartTask( const char *szTask, float flTaskData ) { ChainStartTask( AI_RemapFromGlobal( GetTaskID( szTask ) ), flTaskData ); } + void VScriptChainRunTask( const char *szTask, float flTaskData ) { ChainRunTask( AI_RemapFromGlobal( GetTaskID( szTask ) ), flTaskData ); } + void VScriptFailTask( const char *szFailReason ) { TaskFail( szFailReason ); } + void VScriptCompleteTask() { TaskComplete(); } + int VScriptGetTaskStatus() { return (int)GetTaskStatus(); } + HSCRIPT VScriptGetExpresser(); HSCRIPT VScriptGetCine(); int GetScriptState() { return m_scriptState; } HSCRIPT VScriptGetSquad(); + + HSCRIPT VScriptGetBestSound( int validTypes ); + HSCRIPT VScriptGetFirstHeardSound(); + HSCRIPT VScriptGetNextHeardSound( HSCRIPT hSound ); + + HSCRIPT VScriptGetFirstSeenEntity( int nSeenType ); + HSCRIPT VScriptGetNextSeenEntity( HSCRIPT hEnt, int nSeenType ); #endif //----------------------------------------------------- @@ -2399,6 +2431,14 @@ public: static ScriptHook_t g_Hook_GetActualShootPosition; static ScriptHook_t g_Hook_OverrideMove; static ScriptHook_t g_Hook_ShouldPlayFakeSequenceGesture; + static ScriptHook_t g_Hook_IsValidEnemy; + static ScriptHook_t g_Hook_CanBeAnEnemyOf; + static ScriptHook_t g_Hook_UpdateEnemyMemory; + static ScriptHook_t g_Hook_OnSeeEntity; + static ScriptHook_t g_Hook_OnListened; + static ScriptHook_t g_Hook_BuildScheduleTestBits; + static ScriptHook_t g_Hook_StartTask; + static ScriptHook_t g_Hook_RunTask; #endif private: diff --git a/sp/src/game/server/ai_basenpc_schedule.cpp b/sp/src/game/server/ai_basenpc_schedule.cpp index 56e20e03..8a8bee88 100644 --- a/sp/src/game/server/ai_basenpc_schedule.cpp +++ b/sp/src/game/server/ai_basenpc_schedule.cpp @@ -45,6 +45,10 @@ extern ConVar ai_use_think_optimizations; ConVar ai_simulate_task_overtime( "ai_simulate_task_overtime", "0" ); +#ifdef MAPBASE +ConVar ai_enemy_memory_fixes( "ai_enemy_memory_fixes", "0", FCVAR_NONE, "Toggles Mapbase fixes for certain NPC AI not using enemy memory when it should." ); +#endif + #define MAX_TASKS_RUN 10 struct TaskTimings @@ -276,6 +280,14 @@ void CAI_BaseNPC::NextScheduledTask ( void ) void CAI_BaseNPC::BuildScheduleTestBits( void ) { //NOTENOTE: Always defined in the leaf classes + +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_BuildScheduleTestBits.CanRunInScope( m_ScriptScope )) + { + ScriptVariant_t functionReturn; + g_Hook_BuildScheduleTestBits.Call( m_ScriptScope, &functionReturn, NULL ); + } +#endif } @@ -730,6 +742,23 @@ void CAI_BaseNPC::MaintainSchedule ( void ) AI_PROFILE_SCOPE_BEGIN_( pszTaskName ); AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_StartTask); +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_StartTask.CanRunInScope( m_ScriptScope )) + { + // task, task_id, task_data + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { (bDebugTaskNames) ? pszTaskName : TaskName( pTask->iTask ), pTask->iTask, pTask->flTaskData }; + if (g_Hook_StartTask.Call( m_ScriptScope, &functionReturn, args )) + { + // Returning false overrides original functionality + if (functionReturn.m_bool != false) + StartTask( pTask ); + } + else + StartTask( pTask ); + } + else +#endif StartTask( pTask ); AI_PROFILE_SCOPE_END(); @@ -766,6 +795,23 @@ void CAI_BaseNPC::MaintainSchedule ( void ) int j; for (j = 0; j < 8; j++) { +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_RunTask.CanRunInScope( m_ScriptScope )) + { + // task, task_id, task_data + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { (bDebugTaskNames) ? pszTaskName : TaskName( pTask->iTask ), pTask->iTask, pTask->flTaskData }; + if (g_Hook_RunTask.Call( m_ScriptScope, &functionReturn, args )) + { + // Returning false overrides original functionality + if (functionReturn.m_bool != false) + RunTask( pTask ); + } + else + RunTask( pTask ); + } + else +#endif RunTask( pTask ); if ( GetTaskInterrupt() == 0 || TaskIsComplete() || HasCondition(COND_TASK_FAILED) ) @@ -1971,7 +2017,17 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) flMaxRange = m_flDistTooFar; } +#ifdef MAPBASE + // By default, TASK_GET_PATH_TO_ENEMY_LKP_LOS acts identical to TASK_GET_PATH_TO_ENEMY_LOS. + // Considering the fact TASK_GET_PATH_TO_ENEMY_LKP doesn't use this code, this appears to be a mistake. + // In HL2, this task is used by Combine soldiers, metrocops, striders, and hunters. + // With this change, these NPCs will establish LOS according to enemy memory instead of where the enemy + // actually is. This may make the NPCs more consistent and fair, but their AI and levels built around it + // may have been designed around this bug, so this is currently being tied to a cvar. + Vector vecEnemy = ( task != TASK_GET_PATH_TO_ENEMY_LKP_LOS || !ai_enemy_memory_fixes.GetBool() ) ? GetEnemy()->GetAbsOrigin() : GetEnemyLKP(); +#else Vector vecEnemy = ( task != TASK_GET_PATH_TO_ENEMY_LKP ) ? GetEnemy()->GetAbsOrigin() : GetEnemyLKP(); +#endif Vector vecEnemyEye = vecEnemy + GetEnemy()->GetViewOffset(); Vector posLos; diff --git a/sp/src/game/server/ai_default.cpp b/sp/src/game/server/ai_default.cpp index 8f660ec3..ec35dc58 100644 --- a/sp/src/game/server/ai_default.cpp +++ b/sp/src/game/server/ai_default.cpp @@ -500,6 +500,13 @@ public: g_AI_SensedObjectsManager.Init(); } +#ifdef MAPBASE_VSCRIPT + virtual void RegisterVScript() + { + g_pScriptVM->RegisterInstance( &g_AI_SensedObjectsManager, "SensedObjectsManager" ); + } +#endif + void LevelShutdownPreEntity() { CBaseCombatCharacter::ResetVisibilityCache(); diff --git a/sp/src/game/server/ai_expresserfollowup.cpp b/sp/src/game/server/ai_expresserfollowup.cpp index 38a83a3d..cba7c26b 100644 --- a/sp/src/game/server/ai_expresserfollowup.cpp +++ b/sp/src/game/server/ai_expresserfollowup.cpp @@ -78,6 +78,7 @@ static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity * // See DispatchFollowupThroughQueue() criteria.AppendCriteria( "From_idx", CNumStr( pSpeaker->entindex() ) ); criteria.AppendCriteria( "From_class", pSpeaker->GetClassname() ); + pSpeaker->AppendContextToCriteria( criteria, "From_" ); #endif // if a SUBJECT criteria is missing, put it back in. if ( criteria.FindCriterionIndex( "Subject" ) == -1 ) diff --git a/sp/src/game/server/ai_memory.cpp b/sp/src/game/server/ai_memory.cpp index 7ac69311..1f337910 100644 --- a/sp/src/game/server/ai_memory.cpp +++ b/sp/src/game/server/ai_memory.cpp @@ -225,6 +225,18 @@ AI_EnemyInfo_t *CAI_Enemies::Find( CBaseEntity *pEntity, bool bTryDangerMemory ) } +//----------------------------------------------------------------------------- + +#ifdef MAPBASE +unsigned char CAI_Enemies::FindIndex( CBaseEntity *pEntity ) +{ + if ( pEntity == AI_UNKNOWN_ENEMY ) + pEntity = NULL; + + return m_Map.Find( pEntity ); +} +#endif + //----------------------------------------------------------------------------- AI_EnemyInfo_t *CAI_Enemies::GetDangerMemory() diff --git a/sp/src/game/server/ai_memory.h b/sp/src/game/server/ai_memory.h index d348c53e..c300ec04 100644 --- a/sp/src/game/server/ai_memory.h +++ b/sp/src/game/server/ai_memory.h @@ -63,6 +63,9 @@ public: AI_EnemyInfo_t *GetFirst( AIEnemiesIter_t *pIter ); AI_EnemyInfo_t *GetNext( AIEnemiesIter_t *pIter ); AI_EnemyInfo_t *Find( CBaseEntity *pEntity, bool bTryDangerMemory = false ); +#ifdef MAPBASE + unsigned char FindIndex( CBaseEntity *pEntity ); +#endif AI_EnemyInfo_t *GetDangerMemory(); int NumEnemies() const { return m_Map.Count(); } diff --git a/sp/src/game/server/ai_senses.cpp b/sp/src/game/server/ai_senses.cpp index 45e23e09..abf4e1d9 100644 --- a/sp/src/game/server/ai_senses.cpp +++ b/sp/src/game/server/ai_senses.cpp @@ -298,6 +298,43 @@ CBaseEntity *CAI_Senses::GetNextSeenEntity( AISightIter_t *pIter ) const return NULL; } +//----------------------------------------------------------------------------- + +#ifdef MAPBASE +bool CAI_Senses::GetSeenEntityIndex( AISightIter_t *pIter, CBaseEntity *pSightEnt, seentype_t iSeenType ) const +{ + COMPILE_TIME_ASSERT( sizeof( AISightIter_t ) == sizeof( AISightIterVal_t ) ); + + AISightIterVal_t *pIterVal = (AISightIterVal_t *)pIter; + + // If we're searching for a specific type, start in that array + pIterVal->SeenArray = (char)iSeenType; + int iFirstArray = ( iSeenType == SEEN_ALL ) ? 0 : iSeenType; + + for ( int i = iFirstArray; i < ARRAYSIZE( m_SeenArrays ); i++ ) + { + for ( int j = pIterVal->iNext; j < m_SeenArrays[i]->Count(); j++ ) + { + if ( (*m_SeenArrays[i])[j].Get() == pSightEnt ) + { + pIterVal->array = i; + pIterVal->iNext = j+1; + return true; + } + } + pIterVal->iNext = 0; + + // If we're searching for a specific type, don't move to the next array + if ( pIterVal->SeenArray != SEEN_ALL ) + break; + } + + (*pIter) = (AISightIter_t)(-1); + return false; +} +#endif + + //----------------------------------------------------------------------------- void CAI_Senses::BeginGather() @@ -749,4 +786,27 @@ void CAI_SensedObjectsManager::AddEntity( CBaseEntity *pEntity ) m_SensedObjects.AddToTail( pEntity ); } +#ifdef MAPBASE +void CAI_SensedObjectsManager::RemoveEntity( CBaseEntity *pEntity ) +{ + int i = m_SensedObjects.Find( pEntity ); + if (i == m_SensedObjects.InvalidIndex()) + return; + + pEntity->RemoveFlag( FL_OBJECT ); + m_SensedObjects.FastRemove( i ); +} +#endif + +//----------------------------------------------------------------------------- + +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CAI_SensedObjectsManager, SCRIPT_SINGLETON "Manager which handles sensed objects." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptAddEntity, "AddEntity", "Adds an entity to the sensed object list." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptRemoveEntity, "RemoveEntity", "Removes an entity from the sensed object list." ) + +END_SCRIPTDESC(); +#endif + //============================================================================= diff --git a/sp/src/game/server/ai_senses.h b/sp/src/game/server/ai_senses.h index fed85eb9..e0284909 100644 --- a/sp/src/game/server/ai_senses.h +++ b/sp/src/game/server/ai_senses.h @@ -82,6 +82,9 @@ public: CBaseEntity * GetFirstSeenEntity( AISightIter_t *pIter, seentype_t iSeenType = SEEN_ALL ) const; CBaseEntity * GetNextSeenEntity( AISightIter_t *pIter ) const; +#ifdef MAPBASE + bool GetSeenEntityIndex( AISightIter_t *pIter, CBaseEntity *pSightEnt, seentype_t iSeenType ) const; +#endif CSound * GetFirstHeardSound( AISoundIter_t *pIter ); CSound * GetNextHeardSound( AISoundIter_t *pIter ); @@ -152,6 +155,14 @@ public: CBaseEntity * GetNext( int *pIter ); virtual void AddEntity( CBaseEntity *pEntity ); +#ifdef MAPBASE + virtual void RemoveEntity( CBaseEntity *pEntity ); +#endif + +#ifdef MAPBASE_VSCRIPT + void ScriptAddEntity( HSCRIPT hEnt ) { AddEntity( ToEnt( hEnt ) ); } + void ScriptRemoveEntity( HSCRIPT hEnt ) { RemoveEntity( ToEnt( hEnt ) ); } +#endif private: virtual void OnEntitySpawned( CBaseEntity *pEntity ); diff --git a/sp/src/game/server/ai_speech_new.cpp b/sp/src/game/server/ai_speech_new.cpp index d29b6b39..18c8e1de 100644 --- a/sp/src/game/server/ai_speech_new.cpp +++ b/sp/src/game/server/ai_speech_new.cpp @@ -18,6 +18,7 @@ #include "sceneentity.h" #include "ai_speechqueue.h" #ifdef MAPBASE +#include "mapbase/choreosentence.h" #include "ai_squad.h" #endif @@ -846,6 +847,9 @@ bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response *res case ResponseRules::RESPONSE_NONE: break; +#ifdef MAPBASE + case ResponseRules::RESPONSE_CHOREOSENTENCE: +#endif case ResponseRules::RESPONSE_SPEAK: { if ( !result->ShouldntUseScene() ) @@ -918,7 +922,7 @@ bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response *res textParams.g1 = 255; textParams.b1 = 255; - if (ai_speech_print_mode.GetBool() && GetOuter()->GetGameTextSpeechParams( textParams )) + if (ai_speech_print_mode.GetBool() && GetOuter()->DispatchGetGameTextSpeechParams( textParams )) { CRecipientFilter filter; filter.AddAllPlayers(); @@ -1194,6 +1198,15 @@ float CAI_Expresser::GetResponseDuration( AI_Response *result ) case ResponseRules::RESPONSE_NONE: case ResponseRules::RESPONSE_ENTITYIO: return 0.0f; +#ifdef MAPBASE + case ResponseRules::RESPONSE_CHOREOSENTENCE: + { + const ChoreoSentence_t *pSentence = LookupChoreoSentence( GetOuter(), response ); + if (pSentence) + return GetChoreoSentenceDuration( GetOuter(), *pSentence ); + } + break; +#endif } return 0.0f; diff --git a/sp/src/game/server/ai_squad.cpp b/sp/src/game/server/ai_squad.cpp index 15e7fb68..f364896d 100644 --- a/sp/src/game/server/ai_squad.cpp +++ b/sp/src/game/server/ai_squad.cpp @@ -18,6 +18,10 @@ CAI_SquadManager g_AI_SquadManager; +#ifdef MAPBASE +ConVar ai_squad_broadcast_elusion("ai_squad_broadcast_elusion", "0", FCVAR_NONE, "Tells the entire squad when an enemy is eluded"); +#endif + //----------------------------------------------------------------------------- // CAI_SquadManager // @@ -740,6 +744,25 @@ void CAI_Squad::UpdateEnemyMemory( CAI_BaseNPC *pUpdater, CBaseEntity *pEnemy, c //------------------------------------------------------------------------------ +#ifdef MAPBASE +void CAI_Squad::MarkEnemyAsEluded( CAI_BaseNPC *pUpdater, CBaseEntity *pEnemy ) +{ + if (!ai_squad_broadcast_elusion.GetBool()) + return; + + //Broadcast to all members of the squad + for ( int i = 0; i < m_SquadMembers.Count(); i++ ) + { + if ( m_SquadMembers[i] != pUpdater ) + { + m_SquadMembers[i]->GetEnemies()->MarkAsEluded( pEnemy ); + } + } +} +#endif + +//------------------------------------------------------------------------------ + #ifdef PER_ENEMY_SQUADSLOTS AISquadEnemyInfo_t *CAI_Squad::FindEnemyInfo( CBaseEntity *pEnemy ) @@ -883,14 +906,14 @@ void CAI_Squad::ScriptRemoveFromSquad( HSCRIPT hNPC ) { RemoveFromSquad( HScrip bool CAI_Squad::ScriptIsSilentMember( HSCRIPT hNPC ) { return IsSilentMember( HScriptToClass( hNPC ) ); } -void CAI_Squad::ScriptSetSquadData( int iSlot, const char *data ) +void CAI_Squad::ScriptSetSquadData( int iSlot, int data ) { SetSquadData( iSlot, data ); } -const char *CAI_Squad::ScriptGetSquadData( int iSlot ) +int CAI_Squad::ScriptGetSquadData( int iSlot ) { - const char *data; + int data; GetSquadData( iSlot, &data ); return data; } diff --git a/sp/src/game/server/ai_squad.h b/sp/src/game/server/ai_squad.h index 9604d1f0..0d886c7f 100644 --- a/sp/src/game/server/ai_squad.h +++ b/sp/src/game/server/ai_squad.h @@ -107,6 +107,12 @@ public: void SquadNewEnemy ( CBaseEntity *pEnemy ); void UpdateEnemyMemory( CAI_BaseNPC *pUpdater, CBaseEntity *pEnemy, const Vector &position ); +#ifdef MAPBASE + // The idea behind this is that, if one squad member fails to locate the enemy, nobody in the squad knows where the enemy is + // Makes combat utilizing elusion a bit smoother + // (gated by ai_squad_broadcast_elusion cvar) + void MarkEnemyAsEluded( CAI_BaseNPC *pUpdater, CBaseEntity *pEnemy ); +#endif bool OccupyStrategySlotRange( CBaseEntity *pEnemy, int slotIDStart, int slotIDEnd, int *pSlot ); void VacateStrategySlot( CBaseEntity *pEnemy, int slot); @@ -186,8 +192,8 @@ private: bool ScriptIsSilentMember( HSCRIPT hNPC ); - void ScriptSetSquadData( int iSlot, const char *data ); - const char *ScriptGetSquadData( int iSlot ); + void ScriptSetSquadData( int iSlot, int data ); + int ScriptGetSquadData( int iSlot ); #endif private: diff --git a/sp/src/game/server/baseanimating.cpp b/sp/src/game/server/baseanimating.cpp index 0e226209..28c549d6 100644 --- a/sp/src/game/server/baseanimating.cpp +++ b/sp/src/game/server/baseanimating.cpp @@ -317,7 +317,7 @@ BEGIN_ENT_SCRIPTDESC( CBaseAnimating, CBaseEntity, "Animating models" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetSequenceActivity, "GetSequenceActivity", "Gets the activity ID of the specified sequence index" ) DEFINE_SCRIPTFUNC_NAMED( ScriptSelectWeightedSequence, "SelectWeightedSequence", "Selects a sequence for the specified activity ID" ) DEFINE_SCRIPTFUNC_NAMED( ScriptSelectHeaviestSequence, "SelectHeaviestSequence", "Selects the sequence with the heaviest weight for the specified activity ID" ) - DEFINE_SCRIPTFUNC_NAMED( ScriptGetSequenceKeyValues, "GetSequenceKeyValues", "Get a KeyValue class instance on the specified sequence. WARNING: This uses the same KeyValue pointer as GetModelKeyValues!" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSequenceKeyValues, "GetSequenceKeyValues", "Get a KeyValue class instance on the specified sequence" ) DEFINE_SCRIPTFUNC( ResetSequenceInfo, "" ) DEFINE_SCRIPTFUNC( StudioFrameAdvance, "" ) DEFINE_SCRIPTFUNC( GetPlaybackRate, "" ) @@ -1261,7 +1261,8 @@ void CBaseAnimating::DispatchAnimEvents ( CBaseAnimating *eventHandler ) } #ifdef MAPBASE_VSCRIPT - if (eventHandler->ScriptHookHandleAnimEvent( &event ) == false) + scriptanimevent_t wrapper( event ); + if (!eventHandler->ScriptHookHandleAnimEvent( wrapper )) continue; #endif @@ -1299,11 +1300,11 @@ void CBaseAnimating::DispatchAnimEvents ( CBaseAnimating *eventHandler ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -bool CBaseAnimating::ScriptHookHandleAnimEvent( animevent_t *pEvent ) +bool CBaseAnimating::ScriptHookHandleAnimEvent( scriptanimevent_t &event ) { if (m_ScriptScope.IsInitialized() && g_Hook_HandleAnimEvent.CanRunInScope(m_ScriptScope)) { - HSCRIPT hEvent = g_pScriptVM->RegisterInstance( reinterpret_cast(pEvent) ); + HSCRIPT hEvent = g_pScriptVM->RegisterInstance( &event ); // event ScriptVariant_t args[] = { hEvent }; @@ -2305,21 +2306,14 @@ void CBaseAnimating::ScriptGetBoneTransform( int iBone, HSCRIPT hTransform ) //----------------------------------------------------------------------------- // VScript access to sequence's key values -// for iteration and value access, use: -// ScriptFindKey, ScriptGetFirstSubKey, ScriptGetString, -// ScriptGetInt, ScriptGetFloat, ScriptGetNextKey -// NOTE: This is recycled from ScriptGetModelKeyValues() and uses its pointer!!! //----------------------------------------------------------------------------- -HSCRIPT CBaseAnimating::ScriptGetSequenceKeyValues( int iSequence ) +HSCRIPT_RC CBaseAnimating::ScriptGetSequenceKeyValues( int iSequence ) { KeyValues *pSeqKeyValues = GetSequenceKeyValues( iSequence ); HSCRIPT hScript = NULL; if ( pSeqKeyValues ) { - // UNDONE: how does destructor get called on this - m_pScriptModelKeyValues = hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pSeqKeyValues, true ); - - // UNDONE: who calls ReleaseInstance on this??? Does name need to be unique??? + hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pSeqKeyValues ); } return hScript; diff --git a/sp/src/game/server/baseanimating.h b/sp/src/game/server/baseanimating.h index 4fb3c47c..8c8cf1f4 100644 --- a/sp/src/game/server/baseanimating.h +++ b/sp/src/game/server/baseanimating.h @@ -16,6 +16,9 @@ #include "datacache/idatacache.h" #include "tier0/threadtools.h" +#ifdef MAPBASE_VSCRIPT +struct scriptanimevent_t; +#endif struct animevent_t; struct matrix3x4_t; @@ -146,7 +149,7 @@ public: virtual void DispatchAnimEvents ( CBaseAnimating *eventHandler ); // Handle events that have happend since last time called up until X seconds into the future virtual void HandleAnimEvent( animevent_t *pEvent ); #ifdef MAPBASE_VSCRIPT - bool ScriptHookHandleAnimEvent( animevent_t *pEvent ); + bool ScriptHookHandleAnimEvent( scriptanimevent_t &event ); #endif int LookupPoseParameter( CStudioHdr *pStudioHdr, const char *szName ); @@ -208,7 +211,7 @@ public: int ScriptSelectHeaviestSequence( int activity ) { return SelectHeaviestSequence( (Activity)activity ); } int ScriptSelectWeightedSequence( int activity, int curSequence ) { return SelectWeightedSequence( (Activity)activity, curSequence ); } - HSCRIPT ScriptGetSequenceKeyValues( int iSequence ); + HSCRIPT_RC ScriptGetSequenceKeyValues( int iSequence ); // For VScript int GetSkin() { return m_nSkin; } diff --git a/sp/src/game/server/basecombatcharacter.cpp b/sp/src/game/server/basecombatcharacter.cpp index b7a2692d..668f1525 100644 --- a/sp/src/game/server/basecombatcharacter.cpp +++ b/sp/src/game/server/basecombatcharacter.cpp @@ -3482,13 +3482,13 @@ void CBaseCombatCharacter::AddRelationship( const char *pszRelationship, CBaseEn bool bFoundEntity = false; // Try to get pointer to an entity of this name - CBaseEntity *entity = gEntList.FindEntityByName( NULL, entityString ); + CBaseEntity *entity = gEntList.FindEntityByName( NULL, entityString, this, pActivator ); while( entity ) { // make sure you catch all entities of this name. bFoundEntity = true; AddEntityRelationship(entity, disposition, priority ); - entity = gEntList.FindEntityByName( entity, entityString ); + entity = gEntList.FindEntityByName( entity, entityString, this, pActivator ); } if( !bFoundEntity ) diff --git a/sp/src/game/server/baseentity.cpp b/sp/src/game/server/baseentity.cpp index d62e9536..97bf237d 100644 --- a/sp/src/game/server/baseentity.cpp +++ b/sp/src/game/server/baseentity.cpp @@ -1628,10 +1628,53 @@ void CBaseEntity::TakeDamage( const CTakeDamageInfo &inputInfo ) DamageFilterDamageMod(info); #endif +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_OnTakeDamage.CanRunInScope( m_ScriptScope )) + { + HSCRIPT hInfo = g_pScriptVM->RegisterInstance( &info ); + + // info + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ScriptVariant_t( hInfo ) }; + if ( g_Hook_OnTakeDamage.Call( m_ScriptScope, &functionReturn, args ) ) + { + if (functionReturn.m_type == FIELD_BOOLEAN && functionReturn.m_bool == false) + { + g_pScriptVM->RemoveInstance( hInfo ); + return; + } + } + + g_pScriptVM->RemoveInstance( hInfo ); + } +#endif + OnTakeDamage( info ); } } +//----------------------------------------------------------------------------- +// Purpose: Allows entities to be 'invisible' to NPC senses. +//----------------------------------------------------------------------------- +bool CBaseEntity::CanBeSeenBy( CAI_BaseNPC *pNPC ) +{ +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_CanBeSeenBy.CanRunInScope(m_ScriptScope)) + { + // npc + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pNPC ) }; + if (g_Hook_CanBeSeenBy.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.m_bool == false) + return false; + } + } +#endif + + return true; +} + //----------------------------------------------------------------------------- // Purpose: Returns a value that scales all damage done by this entity. //----------------------------------------------------------------------------- @@ -2251,14 +2294,16 @@ ScriptHook_t CBaseEntity::g_Hook_OnEntText; ScriptHook_t CBaseEntity::g_Hook_VPhysicsCollision; ScriptHook_t CBaseEntity::g_Hook_FireBullets; ScriptHook_t CBaseEntity::g_Hook_OnDeath; +ScriptHook_t CBaseEntity::g_Hook_OnTakeDamage; ScriptHook_t CBaseEntity::g_Hook_OnKilledOther; ScriptHook_t CBaseEntity::g_Hook_HandleInteraction; ScriptHook_t CBaseEntity::g_Hook_ModifyEmitSoundParams; ScriptHook_t CBaseEntity::g_Hook_ModifySentenceParams; +ScriptHook_t CBaseEntity::g_Hook_ModifyOrAppendCriteria; +ScriptHook_t CBaseEntity::g_Hook_CanBeSeenBy; #endif -BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" ) - DEFINE_SCRIPT_INSTANCE_HELPER( &g_BaseEntityScriptInstanceHelper ) +BEGIN_ENT_SCRIPTDESC_ROOT_WITH_HELPER( CBaseEntity, "Root class of all server-side entities", &g_BaseEntityScriptInstanceHelper ) DEFINE_SCRIPTFUNC_NAMED( ConnectOutputToScript, "ConnectOutput", "Adds an I/O connection that will call the named function when the specified output fires" ) DEFINE_SCRIPTFUNC_NAMED( DisconnectOutputFromScript, "DisconnectOutput", "Removes a connected script function from an I/O event." ) @@ -2377,6 +2422,7 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" DEFINE_SCRIPTFUNC_NAMED( ScriptGetContext, "GetContext", "Get a response context value" ) DEFINE_SCRIPTFUNC_NAMED( ScriptAddContext, "AddContext", "Add a response context value" ) + DEFINE_SCRIPTFUNC( RemoveContext, "Remove a response context" ) DEFINE_SCRIPTFUNC( GetContextExpireTime, "Get a response context's expiration time" ) DEFINE_SCRIPTFUNC( GetContextCount, "Get the number of response contexts" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetContextIndex, "GetContextIndex", "Get a response context at a specific index in the form of a table" ) @@ -2515,14 +2561,15 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" DEFINE_SCRIPTHOOK_PARAM( "normal", FIELD_VECTOR ) END_SCRIPTHOOK() - BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_FireBullets, "FireBullets", FIELD_VOID, "Called for every single VPhysics-related collision experienced by this entity." ) - DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) - DEFINE_SCRIPTHOOK_PARAM( "speed", FIELD_FLOAT ) - DEFINE_SCRIPTHOOK_PARAM( "point", FIELD_VECTOR ) - DEFINE_SCRIPTHOOK_PARAM( "normal", FIELD_VECTOR ) + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_FireBullets, "FireBullets", FIELD_VOID, "Called when the entity fires bullets from itself or from a weapon. The parameter is the associated FireBulletsInfo_t handle. Return false to cancel bullet firing." ) + DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT ) END_SCRIPTHOOK() - BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnDeath, "OnDeath", FIELD_BOOLEAN, "Called when the entity dies (Event_Killed). Returning false makes the entity cancel death, although this could have unforeseen consequences. For hooking any damage instead of just death, see filter_script and PassesFinalDamageFilter." ) + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnDeath, "OnDeath", FIELD_BOOLEAN, "Called when the entity dies (Event_Killed). Returning false makes the entity cancel death, although this could have unforeseen consequences. For hooking any damage instead of just death, use OnTakeDamage." ) + DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnTakeDamage, "OnTakeDamage", FIELD_BOOLEAN, "Called when the entity takes damage (OnTakeDamage). Returning false makes the entity cancel the damage, similar to a damage filter. This is called after any damage filter operations." ) DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT ) END_SCRIPTHOOK() @@ -2544,6 +2591,12 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_ModifySentenceParams, "ModifySentenceParams", FIELD_VOID, "Called every time a sentence is emitted on this entity, allowing for its parameters to be modified." ) DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT ) END_SCRIPTHOOK() + + DEFINE_SIMPLE_SCRIPTHOOK( CBaseEntity::g_Hook_ModifyOrAppendCriteria, "ModifyOrAppendCriteria", FIELD_HSCRIPT, "Called when the criteria set is collected for a response. Return a table of keyvalues to add to the criteria set." ) + + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_CanBeSeenBy, "CanBeSeenBy", FIELD_BOOLEAN, "Whether or not this entity can be seen by the specified NPC." ) + DEFINE_SCRIPTHOOK_PARAM( "npc", FIELD_HSCRIPT ) + END_SCRIPTHOOK() #endif END_SCRIPTDESC(); @@ -7544,6 +7597,61 @@ void CBaseEntity::ModifyOrAppendCriteria( AI_CriteriaSet& set ) set.AppendCriteria("spawnflags", UTIL_VarArgs("%i", GetSpawnFlags())); set.AppendCriteria("flags", UTIL_VarArgs("%i", GetFlags())); #endif + +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_ModifyOrAppendCriteria.CanRunInScope(m_ScriptScope)) + { + ScriptVariant_t functionReturn; + if (g_Hook_ModifyOrAppendCriteria.Call( m_ScriptScope, &functionReturn, NULL )) + { + if (functionReturn.m_hScript != NULL) + { + int nIterator = -1; + ScriptVariant_t varKey, varValue; + while ((nIterator = g_pScriptVM->GetKeyValue( functionReturn.m_hScript, nIterator, &varKey, &varValue )) != -1) + { + float flWeight = 1.0f; + char szValue[128]; + switch (varValue.m_type) + { + case FIELD_CSTRING: + { + char *colon = V_strstr( varValue.m_pszString, ":" ); + if (colon) + { + // Use as weight + flWeight = atof(colon+1); + *colon = NULL; + } + V_strncpy( szValue, varValue.m_pszString, sizeof( szValue ) ); + } + break; + case FIELD_BOOLEAN: + V_snprintf( szValue, sizeof( szValue ), "%d", varValue.m_bool ); + break; + case FIELD_INTEGER: + V_snprintf( szValue, sizeof( szValue ), "%i", varValue.m_int ); + break; + case FIELD_FLOAT: + V_snprintf( szValue, sizeof( szValue ), "%f", varValue.m_float ); + break; + case FIELD_VECTOR: + V_snprintf( szValue, sizeof( szValue ), "%f %f %f", varValue.m_pVector->x, varValue.m_pVector->y, varValue.m_pVector->z ); + break; + default: + Warning( "ModifyOrAppendCriteria doesn't know how to handle field %i for %s\n", varValue.m_type, varKey.m_pszString ); + break; + } + + set.AppendCriteria( varKey.m_pszString, szValue, flWeight ); + + g_pScriptVM->ReleaseValue( varKey ); + g_pScriptVM->ReleaseValue( varValue ); + } + } + } + } +#endif } //----------------------------------------------------------------------------- @@ -10154,7 +10262,7 @@ void CBaseEntity::SetScriptOwnerEntity(HSCRIPT pOwner) // ScriptFindKey, ScriptGetFirstSubKey, ScriptGetString, // ScriptGetInt, ScriptGetFloat, ScriptGetNextKey //----------------------------------------------------------------------------- -HSCRIPT CBaseEntity::ScriptGetModelKeyValues( void ) +HSCRIPT_RC CBaseEntity::ScriptGetModelKeyValues( void ) { KeyValues *pModelKeyValues = new KeyValues(""); HSCRIPT hScript = NULL; @@ -10163,16 +10271,12 @@ HSCRIPT CBaseEntity::ScriptGetModelKeyValues( void ) if ( pModelKeyValues->LoadFromBuffer( pszModelName, pBuffer ) ) { - // UNDONE: how does destructor get called on this #ifdef MAPBASE_VSCRIPT - m_pScriptModelKeyValues = hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pModelKeyValues, true ); // Allow VScript to delete this when the instance is removed. + hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pModelKeyValues ); #else + // UNDONE: how does destructor get called on this m_pScriptModelKeyValues = new CScriptKeyValues( pModelKeyValues ); -#endif - // UNDONE: who calls ReleaseInstance on this??? Does name need to be unique??? - -#ifndef MAPBASE_VSCRIPT hScript = g_pScriptVM->RegisterInstance( m_pScriptModelKeyValues ); #endif diff --git a/sp/src/game/server/baseentity.h b/sp/src/game/server/baseentity.h index e5899231..77b7b46d 100644 --- a/sp/src/game/server/baseentity.h +++ b/sp/src/game/server/baseentity.h @@ -1440,7 +1440,7 @@ public: virtual bool FVisible ( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ); virtual bool FVisible( const Vector &vecTarget, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ); - virtual bool CanBeSeenBy( CAI_BaseNPC *pNPC ) { return true; } // allows entities to be 'invisible' to NPC senses. + virtual bool CanBeSeenBy( CAI_BaseNPC *pNPC ); // allows entities to be 'invisible' to NPC senses. // This function returns a value that scales all damage done by this entity. // Use CDamageModifier to hook in damage modifiers on a guy. @@ -2114,7 +2114,7 @@ public: #endif const char* ScriptGetModelName(void) const; - HSCRIPT ScriptGetModelKeyValues(void); + HSCRIPT_RC ScriptGetModelKeyValues(void); void ScriptStopSound(const char* soundname); void ScriptEmitSound(const char* soundname); @@ -2177,10 +2177,13 @@ public: static ScriptHook_t g_Hook_VPhysicsCollision; static ScriptHook_t g_Hook_FireBullets; static ScriptHook_t g_Hook_OnDeath; + static ScriptHook_t g_Hook_OnTakeDamage; static ScriptHook_t g_Hook_OnKilledOther; static ScriptHook_t g_Hook_HandleInteraction; static ScriptHook_t g_Hook_ModifyEmitSoundParams; static ScriptHook_t g_Hook_ModifySentenceParams; + static ScriptHook_t g_Hook_ModifyOrAppendCriteria; + static ScriptHook_t g_Hook_CanBeSeenBy; #endif string_t m_iszVScripts; @@ -2188,9 +2191,7 @@ public: CScriptScope m_ScriptScope; HSCRIPT m_hScriptInstance; string_t m_iszScriptId; -#ifdef MAPBASE_VSCRIPT - HSCRIPT m_pScriptModelKeyValues; -#else +#ifndef MAPBASE_VSCRIPT CScriptKeyValues* m_pScriptModelKeyValues; #endif }; diff --git a/sp/src/game/server/baseflex.cpp b/sp/src/game/server/baseflex.cpp index 49f3c189..2d38d81d 100644 --- a/sp/src/game/server/baseflex.cpp +++ b/sp/src/game/server/baseflex.cpp @@ -816,7 +816,7 @@ bool CBaseFlex::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CCh textParams.g1 = 255; textParams.b1 = 255; - if ( GetGameTextSpeechParams( textParams ) ) + if ( DispatchGetGameTextSpeechParams( textParams ) ) { CRecipientFilter filter; filter.AddAllPlayers(); @@ -1654,6 +1654,12 @@ bool CBaseFlex::ProcessSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, C case CChoreoEvent::SPEAK: return true; +#ifdef MAPBASE + // Prevents "unknown type" console spam on players + case CChoreoEvent::GENERIC: + return true; +#endif + default: { Msg( "unknown type %d in ProcessSceneEvent()\n", event->GetType() ); @@ -2093,8 +2099,16 @@ float CBaseFlex::PlayScene( const char *pszScene, float flDelay, AI_Response *re #ifdef MAPBASE float CBaseFlex::PlayAutoGeneratedSoundScene( const char *soundname, float flDelay, AI_Response *response, IRecipientFilter *filter ) { + if (response && response->GetType() == ResponseRules::RESPONSE_CHOREOSENTENCE) + return InstancedChoreoSentenceScene( this, soundname, NULL, flDelay, false, response, false, filter ); + return InstancedAutoGeneratedSoundScene( this, soundname, NULL, flDelay, false, response, false, filter ); } + +float CBaseFlex::PlayChoreoSentenceScene( const char *pszSentence, float flDelay, AI_Response *response, IRecipientFilter *filter ) +{ + return InstancedChoreoSentenceScene( this, pszSentence, NULL, flDelay, false, response, false, filter ); +} #else float CBaseFlex::PlayAutoGeneratedSoundScene( const char *soundname ) { @@ -2106,11 +2120,17 @@ float CBaseFlex::PlayAutoGeneratedSoundScene( const char *soundname ) //----------------------------------------------------------------------------- // Purpose: Parameters for scene event AI_GameText //----------------------------------------------------------------------------- -bool CBaseFlex::GetGameTextSpeechParams( hudtextparms_t ¶ms ) +bool CBaseFlex::DispatchGetGameTextSpeechParams( hudtextparms_t ¶ms ) { + // First, get any default values overridden by this class + bool bReturn = GetGameTextSpeechParams( params ); + + // Allow VScript to override after ScriptVariant_t varTable; - if (g_pScriptVM->GetValue(m_ScriptScope, "m_GameTextSpeechParams", &varTable) && varTable.m_type == FIELD_HSCRIPT) + if (g_pScriptVM->GetValue( m_ScriptScope, "m_GameTextSpeechParams", &varTable ) && varTable.m_type == FIELD_HSCRIPT) { + bReturn = true; + int nIterator = -1; ScriptVariant_t varKey, varValue; while ((nIterator = g_pScriptVM->GetKeyValue( varTable.m_hScript, nIterator, &varKey, &varValue )) != -1) @@ -2147,13 +2167,18 @@ bool CBaseFlex::GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.fxTime = varValue.m_float; } + else if (FStrEq( varKey.m_pszString, "disabled" )) + { + // Allow the params to disable game_text choreo if needed + bReturn = !(varValue.m_int > 0); + } g_pScriptVM->ReleaseValue( varKey ); g_pScriptVM->ReleaseValue( varValue ); } } - return true; + return bReturn; } #endif diff --git a/sp/src/game/server/baseflex.h b/sp/src/game/server/baseflex.h index ec809534..dda54422 100644 --- a/sp/src/game/server/baseflex.h +++ b/sp/src/game/server/baseflex.h @@ -129,6 +129,7 @@ public: virtual float PlayScene( const char *pszScene, float flDelay = 0.0f, AI_Response *response = NULL, IRecipientFilter *filter = NULL ); #ifdef MAPBASE + virtual float PlayChoreoSentenceScene( const char *pszSentenceName, float flDelay = 0.0f, AI_Response *response = NULL, IRecipientFilter *filter = NULL ); virtual float PlayAutoGeneratedSoundScene( const char *soundname, float flDelay = 0.0f, AI_Response *response = NULL, IRecipientFilter *filter = NULL ); #else virtual float PlayAutoGeneratedSoundScene( const char *soundname ); @@ -141,7 +142,8 @@ public: virtual int GetSpecialDSP( void ) { return 0; } #ifdef MAPBASE - virtual bool GetGameTextSpeechParams( hudtextparms_t ¶ms ); + bool DispatchGetGameTextSpeechParams( hudtextparms_t ¶ms ); + virtual bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { return true; } #endif protected: diff --git a/sp/src/game/server/hl2/cbasehelicopter.cpp b/sp/src/game/server/hl2/cbasehelicopter.cpp index 2b46bce6..76f241bd 100644 --- a/sp/src/game/server/hl2/cbasehelicopter.cpp +++ b/sp/src/game/server/hl2/cbasehelicopter.cpp @@ -42,6 +42,10 @@ virtual void NullThink( void ); ConVar g_debug_basehelicopter( "g_debug_basehelicopter", "0", FCVAR_CHEAT ); +#ifdef MAPBASE +ConVar g_helicopter_use_sight_condition( "g_helicopter_use_sight_condition", "0", 0, "If enabled, helicopters will use the AI sight condition instead of just checking if there's a clear path to the player. This prevents them from cheating." ); +#endif + //--------------------------------------------------------- //--------------------------------------------------------- // TODOs @@ -702,7 +706,11 @@ void CBaseHelicopter::UpdateEnemy() { CBaseEntity *pEnemy = GetEnemy(); GatherEnemyConditions( pEnemy ); +#ifdef MAPBASE + if ( (g_helicopter_use_sight_condition.GetBool() && pEnemy->Classify() != CLASS_BULLSEYE) ? HasCondition( COND_SEE_ENEMY ) : FVisible( pEnemy ) ) +#else if ( FVisible( pEnemy ) ) +#endif { if (m_flLastSeen < gpGlobals->curtime - 2) { diff --git a/sp/src/game/server/hl2/env_speaker.cpp b/sp/src/game/server/hl2/env_speaker.cpp index ad46a25c..6413023f 100644 --- a/sp/src/game/server/hl2/env_speaker.cpp +++ b/sp/src/game/server/hl2/env_speaker.cpp @@ -297,6 +297,19 @@ void CSpeaker::DispatchResponse( const char *conceptName ) CAI_Expresser::RunScriptResponse( pTarget, response, &set, true ); break; } +#endif +#ifdef MAPBASE + case ResponseRules::RESPONSE_CHOREOSENTENCE: + { + CBaseFlex *pFlex = NULL; + if (pTarget != this) + { + // Attempt to get flex on the target + pFlex = dynamic_cast(pTarget); + } + InstancedAutoGeneratedSoundScene(pFlex, response); + break; + } #endif default: break; diff --git a/sp/src/game/server/hl2/hl2_player.cpp b/sp/src/game/server/hl2/hl2_player.cpp index fd0065b6..e9983bf7 100644 --- a/sp/src/game/server/hl2/hl2_player.cpp +++ b/sp/src/game/server/hl2/hl2_player.cpp @@ -122,6 +122,7 @@ ConVar player_autoswitch_enabled( "player_autoswitch_enabled", "1", FCVAR_NONE, #ifdef SP_ANIM_STATE ConVar hl2_use_sp_animstate( "hl2_use_sp_animstate", "1", FCVAR_NONE, "Allows SP HL2 players to use HL2:DM animations for custom player models. (changes may not apply until model is reloaded)" ); +ConVar player_process_scene_events( "player_process_scene_events", "1", FCVAR_NONE, "Allows players to process scene events." ); #endif #endif @@ -268,6 +269,7 @@ public: void InputSetHandModelBodyGroup( inputdata_t &inputdata ); void InputSetPlayerModel( inputdata_t &inputdata ); + void InputSetPlayerDrawLegs( inputdata_t &inputdata ); void InputSetPlayerDrawExternally( inputdata_t &inputdata ); #endif @@ -691,6 +693,7 @@ IMPLEMENT_SERVERCLASS_ST(CHL2_Player, DT_HL2_Player) #endif #ifdef SP_ANIM_STATE SendPropFloat( SENDINFO(m_flAnimRenderYaw), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flAnimRenderZ), 0, SPROP_NOSCALE ), #endif END_SEND_TABLE() @@ -1183,6 +1186,18 @@ void CHL2_Player::PostThink( void ) m_pPlayerAnimState->Update( angEyeAngles.y, angEyeAngles.x ); m_flAnimRenderYaw.Set( m_pPlayerAnimState->GetRenderAngles().y ); + + if (m_pPlayerAnimState->IsJumping() && !m_pPlayerAnimState->IsDuckJumping()) + { + m_flAnimRenderZ.Set( -(GetViewOffset().z) ); + } + else + m_flAnimRenderZ.Set( 0.0f ); + + if (player_process_scene_events.GetBool()) + { + ProcessSceneEvents(); + } } #endif } @@ -1505,6 +1520,7 @@ CStudioHdr *CHL2_Player::OnNewModel() } extern char g_szDefaultPlayerModel[MAX_PATH]; +extern bool g_bDefaultPlayerLegs; extern bool g_bDefaultPlayerDrawExternally; extern char g_szDefaultProtagonist[MAX_PROTAGONIST_NAME]; @@ -1539,6 +1555,7 @@ void CHL2_Player::Spawn(void) RemoveEffects( EF_NODRAW ); } + SetDrawPlayerLegs( g_bDefaultPlayerLegs ); SetDrawPlayerModelExternally( g_bDefaultPlayerDrawExternally ); if (m_iszProtagonistName == NULL_STRING && *g_szDefaultProtagonist) @@ -3376,6 +3393,11 @@ void CHL2_Player::Weapon_Equip( CBaseCombatWeapon *pWeapon ) if( GetActiveWeapon() == NULL ) { m_HL2Local.m_bWeaponLowered = false; + +#ifdef SP_ANIM_STATE + if (m_pPlayerAnimState) + m_pPlayerAnimState->StopWeaponRelax(); +#endif } BaseClass::Weapon_Equip( pWeapon ); @@ -3781,6 +3803,11 @@ bool CHL2_Player::Weapon_Lower( void ) m_HL2Local.m_bWeaponLowered = true; +#ifdef SP_ANIM_STATE + if (m_pPlayerAnimState) + m_pPlayerAnimState->StartWeaponRelax(); +#endif + CBaseCombatWeapon *pWeapon = dynamic_cast(GetActiveWeapon()); if ( pWeapon == NULL ) @@ -3803,6 +3830,11 @@ bool CHL2_Player::Weapon_Ready( void ) m_HL2Local.m_bWeaponLowered = false; +#ifdef SP_ANIM_STATE + if (m_pPlayerAnimState) + m_pPlayerAnimState->StopWeaponRelax(); +#endif + CBaseCombatWeapon *pWeapon = dynamic_cast(GetActiveWeapon()); if ( pWeapon == NULL ) @@ -4016,6 +4048,21 @@ void CHL2_Player::OnRestore() { BaseClass::OnRestore(); m_pPlayerAISquad = g_AI_SquadManager.FindCreateSquad(AllocPooledString(PLAYER_SQUADNAME)); + +#ifdef SP_ANIM_STATE + if ( m_pPlayerAnimState == NULL ) + { + if ( GetModelPtr() && GetModelPtr()->HaveSequenceForActivity(ACT_HL2MP_IDLE) && hl2_use_sp_animstate.GetBool() ) + { + // Here we create and init the player animation state. + m_pPlayerAnimState = CreatePlayerAnimationState(this); + } + else + { + m_flAnimRenderYaw = FLT_MAX; + } + } +#endif } //--------------------------------------------------------- @@ -4763,6 +4810,7 @@ BEGIN_DATADESC( CLogicPlayerProxy ) DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHandModelSkin", InputSetHandModelSkin ), DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHandModelBodyGroup", InputSetHandModelBodyGroup ), DEFINE_INPUTFUNC( FIELD_STRING, "SetPlayerModel", InputSetPlayerModel ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetPlayerDrawLegs", InputSetPlayerDrawLegs ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetPlayerDrawExternally", InputSetPlayerDrawExternally ), DEFINE_INPUT( m_MaxArmor, FIELD_INTEGER, "SetMaxInputArmor" ), DEFINE_INPUT( m_SuitZoomFOV, FIELD_INTEGER, "SetSuitZoomFOV" ), @@ -5241,6 +5289,15 @@ void CLogicPlayerProxy::InputSetPlayerModel( inputdata_t &inputdata ) m_hPlayer->SetModel( STRING(iszModel) ); } +void CLogicPlayerProxy::InputSetPlayerDrawLegs( inputdata_t &inputdata ) +{ + if (!m_hPlayer) + return; + + CBasePlayer *pPlayer = static_cast(m_hPlayer.Get()); + pPlayer->SetDrawPlayerLegs( inputdata.value.Bool() ); +} + void CLogicPlayerProxy::InputSetPlayerDrawExternally( inputdata_t &inputdata ) { if (!m_hPlayer) diff --git a/sp/src/game/server/hl2/hl2_player.h b/sp/src/game/server/hl2/hl2_player.h index 6389e107..f388b896 100644 --- a/sp/src/game/server/hl2/hl2_player.h +++ b/sp/src/game/server/hl2/hl2_player.h @@ -19,7 +19,7 @@ #if defined ( HL2MP ) #include "basemultiplayerplayer.h" #elif defined ( MAPBASE ) -#include "mapbase/singleplayer_animstate.h" +#include "mapbase/mapbase_playeranimstate.h" #endif class CAI_Squad; @@ -458,11 +458,12 @@ private: #endif #ifdef SP_ANIM_STATE - CSinglePlayerAnimState* m_pPlayerAnimState; + CMapbasePlayerAnimState* m_pPlayerAnimState; // At the moment, we network the render angles since almost none of the player anim stuff is done on the client in SP. // If any of this is ever adapted for MP, this method should be replaced with replicating/moving the anim state to the client. CNetworkVar( float, m_flAnimRenderYaw ); + CNetworkVar( float, m_flAnimRenderZ ); #endif }; diff --git a/sp/src/game/server/hl2/item_battery.cpp b/sp/src/game/server/hl2/item_battery.cpp index d5c8b416..fe174c04 100644 --- a/sp/src/game/server/hl2/item_battery.cpp +++ b/sp/src/game/server/hl2/item_battery.cpp @@ -63,3 +63,88 @@ BEGIN_DATADESC( CItemBattery ) END_DATADESC() #endif +#ifdef MAPBASE +extern ConVar sk_battery; + +//----------------------------------------------------------------------------- +// Custom player battery. Heals the player when picked up. +//----------------------------------------------------------------------------- +class CItemBatteryCustom : public CItem +{ +public: + DECLARE_CLASS( CItemBatteryCustom, CItem ); + CItemBatteryCustom(); + + void Spawn( void ); + void Precache( void ); + bool MyTouch( CBasePlayer *pPlayer ); + + float GetItemAmount() { return m_flPowerAmount; } + + void InputSetPowerAmount( inputdata_t &inputdata ) { m_flPowerAmount = inputdata.value.Float(); } + + float m_flPowerAmount; + string_t m_iszTouchSound; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( item_battery_custom, CItemBatteryCustom ); + +#ifdef MAPBASE +BEGIN_DATADESC( CItemBatteryCustom ) + + DEFINE_KEYFIELD( m_flPowerAmount, FIELD_FLOAT, "PowerAmount" ), + DEFINE_KEYFIELD( m_iszTouchSound, FIELD_STRING, "TouchSound" ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPowerAmount", InputSetPowerAmount ), + +END_DATADESC() +#endif + + +CItemBatteryCustom::CItemBatteryCustom() +{ + SetModelName( AllocPooledString( "models/items/battery.mdl" ) ); + m_flPowerAmount = sk_battery.GetFloat(); + m_iszTouchSound = AllocPooledString( "ItemBattery.Touch" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemBatteryCustom::Spawn( void ) +{ + Precache(); + SetModel( STRING( GetModelName() ) ); + + BaseClass::Spawn(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemBatteryCustom::Precache( void ) +{ + PrecacheModel( STRING( GetModelName() ) ); + + PrecacheScriptSound( STRING( m_iszTouchSound ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +// Output : +//----------------------------------------------------------------------------- +bool CItemBatteryCustom::MyTouch( CBasePlayer *pPlayer ) +{ + CHL2_Player *pHL2Player = assert_cast(pPlayer); + if (!pHL2Player || sk_battery.GetFloat() == 0.0f) + return false; + + float flPowerMult = m_flPowerAmount / sk_battery.GetFloat(); + return pHL2Player->ApplyBattery( flPowerMult ); +} +#endif diff --git a/sp/src/game/server/hl2/npc_attackchopper.cpp b/sp/src/game/server/hl2/npc_attackchopper.cpp index ac6efb1c..266cc468 100644 --- a/sp/src/game/server/hl2/npc_attackchopper.cpp +++ b/sp/src/game/server/hl2/npc_attackchopper.cpp @@ -44,6 +44,7 @@ #ifdef MAPBASE #include "filters.h" +#include "ai_hint.h" #endif // memdbgon must be the last include file in a .cpp file!!! @@ -164,6 +165,15 @@ ConVar g_helicopter_bullrush_mega_bomb_health( "g_helicopter_bullrush_mega_bomb_ ConVar g_helicopter_bomb_danger_radius( "g_helicopter_bomb_danger_radius", "120" ); +#ifdef MAPBASE +ConVar g_helicopter_crashpoint_nearest( "g_helicopter_crashpoint_nearest", "1", 0, "Selects the nearest crash point instead of just the first in the entity list." ); + +#ifdef HL2_EPISODIC +ConVar g_helicopter_phys_follow_while_crashing( "g_helicopter_phys_follow_while_crashing", "0", 0, "Allows the phys_bone_followers to follow the helicopter while flying to crash point" ); +#endif + +#endif + Activity ACT_HELICOPTER_DROP_BOMB; Activity ACT_HELICOPTER_CRASHING; @@ -191,7 +201,60 @@ enum #define GRENADE_HELICOPTER_MODEL "models/combine_helicopter/helicopter_bomb01.mdl" +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTargetHelicopterCrash : public CPointEntity +{ + DECLARE_CLASS( CTargetHelicopterCrash, CPointEntity ); +public: + DECLARE_DATADESC(); + + void InputEnable( inputdata_t &inputdata ) + { + m_bDisabled = false; + } + void InputDisable( inputdata_t &inputdata ) + { + m_bDisabled = true; + } + bool IsDisabled( void ) + { + return m_bDisabled; + } + void HelicopterCrashedOnTarget( CBaseHelicopter *pChopper ) + { + m_OnCrashed.FireOutput( pChopper, this ); + } + void HelicopterAcquiredCrashTarget( CBaseHelicopter *pChopper ) + { + m_OnBeginCrash.FireOutput( pChopper, this ); + } + +private: + bool m_bDisabled; + + COutputEvent m_OnCrashed; + COutputEvent m_OnBeginCrash; +}; + +LINK_ENTITY_TO_CLASS( info_target_helicopter_crash, CTargetHelicopterCrash ); + +BEGIN_DATADESC( CTargetHelicopterCrash ) + DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + // Outputs + DEFINE_OUTPUT( m_OnCrashed, "OnCrashed" ), + DEFINE_OUTPUT( m_OnBeginCrash, "OnBeginCrash" ), +END_DATADESC() +#else LINK_ENTITY_TO_CLASS( info_target_helicopter_crash, CPointEntity ); +#endif //------------------------------------------------------------------------------ @@ -499,6 +562,13 @@ private: // Death, etc. void InputSelfDestruct( inputdata_t &inputdata ); +#ifdef MAPBASE + // This is identical to SelfDestruct, except the helicopter won't throw out chunks while flying to a crash point. + // This input is meant to be used when the pilot is killed and there's nothing wrong with the helicopter itself. + // If there are no crash points, the helicopter will explode in place as normal. + void InputSelfDestructNoFX( inputdata_t &inputdata ); +#endif + // Enemy visibility check CBaseEntity *FindTrackBlocker( const Vector &vecViewPoint, const Vector &vecTargetPos ); @@ -522,6 +592,7 @@ private: // Various states of the helicopter firing... bool PoseGunTowardTargetDirection( const Vector &vTargetDir ); + float GetGunPoseSpeed() const; // Compute the position to fire at (vehicle + non-vehicle case) void ComputeFireAtPosition( Vector *pVecActualTargetPosition ); @@ -533,6 +604,17 @@ private: bool DoGunFiring( const Vector &vBasePos, const Vector &vGunDir, const Vector &vecFireAtPosition ); void FireElectricityGun( ); +#ifdef MAPBASE + // Idle aiming + bool FindIdleAimTarget(); + bool ValidIdleAimTarget( CBaseEntity *pAimTarget ); + + bool FValidateHintType( CAI_Hint *pHint ); + + inline CBaseEntity *GetIdleAimTarget() const { return m_hIdleAimTarget; } + inline void SetIdleAimTarget( CBaseEntity *pNewAimTarget ) { m_hIdleAimTarget = pNewAimTarget; } +#endif + // Chooses a point within the circle of death to fire in void PickDirectionToCircleOfDeath( const Vector &vBasePos, const Vector &vecFireAtPosition, Vector *pResult ); @@ -650,6 +732,7 @@ private: void SpotlightStartup(); void SpotlightShutdown(); + CBaseEntity *FindCrashPoint(); CBaseEntity *GetCrashPoint() { return m_hCrashPoint.Get(); } private: @@ -721,6 +804,17 @@ private: bool m_bBombsExplodeOnContact; bool m_bNonCombat; +#ifdef MAPBASE + bool m_bIdleAimAround; + EHANDLE m_hIdleAimTarget; + Vector m_vecIdleAimDir; + float m_flNextIdleAimTime; + + bool m_bDisableSmokeTrails; + bool m_bDisableCorpses; + bool m_bDisableExplosions; +#endif + int m_nNearShots; int m_nMaxNearShots; @@ -860,6 +954,15 @@ BEGIN_DATADESC( CNPC_AttackHelicopter ) #ifdef MAPBASE DEFINE_KEYFIELD( m_flFieldOfView, FIELD_FLOAT, "FieldOfView" ), + + DEFINE_KEYFIELD( m_bIdleAimAround, FIELD_BOOLEAN, "IdleAimAround" ), + DEFINE_FIELD( m_hIdleAimTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_vecIdleAimDir, FIELD_VECTOR ), + DEFINE_FIELD( m_flNextIdleAimTime, FIELD_TIME ), + + DEFINE_KEYFIELD( m_bDisableSmokeTrails, FIELD_BOOLEAN, "DisableSmokeTrails" ), + DEFINE_KEYFIELD( m_bDisableCorpses, FIELD_BOOLEAN, "DisableCorpses" ), + DEFINE_KEYFIELD( m_bDisableExplosions, FIELD_BOOLEAN, "DisableExplosions" ), #endif DEFINE_FIELD( m_hCrashPoint, FIELD_EHANDLE ), @@ -896,6 +999,9 @@ BEGIN_DATADESC( CNPC_AttackHelicopter ) DEFINE_INPUTFUNC( FIELD_VOID, "EnablePathVisibilityTests", InputEnablePathVisibilityTests ), DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestruct", InputSelfDestruct ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestructNoFX", InputSelfDestructNoFX ), +#endif DEFINE_THINKFUNC( BlinkLightsThink ), DEFINE_THINKFUNC( SpotlightThink ), @@ -1646,6 +1752,21 @@ void CNPC_AttackHelicopter::InputSelfDestruct( inputdata_t &inputdata ) TakeDamage( info ); } +#ifdef MAPBASE +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void CNPC_AttackHelicopter::InputSelfDestructNoFX( inputdata_t &inputdata ) +{ + m_bDisableSmokeTrails = true; + m_bDisableCorpses = true; + m_bDisableExplosions = true; + + m_lifeState = LIFE_ALIVE; // Force to die properly. + CTakeDamageInfo info( this, this, Vector(0, 0, 1), WorldSpaceCenter(), GetMaxHealth(), CLASS_MISSILE ); + TakeDamage( info ); +} +#endif + //----------------------------------------------------------------------------- // For scripted times where it *has* to shoot //----------------------------------------------------------------------------- @@ -2245,21 +2366,23 @@ bool CNPC_AttackHelicopter::PoseGunTowardTargetDirection( const Vector &vTargetD } } + float flPoseSpeed = GetGunPoseSpeed(); + if (angles.x > m_angGun.x) { - m_angGun.x = MIN( angles.x, m_angGun.x + 12 ); + m_angGun.x = MIN( angles.x, m_angGun.x + flPoseSpeed ); } if (angles.x < m_angGun.x) { - m_angGun.x = MAX( angles.x, m_angGun.x - 12 ); + m_angGun.x = MAX( angles.x, m_angGun.x - flPoseSpeed ); } if (angles.y > m_angGun.y) { - m_angGun.y = MIN( angles.y, m_angGun.y + 12 ); + m_angGun.y = MIN( angles.y, m_angGun.y + flPoseSpeed ); } if (angles.y < m_angGun.y) { - m_angGun.y = MAX( angles.y, m_angGun.y - 12 ); + m_angGun.y = MAX( angles.y, m_angGun.y - flPoseSpeed ); } SetPoseParameter( m_poseWeapon_Pitch, -m_angGun.x ); @@ -2269,6 +2392,20 @@ bool CNPC_AttackHelicopter::PoseGunTowardTargetDirection( const Vector &vTargetD } +//------------------------------------------------------------------------------ +// Various states of the helicopter firing... +//------------------------------------------------------------------------------ +float CNPC_AttackHelicopter::GetGunPoseSpeed() const +{ +#ifdef MAPBASE + if (m_bIdleAimAround && !GetEnemy()) + return 4.0f; +#endif + + return 12.0f; +} + + //------------------------------------------------------------------------------ // Compute the enemy position (non-vehicle case) //------------------------------------------------------------------------------ @@ -2363,6 +2500,87 @@ bool CNPC_AttackHelicopter::DoGunIdle( const Vector &vGunDir, const Vector &vTar } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Finds an idle aim target. Based on CNPC_PlayerCompanion code +//----------------------------------------------------------------------------- +bool CNPC_AttackHelicopter::FindIdleAimTarget() +{ + CAI_Hint *pHint; + CHintCriteria hintCriteria; + CBaseEntity *pPriorAimTarget = GetIdleAimTarget(); + + hintCriteria.SetHintType( HINT_WORLD_VISUALLY_INTERESTING ); + hintCriteria.SetFlag( bits_HINT_NODE_VISIBLE | bits_HINT_NODE_IN_VIEWCONE | bits_HINT_NPC_IN_NODE_FOV | bits_HINT_NODE_USE_GROUP | bits_HINT_NODE_NEAREST ); + hintCriteria.AddIncludePosition( GetAbsOrigin(), CHOPPER_MAX_GUN_DIST ); + pHint = CAI_HintManager::FindHint( this, hintCriteria ); + + if( pHint ) + { + if ( !ValidIdleAimTarget( pHint ) ) + { + return false; + } + + if ( pHint != pPriorAimTarget ) + { + // Notify of the change. + SetIdleAimTarget( pHint ); + return true; + } + } + + // Didn't find an aim target, or found the same one. + return false; +} + +//----------------------------------------------------------------------------- +// Finds an idle aim target. Based on CNPC_PlayerCompanion code +//----------------------------------------------------------------------------- +bool CNPC_AttackHelicopter::ValidIdleAimTarget( CBaseEntity *pAimTarget ) +{ + Vector vecTargetOrigin = pAimTarget->GetAbsOrigin(); + + Vector vBasePos; + GetAttachment( m_nGunBaseAttachment, vBasePos ); + + if ( vecTargetOrigin.z > vBasePos.z || (vecTargetOrigin - vBasePos).Length() < 128.0f ) + { + // Too close! + return false; + } + + trace_t tr; + UTIL_TraceLine( vBasePos, vecTargetOrigin, MASK_BLOCKLOS, this, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction < 1.0f && tr.m_pEnt != pAimTarget ) + { + // No LOS + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CNPC_AttackHelicopter::FValidateHintType( CAI_Hint *pHint ) +{ + switch( pHint->HintType() ) + { + case HINT_WORLD_VISUALLY_INTERESTING: + return true; + break; + + default: + break; + } + + return BaseClass::FValidateHintType( pHint ); +} +#endif + + //------------------------------------------------------------------------------ // How easy is the target to hit? //------------------------------------------------------------------------------ @@ -3653,12 +3871,20 @@ int CNPC_AttackHelicopter::OnTakeDamage_Alive( const CTakeDamageInfo &info ) } } +#ifdef MAPBASE + if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_SMOKE_TRAILS ) && !m_bDisableSmokeTrails ) +#else if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_SMOKE_TRAILS ) ) +#endif { AddSmokeTrail( info.GetDamagePosition() ); } +#ifdef MAPBASE + if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_CORPSES ) && !m_bDisableCorpses ) +#else if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_CORPSES ) ) +#endif { if ( nPrevHealth != GetMaxHealth() ) { @@ -3666,7 +3892,11 @@ int CNPC_AttackHelicopter::OnTakeDamage_Alive( const CTakeDamageInfo &info ) } } +#ifdef MAPBASE + if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_EXPLOSIONS ) && !m_bDisableExplosions ) +#else if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_EXPLOSIONS ) ) +#endif { ExplodeAndThrowChunk( info.GetDamagePosition() ); } @@ -3774,6 +4004,46 @@ void Chopper_BecomeChunks( CBaseEntity *pChopper ) pBodyChunk->m_pCockpitConstraint = physenv->CreateFixedConstraint( pBodyObject, pCockpitObject, pGroup, fixed ); } +//----------------------------------------------------------------------------- +// Purpose: Find a valid crash point +//----------------------------------------------------------------------------- +CBaseEntity *CNPC_AttackHelicopter::FindCrashPoint() +{ +#ifdef MAPBASE + float flNearest = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH; + CTargetHelicopterCrash *pNearest = NULL; + CBaseEntity *pEnt = NULL; + while( (pEnt = gEntList.FindEntityByClassname(pEnt, "info_target_helicopter_crash")) != NULL ) + { + CTargetHelicopterCrash *pCrashTarget = assert_cast(pEnt); + if ( pCrashTarget->IsDisabled() ) + continue; + + if (g_helicopter_crashpoint_nearest.GetBool()) + { + float flDist = ( pEnt->WorldSpaceCenter() - WorldSpaceCenter() ).LengthSqr(); + if( flDist < flNearest ) + { + pNearest = pCrashTarget; + flNearest = flDist; + } + } + else + { + pNearest = pCrashTarget; + break; + } + } + + if (pNearest) + pNearest->HelicopterAcquiredCrashTarget( this ); + + return pNearest; +#else + return gEntList.FindEntityByClassname( NULL, "info_target_helicopter_crash" ); +#endif +} + //----------------------------------------------------------------------------- // Purpose: Start us crashing //----------------------------------------------------------------------------- @@ -3791,7 +4061,7 @@ void CNPC_AttackHelicopter::Event_Killed( const CTakeDamageInfo &info ) if( GetCrashPoint() == NULL ) { - CBaseEntity *pCrashPoint = gEntList.FindEntityByClassname( NULL, "info_target_helicopter_crash" ); + CBaseEntity *pCrashPoint = FindCrashPoint(); if( pCrashPoint != NULL ) { m_hCrashPoint.Set( pCrashPoint ); @@ -3812,6 +4082,12 @@ void CNPC_AttackHelicopter::Event_Killed( const CTakeDamageInfo &info ) return; } } +#ifdef MAPBASE + else + { + assert_cast( GetCrashPoint() )->HelicopterCrashedOnTarget( this ); + } +#endif Chopper_BecomeChunks( this ); StopLoopingSounds(); @@ -3869,7 +4145,11 @@ void CNPC_AttackHelicopter::PrescheduleThink( void ) SetDesiredPosition( GetCrashPoint()->WorldSpaceCenter() ); } +#ifdef MAPBASE + if ( !m_bDisableExplosions && random->RandomInt( 0, 4 ) == 0 ) +#else if ( random->RandomInt( 0, 4 ) == 0 ) +#endif { Vector explodePoint; CollisionProp()->RandomPointInBounds( Vector(0.25,0.25,0.25), Vector(0.75,0.75,0.75), &explodePoint ); @@ -4821,6 +5101,15 @@ void CNPC_AttackHelicopter::Hunt( void ) { Flight(); UpdatePlayerDopplerShift( ); + +#if defined(MAPBASE) && defined(HL2_EPISODIC) + if (g_helicopter_phys_follow_while_crashing.GetBool()) + { + // Update our bone followers + m_BoneFollowerManager.UpdateBoneFollowers( this ); + } +#endif // HL2_EPISODIC + return; } @@ -4885,6 +5174,55 @@ void CNPC_AttackHelicopter::Hunt( void ) #endif } +#ifdef MAPBASE + if (m_bIdleAimAround) + { + if (!GetEnemy()) + { + if (GetIdleAimTarget()) + { + if (!ValidIdleAimTarget( GetIdleAimTarget() )) + { + SetIdleAimTarget( NULL ); + m_flNextIdleAimTime = gpGlobals->curtime; + } + } + + if (m_flNextIdleAimTime < gpGlobals->curtime) + { + if (!FindIdleAimTarget()) + { + SetIdleAimTarget( NULL ); + + // Find a random direction in front of us instead + Vector vBasePos, vecForward, vecRight, vecUp; + GetAttachment( m_nGunBaseAttachment, vBasePos, &vecForward, &vecRight, &vecUp ); + + m_vecIdleAimDir = vecForward + (vecRight * RandomFloat( -0.25f, 0.25f )) + (vecUp * RandomFloat( -0.8f, -0.25f )); + } + + m_flNextIdleAimTime = gpGlobals->curtime + RandomFloat( 4.0f, 6.0f ); + } + + if (GetIdleAimTarget()) + { + // Get gun attachment points + Vector vBasePos; + GetAttachment( m_nGunBaseAttachment, vBasePos ); + + m_vecIdleAimDir = GetIdleAimTarget()->GetAbsOrigin() - vBasePos; + VectorNormalize( m_vecIdleAimDir ); + } + + PoseGunTowardTargetDirection( m_vecIdleAimDir ); + } + else if (GetIdleAimTarget()) + { + SetIdleAimTarget( NULL ); + } + } +#endif + #ifdef HL2_EPISODIC // Update our bone followers m_BoneFollowerManager.UpdateBoneFollowers(this); diff --git a/sp/src/game/server/hl2/npc_combine.cpp b/sp/src/game/server/hl2/npc_combine.cpp index 93116b4d..c1375eb7 100644 --- a/sp/src/game/server/hl2/npc_combine.cpp +++ b/sp/src/game/server/hl2/npc_combine.cpp @@ -2551,6 +2551,24 @@ int CNPC_Combine::TranslateSchedule( int scheduleType ) } } +#ifdef MAPBASE + extern ConVar ai_enemy_memory_fixes; + + // SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE uses TASK_GET_PATH_TO_ENEMY_LKP_LOS, a task with a mistake + // detailed in CAI_BaseNPC::StartTask and fixed by ai_enemy_memory_fixes. + // + // However, SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE only stops being used once the NPC has LOS to its target. + // Since the fixed task now uses the enemy's last known position instead of the enemy's actual position, + // this schedule risks getting stuck in a loop. + // + // This code makes the soldier run up directly to the last known position if it's visible, allowing the AI + // to mark the enemy as eluded. + if ( ai_enemy_memory_fixes.GetBool() && FVisible( GetEnemyLKP() ) ) + { + return SCHED_COMBINE_PRESS_ATTACK; + } +#endif + return SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE; } break; diff --git a/sp/src/game/server/hl2/npc_combine.h b/sp/src/game/server/hl2/npc_combine.h index d6a500b9..c19b4ba4 100644 --- a/sp/src/game/server/hl2/npc_combine.h +++ b/sp/src/game/server/hl2/npc_combine.h @@ -69,6 +69,7 @@ public: int RangeAttack2Conditions( float flDot, float flDist ); // For innate grenade attack int MeleeAttack1Conditions( float flDot, float flDist ); // For kick/punch bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ); + bool FVisible( const Vector &vecTarget, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ) { return BaseClass::FVisible( vecTarget, traceMask, ppBlocker ); } virtual bool IsCurTaskContinuousMove(); virtual float GetJumpGravity() const { return 1.8f; } diff --git a/sp/src/game/server/hl2/npc_combinedropship.cpp b/sp/src/game/server/hl2/npc_combinedropship.cpp index bc6067f2..557756e7 100644 --- a/sp/src/game/server/hl2/npc_combinedropship.cpp +++ b/sp/src/game/server/hl2/npc_combinedropship.cpp @@ -134,6 +134,11 @@ enum LandingState_t LANDING_HOVER_DESCEND, LANDING_HOVER_TOUCHDOWN, LANDING_END_HOVER, + +#ifdef MAPBASE + // Strider dropoff + LANDING_STRIDER, +#endif }; @@ -295,6 +300,9 @@ private: bool IsHovering(); void UpdateGroundRotorWashSound( float flAltitude ); void UpdateRotorWashVolume( CSoundPatch *pRotorSound, float flVolume, float flDeltaTime ); +#ifdef MAPBASE + void DeathNotice(CBaseEntity* pVictim); +#endif private: // Timers @@ -353,6 +361,12 @@ private: string_t m_sRollermineTemplate; string_t m_sRollermineTemplateData; +#ifdef MAPBASE + // Template for strider carried by this dropship + string_t m_sStriderTemplate; + string_t m_sStriderTemplateData; +#endif + // Cached attachment points int m_iMuzzleAttachment; int m_iMachineGunBaseAttachment; @@ -827,6 +841,11 @@ BEGIN_DATADESC( CNPC_CombineDropship ) DEFINE_KEYFIELD( m_sRollermineTemplate, FIELD_STRING, "RollermineTemplate" ), DEFINE_FIELD( m_sRollermineTemplateData, FIELD_STRING ), +#ifdef MAPBASE + DEFINE_KEYFIELD(m_sStriderTemplate, FIELD_STRING, "StriderTemplate"), + DEFINE_FIELD(m_sStriderTemplateData, FIELD_STRING), +#endif + DEFINE_ARRAY( m_sNPCTemplateData, FIELD_STRING, DROPSHIP_MAX_SOLDIERS ), DEFINE_KEYFIELD( m_sNPCTemplate[0], FIELD_STRING, "NPCTemplate" ), DEFINE_KEYFIELD( m_sNPCTemplate[1], FIELD_STRING, "NPCTemplate2" ), @@ -962,12 +981,60 @@ void CNPC_CombineDropship::Spawn( void ) break; case CRATE_STRIDER: +#ifdef MAPBASE + if ( m_sStriderTemplate != NULL_STRING ) + { + m_sStriderTemplateData = Templates_FindByTargetName( STRING( m_sStriderTemplate ) ); + if (m_sStriderTemplateData == NULL_STRING) + { + Warning("npc_combinedropship %s: Strider Template %s not found!\n", STRING(GetEntityName()), STRING(m_sStriderTemplate)); + break; + } + + CAI_BaseNPC* pent = NULL; + CBaseEntity* pEntity = NULL; + MapEntity_ParseEntity( pEntity, STRING( m_sStriderTemplateData ), NULL); + if ( pEntity != NULL ) + { + pent = ( CAI_BaseNPC* )pEntity; + } + + if ( !FClassnameIs(pent, "npc_strider")) + { + Warning("npc_combinedropship %s: Strider Template %s is not a strider!\n", STRING(GetEntityName()), STRING(m_sStriderTemplate)); + break; + } + + m_OnSpawnNPC.Set( pEntity, pEntity, this ); + + pent->RemoveSpawnFlags( SF_NPC_TEMPLATE ); + + pent->SetOwnerEntity( this ); + + m_hContainer = pent; + m_hContainer->SetAbsOrigin( GetAbsOrigin() - Vector(0, 0, 100) ); + m_hContainer->SetAbsAngles( GetAbsAngles() ); + m_hContainer->SetParent( this, 0 ); + m_hContainer->SetOwnerEntity( this ); + DispatchSpawn(pent); + } + else + { + m_hContainer = ( CBaseAnimating* )CreateEntityByName("npc_strider"); + m_hContainer->SetAbsOrigin( GetAbsOrigin() - Vector(0, 0, 100) ); + m_hContainer->SetAbsAngles( GetAbsAngles() ); + m_hContainer->SetParent( this, 0 ); + m_hContainer->SetOwnerEntity( this ); + m_hContainer->Spawn(); + } +#else m_hContainer = (CBaseAnimating*)CreateEntityByName( "npc_strider" ); m_hContainer->SetAbsOrigin( GetAbsOrigin() - Vector( 0, 0 , 100 ) ); m_hContainer->SetAbsAngles( GetAbsAngles() ); m_hContainer->SetParent(this, 0); m_hContainer->SetOwnerEntity(this); m_hContainer->Spawn(); +#endif m_hContainer->SetAbsOrigin( GetAbsOrigin() - Vector( 0, 0 , 100 ) ); #ifdef MAPBASE m_OnSpawnNPC.Set( m_hContainer, m_hContainer, this ); @@ -1201,13 +1268,24 @@ void CNPC_CombineDropship::Precache( void ) void CNPC_CombineDropship::Flight( void ) { // Only run the flight model in some flight states +#ifdef MAPBASE bool bRunFlight = ( GetLandingState() == LANDING_NO || GetLandingState() == LANDING_LEVEL_OUT || GetLandingState() == LANDING_LIFTOFF || GetLandingState() == LANDING_SWOOPING || GetLandingState() == LANDING_DESCEND || GetLandingState() == LANDING_HOVER_LEVEL_OUT || - GetLandingState() == LANDING_HOVER_DESCEND ); + GetLandingState() == LANDING_HOVER_DESCEND || + GetLandingState() == LANDING_STRIDER ); +#else + bool bRunFlight = (GetLandingState() == LANDING_NO || + GetLandingState() == LANDING_LEVEL_OUT || + GetLandingState() == LANDING_LIFTOFF || + GetLandingState() == LANDING_SWOOPING || + GetLandingState() == LANDING_DESCEND || + GetLandingState() == LANDING_HOVER_LEVEL_OUT || + GetLandingState() == LANDING_HOVER_DESCEND ); +#endif Vector forward, right, up; GetVectors( &forward, &right, &up ); @@ -1429,8 +1507,14 @@ void CNPC_CombineDropship::Flight( void ) } // If we're landing, deliberately tuck in the back end +#ifdef MAPBASE if ( GetLandingState() == LANDING_DESCEND || GetLandingState() == LANDING_TOUCHDOWN || + GetLandingState() == LANDING_UNLOADING || GetLandingState() == LANDING_UNLOADED || + GetLandingState() == LANDING_STRIDER || IsHovering() ) +#else + if ( GetLandingState() == LANDING_DESCEND || GetLandingState() == LANDING_TOUCHDOWN || GetLandingState() == LANDING_UNLOADING || GetLandingState() == LANDING_UNLOADED || IsHovering() ) +#endif { finspeed = -60; } @@ -1779,6 +1863,16 @@ void CNPC_CombineDropship::InputDropStrider( inputdata_t &inputdata ) return; } +#ifdef MAPBASE + if (m_iszLandTarget != NULL_STRING) + { + LandCommon(); + } + else + { + SetLandingState(LANDING_STRIDER); + } +#else QAngle angles = GetAbsAngles(); angles.x = 0.0; @@ -1790,6 +1884,7 @@ void CNPC_CombineDropship::InputDropStrider( inputdata_t &inputdata ) m_hContainer->SetAbsVelocity( vec3_origin ); m_hContainer = NULL; +#endif } //----------------------------------------------------------------------------- @@ -2016,7 +2111,11 @@ void CNPC_CombineDropship::SetLandingState( LandingState_t landingState ) if ( m_pDescendingWarningSound ) { CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); +#ifdef MAPBASE + if ( ( landingState == LANDING_DESCEND ) || ( landingState == LANDING_TOUCHDOWN ) || ( landingState == LANDING_UNLOADING ) || ( landingState == LANDING_UNLOADED ) || ( landingState == LANDING_HOVER_DESCEND ) || ( landingState == LANDING_STRIDER ) ) +#else if ( ( landingState == LANDING_DESCEND ) || ( landingState == LANDING_TOUCHDOWN ) || ( landingState == LANDING_UNLOADING ) || ( landingState == LANDING_UNLOADED ) || ( landingState == LANDING_HOVER_DESCEND ) ) +#endif { controller.SoundChangeVolume( m_pDescendingWarningSound, m_bSuppressSound ? 0.0f : 1.0f, 0.3f ); } @@ -2112,8 +2211,15 @@ void CNPC_CombineDropship::PrescheduleThink( void ) if ( flDistance < 70 && flSpeed < 100 ) { m_flLandingSpeed = flSpeed; - - if( IsHovering() ) +#ifdef MAPBASE + if ( m_iCrateType == CRATE_STRIDER ) + { + SetLandingState( LANDING_STRIDER ); + } + else if( IsHovering() ) +#else + if ( IsHovering() ) +#endif { SetLandingState( LANDING_HOVER_DESCEND ); } @@ -2315,6 +2421,98 @@ void CNPC_CombineDropship::PrescheduleThink( void ) } break; +#ifdef MAPBASE + case LANDING_STRIDER: + { + if (!m_hContainer) + { + // Strider died, get out of here + SetLandingState(LANDING_LIFTOFF); + return; + } + + // Orient myself to the desired direction + bool bStillOrienting = false; + Vector targetDir; + if (m_hLandTarget) + { + // We've got a land target, so match it's orientation + AngleVectors(m_hLandTarget->GetAbsAngles(), &targetDir); + } + else + { + // No land target. + targetDir = GetDesiredPosition() - GetAbsOrigin(); + } + + // Don't unload until we're facing the way the dropoff point specifies + float flTargetYaw = UTIL_VecToYaw(targetDir); + float flDeltaYaw = UTIL_AngleDiff(flTargetYaw, GetAbsAngles().y); + if (fabs(flDeltaYaw) > 5) + { + bStillOrienting = true; + } + + // Ensure we land on the drop point. Stop dropping if we're still turning. + Vector vecToTarget = (GetDesiredPosition() - GetAbsOrigin()); + float flDistance = vecToTarget.Length(); + float flRampedSpeed = m_flLandingSpeed * (flDistance / 70); + Vector vecVelocity = (flRampedSpeed / flDistance) * vecToTarget; + +#define STRIDER_LANDING_HEIGHT 540.0f + + float flFactor = MIN(1.0, MAX(0.1f, (flAltitude - STRIDER_LANDING_HEIGHT) / flAltitude)); + float flDescendVelocity = MIN(-75, MAX_LAND_VEL * flFactor); + + vecVelocity.z = flDescendVelocity; + + SetAbsVelocity(vecVelocity); + + if (flAltitude < 600) + { + QAngle angles = GetLocalAngles(); + + // Level out quickly. + angles.x = UTIL_Approach(0.0, angles.x, 0.2); + angles.z = UTIL_Approach(0.0, angles.z, 0.2); + + SetLocalAngles(angles); + } + else + { + // randomly move as if buffeted by ground effects + // gently flatten ship from starting pitch/yaw + m_existPitch = UTIL_Approach(0.0, m_existPitch, 1); + m_existRoll = UTIL_Approach(0.0, m_existRoll, 1); + + QAngle angles = GetLocalAngles(); + angles.x = m_existPitch + (sin(gpGlobals->curtime * 3.5f) * DROPSHIP_MAX_LAND_TILT); + angles.z = m_existRoll + (sin(gpGlobals->curtime * 3.75f) * DROPSHIP_MAX_LAND_TILT); + SetLocalAngles(angles); + } + + DoRotorWash(); + + if (!bStillOrienting && flAltitude < STRIDER_LANDING_HEIGHT) + { + QAngle angles = GetAbsAngles(); + + m_hContainer->SetParent(NULL, 0); + m_hContainer->SetOwnerEntity(NULL); + m_hContainer->SetAbsAngles(angles); + m_hContainer->SetAbsVelocity(vec3_origin); + + m_hContainer = NULL; + + m_flTimeTakeOff = gpGlobals->curtime + 3.5f; + SetLandingState(LANDING_UNLOADING); + + return; + } + } + break; +#endif + case LANDING_UNLOADING: { // If we've got no specified takeoff time, we're still waiting for troops to exit. Idle. @@ -2817,7 +3015,11 @@ void CNPC_CombineDropship::UpdatePickupNavigation( void ) void CNPC_CombineDropship::UpdateLandTargetNavigation( void ) { Vector vecPickup = m_hLandTarget->WorldSpaceCenter(); +#ifdef MAPBASE + vecPickup.z += ( m_iCrateType == CRATE_STRIDER ) ? 732 : 256; +#else vecPickup.z += 256; +#endif SetDesiredPosition( vecPickup ); //NDebugOverlay::Cross3D( GetDesiredPosition(), -Vector(32,32,32), Vector(32,32,32), 0, 255, 255, true, 0.1f ); @@ -2850,7 +3052,11 @@ void CNPC_CombineDropship::Hunt( void ) // Face our desired position. m_vecDesiredFaceDir = desiredDir; +#ifdef MAPBASE + if ( GetLandingState() == LANDING_DESCEND || GetLandingState() == LANDING_LEVEL_OUT || GetLandingState() == LANDING_STRIDER || IsHovering() ) +#else if ( GetLandingState() == LANDING_DESCEND || GetLandingState() == LANDING_LEVEL_OUT || IsHovering() ) +#endif { if ( m_hLandTarget ) { @@ -3150,6 +3356,19 @@ void CNPC_CombineDropship::MakeTracer( const Vector &vecTracerSrc, const trace_t } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Need a way to tell if our strider died +//----------------------------------------------------------------------------- +void CNPC_CombineDropship::DeathNotice( CBaseEntity *pVictim ) +{ + if ( m_iCrateType == CRATE_STRIDER && GetLandingState() == LANDING_NO ) + { + m_OnContainerShotDownBeforeDropoff.Set( 1, m_hContainer, this ); + } +} +#endif + AI_BEGIN_CUSTOM_NPC( npc_combinedropship, CNPC_CombineDropship ) DECLARE_ACTIVITY( ACT_DROPSHIP_FLY_IDLE ); diff --git a/sp/src/game/server/hl2/npc_combinegunship.cpp b/sp/src/game/server/hl2/npc_combinegunship.cpp index dfe0aa20..49e7183c 100644 --- a/sp/src/game/server/hl2/npc_combinegunship.cpp +++ b/sp/src/game/server/hl2/npc_combinegunship.cpp @@ -187,15 +187,29 @@ public: { return m_bDisabled; } +#ifdef MAPBASE + void GunshipCrashedOnTarget( CBaseHelicopter *pGunship ) + { + m_OnCrashed.FireOutput( pGunship, this ); + } + void GunshipAcquiredCrashTarget( CBaseHelicopter *pGunship ) + { + m_OnBeginCrash.FireOutput( pGunship, this ); + } +#else void GunshipCrashedOnTarget( void ) { m_OnCrashed.FireOutput( this, this ); } +#endif private: bool m_bDisabled; COutputEvent m_OnCrashed; +#ifdef MAPBASE + COutputEvent m_OnBeginCrash; +#endif }; LINK_ENTITY_TO_CLASS( info_target_gunshipcrash, CTargetGunshipCrash ); @@ -209,6 +223,9 @@ BEGIN_DATADESC( CTargetGunshipCrash ) // Outputs DEFINE_OUTPUT( m_OnCrashed, "OnCrashed" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnBeginCrash, "OnBeginCrash" ), +#endif END_DATADESC() @@ -1564,7 +1581,11 @@ void CNPC_CombineGunship::PrescheduleThink( void ) { BeginDestruct(); m_OnCrashed.FireOutput( this, this ); +#ifdef MAPBASE + m_hCrashTarget->GunshipCrashedOnTarget( this ); +#else m_hCrashTarget->GunshipCrashedOnTarget(); +#endif return; } } @@ -1976,6 +1997,10 @@ bool CNPC_CombineGunship::FindNearestGunshipCrash( void ) m_flNextGunshipCrashFind = gpGlobals->curtime + 0.5; m_flEndDestructTime = 0; +#ifdef MAPBASE + m_hCrashTarget->GunshipAcquiredCrashTarget( this ); +#endif + if ( g_debug_gunship.GetInt() ) { NDebugOverlay::Line(GetAbsOrigin(), m_hCrashTarget->GetAbsOrigin(), 0,255,0, true, 0.5); diff --git a/sp/src/game/server/hl2/npc_strider.cpp b/sp/src/game/server/hl2/npc_strider.cpp index a4289fdb..6fea7efb 100644 --- a/sp/src/game/server/hl2/npc_strider.cpp +++ b/sp/src/game/server/hl2/npc_strider.cpp @@ -1327,6 +1327,15 @@ void CNPC_Strider::BuildScheduleTestBits() //--------------------------------------------------------- int CNPC_Strider::SelectSchedule() { +#ifdef MAPBASE + if( GetMoveType() == MOVETYPE_NONE ) + { + // Dropship just released me. + AddFlag(FL_FLY); + SetMoveType( MOVETYPE_STEP ); + return SCHED_STRIDER_FALL_TO_GROUND; + } +#endif /* if( GetMoveType() != MOVETYPE_FLY ) { @@ -1511,6 +1520,26 @@ int CNPC_Strider::TranslateSchedule( int scheduleType ) return SCHED_COMBAT_PATROL; } } + else + { +#ifdef MAPBASE + extern ConVar ai_enemy_memory_fixes; + + // Striders convert TASK_GET_PATH_TO_ENEMY_LOS to TASK_GET_PATH_TO_ENEMY_LKP_LOS, a task which incorrectly + // acts identically to the former. This is detailed in CAI_BaseNPC::StartTask and fixed by ai_enemy_memory_fixes. + // + // However, SCHED_ESTABLISH_LINE_OF_FIRE only stops being used once the NPC has LOS to its target. + // Since the fixed task now uses the enemy's last known position instead of the enemy's actual position, + // this schedule risks getting stuck in a loop. + // + // This code chains back up to SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK, which is what's supposed to happen when a + // strider is eluded in this way. + if ( ai_enemy_memory_fixes.GetBool() && FVisible( GetEnemyLKP() ) ) + { + return TranslateSchedule( SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK ); + } +#endif + } break; @@ -5818,6 +5847,18 @@ AI_BEGIN_CUSTOM_NPC( npc_strider, CNPC_Strider ) " COND_STRIDER_SHOULD_STAND" ) +#ifdef MAPBASE + DEFINE_SCHEDULE + ( + SCHED_STRIDER_FALL_TO_GROUND, + + " Tasks " + " TASK_SOUND_WAKE 0" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_STRIDER_DEPLOY" + "" + " Interrupts " + ) +#else DEFINE_SCHEDULE ( SCHED_STRIDER_FALL_TO_GROUND, @@ -5827,7 +5868,7 @@ AI_BEGIN_CUSTOM_NPC( npc_strider, CNPC_Strider ) "" " Interrupts " ) - +#endif AI_END_CUSTOM_NPC() diff --git a/sp/src/game/server/hl2/npc_strider.h b/sp/src/game/server/hl2/npc_strider.h index d5bdd209..54c24b55 100644 --- a/sp/src/game/server/hl2/npc_strider.h +++ b/sp/src/game/server/hl2/npc_strider.h @@ -188,6 +188,7 @@ public: bool HasPass() { return m_PlayerFreePass.HasPass(); } bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ); + bool FVisible( const Vector &vecTarget, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ) { return BaseClass::FVisible( vecTarget, traceMask, ppBlocker ); } Vector BodyTarget( const Vector &posSrc, bool bNoisy ); bool IsValidEnemy( CBaseEntity *pTarget ); diff --git a/sp/src/game/server/hl2/proto_sniper.cpp b/sp/src/game/server/hl2/proto_sniper.cpp index 4315e35c..cd7c309e 100644 --- a/sp/src/game/server/hl2/proto_sniper.cpp +++ b/sp/src/game/server/hl2/proto_sniper.cpp @@ -2645,16 +2645,19 @@ Vector CProtoSniper::DesiredBodyTarget( CBaseEntity *pTarget ) { // By default, aim for the center Vector vecTarget = pTarget->WorldSpaceCenter(); + const Vector vecBulletOrigin = GetBulletOrigin(); #ifdef MAPBASE_VSCRIPT - if (m_ScriptScope.IsInitialized() && g_Hook_GetActualShootPosition.CanRunInScope(m_ScriptScope)) + if ( m_ScriptScope.IsInitialized() && g_Hook_GetActualShootPosition.CanRunInScope( m_ScriptScope ) ) { ScriptVariant_t functionReturn; - ScriptVariant_t args[] = { GetBulletOrigin(), ToHScript( pTarget ) }; - if (g_Hook_GetActualShootPosition.Call( m_ScriptScope, &functionReturn, args )) + ScriptVariant_t args[] = { vecBulletOrigin, ToHScript( pTarget ) }; + if ( g_Hook_GetActualShootPosition.Call( m_ScriptScope, &functionReturn, args ) ) { - if (functionReturn.m_type == FIELD_VECTOR && functionReturn.m_pVector->LengthSqr() != 0.0f) + if ( functionReturn.m_type == FIELD_VECTOR && functionReturn.m_pVector->LengthSqr() != 0.0f ) + { return *functionReturn.m_pVector; + } } } #endif @@ -2682,12 +2685,12 @@ Vector CProtoSniper::DesiredBodyTarget( CBaseEntity *pTarget ) { if( flTimeSinceLastMiss > 0.0f && flTimeSinceLastMiss < 4.0f && hl2_episodic.GetBool() ) { - vecTarget = pTarget->BodyTarget( GetBulletOrigin(), false ); + vecTarget = pTarget->BodyTarget( vecBulletOrigin, false ); } else { // Shoot zombies in the headcrab - vecTarget = pTarget->HeadTarget( GetBulletOrigin() ); + vecTarget = pTarget->HeadTarget( vecBulletOrigin ); } } else if( pTarget->Classify() == CLASS_ANTLION ) diff --git a/sp/src/game/server/mapbase/choreosentence.cpp b/sp/src/game/server/mapbase/choreosentence.cpp new file mode 100644 index 00000000..9edca7e7 --- /dev/null +++ b/sp/src/game/server/mapbase/choreosentence.cpp @@ -0,0 +1,985 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: VCD-based sentences. +// +//=============================================================================// + +#include "cbase.h" +#include "utlhashtable.h" +#include "engine/IEngineSound.h" +#include "soundchars.h" +#include "choreosentence.h" +#include "mapbase/matchers.h" +#include "ai_speech_new.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//---------------------------------------------------------------------------- + +#define IsVirtualWord(pszWord) (pszWord[0] == 'V' && pszWord[1] == '_') +#define RemoveVirtualPrefix(pszWord) IsVirtualWord(pszWord) ? pszWord + 2 : pszWord + +//---------------------------------------------------------------------------- + +class CChoreoSentenceSystem : public CAutoGameSystem +{ +public: + + struct VirtualWord_t; + struct VirtualWordDefinition_t; + struct VirtualWordDefinitionList_t; + struct SentenceContextPrerequisite_t; + + //---------------------------------------------------------------------------- + + CChoreoSentenceSystem() : m_Strings( 256, 0, &StringLessThan ) { } + ~CChoreoSentenceSystem() { PurgeSentences(); } + + bool Init() + { + // Load the default choreo sentences files + LoadChoreoSentenceManifest( "scripts/choreosentences_manifest.txt" ); + return true; + } + + void Shutdown() + { + PurgeSentences(); + } + + //---------------------------------------------------------------------------- + + void LoadChoreoSentenceManifest( const char *pszFile ) + { + KeyValues *pManifest = new KeyValues( "ChoreoSentencesManifest" ); + if (pManifest->LoadFromFile( filesystem, pszFile )) + { + FOR_EACH_SUBKEY( pManifest, pSubKey ) + { + LoadChoreoSentenceFile( pSubKey->GetString() ); + } + } + pManifest->deleteThis(); + + //m_Strings.Compact( true ); + } + + void LoadChoreoSentenceFile( const char *pszFile ) + { + KeyValues *pManifest = new KeyValues( "ChoreoSentences" ); + if (pManifest->LoadFromFile( filesystem, pszFile )) + { + //---------------------------------------------------------------------------- + // Context Prerequisites + //---------------------------------------------------------------------------- + KeyValues *pSentenceContextPrerequisites = pManifest->FindKey( "SentenceContextPrerequisites" ); + FOR_EACH_SUBKEY( pSentenceContextPrerequisites, pSubKey ) + { + KeyValues *pCondition = pSubKey->GetFirstSubKey(); + if (pCondition) + { + // + // Only add if there is a subkey for condition + // + int i = m_ContextPrerequisites.AddToTail(); + m_ContextPrerequisites[i].nConditionType = DeduceCondition( pCondition->GetName(), V_strlen( pCondition->GetName() ) ); + + m_ContextPrerequisites[i].pszContextName = AllocateString( pSubKey->GetName() ); + m_ContextPrerequisites[i].pszCondition = AllocateString( pCondition->GetString() ); + } + } + + //---------------------------------------------------------------------------- + // Virtual Words + //---------------------------------------------------------------------------- + KeyValues *pVirtualWords = pManifest->FindKey( "VirtualWords" ); + FOR_EACH_SUBKEY( pVirtualWords, pSubKey ) + { + // + // Determine if this has nested virtual words or not + // + KeyValues *pMatches = pSubKey->FindKey( "matches" ); + if (pMatches) + { + // + // It has nested virtual words + // + int i = m_VirtualWordLists.Insert( RemoveVirtualPrefix( pSubKey->GetName() ) ); + m_VirtualWordLists[i].pszCriterion = AllocateString( pSubKey->GetString( "criterion" ) ); + + bool bFirst = true; + FOR_EACH_SUBKEY( pMatches, pWordList ) + { + if (bFirst) + { + // First one is always default + if (LoadVirtualWordDefinitionFromKV( pWordList, m_VirtualWordLists[i].m_DefaultWordDefinition, pSubKey->GetName() )) + bFirst = false; + } + else + { + int i2 = m_VirtualWordLists[i].m_WordDefinitions.Insert( pWordList->GetName() ); + if (!LoadVirtualWordDefinitionFromKV( pWordList, m_VirtualWordLists[i].m_WordDefinitions[i2], pSubKey->GetName() )) + m_VirtualWordLists[i].m_WordDefinitions.RemoveAt( i2 ); + } + } + } + else + { + // + // It is a virtual word + // + int i = m_VirtualWords.Insert( RemoveVirtualPrefix( pSubKey->GetName() ) ); + if (!LoadVirtualWordDefinitionFromKV( pSubKey, m_VirtualWords[i], pSubKey->GetName() )) + m_VirtualWords.RemoveAt( i ); + } + } + + //---------------------------------------------------------------------------- + // Sentences + //---------------------------------------------------------------------------- + KeyValues *pSentences = pManifest->FindKey( "Sentences" ); + FOR_EACH_SUBKEY( pSentences, pSetKey ) + { + FOR_EACH_SUBKEY( pSetKey, pSubKey ) + { + if (pSubKey->GetName() && *pSubKey->GetString()) + { + int i = m_Sentences.Insert( pSubKey->GetName() ); + + char szPathName[MAX_PATH]; + V_strncpy( szPathName, pSetKey->GetName(), sizeof( szPathName ) ); + V_FixSlashes( szPathName ); + m_Sentences[i].pszPrefix = AllocateString( szPathName ); + + //m_Sentences[i].pszPrefix = AllocateString( pSetKey->GetName() ); + + ParseChoreoSentence( NULL, pSubKey->GetString(), m_Sentences[i] ); + m_Sentences[i].pszName = m_Sentences.GetElementName( i ); + } + } + } + } + pManifest->deleteThis(); + } + + bool LoadVirtualWordDefinitionFromKV( KeyValues *pWordList, VirtualWordDefinition_t &wordDefinition, const char *pszWordName ) + { + // + // Get the condition for this virtual word + // + KeyValues *pCondition = pWordList->FindKey( "condition" ); + if (pCondition) + { + int nStrLen = V_strlen( pCondition->GetString() ); + const char *pColon = V_strnchr( pCondition->GetString(), ':', nStrLen ); + if (pColon) + { + wordDefinition.pszCondition = AllocateString( pColon + 1 ); + wordDefinition.nConditionType = DeduceCondition( pCondition->GetString(), pColon - pCondition->GetString() ); + } + else + { + wordDefinition.nConditionType = DeduceCondition( pCondition->GetString(), nStrLen ); + } + } + else + { + wordDefinition.nConditionType = 0; + wordDefinition.pszCondition = ""; + } + + // + // Get the words themselves + // + KeyValues *pWords = pWordList->FindKey( "words" ); + if (pWords) + { + FOR_EACH_SUBKEY( pWords, pWord ) + { + int i = wordDefinition.m_Words.AddToTail(); + V_strncpy( wordDefinition.m_Words[i].szWord, pWord->GetString(), sizeof( wordDefinition.m_Words[i].szWord ) ); + } + } + else + { + pWords = pWordList->FindKey( "words_from" ); + if (pWords) + { + const char *pszWordsTarget = RemoveVirtualPrefix( pWords->GetString() ); + + // + // Get from another virtual word + // + FOR_EACH_DICT_FAST( m_VirtualWords, i ) + { + if (FStrEq( m_VirtualWords.GetElementName( i ), pszWordsTarget )) + { + wordDefinition.m_Words.AddVectorToTail( m_VirtualWords[i].m_Words ); + } + } + } + } + + if (wordDefinition.m_Words.Count() <= 0) + { + Warning( "WARNING: No words found on virtual word %s\n", pszWordName ); + return false; + } + + return true; + } + + //---------------------------------------------------------------------------- + + void PurgeSentences() + { + m_ContextPrerequisites.RemoveAll(); + m_VirtualWordLists.RemoveAll(); + m_VirtualWords.RemoveAll(); + m_Sentences.RemoveAll(); + + for (unsigned int i = 0; i < m_Strings.Count(); i++) + { + delete m_Strings[i]; + } + m_Strings.Purge(); + } + + //---------------------------------------------------------------------------- + + void PrintEverything() + { + Msg( "CONTEXT PREREQUISITES\n\n" ); + + FOR_EACH_VEC( m_ContextPrerequisites, i ) + { + Msg( "\t\"%s\"\n\t\tCondition: %i (\"%s\")\n\n", m_ContextPrerequisites[i].pszContextName, m_ContextPrerequisites[i].nConditionType, m_ContextPrerequisites[i].pszCondition ); + } + + Msg( "VIRTUAL WORDS\n\n" ); + + FOR_EACH_DICT_FAST( m_VirtualWords, i ) + { + char szWords[256] = { 0 }; + FOR_EACH_VEC( m_VirtualWords[i].m_Words, nWord ) + { + V_strncat( szWords, "\t\t\t\"", sizeof( szWords ) ); + V_strncat( szWords, m_VirtualWords[i].m_Words[nWord].szWord, sizeof( szWords ) ); + V_strncat( szWords, "\"\n", sizeof( szWords ) ); + } + + Msg( "\t\"%s\"\n\t\tCondition: %i (\"%s\")\n\t\tWords:\n%s\n\n", m_VirtualWords.GetElementName( i ), m_VirtualWords[i].nConditionType, m_VirtualWords[i].pszCondition, szWords ); + } + + Msg( "SENTENCES\n\n" ); + + FOR_EACH_DICT_FAST( m_Sentences, i ) + { + char szWords[128] = { 0 }; + FOR_EACH_VEC( m_Sentences[i].m_Words, nWord ) + { + if (m_Sentences[i].m_Words[nWord].nPitch != 100) + { + V_snprintf( szWords, sizeof( szWords ), "%s(p%i) ", szWords, m_Sentences[i].m_Words[nWord].nPitch ); + } + + if (m_Sentences[i].m_Words[nWord].nVol != 100) + { + V_snprintf( szWords, sizeof( szWords ), "%s(v%i) ", szWords, m_Sentences[i].m_Words[nWord].nVol ); + } + + V_strncat( szWords, "\"", sizeof( szWords ) ); + V_strncat( szWords, m_Sentences[i].m_Words[nWord].pszWord, sizeof( szWords ) ); + V_strncat( szWords, "\" ", sizeof( szWords ) ); + } + + Msg( "\t\"%s\"\n\t\tPrefix: \"%s\"\n\t\tCaption: \"%s\"\n\t\tWords: %s\n\n", m_Sentences.GetElementName( i ), m_Sentences[i].pszPrefix, m_Sentences[i].pszCaption ? m_Sentences[i].pszCaption : "N/A", szWords); + } + } + + void PrintStrings() + { + CUtlVector strings( 0, m_Strings.Count() ); + for ( UtlHashHandle_t i = m_Strings.FirstInorder(); i != m_Strings.InvalidIndex(); i = m_Strings.NextInorder(i) ) + { + strings.AddToTail( m_Strings[i] ); + } + + struct _Local { + static int __cdecl F(const char * const *a, const char * const *b) { return strcmp(*a, *b); } + }; + strings.Sort( _Local::F ); + + for ( int i = 0; i < strings.Count(); ++i ) + { + Msg( " %d (0x%p) : %s\n", i, strings[i], strings[i] ); + } + Msg( "\n" ); + Msg( "Size: %d items\n", strings.Count() ); + } + + //---------------------------------------------------------------------------- + + const ChoreoSentence_t *LookupChoreoSentence( CBaseEntity *pSpeaker, const char *pszSentenceName ) + { + FOR_EACH_DICT_FAST( m_Sentences, i ) + { + if (FStrEq( m_Sentences.GetElementName( i ), pszSentenceName )) + { + return &m_Sentences[i]; + } + } + + return NULL; + } + + //---------------------------------------------------------------------------- + + void PrecacheVirtualWord( const char *pszWord, const char *pszPrefix, const char *pszSuffix ) + { + pszWord = RemoveVirtualPrefix( pszWord ); + + FOR_EACH_DICT_FAST( m_VirtualWords, i ) + { + if (FStrEq( m_VirtualWords.GetElementName( i ), pszWord )) + { + // Precache all words + FOR_EACH_VEC( m_VirtualWords[i].m_Words, i2 ) + { + CBaseEntity::PrecacheScriptSound( UTIL_VarArgs( "%s%s%s", pszPrefix, m_VirtualWords[i].m_Words[i2].szWord, pszSuffix ) ); + } + return; + } + } + + FOR_EACH_DICT_FAST( m_VirtualWordLists, i ) + { + if (FStrEq( m_VirtualWordLists.GetElementName( i ), pszWord )) + { + // Precache all words in default definition + FOR_EACH_VEC( m_VirtualWordLists[i].m_DefaultWordDefinition.m_Words, i2 ) + { + CBaseEntity::PrecacheScriptSound( UTIL_VarArgs( "%s%s%s", pszPrefix, m_VirtualWordLists[i].m_DefaultWordDefinition.m_Words[i2].szWord, pszSuffix)); + } + + // Precache all words in nested definitions + FOR_EACH_DICT( m_VirtualWordLists[i].m_WordDefinitions, i2 ) + { + FOR_EACH_VEC( m_VirtualWordLists[i].m_WordDefinitions[i2].m_Words, i3 ) + { + CBaseEntity::PrecacheScriptSound( UTIL_VarArgs( "%s%s%s", pszPrefix, m_VirtualWordLists[i].m_WordDefinitions[i2].m_Words[i3].szWord, pszSuffix ) ); + } + } + return; + } + } + } + + inline const VirtualWord_t &ResolveVirtualWordDefinition( CBaseEntity *pSpeaker, const VirtualWordDefinition_t &wordDefinition ) + { + // Resolve this condition + int nIndex = ResolveVirtualCondition( pSpeaker, wordDefinition.nConditionType, wordDefinition.pszCondition, wordDefinition.m_Words.Count() ); + Assert( nIndex >= 0 && nIndex < wordDefinition.m_Words.Count() ); + + return wordDefinition.m_Words[nIndex]; + } + + const char *ResolveVirtualWord( CBaseEntity *pSpeaker, const char *pszWord ) + { + pszWord = RemoveVirtualPrefix( pszWord ); + + FOR_EACH_DICT_FAST( m_VirtualWords, i ) + { + if (FStrEq( m_VirtualWords.GetElementName( i ), pszWord )) + { + return ResolveVirtualWordDefinition( pSpeaker, m_VirtualWords[i] ).szWord; + } + } + + FOR_EACH_DICT_FAST( m_VirtualWordLists, i ) + { + if (FStrEq( m_VirtualWordLists.GetElementName( i ), pszWord )) + { + AI_CriteriaSet set; + if (pSpeaker) + pSpeaker->ModifyOrAppendCriteria( set ); + + int nCriterion = set.FindCriterionIndex( m_VirtualWordLists[i].pszCriterion ); + if (set.IsValidIndex( nCriterion )) + { + const char *pszValue = set.GetValue( nCriterion ); + + // Find the set of virtual words that matches the criterion + FOR_EACH_DICT( m_VirtualWordLists[i].m_WordDefinitions, i2 ) + { + if (Matcher_Match( m_VirtualWordLists[i].m_WordDefinitions.GetElementName(i2), pszValue )) + { + return ResolveVirtualWordDefinition( pSpeaker, m_VirtualWordLists[i].m_WordDefinitions[i2] ).szWord; + } + } + + // Return the default + return ResolveVirtualWordDefinition( pSpeaker, m_VirtualWordLists[i].m_DefaultWordDefinition ).szWord; + } + } + } + + CGWarning( 0, CON_GROUP_CHOREO, "Choreo sentence can't find virtual word \"%s\"\n", pszWord ); + return ""; + } + + int ResolveVirtualCondition( CBaseEntity *pSpeaker, int nConditionType, const char *pszCondition, int nWordCount ) + { + switch (nConditionType) + { + default: + case ConditionType_None: + // Randomize between each word + return RandomInt( 0, nWordCount - 1 ); + break; + + case ConditionType_Context: + // Return context as integer + if (pSpeaker) + { + if (pSpeaker->FindContextByName( pszCondition ) == -1) + { + // Check if this is a prerequisite, and if it is, then apply it + FOR_EACH_VEC( m_ContextPrerequisites, i ) + { + if (FStrEq( m_ContextPrerequisites[i].pszContextName, pszCondition )) + { + pSpeaker->AddContext( pszCondition, UTIL_VarArgs("%i", ResolveVirtualCondition(pSpeaker, m_ContextPrerequisites[i].nConditionType, m_ContextPrerequisites[i].pszCondition, nWordCount))); + } + } + } + + int nContext = clamp( atoi( pSpeaker->GetContextValue( pszCondition ) ), 0, nWordCount - 1 ); + return nContext; + } + break; + + case ConditionType_VScript: + { + // Return VScript code result + g_pScriptVM->SetValue( "_choreo_val", "" ); + + HSCRIPT hCreateChainScript = g_pScriptVM->CompileScript( UTIL_VarArgs( "_choreo_val = (%s)", pszCondition ) ); + g_pScriptVM->Run( hCreateChainScript, pSpeaker ? pSpeaker->GetOrCreatePrivateScriptScope() : NULL ); + + ScriptVariant_t scriptVar; + g_pScriptVM->GetValue( "_choreo_val", &scriptVar ); + + g_pScriptVM->ClearValue( "_choreo_val" ); + + if (scriptVar.m_int < 0 || scriptVar.m_int >= nWordCount) + { + Warning("Choreo sentence script var %i from '%s' out of range\n", scriptVar.m_int, pszCondition ); + scriptVar.m_int = 0; + } + + return scriptVar.m_int; + } + break; + + case ConditionType_DistTo: + case ConditionType_DirTo: + case ConditionType_GridX: + case ConditionType_GridY: + { + CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, pszCondition, pSpeaker ); + if (pSpeaker && pTarget) + { + if (nConditionType == ConditionType_DistTo) + { + // Convert to meters + return (pSpeaker->GetAbsOrigin() - pTarget->GetAbsOrigin()).Length() / 52.49344; + } + else if (nConditionType == ConditionType_DirTo) + { + Vector vecDir = (pSpeaker->GetAbsOrigin() - pTarget->GetAbsOrigin()); + return vecDir.y; + } + else if (nConditionType == ConditionType_GridX) + { + return (int)(pTarget->GetAbsOrigin().x / 10.0f) % 20; + } + else if (nConditionType == ConditionType_GridY) + { + return (int)(pTarget->GetAbsOrigin().y / 10.0f) % 20; + } + } + } + break; + } + + return 0; + } + + //---------------------------------------------------------------------------- + + enum + { + ConditionType_None, // No condition (random) + ConditionType_Context, // Word should match value from specified context + ConditionType_VScript, // Word should match value returned from specified VScript code + + // HL2 sentence parameters + ConditionType_DistTo, // Word should match distance to specified target + ConditionType_DirTo, // Word should match direction to specified target + ConditionType_GridX, // Word should match world X axis grid of specified target + ConditionType_GridY, // Word should match world Y axis grid of specified target + }; + + int DeduceCondition( const char *pszCondition, int nNumChars ) + { + if (V_strncmp( pszCondition, "context", nNumChars ) == 0) + return ConditionType_Context; + else if (V_strncmp( pszCondition, "vscript", nNumChars ) == 0) + return ConditionType_VScript; + else if (V_strncmp( pszCondition, "dist_to", nNumChars ) == 0) + return ConditionType_DistTo; + else if (V_strncmp( pszCondition, "dir_to", nNumChars ) == 0) + return ConditionType_DirTo; + else if (V_strncmp( pszCondition, "gridx", nNumChars ) == 0) + return ConditionType_GridX; + else if (V_strncmp( pszCondition, "gridy", nNumChars ) == 0) + return ConditionType_GridY; + + return ConditionType_None; + } + + struct VirtualWord_t + { + char szWord[MAX_CHOREO_SENTENCE_VIRTUAL_WORD_LEN]; + }; + + struct VirtualWordDefinition_t + { + VirtualWordDefinition_t() {} + VirtualWordDefinition_t( const VirtualWordDefinition_t &src ) + { + pszCondition = src.pszCondition; + nConditionType = src.nConditionType; + m_Words.RemoveAll(); + m_Words.AddVectorToTail( src.m_Words ); + } + + const char *pszCondition; + int nConditionType; + + CUtlVector< VirtualWord_t > m_Words; + }; + + struct VirtualWordDefinitionList_t + { + VirtualWordDefinitionList_t() {} + VirtualWordDefinitionList_t( const VirtualWordDefinitionList_t &src ) + { + pszCriterion = src.pszCriterion; + m_DefaultWordDefinition = src.m_DefaultWordDefinition; + + m_WordDefinitions.RemoveAll(); + m_WordDefinitions.EnsureCapacity( src.m_WordDefinitions.Count() ); + FOR_EACH_DICT_FAST( src.m_WordDefinitions, i ) + m_WordDefinitions.Insert( src.m_WordDefinitions.GetElementName( i ), src.m_WordDefinitions[i] ); + } + + const char *pszCriterion; + + VirtualWordDefinition_t m_DefaultWordDefinition; + CUtlDict< VirtualWordDefinition_t, int > m_WordDefinitions; + }; + + struct SentenceContextPrerequisite_t + { + SentenceContextPrerequisite_t() {} + SentenceContextPrerequisite_t( const SentenceContextPrerequisite_t &src ) + { + pszContextName = src.pszContextName; + pszCondition = src.pszCondition; + nConditionType = src.nConditionType; + } + + const char *pszContextName; + const char *pszCondition; + int nConditionType; + }; + + //---------------------------------------------------------------------------- + + const char *FindString( const char *string ) + { + unsigned short i = m_Strings.Find( string ); + return i == m_Strings.InvalidIndex() ? NULL : m_Strings[i]; + } + + const char *AllocateString( const char *string ) + { + int i = m_Strings.Find( string ); + if ( i != m_Strings.InvalidIndex() ) + { + return m_Strings[i]; + } + + int len = Q_strlen( string ); + char *out = new char[ len + 1 ]; + Q_memcpy( out, string, len ); + out[ len ] = 0; + + return m_Strings[ m_Strings.Insert( out ) ]; + } + + //---------------------------------------------------------------------------- + + const CUtlDict< ChoreoSentence_t, int > &GetSentences() { return m_Sentences; } + +private: + + // Context prerequisites to be applied if not already applied + CUtlVector< SentenceContextPrerequisite_t > m_ContextPrerequisites; + + // Embedded lists of virtual words based on a criterion + CUtlDict< VirtualWordDefinitionList_t, int > m_VirtualWordLists; + + // Lists of virtual words (does not include nested ones) + CUtlDict< VirtualWordDefinition_t, int > m_VirtualWords; + + // Sentences themselves + CUtlDict< ChoreoSentence_t, int > m_Sentences; + + // Dedicated strings, copied from game string pool + CUtlRBTree m_Strings; + +}; + +static CChoreoSentenceSystem g_ChoreoSentenceSystem; + +//----------------------------------------------------------------------------- + +CON_COMMAND( choreosentence_reload, "Reloads choreo sentences" ) +{ + //int nStringCount = g_ChoreoSentenceSystem.GetStringCount(); + g_ChoreoSentenceSystem.PurgeSentences(); + + //g_ChoreoSentenceSystem.ReserveStrings( nStringCount ); + g_ChoreoSentenceSystem.LoadChoreoSentenceManifest( "scripts/choreosentences_manifest.txt" ); +} + +CON_COMMAND( choreosentence_dump, "Prints all choreo sentence stuff" ) +{ + g_ChoreoSentenceSystem.PrintEverything(); +} + +CON_COMMAND( choreosentence_dump_strings, "Prints all strings allocated by choreo sentences" ) +{ + g_ChoreoSentenceSystem.PrintStrings(); +} + +//------------------------------------------------------------------------------ +// Purpose : +//------------------------------------------------------------------------------ +static int AutoCompleteChoreoSentences(const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0) +{ + FOR_EACH_DICT_FAST( g_ChoreoSentenceSystem.GetSentences(), i ) + { + CUtlString sym = g_ChoreoSentenceSystem.GetSentences().GetElementName( i ); + + if (Q_strnicmp( sym, substring, checklen ) != 0) + continue; + + int idx = symbols.Find( sym ); + if (idx == symbols.InvalidIndex()) + { + symbols.Insert( sym ); + } + + // Too many + if (symbols.Count() >= COMMAND_COMPLETION_MAXITEMS) + break; + } + + // Now fill in the results + for (int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder(i)) + { + const char *name = symbols[i].String(); + + char buf[512]; + Q_snprintf( buf, sizeof( buf ), "%s %s", cmdname, name ); + Q_strlower( buf ); + + CUtlString command; + command = buf; + commands.AddToTail(command); + } + + return symbols.Count(); +} + +class CChoreoSentenceAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback +{ +public: + virtual void CommandCallback( const CCommand &command ) + { + if (command.ArgC() != 2) + { + Msg( "Format: choreosentence_play \n" ); + return; + } + + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if (pPlayer) + { + const ChoreoSentence_t *pSentence = LookupChoreoSentence( pPlayer, command.Arg( 1 ) ); + if (pSentence) + PrecacheChoreoSentence( *pSentence ); + + pPlayer->PlayChoreoSentenceScene( command.Arg( 1 ) ); + } + } + + virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands ) + { + if ( !g_pGameRules ) + { + return 0; + } + + const char *cmdname = "choreosentence_play"; + + char *substring = (char *)partial; + if ( Q_strstr( partial, cmdname ) ) + { + substring = (char *)partial + strlen( cmdname ) + 1; + } + + int checklen = Q_strlen( substring ); + + extern bool UtlStringLessFunc( const CUtlString & lhs, const CUtlString & rhs ); + CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc ); + + return AutoCompleteChoreoSentences(cmdname, commands, symbols, substring, checklen); + } +}; + +static CChoreoSentenceAutoCompletionFunctor g_ChoreoSentenceAutoComplete; +static ConCommand choreosentence_play("choreosentence_play", &g_ChoreoSentenceAutoComplete, "Plays the specified choreo sentence on the player", FCVAR_CHEAT, &g_ChoreoSentenceAutoComplete ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *ChoreoSentence_t::GetWordString( CBaseEntity *pSpeaker, int i ) const +{ + const char *pszExtension = ""; + if (V_strrchr( pszPrefix, CORRECT_PATH_SEPARATOR )) + { + // Use waves if our prefix is a path + pszExtension = ".wav"; + } + + // TODO: Something more elaborate than UTIL_VarArgs? + if (m_Words[i].bVirtual) + { + const char *pszVirtualWord = g_ChoreoSentenceSystem.ResolveVirtualWord( pSpeaker, m_Words[i].pszWord ); + return UTIL_VarArgs( "%s%s%s", pszPrefix, pszVirtualWord, pszExtension ); + } + + return UTIL_VarArgs( "%s%s%s", pszPrefix, m_Words[i].pszWord, pszExtension ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void LoadChoreoSentenceFile( const char *pszFile ) +{ + g_ChoreoSentenceSystem.LoadChoreoSentenceFile( pszFile ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const ChoreoSentence_t *LookupChoreoSentence( CBaseEntity *pSpeaker, const char *pszSentenceName ) +{ + return g_ChoreoSentenceSystem.LookupChoreoSentence( pSpeaker, pszSentenceName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PrecacheChoreoSentence( const ChoreoSentence_t &sentence ) +{ + FOR_EACH_VEC( sentence.m_Words, i ) + { + if (sentence.m_Words[i].bVirtual) + { + // Precache all virtual words + const char *pszExtension = ""; + if (V_strrchr( sentence.pszPrefix, CORRECT_PATH_SEPARATOR )) + pszExtension = ".wav"; + + g_ChoreoSentenceSystem.PrecacheVirtualWord( sentence.m_Words[i].pszWord, sentence.pszPrefix, pszExtension ); + } + else + { + CBaseEntity::PrecacheScriptSound( sentence.GetWordString( NULL, i ) ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool ParseChoreoSentence( CBaseEntity *pSpeaker, const char *pszRawSentence, ChoreoSentence_t &sentence ) +{ + if (pszRawSentence == NULL || *pszRawSentence == NULL) + return false; + + char szSentence[256]; + + // First, try getting the prefix + const char *pColon = V_strnchr( pszRawSentence, ':', V_strlen( pszRawSentence ) ); + if (pColon) + { + // Sentence is everything after colon + Q_strncpy( szSentence, pColon + 1, sizeof( szSentence ) ); + + // Copy everything before colon for prefix + char szPathName[MAX_PATH]; + V_strncpy( szPathName, pszRawSentence, pColon - pszRawSentence + 1 ); + V_FixSlashes( szPathName ); + sentence.pszPrefix = g_ChoreoSentenceSystem.AllocateString( szPathName ); + } + else + { + // It's all one sentence + Q_strncpy( szSentence, pszRawSentence, sizeof( szSentence ) ); + } + + // Now get any parameters + const char *pSemiColon = V_strnchr( szSentence, ';', sizeof( szSentence ) ); + if (pSemiColon) + { + // Caption is whatever's after the semicolon + const char *pszCaption = pSemiColon+1; + if (pszCaption[0] == ' ') + pszCaption++; + + sentence.pszCaption = g_ChoreoSentenceSystem.AllocateString( pszCaption ); + + // Replace semicolon with null terminator + szSentence[pSemiColon - szSentence] = '\0'; + } + + // Next, split up the sentence itself + bool success = ParseChoreoSentenceContents( pSpeaker, szSentence, sentence ); + + return success; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool ParseChoreoSentenceContents( CBaseEntity *pSpeaker, char *pszSentence, ChoreoSentence_t &sentence ) +{ + int nCurVol = 100, nCurPitch = 100; + + char *pszToken = strtok( pszSentence, " " ); + for (; pszToken != NULL; pszToken = strtok( NULL, " " )) + { + if (!pszToken || !*pszToken) + continue; + + // Check if this is a command number + if (pszToken[0] == '(') + { + pszToken++; + + // Remove closing parentheses + //int end = V_strlen( pszToken )-1; + //if (pszToken[end] == ')') + // pszToken[end] = '\0'; + + int nNum = atoi( pszToken + 1 ); + if (nNum > 0) + { + switch (pszToken[0]) + { + // TODO: Recognize e, t, etc.? + case 'v': nCurVol = nNum; break; + case 'p': nCurPitch = nNum; break; + } + continue; + } + else + { + Msg( "Zero command number in %s\n", pszSentence ); + } + } + + int nWord = sentence.m_Words.AddToTail(); + + sentence.m_Words[nWord].nVol = nCurVol; + sentence.m_Words[nWord].nPitch = nCurPitch; + + // Check if this is virtual + if (IsVirtualWord( pszToken )) + sentence.m_Words[nWord].bVirtual = true; + + // Check for periods or commas + int end = V_strlen( pszToken )-1; + if (pszToken[end] == ',') + { + int nWord2 = sentence.m_Words.AddToTail(); + sentence.m_Words[nWord2].pszWord = g_ChoreoSentenceSystem.AllocateString( "_comma" ); + sentence.m_Words[nWord2].nVol = nCurVol; + sentence.m_Words[nWord2].nPitch = nCurPitch; + pszToken[end] = '\0'; + } + else if (pszToken[end] == '.') + { + int nWord2 = sentence.m_Words.AddToTail(); + sentence.m_Words[nWord2].pszWord = g_ChoreoSentenceSystem.AllocateString( "_period" ); + sentence.m_Words[nWord2].nVol = nCurVol; + sentence.m_Words[nWord2].nPitch = nCurPitch; + pszToken[end] = '\0'; + } + + sentence.m_Words[nWord].pszWord = g_ChoreoSentenceSystem.AllocateString( pszToken ); + } + + return sentence.m_Words.Count() > 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float GetChoreoSentenceDuration( CBaseFlex *pSpeaker, const ChoreoSentence_t &sentence ) +{ + const char *actormodel = (pSpeaker ? STRING( pSpeaker->GetModelName() ) : NULL); + float flLength = 0.0f; + + FOR_EACH_VEC( sentence.m_Words, i ) + { + //float duration = CBaseEntity::GetSoundDuration( sentence.GetWordString(pSpeaker, i), actormodel ); + + float duration; + const char *pszWord = sentence.GetWordString( pSpeaker, i ); + + // For now, call the engine functions manually instead of using CBaseEntity::GetSoundDuration so that we could get around the WaveTrace warning + if ( V_stristr( pszWord, ".wav" ) || V_stristr( pszWord, ".mp3" ) ) + { + duration = enginesound->GetSoundDuration( PSkipSoundChars( pszWord ) ); + } + else + { + extern ISoundEmitterSystemBase *soundemitterbase; + duration = enginesound->GetSoundDuration( PSkipSoundChars( soundemitterbase->GetWavFileForSound( pszWord, actormodel ) ) ); + } + + flLength += duration; + } + + return flLength; +} diff --git a/sp/src/game/server/mapbase/choreosentence.h b/sp/src/game/server/mapbase/choreosentence.h new file mode 100644 index 00000000..55371ed1 --- /dev/null +++ b/sp/src/game/server/mapbase/choreosentence.h @@ -0,0 +1,67 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: VCD-based sentences. +// +//=============================================================================// + +#ifndef CHOREOSENTENCE_H +#define CHOREOSENTENCE_H + +#include "cbase.h" + + +#define MAX_CHOREO_SENTENCE_PREFIX_LEN 64 +#define MAX_CHOREO_SENTENCE_WORD_LEN 32 +#define MAX_CHOREO_SENTENCE_CAPTION_LEN 64 + +#define MAX_CHOREO_SENTENCE_VIRTUAL_WORD_LEN 32 + +struct ChoreoSentenceWord_t +{ + ChoreoSentenceWord_t() {} + ChoreoSentenceWord_t( const ChoreoSentenceWord_t &src ) + { + pszWord = src.pszWord; + nPitch = src.nPitch; nVol = src.nVol; + bVirtual = src.bVirtual; + } + + const char *pszWord; + int nPitch, nVol = 100; + bool bVirtual = false; +}; + +struct ChoreoSentence_t +{ + ChoreoSentence_t() {} + ChoreoSentence_t( const ChoreoSentence_t &src ) + { + pszName = src.pszName; + pszPrefix = src.pszPrefix; + pszCaption = src.pszCaption; + m_Words.RemoveAll(); + m_Words.AddVectorToTail( src.m_Words ); + } + + const char *GetWordString( CBaseEntity *pSpeaker, int i ) const; + + CUtlVector< ChoreoSentenceWord_t > m_Words; + const char *pszName; + const char *pszPrefix; + const char *pszCaption; +}; + +//---------------------------------------------------------------------------- + +extern void LoadChoreoSentenceFile( const char *pszFile ); + +extern const ChoreoSentence_t *LookupChoreoSentence( CBaseEntity *pSpeaker, const char *pszSentenceName ); + +extern void PrecacheChoreoSentence( const ChoreoSentence_t &sentence ); + +bool ParseChoreoSentence( CBaseEntity *pSpeaker, const char *pszRawSentence, ChoreoSentence_t &sentence ); +bool ParseChoreoSentenceContents( CBaseEntity *pSpeaker, char *pszSentence, ChoreoSentence_t &sentence ); + +extern float GetChoreoSentenceDuration( CBaseFlex *pSpeaker, const ChoreoSentence_t &sentence ); + +#endif // CHOREOSENTENCE_H diff --git a/sp/src/game/server/mapbase/logic_externaldata.cpp b/sp/src/game/server/mapbase/logic_externaldata.cpp index 588bb6c7..90399a51 100644 --- a/sp/src/game/server/mapbase/logic_externaldata.cpp +++ b/sp/src/game/server/mapbase/logic_externaldata.cpp @@ -38,8 +38,8 @@ public: void InputReload( inputdata_t &inputdata ); #ifdef MAPBASE_VSCRIPT - HSCRIPT ScriptGetKeyValues( void ); - HSCRIPT ScriptGetKeyValueBlock( void ); + HSCRIPT_RC ScriptGetKeyValues( void ); + HSCRIPT_RC ScriptGetKeyValueBlock( void ); void ScriptSetKeyValues( HSCRIPT hKV ); void ScriptSetKeyValueBlock( HSCRIPT hKV ); @@ -275,7 +275,7 @@ void CLogicExternalData::InputReload( inputdata_t &inputdata ) #ifdef MAPBASE_VSCRIPT //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -HSCRIPT CLogicExternalData::ScriptGetKeyValues( void ) +HSCRIPT_RC CLogicExternalData::ScriptGetKeyValues( void ) { if (m_bReloadBeforeEachAction) LoadFile(); @@ -283,8 +283,9 @@ HSCRIPT CLogicExternalData::ScriptGetKeyValues( void ) HSCRIPT hScript = NULL; if (m_pRoot) { - // Does this need to be destructed or freed? m_pScriptModelKeyValues apparently doesn't. - hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, m_pRoot, false ); + KeyValues *pCopy = new KeyValues( NULL ); + *pCopy = *m_pRoot; + hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pCopy ); } return hScript; @@ -292,7 +293,7 @@ HSCRIPT CLogicExternalData::ScriptGetKeyValues( void ) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -HSCRIPT CLogicExternalData::ScriptGetKeyValueBlock( void ) +HSCRIPT_RC CLogicExternalData::ScriptGetKeyValueBlock( void ) { if (m_bReloadBeforeEachAction) LoadFile(); @@ -300,8 +301,9 @@ HSCRIPT CLogicExternalData::ScriptGetKeyValueBlock( void ) HSCRIPT hScript = NULL; if (m_pBlock) { - // Does this need to be destructed or freed? m_pScriptModelKeyValues apparently doesn't. - hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, m_pBlock, false ); + KeyValues *pCopy = new KeyValues( NULL ); + *pCopy = *m_pBlock; + hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pCopy ); } return hScript; @@ -321,7 +323,8 @@ void CLogicExternalData::ScriptSetKeyValues( HSCRIPT hKV ) KeyValues *pKV = scriptmanager->GetKeyValuesFromScriptKV( g_pScriptVM, hKV ); if (pKV) { - m_pRoot = pKV; + m_pRoot = new KeyValues( NULL ); + *m_pRoot = *pKV; } } @@ -336,7 +339,8 @@ void CLogicExternalData::ScriptSetKeyValueBlock( HSCRIPT hKV ) KeyValues *pKV = scriptmanager->GetKeyValuesFromScriptKV( g_pScriptVM, hKV ); if (pKV) { - m_pBlock = pKV; + m_pBlock = new KeyValues( NULL ); + *m_pBlock = *pKV; } } diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp index 7c8241bc..3ee5963f 100644 --- a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -93,6 +93,7 @@ public: virtual Activity GetPrimaryAttackActivity(void) { return m_CustomData.m_bHitUsesMissAnim ? ACT_VM_MISSCENTER : BaseClass::GetPrimaryAttackActivity(); } const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } + const char* GetName( void ) const { return STRING( m_iClassname ); } virtual int GetDamageType() { return g_nDamageClassTypeBits[m_CustomData.m_nDamageClass]; } virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript); @@ -381,6 +382,7 @@ public: CHLCustomWeaponGun(); virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript); const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } + const char* GetName( void ) const { return STRING( m_iClassname ); } // Weapon behaviour virtual void ItemPostFrame(void); // called each frame by the player PostThink diff --git a/sp/src/game/server/player.cpp b/sp/src/game/server/player.cpp index 89581d85..dad94dea 100644 --- a/sp/src/game/server/player.cpp +++ b/sp/src/game/server/player.cpp @@ -7964,7 +7964,7 @@ Activity CBasePlayer::Weapon_TranslateActivity( Activity baseAct, bool *pRequire { Activity weaponTranslation = BaseClass::Weapon_TranslateActivity( baseAct, pRequired ); - if ( GetActiveWeapon() && GetActiveWeapon()->IsEffectActive(EF_NODRAW) && baseAct != ACT_ARM ) + if ( GetActiveWeapon() && !GetActiveWeapon()->IsWeaponVisible() && baseAct != ACT_ARM ) { // Our weapon is holstered. Use the base activity. return baseAct; @@ -8871,6 +8871,7 @@ void SendProxy_ShiftPlayerSpawnflags( const SendProp *pProp, const void *pStruct // See baseplayer_shared.h for more details. SendPropInt ( SENDINFO( m_spawnflags ), 3, SPROP_UNSIGNED, SendProxy_ShiftPlayerSpawnflags ), + SendPropBool ( SENDINFO( m_bDrawPlayerLegs ) ), SendPropBool ( SENDINFO( m_bDrawPlayerModelExternally ) ), SendPropBool ( SENDINFO( m_bInTriggerFall ) ), #endif diff --git a/sp/src/game/server/player.h b/sp/src/game/server/player.h index c3ef666a..9c147c08 100644 --- a/sp/src/game/server/player.h +++ b/sp/src/game/server/player.h @@ -1179,6 +1179,9 @@ public: int m_nNumCrateHudHints; #ifdef MAPBASE + bool GetDrawPlayerLegs( void ) { return m_bDrawPlayerLegs; } + void SetDrawPlayerLegs( bool bToggle ) { m_bDrawPlayerLegs.Set( bToggle ); } + bool GetDrawPlayerModelExternally( void ) { return m_bDrawPlayerModelExternally; } void SetDrawPlayerModelExternally( bool bToggle ) { m_bDrawPlayerModelExternally.Set( bToggle ); } #endif @@ -1222,6 +1225,7 @@ private: char m_szNetname[MAX_PLAYER_NAME_LENGTH]; #ifdef MAPBASE + CNetworkVar( bool, m_bDrawPlayerLegs ); CNetworkVar( bool, m_bDrawPlayerModelExternally ); #endif diff --git a/sp/src/game/server/player_command.cpp b/sp/src/game/server/player_command.cpp index 7614babe..d5a6a1f0 100644 --- a/sp/src/game/server/player_command.cpp +++ b/sp/src/game/server/player_command.cpp @@ -393,7 +393,7 @@ void CPlayerMove::RunCommand ( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper if ( weapon ) { VPROF( "player->SelectItem()" ); - player->SelectItem( weapon->GetClassname(), ucmd->weaponsubtype ); + player->SelectItem( weapon->GetName(), ucmd->weaponsubtype ); } } diff --git a/sp/src/game/server/sceneentity.cpp b/sp/src/game/server/sceneentity.cpp index 06f8f8fd..6c1d4d71 100644 --- a/sp/src/game/server/sceneentity.cpp +++ b/sp/src/game/server/sceneentity.cpp @@ -41,6 +41,10 @@ #include "npc_alyx_episodic.h" #endif // HL2_EPISODIC +#ifdef MAPBASE +#include "mapbase/choreosentence.h" +#endif + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -57,6 +61,10 @@ class CBaseFlex; static ConVar scene_forcecombined( "scene_forcecombined", "0", 0, "When playing back, force use of combined .wav files even in english." ); static ConVar scene_maxcaptionradius( "scene_maxcaptionradius", "1200", 0, "Only show closed captions if recipient is within this many units of speaking actor (0==disabled)." ); +#ifdef MAPBASE +static ConVar scene_resume_use_last_speaking( "scene_resume_use_last_speaking", "1", 0, "Uses the last actor who spoke for resume scene calls, rather than the first actor of the scene" ); +#endif + // Assume sound system is 100 msec lagged (only used if we can't find snd_mixahead cvar!) #define SOUND_SYSTEM_LATENCY_DEFAULT ( 0.1f ) @@ -412,6 +420,10 @@ public: // If this scene is waiting on an actor, give up and quit trying. void InputStopWaitingForActor( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputPauseAtLastInterrupt( inputdata_t &inputdata ); +#endif + virtual void StartPlayback( void ); virtual void PausePlayback( void ); virtual void ResumePlayback( void ); @@ -482,6 +494,9 @@ public: bool InvolvesActor( CBaseEntity *pActor ); // NOTE: returns false if scene hasn't loaded yet void GenerateSoundScene( CBaseFlex *pActor, const char *soundname ); +#ifdef MAPBASE + void GenerateChoreoSentenceScene( CBaseFlex *pActor, const char *pszSentenceName ); +#endif virtual float GetPostSpeakDelay() { return 1.0; } @@ -489,6 +504,7 @@ public: bool HasFlexAnimation( void ); #ifdef MAPBASE bool IsPlayingSpeech( void ); + CBaseFlex *GetLastSpeakingActor( void ); #endif void SetCurrentTime( float t, bool forceClientSync ); @@ -594,6 +610,9 @@ private: void PrecacheScene( CChoreoScene *scene ); CChoreoScene *GenerateSceneForSound( CBaseFlex *pFlexActor, const char *soundname ); +#ifdef MAPBASE + CChoreoScene *GenerateSceneForSentenceName( CBaseFlex *pFlexActor, const char *pszSentenceName ); +#endif bool CheckActors(); @@ -643,6 +662,9 @@ private: bool m_bGenerated; string_t m_iszSoundName; CHandle< CBaseFlex > m_hActor; +#ifdef MAPBASE + bool m_bChoreoSentence; +#endif EHANDLE m_hActivator; @@ -736,6 +758,9 @@ BEGIN_DATADESC( CSceneEntity ) DEFINE_FIELD( m_bGenerated, FIELD_BOOLEAN ), DEFINE_FIELD( m_iszSoundName, FIELD_STRING ), DEFINE_FIELD( m_hActor, FIELD_EHANDLE ), +#ifdef MAPBASE + DEFINE_FIELD( m_bChoreoSentence, FIELD_BOOLEAN ), +#endif DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ), // DEFINE_FIELD( m_bSceneMissing, FIELD_BOOLEAN ), @@ -757,8 +782,9 @@ BEGIN_DATADESC( CSceneEntity ) DEFINE_INPUTFUNC( FIELD_STRING, "InterjectResponse", InputInterjectResponse ), DEFINE_INPUTFUNC( FIELD_VOID, "StopWaitingForActor", InputStopWaitingForActor ), DEFINE_INPUTFUNC( FIELD_INTEGER, "Trigger", InputTriggerEvent ), - #ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "PauseAtLastInterrupt", InputPauseAtLastInterrupt ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget1", InputSetTarget1 ), DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget2", InputSetTarget2 ), DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget3", InputSetTarget3 ), @@ -953,6 +979,146 @@ CChoreoScene *CSceneEntity::GenerateSceneForSound( CBaseFlex *pFlexActor, const return scene; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *pszSentenceName - +// Output : CChoreoScene +//----------------------------------------------------------------------------- +CChoreoScene *CSceneEntity::GenerateSceneForSentenceName( CBaseFlex *pFlexActor, const char *pszSentenceName ) +{ + const ChoreoSentence_t *pSentence = LookupChoreoSentence( pFlexActor, pszSentenceName ); + if ( !pSentence ) + { + Warning( "CSceneEntity::GenerateSceneForSentenceName: Couldn't find sentence '%s'\n", pszSentenceName ); + return NULL; + } + + // TODO: Raw sentence support? + // ChoreoSentence_t sentence; + // if ( !ParseChoreoSentence( pFlexActor, pszSentence, sentence ) ) + // { + // Warning( "CSceneEntity::GenerateSceneForSentence: Couldn't parse sentence from '%s'\n", pszSentence ); + // return NULL; + // } + + CChoreoScene *scene = new CChoreoScene( this ); + if ( !scene ) + { + Warning( "CSceneEntity::GenerateSceneForSentenceName: Failed to allocated new scene!!!\n" ); + } + else + { + scene->SetPrintFunc( LocalScene_Printf ); + + CChoreoActor *actor = scene->AllocActor(); + CChoreoChannel *channel = scene->AllocChannel(); + + Assert( actor ); + Assert( channel ); + + if ( !actor || !channel ) + { + Warning( "CSceneEntity::GenerateSceneForSentenceName: Alloc of actor or channel failed!!!\n" ); + delete scene; + return NULL; + } + + // Set us up the actorz + actor->SetName( "!self" ); // Could be pFlexActor->GetName()? + actor->SetActive( true ); + + // Set us up the channelz + channel->SetName( STRING( m_iszSceneFile ) ); + channel->SetActor( actor ); + + // Add to actor + actor->AddChannel( channel ); + + // Set us up the eventz + const char *actormodel = (pFlexActor ? STRING( pFlexActor->GetModelName() ) : NULL); + float flCurTime = 0.0f; + FOR_EACH_VEC( pSentence->m_Words, i ) + { + const char *pszWord = pSentence->GetWordString( pFlexActor, i ); + + float duration = CBaseEntity::GetSoundDuration( pszWord, actormodel ); + if (duration <= 0.0f) + { + Warning( "CSceneEntity::GenerateSceneForSentenceName: Couldn't determine duration of %s\n", pszWord ); + } + + CChoreoEvent *event = scene->AllocEvent(); + Assert( event ); + + if ( !event ) + { + Warning( "CSceneEntity::GenerateSceneForSentenceName: Alloc of event failed!!!\n" ); + delete scene; + return NULL; + } + + if (pSentence->pszCaption) + { + // First word gets the caption, others fall back to it + if (i == 0) + { + event->SetCloseCaptionType( CChoreoEvent::CC_MASTER ); + event->SetCloseCaptionToken( pSentence->pszCaption ); + } + else + { + event->SetCloseCaptionType( CChoreoEvent::CC_SLAVE ); + } + } + //else if (pSentence->pszName) + //{ + // // TODO: Caption from name? + //} + + if (pSentence->m_Words[i].nVol != 100) + { + event->SetYaw( pSentence->m_Words[i].nVol ); + } + + if (pSentence->m_Words[i].nPitch != 100) + { + duration *= (100.0f / ((float)pSentence->m_Words[i].nPitch)); + event->SetPitch( pSentence->m_Words[i].nPitch ); + } + + // HACKHACK: Need to be spaced away from repeated sound to avoid changing the previous sound's pitch instead + if (i+1 < pSentence->m_Words.Count() && pSentence->m_Words[i+1].pszWord == pSentence->m_Words[i].pszWord + && pSentence->m_Words[i + 1].nPitch != 100) + duration += 0.1f; + + event->SetType( CChoreoEvent::SPEAK ); + event->SetName( pszWord ); + event->SetParameters( pszWord ); + event->SetStartTime( flCurTime ); + event->SetUsingRelativeTag( false ); + event->SetEndTime( flCurTime + duration ); + event->SnapTimes(); + + //Msg( "%i %s: %f -> %f (%f)\n", i, pszWord, flCurTime, flCurTime + duration, duration ); + + // Add to channel + channel->AddEvent( event ); + + // Point back to our owners + event->SetChannel( channel ); + event->SetActor( actor ); + + flCurTime += duration; + } + + } + + return scene; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -960,6 +1126,13 @@ void CSceneEntity::Activate() { if ( m_bGenerated && !m_pScene ) { +#ifdef MAPBASE + if (m_bChoreoSentence) + { + m_pScene = GenerateSceneForSentenceName( m_hActor, STRING( m_iszSoundName ) ); + } + else +#endif m_pScene = GenerateSceneForSound( m_hActor, STRING( m_iszSoundName ) ); } @@ -1138,6 +1311,22 @@ void CSceneEntity::GenerateSoundScene( CBaseFlex *pActor, const char *soundname m_hActor = pActor; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pActor - +// *pszSentenceName - +//----------------------------------------------------------------------------- +void CSceneEntity::GenerateChoreoSentenceScene( CBaseFlex *pActor, const char *pszSentenceName ) +{ + m_bGenerated = true; + m_iszSoundName = MAKE_STRING( pszSentenceName ); + m_hActor = pActor; + + m_bChoreoSentence = true; +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. @@ -1185,6 +1374,33 @@ bool CSceneEntity::IsPlayingSpeech( void ) return false; } + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseFlex *CSceneEntity::GetLastSpeakingActor( void ) +{ + if ( m_pScene ) + { + CChoreoActor *pLastActor = NULL; + float flTime = m_pScene->GetTime(); + for ( int i = 0; i < m_pScene->GetNumEvents(); i++ ) + { + CChoreoEvent *e = m_pScene->GetEvent( i ); + if ( e->GetType() == CChoreoEvent::SPEAK ) + { + if ( flTime >= e->GetStartTime() ) + pLastActor = e->GetActor(); + } + } + + if (pLastActor) + return FindNamedActor( pLastActor ); + } + + // Fall back to the first actor + return FindNamedActor( 0 ); +} #endif @@ -1930,6 +2146,26 @@ void CSceneEntity::DispatchStartSpeak( CChoreoScene *scene, CBaseFlex *actor, CC // Warning( "Speak %s\n", soundname ); +#ifdef MAPBASE + if ( m_fPitch != 1.0f || event->GetPitch() != 0 ) + { + if ( es.m_nPitch && es.m_nPitch != 100 ) + es.m_nPitch = static_cast( es.m_nPitch ) * m_fPitch; + else + { + float flPitch = (event->GetPitch() != 0 ? event->GetPitch() : 100.0f); + es.m_nPitch = flPitch * m_fPitch; + } + + es.m_nFlags |= SND_CHANGE_PITCH; + } + + if ( event->GetYaw() != 0 ) + { + es.m_flVolume = (((float)event->GetYaw()) / 100.0f); + es.m_nFlags |= SND_CHANGE_VOL; + } +#else if ( m_fPitch != 1.0f ) { if ( es.m_nPitch ) @@ -1939,6 +2175,7 @@ void CSceneEntity::DispatchStartSpeak( CChoreoScene *scene, CBaseFlex *actor, CC es.m_nFlags |= SND_CHANGE_PITCH; } +#endif EmitSound( filter2, actor->entindex(), es ); actor->AddSceneEvent( scene, event ); @@ -2468,6 +2705,39 @@ void CSceneEntity::InputInterjectResponse( inputdata_t &inputdata ) } #ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CSceneEntity::InputPauseAtLastInterrupt( inputdata_t &inputdata ) +{ + PausePlayback(); + m_bPausedViaInput = true; + + if ( m_pScene ) + { + float flLastInterrupt = 0.0f; + float flTime = m_pScene->GetTime(); + for ( int i = 0; i < m_pScene->GetNumEvents(); i++ ) + { + CChoreoEvent *e = m_pScene->GetEvent( i ); + if ( e->GetType() == CChoreoEvent::INTERRUPT ) + { + if ( flTime > e->GetEndTime() && e->GetEndTime() > flLastInterrupt ) + { + // Set the scene's time to the last interrupt point's end time + flLastInterrupt = e->GetEndTime(); + break; + } + } + } + + if (flLastInterrupt != 0.0f) + { + // Set the scene's time to the last interrupt point's end time + m_pScene->SetTime( flLastInterrupt ); + } + } +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CSceneEntity::SetTarget( int nTarget, string_t pTargetName, CBaseEntity *pActivator, CBaseEntity *pCaller ) @@ -3096,13 +3366,22 @@ void CSceneEntity::QueueResumePlayback( void ) // If it has ".vcd" somewhere in the string, try using it as a scene file first if ( Q_stristr( STRING(m_iszResumeSceneFile), ".vcd" ) ) { - bStartedScene = InstancedScriptedScene( NULL, STRING(m_iszResumeSceneFile), &m_hWaitingForThisResumeScene, 0, false ) != 0; +#ifdef MAPBASE + CBaseFlex *pActor = scene_resume_use_last_speaking.GetBool() ? GetLastSpeakingActor() : FindNamedActor( 0 ); +#else + CBaseFlex *pActor = NULL; +#endif + bStartedScene = InstancedScriptedScene( pActor, STRING(m_iszResumeSceneFile), &m_hWaitingForThisResumeScene, 0, false ) != 0; } // HACKHACK: For now, get the first target, and see if we can find a response for him if ( !bStartedScene ) { +#ifdef MAPBASE + CBaseFlex *pActor = scene_resume_use_last_speaking.GetBool() ? GetLastSpeakingActor() : FindNamedActor( 0 ); +#else CBaseFlex *pActor = FindNamedActor( 0 ); +#endif if ( pActor ) { CAI_BaseActor *pBaseActor = dynamic_cast(pActor); @@ -3115,7 +3394,7 @@ void CSceneEntity::QueueResumePlayback( void ) if ( result ) { const char* szResponse = response.GetResponsePtr(); - bStartedScene = InstancedScriptedScene( NULL, szResponse, &m_hWaitingForThisResumeScene, 0, false ) != 0; + bStartedScene = InstancedScriptedScene( pActor, szResponse, &m_hWaitingForThisResumeScene, 0, false ) != 0; } #else AI_Response *result = pBaseActor->SpeakFindResponse( STRING(m_iszResumeSceneFile), NULL ); @@ -5667,6 +5946,62 @@ void CInstancedSceneEntity::OnLoaded() #endif } +#ifdef MAPBASE +float InstancedChoreoSentenceScene( CBaseFlex *pActor, const char *pszSentence, EHANDLE *phSceneEnt, + float flPostDelay, bool bIsBackground, AI_Response *response, + bool bMultiplayer, IRecipientFilter *filter /* = NULL */ ) +{ + if ( !pActor ) + { + Warning( "InstancedChoreoSentenceScene: Expecting non-NULL pActor for sound %s\n", pszSentence ); + return 0; + } + + CInstancedSceneEntity *pScene = (CInstancedSceneEntity *)CBaseEntity::CreateNoSpawn( "instanced_scripted_scene", vec3_origin, vec3_angle ); + + Q_strncpy( pScene->m_szInstanceFilename, UTIL_VarArgs( "AutoGenerated(%s)", pszSentence ), sizeof( pScene->m_szInstanceFilename ) ); + pScene->m_iszSceneFile = MAKE_STRING( pScene->m_szInstanceFilename ); + + pScene->m_hOwner = pActor; + pScene->m_bHadOwner = pActor != NULL; + + pScene->GenerateChoreoSentenceScene( pActor, pszSentence ); + + pScene->m_bMultiplayer = bMultiplayer; + pScene->SetPostSpeakDelay( flPostDelay ); + DispatchSpawn( pScene ); + pScene->Activate(); + pScene->m_bIsBackground = bIsBackground; + + pScene->SetBackground( bIsBackground ); + pScene->SetRecipientFilter( filter ); + + if ( response ) + { + float flPreDelay = response->GetPreDelay(); + if ( flPreDelay ) + { + pScene->SetPreDelay( flPreDelay ); + } + } + + pScene->StartPlayback(); + + if ( response ) + { + // If the response wants us to abort on NPC state switch, remember that + pScene->SetBreakOnNonIdle( response->ShouldBreakOnNonIdle() ); + } + + if ( phSceneEnt ) + { + *phSceneEnt = pScene; + } + + return pScene->EstimateLength(); +} +#endif + bool g_bClientFlex = true; LINK_ENTITY_TO_CLASS( scene_manager, CSceneManager ); diff --git a/sp/src/game/server/sceneentity.h b/sp/src/game/server/sceneentity.h index ea286fe9..8ab19a77 100644 --- a/sp/src/game/server/sceneentity.h +++ b/sp/src/game/server/sceneentity.h @@ -28,6 +28,7 @@ int GetRecentNPCSpeech( recentNPCSpeech_t speech[ SPEECH_LIST_MAX_SOUNDS ] ); float InstancedScriptedScene( CBaseFlex *pActor, const char *pszScene, EHANDLE *phSceneEnt = NULL, float flPostDelay = 0.0f, bool bIsBackground = false, AI_Response *response = NULL, bool bMultiplayer = false, IRecipientFilter *filter = NULL ); #ifdef MAPBASE float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, char const *soundname, EHANDLE *phSceneEnt = NULL, float flPostDelay = 0.0f, bool bIsBackground = false, AI_Response *response = NULL, bool bMultiplayer = false, IRecipientFilter *filter = NULL ); +float InstancedChoreoSentenceScene( CBaseFlex *pActor, char const *pszSentence, EHANDLE *phSceneEnt = NULL, float flPostDelay = 0.0f, bool bIsBackground = false, AI_Response *response = NULL, bool bMultiplayer = false, IRecipientFilter *filter = NULL ); #else float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, char const *soundname, EHANDLE *phSceneEnt = NULL ); #endif diff --git a/sp/src/game/server/server_mapbase.vpc b/sp/src/game/server/server_mapbase.vpc index ec54d292..2d1113d2 100644 --- a/sp/src/game/server/server_mapbase.vpc +++ b/sp/src/game/server/server_mapbase.vpc @@ -46,8 +46,8 @@ $Project $File "$SRCDIR\game\shared\mapbase\MapEdit.h" $File "$SRCDIR\game\shared\mapbase\matchers.cpp" $File "$SRCDIR\game\shared\mapbase\matchers.h" - $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.cpp" - $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.h" + $File "$SRCDIR\game\shared\mapbase\mapbase_playeranimstate.cpp" + $File "$SRCDIR\game\shared\mapbase\mapbase_playeranimstate.h" $File "$SRCDIR\game\shared\mapbase\protagonist_system.cpp" $File "$SRCDIR\game\shared\mapbase\protagonist_system.h" $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.cpp" [$MAPBASE_VSCRIPT] @@ -65,6 +65,8 @@ $Project $File "mapbase\ai_grenade.h" $File "mapbase\ai_monitor.cpp" $File "mapbase\ai_weaponmodifier.cpp" + $File "mapbase\choreosentence.cpp" + $File "mapbase\choreosentence.h" $File "mapbase\custom_weapon_factory.cpp" $File "mapbase\custom_weapon_factory.h" $File "mapbase\closecaption_entity.cpp" diff --git a/sp/src/game/server/soundent.cpp b/sp/src/game/server/soundent.cpp index 59273206..a84f7676 100644 --- a/sp/src/game/server/soundent.cpp +++ b/sp/src/game/server/soundent.cpp @@ -66,6 +66,7 @@ BEGIN_SCRIPTDESC_ROOT( CSound, "A sound NPCs can hear." ) DEFINE_SCRIPTFUNC( ValidateOwner, "Returns true if the sound's owner is still valid or if the sound never had an owner in the first place." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetOwner, "GetOwner", "Gets the sound's owner." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetTarget, "GetTarget", "Gets the sound's target." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptFreeSound, "FreeSound", "Frees the sound from the sound list." ) END_SCRIPTDESC(); #endif diff --git a/sp/src/game/server/soundent.h b/sp/src/game/server/soundent.h index 7ce3d401..47a1f742 100644 --- a/sp/src/game/server/soundent.h +++ b/sp/src/game/server/soundent.h @@ -140,6 +140,8 @@ public: // For VScript functions HSCRIPT ScriptGetOwner() const { return ToHScript( m_hOwner ); } HSCRIPT ScriptGetTarget() const { return ToHScript( m_hTarget ); } + + void ScriptFreeSound() { m_flExpireTime = gpGlobals->curtime; m_bNoExpirationTime = false; } #endif EHANDLE m_hOwner; // sound's owner diff --git a/sp/src/game/server/vscript_server.cpp b/sp/src/game/server/vscript_server.cpp index aa14a1b4..d525f96c 100644 --- a/sp/src/game/server/vscript_server.cpp +++ b/sp/src/game/server/vscript_server.cpp @@ -22,6 +22,10 @@ extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * ); +#ifdef MAPBASE_VSCRIPT +extern int vscript_debugger_port; +#endif + // #define VMPROFILE 1 #ifdef VMPROFILE @@ -663,6 +667,14 @@ bool VScriptServerInit() RegisterSharedScriptFunctions(); #endif +#ifdef MAPBASE_VSCRIPT + if ( vscript_debugger_port ) + { + g_pScriptVM->ConnectDebugger( vscript_debugger_port ); + vscript_debugger_port = 0; + } +#endif + if (scriptLanguage == SL_SQUIRREL) { g_pScriptVM->Run( g_Script_vscript_server ); diff --git a/sp/src/game/server/vscript_server.nut b/sp/src/game/server/vscript_server.nut index ad48da19..500c161c 100644 --- a/sp/src/game/server/vscript_server.nut +++ b/sp/src/game/server/vscript_server.nut @@ -72,16 +72,16 @@ function ImpulseScale( flTargetMass, flDesiredSpeed ) } __Documentation.RegisterHelp( "ImpulseScale", "float ImpulseScale(float, float)", "Returns an impulse scale required to push an object." ); -local PrecacheModel = PrecacheModel +local PrecacheModel = DoPrecacheModel function PrecacheModel( a, b = true ) { return PrecacheModel( a, b ) } -local PrecacheOther = PrecacheOther +local PrecacheOther = DoPrecacheOther function PrecacheOther( a, b = "" ) { - PrecacheOther( a, b ) + return PrecacheOther( a, b ) } function __ReplaceClosures( script, scope ) @@ -106,10 +106,9 @@ function __ReplaceClosures( script, scope ) } } -local __OutputsPattern = regexp("^On.*Output$"); - function ConnectOutputs( table ) { + local __OutputsPattern = regexp("^On.*Output$"); local nCharsToStrip = 6; foreach( key, val in table ) { diff --git a/sp/src/game/shared/activitylist.cpp b/sp/src/game/shared/activitylist.cpp index 0b415d75..4a8e0975 100644 --- a/sp/src/game/shared/activitylist.cpp +++ b/sp/src/game/shared/activitylist.cpp @@ -2694,6 +2694,18 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SLAM ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_SMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_AR2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_PHYSGUN ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_GRENADE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_RPG ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_MELEE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_SLAM ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_REVOLVER ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_REVOLVER ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_REVOLVER ); @@ -2702,6 +2714,7 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_REVOLVER ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_REVOLVER ); #if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES @@ -2713,6 +2726,7 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_AR1 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR1 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_AR1 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_AR1 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_AR3 ); @@ -2723,6 +2737,7 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_AR3 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR3 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_AR3 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_AR3 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_SMG2 ); @@ -2733,6 +2748,7 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG2 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG2 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_SMG2 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_SMG2 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_SMG3 ); @@ -2743,6 +2759,7 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG3 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG3 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_SMG3 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_SMG3 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_HMG1 ); @@ -2753,6 +2770,7 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_HMG1 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_HMG1 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_HMG1 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_HMG1 ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_SNIPER_RIFLE ); @@ -2763,6 +2781,7 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_SNIPER_RIFLE ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SNIPER_RIFLE ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_SNIPER_RIFLE ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_SNIPER_RIFLE ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_DUAL_PISTOLS ); @@ -2773,6 +2792,7 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_DUAL_PISTOLS ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_DUAL_PISTOLS ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELAX_DUAL_PISTOLS ); REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_DUAL_PISTOLS ); #endif diff --git a/sp/src/game/shared/ai_activity.h b/sp/src/game/shared/ai_activity.h index c8bf150a..35d584cf 100644 --- a/sp/src/game/shared/ai_activity.h +++ b/sp/src/game/shared/ai_activity.h @@ -2598,6 +2598,18 @@ typedef enum ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SLAM, + ACT_HL2MP_GESTURE_RELAX, + ACT_HL2MP_GESTURE_RELAX_PISTOL, + ACT_HL2MP_GESTURE_RELAX_SHOTGUN, + ACT_HL2MP_GESTURE_RELAX_SMG1, + ACT_HL2MP_GESTURE_RELAX_AR2, + ACT_HL2MP_GESTURE_RELAX_PHYSGUN, + ACT_HL2MP_GESTURE_RELAX_GRENADE, + ACT_HL2MP_GESTURE_RELAX_RPG, + ACT_HL2MP_GESTURE_RELAX_CROSSBOW, + ACT_HL2MP_GESTURE_RELAX_MELEE, + ACT_HL2MP_GESTURE_RELAX_SLAM, + ACT_HL2MP_IDLE_REVOLVER, ACT_HL2MP_RUN_REVOLVER, ACT_HL2MP_WALK_REVOLVER, @@ -2606,6 +2618,7 @@ typedef enum ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER, ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER, ACT_HL2MP_GESTURE_RELOAD_REVOLVER, + ACT_HL2MP_GESTURE_RELAX_REVOLVER, ACT_HL2MP_JUMP_REVOLVER, #if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES @@ -2618,6 +2631,7 @@ typedef enum ACT_HL2MP_GESTURE_RANGE_ATTACK_AR1, ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR1, ACT_HL2MP_GESTURE_RELOAD_AR1, + ACT_HL2MP_GESTURE_RELAX_AR1, ACT_HL2MP_JUMP_AR1, ACT_HL2MP_IDLE_AR3, @@ -2628,6 +2642,7 @@ typedef enum ACT_HL2MP_GESTURE_RANGE_ATTACK_AR3, ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR3, ACT_HL2MP_GESTURE_RELOAD_AR3, + ACT_HL2MP_GESTURE_RELAX_AR3, ACT_HL2MP_JUMP_AR3, ACT_HL2MP_IDLE_SMG2, @@ -2638,6 +2653,7 @@ typedef enum ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG2, ACT_HL2MP_GESTURE_RELOAD_SMG2, + ACT_HL2MP_GESTURE_RELAX_SMG2, ACT_HL2MP_JUMP_SMG2, ACT_HL2MP_IDLE_SMG3, @@ -2648,6 +2664,7 @@ typedef enum ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG3, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG3, ACT_HL2MP_GESTURE_RELOAD_SMG3, + ACT_HL2MP_GESTURE_RELAX_SMG3, ACT_HL2MP_JUMP_SMG3, ACT_HL2MP_IDLE_HMG1, @@ -2658,6 +2675,7 @@ typedef enum ACT_HL2MP_GESTURE_RANGE_ATTACK_HMG1, ACT_HL2MP_GESTURE_RANGE_ATTACK2_HMG1, ACT_HL2MP_GESTURE_RELOAD_HMG1, + ACT_HL2MP_GESTURE_RELAX_HMG1, ACT_HL2MP_JUMP_HMG1, ACT_HL2MP_IDLE_SNIPER_RIFLE, @@ -2668,6 +2686,7 @@ typedef enum ACT_HL2MP_GESTURE_RANGE_ATTACK_SNIPER_RIFLE, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SNIPER_RIFLE, ACT_HL2MP_GESTURE_RELOAD_SNIPER_RIFLE, + ACT_HL2MP_GESTURE_RELAX_SNIPER_RIFLE, ACT_HL2MP_JUMP_SNIPER_RIFLE, ACT_HL2MP_IDLE_DUAL_PISTOLS, @@ -2678,6 +2697,7 @@ typedef enum ACT_HL2MP_GESTURE_RANGE_ATTACK_DUAL_PISTOLS, ACT_HL2MP_GESTURE_RANGE_ATTACK2_DUAL_PISTOLS, ACT_HL2MP_GESTURE_RELOAD_DUAL_PISTOLS, + ACT_HL2MP_GESTURE_RELAX_DUAL_PISTOLS, ACT_HL2MP_JUMP_DUAL_PISTOLS, #endif diff --git a/sp/src/game/shared/ai_responsesystem_new.cpp b/sp/src/game/shared/ai_responsesystem_new.cpp index b639f270..a09f6f37 100644 --- a/sp/src/game/shared/ai_responsesystem_new.cpp +++ b/sp/src/game/shared/ai_responsesystem_new.cpp @@ -31,6 +31,9 @@ #ifdef GAME_DLL #include "sceneentity.h" +#ifdef MAPBASE +#include "mapbase/choreosentence.h" +#endif #endif #include "networkstringtabledefs.h" @@ -492,6 +495,17 @@ void CGameResponseSystem::Precache() CBaseEntity::PrecacheScriptSound( response.value ); } break; +#ifdef MAPBASE + case RESPONSE_CHOREOSENTENCE: + { + const ChoreoSentence_t *pSentence = LookupChoreoSentence( NULL, response.value ); + if (pSentence) + PrecacheChoreoSentence( *pSentence ); + else + Msg( "Choreo scene '%s' not found\n", response.value ); + } + break; +#endif } } } diff --git a/sp/src/game/shared/base_playeranimstate.cpp b/sp/src/game/shared/base_playeranimstate.cpp index 82cb75aa..d0426df4 100644 --- a/sp/src/game/shared/base_playeranimstate.cpp +++ b/sp/src/game/shared/base_playeranimstate.cpp @@ -18,12 +18,12 @@ #include "c_baseplayer.h" #include "engine/ivdebugoverlay.h" - ConVar cl_showanimstate( "cl_showanimstate", "-1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Show the (client) animation state for the specified entity (-1 for none)." ); - ConVar showanimstate_log( "cl_showanimstate_log", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "1 to output cl_showanimstate to Msg(). 2 to store in AnimStateClient.log. 3 for both." ); + ConVar cl_showanimstate( "cl_showanimstate", "-1", FCVAR_CHEAT /*| FCVAR_DEVELOPMENTONLY*/, "Show the (client) animation state for the specified entity (-1 for none)." ); + ConVar showanimstate_log( "cl_showanimstate_log", "0", FCVAR_CHEAT /*| FCVAR_DEVELOPMENTONLY*/, "1 to output cl_showanimstate to Msg(). 2 to store in AnimStateClient.log. 3 for both." ); #else #include "player.h" - ConVar sv_showanimstate( "sv_showanimstate", "-1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Show the (server) animation state for the specified entity (-1 for none)." ); - ConVar showanimstate_log( "sv_showanimstate_log", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "1 to output sv_showanimstate to Msg(). 2 to store in AnimStateServer.log. 3 for both." ); + ConVar sv_showanimstate( "sv_showanimstate", "-1", FCVAR_CHEAT /*| FCVAR_DEVELOPMENTONLY*/, "Show the (server) animation state for the specified entity (-1 for none)." ); + ConVar showanimstate_log( "sv_showanimstate_log", "0", FCVAR_CHEAT /*| FCVAR_DEVELOPMENTONLY*/, "1 to output sv_showanimstate to Msg(). 2 to store in AnimStateServer.log. 3 for both." ); #endif @@ -654,9 +654,11 @@ void CBasePlayerAnimState::ComputePoseParam_MoveYaw( CStudioHdr *pStudioHdr ) if ( m_AnimConfig.m_LegAnimType == LEGANIM_9WAY ) { +#ifndef MAPBASE // This causes problems with entities that rely on the player having a pitch (TODO: gate behind virtual function?) #ifndef CLIENT_DLL //Adrian: Make the model's angle match the legs so the hitboxes match on both sides. GetOuter()->SetLocalAngles( QAngle( 0, m_flCurrentFeetYaw, 0 ) ); +#endif #endif int iMoveX = GetOuter()->LookupPoseParameter( pStudioHdr, "move_x" ); @@ -942,6 +944,15 @@ void CBasePlayerAnimState::GetOuterAbsVelocity( Vector& vel ) const #else vel = GetOuter()->GetAbsVelocity(); #endif + +#ifdef MAPBASE + if (GetOuter()->IsPlayer()) + { + CBasePlayer *pPlayer = ToBasePlayer( GetOuter() ); + if (pPlayer->GetLaggedMovementValue() != 1.0f) + vel *= pPlayer->GetLaggedMovementValue(); + } +#endif } @@ -1024,9 +1035,15 @@ void CBasePlayerAnimState::DebugShowAnimState( int iStartLine ) (float)pLayer->m_flWeight ); } +#ifdef MAPBASE + for ( int i=0; i < m_pOuter->GetNumAnimOverlays(); i++ ) + { + CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( i ); +#else for ( int i=0; i < m_pOuter->GetNumAnimOverlays()-1; i++ ) { CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( AIMSEQUENCE_LAYER + i ); +#endif #ifdef CLIENT_DLL AnimStatePrintf( iLine++, "%s(%d), weight: %.2f, cycle: %.2f, order (%d), aim (%d)", !pLayer->IsActive() ? "-- ": (pLayer->m_nSequence == 0 ? "-- " : GetSequenceName( m_pOuter->GetModelPtr(), pLayer->m_nSequence ) ), diff --git a/sp/src/game/shared/basecombatweapon_shared.h b/sp/src/game/shared/basecombatweapon_shared.h index ee90c7c5..d7fd5c47 100644 --- a/sp/src/game/shared/basecombatweapon_shared.h +++ b/sp/src/game/shared/basecombatweapon_shared.h @@ -307,6 +307,10 @@ public: virtual bool ShouldBlockPrimaryFire() { return false; } #ifdef CLIENT_DLL +#ifdef MAPBASE + virtual bool DispatchMuzzleEffect( const char *options, bool isFirstPerson ); +#endif + virtual void CreateMove( float flInputSampleTime, CUserCmd *pCmd, const QAngle &vecOldViewAngles ) {} virtual int CalcOverrideModelIndex() OVERRIDE; #endif diff --git a/sp/src/game/shared/baseentity_shared.cpp b/sp/src/game/shared/baseentity_shared.cpp index 5f957612..8f5660a2 100644 --- a/sp/src/game/shared/baseentity_shared.cpp +++ b/sp/src/game/shared/baseentity_shared.cpp @@ -3033,7 +3033,7 @@ void CBaseEntity::ScriptSetContextThink( const char* szContext, HSCRIPT hFunc, f float nextthink = gpGlobals->curtime + flTime; - pf->m_hfnThink = hFunc; + pf->m_hfnThink = g_pScriptVM->CopyObject( hFunc ); pf->m_flNextThink = nextthink; #ifdef GAME_DLL diff --git a/sp/src/game/shared/baseentity_shared.h b/sp/src/game/shared/baseentity_shared.h index 85a0ffd8..4891b0bc 100644 --- a/sp/src/game/shared/baseentity_shared.h +++ b/sp/src/game/shared/baseentity_shared.h @@ -255,7 +255,7 @@ inline HSCRIPT ToHScript(CBaseEntity* pEnt) return (pEnt) ? pEnt->GetScriptInstance() : NULL; } -template <> ScriptClassDesc_t* GetScriptDesc(CBaseEntity*); +template <> ScriptClassDesc_t* GetScriptDesc(CBaseEntity*, bool); inline CBaseEntity* ToEnt(HSCRIPT hScript) { return (hScript) ? (CBaseEntity*)g_pScriptVM->GetInstanceValue(hScript, GetScriptDescForClass(CBaseEntity)) : NULL; diff --git a/sp/src/game/shared/mapbase/singleplayer_animstate.cpp b/sp/src/game/shared/mapbase/mapbase_playeranimstate.cpp similarity index 66% rename from sp/src/game/shared/mapbase/singleplayer_animstate.cpp rename to sp/src/game/shared/mapbase/mapbase_playeranimstate.cpp index 7c8b92c9..246da424 100644 --- a/sp/src/game/shared/mapbase/singleplayer_animstate.cpp +++ b/sp/src/game/shared/mapbase/mapbase_playeranimstate.cpp @@ -15,7 +15,7 @@ //=============================================================================// #include "cbase.h" -#include "singleplayer_animstate.h" +#include "mapbase_playeranimstate.h" #include "tier0/vprof.h" #include "animation.h" #include "studio.h" @@ -23,25 +23,33 @@ #include "utldict.h" #include "filesystem.h" #include "in_buttons.h" +#include "gamemovement.h" #include "datacache/imdlcache.h" +#ifdef CLIENT_DLL +#include "input.h" +#endif -extern ConVar mp_facefronttime, mp_feetyawrate, mp_ik; +extern ConVar mp_facefronttime, mp_feetyawrate; -ConVar sv_playeranimstate_animtype( "sv_playeranimstate_animtype", "0", FCVAR_NONE, "The leg animation type used by the singleplayer animation state. 9way = 0, 8way = 1, GoldSrc = 2" ); -ConVar sv_playeranimstate_bodyyaw( "sv_playeranimstate_bodyyaw", "45.0", FCVAR_NONE, "The maximum body yaw used by the singleplayer animation state." ); -ConVar sv_playeranimstate_use_aim_sequences( "sv_playeranimstate_use_aim_sequences", "1", FCVAR_NONE, "Allows the singleplayer animation state to use aim sequences." ); +ConVar sv_playeranimstate_animtype( "sv_playeranimstate_animtype", "0", FCVAR_NONE, "The leg animation type used by the Mapbase animation state. 9way = 0, 8way = 1, GoldSrc = 2" ); +ConVar sv_playeranimstate_bodyyaw( "sv_playeranimstate_bodyyaw", "45.0", FCVAR_NONE, "The maximum body yaw used by the Mapbase animation state." ); +ConVar sv_playeranimstate_use_aim_sequences( "sv_playeranimstate_use_aim_sequences", "0", FCVAR_NONE, "Allows the Mapbase animation state to use aim sequences." ); +ConVar sv_playeranimstate_use_walk_anims( "sv_playeranimstate_use_walk_anims", "0", FCVAR_NONE, "Allows the Mapbase animation state to use walk animations when the player is walking." ); #define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f +#define WEAPON_RELAX_TIME 0.5f + #define FIRESEQUENCE_LAYER (AIMSEQUENCE_LAYER+NUM_AIMSEQUENCE_LAYERS) #define RELOADSEQUENCE_LAYER (FIRESEQUENCE_LAYER + 1) -#define NUM_LAYERS_WANTED (RELOADSEQUENCE_LAYER + 1) +#define MISCSEQUENCE_LAYER (RELOADSEQUENCE_LAYER + 1) +#define NUM_LAYERS_WANTED (MISCSEQUENCE_LAYER + 1) -CSinglePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer ) +CMapbasePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer ) { MDLCACHE_CRITICAL_SECTION(); - CSinglePlayerAnimState *pState = new CSinglePlayerAnimState( pPlayer ); + CMapbasePlayerAnimState *pState = new CMapbasePlayerAnimState( pPlayer ); // Setup the movement data. CModAnimConfig movementData; @@ -65,22 +73,25 @@ CSinglePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer ) extern ConVar sv_backspeed; extern ConVar mp_feetyawrate; extern ConVar mp_facefronttime; -extern ConVar mp_ik; -CSinglePlayerAnimState::CSinglePlayerAnimState( CBasePlayer *pPlayer ): m_pPlayer( pPlayer ) +CMapbasePlayerAnimState::CMapbasePlayerAnimState( CBasePlayer *pPlayer ): m_pPlayer( pPlayer ) { }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -Activity CSinglePlayerAnimState::CalcMainActivity() +Activity CMapbasePlayerAnimState::CalcMainActivity() { -#ifdef CLIENT_DLL - return ACT_IDLE; -#else float speed = GetOuter()->GetAbsVelocity().Length2D(); + if (m_pPlayer->GetLaggedMovementValue() != 1.0f) + speed *= m_pPlayer->GetLaggedMovementValue(); + + // May not always be precise + if (speed < 0.01f) + speed = 0.0f; + if ( HandleJumping() ) { return ACT_HL2MP_JUMP; @@ -95,7 +106,24 @@ Activity CSinglePlayerAnimState::CalcMainActivity() } else { - if ( GetOuter()->GetFlags() & FL_DUCKING ) + bool bDucking = (GetOuter()->GetFlags() & FL_DUCKING) ? true : false; + + // (currently singleplayer-exclusive since clients can't read whether other players are holding down IN_DUCK) + if (m_pPlayer->m_Local.m_flDucktime > 0 && gpGlobals->maxClients == 1) + { + // Consider ducking if half-way through duck time + bDucking = (m_pPlayer->m_Local.m_flDucktime < (GAMEMOVEMENT_DUCK_TIME * 0.9f)); + + // Unducking +#ifdef CLIENT_DLL + if (!((m_pPlayer->IsLocalPlayer() ? input->GetButtonBits( 0 ) : m_pPlayer->GetCurrentUserCommand()->buttons) & IN_DUCK)) +#else + if (!(m_pPlayer->m_nButtons & IN_DUCK)) +#endif + bDucking = !bDucking; + } + + if ( bDucking ) { if ( speed > 0 ) { @@ -111,7 +139,7 @@ Activity CSinglePlayerAnimState::CalcMainActivity() if ( speed > 0 ) { #if EXPANDED_HL2DM_ACTIVITIES - if ( m_pPlayer->GetButtons() & IN_WALK ) + if ( m_pPlayer->m_nButtons & IN_WALK && sv_playeranimstate_use_walk_anims.GetBool() ) { idealActivity = ACT_HL2MP_WALK; } @@ -132,19 +160,22 @@ Activity CSinglePlayerAnimState::CalcMainActivity() } //return m_pPlayer->GetActivity(); -#endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CSinglePlayerAnimState::SetPlayerAnimation( PLAYER_ANIM playerAnim ) +void CMapbasePlayerAnimState::SetPlayerAnimation( PLAYER_ANIM playerAnim ) { if ( playerAnim == PLAYER_ATTACK1 ) { m_iFireSequence = SelectWeightedSequence( TranslateActivity( ACT_HL2MP_GESTURE_RANGE_ATTACK ) ); m_bFiring = m_iFireSequence != -1; m_flFireCycle = 0; + + // Be sure to stop reloading + m_bReloading = false; + m_flReloadCycle = 0; } else if ( playerAnim == PLAYER_ATTACK2 ) { @@ -155,6 +186,10 @@ void CSinglePlayerAnimState::SetPlayerAnimation( PLAYER_ANIM playerAnim ) #endif m_bFiring = m_iFireSequence != -1; m_flFireCycle = 0; + + // Be sure to stop reloading + m_bReloading = false; + m_flReloadCycle = 0; } else if ( playerAnim == PLAYER_JUMP ) { @@ -162,6 +197,7 @@ void CSinglePlayerAnimState::SetPlayerAnimation( PLAYER_ANIM playerAnim ) if (!m_bJumping) { m_bJumping = true; + m_bDuckJumping = (GetOuter()->GetFlags() & FL_DUCKING) ? true : false; //m_pPlayer->m_nButtons & IN_DUCK; m_bFirstJumpFrame = true; m_flJumpStartTime = gpGlobals->curtime; } @@ -184,13 +220,13 @@ void CSinglePlayerAnimState::SetPlayerAnimation( PLAYER_ANIM playerAnim ) if (m_iWeaponSwitchSequence != -1) { // clear other events that might be playing in our layer - m_bPlayingMisc = false; + //m_bPlayingMisc = false; m_bReloading = false; m_bWeaponSwitching = true; m_flWeaponSwitchCycle = 0; - m_flMiscBlendOut = 0.1f; - m_flMiscBlendIn = 0.1f; + //m_flMiscBlendOut = 0.1f; + //m_flMiscBlendIn = 0.1f; m_bMiscNoOverride = false; } } @@ -199,10 +235,10 @@ void CSinglePlayerAnimState::SetPlayerAnimation( PLAYER_ANIM playerAnim ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -Activity CSinglePlayerAnimState::TranslateActivity( Activity actDesired ) +Activity CMapbasePlayerAnimState::TranslateActivity( Activity actDesired ) { -#ifdef CLIENT_DLL - return actDesired; +#if defined(CLIENT_DLL) && !defined(MAPBASE_MP) + return actDesired; #else return m_pPlayer->Weapon_TranslateActivity( actDesired ); #endif @@ -211,7 +247,7 @@ Activity CSinglePlayerAnimState::TranslateActivity( Activity actDesired ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -bool CSinglePlayerAnimState::HandleJumping() +bool CMapbasePlayerAnimState::HandleJumping() { if ( m_bJumping ) { @@ -230,6 +266,7 @@ bool CSinglePlayerAnimState::HandleJumping() if ( m_pOuter->GetFlags() & FL_ONGROUND || GetOuter()->GetGroundEntity() != NULL) { m_bJumping = false; + m_bDuckJumping = false; RestartMainSequence(); // Reset the animation. } } @@ -242,20 +279,21 @@ bool CSinglePlayerAnimState::HandleJumping() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CSinglePlayerAnimState::ComputeSequences( CStudioHdr *pStudioHdr ) +void CMapbasePlayerAnimState::ComputeSequences( CStudioHdr *pStudioHdr ) { CBasePlayerAnimState::ComputeSequences(pStudioHdr); ComputeFireSequence(); ComputeMiscSequence(); ComputeReloadSequence(); - ComputeWeaponSwitchSequence(); + ComputeWeaponSwitchSequence(); + ComputeRelaxSequence(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CSinglePlayerAnimState::AddMiscSequence( int iSequence, float flBlendIn, float flBlendOut, float flPlaybackRate, bool bHoldAtEnd, bool bOnlyWhenStill ) +void CMapbasePlayerAnimState::AddMiscSequence( int iSequence, float flBlendIn, float flBlendOut, float flPlaybackRate, bool bHoldAtEnd, bool bOnlyWhenStill ) { Assert( iSequence != -1 ); @@ -265,7 +303,7 @@ void CSinglePlayerAnimState::AddMiscSequence( int iSequence, float flBlendIn, fl m_bPlayingMisc = true; m_bMiscHoldAtEnd = bHoldAtEnd; - m_bReloading = false; + //m_bReloading = false; m_flMiscCycle = 0; m_bMiscOnlyWhenStill = bOnlyWhenStill; m_bMiscNoOverride = true; @@ -275,12 +313,36 @@ void CSinglePlayerAnimState::AddMiscSequence( int iSequence, float flBlendIn, fl //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CSinglePlayerAnimState::ClearAnimationState() +void CMapbasePlayerAnimState::StartWeaponRelax() +{ + if (m_bWeaponRelaxing) + return; + + m_bWeaponRelaxing = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMapbasePlayerAnimState::StopWeaponRelax() +{ + if (!m_bWeaponRelaxing) + return; + + m_bWeaponRelaxing = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMapbasePlayerAnimState::ClearAnimationState() { m_bJumping = false; + m_bDuckJumping = false; m_bFiring = false; m_bReloading = false; m_bWeaponSwitching = false; + m_bWeaponRelaxing = false; m_bPlayingMisc = false; m_flReloadBlendIn = 0.0f; m_flReloadBlendOut = 0.0f; @@ -289,32 +351,42 @@ void CSinglePlayerAnimState::ClearAnimationState() CBasePlayerAnimState::ClearAnimationState(); } -void CSinglePlayerAnimState::ClearAnimationLayers() +void CMapbasePlayerAnimState::ClearAnimationLayers() { VPROF( "CBasePlayerAnimState::ClearAnimationLayers" ); + + // In c_baseanimatingoverlay.cpp, this sometimes desyncs from the interpolated overlays and causes a crash in ResizeAnimationLayerCallback when the player dies. (pVec->Count() != pVecIV->Count()) + // Is there a better way of getting around this issue? +#ifndef CLIENT_DLL if ( !m_pOuter ) return; m_pOuter->SetNumAnimOverlays( NUM_LAYERS_WANTED ); for ( int i=0; i < m_pOuter->GetNumAnimOverlays(); i++ ) { + // If we're not using aim sequences, leave the aim layers alone + // (allows them to be used outside of anim state) + if ( !m_AnimConfig.m_bUseAimSequences && i <= NUM_AIMSEQUENCE_LAYERS ) + continue; + m_pOuter->GetAnimOverlay( i )->SetOrder( CBaseAnimatingOverlay::MAX_OVERLAYS ); #ifndef CLIENT_DLL m_pOuter->GetAnimOverlay( i )->m_fFlags = 0; #endif } +#endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -int CSinglePlayerAnimState::CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle ) +int CMapbasePlayerAnimState::CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle ) { // TODO? return m_pOuter->LookupSequence( "soldier_Aim_9_directions" ); } -void CSinglePlayerAnimState::UpdateLayerSequenceGeneric( int iLayer, bool &bEnabled, +void CMapbasePlayerAnimState::UpdateLayerSequenceGeneric( int iLayer, bool &bEnabled, float &flCurCycle, int &iSequence, bool bWaitAtEnd, float fBlendIn, float fBlendOut, bool bMoveBlend, float fPlaybackRate, bool bUpdateCycle /* = true */ ) { @@ -383,7 +455,7 @@ void CSinglePlayerAnimState::UpdateLayerSequenceGeneric( int iLayer, bool &bEnab pLayer->m_flPlaybackRate = fPlaybackRate; pLayer->m_flWeight = 1.0f; - if (iLayer == RELOADSEQUENCE_LAYER) + if (fBlendIn > 0.0f || fBlendOut > 0.0f) { // blend this layer in and out for smooth reloading if (flCurCycle < fBlendIn && fBlendIn>0) @@ -418,25 +490,91 @@ void CSinglePlayerAnimState::UpdateLayerSequenceGeneric( int iLayer, bool &bEnab pLayer->SetOrder( iLayer ); } -void CSinglePlayerAnimState::ComputeFireSequence() +void CMapbasePlayerAnimState::ComputeFireSequence() { UpdateLayerSequenceGeneric( FIRESEQUENCE_LAYER, m_bFiring, m_flFireCycle, m_iFireSequence, false ); } -void CSinglePlayerAnimState::ComputeReloadSequence() +void CMapbasePlayerAnimState::ComputeReloadSequence() { UpdateLayerSequenceGeneric( RELOADSEQUENCE_LAYER, m_bReloading, m_flReloadCycle, m_iReloadSequence, false, m_flReloadBlendIn, m_flReloadBlendOut, false, m_fReloadPlaybackRate ); } -void CSinglePlayerAnimState::ComputeWeaponSwitchSequence() +void CMapbasePlayerAnimState::ComputeWeaponSwitchSequence() { UpdateLayerSequenceGeneric( RELOADSEQUENCE_LAYER, m_bWeaponSwitching, m_flWeaponSwitchCycle, m_iWeaponSwitchSequence, false, 0, 0.5f ); } -// does misc gestures if we're not firing -void CSinglePlayerAnimState::ComputeMiscSequence() +void CMapbasePlayerAnimState::ComputeRelaxSequence() { - UpdateLayerSequenceGeneric( RELOADSEQUENCE_LAYER, m_bPlayingMisc, m_flMiscCycle, m_iMiscSequence, m_bMiscHoldAtEnd, m_flMiscBlendIn, m_flMiscBlendOut, m_bMiscOnlyWhenStill, m_fMiscPlaybackRate ); + bool bRelaxing = m_bWeaponRelaxing; + float flRelaxSpeed = 0.05f; + + if ((m_bFiring && m_flFireCycle < 1.0f) || m_bReloading) + { + // Keep weapon raised + bRelaxing = false; + flRelaxSpeed = 0.5f; + //GetOuter()->SetPoseParameter( GetOuter()->LookupPoseParameter( "weapon_lower" ), 0.0f ); + } + + if (bRelaxing ? m_flWeaponRelaxAmount != 1.0f : m_flWeaponRelaxAmount != 0.0f) + { + if (bRelaxing) + m_flWeaponRelaxAmount += flRelaxSpeed; + else + m_flWeaponRelaxAmount -= flRelaxSpeed; + + m_flWeaponRelaxAmount = clamp( m_flWeaponRelaxAmount, 0.0f, 1.0f ); + + GetOuter()->SetPoseParameter( GetOuter()->LookupPoseParameter( "weapon_lower" ), m_flWeaponRelaxAmount ); + + /*int nPose = GetOuter()->LookupPoseParameter( "weapon_lower" ); + if (nPose != -1) + { + float flValue = RemapValClamped( (m_flWeaponRelaxTime - gpGlobals->curtime), 0.0f, 0.5f, 0.0f, 1.0f ); + + if (flValue <= 0.0f) + { + // All done + m_flWeaponRelaxTime = FLT_MAX; + } + + if (m_bWeaponRelaxing) + flValue = 1.0f - flValue; + + GetOuter()->SetPoseParameter( nPose, SimpleSpline( flValue ) ); + }*/ + } + else if (bRelaxing) + { + GetOuter()->SetPoseParameter( GetOuter()->LookupPoseParameter( "weapon_lower" ), 1.0f ); + } + + /*bool bEnabled = m_bWeaponRelaxing; + bool bUpdateCycle = true; + if (bEnabled) + { + if (m_flWeaponRelaxCycle >= 0.5f) + { + // Pause at 0.5 + m_flWeaponRelaxCycle = 0.5f; + bUpdateCycle = false; + } + } + else if (m_flWeaponRelaxCycle < 1.0f) + { + // Make sure we exit the relax + bEnabled = true; + } + + UpdateLayerSequenceGeneric( AIMSEQUENCE_LAYER, bEnabled, m_flWeaponRelaxCycle, m_iWeaponRelaxSequence, false, 0.5f, 0.5f, false, 1.0f, bUpdateCycle );*/ +} + +// does misc gestures if we're not firing +void CMapbasePlayerAnimState::ComputeMiscSequence() +{ + UpdateLayerSequenceGeneric( MISCSEQUENCE_LAYER, m_bPlayingMisc, m_flMiscCycle, m_iMiscSequence, m_bMiscHoldAtEnd, m_flMiscBlendIn, m_flMiscBlendOut, m_bMiscOnlyWhenStill, m_fMiscPlaybackRate ); } //----------------------------------------------------------------------------- @@ -444,7 +582,7 @@ void CSinglePlayerAnimState::ComputeMiscSequence() // Input : - // Output : float //----------------------------------------------------------------------------- -float CSinglePlayerAnimState::GetCurrentMaxGroundSpeed() +float CMapbasePlayerAnimState::GetCurrentMaxGroundSpeed() { CStudioHdr *pStudioHdr = GetOuter()->GetModelPtr(); @@ -481,11 +619,42 @@ float CSinglePlayerAnimState::GetCurrentMaxGroundSpeed() return speed; } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline bool CMapbasePlayerAnimState::ShouldUseAimPoses( void ) const +{ + return GetAimPoseBlend() > 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CMapbasePlayerAnimState::GetAimPoseBlend( void ) const +{ + if (!GetOuter()->MyCombatCharacterPointer() || !GetOuter()->MyCombatCharacterPointer()->GetActiveWeapon() + || GetOuter()->MyCombatCharacterPointer()->GetActiveWeapon()->IsEffectActive( EF_NODRAW )) + return 0.0f; + + return 1.0f - m_flWeaponRelaxAmount; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CMapbasePlayerAnimState::SetOuterBodyYaw( float flValue ) +{ + float flAimPoseBlend = GetAimPoseBlend(); + + GetOuter()->SetPoseParameter( GetOuter()->LookupPoseParameter( "aim_yaw" ), flValue * flAimPoseBlend ); + return CBasePlayerAnimState::SetOuterBodyYaw( flValue * (1.0f - flAimPoseBlend) ); +} + //----------------------------------------------------------------------------- // Purpose: Override for backpeddling // Input : dt - //----------------------------------------------------------------------------- -void CSinglePlayerAnimState::ComputePoseParam_BodyYaw( void ) +void CMapbasePlayerAnimState::ComputePoseParam_BodyYaw( void ) { CBasePlayerAnimState::ComputePoseParam_BodyYaw(); @@ -495,7 +664,7 @@ void CSinglePlayerAnimState::ComputePoseParam_BodyYaw( void ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CSinglePlayerAnimState::ComputePoseParam_BodyLookYaw( void ) +void CMapbasePlayerAnimState::ComputePoseParam_BodyLookYaw( void ) { // See if we even have a blender for pitch int upper_body_yaw = GetOuter()->LookupPoseParameter( "aim_yaw" ); @@ -632,7 +801,7 @@ void CSinglePlayerAnimState::ComputePoseParam_BodyLookYaw( void ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CSinglePlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ) +void CMapbasePlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ) { // Get pitch from v_angle float flPitch = m_flEyePitch; @@ -643,16 +812,19 @@ void CSinglePlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr } flPitch = clamp( flPitch, -90, 90 ); + //float flAimPoseBlend = GetAimPoseBlend(); + // See if we have a blender for pitch GetOuter()->SetPoseParameter( pStudioHdr, "aim_pitch", flPitch ); + GetOuter()->SetPoseParameter( pStudioHdr, "head_pitch", flPitch ); - ComputePoseParam_HeadPitch( pStudioHdr ); + //ComputePoseParam_HeadPitch( pStudioHdr ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CSinglePlayerAnimState::ComputePoseParam_HeadPitch( CStudioHdr *pStudioHdr ) +void CMapbasePlayerAnimState::ComputePoseParam_HeadPitch( CStudioHdr *pStudioHdr ) { // Get pitch from v_angle int iHeadPitch = GetOuter()->LookupPoseParameter("head_pitch"); diff --git a/sp/src/game/shared/mapbase/singleplayer_animstate.h b/sp/src/game/shared/mapbase/mapbase_playeranimstate.h similarity index 80% rename from sp/src/game/shared/mapbase/singleplayer_animstate.h rename to sp/src/game/shared/mapbase/mapbase_playeranimstate.h index a805dffc..5e3bf514 100644 --- a/sp/src/game/shared/mapbase/singleplayer_animstate.h +++ b/sp/src/game/shared/mapbase/mapbase_playeranimstate.h @@ -14,8 +14,8 @@ // //=============================================================================// -#ifndef SINGLEPLAYER_ANIMSTATE_H -#define SINGLEPLAYER_ANIMSTATE_H +#ifndef MAPBASE_PLAYERANIMSTATE_H +#define MAPBASE_PLAYERANIMSTATE_H #ifdef _WIN32 #pragma once #endif @@ -34,10 +34,10 @@ #define SP_ANIM_STATE 1 #endif -class CSinglePlayerAnimState : public CBasePlayerAnimState +class CMapbasePlayerAnimState : public CBasePlayerAnimState { public: - CSinglePlayerAnimState( CBasePlayer *pPlayer ); + CMapbasePlayerAnimState( CBasePlayer *pPlayer ); Activity CalcMainActivity(); int CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle ); @@ -49,10 +49,16 @@ public: void ComputeSequences( CStudioHdr *pStudioHdr ); void AddMiscSequence( int iSequence, float flBlendIn = 0.0f, float flBlendOut = 0.0f, float flPlaybackRate = 1.0f, bool bHoldAtEnd = false, bool bOnlyWhenStill = false ); + + void StartWeaponRelax(); + void StopWeaponRelax(); void ClearAnimationState(); void ClearAnimationLayers(); + inline bool IsJumping() const { return m_bJumping; } + inline bool IsDuckJumping() const { return m_bDuckJumping; } + private: bool HandleJumping(); @@ -60,13 +66,19 @@ private: void ComputeFireSequence(); void ComputeReloadSequence(); void ComputeWeaponSwitchSequence(); + void ComputeRelaxSequence(); void ComputeMiscSequence(); void UpdateLayerSequenceGeneric( int iLayer, bool &bEnabled, float &flCurCycle, int &iSequence, bool bWaitAtEnd, - float fBlendIn=0.15f, float fBlendOut=0.15f, bool bMoveBlend = false, + float fBlendIn=0.0f, float fBlendOut=0.0f, bool bMoveBlend = false, float fPlaybackRate=1.0f, bool bUpdateCycle = true ); + bool ShouldUseAimPoses() const; + float GetAimPoseBlend() const; + + float SetOuterBodyYaw( float flValue ); + void ComputePoseParam_BodyYaw( void ); void ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ); void ComputePoseParam_BodyLookYaw( void ); @@ -76,6 +88,7 @@ private: // Current state variables. bool m_bJumping; // Set on a jump event. + bool m_bDuckJumping; // Jump started while ducking float m_flJumpStartTime; bool m_bFirstJumpFrame; @@ -90,6 +103,9 @@ private: float m_flWeaponSwitchCycle; int m_iWeaponSwitchSequence; + bool m_bWeaponRelaxing; + float m_flWeaponRelaxAmount; + bool m_bPlayingMisc; float m_flMiscCycle, m_flMiscBlendOut, m_flMiscBlendIn; int m_iMiscSequence; @@ -105,6 +121,6 @@ private: float m_flFireCycle; }; -CSinglePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer ); +CMapbasePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer ); -#endif // SINGLEPLAYER_ANIMSTATE_H +#endif // MAPBASE_PLAYERANIMSTATE_H diff --git a/sp/src/game/shared/mapbase/mapbase_shared.cpp b/sp/src/game/shared/mapbase/mapbase_shared.cpp index 5ef52b2b..2345d9ca 100644 --- a/sp/src/game/shared/mapbase/mapbase_shared.cpp +++ b/sp/src/game/shared/mapbase/mapbase_shared.cpp @@ -29,6 +29,7 @@ #include "AI_ResponseSystem.h" #include "mapbase/SystemConvarMod.h" #include "gameinterface.h" +#include "mapbase/choreosentence.h" #endif #if defined(HL2_DLL) || defined(HL2_CLIENT_DLL) #include "protagonist_system.h" @@ -88,6 +89,7 @@ char g_iszGameName[128]; #ifdef GAME_DLL // Default player configuration char g_szDefaultPlayerModel[MAX_PATH]; +bool g_bDefaultPlayerLegs; bool g_bDefaultPlayerDrawExternally; char g_szDefaultHandsModel[MAX_PATH]; @@ -115,7 +117,7 @@ enum MANIFEST_HUDLAYOUT, #else MANIFEST_TALKER, - //MANIFEST_SENTENCES, + MANIFEST_CHOREOSENTENCES, MANIFEST_ACTBUSY, #endif #ifdef MAPBASE_VSCRIPT @@ -158,7 +160,7 @@ static const ManifestType_t gm_szManifestFileStrings[MANIFEST_NUM_TYPES] = { { "hudlayout", "mapbase_load_hudlayout", "Should we load map-specific HUD layout overrides? e.g. \"maps/_hudlayout.res\"" }, #else { "talker", "mapbase_load_talker", "Should we load map-specific talker files? e.g. \"maps/_talker.txt\"" }, - //{ "sentences", "mapbase_load_sentences", "Should we load map-specific sentences? e.g. \"maps/_sentences.txt\"" }, + { "choreosentences", "mapbase_load_choreosentences", "Should we load map-specific choreo sentences? e.g. \"maps/_choreosentences.txt\"" }, { "actbusy", "mapbase_load_actbusy", "Should we load map-specific actbusy files? e.g. \"maps/_actbusy.txt\"" }, #endif #ifdef MAPBASE_VSCRIPT @@ -249,6 +251,7 @@ public: #ifdef GAME_DLL Q_strncpy( g_szDefaultPlayerModel, gameinfo->GetString( "player_default_model", "models/player.mdl" ), sizeof( g_szDefaultPlayerModel ) ); + g_bDefaultPlayerLegs = gameinfo->GetBool( "player_default_legs", false ); g_bDefaultPlayerDrawExternally = gameinfo->GetBool( "player_default_draw_externally", false ); Q_strncpy( g_szDefaultHandsModel, gameinfo->GetString( "player_default_hands", "models/weapons/v_hands.mdl" ), sizeof( g_szDefaultHandsModel ) ); @@ -483,7 +486,7 @@ public: LoadResponseSystemFile(value); //PrecacheCustomResponseSystem( value ); } break; //case MANIFEST_SOUNDSCAPES: { g_SoundscapeSystem.AddSoundscapeFile(value); } break; - //case MANIFEST_SENTENCES: { engine->PrecacheSentenceFile(value); } break; + case MANIFEST_CHOREOSENTENCES: { LoadChoreoSentenceFile(value); } break; case MANIFEST_ACTBUSY: { ParseCustomActbusyFile(value); } break; #endif #ifdef MAPBASE_VSCRIPT @@ -631,6 +634,7 @@ public: #else void LoadCustomTalkerFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_TALKER, false ); } void LoadCustomActbusyFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_ACTBUSY, false ); } + void LoadCustomChoreoSentenceFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_CHOREOSENTENCES, false ); } #endif const char *GetModName() { return g_iszGameName; } @@ -679,6 +683,7 @@ BEGIN_SCRIPTDESC_ROOT( CMapbaseSystem, SCRIPT_SINGLETON "All-purpose Mapbase sys #else DEFINE_SCRIPTFUNC( LoadCustomTalkerFile, "Loads a custom talker file." ) DEFINE_SCRIPTFUNC( LoadCustomActbusyFile, "Loads a custom actbusy file." ) + DEFINE_SCRIPTFUNC( LoadCustomChoreoSentenceFile, "Loads a custom choreo sentence file." ) #endif DEFINE_SCRIPTFUNC( GetModName, "Gets the name of the mod. This is the name which shows up on Steam, RPC, etc." ) diff --git a/sp/src/game/shared/mapbase/vscript_consts_shared.cpp b/sp/src/game/shared/mapbase/vscript_consts_shared.cpp index bb47bcb1..2998aada 100644 --- a/sp/src/game/shared/mapbase/vscript_consts_shared.cpp +++ b/sp/src/game/shared/mapbase/vscript_consts_shared.cpp @@ -14,6 +14,7 @@ #include "c_ai_basenpc.h" #else #include "ai_basenpc.h" +#include "ai_senses.h" #include "globalstate.h" #endif @@ -357,7 +358,8 @@ void RegisterSharedScriptConstants() ScriptRegisterConstant( g_pScriptVM, ROPE_NO_GRAVITY, "Disable gravity on this rope. (for use in rope flags)" ); ScriptRegisterConstant( g_pScriptVM, ROPE_NUMFLAGS, "The number of rope flags recognized by the game." ); - ScriptRegisterConstantNamed( g_pScriptVM, Vector( ROPE_GRAVITY ), "ROPE_GRAVITY", "Default rope gravity vector." ); + static Vector vecRopeGravity( ROPE_GRAVITY ); + ScriptRegisterConstantNamed( g_pScriptVM, vecRopeGravity, "ROPE_GRAVITY", "Default rope gravity vector." ); // // Sounds @@ -427,8 +429,8 @@ void RegisterSharedScriptConstants() #ifdef GAME_DLL // - // AI Sounds - // (QueryHearSound hook can use these) + // AI Senses + // (NPC hooks can use these) // ScriptRegisterConstant( g_pScriptVM, SOUND_NONE, "Sound type used in QueryHearSound hooks, etc." ); ScriptRegisterConstant( g_pScriptVM, SOUND_COMBAT, "Sound type used in QueryHearSound hooks, etc." ); @@ -481,6 +483,11 @@ void RegisterSharedScriptConstants() ScriptRegisterConstantNamed( g_pScriptVM, (int)SOUNDENT_VOLUME_PISTOL, "SOUNDENT_VOLUME_PISTOL", "Sound volume preset for use in InsertAISound, etc." ); ScriptRegisterConstantNamed( g_pScriptVM, (int)SOUNDENT_VOLUME_EMPTY, "SOUNDENT_VOLUME_PISTOL", "Sound volume preset for use in InsertAISound, etc." ); + ScriptRegisterConstantNamed( g_pScriptVM, (int)SEEN_ALL, "SEEN_ALL", "All NPC sight arrays. Used in GetFirstSeenEntity, etc." ); + ScriptRegisterConstantNamed( g_pScriptVM, (int)SEEN_HIGH_PRIORITY, "SEEN_HIGH_PRIORITY", "NPC sight array for players. Used in GetFirstSeenEntity, etc." ); + ScriptRegisterConstantNamed( g_pScriptVM, (int)SEEN_NPCS, "SEEN_NPCS", "NPC sight array for other NPCs. Used in GetFirstSeenEntity, etc." ); + ScriptRegisterConstantNamed( g_pScriptVM, (int)SEEN_MISC, "SEEN_MISC", "NPC sight array for objects. Used in GetFirstSeenEntity, etc." ); + // // Capabilities // diff --git a/sp/src/game/shared/mapbase/vscript_funcs_shared.cpp b/sp/src/game/shared/mapbase/vscript_funcs_shared.cpp index 227103df..7260029f 100644 --- a/sp/src/game/shared/mapbase/vscript_funcs_shared.cpp +++ b/sp/src/game/shared/mapbase/vscript_funcs_shared.cpp @@ -255,11 +255,10 @@ void ScriptDispatchSpawn( HSCRIPT hEntity ) //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- -static HSCRIPT CreateDamageInfo( HSCRIPT hInflictor, HSCRIPT hAttacker, const Vector &vecForce, const Vector &vecDamagePos, float flDamage, int iDamageType ) +static HSCRIPT_RC CreateDamageInfo( HSCRIPT hInflictor, HSCRIPT hAttacker, const Vector &vecForce, const Vector &vecDamagePos, float flDamage, int iDamageType ) { - // The script is responsible for deleting this via DestroyDamageInfo(). CTakeDamageInfo *damageInfo = new CTakeDamageInfo( ToEnt(hInflictor), ToEnt(hAttacker), flDamage, iDamageType ); - HSCRIPT hScript = g_pScriptVM->RegisterInstance( damageInfo ); + HSCRIPT hScript = g_pScriptVM->RegisterInstance( damageInfo, true ); damageInfo->SetDamagePosition( vecDamagePos ); damageInfo->SetDamageForce( vecForce ); @@ -267,14 +266,8 @@ static HSCRIPT CreateDamageInfo( HSCRIPT hInflictor, HSCRIPT hAttacker, const Ve return hScript; } -static void DestroyDamageInfo( HSCRIPT hDamageInfo ) +static void DestroyDamageInfo( HSCRIPT ) { - CTakeDamageInfo *pInfo = HScriptToClass< CTakeDamageInfo >( hDamageInfo ); - if ( pInfo ) - { - g_pScriptVM->RemoveInstance( hDamageInfo ); - delete pInfo; - } } void ScriptCalculateExplosiveDamageForce( HSCRIPT info, const Vector &vecDir, const Vector &vecForceOrigin, float flScale ) @@ -317,6 +310,8 @@ void ScriptGuessDamageForce( HSCRIPT info, const Vector &vecForceDir, const Vect // //----------------------------------------------------------------------------- BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptGameTrace, "CGameTrace", "trace_t" ) + DEFINE_SCRIPT_REFCOUNTED_INSTANCE() + DEFINE_SCRIPTFUNC( DidHitWorld, "Returns whether the trace hit the world entity or not." ) DEFINE_SCRIPTFUNC( DidHitNonWorldEntity, "Returns whether the trace hit something other than the world entity." ) DEFINE_SCRIPTFUNC( GetEntityIndex, "Returns the index of whatever entity this trace hit." ) @@ -347,7 +342,7 @@ BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptGameTrace, "CGameTrace", "trace_t" ) DEFINE_SCRIPTFUNC( Surface, "" ) DEFINE_SCRIPTFUNC( Plane, "" ) - DEFINE_SCRIPTFUNC( Destroy, "Deletes this instance. Important for preventing memory leaks." ) + DEFINE_SCRIPTFUNC( Destroy, SCRIPT_HIDE ) END_SCRIPTDESC(); BEGIN_SCRIPTDESC_ROOT_NAMED( scriptsurfacedata_t, "surfacedata_t", "" ) @@ -376,37 +371,28 @@ END_SCRIPTDESC(); CPlaneTInstanceHelper g_PlaneTInstanceHelper; -BEGIN_SCRIPTDESC_ROOT( cplane_t, "" ) - DEFINE_SCRIPT_INSTANCE_HELPER( &g_PlaneTInstanceHelper ) +BEGIN_SCRIPTDESC_ROOT_WITH_HELPER( cplane_t, "", &g_PlaneTInstanceHelper ) END_SCRIPTDESC(); -static HSCRIPT ScriptTraceLineComplex( const Vector &vecStart, const Vector &vecEnd, HSCRIPT entIgnore, int iMask, int iCollisionGroup ) +static HSCRIPT_RC ScriptTraceLineComplex( const Vector &vecStart, const Vector &vecEnd, HSCRIPT entIgnore, int iMask, int iCollisionGroup ) { - // The script is responsible for deleting this via Destroy(). CScriptGameTrace *tr = new CScriptGameTrace(); CBaseEntity *pIgnore = ToEnt( entIgnore ); UTIL_TraceLine( vecStart, vecEnd, iMask, pIgnore, iCollisionGroup, tr ); - tr->RegisterSurface(); - tr->RegisterPlane(); - - return tr->GetScriptInstance(); + return g_pScriptVM->RegisterInstance( tr, true ); } -static HSCRIPT ScriptTraceHullComplex( const Vector &vecStart, const Vector &vecEnd, const Vector &hullMin, const Vector &hullMax, +static HSCRIPT_RC ScriptTraceHullComplex( const Vector &vecStart, const Vector &vecEnd, const Vector &hullMin, const Vector &hullMax, HSCRIPT entIgnore, int iMask, int iCollisionGroup ) { - // The script is responsible for deleting this via Destroy(). CScriptGameTrace *tr = new CScriptGameTrace(); CBaseEntity *pIgnore = ToEnt( entIgnore ); UTIL_TraceHull( vecStart, vecEnd, hullMin, hullMax, iMask, pIgnore, iCollisionGroup, tr ); - tr->RegisterSurface(); - tr->RegisterPlane(); - - return tr->GetScriptInstance(); + return g_pScriptVM->RegisterInstance( tr, true ); } //----------------------------------------------------------------------------- @@ -477,12 +463,11 @@ void FireBulletsInfo_t::ScriptSetAdditionalIgnoreEnt( HSCRIPT value ) m_pAdditionalIgnoreEnt = ToEnt( value ); } -static HSCRIPT CreateFireBulletsInfo( int cShots, const Vector &vecSrc, const Vector &vecDirShooting, +static HSCRIPT_RC CreateFireBulletsInfo( int cShots, const Vector &vecSrc, const Vector &vecDirShooting, const Vector &vecSpread, float iDamage, HSCRIPT pAttacker ) { - // The script is responsible for deleting this via DestroyFireBulletsInfo(). FireBulletsInfo_t *info = new FireBulletsInfo_t(); - HSCRIPT hScript = g_pScriptVM->RegisterInstance( info ); + HSCRIPT hScript = g_pScriptVM->RegisterInstance( info, true ); info->SetShots( cShots ); info->SetSource( vecSrc ); @@ -494,14 +479,8 @@ static HSCRIPT CreateFireBulletsInfo( int cShots, const Vector &vecSrc, const Ve return hScript; } -static void DestroyFireBulletsInfo( HSCRIPT hBulletsInfo ) +static void DestroyFireBulletsInfo( HSCRIPT ) { - FireBulletsInfo_t *pInfo = HScriptToClass< FireBulletsInfo_t >( hBulletsInfo ); - if ( pInfo ) - { - g_pScriptVM->RemoveInstance( hBulletsInfo ); - delete pInfo; - } } //----------------------------------------------------------------------------- @@ -509,9 +488,7 @@ static void DestroyFireBulletsInfo( HSCRIPT hBulletsInfo ) //----------------------------------------------------------------------------- CAnimEventTInstanceHelper g_AnimEventTInstanceHelper; -BEGIN_SCRIPTDESC_ROOT( scriptanimevent_t, "" ) - DEFINE_SCRIPT_INSTANCE_HELPER( &g_AnimEventTInstanceHelper ) - +BEGIN_SCRIPTDESC_ROOT_WITH_HELPER( scriptanimevent_t, "", &g_AnimEventTInstanceHelper ) DEFINE_SCRIPTFUNC( GetEvent, "" ) DEFINE_SCRIPTFUNC( SetEvent, "" ) @@ -535,7 +512,7 @@ bool CAnimEventTInstanceHelper::Get( void *p, const char *pszKey, ScriptVariant_ { DevWarning( "VScript animevent_t.%s: animevent_t metamethod members are deprecated! Use 'script_help animevent_t' to see the correct functions.\n", pszKey ); - animevent_t *ani = ((animevent_t *)p); + animevent_t *ani = &((scriptanimevent_t *)p)->event; if (FStrEq( pszKey, "event" )) variant = ani->event; else if (FStrEq( pszKey, "options" )) @@ -558,18 +535,28 @@ bool CAnimEventTInstanceHelper::Set( void *p, const char *pszKey, ScriptVariant_ { DevWarning( "VScript animevent_t.%s: animevent_t metamethod members are deprecated! Use 'script_help animevent_t' to see the correct functions.\n", pszKey ); - animevent_t *ani = ((animevent_t *)p); + scriptanimevent_t *script_ani = ((scriptanimevent_t *)p); + animevent_t *ani = &script_ani->event; if (FStrEq( pszKey, "event" )) - ani->event = variant; + { + return variant.AssignTo( &ani->event ); + } else if (FStrEq( pszKey, "options" )) - ani->options = variant; + { + char *szOptions; + if (!variant.AssignTo( &szOptions )) + { + return false; + } + script_ani->SetOptions( szOptions ); + } else if (FStrEq( pszKey, "cycle" )) - ani->cycle = variant; + return variant.AssignTo( &ani->cycle ); else if (FStrEq( pszKey, "eventtime" )) - ani->eventtime = variant; + return variant.AssignTo( &ani->eventtime ); else if (FStrEq( pszKey, "type" )) - ani->type = variant; - else if (FStrEq( pszKey, "source" )) + return variant.AssignTo( &ani->type ); + else if (FStrEq( pszKey, "source" ) && variant.m_type == FIELD_HSCRIPT) { CBaseEntity *pEnt = ToEnt( variant.m_hScript ); if (pEnt) @@ -1046,14 +1033,14 @@ void RegisterSharedScriptFunctions() #endif ScriptRegisterFunction( g_pScriptVM, CreateDamageInfo, "" ); - ScriptRegisterFunction( g_pScriptVM, DestroyDamageInfo, "" ); + ScriptRegisterFunction( g_pScriptVM, DestroyDamageInfo, SCRIPT_HIDE ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCalculateExplosiveDamageForce, "CalculateExplosiveDamageForce", "Fill out a damage info handle with a damage force for an explosive." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCalculateBulletDamageForce, "CalculateBulletDamageForce", "Fill out a damage info handle with a damage force for a bullet impact." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCalculateMeleeDamageForce, "CalculateMeleeDamageForce", "Fill out a damage info handle with a damage force for a melee impact." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGuessDamageForce, "GuessDamageForce", "Try and guess the physics force to use." ); ScriptRegisterFunction( g_pScriptVM, CreateFireBulletsInfo, "" ); - ScriptRegisterFunction( g_pScriptVM, DestroyFireBulletsInfo, "" ); + ScriptRegisterFunction( g_pScriptVM, DestroyFireBulletsInfo, SCRIPT_HIDE ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptTraceLineComplex, "TraceLineComplex", "Complex version of TraceLine which takes 2 points, an ent to ignore, a trace mask, and a collision group. Returns a handle which can access all trace info." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptTraceHullComplex, "TraceHullComplex", "Takes 2 points, min/max hull bounds, an ent to ignore, a trace mask, and a collision group to trace to a point using a hull. Returns a handle which can access all trace info." ); @@ -1071,10 +1058,10 @@ void RegisterSharedScriptFunctions() // // Precaching // - ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPrecacheModel, "PrecacheModel", "Precaches a model for later usage." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPrecacheModel, "DoPrecacheModel", SCRIPT_ALIAS( "PrecacheModel", "Precaches a model for later usage." ) ); ScriptRegisterFunction( g_pScriptVM, PrecacheMaterial, "Precaches a material for later usage." ); ScriptRegisterFunction( g_pScriptVM, PrecacheParticleSystem, "Precaches a particle system for later usage." ); - ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPrecacheOther, "PrecacheOther", "Precaches an entity class for later usage." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPrecacheOther, "DoPrecacheOther", SCRIPT_ALIAS( "PrecacheOther", "Precaches an entity class for later usage." ) ); // // NPCs diff --git a/sp/src/game/shared/mapbase/vscript_funcs_shared.h b/sp/src/game/shared/mapbase/vscript_funcs_shared.h index bcf91741..16db5ffa 100644 --- a/sp/src/game/shared/mapbase/vscript_funcs_shared.h +++ b/sp/src/game/shared/mapbase/vscript_funcs_shared.h @@ -46,12 +46,14 @@ public: class CSurfaceScriptHelper { public: - // This class is owned by CScriptGameTrace, and cannot be accessed without being initialised in CScriptGameTrace::RegisterSurface() - //CSurfaceScriptHelper() : m_pSurface(NULL), m_hSurfaceData(NULL) {} + CSurfaceScriptHelper() : m_pSurface(NULL), m_hSurfaceData(NULL) {} ~CSurfaceScriptHelper() { - g_pScriptVM->RemoveInstance( m_hSurfaceData ); + if ( m_hSurfaceData ) + { + g_pScriptVM->RemoveInstance( m_hSurfaceData ); + } } void Init( csurface_t *surf ) @@ -98,16 +100,10 @@ class CScriptGameTrace : public CGameTrace public: CScriptGameTrace() : m_surfaceAccessor(NULL), m_planeAccessor(NULL) { - m_hScriptInstance = g_pScriptVM->RegisterInstance( this ); } ~CScriptGameTrace() { - if ( m_hScriptInstance ) - { - g_pScriptVM->RemoveInstance( m_hScriptInstance ); - } - if ( m_surfaceAccessor ) { g_pScriptVM->RemoveInstance( m_surfaceAccessor ); @@ -119,22 +115,6 @@ public: } } - void RegisterSurface() - { - m_surfaceHelper.Init( &surface ); - m_surfaceAccessor = g_pScriptVM->RegisterInstance( &m_surfaceHelper ); - } - - void RegisterPlane() - { - m_planeAccessor = g_pScriptVM->RegisterInstance( &plane ); - } - - HSCRIPT GetScriptInstance() const - { - return m_hScriptInstance; - } - public: float FractionLeftSolid() const { return fractionleftsolid; } int HitGroup() const { return hitgroup; } @@ -154,15 +134,30 @@ public: bool AllSolid() const { return allsolid; } bool StartSolid() const { return startsolid; } - HSCRIPT Surface() const { return m_surfaceAccessor; } - HSCRIPT Plane() const { return m_planeAccessor; } + HSCRIPT Surface() + { + if ( !m_surfaceAccessor ) + { + m_surfaceHelper.Init( &surface ); + m_surfaceAccessor = g_pScriptVM->RegisterInstance( &m_surfaceHelper ); + } - void Destroy() { delete this; } + return m_surfaceAccessor; + } + + HSCRIPT Plane() + { + if ( !m_planeAccessor ) + m_planeAccessor = g_pScriptVM->RegisterInstance( &plane ); + + return m_planeAccessor; + } + + void Destroy() {} private: HSCRIPT m_surfaceAccessor; HSCRIPT m_planeAccessor; - HSCRIPT m_hScriptInstance; CSurfaceScriptHelper m_surfaceHelper; @@ -172,30 +167,47 @@ private: //----------------------------------------------------------------------------- // Exposes animevent_t to VScript //----------------------------------------------------------------------------- -struct scriptanimevent_t : public animevent_t +struct scriptanimevent_t { - int GetEvent() { return event; } - void SetEvent( int nEvent ) { event = nEvent; } + friend class CAnimEventTInstanceHelper; - const char *GetOptions() { return options; } - void SetOptions( const char *pOptions ) { options = pOptions; } +public: + scriptanimevent_t( animevent_t &event ) : event( event ), options( NULL ) { } + ~scriptanimevent_t( ) { delete[] options; } - float GetCycle() { return cycle; } - void SetCycle( float flCycle ) { cycle = flCycle; } + int GetEvent() { return event.event; } + void SetEvent( int nEvent ) { event.event = nEvent; } - float GetEventTime() { return eventtime; } - void SetEventTime( float flEventTime ) { eventtime = flEventTime; } + const char *GetOptions() { return event.options; } + void SetOptions( const char *pOptions ) + { + size_t len = strlen( pOptions ); + delete[] options; + event.options = options = new char[len + 1]; + memcpy( options, pOptions, len + 1 ); + } - int GetType() { return type; } - void SetType( int nType ) { eventtime = type; } + float GetCycle() { return event.cycle; } + void SetCycle( float flCycle ) { event.cycle = flCycle; } - HSCRIPT GetSource() { return ToHScript( pSource ); } + float GetEventTime() { return event.eventtime; } + void SetEventTime( float flEventTime ) { event.eventtime = flEventTime; } + + int GetType() { return event.type; } + void SetType( int nType ) { event.type = nType; } + + HSCRIPT GetSource() { return ToHScript( event.pSource ); } void SetSource( HSCRIPT hSource ) { CBaseEntity *pEnt = ToEnt( hSource ); if (pEnt) - pSource = pEnt->GetBaseAnimating(); + event.pSource = pEnt->GetBaseAnimating(); } + +private: + animevent_t &event; + // storage for ScriptVariant_t string, which may be temporary + char *options; }; class CAnimEventTInstanceHelper : public IScriptInstanceHelper diff --git a/sp/src/game/shared/mapbase/vscript_singletons.cpp b/sp/src/game/shared/mapbase/vscript_singletons.cpp index d9950998..7638762e 100644 --- a/sp/src/game/shared/mapbase/vscript_singletons.cpp +++ b/sp/src/game/shared/mapbase/vscript_singletons.cpp @@ -2920,7 +2920,7 @@ int CScriptGameEventListener::ListenToGameEvent( const char* szEvent, HSCRIPT hF if ( bValid ) { m_iContextHash = HashContext( szContext ); - m_hCallback = hFunc; + m_hCallback = g_pScriptVM->CopyObject( hFunc ); m_bActive = true; s_Listeners.AddToTail( this ); @@ -3247,7 +3247,7 @@ public: // NOTE: These two functions are new with Mapbase and have no Valve equivalent static bool KeyValuesWrite( const char *szFile, HSCRIPT hInput ); - static HSCRIPT KeyValuesRead( const char *szFile ); + static HSCRIPT_RC KeyValuesRead( const char *szFile ); void LevelShutdownPostEntity() { @@ -3433,7 +3433,7 @@ bool CScriptReadWriteFile::KeyValuesWrite( const char *szFile, HSCRIPT hInput ) //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- -HSCRIPT CScriptReadWriteFile::KeyValuesRead( const char *szFile ) +HSCRIPT_RC CScriptReadWriteFile::KeyValuesRead( const char *szFile ) { char pszFullName[MAX_PATH]; V_snprintf( pszFullName, sizeof(pszFullName), SCRIPT_RW_FULL_PATH_FMT, szFile ); @@ -3458,7 +3458,7 @@ HSCRIPT CScriptReadWriteFile::KeyValuesRead( const char *szFile ) return NULL; } - HSCRIPT hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pKV, true ); // bAllowDestruct is supposed to automatically remove the involved KV + HSCRIPT hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pKV ); return hScript; } @@ -4609,9 +4609,9 @@ public: CScriptConCommand( const char *name, HSCRIPT fn, const char *helpString, int flags, ConCommand *pLinked = NULL ) : BaseClass( name, this, helpString, flags, 0 ), m_pLinked(pLinked), - m_hCallback(fn), m_hCompletionCallback(NULL) { + m_hCallback = g_pScriptVM->CopyObject( fn ); m_nCmdNameLen = V_strlen(name) + 1; Assert( m_nCmdNameLen - 1 <= 128 ); } @@ -4701,7 +4701,7 @@ public: BaseClass::m_pCommandCompletionCallback = this; BaseClass::m_bHasCompletionCallback = true; - m_hCompletionCallback = fn; + m_hCompletionCallback = g_pScriptVM->CopyObject( fn ); } else { @@ -4720,7 +4720,8 @@ public: if ( m_hCallback ) g_pScriptVM->ReleaseScript( m_hCallback ); - m_hCallback = fn; + + m_hCallback = g_pScriptVM->CopyObject( fn ); } else { @@ -4781,7 +4782,7 @@ public: if (fn) { - m_hCallback = fn; + m_hCallback = g_pScriptVM->CopyObject( fn ); BaseClass::InstallChangeCallback( (FnChangeCallback_t)ScriptConVarCallback ); } else diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.h b/sp/src/game/shared/mapbase/weapon_custom_scripted.h index c227e35c..304db6e9 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.h +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.h @@ -60,6 +60,7 @@ public: bool IsPredicted( void ) const { return m_iszClientScripts[0] != '\0'; } const char* GetWeaponScriptName() { return m_iszWeaponScriptName[0] != '\0' ? m_iszWeaponScriptName : BaseClass::GetWeaponScriptName(); } + const char* GetName() const { return m_iszWeaponScriptName[0] != '\0' ? STRING( m_iClassname ) : BaseClass::GetName(); } // Weapon selection bool HasAnyAmmo( void ); // Returns true is weapon has ammo diff --git a/sp/src/game/shared/takedamageinfo.cpp b/sp/src/game/shared/takedamageinfo.cpp index 269f8043..9bcd227b 100644 --- a/sp/src/game/shared/takedamageinfo.cpp +++ b/sp/src/game/shared/takedamageinfo.cpp @@ -33,6 +33,8 @@ END_DATADESC() #ifdef MAPBASE_VSCRIPT BEGIN_SCRIPTDESC_ROOT( CTakeDamageInfo, "Damage information handler." ) + DEFINE_SCRIPT_REFCOUNTED_INSTANCE() + DEFINE_SCRIPTFUNC_NAMED( ScriptGetInflictor, "GetInflictor", "Gets the inflictor." ) DEFINE_SCRIPTFUNC_NAMED( ScriptSetInflictor, "SetInflictor", "Sets the inflictor." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetWeapon, "GetWeapon", "Gets the weapon." ) @@ -590,4 +592,4 @@ void CTakeDamageInfo::DebugGetDamageTypeString(unsigned int damageType, char *ou #define DMG_BLAST_SURFACE (1<<27) // A blast on the surface of water that cannot harm things underwater #define DMG_DIRECT (1<<28) #define DMG_BUCKSHOT (1<<29) // not quite a bullet. Little, rounder, different. -*/ \ No newline at end of file +*/ diff --git a/sp/src/game/shared/vscript_shared.cpp b/sp/src/game/shared/vscript_shared.cpp index c41ddea6..e3386e68 100644 --- a/sp/src/game/shared/vscript_shared.cpp +++ b/sp/src/game/shared/vscript_shared.cpp @@ -37,6 +37,7 @@ extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * ); #ifdef MAPBASE_VSCRIPT // This is to ensure a dependency exists between the vscript library and the game DLLs extern int vscript_token; +extern int vscript_debugger_port; int vscript_token_hack = vscript_token; #endif @@ -390,12 +391,30 @@ CON_COMMAND_F( script_debug, "Connect the vscript VM to the script debugger", FC if ( !IsCommandIssuedByServerAdmin() ) return; +#ifdef MAPBASE_VSCRIPT +#ifdef GAME_DLL + int port = 1212; +#else + int port = 1213; +#endif +#endif + if ( !g_pScriptVM ) { +#ifdef MAPBASE_VSCRIPT + vscript_debugger_port = port; + CGMsg( 0, CON_GROUP_VSCRIPT, "VScript VM is not running, waiting for it to attach the debugger to port %d...\n", port ); +#else CGWarning( 0, CON_GROUP_VSCRIPT, "Scripting disabled or no server running\n" ); +#endif return; } + +#ifdef MAPBASE_VSCRIPT + g_pScriptVM->ConnectDebugger( port ); +#else g_pScriptVM->ConnectDebugger(); +#endif } #ifdef CLIENT_DLL @@ -507,7 +526,7 @@ void RunAutorunScripts() //----------------------------------------------------------------------------- -static short VSCRIPT_SERVER_SAVE_RESTORE_VERSION = 2; +static short VSCRIPT_SERVER_SAVE_RESTORE_VERSION = 3; //----------------------------------------------------------------------------- diff --git a/sp/src/game/shared/vscript_shared.h b/sp/src/game/shared/vscript_shared.h index 50834220..0447ab8b 100644 --- a/sp/src/game/shared/vscript_shared.h +++ b/sp/src/game/shared/vscript_shared.h @@ -21,12 +21,16 @@ inline bool VScriptRunScript( const char *pszScriptName, bool bWarnMissing = fal #define DECLARE_ENT_SCRIPTDESC() ALLOW_SCRIPT_ACCESS(); virtual ScriptClassDesc_t *GetScriptDesc() -#define BEGIN_ENT_SCRIPTDESC( className, baseClass, description ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC( className, baseClass, description ) -#define BEGIN_ENT_SCRIPTDESC_ROOT( className, description ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC_ROOT( className, description ) -#define BEGIN_ENT_SCRIPTDESC_NAMED( className, baseClass, scriptName, description ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC_NAMED( className, baseClass, scriptName, description ) -#define BEGIN_ENT_SCRIPTDESC_ROOT_NAMED( className, scriptName, description ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC_ROOT_NAMED( className, scriptName, description ) +#define BEGIN_ENT_SCRIPTDESC( className, baseClass, description ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC( className, baseClass, description ) +#define BEGIN_ENT_SCRIPTDESC_WITH_HELPER( className, baseClass, description, helper ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC_WITH_HELPER( className, baseClass, description, helper ) +#define BEGIN_ENT_SCRIPTDESC_ROOT( className, description ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC_ROOT( className, description ) +#define BEGIN_ENT_SCRIPTDESC_ROOT_WITH_HELPER( className, description, helper ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC_ROOT_WITH_HELPER( className, description, helper ) +#define BEGIN_ENT_SCRIPTDESC_NAMED( className, baseClass, scriptName, description ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC_NAMED( className, baseClass, scriptName, description ) +#define BEGIN_ENT_SCRIPTDESC_NAMED_WITH_HELPER( className, baseClass, scriptName, description, helper ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC_NAMED_WITH_HELPER( className, baseClass, scriptName, description, helper ) +#define BEGIN_ENT_SCRIPTDESC_ROOT_NAMED( className, scriptName, description ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC_ROOT_NAMED( className, scriptName, description ) +#define BEGIN_ENT_SCRIPTDESC_ROOT_NAMED_WITH_HELPER( className, scriptName, description, helper ) _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ); BEGIN_SCRIPTDESC_ROOT_NAMED_WITH_HELPER( className, scriptName, description, helper ) -#define _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ) template <> ScriptClassDesc_t * GetScriptDesc( className * ); ScriptClassDesc_t *className::GetScriptDesc() { return ::GetScriptDesc( this ); } +#define _IMPLEMENT_ENT_SCRIPTDESC_ACCESSOR( className ) template <> ScriptClassDesc_t * GetScriptDesc( className *, bool ); ScriptClassDesc_t *className::GetScriptDesc() { return ::GetScriptDesc( this ); } // Only allow scripts to create entities during map initialization bool IsEntityCreationAllowedInScripts( void ); diff --git a/sp/src/materialsystem/stdshaders/skin_dx9_helper.cpp b/sp/src/materialsystem/stdshaders/skin_dx9_helper.cpp index 93e2beba..31a02980 100644 --- a/sp/src/materialsystem/stdshaders/skin_dx9_helper.cpp +++ b/sp/src/materialsystem/stdshaders/skin_dx9_helper.cpp @@ -767,16 +767,7 @@ void DrawSkin_DX9_Internal( CBaseVSShader *pShader, IMaterialVar** params, IShad pShader->SetVertexShaderTextureTransform( VERTEX_SHADER_SHADER_SPECIFIC_CONST_0, info.m_nBaseTextureTransform ); -#ifdef MAPBASE - // The original code makes it seem like we have the opportunity to support both $bumptransform and $detail at the same time, - // and that may or may not have been Valve's intention, but we'd need to add another texcoord for this and it's already - // a limitation with the non-skin shader anyway. - if ( bHasBump ) - { - pShader->SetVertexShaderTextureTransform( VERTEX_SHADER_SHADER_SPECIFIC_CONST_4, info.m_nBumpTransform ); - } - else -#else +#ifndef MAPBASE // See below if( bHasBump ) { pShader->SetVertexShaderTextureTransform( VERTEX_SHADER_SHADER_SPECIFIC_CONST_2, info.m_nBumpTransform ); @@ -794,6 +785,15 @@ void DrawSkin_DX9_Internal( CBaseVSShader *pShader, IMaterialVar** params, IShad info.m_nBaseTextureTransform, info.m_nDetailScale ); } +#ifdef MAPBASE + // The original code makes it seem like we have the opportunity to support both $bumptransform and $detail at the same time, + // and that may or may not have been Valve's intention, but we'd need to add another texcoord for this and it's already + // a limitation with the non-skin shader anyway. + else if ( bHasBump ) + { + pShader->SetVertexShaderTextureTransform( VERTEX_SHADER_SHADER_SPECIFIC_CONST_4, info.m_nBumpTransform ); + } +#endif pShader->SetModulationPixelShaderDynamicState_LinearColorSpace( 1 ); pShader->SetPixelShaderConstant_W( PSREG_SELFILLUMTINT, info.m_nSelfIllumTint, fBlendFactor ); diff --git a/sp/src/materialsystem/stdshaders/splinerope.cpp b/sp/src/materialsystem/stdshaders/splinerope.cpp index f4618021..b051c5b1 100644 --- a/sp/src/materialsystem/stdshaders/splinerope.cpp +++ b/sp/src/materialsystem/stdshaders/splinerope.cpp @@ -49,6 +49,14 @@ BEGIN_VS_SHADER( SDK_Cable_DX9, "Help for SplineRope" ) { bool bShaderSrgbRead = ( IsX360() && params[SHADERSRGBREAD360]->GetIntValue() ); bool bShadowDepth = ( params[SHADOWDEPTH]->GetIntValue() != 0 ); + +#ifdef MAPBASE + BlendType_t nBlendType = EvaluateBlendRequirements( BASETEXTURE, true ); + bool bFullyOpaque = (nBlendType != BT_BLENDADD) && (nBlendType != BT_BLEND) && !IS_FLAG_SET( MATERIAL_VAR_ALPHATEST ); //dest alpha is free for special use +#else + bool bFullyOpaque = true; +#endif + SHADOW_STATE { // draw back-facing because of yaw spin @@ -65,8 +73,19 @@ BEGIN_VS_SHADER( SDK_Cable_DX9, "Help for SplineRope" ) } else { +#ifdef MAPBASE + if (IS_FLAG_SET( MATERIAL_VAR_TRANSLUCENT )) + { + pShaderShadow->EnableDepthWrites( false ); + pShaderShadow->EnableBlending( true ); + pShaderShadow->BlendFunc( SHADER_BLEND_SRC_ALPHA, SHADER_BLEND_ONE_MINUS_SRC_ALPHA ); + } + + pShaderShadow->EnableAlphaTest( IS_FLAG_SET( MATERIAL_VAR_ALPHATEST ) ); +#endif + // We need to write to dest alpha for depth feathering. - pShaderShadow->EnableAlphaWrites( true ); + pShaderShadow->EnableAlphaWrites( bFullyOpaque ); // base texture pShaderShadow->EnableTexture( SHADER_SAMPLER0, true ); @@ -150,7 +169,7 @@ BEGIN_VS_SHADER( SDK_Cable_DX9, "Help for SplineRope" ) if ( g_pHardwareConfig->SupportsPixelShaders_2_b() ) { DECLARE_DYNAMIC_PIXEL_SHADER( sdk_splinerope_ps20b ); - SET_DYNAMIC_PIXEL_SHADER_COMBO( WRITE_DEPTH_TO_DESTALPHA, pShaderAPI->ShouldWriteDepthToDestAlpha() ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( WRITE_DEPTH_TO_DESTALPHA, bFullyOpaque && pShaderAPI->ShouldWriteDepthToDestAlpha() ); //SET_DYNAMIC_PIXEL_SHADER_COMBO( PIXELFOGTYPE, pShaderAPI->GetSceneFogMode() == MATERIAL_FOG_LINEAR_BELOW_FOG_Z ); SET_DYNAMIC_PIXEL_SHADER_COMBO( PIXELFOGTYPE, pShaderAPI->GetPixelFogCombo() ); SET_DYNAMIC_PIXEL_SHADER( sdk_splinerope_ps20b ); diff --git a/sp/src/public/responserules/response_types.h b/sp/src/public/responserules/response_types.h index 2e80cc5a..d30def80 100644 --- a/sp/src/public/responserules/response_types.h +++ b/sp/src/public/responserules/response_types.h @@ -99,6 +99,7 @@ namespace ResponseRules #ifdef MAPBASE RESPONSE_VSCRIPT, // Run VScript code RESPONSE_VSCRIPT_FILE, // Run a VScript file (bypasses ugliness and character limits when just using IncludeScript() with RESPONSE_VSCRIPT) + RESPONSE_CHOREOSENTENCE, // Sentences put together as VCDs, see choreosentence.h for more information #endif NUM_RESPONSES, diff --git a/sp/src/public/vscript/ivscript.h b/sp/src/public/vscript/ivscript.h index 58f981e0..5da06102 100644 --- a/sp/src/public/vscript/ivscript.h +++ b/sp/src/public/vscript/ivscript.h @@ -141,6 +141,20 @@ class KeyValues; DECLARE_POINTER_HANDLE( HSCRIPT ); #define INVALID_HSCRIPT ((HSCRIPT)-1) +// Reference counted HSCRIPT return value +// +// This is an alias for HSCRIPT that is converted back to HSCRIPT on return +// from vscript function bindings; it signals the vscript implementation to +// release its hold and let the script control the lifetime +// of the registered instance. +struct HSCRIPT_RC +{ + HSCRIPT val; + HSCRIPT_RC( HSCRIPT v ) { val = v; } + HSCRIPT operator=( HSCRIPT v ) { val = v; return val; } + operator HSCRIPT() { return val; } +}; + typedef unsigned int HScriptRaw; #endif @@ -162,7 +176,7 @@ public: virtual void DestroyVM( IScriptVM * ) = 0; #ifdef MAPBASE_VSCRIPT - virtual HSCRIPT CreateScriptKeyValues( IScriptVM *pVM, KeyValues *pKV, bool bAllowDestruct ) = 0; + virtual HSCRIPT CreateScriptKeyValues( IScriptVM *pVM, KeyValues *pKV, bool bBorrow = false ) = 0; virtual KeyValues *GetKeyValuesFromScriptKV( IScriptVM *pVM, HSCRIPT hSKV ) = 0; #endif }; @@ -177,6 +191,9 @@ enum ExtendedFieldType FIELD_CSTRING, FIELD_HSCRIPT, FIELD_VARIANT, +#ifdef MAPBASE_VSCRIPT + FIELD_HSCRIPT_RC, +#endif }; typedef int ScriptDataType_t; @@ -197,6 +214,7 @@ DECLARE_DEDUCE_FIELDTYPE( FIELD_CHARACTER, char ); DECLARE_DEDUCE_FIELDTYPE( FIELD_HSCRIPT, HSCRIPT ); DECLARE_DEDUCE_FIELDTYPE( FIELD_VARIANT, ScriptVariant_t ); #ifdef MAPBASE_VSCRIPT +DECLARE_DEDUCE_FIELDTYPE( FIELD_HSCRIPT_RC, HSCRIPT_RC ); DECLARE_DEDUCE_FIELDTYPE( FIELD_VECTOR, QAngle ); DECLARE_DEDUCE_FIELDTYPE( FIELD_VECTOR, const QAngle& ); #endif @@ -298,9 +316,13 @@ struct ScriptMemberDesc_t enum ScriptFuncBindingFlags_t { SF_MEMBER_FUNC = 0x01, +#ifdef MAPBASE_VSCRIPT + SF_REFCOUNTED_RET = 0x02, +#endif }; -typedef bool (*ScriptBindingFunc_t)( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn ); +union ScriptVariantTemporaryStorage_t; +typedef bool (*ScriptBindingFunc_t)( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ); struct ScriptFunctionBinding_t { @@ -321,11 +343,6 @@ public: #ifdef MAPBASE_VSCRIPT virtual bool Get( void *p, const char *pszKey, ScriptVariant_t &variant ) { return false; } virtual bool Set( void *p, const char *pszKey, ScriptVariant_t &variant ) { return false; } - - virtual ScriptVariant_t *Add( void *p, ScriptVariant_t &variant ) { return NULL; } - virtual ScriptVariant_t *Subtract( void *p, ScriptVariant_t &variant ) { return NULL; } - virtual ScriptVariant_t *Multiply( void *p, ScriptVariant_t &variant ) { return NULL; } - virtual ScriptVariant_t *Divide( void *p, ScriptVariant_t &variant ) { return NULL; } #endif }; @@ -360,6 +377,7 @@ struct ScriptClassDesc_t IScriptInstanceHelper * pHelper; // optional helper #ifdef MAPBASE_VSCRIPT +public: static CUtlVector& AllClassesDesc() { static CUtlVector classes; @@ -389,13 +407,17 @@ struct ScriptVariant_t ScriptVariant_t( bool val ) : m_flags( 0 ), m_type( FIELD_BOOLEAN ) { m_bool = val; } ScriptVariant_t( HSCRIPT val ) : m_flags( 0 ), m_type( FIELD_HSCRIPT ) { m_hScript = val; } - ScriptVariant_t( const Vector &val, bool bCopy = false ) : m_flags( 0 ), m_type( FIELD_VECTOR ) { if ( !bCopy ) { m_pVector = &val; } else { m_pVector = new Vector( val ); m_flags |= SV_FREE; } } - ScriptVariant_t( const Vector *val, bool bCopy = false ) : m_flags( 0 ), m_type( FIELD_VECTOR ) { if ( !bCopy ) { m_pVector = val; } else { m_pVector = new Vector( *val ); m_flags |= SV_FREE; } } - ScriptVariant_t( const char *val , bool bCopy = false ) : m_flags( 0 ), m_type( FIELD_CSTRING ) { if ( !bCopy ) { m_pszString = val; } else { m_pszString = strdup( val ); m_flags |= SV_FREE; } } + ScriptVariant_t( const Vector &val ) : m_flags( 0 ), m_type( FIELD_VECTOR ) { m_pVector = &val; } + ScriptVariant_t( const Vector *val ) : ScriptVariant_t( *val ) { } + + ScriptVariant_t( const char *val ) : m_flags( 0 ), m_type( FIELD_CSTRING ) { m_pszString = val; } #ifdef MAPBASE_VSCRIPT - ScriptVariant_t( const QAngle &val, bool bCopy = false ) : m_flags( 0 ), m_type( FIELD_VECTOR ) { if ( !bCopy ) { m_pAngle = &val; } else { m_pAngle = new QAngle( val ); m_flags |= SV_FREE; } } - ScriptVariant_t( const QAngle *val, bool bCopy = false ) : m_flags( 0 ), m_type( FIELD_VECTOR ) { if ( !bCopy ) { m_pAngle = val; } else { m_pAngle = new QAngle( *val ); m_flags |= SV_FREE; } } + ScriptVariant_t( const QAngle &val ) : m_flags( 0 ), m_type( FIELD_VECTOR ) { m_pAngle = &val; } + ScriptVariant_t( const QAngle *val ) : ScriptVariant_t( *val ) { } + + ScriptVariant_t( Vector &&val ) = delete; + ScriptVariant_t( QAngle &&val ) = delete; #endif bool IsNull() const { return (m_type == FIELD_VOID ); } @@ -403,74 +425,41 @@ struct ScriptVariant_t operator int() const { Assert( m_type == FIELD_INTEGER ); return m_int; } operator float() const { Assert( m_type == FIELD_FLOAT ); return m_float; } operator const char *() const { Assert( m_type == FIELD_CSTRING ); return ( m_pszString ) ? m_pszString : ""; } - operator const Vector &() const { Assert( m_type == FIELD_VECTOR ); static Vector vecNull(0, 0, 0); return (m_pVector) ? *m_pVector : vecNull; } + operator const Vector &() const { Assert( m_type == FIELD_VECTOR ); return (m_pVector) ? *m_pVector : vec3_origin; } operator char() const { Assert( m_type == FIELD_CHARACTER ); return m_char; } operator bool() const { Assert( m_type == FIELD_BOOLEAN ); return m_bool; } operator HSCRIPT() const { Assert( m_type == FIELD_HSCRIPT ); return m_hScript; } #ifdef MAPBASE_VSCRIPT - operator const QAngle &() const { Assert( m_type == FIELD_VECTOR ); static QAngle vecNull(0, 0, 0); return (m_pAngle) ? *m_pAngle : vecNull; } + operator const QAngle &() const { Assert( m_type == FIELD_VECTOR ); return (m_pAngle) ? *m_pAngle : vec3_angle; } #endif - void operator=( int i ) { m_type = FIELD_INTEGER; m_int = i; } - void operator=( float f ) { m_type = FIELD_FLOAT; m_float = f; } - void operator=( double f ) { m_type = FIELD_FLOAT; m_float = (float)f; } - void operator=( const Vector &vec ) { m_type = FIELD_VECTOR; m_pVector = &vec; } - void operator=( const Vector *vec ) { m_type = FIELD_VECTOR; m_pVector = vec; } - void operator=( const char *psz ) { m_type = FIELD_CSTRING; m_pszString = psz; } - void operator=( char c ) { m_type = FIELD_CHARACTER; m_char = c; } - void operator=( bool b ) { m_type = FIELD_BOOLEAN; m_bool = b; } - void operator=( HSCRIPT h ) { m_type = FIELD_HSCRIPT; m_hScript = h; } + void operator=( int i ) { m_type = FIELD_INTEGER; m_flags = 0; m_int = i; } + void operator=( float f ) { m_type = FIELD_FLOAT; m_flags = 0; m_float = f; } + void operator=( double f ) { m_type = FIELD_FLOAT; m_flags = 0; m_float = (float)f; } + void operator=( const Vector &vec ) { m_type = FIELD_VECTOR; m_flags = 0; m_pVector = &vec; } + void operator=( const Vector *vec ) { m_type = FIELD_VECTOR; m_flags = 0; m_pVector = vec; } + void operator=( const char *psz ) { m_type = FIELD_CSTRING; m_flags = 0; m_pszString = psz; } + void operator=( char c ) { m_type = FIELD_CHARACTER; m_flags = 0; m_char = c; } + void operator=( bool b ) { m_type = FIELD_BOOLEAN; m_flags = 0; m_bool = b; } + void operator=( HSCRIPT h ) { m_type = FIELD_HSCRIPT; m_flags = 0; m_hScript = h; } #ifdef MAPBASE_VSCRIPT - void operator=( const QAngle &vec ) { m_type = FIELD_VECTOR; m_pAngle = &vec; } - void operator=( const QAngle *vec ) { m_type = FIELD_VECTOR; m_pAngle = vec; } + void operator=( const QAngle &ang ) { m_type = FIELD_VECTOR; m_flags = 0; m_pAngle = ∠ } + void operator=( const QAngle *ang ) { m_type = FIELD_VECTOR; m_flags = 0; m_pAngle = ang; } + + void operator=( Vector &&vec ) = delete; + void operator=( QAngle &&ang ) = delete; #endif - void Free() { if ( ( m_flags & SV_FREE ) && ( m_type == FIELD_HSCRIPT || m_type == FIELD_VECTOR || m_type == FIELD_CSTRING ) ) delete m_pszString; } // Generally only needed for return results - - template - T Get() + void Free() { - T value; - AssignTo( &value ); - return value; - } - - template - bool AssignTo( T *pDest ) - { - ScriptDataType_t destType = ScriptDeduceType( T ); - if ( destType == FIELD_TYPEUNKNOWN ) + // Generally only needed for return results + if ( ! ( m_flags & SV_FREE ) ) { - DevWarning( "Unable to convert script variant to unknown type\n" ); - } - if ( destType == m_type ) - { - *pDest = *this; - return true; + return; } - if ( m_type != FIELD_VECTOR && m_type != FIELD_CSTRING && destType != FIELD_VECTOR && destType != FIELD_CSTRING ) - { - switch ( m_type ) - { - case FIELD_VOID: *pDest = 0; break; - case FIELD_INTEGER: *pDest = m_int; return true; - case FIELD_FLOAT: *pDest = m_float; return true; - case FIELD_CHARACTER: *pDest = m_char; return true; - case FIELD_BOOLEAN: *pDest = m_bool; return true; - case FIELD_HSCRIPT: *pDest = m_hScript; return true; - } - } - else - { - DevWarning( "No free conversion of %s script variant to %s right now\n", - ScriptFieldTypeName( m_type ), ScriptFieldTypeName() ); - if ( destType != FIELD_VECTOR ) - { - *pDest = 0; - } - } - return false; + AssertMsg( m_type == FIELD_CSTRING || m_type == FIELD_VECTOR, "Don't know how to free script variant of type %d", m_type ); + free( (void*)m_pszString ); } bool AssignTo( float *pDest ) @@ -525,25 +514,38 @@ struct ScriptVariant_t bool AssignTo( ScriptVariant_t *pDest ) { + pDest->Free(); pDest->m_type = m_type; - if ( m_type == FIELD_VECTOR ) + if ( m_flags & SV_FREE ) { - pDest->m_pVector = new Vector; - ((Vector *)(pDest->m_pVector))->Init( m_pVector->x, m_pVector->y, m_pVector->z ); - pDest->m_flags |= SV_FREE; - } - else if ( m_type == FIELD_CSTRING ) - { - pDest->m_pszString = strdup( m_pszString ); - pDest->m_flags |= SV_FREE; + if ( m_type == FIELD_VECTOR ) + { + pDest->m_pVector = (Vector*)malloc( sizeof( Vector ) ); + pDest->EmplaceAllocedVector( *m_pVector ); + m_flags |= SV_FREE; + } + else if ( m_type == FIELD_CSTRING ) + { + pDest->m_pszString = strdup( m_pszString ); + pDest->m_flags |= SV_FREE; + } + else + { + Assert( false ); + pDest->m_int = m_int; + pDest->m_flags &= ~SV_FREE; + } } else { pDest->m_int = m_int; + pDest->m_flags &= ~SV_FREE; } return false; } + void EmplaceAllocedVector( const Vector &vec ); + union { int m_int; @@ -565,8 +567,25 @@ struct ScriptVariant_t private: }; +#include "tier0/memdbgoff.h" +inline void ScriptVariant_t::EmplaceAllocedVector( const Vector &vec ) +{ + new ( (Vector*)m_pVector ) Vector( vec ); +} +#include "tier0/memdbgon.h" + #define SCRIPT_VARIANT_NULL ScriptVariant_t() +union ScriptVariantTemporaryStorage_t +{ + // members must be initialized via placement-new + ScriptVariantTemporaryStorage_t() { } + + // members must have trivial destructor, since no destructor will be invoked + Vector m_vec; + QAngle m_ang; +}; + #ifdef MAPBASE_VSCRIPT //--------------------------------------------------------- struct ScriptConstantBinding_t @@ -618,6 +637,15 @@ struct ScriptEnumDesc_t #define ScriptInitMemberFunctionBindingNamed( pScriptFunction, class, func, scriptName ) ScriptInitMemberFunctionBinding_( pScriptFunction, class, func, scriptName ) #define ScriptInitMemberFunctionBinding_( pScriptFunction, class, func, scriptName ) do { ScriptInitMemberFuncDescriptor_( (&(pScriptFunction)->m_desc), class, func, scriptName ); (pScriptFunction)->m_pfnBinding = ScriptCreateBinding( ((class *)0), &class::func ); (pScriptFunction)->m_pFunction = ScriptConvertFuncPtrToVoid( &class::func ); (pScriptFunction)->m_flags = SF_MEMBER_FUNC; } while (0) +#ifdef MAPBASE_VSCRIPT +// Convert HSCRIPT_RC return type into HSCRIPT return with SF_REFCOUNTED_RET binding flag +#undef ScriptInitFunctionBindingNamed +#define ScriptInitFunctionBindingNamed( pScriptFunction, func, scriptName ) do { ScriptInitFuncDescriptorNamed( (&(pScriptFunction)->m_desc), func, scriptName ); (pScriptFunction)->m_pfnBinding = ScriptCreateBinding( &func ); (pScriptFunction)->m_pFunction = (void *)&func; if ( (pScriptFunction)->m_desc.m_ReturnType == FIELD_HSCRIPT_RC ) { (pScriptFunction)->m_desc.m_ReturnType = FIELD_HSCRIPT; (pScriptFunction)->m_flags |= SF_REFCOUNTED_RET; } } while (0) + +#undef ScriptInitMemberFunctionBinding_ +#define ScriptInitMemberFunctionBinding_( pScriptFunction, class, func, scriptName ) do { ScriptInitMemberFuncDescriptor_( (&(pScriptFunction)->m_desc), class, func, scriptName ); (pScriptFunction)->m_pfnBinding = ScriptCreateBinding( ((class *)0), &class::func ); (pScriptFunction)->m_pFunction = ScriptConvertFuncPtrToVoid( &class::func ); (pScriptFunction)->m_flags = SF_MEMBER_FUNC; if ( (pScriptFunction)->m_desc.m_ReturnType == FIELD_HSCRIPT_RC ) { (pScriptFunction)->m_desc.m_ReturnType = FIELD_HSCRIPT; (pScriptFunction)->m_flags |= SF_REFCOUNTED_RET; } } while (0) +#endif + #define ScriptInitClassDesc( pClassDesc, class, pBaseClassDesc ) ScriptInitClassDescNamed( pClassDesc, class, pBaseClassDesc, #class ) #define ScriptInitClassDescNamed( pClassDesc, class, pBaseClassDesc, scriptName ) ScriptInitClassDescNamed_( pClassDesc, class, pBaseClassDesc, scriptName ) #define ScriptInitClassDescNoBase( pClassDesc, class ) ScriptInitClassDescNoBaseNamed( pClassDesc, class, #class ) @@ -659,7 +687,7 @@ static inline int ToConstantVariant(int value) // This is used for registering variants (particularly vectors) not tied to existing variables. // The principal difference is that m_data is initted with bCopy set to true. #define ScriptRegisterConstantFromTemp( pVM, constant, description ) ScriptRegisterConstantFromTempNamed( pVM, constant, #constant, description ) -#define ScriptRegisterConstantFromTempNamed( pVM, constant, scriptName, description ) do { static ScriptConstantBinding_t binding; binding.m_pszScriptName = scriptName; binding.m_pszDescription = description; binding.m_data = ScriptVariant_t( constant, true ); pVM->RegisterConstant( &binding ); } while (0) +#define ScriptRegisterConstantFromTempNamed( pVM, constant, scriptName, description ) do { static const auto constantStorage = constant; static ScriptConstantBinding_t binding; binding.m_pszScriptName = scriptName; binding.m_pszDescription = description; binding.m_data = ScriptVariant_t( constantStorage ); pVM->RegisterConstant( &binding ); } while (0) //----------------------------------------------------------------------------- // @@ -697,37 +725,48 @@ static inline int ToConstantVariant(int value) // //----------------------------------------------------------------------------- -#define ALLOW_SCRIPT_ACCESS() template friend ScriptClassDesc_t *GetScriptDesc(T *); +#define ALLOW_SCRIPT_ACCESS() template friend ScriptClassDesc_t *GetScriptDesc(T *, bool); -#define BEGIN_SCRIPTDESC( className, baseClass, description ) BEGIN_SCRIPTDESC_NAMED( className, baseClass, #className, description ) -#define BEGIN_SCRIPTDESC_ROOT( className, description ) BEGIN_SCRIPTDESC_ROOT_NAMED( className, #className, description ) +#define BEGIN_SCRIPTDESC( className, baseClass, description ) BEGIN_SCRIPTDESC_WITH_HELPER( className, baseClass, description, NULL ) +#define BEGIN_SCRIPTDESC_WITH_HELPER( className, baseClass, description, helper ) BEGIN_SCRIPTDESC_NAMED_WITH_HELPER( className, baseClass, #className, description, helper ) +#define BEGIN_SCRIPTDESC_ROOT( className, description ) BEGIN_SCRIPTDESC_ROOT_WITH_HELPER( className, description, NULL ) +#define BEGIN_SCRIPTDESC_ROOT_WITH_HELPER( className, description, helper ) BEGIN_SCRIPTDESC_ROOT_NAMED_WITH_HELPER( className, #className, description, helper ) -#define BEGIN_SCRIPTDESC_NAMED( className, baseClass, scriptName, description ) \ - template <> ScriptClassDesc_t* GetScriptDesc(baseClass*); \ - template <> ScriptClassDesc_t* GetScriptDesc(className*); \ - ScriptClassDesc_t & g_##className##_ScriptDesc = *GetScriptDesc(nullptr); \ - template <> ScriptClassDesc_t* GetScriptDesc(className*) \ +#define BEGIN_SCRIPTDESC_NAMED_WITH_HELPER( className, baseClass, scriptName, description, helper ) \ + template <> ScriptClassDesc_t* GetScriptDesc(baseClass*, bool); \ + template <> ScriptClassDesc_t* GetScriptDesc(className*, bool); \ + ScriptClassDesc_t & g_##className##_ScriptDesc = *GetScriptDesc(nullptr, true); \ + template <> ScriptClassDesc_t* GetScriptDesc(className*, bool init) \ { \ static ScriptClassDesc_t g_##className##_ScriptDesc; \ typedef className _className; \ ScriptClassDesc_t *pDesc = &g_##className##_ScriptDesc; \ - if (pDesc->m_pszClassname) return pDesc; \ - pDesc->m_pszDescription = description; \ - ScriptInitClassDescNamed( pDesc, className, GetScriptDescForClass( baseClass ), scriptName ); \ - ScriptClassDesc_t *pInstanceHelperBase = pDesc->m_pBaseDesc; \ - while ( pInstanceHelperBase ) \ + if (!pDesc->m_pszClassname) \ { \ - if ( pInstanceHelperBase->pHelper ) \ + pDesc->m_pszDescription = description; \ + ScriptClassDesc_t *pBaseDesc = GetScriptDescForClass( baseClass ); \ + ScriptInitClassDescNamed( pDesc, className, pBaseDesc, scriptName ); \ + pDesc->pHelper = helper; \ + if ( !pDesc->pHelper ) \ { \ - pDesc->pHelper = pInstanceHelperBase->pHelper; \ - break; \ + while ( pBaseDesc ) \ + { \ + if ( pBaseDesc->pHelper ) \ + { \ + pDesc->pHelper = pBaseDesc->pHelper; \ + break; \ + } \ + pBaseDesc = pBaseDesc->m_pBaseDesc; \ + } \ } \ - pInstanceHelperBase = pInstanceHelperBase->m_pBaseDesc; \ - } + } \ + if (!init) return pDesc; #define BEGIN_SCRIPTDESC_ROOT_NAMED( className, scriptName, description ) \ - BEGIN_SCRIPTDESC_NAMED( className, ScriptNoBase_t, scriptName, description ) + BEGIN_SCRIPTDESC_ROOT_NAMED_WITH_HELPER( className, scriptName, description, NULL ) +#define BEGIN_SCRIPTDESC_ROOT_NAMED_WITH_HELPER( className, scriptName, description, helper ) \ + BEGIN_SCRIPTDESC_NAMED_WITH_HELPER( className, ScriptNoBase_t, scriptName, description, helper ) #define END_SCRIPTDESC() \ return pDesc; \ @@ -736,9 +775,13 @@ static inline int ToConstantVariant(int value) #define DEFINE_SCRIPTFUNC( func, description ) DEFINE_SCRIPTFUNC_NAMED( func, #func, description ) #define DEFINE_SCRIPTFUNC_NAMED( func, scriptName, description ) ScriptAddFunctionToClassDescNamed( pDesc, _className, func, scriptName, description ); #define DEFINE_SCRIPT_CONSTRUCTOR() ScriptAddConstructorToClassDesc( pDesc, _className ); -#define DEFINE_SCRIPT_INSTANCE_HELPER( p ) pDesc->pHelper = (p); +#define DEFINE_SCRIPT_INSTANCE_HELPER( p ) MUST_USE_BEGIN_SCRIPTDESC_WITH_HELPER_INSTEAD #ifdef MAPBASE_VSCRIPT +// Allow instance to be deleted but not constructed +// Not needed if the class has a constructor +#define DEFINE_SCRIPT_REFCOUNTED_INSTANCE() do { pDesc->m_pfnDestruct = &CScriptConstructor<_className>::Destruct; } while (0); + // Use this for hooks which have no parameters #define DEFINE_SIMPLE_SCRIPTHOOK( hook, hookName, returnType, description ) \ if (!hook.m_bDefined) \ @@ -781,10 +824,10 @@ static inline int ToConstantVariant(int value) do { ScriptMemberDesc_t *pBinding = &((pDesc)->m_Members[(pDesc)->m_Members.AddToTail()]); pBinding->m_pszScriptName = varName; pBinding->m_pszDescription = description; pBinding->m_ReturnType = returnType; } while (0); #endif -template ScriptClassDesc_t *GetScriptDesc(T *); +template ScriptClassDesc_t *GetScriptDesc(T *, bool = false); struct ScriptNoBase_t; -template <> inline ScriptClassDesc_t *GetScriptDesc( ScriptNoBase_t *) { return NULL; } +template <> inline ScriptClassDesc_t *GetScriptDesc( ScriptNoBase_t *, bool ) { return NULL; } #define GetScriptDescForClass( className ) GetScriptDesc( ( className *)NULL ) @@ -838,7 +881,11 @@ public: virtual bool Init() = 0; virtual void Shutdown() = 0; +#ifdef MAPBASE_VSCRIPT + virtual bool ConnectDebugger( int port = 0 ) = 0; +#else virtual bool ConnectDebugger() = 0; +#endif virtual void DisconnectDebugger() = 0; virtual ScriptLanguage_t GetLanguage() = 0; @@ -927,14 +974,13 @@ public: //-------------------------------------------------------- #ifdef MAPBASE_VSCRIPT - // When a RegisterInstance instance is deleted, VScript normally treats it as a strong reference and only deregisters the instance itself, preserving the registered data - // it points to so the game can continue to use it. - // bAllowDestruct is supposed to allow VScript to treat it as a weak reference created by the script, destructing the registered data automatically like any other type. - // This is useful for classes pretending to be primitive types. - virtual HSCRIPT RegisterInstance( ScriptClassDesc_t *pDesc, void *pInstance, bool bAllowDestruct = false ) = 0; + // if bRefCounted is true, pInstance memory will be deleted by the script, + // returning the result will then behave as if the instance was constructed in script. + // Functions that return the result of this need to return HSCRIPT_RC + virtual HSCRIPT RegisterInstance( ScriptClassDesc_t *pDesc, void *pInstance, bool bRefCounted = false ) = 0; virtual void SetInstanceUniqeId( HSCRIPT hInstance, const char *pszId ) = 0; - template HSCRIPT RegisterInstance( T *pInstance, bool bAllowDestruct = false ) { return RegisterInstance( GetScriptDesc( pInstance ), pInstance, bAllowDestruct ); } - template HSCRIPT RegisterInstance( T *pInstance, const char *pszInstance, HSCRIPT hScope = NULL, bool bAllowDestruct = false) { HSCRIPT hInstance = RegisterInstance( GetScriptDesc( pInstance ), pInstance, bAllowDestruct ); SetValue( hScope, pszInstance, hInstance ); return hInstance; } + template HSCRIPT RegisterInstance( T *pInstance, bool bRefCounted = false ) { return RegisterInstance( GetScriptDesc( pInstance ), pInstance, bRefCounted ); } + template HSCRIPT RegisterInstance( T *pInstance, const char *pszInstance, HSCRIPT hScope = NULL, bool bRefCounted = false) { HSCRIPT hInstance = RegisterInstance( GetScriptDesc( pInstance ), pInstance, bRefCounted ); SetValue( hScope, pszInstance, hInstance ); return hInstance; } #else virtual HSCRIPT RegisterInstance( ScriptClassDesc_t *pDesc, void *pInstance ) = 0; virtual void SetInstanceUniqeId( HSCRIPT hInstance, const char *pszId ) = 0; @@ -983,6 +1029,8 @@ public: #ifdef MAPBASE_VSCRIPT virtual void CreateArray(ScriptVariant_t &arr, int size = 0) = 0; virtual bool ArrayAppend(HSCRIPT hArray, const ScriptVariant_t &val) = 0; + // To hold strong references to script objects + virtual HSCRIPT CopyObject(HSCRIPT obj) = 0; #endif //---------------------------------------------------------------------------- diff --git a/sp/src/public/vscript/vscript_templates.h b/sp/src/public/vscript/vscript_templates.h index 2d7058a3..9f2054cc 100644 --- a/sp/src/public/vscript/vscript_templates.h +++ b/sp/src/public/vscript/vscript_templates.h @@ -326,8 +326,8 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) class CNonMemberScriptBinding##N \ { \ public: \ - static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn ) \ - { \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ Assert( nArguments == N ); \ Assert( pReturn ); \ Assert( !pContext ); \ @@ -337,17 +337,15 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) return false; \ } \ *pReturn = ((FUNC_TYPE)pFunction)( SCRIPT_BINDING_ARGS_##N ); \ - if ( pReturn->m_type == FIELD_VECTOR ) \ - pReturn->m_pVector = new Vector(*pReturn->m_pVector); \ - return true; \ - } \ + return true; \ + } \ }; \ \ template \ class CNonMemberScriptBinding##N \ { \ public: \ - static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn ) \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ { \ Assert( nArguments == N ); \ Assert( !pReturn ); \ @@ -362,12 +360,52 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) } \ }; \ \ + template \ + class CNonMemberScriptBinding##N \ + { \ + public: \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ + Assert( nArguments == N ); \ + Assert( pReturn ); \ + Assert( !pContext ); \ + \ + if ( nArguments != N || !pReturn || pContext ) \ + { \ + return false; \ + } \ + new ( &temporaryReturnStorage.m_vec ) Vector( ((FUNC_TYPE)pFunction)( SCRIPT_BINDING_ARGS_##N ) ); \ + *pReturn = temporaryReturnStorage.m_vec; \ + return true; \ + } \ + }; \ + \ + template \ + class CNonMemberScriptBinding##N \ + { \ + public: \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ + Assert( nArguments == N ); \ + Assert( pReturn ); \ + Assert( !pContext ); \ + \ + if ( nArguments != N || !pReturn || pContext ) \ + { \ + return false; \ + } \ + new ( &temporaryReturnStorage.m_ang ) QAngle( ((FUNC_TYPE)pFunction)( SCRIPT_BINDING_ARGS_##N ) ); \ + *pReturn = temporaryReturnStorage.m_ang; \ + return true; \ + } \ + }; \ + \ template \ class CMemberScriptBinding##N \ { \ public: \ - static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn ) \ - { \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ Assert( nArguments == N ); \ Assert( pReturn ); \ Assert( pContext ); \ @@ -377,17 +415,15 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) return false; \ } \ *pReturn = (((OBJECT_TYPE_PTR)(pContext))->*ScriptConvertFuncPtrFromVoid(pFunction))( SCRIPT_BINDING_ARGS_##N ); \ - if ( pReturn->m_type == FIELD_VECTOR ) \ - pReturn->m_pVector = new Vector(*pReturn->m_pVector); \ - return true; \ - } \ + return true; \ + } \ }; \ \ template \ class CMemberScriptBinding##N \ { \ public: \ - static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn ) \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ { \ Assert( nArguments == N ); \ Assert( !pReturn ); \ @@ -402,6 +438,46 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) } \ }; \ \ + template \ + class CMemberScriptBinding##N \ + { \ + public: \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ + Assert( nArguments == N ); \ + Assert( pReturn ); \ + Assert( pContext ); \ + \ + if ( nArguments != N || !pReturn || !pContext ) \ + { \ + return false; \ + } \ + new ( &temporaryReturnStorage.m_vec ) Vector( (((OBJECT_TYPE_PTR)(pContext))->*ScriptConvertFuncPtrFromVoid(pFunction))( SCRIPT_BINDING_ARGS_##N ) ); \ + *pReturn = temporaryReturnStorage.m_vec; \ + return true; \ + } \ + }; \ + \ + template \ + class CMemberScriptBinding##N \ + { \ + public: \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ + Assert( nArguments == N ); \ + Assert( pReturn ); \ + Assert( pContext ); \ + \ + if ( nArguments != N || !pReturn || !pContext ) \ + { \ + return false; \ + } \ + new ( &temporaryReturnStorage.m_ang ) QAngle( (((OBJECT_TYPE_PTR)(pContext))->*ScriptConvertFuncPtrFromVoid(pFunction))( SCRIPT_BINDING_ARGS_##N ) ); \ + *pReturn = temporaryReturnStorage.m_ang; \ + return true; \ + } \ + }; \ + \ template \ inline ScriptBindingFunc_t ScriptCreateBinding(FUNCTION_RETTYPE (*pfnProxied)( FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N ) ) \ { \ @@ -423,7 +499,11 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) return &CMemberScriptBinding##N::Call; \ } +//note: no memory is actually allocated in the functions that get defined, +// it merely uses placement-new for which we need to disable this +#include "tier0/memdbgoff.h" FUNC_GENERATE_ALL( DEFINE_SCRIPT_BINDINGS ); +#include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // diff --git a/sp/src/responserules/runtime/response_system.cpp b/sp/src/responserules/runtime/response_system.cpp index 7ae9276c..8f41ad30 100644 --- a/sp/src/responserules/runtime/response_system.cpp +++ b/sp/src/responserules/runtime/response_system.cpp @@ -1494,23 +1494,35 @@ void CResponseSystem::ParseInclude() #ifdef MAPBASE char scriptfile[256]; GetCurrentScript( scriptfile, sizeof( scriptfile ) ); + V_RemoveDotSlashes( scriptfile ); + + const char *pScriptFile = scriptfile; + if (pScriptFile[0] == CORRECT_PATH_SEPARATOR || pScriptFile[0] == INCORRECT_PATH_SEPARATOR) + pScriptFile++; // Gets first path // (for example, an #include from a file in resource/script/resp will return resource) - size_t len = strlen(scriptfile)-1; + size_t len = strlen( pScriptFile )-1; for (size_t i = 0; i < len; i++) { - if (scriptfile[i] == CORRECT_PATH_SEPARATOR || scriptfile[i] == INCORRECT_PATH_SEPARATOR) + if (pScriptFile[i] == CORRECT_PATH_SEPARATOR || pScriptFile[i] == INCORRECT_PATH_SEPARATOR) { len = i; } } - Q_strncpy(includefile, scriptfile, len+1); + Q_strncpy(includefile, pScriptFile, len+1); - if (len+1 != strlen(scriptfile)) + if (len+1 != strlen( pScriptFile )) { - Q_strncat( includefile, "/", sizeof( includefile ) ); - Q_strncat( includefile, token, sizeof( includefile ) ); + if (!includefile[0]) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "%s: Unable to parse first folder from parent script \"%s\", falling back to 'scripts/'\n", token, pScriptFile ); + } + else + { + Q_strncat( includefile, CORRECT_PATH_SEPARATOR_S, sizeof( includefile ) ); + Q_strncat( includefile, token, sizeof( includefile ) ); + } } else includefile[0] = '\0'; @@ -1643,6 +1655,8 @@ inline ResponseType_t ComputeResponseType( const char *s ) return RESPONSE_VSCRIPT_FILE; else return RESPONSE_VSCRIPT; + case 'c': + return RESPONSE_CHOREOSENTENCE; #endif } diff --git a/sp/src/responserules/runtime/rr_response.cpp b/sp/src/responserules/runtime/rr_response.cpp index b652e8b3..eb21a55b 100644 --- a/sp/src/responserules/runtime/rr_response.cpp +++ b/sp/src/responserules/runtime/rr_response.cpp @@ -260,6 +260,8 @@ const char *CRR_Response::DescribeResponse( ResponseType_t type ) return "RESPONSE_VSCRIPT"; case ResponseRules::RESPONSE_VSCRIPT_FILE: return "RESPONSE_VSCRIPT_FILE"; + case ResponseRules::RESPONSE_CHOREOSENTENCE: + return "RESPONSE_CHOREOSENTENCE"; #endif } diff --git a/sp/src/utils/vbsp/vbsp.cpp b/sp/src/utils/vbsp/vbsp.cpp index 75722bfe..54d9e681 100644 --- a/sp/src/utils/vbsp/vbsp.cpp +++ b/sp/src/utils/vbsp/vbsp.cpp @@ -1354,6 +1354,15 @@ int RunVBSP( int argc, char **argv ) " -replacematerials : Substitute materials according to materialsub.txt in content\\maps\n" " -FullMinidumps : Write large minidumps on crash.\n" " -nohiddenmaps : Exclude manifest maps if they are currently hidden.\n" +#ifdef MAPBASE + " -defaultcubemap : Makes a dummy cubemap.\n" + " -skyboxcubemap : Makes a skybox cubemaps for LDR cubemaps. (HDR skybox cubemaps are not supported)\n" + " -defaultcubemapres : Sets the dummy cubemap resolution. (Default 32)\n" + " -defaultproppermodelsstatic : Inserts propper_model into the level.\n" + " -strippropperentities : Strip out any entities with 'propper_' in their classname, as they don't actually exist in-game.\n" + " -scripting : Vscript vbsp system.\n" + " -doc : Prints all the related documentation of vbsp Vscript.\n" +#endif ); } diff --git a/sp/src/utils/vbsp/vscript_funcs_vmfs.cpp b/sp/src/utils/vbsp/vscript_funcs_vmfs.cpp index 8ef38c81..57b22898 100644 --- a/sp/src/utils/vbsp/vscript_funcs_vmfs.cpp +++ b/sp/src/utils/vbsp/vscript_funcs_vmfs.cpp @@ -18,7 +18,7 @@ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -static HSCRIPT VMFKV_CreateBlank() +static HSCRIPT_RC VMFKV_CreateBlank() { KeyValues *pKV = new KeyValues("VMF"); @@ -28,7 +28,7 @@ static HSCRIPT VMFKV_CreateBlank() pWorld->SetString( "classname", "worldspawn" ); } - return scriptmanager->CreateScriptKeyValues( g_pScriptVM, pKV, true ); + return scriptmanager->CreateScriptKeyValues( g_pScriptVM, pKV ); } static bool VMFKV_SaveToFile( const char *szFile, HSCRIPT hKV ) @@ -69,7 +69,7 @@ static bool VMFKV_SaveToFile( const char *szFile, HSCRIPT hKV ) return res; } -static HSCRIPT VMFKV_LoadFromFile( const char *szFile ) +static HSCRIPT_RC VMFKV_LoadFromFile( const char *szFile ) { char pszFullName[MAX_PATH]; V_snprintf( pszFullName, sizeof(pszFullName), NULL, szFile ); @@ -87,7 +87,7 @@ static HSCRIPT VMFKV_LoadFromFile( const char *szFile ) return NULL; } - HSCRIPT hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pKV, true ); // bAllowDestruct is supposed to automatically remove the involved KV + HSCRIPT hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pKV ); return hScript; } @@ -142,7 +142,7 @@ static HSCRIPT VMFKV_AddEntityFromTables( HSCRIPT hVMF, HSCRIPT hKV, HSCRIPT hIO } } - return scriptmanager->CreateScriptKeyValues( g_pScriptVM, pEnt, false ); + return scriptmanager->CreateScriptKeyValues( g_pScriptVM, pEnt, true ); } //----------------------------------------------------------------------------- diff --git a/sp/src/utils/vrad/vrad.cpp b/sp/src/utils/vrad/vrad.cpp index a7cba1c0..79cc467d 100644 --- a/sp/src/utils/vrad/vrad.cpp +++ b/sp/src/utils/vrad/vrad.cpp @@ -2471,6 +2471,15 @@ int ParseCommandLine( int argc, char **argv, bool *onlydetail ) { do_fast = true; } +#ifdef MAPBASE + else if(!Q_stricmp(argv[i], "-ultrafast")) + { + do_fast = true; + g_bFastAmbient = true; + do_extra = false; + numbounce = 1; + } +#endif else if (!Q_stricmp(argv[i],"-noskyboxrecurse")) { g_bNoSkyRecurse = true; @@ -2491,6 +2500,26 @@ int ParseCommandLine( int argc, char **argv, bool *onlydetail ) return 1; } } +#ifdef MAPBASE + else if (!Q_stricmp(argv[i], "-extrapasses")) + { + if (++i < argc) + { + int extrapassesParam = atoi(argv[i]); + if (extrapassesParam < 0) + { + Warning("Error: expected non-negative value after '-extrapasses'\n"); + return 1; + } + extrapasses = extrapassesParam; + } + else + { + Warning("Error: expected a value after '-extrapasses'\n"); + return 1; + } + } +#endif else if (!Q_stricmp(argv[i],"-centersamples")) { do_centersamples = true; @@ -2777,9 +2806,15 @@ void PrintUsage( int argc, char **argv ) " -v (or -verbose): Turn on verbose output (also shows more command\n" " -bounce # : Set max number of bounces (default: 100).\n" " -fast : Quick and dirty lighting.\n" +#ifdef MAPBASE + " -ultrafast : Very quick and dirty lighting, same as -fast -fastambient -noextra -bounce 1\n" +#endif " -fastambient : Per-leaf ambient sampling is lower quality to save compute time.\n" " -final : High quality processing. equivalent to -extrasky 16.\n" - " -extrasky n : trace N times as many rays for indirect light and sky ambient.\n" + " -extrasky # : trace # times as many rays for indirect light and sky ambient.\n" +#ifdef MAPBASE + " -extrapasses # : Lets you scale how many extra passes you want your map to go through (default 4), differences above this value are minimal.\n" +#endif " -low : Run as an idle-priority process.\n" " -mpi : Use VMPI to distribute computations.\n" " -rederror : Show errors in red.\n" @@ -2813,7 +2848,7 @@ void PrintUsage( int argc, char **argv ) " -loghash : Log the sample hash table to samplehash.txt.\n" " -onlydetail : Only light detail props and per-leaf lighting.\n" " -maxdispsamplesize #: Set max displacement sample size (default: 512).\n" - " -softsun : Treat the sun as an area light source of size degrees." + " -softsun # : Treat the sun as an area light source of size # degrees." " Produces soft shadows.\n" " Recommended values are between 0 and 5. Default is 0.\n" " -FullMinidumps : Write large minidumps on crash.\n" diff --git a/sp/src/vgui2/vgui_controls/RichText.cpp b/sp/src/vgui2/vgui_controls/RichText.cpp index 66802324..3e8251df 100644 --- a/sp/src/vgui2/vgui_controls/RichText.cpp +++ b/sp/src/vgui2/vgui_controls/RichText.cpp @@ -847,34 +847,40 @@ void RichText::Paint() // 3. // Calculate the range of text to draw all at once - int iLim = m_TextStream.Count(); + int iLim = m_TextStream.Count() - 1; + // Stop at the next line break + if ( m_LineBreaks.IsValidIndex( lineBreakIndexIndex ) && m_LineBreaks[lineBreakIndexIndex] <= iLim ) + iLim = m_LineBreaks[lineBreakIndexIndex] - 1; + // Stop at the next format change if ( m_FormatStream.IsValidIndex(renderState.formatStreamIndex) && - m_FormatStream[renderState.formatStreamIndex].textStreamIndex < iLim && + m_FormatStream[renderState.formatStreamIndex].textStreamIndex <= iLim && m_FormatStream[renderState.formatStreamIndex].textStreamIndex >= i && m_FormatStream[renderState.formatStreamIndex].textStreamIndex ) { - iLim = m_FormatStream[renderState.formatStreamIndex].textStreamIndex; + iLim = m_FormatStream[renderState.formatStreamIndex].textStreamIndex - 1; } - // Stop at the next line break - if ( m_LineBreaks.IsValidIndex( lineBreakIndexIndex ) && m_LineBreaks[lineBreakIndexIndex] < iLim ) - iLim = m_LineBreaks[lineBreakIndexIndex]; + // Stop when entering or exiting the selected range + if ( i < selection0 && iLim >= selection0 ) + iLim = selection0 - 1; + if ( i >= selection0 && i < selection1 && iLim >= selection1 ) + iLim = selection1 - 1; // Handle non-drawing characters specially - for ( int iT = i; iT < iLim; iT++ ) + for ( int iT = i; iT <= iLim; iT++ ) { if ( iswcntrl(m_TextStream[iT]) ) { - iLim = iT; + iLim = iT - 1; break; } } // 4. // Draw the current text range - if ( iLim <= i ) + if ( iLim < i ) { if ( m_TextStream[i] == '\t' ) { @@ -887,8 +893,8 @@ void RichText::Paint() } else { - renderState.x += DrawString(i, iLim - 1, renderState, hFontCurrent ); - i = iLim; + renderState.x += DrawString(i, iLim, renderState, hFontCurrent ); + i = iLim + 1; } } diff --git a/sp/src/vpc_scripts/newer_vs_toolsets.vpc b/sp/src/vpc_scripts/newer_vs_toolsets.vpc index 950e1677..5e75a494 100644 --- a/sp/src/vpc_scripts/newer_vs_toolsets.vpc +++ b/sp/src/vpc_scripts/newer_vs_toolsets.vpc @@ -12,6 +12,10 @@ $Conditional VS2017 "0" // Toggles Visual Studio 2017 (v141) toolset $Conditional VS2019 "0" // Toggles Visual Studio 2019 (v142) toolset $Conditional VS2022 "0" // Toggles Visual Studio 2022 (v143) toolset +// +// WARNING: If you do not have Visual Studio 2013 installed and try to use VPC, it may produce an error about a missing RegKey. +// You can fix this without installing VS2013 by running newer_vs_toolsets_regkey_fix.reg, which is in the same directory as this file. +// This .reg file adds a registry key which is normally created by installing VS2013. You will only have to do this once per machine. // // VPC may still say "Generating for Visual Studio 2013" even when using one of the above toolsets. This message is irrelevant and can be ignored. // diff --git a/sp/src/vpc_scripts/newer_vs_toolsets_regkey_fix.reg b/sp/src/vpc_scripts/newer_vs_toolsets_regkey_fix.reg new file mode 100644 index 00000000..dfb81949 --- /dev/null +++ b/sp/src/vpc_scripts/newer_vs_toolsets_regkey_fix.reg @@ -0,0 +1,7 @@ +Windows Registry Editor Version 5.00 + +; https://github.com/ValveSoftware/source-sdk-2013/issues/72#issuecomment-326633328 +; If you are running a 32-bit system, you may need to remove WOW6432Node from the path. + +[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\10.0\Projects\{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}] +"DefaultProjectExtension"="vcproj" diff --git a/sp/src/vscript/sqdbg/include/sqdbg.h b/sp/src/vscript/sqdbg/include/sqdbg.h new file mode 100644 index 00000000..fe1415d4 --- /dev/null +++ b/sp/src/vscript/sqdbg/include/sqdbg.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// github.com/samisalreadytaken/sqdbg +//----------------------------------------------------------------------- +// +// Squirrel Debugger +// + +#ifndef SQDBG_H +#define SQDBG_H + +#include + +#define SQDBG_SV_API_VER 1 + +#ifndef SQDBG_API +#ifdef SQDBG_DLL + #if defined(_WIN32) + #ifdef SQDBG_DLL_EXPORT + #define SQDBG_API __declspec(dllexport) + #else + #define SQDBG_API __declspec(dllimport) + #endif + #elif defined(__GNUC__) + #ifdef SQDBG_DLL_EXPORT + #define SQDBG_API __attribute__((visibility("default"))) + #else + #define SQDBG_API extern + #endif + #else + #define SQDBG_API extern + #endif +#else + #define SQDBG_API extern +#endif +#endif + +struct SQDebugServer; +typedef SQDebugServer* HSQDEBUGSERVER; + +#ifdef __cplusplus +extern "C" { +#endif + +// Create and attach a new debugger +// Memory is owned by the VM, it is freed when the VM dies or +// the debugger is disconnected via sqdbg_destroy_debugger() +SQDBG_API HSQDEBUGSERVER sqdbg_attach_debugger( HSQUIRRELVM vm ); + +// Detach and destroy the debugger attached to this VM +// Invalidates the handle returned from sqdbg_attach_debugger() +SQDBG_API void sqdbg_destroy_debugger( HSQUIRRELVM vm ); + +// Open specified port and allow client connections +// If port is 0, the system will choose a unique available port +// Returns 0 on success +SQDBG_API int sqdbg_listen_socket( HSQDEBUGSERVER dbg, unsigned short port ); + +// Process client connections and incoming messages +// Blocks on script breakpoints while a client is connected +SQDBG_API void sqdbg_frame( HSQDEBUGSERVER dbg ); + +// Copies the script to be able to source it to debugger clients +SQDBG_API void sqdbg_on_script_compile( HSQDEBUGSERVER dbg, const SQChar *script, SQInteger size, + const SQChar *sourcename, SQInteger sourcenamelen ); + +#ifdef __cplusplus +} +#endif + +#endif // SQDBG_H diff --git a/sp/src/vscript/sqdbg/sqdbg/debug.h b/sp/src/vscript/sqdbg/sqdbg/debug.h new file mode 100644 index 00000000..e7d04b9c --- /dev/null +++ b/sp/src/vscript/sqdbg/sqdbg/debug.h @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------- +// github.com/samisalreadytaken/sqdbg +//----------------------------------------------------------------------- +// + +#ifndef SQDBG_DEBUG_H +#define SQDBG_DEBUG_H + +#if 0 + +#ifdef _DEBUG + #ifdef _WIN32 + #include + + bool __IsDebuggerPresent(); + const char *GetModuleBaseName(); + + #define DebuggerBreak() do { if ( __IsDebuggerPresent() ) __debugbreak(); } while(0) + + #define Assert( x ) \ + do { \ + __CAT( L, __LINE__ ): \ + if ( !(x) && (1 == _CrtDbgReport(_CRT_ASSERT, __FILE__, __LINE__, GetModuleBaseName(), #x)) ) \ + { \ + if ( !__IsDebuggerPresent() ) \ + goto __CAT( L, __LINE__ ); \ + __debugbreak(); \ + } \ + } while(0) + + #define AssertMsg( x, msg ) \ + do { \ + __CAT( L, __LINE__ ): \ + if ( !(x) && (1 == _CrtDbgReport(_CRT_ASSERT, __FILE__, __LINE__, GetModuleBaseName(), msg)) ) \ + { \ + if ( !__IsDebuggerPresent() ) \ + goto __CAT( L, __LINE__ ); \ + __debugbreak(); \ + } \ + } while(0) + + #define AssertMsg1( x, msg, a1 ) \ + do { \ + __CAT( L, __LINE__ ): \ + if ( !(x) && (1 == _CrtDbgReport(_CRT_ASSERT, __FILE__, __LINE__, GetModuleBaseName(), msg, a1)) ) \ + { \ + if ( !__IsDebuggerPresent() ) \ + goto __CAT( L, __LINE__ ); \ + __debugbreak(); \ + } \ + } while(0) + + #define AssertMsg2( x, msg, a1, a2 ) \ + do { \ + __CAT( L, __LINE__ ): \ + if ( !(x) && (1 == _CrtDbgReport(_CRT_ASSERT, __FILE__, __LINE__, GetModuleBaseName(), msg, a1, a2)) ) \ + { \ + if ( !__IsDebuggerPresent() ) \ + goto __CAT( L, __LINE__ ); \ + __debugbreak(); \ + } \ + } while(0) + #else + extern "C" int printf(const char *, ...); + + #define DebuggerBreak() asm("int3") + + #define Assert( x ) \ + do { \ + if ( !(x) ) \ + { \ + ::printf("Assertion failed %s:%d: %s\n", __FILE__, __LINE__, #x); \ + DebuggerBreak(); \ + } \ + } while(0) + + #define AssertMsg( x, msg ) \ + do { \ + if ( !(x) ) \ + { \ + ::printf("Assertion failed %s:%d: %s\n", __FILE__, __LINE__, msg); \ + DebuggerBreak(); \ + } \ + } while(0) + + #define AssertMsg1( x, msg, a1 ) \ + do { \ + if ( !(x) ) \ + { \ + ::printf("Assertion failed %s:%d: ", __FILE__, __LINE__); \ + ::printf(msg, a1); \ + ::printf("\n"); \ + DebuggerBreak(); \ + } \ + } while(0) + + #define AssertMsg2( x, msg, a1, a2 ) \ + do { \ + if ( !(x) ) \ + { \ + ::printf("Assertion failed %s:%d: ", __FILE__, __LINE__); \ + ::printf(msg, a1, a2); \ + ::printf("\n"); \ + DebuggerBreak(); \ + } \ + } while(0) + #endif + #define Verify( x ) Assert(x) +#else + #define DebuggerBreak() ((void)0) + #define Assert( x ) ((void)0) + #define AssertMsg( x, msg ) ((void)0) + #define AssertMsg1( x, msg, a1 ) ((void)0) + #define AssertMsg2( x, msg, a1, a2 ) ((void)0) + #define Verify( x ) x +#endif // _DEBUG + +#endif + +#include + +// Misdefined for GCC in platform.h +#undef UNREACHABLE + +#ifdef _WIN32 + #define UNREACHABLE() do { Assert(!"UNREACHABLE"); __assume(0); } while(0) +#else + #define UNREACHABLE() do { Assert(!"UNREACHABLE"); __builtin_unreachable(); } while(0) +#endif + +#endif // SQDBG_DEBUG_H diff --git a/sp/src/vscript/sqdbg/sqdbg/json.h b/sp/src/vscript/sqdbg/sqdbg/json.h new file mode 100644 index 00000000..45808d00 --- /dev/null +++ b/sp/src/vscript/sqdbg/sqdbg/json.h @@ -0,0 +1,1200 @@ +//----------------------------------------------------------------------- +// github.com/samisalreadytaken/sqdbg +//----------------------------------------------------------------------- +// + +#ifndef SQDBG_JSON_H +#define SQDBG_JSON_H + +// Most messages are going to require less than 256 bytes, +// only approaching 1024 on large breakpoint requests +#define JSON_SCRATCH_CHUNK_SIZE 1024 + +typedef enum +{ + JSON_NULL = 0x0000, + JSON_BOOL = 0x0001, + JSON_INTEGER = 0x0002, + JSON_FLOAT = 0x0004, + JSON_STRING = 0x0008, + JSON_TABLE = 0x0010, + JSON_ARRAY = 0x0020, +} JSONTYPE; + +class json_table_t; +class json_array_t; +struct json_value_t; + +struct ostr_t +{ + // ushort can handle strings that fit the recv net buf, + // use a larger type for validation of sent messages in the send net buf +#ifdef SQDBG_VALIDATE_SENT_MSG + typedef unsigned int index_t; +#else + typedef unsigned short index_t; +#endif + + index_t ofs; + index_t len; +}; + +struct json_value_t +{ + union + { + ostr_t _string; + int _integer; + json_table_t *_table; + json_array_t *_array; + }; + + int type; +}; + +struct json_field_t +{ + ostr_t key; + json_value_t val; +}; + +class json_array_t +{ +public: + const char *m_pBase; + CScratch< JSON_SCRATCH_CHUNK_SIZE > *m_Allocator; + int *m_Elements; + unsigned short m_nElementCount; + unsigned short m_nElementsSize; + + void Init( const char *base, CScratch< JSON_SCRATCH_CHUNK_SIZE > *allocator ) + { + m_pBase = base; + m_Allocator = allocator; + m_nElementCount = 0; + m_nElementsSize = 0; + } + + json_value_t *NewElement() + { + if ( m_nElementCount == m_nElementsSize ) + { + // doesn't free old ptr, this is an uncommon operation and extra allocation is fine + int oldsize = m_nElementsSize; + int *oldptr = m_Elements; + + m_nElementsSize = !m_nElementsSize ? 8 : ( m_nElementsSize << 1 ); + m_Elements = (int*)m_Allocator->Alloc( m_nElementsSize * sizeof(int) ); + + if ( oldsize ) + memcpy( m_Elements, oldptr, oldsize * sizeof(int) ); + } + + int index; + json_value_t *ret = (json_value_t*)m_Allocator->Alloc( sizeof(json_value_t), &index ); + m_Elements[ m_nElementCount++ ] = index; + return ret; + } + + int size() const + { + return m_nElementCount; + } + + bool GetString( int i, string_t *out ) + { + Assert( m_nElementCount ); + Assert( i >= 0 && i < m_nElementCount ); + + json_value_t *val = (json_value_t*)m_Allocator->Get( m_Elements[i] ); + + if ( val->type & JSON_STRING ) + { + out->Assign( m_pBase + val->_string.ofs, val->_string.len ); + return true; + } + + return false; + } + + bool GetTable( int i, json_table_t **out ) + { + Assert( m_nElementCount ); + Assert( i >= 0 && i < m_nElementCount ); + + json_value_t *val = (json_value_t*)m_Allocator->Get( m_Elements[i] ); + + if ( val->type & JSON_TABLE ) + { + *out = val->_table; + return true; + } + + return false; + } +}; + +class json_table_t +{ +public: + const char *m_pBase; + CScratch< JSON_SCRATCH_CHUNK_SIZE > *m_Allocator; + int *m_Elements; + unsigned short m_nElementCount; + unsigned short m_nElementsSize; + + void Init( const char *base, CScratch< JSON_SCRATCH_CHUNK_SIZE > *allocator ) + { + m_pBase = base; + m_Allocator = allocator; + m_nElementCount = 0; + m_nElementsSize = 0; + } + + json_value_t *Get( const string_t &key ) + { + for ( int i = 0; i < m_nElementCount; i++ ) + { + json_field_t *kv = (json_field_t*)m_Allocator->Get( m_Elements[i] ); + + if ( key.IsEqualTo( m_pBase + kv->key.ofs, kv->key.len ) ) + return &kv->val; + } + + return NULL; + } + + json_value_t *Get( const string_t &key ) const + { + return const_cast< json_table_t * >( this )->Get( key ); + } + + json_field_t *NewElement() + { + if ( m_nElementCount == m_nElementsSize ) + { + int oldsize = m_nElementsSize; + int *oldptr = m_Elements; + + m_nElementsSize = !m_nElementsSize ? 8 : ( m_nElementsSize << 1 ); + m_Elements = (int*)m_Allocator->Alloc( m_nElementsSize * sizeof(int) ); + + if ( oldsize ) + memcpy( m_Elements, oldptr, oldsize * sizeof(int) ); + } + + int index; + json_field_t *ret = (json_field_t*)m_Allocator->Alloc( sizeof(json_field_t), &index ); + m_Elements[ m_nElementCount++ ] = index; + return ret; + } + + bool GetBool( const string_t &key, bool *out ) const + { + json_value_t *kval = Get( key ); + + if ( kval && ( kval->type & JSON_BOOL ) ) + { + *out = kval->_integer; + return true; + } + + *out = false; + return false; + } + + bool GetInt( const string_t &key, int *out, int defaultVal = 0 ) const + { + json_value_t *kval = Get( key ); + + if ( kval && ( kval->type & JSON_INTEGER ) ) + { + *out = kval->_integer; + return true; + } + + *out = defaultVal; + return false; + } + + bool GetString( const string_t &key, string_t *out, const char *defaultVal = "" ) const + { + json_value_t *kval = Get( key ); + + if ( kval && ( kval->type & JSON_STRING ) ) + { + out->Assign( m_pBase + kval->_string.ofs, kval->_string.len ); + return true; + } + + out->Assign( defaultVal, strlen(defaultVal) ); + return false; + } + + bool GetTable( const string_t &key, json_table_t **out ) const + { + json_value_t *kval = Get( key ); + + if ( kval && ( kval->type & JSON_TABLE ) ) + { + *out = kval->_table; + return true; + } + + *out = NULL; + return false; + } + + bool GetArray( const string_t &key, json_array_t **out ) const + { + json_value_t *kval = Get( key ); + + if ( kval && ( kval->type & JSON_ARRAY ) ) + { + *out = kval->_array; + return true; + } + + *out = NULL; + return false; + } + + bool Get( const string_t &key, bool *out ) const { return GetBool( key, out ); } + bool Get( const string_t &key, int *out ) const { return GetInt( key, out ); } + bool Get( const string_t &key, string_t *out ) const { return GetString( key, out ); } + bool Get( const string_t &key, json_table_t **out ) const { return GetTable( key, out ); } + bool Get( const string_t &key, json_array_t **out ) const { return GetArray( key, out ); } +}; + +static inline void PutStrL( CBuffer *buffer, const string_t &str ) +{ + buffer->_base.Ensure( buffer->size() + str.len ); + memcpy( buffer->base() + buffer->size(), str.ptr, str.len ); + buffer->_size += str.len; +} + +static inline void PutStr( CBuffer *buffer, const string_t &str, bool quote ) +{ + const char *c = str.ptr; + unsigned int i = str.len; + + unsigned int len = 2 + i; + + if ( quote ) + len += 4; + + for ( ; i--; c++ ) + { + switch ( *c ) + { + case '\"': case '\\': case '\b': + case '\f': case '\n': case '\r': case '\t': + len++; + if ( quote ) + { + len++; + if ( *c == '\"' || *c == '\\' ) + len++; + } + break; + default: + if ( !IN_RANGE_CHAR( *(unsigned char*)c, 0x20, 0x7E ) ) + { + int ret = IsValidUTF8( (unsigned char*)c, i + 1 ); + if ( ret != 0 ) + { + i -= ret - 1; + c += ret - 1; + } + else + { + if ( !quote ) + { + len += sizeof(uint16_t) * 2 + 1; + } + else + { + len += sizeof(SQChar) * 2 + 2; + } + } + } + } + } + + buffer->_base.Ensure( buffer->size() + len ); + + char *mem = buffer->base(); + unsigned int idx = buffer->size(); + + c = str.ptr; + i = str.len; + + mem[idx++] = '\"'; + + if ( quote ) + { + mem[idx++] = '\\'; + mem[idx++] = '\"'; + } + + for ( ; i--; c++ ) + { + mem[idx++] = *c; + + switch ( *c ) + { + case '\"': + mem[idx-1] = '\\'; + if ( quote ) + { + mem[idx++] = '\\'; + mem[idx++] = '\\'; + } + mem[idx++] = '\"'; + break; + case '\\': + if ( quote ) + { + mem[idx++] = '\\'; + mem[idx++] = '\\'; + } + mem[idx++] = '\\'; + break; + case '\b': + mem[idx-1] = '\\'; + if ( quote ) + mem[idx++] = '\\'; + mem[idx++] = 'b'; + break; + case '\f': + mem[idx-1] = '\\'; + if ( quote ) + mem[idx++] = '\\'; + mem[idx++] = 'f'; + break; + case '\n': + mem[idx-1] = '\\'; + if ( quote ) + mem[idx++] = '\\'; + mem[idx++] = 'n'; + break; + case '\r': + mem[idx-1] = '\\'; + if ( quote ) + mem[idx++] = '\\'; + mem[idx++] = 'r'; + break; + case '\t': + mem[idx-1] = '\\'; + if ( quote ) + mem[idx++] = '\\'; + mem[idx++] = 't'; + break; + default: + if ( !IN_RANGE_CHAR( *(unsigned char*)c, 0x20, 0x7E ) ) + { + int ret = IsValidUTF8( (unsigned char*)c, i + 1 ); + if ( ret != 0 ) + { + memcpy( mem + idx, c + 1, ret - 1 ); + idx += ret - 1; + i -= ret - 1; + c += ret - 1; + } + else + { + mem[idx-1] = '\\'; + + if ( !quote ) + { + mem[idx++] = 'u'; + idx += printhex< true, false >( + mem + idx, + buffer->capacity() - idx, + (uint16_t)*(unsigned char*)c ); + } + else + { + mem[idx++] = '\\'; + mem[idx++] = 'x'; + idx += printhex< true, false >( + mem + idx, + buffer->capacity() - idx, + (SQChar)*(unsigned char*)c ); + } + } + } + } + } + + if ( quote ) + { + mem[idx++] = '\\'; + mem[idx++] = '\"'; + } + + mem[idx++] = '\"'; + + buffer->_size = idx; +} + +#ifdef SQUNICODE +static inline void PutStr( CBuffer *buffer, const sqstring_t &str, bool quote ) +{ + unsigned int len; + + if ( !quote ) + { + len = 2 + UTF8Length< kUTFEscapeJSON >( str.ptr, str.len ); + } + else + { + len = 2 + UTF8Length< kUTFEscapeQuoted >( str.ptr, str.len ); + } + + buffer->_base.Ensure( buffer->size() + len ); + buffer->base()[buffer->_size++] = '\"'; + + if ( !quote ) + { + len = SQUnicodeToUTF8< kUTFEscapeJSON >( + buffer->base() + buffer->size(), + buffer->capacity() - buffer->size(), + str.ptr, + str.len ); + } + else + { + len = SQUnicodeToUTF8< kUTFEscapeQuoted >( + buffer->base() + buffer->size(), + buffer->capacity() - buffer->size(), + str.ptr, + str.len ); + } + + buffer->_size += len; + buffer->base()[buffer->_size++] = '\"'; +} +#endif + +static inline void PutChar( CBuffer *buffer, char c ) +{ + buffer->_base.Ensure( buffer->size() + 1 ); + buffer->base()[buffer->_size++] = c; +} + +template < typename I > +static inline void PutInt( CBuffer *buffer, I val, bool hex = false ) +{ + int len; + buffer->_base.Ensure( buffer->size() + countdigits( val ) + 1 ); + + if ( !hex ) + { + len = printint( buffer->base() + buffer->size(), buffer->capacity() - buffer->size(), val ); + } + else + { + len = printhex< false >( buffer->base() + buffer->size(), buffer->capacity() - buffer->size(), val ); + } + + buffer->_size += len; +} + +class wjson_table_t; +class wjson_array_t; + +class wjson_t +{ +public: + CBuffer *m_pBuffer; + int m_nElementCount; + + wjson_t( CBuffer *b ) : + m_pBuffer(b), + m_nElementCount(0) + { + } +}; + +class wjson_table_t : public wjson_t +{ +public: + wjson_table_t( CBuffer &b ) : wjson_t(&b) + { + PutChar( m_pBuffer, '{' ); + } + + ~wjson_table_t() + { + PutChar( m_pBuffer, '}' ); + } + + wjson_table_t( const wjson_t &src ) : wjson_t(src) + { + } + + wjson_table_t( const wjson_table_t &src ); + + void PutKey( const string_t &key ) + { + if ( m_nElementCount++ ) + PutChar( m_pBuffer, ',' ); + + PutChar( m_pBuffer, '\"' ); + PutStrL( m_pBuffer, key ); + PutChar( m_pBuffer, '\"' ); + PutChar( m_pBuffer, ':' ); + } + + void SetInt( const string_t &key, int val ) + { + PutKey( key ); + PutInt( m_pBuffer, val ); + } + + void SetNull( const string_t &key ) + { + PutKey( key ); + PutStrL( m_pBuffer, "null" ); + } + + void SetBool( const string_t &key, bool val ) + { + PutKey( key ); + PutStrL( m_pBuffer, val ? string_t("true") : string_t("false") ); + } + + void SetString( const string_t &key, const string_t &val, bool quote = false ) + { + PutKey( key ); + PutStr( m_pBuffer, val, quote ); + } + +#ifdef SQUNICODE + void SetString( const string_t &key, const sqstring_t &val, bool quote = false ) + { + PutKey( key ); + PutStr( m_pBuffer, val, quote ); + } +#endif + + void SetIntString( const string_t &key, int val ) + { + PutKey( key ); + PutChar( m_pBuffer, '\"' ); + PutInt( m_pBuffer, val ); + PutChar( m_pBuffer, '\"' ); + } + + template < typename I > + void SetIntBrackets( const string_t &key, I val, bool hex = false ) + { + PutKey( key ); + PutChar( m_pBuffer, '\"' ); + PutChar( m_pBuffer, '[' ); + PutInt( m_pBuffer, val, hex ); + PutChar( m_pBuffer, ']' ); + PutChar( m_pBuffer, '\"' ); + } + + wjson_t SetArray( const string_t &key ) + { + PutKey( key ); + PutChar( m_pBuffer, '[' ); + return { m_pBuffer }; + } + + wjson_t SetTable( const string_t &key ) + { + PutKey( key ); + PutChar( m_pBuffer, '{' ); + return { m_pBuffer }; + } + + void Set( const string_t &key, bool val ) { SetBool( key, val ); } + void Set( const string_t &key, int val ) { SetInt( key, val ); } + void Set( const string_t &key, unsigned int val ) { SetInt( key, val ); } + void Set( const string_t &key, const string_t &val ) { SetString( key, val ); } +}; + +class wjson_array_t : public wjson_t +{ +public: + wjson_array_t( CBuffer &b ) : wjson_t(&b) + { + PutChar( m_pBuffer, '[' ); + } + + ~wjson_array_t() + { + PutChar( m_pBuffer, ']' ); + } + + wjson_array_t( const wjson_t &src ) : wjson_t(src) + { + } + + wjson_array_t( const wjson_array_t &src ); + + int size() + { + return m_nElementCount; + } + + wjson_t AppendTable() + { + if ( m_nElementCount++ ) + PutChar( m_pBuffer, ',' ); + + PutChar( m_pBuffer, '{' ); + return { m_pBuffer }; + } + + void Append( int val ) + { + if ( m_nElementCount++ ) + PutChar( m_pBuffer, ',' ); + + PutInt( m_pBuffer, val ); + } + + void Append( const string_t &val ) + { + if ( m_nElementCount++ ) + PutChar( m_pBuffer, ',' ); + + PutChar( m_pBuffer, '\"' ); + PutStrL( m_pBuffer, val ); + PutChar( m_pBuffer, '\"' ); + } +}; + +class JSONParser +{ +private: + char *m_cur; + char *m_end; + char *m_start; + CScratch< JSON_SCRATCH_CHUNK_SIZE > *m_Allocator; + char *m_error; + + enum + { + Token_Error = 0, + Token_String, + Token_Integer, + Token_Float, + Token_False, + Token_True = Token_False + 1, + Token_Null, + Token_Table = '{', + Token_Array = '[', + }; + +public: + JSONParser( CScratch< JSON_SCRATCH_CHUNK_SIZE > *allocator, char *ptr, int len, json_table_t *pTable ) : + m_cur( ptr ), + m_end( ptr + len + 1 ), + m_start( ptr ), + m_Allocator( allocator ), + m_error( NULL ) + { + string_t token; + char type = NextToken( token ); + + if ( type == '{' ) + { + pTable->Init( m_start, m_Allocator ); + ParseTable( pTable, token ); + } + else + { + SetError( "expected '%c', got '0x%02x' @ %i", '{', Char(type), Index() ); + } + } + + const char *GetError() const + { + return m_error; + } + +private: + int Index() + { + return m_cur - m_start; + } + + unsigned char Char( char token ) + { + if ( token != Token_Error ) + return (unsigned char)token; + + return *(unsigned char*)m_cur; + } + + void SetError( const char *fmt, ... ) + { + if ( m_error ) + return; + + const int size = 48; + m_error = m_Allocator->Alloc( size ); + + va_list va; + va_start( va, fmt ); + int len = vsnprintf( m_error, size, fmt, va ); + va_end( va ); + + if ( len < 0 || len > size-1 ) + len = size-1; + + m_error[len] = 0; + } + + char NextToken( string_t &token ) + { + while ( m_cur < m_end ) + { + switch ( *m_cur ) + { + case 0x20: case 0x0A: case 0x0D: case 0x09: + m_cur++; + break; + + case '\"': + return ParseString( token ); + + case '-': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return ParseNumber( token ); + + case ':': case ',': + case '{': case '}': + case '[': case ']': + return *m_cur++; + + case 't': + if ( m_cur + 4 >= m_end || + m_cur[1] != 'r' || m_cur[2] != 'u' || m_cur[3] != 'e' ) + { + SetError( "expected %s @ %i", "\"true\"", Index() ); + return Token_Error; + } + + m_cur += 4; + return Token_True; + + case 'f': + if ( m_cur + 5 >= m_end || + m_cur[1] != 'a' || m_cur[2] != 'l' || m_cur[3] != 's' || m_cur[4] != 'e' ) + { + SetError( "expected %s @ %i", "\"false\"", Index() ); + return Token_Error; + } + + m_cur += 5; + return Token_False; + + case 'n': + if ( m_cur + 4 >= m_end || + m_cur[1] != 'u' || m_cur[2] != 'l' || m_cur[3] != 'l' ) + { + SetError( "expected %s @ %i", "\"null\"", Index() ); + return Token_Error; + } + + m_cur += 4; + return Token_Null; + + default: + return Token_Error; + } + } + + return Token_Error; + } + + char ParseString( string_t &token ) + { + char *pStart = ++m_cur; + bool bEscape = false; + + for (;;) + { + if ( m_cur >= m_end ) + { + SetError( "unfinished string @ %i", Index() ); + return Token_Error; + } + + // end + if ( *m_cur == '\"' ) + { + *m_cur = 0; + token.Assign( pStart, m_cur - pStart ); + m_cur++; + break; + } + + if ( *m_cur != '\\' ) + { + m_cur++; + continue; + } + + m_cur++; + + if ( m_cur >= m_end ) + { + SetError( "unfinished string @ %i", Index() ); + return Token_Error; + } + + bEscape = true; + + // Defer unescape until the end of the string is found + switch ( *m_cur ) + { + case '\"': case '\\': case '/': + case 'b': case 'f': + case 'n': case 'r': case 't': + m_cur++; + break; + + case 'u': + if ( m_cur + 4 >= m_end || + !_isxdigit( m_cur[1] ) || !_isxdigit( m_cur[2] ) || + !_isxdigit( m_cur[3] ) || !_isxdigit( m_cur[4] ) ) + { + SetError( "invalid hex escape @ %i", Index() ); + return Token_Error; + } + + m_cur += 4; + break; + + default: + SetError( "invalid escape char '0x%02x' @ %i", *(unsigned char*)m_cur, Index() ); + return Token_Error; + } + } + + if ( bEscape ) + { + char *cur = pStart; + char *end = pStart + token.len; + + do + { + if ( cur[0] != '\\' ) + { + cur++; + continue; + } + +#define _shift( bytesWritten, bytesRead ) \ + memmove( cur + (bytesWritten), cur + (bytesRead), end - ( cur + (bytesRead) ) ); \ + cur += (bytesWritten); \ + end -= (bytesRead) - (bytesWritten); + + switch ( cur[1] ) + { + case '\\': +shift_one: + _shift( 0, 1 ); + cur++; + break; + case '\"': goto shift_one; + case '/': goto shift_one; + case 'b': cur[1] = '\b'; goto shift_one; + case 'f': cur[1] = '\f'; goto shift_one; + case 'n': cur[1] = '\n'; goto shift_one; + case 'r': cur[1] = '\r'; goto shift_one; + case 't': cur[1] = '\t'; goto shift_one; + case 'u': + { + unsigned int val; + Verify( atox( { cur + 2, 4 }, &val ) ); + + if ( val <= 0xFF ) + { + cur[0] = (char)val; + + _shift( 1, 6 ); + break; + } + else if ( val <= 0x7FF ) + { + UTF8_2_FROM_UTF32( (unsigned char*)cur, val ); + + _shift( 2, 6 ); + break; + } + else if ( UTF_SURROGATE(val) ) + { + if ( UTF_SURROGATE_LEAD(val) ) + { + if ( cur + 11 < end && + cur[6] == '\\' && cur[7] == 'u' && + _isxdigit( cur[8] ) && _isxdigit( cur[9] ) && + _isxdigit( cur[10] ) && _isxdigit( cur[11] ) ) + { + unsigned int low; + Verify( atox( { cur + 8, 4 }, &low ) ); + + if ( UTF_SURROGATE_TRAIL( low ) ) + { + val = UTF32_FROM_UTF16_SURROGATE( val, low ); + UTF8_4_FROM_UTF32( (unsigned char*)cur, val ); + + _shift( 4, 12 ); + break; + } + } + } + } + + UTF8_3_FROM_UTF32( (unsigned char*)cur, val ); + + _shift( 3, 6 ); + break; + } + default: UNREACHABLE(); + } + +#undef _shift + } + while ( cur < end ); + + token.len = end - pStart; + token.ptr[token.len] = 0; + } + + return Token_String; + } + + char ParseNumber( string_t &token ) + { + const char *pStart = m_cur; + char type; + + if ( *m_cur == '-' ) + { + m_cur++; + if ( m_cur >= m_end ) + goto err_eof; + } + + if ( *m_cur == '0' ) + { + m_cur++; + if ( m_cur >= m_end ) + goto err_eof; + } + else if ( IN_RANGE_CHAR( *(unsigned char*)m_cur, '1', '9' ) ) + { + do + { + m_cur++; + if ( m_cur >= m_end ) + goto err_eof; + } + while ( IN_RANGE_CHAR( *(unsigned char*)m_cur, '0', '9' ) ); + } + else + { + SetError( "unexpected char '0x%02x' in number @ %i", *(unsigned char*)m_cur, Index() ); + return Token_Error; + } + + type = Token_Integer; + + if ( *m_cur == '.' ) + { + type = Token_Float; + m_cur++; + + while ( m_cur < m_end && IN_RANGE_CHAR( *(unsigned char*)m_cur, '0', '9' ) ) + m_cur++; + + if ( m_cur >= m_end ) + goto err_eof; + } + + if ( *m_cur == 'e' || *m_cur == 'E' ) + { + type = Token_Float; + m_cur++; + + if ( m_cur >= m_end ) + goto err_eof; + + if ( *m_cur == '-' || *m_cur == '+' ) + { + m_cur++; + + if ( m_cur >= m_end ) + goto err_eof; + } + + while ( m_cur < m_end && IN_RANGE_CHAR( *(unsigned char*)m_cur, '0', '9' ) ) + m_cur++; + } + + token.Assign( pStart, m_cur - pStart ); + return type; + +err_eof: + SetError( "unexpected eof" ); + return Token_Error; + } + + char ParseTable( json_table_t *pTable, string_t &token ) + { + char type = NextToken( token ); + + if ( type == '}' ) + return Token_Table; + + for (;;) + { + if ( type != Token_String ) + { + SetError( "expected %s, got '0x%02x' @ %i", "string", Char(type), Index() ); + return Token_Error; + } + + type = NextToken( token ); + + if ( type != ':' ) + { + SetError( "expected '%c', got '0x%02x' @ %i", ':', Char(type), Index() ); + return Token_Error; + } + + json_field_t *kv = pTable->NewElement(); + + Assert( token.ptr - m_start < (ostr_t::index_t)-1 ); + kv->key.ofs = token.ptr - m_start; + kv->key.len = (ostr_t::index_t)token.len; + + type = NextToken( token ); + type = ParseValue( type, token, &kv->val ); + + if ( type == Token_Error ) + { + SetError( "invalid token '0x%02x' @ %i", Char(type), Index() ); + return Token_Error; + } + + type = NextToken( token ); + + if ( type == ',' ) + { + type = NextToken( token ); + } + else if ( type == '}' ) + { + return Token_Table; + } + else + { + SetError( "expected '%c', got '0x%02x' @ %i", '}', Char(type), Index() ); + return Token_Error; + } + } + } + + char ParseArray( json_array_t *pArray, string_t &token ) + { + char type = NextToken( token ); + + if ( type == ']' ) + return Token_Array; + + for (;;) + { + if ( type == Token_Error ) + { + SetError( "expected '%c', got '0x%02x' @ %i", ']', Char(type), Index() ); + return Token_Error; + } + + json_value_t *val = pArray->NewElement(); + type = ParseValue( type, token, val ); + + if ( type == Token_Error ) + { + SetError( "invalid token '0x%02x' @ %i", Char(type), Index() ); + return Token_Error; + } + + type = NextToken( token ); + + if ( type == ',' ) + { + type = NextToken( token ); + } + else if ( type == ']' ) + { + return Token_Array; + } + else + { + SetError( "expected '%c', got '0x%02x' @ %i", ']', Char(type), Index() ); + return Token_Error; + } + } + } + + char ParseValue( char type, string_t &token, json_value_t *value ) + { + switch ( type ) + { + case Token_Integer: + if ( token.len > FMT_INT_LEN ) + { + SetError( "invalid integer literal @ %i", Index() ); + return Token_Error; + } + + value->type = JSON_INTEGER; + Verify( atoi( token, &value->_integer ) ); + return type; + case Token_Float: + value->type = JSON_FLOAT; + // floats are unused, ignore value + return type; + case Token_String: + value->type = JSON_STRING; + Assert( token.ptr - m_start < (ostr_t::index_t)-1 ); + value->_string.ofs = token.ptr - m_start; + value->_string.len = (ostr_t::index_t)token.len; + return type; + case '{': + value->type = JSON_TABLE; + value->_table = (json_table_t*)m_Allocator->Alloc( sizeof(json_table_t) ); + value->_table->Init( m_start, m_Allocator ); + return ParseTable( value->_table, token ); + case '[': + value->type = JSON_ARRAY; + value->_array = (json_array_t*)m_Allocator->Alloc( sizeof(json_array_t) ); + value->_array->Init( m_start, m_Allocator ); + return ParseArray( value->_array, token ); + case Token_False: + case Token_True: + value->type = JSON_BOOL; + value->_integer = type - Token_False; + return type; + case Token_Null: + value->type = JSON_NULL; + return type; + default: + return type; + } + } +}; + +#endif // SQDBG_JSON_H diff --git a/sp/src/vscript/sqdbg/sqdbg/net.h b/sp/src/vscript/sqdbg/sqdbg/net.h new file mode 100644 index 00000000..d8a1ad8d --- /dev/null +++ b/sp/src/vscript/sqdbg/sqdbg/net.h @@ -0,0 +1,846 @@ +//----------------------------------------------------------------------- +// github.com/samisalreadytaken/sqdbg +//----------------------------------------------------------------------- +// + +#ifndef SQDBG_NET_H +#define SQDBG_NET_H + +#ifdef _WIN32 + #include + #include + #ifdef _DEBUG + #include + + inline bool __IsDebuggerPresent() + { + return IsDebuggerPresent(); + } + + inline const char *GetModuleBaseName() + { + static char module[MAX_PATH]; + int len = GetModuleFileNameA( NULL, module, sizeof(module) ); + + if ( len != 0 ) + { + for ( char *pBase = module + len; pBase-- > module; ) + { + if ( *pBase == '\\' ) + return pBase + 1; + } + + return module; + } + + return ""; + } + #endif + + #pragma comment(lib, "Ws2_32.lib") + + #undef RegisterClass + #undef SendMessage + #undef Yield + #undef CONST + #undef PURE + + #undef errno + #define errno WSAGetLastError() + #define strerr(e) gai_strerror(e) +#else + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #define closesocket close + #define ioctlsocket ioctl + #define strerr(e) strerror(e) + + typedef int SOCKET; + #define INVALID_SOCKET -1 + #define SOCKET_ERROR -1 + #define SD_BOTH SHUT_RDWR +#endif + +#ifdef _DEBUG + class CEntryCounter + { + public: + int *count; + CEntryCounter( int *p ) : count(p) { (*count)++; } + ~CEntryCounter() { (*count)--; } + }; + + #define TRACK_ENTRIES() \ + static int s_EntryCount = 0; \ + CEntryCounter entrycounter( &s_EntryCount ); +#else + #define TRACK_ENTRIES() +#endif + +void *sqdbg_malloc( unsigned int size ); +void *sqdbg_realloc( void *p, unsigned int oldsize, unsigned int size ); +void sqdbg_free( void *p, unsigned int size ); + +#ifndef SQDBG_NET_BUF_SIZE +#define SQDBG_NET_BUF_SIZE ( 16 * 1024 ) +#endif + +class CMessagePool +{ +public: + typedef int index_t; + +#pragma pack(push, 4) + struct message_t + { + index_t next; + index_t prev; + unsigned short len; + char ptr[1]; + }; +#pragma pack(pop) + + struct chunk_t + { + char *ptr; + int count; + }; + + static const index_t INVALID_INDEX = 0x80000000; + + // Message queue is going to be less than 16 unless + // there is many variable evaluations at once or network lag + static const int MEM_CACHE_CHUNKS_ALIGN = 16; + + // Most messages are going to be less than 256 bytes, + // only exceeding it on long file paths and long evaluate strings + static const int MEM_CACHE_CHUNKSIZE = 256; + + message_t *Get( index_t index ) + { + Assert( index != INVALID_INDEX ); + + int msgIdx = index & 0x0000ffff; + int chunkIdx = index >> 16; + + Assert( m_Memory ); + Assert( chunkIdx < m_MemChunkCount ); + + chunk_t *chunk = &m_Memory[ chunkIdx ]; + Assert( msgIdx < chunk->count ); + + return (message_t*)&chunk->ptr[ msgIdx * MEM_CACHE_CHUNKSIZE ]; + } + + chunk_t *m_Memory; + int m_MemChunkCount; + int m_ElemCount; + + index_t m_Head; + index_t m_Tail; + + index_t NewMessage( char *pcsMsg, int nLength ) + { + if ( !m_Memory ) + { + m_Memory = (chunk_t*)sqdbg_malloc( m_MemChunkCount * sizeof(chunk_t) ); + AssertOOM( m_Memory, m_MemChunkCount * sizeof(chunk_t) ); + memset( (char*)m_Memory, 0, m_MemChunkCount * sizeof(chunk_t) ); + + chunk_t *chunk = &m_Memory[0]; + chunk->count = MEM_CACHE_CHUNKS_ALIGN; + chunk->ptr = (char*)sqdbg_malloc( chunk->count * MEM_CACHE_CHUNKSIZE ); + AssertOOM( chunk->ptr, chunk->count * MEM_CACHE_CHUNKSIZE ); + memset( chunk->ptr, 0, chunk->count * MEM_CACHE_CHUNKSIZE ); + } + + int requiredChunks = ( sizeof(message_t) + nLength - 1 ) / MEM_CACHE_CHUNKSIZE + 1; + int matchedChunks = 0; + int msgIdx = 0; + int chunkIdx = 0; + + for (;;) + { + chunk_t *chunk = &m_Memory[ chunkIdx ]; + Assert( chunk->count && chunk->ptr ); + + message_t *msg = (message_t*)&chunk->ptr[ msgIdx * MEM_CACHE_CHUNKSIZE ]; + + if ( msg->len == 0 ) + { + if ( ++matchedChunks == requiredChunks ) + { + msgIdx = msgIdx - matchedChunks + 1; + msg = (message_t*)&chunk->ptr[ msgIdx * MEM_CACHE_CHUNKSIZE ]; + + Assert( nLength >= 0 ); + Assert( nLength < ( 1 << ( sizeof(message_t::len) * 8 ) ) ); + + msg->next = msg->prev = INVALID_INDEX; + msg->len = (unsigned short)nLength; + memcpy( msg->ptr, pcsMsg, nLength ); + + return ( chunkIdx << 16 ) | msgIdx; + } + } + else + { + matchedChunks = 0; + } + + msgIdx += ( sizeof(message_t) + msg->len - 1 ) / MEM_CACHE_CHUNKSIZE + 1; + + Assert( msgIdx < 0x0000ffff ); + + if ( msgIdx < chunk->count ) + continue; + + msgIdx = 0; + matchedChunks = 0; + + if ( ++chunkIdx >= m_MemChunkCount ) + { + int oldcount = m_MemChunkCount; + m_MemChunkCount += 4; + m_Memory = (chunk_t*)sqdbg_realloc( m_Memory, + oldcount * sizeof(chunk_t), + m_MemChunkCount * sizeof(chunk_t) ); + AssertOOM( m_Memory, m_MemChunkCount * sizeof(chunk_t) ); + memset( (char*)m_Memory + oldcount * sizeof(chunk_t), + 0, + (m_MemChunkCount - oldcount) * sizeof(chunk_t) ); + } + + chunk = &m_Memory[ chunkIdx ]; + + if ( chunk->count == 0 ) + { + Assert( chunk->ptr == NULL ); + + chunk->count = ( requiredChunks + ( MEM_CACHE_CHUNKS_ALIGN - 1 ) ) & ~( MEM_CACHE_CHUNKS_ALIGN - 1 ); + chunk->ptr = (char*)sqdbg_malloc( chunk->count * MEM_CACHE_CHUNKSIZE ); + AssertOOM( chunk->ptr, chunk->count * MEM_CACHE_CHUNKSIZE ); + memset( chunk->ptr, 0, chunk->count * MEM_CACHE_CHUNKSIZE ); + } + + Assert( chunkIdx < 0x00007fff ); + } + } + + void DeleteMessage( message_t *pMsg ) + { + if ( pMsg->len == 0 ) + return; + + Assert( pMsg->len > 0 ); + Assert( m_ElemCount > 0 ); + m_ElemCount--; + + int msgIdx = ( ( sizeof(message_t) + pMsg->len + + ( MEM_CACHE_CHUNKSIZE - 1 ) ) & ~( MEM_CACHE_CHUNKSIZE - 1 ) ) / MEM_CACHE_CHUNKSIZE; + + do + { + pMsg->next = pMsg->prev = INVALID_INDEX; + pMsg->len = 0; + pMsg->ptr[0] = 0; + + pMsg = (message_t*)( (char*)pMsg + MEM_CACHE_CHUNKSIZE ); + } + while ( --msgIdx > 0 ); + } + +public: + CMessagePool() : + m_Memory( NULL ), + m_MemChunkCount( 4 ), + m_ElemCount( 0 ), + m_Head( INVALID_INDEX ), + m_Tail( INVALID_INDEX ) + { + } + + ~CMessagePool() + { + if ( m_Memory ) + { + for ( int chunkIdx = 0; chunkIdx < m_MemChunkCount; chunkIdx++ ) + { + chunk_t *chunk = &m_Memory[ chunkIdx ]; + + for ( int msgIdx = 0; msgIdx < chunk->count; ) + { + message_t *msg = (message_t*)&chunk->ptr[ msgIdx * MEM_CACHE_CHUNKSIZE ]; + Assert( msg->len == 0 && msg->ptr[0] == 0 ); + msgIdx += ( sizeof(message_t) + msg->len - 1 ) / MEM_CACHE_CHUNKSIZE + 1; + DeleteMessage( msg ); + } + + sqdbg_free( chunk->ptr, chunk->count * MEM_CACHE_CHUNKSIZE ); + } + + sqdbg_free( m_Memory, m_MemChunkCount * sizeof(chunk_t) ); + } + + Assert( m_ElemCount == 0 ); + } + + void Shrink() + { + Assert( m_ElemCount == 0 ); + + if ( !m_Memory ) + return; + + for ( int chunkIdx = 1; chunkIdx < m_MemChunkCount; chunkIdx++ ) + { + chunk_t *chunk = &m_Memory[ chunkIdx ]; + + if ( chunk->count ) + { +#ifdef _DEBUG + for ( int msgIdx = 0; msgIdx < chunk->count; ) + { + message_t *msg = (message_t*)&chunk->ptr[ msgIdx * MEM_CACHE_CHUNKSIZE ]; + Assert( msg->len == 0 && msg->ptr[0] == 0 ); + msgIdx += ( sizeof(message_t) + msg->len - 1 ) / MEM_CACHE_CHUNKSIZE + 1; + } +#endif + sqdbg_free( chunk->ptr, chunk->count * MEM_CACHE_CHUNKSIZE ); + + chunk->count = 0; + chunk->ptr = NULL; + } + } + + if ( m_MemChunkCount > 4 ) + { + int oldcount = m_MemChunkCount; + m_MemChunkCount = 4; + m_Memory = (chunk_t*)sqdbg_realloc( m_Memory, + oldcount * sizeof(chunk_t), + m_MemChunkCount * sizeof(chunk_t) ); + AssertOOM( m_Memory, m_MemChunkCount * sizeof(chunk_t) ); + } + } + + void Add( char *pcsMsg, int nLength ) + { + index_t newMsg = NewMessage( pcsMsg, nLength ); + + m_ElemCount++; + + // Add to tail + if ( m_Tail == INVALID_INDEX ) + { + Assert( m_Head == INVALID_INDEX ); + m_Head = m_Tail = newMsg; + } + else + { + Get(newMsg)->prev = m_Tail; + Get(m_Tail)->next = newMsg; + m_Tail = newMsg; + } + } + + template < typename T, void (T::*callback)( char *ptr, int len ) > + void Service( T *ctx ) + { + TRACK_ENTRIES(); + + index_t msg = m_Head; + + while ( msg != INVALID_INDEX ) + { + message_t *pMsg = Get(msg); + + Assert( pMsg->len || ( pMsg->next == INVALID_INDEX && pMsg->prev == INVALID_INDEX ) ); + + if ( pMsg->len == 0 ) + break; + + // Advance before execution + index_t next = pMsg->next; + index_t prev = pMsg->prev; + + pMsg->next = INVALID_INDEX; + pMsg->prev = INVALID_INDEX; + + if ( prev != INVALID_INDEX ) + Get(prev)->next = next; + + if ( next != INVALID_INDEX ) + Get(next)->prev = prev; + + if ( msg == m_Head ) + { + // prev could be non-null on re-entry + //Assert( prev == INVALID_INDEX ); + m_Head = next; + } + + if ( msg == m_Tail ) + { + Assert( next == INVALID_INDEX && prev == INVALID_INDEX ); + m_Tail = INVALID_INDEX; + } + + (ctx->*callback)( pMsg->ptr, pMsg->len ); + + Assert( Get(msg) == pMsg ); + + DeleteMessage( pMsg ); + msg = next; + } + } + + void Clear() + { + index_t msg = m_Head; + + while ( msg != INVALID_INDEX ) + { + message_t *pMsg = Get(msg); + + index_t next = pMsg->next; + index_t prev = pMsg->prev; + + if ( prev != INVALID_INDEX ) + Get(prev)->next = next; + + if ( next != INVALID_INDEX ) + Get(next)->prev = prev; + + if ( msg == m_Head ) + { + Assert( prev == INVALID_INDEX ); + m_Head = next; + } + + if ( msg == m_Tail ) + { + Assert( next == INVALID_INDEX && prev == INVALID_INDEX ); + m_Tail = INVALID_INDEX; + } + + DeleteMessage( pMsg ); + msg = next; + } + + Assert( m_Head == INVALID_INDEX && m_Tail == INVALID_INDEX ); + } +}; + +static inline bool SocketWouldBlock() +{ +#ifdef _WIN32 + return WSAGetLastError() == WSAEWOULDBLOCK || WSAGetLastError() == WSAEINPROGRESS; +#else + return errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS; +#endif +} + +static inline void CloseSocket( SOCKET *sock ) +{ + if ( *sock != INVALID_SOCKET ) + { + shutdown( *sock, SD_BOTH ); + closesocket( *sock ); + *sock = INVALID_SOCKET; + } +} + + +class CServerSocket +{ +private: + SOCKET m_Socket; + SOCKET m_ServerSocket; + + CMessagePool m_MessagePool; + + char *m_pRecvBufPtr; + char m_pRecvBuf[ SQDBG_NET_BUF_SIZE ]; + + bool m_bWSAInit; + +public: + const char *m_pszLastMsgFmt; + const char *m_pszLastMsg; + +public: + bool IsListening() + { + return m_ServerSocket != INVALID_SOCKET; + } + + bool IsClientConnected() + { + return m_Socket != INVALID_SOCKET; + } + + unsigned short GetServerPort() + { + if ( m_ServerSocket != INVALID_SOCKET ) + { + sockaddr_in addr; + socklen_t len = sizeof(addr); + + if ( getsockname( m_ServerSocket, (sockaddr*)&addr, &len ) != SOCKET_ERROR ) + return ntohs( addr.sin_port ); + } + + return 0; + } + + bool ListenSocket( unsigned short port ) + { + if ( m_ServerSocket != INVALID_SOCKET ) + return true; + +#ifdef _WIN32 + if ( !m_bWSAInit ) + { + WSADATA wsadata; + if ( WSAStartup( MAKEWORD(2,2), &wsadata ) != 0 ) + { + int err = errno; + m_pszLastMsgFmt = "(sqdbg) WSA startup failed"; + m_pszLastMsg = strerr(err); + return false; + } + m_bWSAInit = true; + } +#endif + + m_ServerSocket = socket( AF_INET, SOCK_STREAM, 0 ); + + if ( m_ServerSocket == INVALID_SOCKET ) + { + int err = errno; + Shutdown(); + m_pszLastMsgFmt = "(sqdbg) Failed to open socket"; + m_pszLastMsg = strerr(err); + return false; + } + + u_long iMode = 1; +#ifdef _WIN32 + if ( ioctlsocket( m_ServerSocket, FIONBIO, &iMode ) == SOCKET_ERROR ) +#else + int f = fcntl( m_ServerSocket, F_GETFL ); + if ( f == -1 || fcntl( m_ServerSocket, F_SETFL, f | O_NONBLOCK ) == -1 ) +#endif + { + int err = errno; + Shutdown(); + m_pszLastMsgFmt = "(sqdbg) Failed to set socket non-blocking"; + m_pszLastMsg = strerr(err); + return false; + } + + iMode = 1; + + if ( setsockopt( m_ServerSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&iMode, sizeof(iMode) ) == SOCKET_ERROR ) + { + int err = errno; + Shutdown(); + m_pszLastMsgFmt = "(sqdbg) Failed to set TCP nodelay"; + m_pszLastMsg = strerr(err); + return false; + } + + linger ln; + ln.l_onoff = 0; + ln.l_linger = 0; + + if ( setsockopt( m_ServerSocket, SOL_SOCKET, SO_LINGER, (char*)&ln, sizeof(ln) ) == SOCKET_ERROR ) + { + int err = errno; + Shutdown(); + m_pszLastMsgFmt = "(sqdbg) Failed to set don't linger"; + m_pszLastMsg = strerr(err); + return false; + } + + sockaddr_in addr; + memset( &addr, 0, sizeof(addr) ); + addr.sin_family = AF_INET; + addr.sin_port = htons( port ); + addr.sin_addr.s_addr = htonl( INADDR_ANY ); + + if ( bind( m_ServerSocket, (sockaddr*)&addr, sizeof(addr) ) == SOCKET_ERROR ) + { + int err = errno; + Shutdown(); + m_pszLastMsgFmt = "(sqdbg) Failed to bind socket on port"; + m_pszLastMsg = strerr(err); + return false; + } + + if ( listen( m_ServerSocket, 0 ) == SOCKET_ERROR ) + { + int err = errno; + Shutdown(); + m_pszLastMsgFmt = "(sqdbg) Failed to listen to socket"; + m_pszLastMsg = strerr(err); + return false; + } + + return true; + } + + bool Listen() + { + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + fd_set rfds; + FD_ZERO( &rfds ); + FD_SET( m_ServerSocket, &rfds ); + + select( 0, &rfds, NULL, NULL, &tv ); + + if ( !FD_ISSET( m_ServerSocket, &rfds ) ) + return false; + + FD_CLR( m_ServerSocket, &rfds ); + + sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + + m_Socket = accept( m_ServerSocket, (sockaddr*)&addr, &addrlen ); + + if ( m_Socket == INVALID_SOCKET ) + return false; + +#ifndef _WIN32 + int f = fcntl( m_Socket, F_GETFL ); + if ( f == -1 || fcntl( m_Socket, F_SETFL, f | O_NONBLOCK ) == -1 ) + { + int err = errno; + DisconnectClient(); + m_pszLastMsgFmt = "(sqdbg) Failed to set socket non-blocking"; + m_pszLastMsg = strerr(err); + return false; + } +#endif + + m_pszLastMsg = inet_ntoa( addr.sin_addr ); + return true; + } + + void Shutdown() + { + CloseSocket( &m_Socket ); + CloseSocket( &m_ServerSocket ); + +#ifdef _WIN32 + if ( m_bWSAInit ) + { + WSACleanup(); + m_bWSAInit = false; + } +#endif + + m_MessagePool.Clear(); + m_pRecvBufPtr = m_pRecvBuf; + memset( m_pRecvBuf, -1, sizeof( m_pRecvBuf ) ); + } + + void DisconnectClient() + { + CloseSocket( &m_Socket ); + + m_MessagePool.Clear(); + m_pRecvBufPtr = m_pRecvBuf; + memset( m_pRecvBuf, -1, sizeof( m_pRecvBuf ) ); + } + + bool Send( const char *buf, int len ) + { + for (;;) + { + int bytesSend = send( m_Socket, buf, len, 0 ); + + if ( bytesSend == SOCKET_ERROR ) + { + // Keep blocking + if ( SocketWouldBlock() ) + continue; + + int err = errno; + DisconnectClient(); + m_pszLastMsgFmt = "(sqdbg) Network error"; + m_pszLastMsg = strerr(err); + return false; + } + + if ( len == bytesSend ) + return true; + + len -= bytesSend; + } + } + + bool Recv() + { + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + fd_set rfds; + FD_ZERO( &rfds ); + FD_SET( m_Socket, &rfds ); + + select( 0, &rfds, NULL, NULL, &tv ); + + if ( !FD_ISSET( m_Socket, &rfds ) ) + return true; + + FD_CLR( m_Socket, &rfds ); + + u_long readlen = 0; + ioctlsocket( m_Socket, FIONREAD, &readlen ); + + int bufsize = m_pRecvBuf + sizeof(m_pRecvBuf) - m_pRecvBufPtr; + + if ( bufsize <= 0 || (unsigned int)bufsize < readlen ) + { + DisconnectClient(); + m_pszLastMsgFmt = "(sqdbg) Net message buffer is full"; + m_pszLastMsg = NULL; + return false; + } + + for (;;) + { + int bytesRecv = recv( m_Socket, m_pRecvBufPtr, bufsize, 0 ); + + if ( bytesRecv == SOCKET_ERROR ) + { + if ( SocketWouldBlock() ) + break; + + int err = errno; + DisconnectClient(); + m_pszLastMsgFmt = "(sqdbg) Network error"; + m_pszLastMsg = strerr(err); + return false; + } + + if ( !bytesRecv ) + { +#ifdef _WIN32 + WSASetLastError( WSAECONNRESET ); +#else + errno = ECONNRESET; +#endif + int err = errno; + DisconnectClient(); + m_pszLastMsgFmt = "(sqdbg) Client disconnected"; + m_pszLastMsg = strerr(err); + return false; + } + + m_pRecvBufPtr += bytesRecv; + bufsize -= bytesRecv; + } + + return true; + } + + // + // Header reader sets message pointer to the content start + // + template < bool (readHeader)( char **ppMsg, int *pLength ) > + bool Parse() + { + // Nothing to parse + if ( m_pRecvBufPtr == m_pRecvBuf ) + return true; + + char *pMsg = m_pRecvBuf; + int nLength = sizeof(m_pRecvBuf); + + while ( readHeader( &pMsg, &nLength ) ) + { + char *pMsgEnd = pMsg + (unsigned int)nLength; + + if ( pMsgEnd >= m_pRecvBuf + sizeof(m_pRecvBuf) ) + { + DisconnectClient(); + m_pszLastMsgFmt = "(sqdbg) Client disconnected"; + + if ( nLength == -1 ) + { + m_pszLastMsg = "malformed message"; + } + else + { + m_pszLastMsg = "content is too large"; + } + + return false; + } + + // Entire message wasn't received, wait for it + if ( m_pRecvBufPtr < pMsgEnd ) + break; + + m_MessagePool.Add( pMsg, nLength ); + + // Last message + if ( m_pRecvBufPtr == pMsgEnd ) + { + memset( m_pRecvBuf, 0, m_pRecvBufPtr - m_pRecvBuf ); + m_pRecvBufPtr = m_pRecvBuf; + break; + } + + // Next message + int shift = m_pRecvBufPtr - pMsgEnd; + memmove( m_pRecvBuf, pMsgEnd, shift ); + memset( m_pRecvBuf + shift, 0, m_pRecvBufPtr - ( m_pRecvBuf + shift ) ); + m_pRecvBufPtr = m_pRecvBuf + shift; + pMsg = m_pRecvBuf; + nLength = sizeof(m_pRecvBuf); + } + + return true; + } + + template < typename T, void (T::*callback)( char *ptr, int len ) > + void Execute( T *ctx ) + { + m_MessagePool.Service< T, callback >( ctx ); + + if ( m_Socket == INVALID_SOCKET && m_MessagePool.m_ElemCount == 0 ) + { + m_MessagePool.Shrink(); + } + } + +public: + CServerSocket() : + m_Socket( INVALID_SOCKET ), + m_ServerSocket( INVALID_SOCKET ), + m_pRecvBufPtr( m_pRecvBuf ), + m_bWSAInit( false ) + { + Assert( sizeof(m_pRecvBuf) <= ( 1 << ( sizeof(CMessagePool::message_t::len) * 8 ) ) ); + } +}; + +#endif // SQDBG_NET_H diff --git a/sp/src/vscript/sqdbg/sqdbg/protocol.h b/sp/src/vscript/sqdbg/sqdbg/protocol.h new file mode 100644 index 00000000..9d91ef2f --- /dev/null +++ b/sp/src/vscript/sqdbg/sqdbg/protocol.h @@ -0,0 +1,300 @@ +//----------------------------------------------------------------------- +// github.com/samisalreadytaken/sqdbg +//----------------------------------------------------------------------- +// + +#ifndef SQDBG_DAP_H +#define SQDBG_DAP_H + +#define DAP_HEADER_CONTENTLENGTH "Content-Length" +#define DAP_HEADER_END "\r\n\r\n" +#define DAP_HEADER_MAXSIZE ( STRLEN(DAP_HEADER_CONTENTLENGTH ": ") + STRLEN(DAP_HEADER_END) + FMT_UINT32_LEN ) + +inline void DAP_Serialise( CBuffer *buffer ) +{ + Assert( buffer->size() > 0 && buffer->size() < INT_MAX ); + + char *mem = buffer->base(); + int contentSize = buffer->size() - DAP_HEADER_MAXSIZE; + int digits = countdigits( contentSize ); + int padding = FMT_UINT32_LEN - digits; + + int nearest = 10; + while ( contentSize >= nearest ) + nearest *= 10; + + contentSize += padding; + + if ( contentSize >= nearest ) + { + // Padding between header and content increased content size digits, + // add padding in the end to match + padding--; + digits++; + buffer->_base.Ensure( buffer->size() + 1 ); + mem[buffer->_size++] = ' '; + } + + memcpy( mem, DAP_HEADER_CONTENTLENGTH ": ", STRLEN(DAP_HEADER_CONTENTLENGTH ": ") ); + + int idx = STRLEN(DAP_HEADER_CONTENTLENGTH ": ") + digits; + + for ( int i = idx - 1; contentSize; ) + { + char c = contentSize % 10; + contentSize /= 10; + mem[i--] = '0' + c; + } + + memcpy( mem + idx, DAP_HEADER_END, STRLEN(DAP_HEADER_END) ); + idx += STRLEN(DAP_HEADER_END); + memset( mem + idx, ' ', padding ); +} + +inline void DAP_Free( CBuffer *buffer ) +{ + buffer->_size = 0; +} + +static inline void ParseFieldName( const char *pMemEnd, char *pStart, int *len ) +{ + char *c = pStart; + + for (;;) + { + if ( IN_RANGE_CHAR( ((unsigned char*)c)[0], 0x20, 0x7E ) ) + { + if ( c + 1 >= pMemEnd ) + { + *len = -1; + return; + } + + if ( c[0] == ':' ) + { + if ( c[1] == ' ' ) + { + *len = c - pStart; + return; + } + + *len = 0; + return; + } + + c++; + } + else + { + *len = 0; + return; + } + } +} + +static inline void ParseFieldValue( const char *pMemEnd, char *pStart, int *len ) +{ + char *c = pStart; + + for (;;) + { + if ( c + 1 >= pMemEnd ) + { + *len = -1; + return; + } + + if ( c[0] == '\n' ) + { + *len = 0; + return; + } + + if ( c[0] == '\r' && c[1] == '\n' ) + { + *len = c - pStart; + return; + } + + c++; + } +} + +inline bool DAP_ReadHeader( char **ppMsg, int *pLength ) +{ + char *pMsg = *ppMsg; + const char *pMemEnd = pMsg + *pLength; + int nContentLength = 0; + + for (;;) + { + int len; + ParseFieldName( pMemEnd, pMsg, &len ); + + if ( len == 0 ) + goto invalid; + + if ( len == -1 ) + return false; + + if ( len == (int)STRLEN(DAP_HEADER_CONTENTLENGTH) && + !memcmp( pMsg, DAP_HEADER_CONTENTLENGTH, STRLEN(DAP_HEADER_CONTENTLENGTH) ) ) + { + // Duplicate length field + if ( nContentLength ) + goto ignore; + + pMsg += len + 2; + + for ( char *pStart = pMsg;; ) + { + if ( pMsg >= pMemEnd ) + return false; + + if ( IN_RANGE_CHAR( *(unsigned char*)pMsg, '0', '9' ) ) + { + nContentLength = nContentLength * 10 + *pMsg - '0'; + pMsg++; + + if ( pMsg - pStart > (int)FMT_UINT32_LEN ) + goto invalid; + } + // Strict - no whitespace allowed + else + { + if ( pMsg + 1 >= pMemEnd ) + return false; + + if ( pMsg[0] == '\r' && pMsg[1] == '\n' ) + { + if ( nContentLength <= 0 ) + goto invalid; + + *pLength = nContentLength; + pMsg += 2; + break; + } + else + { + goto invalid; + } + } + } + } + // Ignore unknown header fields + else + { +ignore: + pMsg += len + 2; + + ParseFieldValue( pMemEnd, pMsg, &len ); + + if ( len == 0 ) + goto invalid; + + if ( len == -1 ) + return false; + + pMsg += len + 2; + } + + if ( pMsg + 1 >= pMemEnd ) + return false; + + if ( pMsg[0] == '\r' && pMsg[1] == '\n' ) + { + *ppMsg = pMsg + 2; + return true; + } + } + +invalid: + // Signal that the client needs to be dropped + *pLength = -1; + *ppMsg = pMsg; + return true; +} + +#ifdef SQDBG_VALIDATE_SENT_MSG +inline void DAP_Test( CScratch< JSON_SCRATCH_CHUNK_SIZE > *scratch, CBuffer *buffer ) +{ + char *pMsg = buffer->base(); + int nLength = buffer->size(); + + bool res = DAP_ReadHeader( &pMsg, &nLength ); + Assert( res && nLength < buffer->size() ); + + if ( res ) + { + json_table_t table; + JSONParser parser( scratch, pMsg, nLength, &table ); + + AssertMsg1( !parser.GetError(), "%s", parser.GetError() ); + } +} +#else +#define DAP_Test(...) (void)0 +#endif + +#define _DAP_INIT_BUF( _buf ) \ + CBufTmpCache _bufcache( (_buf) ); \ + (_buf)->_size = DAP_HEADER_MAXSIZE; \ + (void)0 + +#define DAP_START_REQUEST( _seq, _cmd ) \ +{ \ + _DAP_INIT_BUF( &m_SendBuf ); \ + { \ + wjson_table_t packet( m_SendBuf ); \ + packet.SetInt( "seq", _seq ); \ + packet.SetString( "type", "request" ); \ + packet.SetString( "command", _cmd ); + +#define _DAP_START_RESPONSE( _seq, _cmd, _suc ) \ +if ( IsClientConnected() ) \ +{ \ + _DAP_INIT_BUF( &m_SendBuf ); \ + { \ + wjson_table_t packet( m_SendBuf ); \ + packet.SetInt( "request_seq", _seq ); \ + packet.SetString( "type", "response" ); \ + packet.SetString( "command", _cmd ); \ + packet.SetBool( "success", _suc ); + +#define DAP_START_RESPONSE( _seq, _cmd ) \ + _DAP_START_RESPONSE( _seq, _cmd, true ); + +#define DAP_ERROR_RESPONSE( _seq, _cmd ) \ + _DAP_START_RESPONSE( _seq, _cmd, false ); + +#define DAP_ERROR_BODY( _id, _fmt ) \ + wjson_table_t body = packet.SetTable( "body" ); \ + wjson_table_t error = body.SetTable( "error" ); \ + error.SetInt( "id", _id ); \ + error.SetString( "format", _fmt ); \ + +#define DAP_START_EVENT( _seq, _ev ) \ +{ \ + _DAP_INIT_BUF( &m_SendBuf ); \ + { \ + wjson_table_t packet( m_SendBuf ); \ + packet.SetInt( "seq", _seq ); \ + packet.SetString( "type", "event" ); \ + packet.SetString( "event", _ev ); + +#define DAP_SET( _key, _val ) \ + packet.Set( _key, _val ); + +#define DAP_SET_TABLE( _val ) \ + wjson_table_t _val = packet.SetTable( #_val ); + +#define DAP_SEND() \ + } \ +\ + DAP_Serialise( &m_SendBuf ); \ + Send( m_SendBuf.base(), m_SendBuf.size() ); \ + DAP_Test( &m_ReadBuf, &m_SendBuf ); \ + DAP_Free( &m_SendBuf ); \ +} + +#endif // SQDBG_DAP_H diff --git a/sp/src/vscript/sqdbg/sqdbg/server.cpp b/sp/src/vscript/sqdbg/sqdbg/server.cpp new file mode 100644 index 00000000..75009911 --- /dev/null +++ b/sp/src/vscript/sqdbg/sqdbg/server.cpp @@ -0,0 +1,19094 @@ +//----------------------------------------------------------------------- +// github.com/samisalreadytaken/sqdbg +//----------------------------------------------------------------------- +// +// Squirrel Debugger +// + +#define SQDBG_SV_VER 3 + +#include "sqdbg.h" + +#ifndef _WIN32 +#include // isfinite +#include // INT_MIN +#endif +#include +#include +#include +#include +#include +#include +#include +#ifndef SQDBG_DISABLE_PROFILER +#include +#endif + +#define ___CAT(a, b) a##b +#define __CAT(a, b) ___CAT(a,b) + +#define STR_NOMEM "**OUT OF MEMORY**" +#define AssertOOM( p, size ) AssertMsg1( (p), "**OUT OF MEMORY** (%u)", (unsigned int)(size) ) + +#include "debug.h" +#include "vec.h" +#include "net.h" + +// For Squirrel headers +#ifndef assert +#define assert Assert +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef _WIN32 +void sqdbg_sleep( int ms ) +{ + ::Sleep( (DWORD)ms ); +} +#else +#include +void sqdbg_sleep( int ms ) +{ + timespec t; + t.tv_nsec = ms * 1000000; + t.tv_sec = 0; + nanosleep( &t, NULL ); +} +#endif + +#if defined(SQDBG_DEBUGGER_ECHO_OUTPUT) && defined(_WIN32) + #ifdef SQUNICODE + #define _OutputDebugString(s) OutputDebugStringW(s) + #else + #define _OutputDebugString(s) OutputDebugStringA(s) + #endif + #define _OutputDebugStringA(s) OutputDebugStringA(s) + + void _OutputDebugStringFmt( const SQChar *fmt, ... ) + { + SQChar buf[256]; + va_list va; + va_start( va, fmt ); + #ifdef SQUNICODE + int len = vswprintf( buf, sizeof(buf)/sizeof(SQChar), fmt, va ); + #else + int len = vsnprintf( buf, sizeof(buf)/sizeof(SQChar), fmt, va ); + #endif + va_end( va ); + + if ( len < 0 || len > (int)(sizeof(buf)/sizeof(SQChar))-1 ) + len = (int)(sizeof(buf)/sizeof(SQChar))-1; + + _OutputDebugString( buf ); + } +#else + #define _OutputDebugString(s) (void)0 + #define _OutputDebugStringA(s) (void)0 + #define _OutputDebugStringFmt(...) (void)0 +#endif + +#define _ArraySize(p) (sizeof((p))/sizeof(*(p))) + +#define memzero(p) memset( (char*)(p), 0, sizeof(*(p)) ) + +#define ALIGN(v, a) (((v) + ((a)-1)) & ~((a)-1)) + +#ifndef _WIN32 +#undef offsetof +#define offsetof(a,b) ((size_t)(&(((a*)0)->b))) +#endif + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#undef _SC +#ifdef SQUNICODE + #define _SC(s) __CAT( L, s ) +#else + #define _SC(s) s +#endif + +#if defined(SQUNICODE) && !defined(WCHAR_SIZE) +#ifdef _WIN32 + #define WCHAR_SIZE 2 +#else + #define WCHAR_SIZE 4 +#endif +#endif + +#ifndef scstrlen +#ifdef SQUNICODE + #define scstrlen wcslen +#else + #define scstrlen strlen +#endif +#endif + +#ifndef scstrchr +#ifdef SQUNICODE + #define scstrchr wcschr +#else + #define scstrchr strchr +#endif +#endif + +#ifndef scstricmp +#ifdef SQUNICODE + #ifdef _WIN32 + #define scstricmp _wcsicmp + #else + #define scstricmp wcscmp + #endif +#else + #ifdef _WIN32 + #define scstricmp _stricmp + #else + #define scstricmp strcasecmp + #endif +#endif +#endif + +#ifndef scsprintf +#ifdef SQUNICODE + #define scsprintf swprintf +#else + #define scsprintf snprintf +#endif +#endif + +#ifndef scvsprintf +#ifdef SQUNICODE + #define scvsprintf vswprintf +#else + #define scvsprintf vsnprintf +#endif +#endif + +#ifndef sq_rsl +#define sq_rsl(l) ((l)*sizeof(SQChar)) +#endif + +#define SQStringFromSQChar(_pch) \ + ( (SQString*)( (char*)(_pch) - (char*)offsetof( SQString, _val ) ) ) + +#ifndef SQUIRREL_VERSION_NUMBER +#error "SQUIRREL_VERSION_NUMBER is undefined" +#endif + +#if SQUIRREL_VERSION_NUMBER >= 300 + #define _fp(func) (func) + #define _outervalptr(outervar) (_outer((outervar))->_valptr) + #define _nativenoutervalues(p) (p)->_noutervalues + #define NATIVE_DEBUG_HOOK + + #ifdef NATIVE_DEBUG_HOOK + #define SUPPORTS_RESTART_FRAME + #define DEBUG_HOOK_CACHED_SQDBG + #endif + + #ifdef NATIVE_DEBUG_HOOK + typedef SQDEBUGHOOK _SQDEBUGHOOK; + #else + typedef SQFUNCTION _SQDEBUGHOOK; + #endif + + #define CLOSURE_ENV_ISVALID(env) (env) + #define CLOSURE_ENV_OBJ(env) ((env)->_obj) + + #if SQUIRREL_VERSION_NUMBER >= 310 + #define CLOSURE_ROOT + #endif +#else + #define _fp(func) _funcproto(func) + #define _outervalptr(outervar) (&(outervar)) + #define _nativenoutervalues(p) (p)->_outervalues.size() + typedef SQFUNCTION _SQDEBUGHOOK; + + #if SQUIRREL_VERSION_NUMBER >= 212 + #define CLOSURE_ENV_ISVALID(env) (sq_type(env) == OT_WEAKREF && _weakref(env)) + #define CLOSURE_ENV_OBJ(env) (_weakref(env)->_obj) + #endif + + #if SQUIRREL_VERSION_NUMBER < 225 + #undef _rawval + #if defined(_SQ64) + #define _rawval(o) ((uint64_t)((o)._unVal.nInteger)) + #elif defined(SQUSEDOUBLE) + #define _rawval(o) (*(uint64_t*)&((o)._unVal.fFloat)) + #else + #define _rawval(o) ((uintptr_t)((o)._unVal.pRefCounted)) + #endif + + #if SQUIRREL_VERSION_NUMBER < 223 + #if defined(_SQ64) || defined(SQUSEDOUBLE) + typedef uint64_t SQRawObjectVal; + #else + typedef uintptr_t SQRawObjectVal; + #endif + #endif + #endif + + #undef type + #undef is_delegable + #define is_delegable(t) (sq_type(t) & SQOBJECT_DELEGABLE) + + #ifndef SQUNICODE + #undef scvsprintf + #define scvsprintf vsnprintf + #endif + + #undef scsprintf + #ifdef SQUNICODE + #define scsprintf swprintf + #else + #define scsprintf snprintf + #endif +#endif + +#include "str.h" +#include "json.h" +#include "protocol.h" + +#define SQ_FOREACH_OP( obj, key, val ) \ + { \ + int _jump; \ + for ( SQObjectPtr _pos, key, val; \ + m_pCurVM->FOREACH_OP( obj, key, val, _pos, 0, 666, _jump ) && \ + _jump != 666; ) \ + { + +#define SQ_FOREACH_END() } } + +#define FOREACH_SQTABLE( pTable, key, val )\ + SQInteger _i = 0;\ + for ( SQObjectPtr pi = _i; (_i = pTable->Next( false, pi, key, val )) != -1; pi._unVal.nInteger = _i ) + +#ifndef SQDBG_EXCLUDE_DEFAULT_MEMFUNCTIONS +inline void *sqdbg_malloc( unsigned int size ) +{ + extern void *sq_vm_malloc( SQUnsignedInteger size ); + return sq_vm_malloc( size ); +} + +inline void *sqdbg_realloc( void *p, unsigned int oldsize, unsigned int size ) +{ + extern void *sq_vm_realloc( void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size ); + return sq_vm_realloc( p, oldsize, size ); +} + +inline void sqdbg_free( void *p, unsigned int size ) +{ + extern void sq_vm_free( void *p, SQUnsignedInteger size ); + sq_vm_free( p, size ); +} +#endif + +template < typename T > +void CopyString( CScratch<> *allocator, const T &src, T *dst ) +{ + Assert( src.ptr ); + + if ( src.len ) + { + if ( src.len != dst->len ) + { + if ( dst->len ) + allocator->Free( dst->ptr ); + + void *mem = allocator->Alloc( ( src.len + 1 ) * sizeof(*dst->ptr), NULL, false ); + + if ( !mem ) + { + if ( dst->ptr && dst->len ) + { + if ( sizeof(*dst->ptr) == 1 ) + { + memcpy( dst->ptr, STR_NOMEM, min( dst->len, STRLEN(STR_NOMEM) ) * sizeof(*dst->ptr) ); + } + else + { + memcpy( dst->ptr, _SC(STR_NOMEM), min( dst->len, STRLEN(STR_NOMEM) ) * sizeof(*dst->ptr) ); + } + } + + return; + } + + *(void**)&dst->ptr = mem; + dst->len = src.len; + dst->ptr[dst->len] = 0; + } + else + { + Assert( dst->ptr ); + Assert( dst->ptr[dst->len] == 0 ); + } + + memcpy( dst->ptr, src.ptr, dst->len * sizeof(*dst->ptr) ); + } + else + { + if ( dst->len ) + { + allocator->Free( dst->ptr ); + dst->ptr = NULL; + dst->len = 0; + } + else + { + Assert( !dst->ptr ); + } + } +} + +#ifdef SQUNICODE +void CopyString( CScratch<> *allocator, const string_t &src, sqstring_t *dst ) +{ + Assert( src.ptr ); + + unsigned int srclen = SQUnicodeLength( src.ptr, src.len ); + + if ( srclen ) + { + if ( srclen != dst->len ) + { + if ( dst->len ) + allocator->Free( dst->ptr ); + + void *mem = allocator->Alloc( ( srclen + 1 ) * sizeof(*dst->ptr), NULL, false ); + + if ( !mem ) + { + if ( dst->ptr && dst->len ) + memcpy( dst->ptr, _SC(STR_NOMEM), min( dst->len, STRLEN(STR_NOMEM) ) * sizeof(*dst->ptr) ); + + return; + } + + *(void**)&dst->ptr = mem; + dst->len = srclen; + dst->ptr[dst->len] = 0; + } + else + { + Assert( dst->ptr ); + Assert( dst->ptr[dst->len] == 0 ); + } + + UTF8ToSQUnicode( dst->ptr, dst->len * sizeof(*dst->ptr), src.ptr, src.len ); + } + else + { + if ( dst->len ) + { + allocator->Free( dst->ptr ); + dst->ptr = NULL; + dst->len = 0; + } + else + { + Assert( !dst->ptr ); + } + } +} + +void CopyString( CScratch<> *allocator, const sqstring_t &src, string_t *dst ) +{ + Assert( src.ptr ); + + unsigned int srclen = UTF8Length( src.ptr, src.len ); + + if ( srclen ) + { + if ( srclen != dst->len ) + { + if ( dst->len ) + allocator->Free( dst->ptr ); + + void *mem = allocator->Alloc( ( srclen + 1 ) * sizeof(*dst->ptr), NULL, false ); + + if ( !mem ) + { + if ( dst->ptr && dst->len ) + memcpy( dst->ptr, STR_NOMEM, min( dst->len, STRLEN(STR_NOMEM) ) * sizeof(*dst->ptr) ); + + return; + } + + *(void**)&dst->ptr = mem; + dst->len = srclen; + dst->ptr[dst->len] = 0; + } + else + { + Assert( dst->ptr ); + Assert( dst->ptr[dst->len] == 0 ); + } + + SQUnicodeToUTF8( dst->ptr, dst->len * sizeof(*dst->ptr), src.ptr, src.len ); + } + else + { + if ( dst->len ) + { + allocator->Free( dst->ptr ); + dst->ptr = NULL; + dst->len = 0; + } + else + { + Assert( !dst->ptr ); + } + } +} +#endif + +template < typename T > +void FreeString( CScratch<> *allocator, T *dst ) +{ + if ( dst->len ) + { + allocator->Free( dst->ptr ); + dst->ptr = NULL; + dst->len = 0; + } +} + +inline SQString *CreateSQString( HSQUIRRELVM vm, const sqstring_t &str ) +{ + return SQString::Create( vm->_sharedstate, str.ptr, str.len ); +} + +inline SQString *CreateSQString( SQSharedState *ss, const sqstring_t &str ) +{ + return SQString::Create( ss, str.ptr, str.len ); +} + +inline bool SQTable_Get( SQTable *table, const sqstring_t &key, SQObjectPtr &val ) +{ +#if SQUIRREL_VERSION_NUMBER >= 300 + #ifdef SQUNICODE + Assert( key.ptr[key.len] == 0 ); + return table->GetStr( key.ptr, key.len, val ); + #else + // SQTable::GetStr ignores string length with strcmp, need a terminated string here. + // If the string is not already terminated, then it's a writable, non-const string + // and its source is the network buffer + char z = key.ptr[key.len]; + + if ( z != 0 ) + key.ptr[key.len] = 0; + + bool r = table->GetStr( key.ptr, key.len, val ); + + if ( z != 0 ) + key.ptr[key.len] = z; + + return r; + #endif +#else + SQObjectPtr str = SQString::Create( table->_sharedstate, key.ptr, key.len ); + return table->Get( str, val ); +#endif +} + +// uses the scratch pad for unicode conversion temporary buffer +inline SQString *CreateSQString( SQDebugServer *dbg, const string_t &str ); +inline bool SQTable_Get( SQDebugServer *dbg, SQTable *table, const string_t &key, SQObjectPtr &val ); + +inline int GetFunctionDeclarationLine( SQFunctionProto *func ) +{ + // not exactly function declaration line, but close enough + Assert( func->_nlineinfos > 0 ); + Assert( func->_lineinfos[0]._line == func->GetLine( func->_instructions ) ); + return func->_lineinfos[0]._line; +} + +inline bool IsFalse( const SQObject &obj ) +{ +#if SQUIRREL_VERSION_NUMBER >= 225 || defined(_SQ64) == defined(SQUSEDOUBLE) + return ( ( sq_type(obj) & SQOBJECT_CANBEFALSE ) && _rawval(obj) == 0 ); +#else +#if defined(_SQ64) + return ( ( sq_type(obj) & SQOBJECT_CANBEFALSE ) && + ( ( _rawval(obj) == 0 ) || ( sq_type(obj) == OT_FLOAT && _float(obj) == 0.0 ) ) ); +#else // SQUSEDOUBLE + return ( ( sq_type(obj) & SQOBJECT_CANBEFALSE ) && + ( ( _rawval(obj) == 0 ) || ( sq_type(obj) == OT_INTEGER && _integer(obj) == 0 ) ) ); +#endif +#endif +} + +inline void SetBool( SQObjectPtr &obj, int state ) +{ + obj.Null(); + obj._type = OT_BOOL; + obj._unVal.nInteger = state; +} + +inline bool IsEqual( const SQObject &o1, const SQObject &o2 ) +{ + return ( _rawval(o1) == _rawval(o2) && sq_type(o1) == sq_type(o2) ); +} + +template < typename C > +inline void StripFileName( C **ptr, unsigned int *len ) +{ + for ( C *c = *ptr + *len - 1; c >= *ptr; c-- ) + { + if ( *c == '/' || *c == '\\' ) + { + c++; + *len = *ptr + *len - c; + *ptr = c; + break; + } + } +} + +#ifdef _DEBUG +class CStackCheck +{ +private: + HSQUIRRELVM vm; + int top; + +public: + CStackCheck( HSQUIRRELVM v ) + { + vm = v; + top = vm->_top; + } + + ~CStackCheck() + { + Assert( vm->_top == top ); + } +}; +#else +class CStackCheck +{ +public: + CStackCheck( HSQUIRRELVM ) {} +}; +#endif + + +#ifndef SQDBG_DISABLE_PROFILER +class CProfiler +{ +public: + typedef double sample_t; + typedef unsigned int hnode_t; + typedef unsigned int hgroup_t; + static const hnode_t INVALID_HANDLE = (hnode_t)-1; + +#pragma pack(push, 4) + struct node_t + { + void *func; + hnode_t caller; + unsigned int calls; + sample_t samples; + sample_t sampleStart; + hnode_t id; + }; +#pragma pack(pop) + + struct nodetag_t + { + SQString *funcsrc; + SQString *funcname; + }; + + struct group_t + { + unsigned int hits; + unsigned int peakHit; + sample_t samples; + sample_t peak; + sample_t sampleStart; + SQString *tag; + }; + + static sample_t Sample() + { + std::chrono::duration< double, std::micro > d = + std::chrono::high_resolution_clock::now().time_since_epoch(); + return d.count(); + } + +private: + enum + { + kProfDisabled = 0, + kProfActive, + kProfPaused, + }; + + int m_State; + int m_nPauseLevel; + vector< node_t > m_Nodes; + vector< hnode_t > m_CallStack; + vector< group_t > m_Groups; + vector< hgroup_t > m_GroupStack; + vector< nodetag_t > m_NodeTags; + +private: + node_t *FindNode( hnode_t caller, void *func, hnode_t *handle ) + { + hnode_t count = m_Nodes.size(); + + // Start searching from caller, + // new nodes are added after them + hnode_t i = caller != INVALID_HANDLE ? caller : 0; + + for ( ; i < count; i++ ) + { + node_t &node = m_Nodes[i]; + if ( node.func == func && node.caller == caller ) + { + Assert( (hnode_t)i == node.id ); + *handle = (hnode_t)i; + return &node; + } + } + + return NULL; + } + + group_t *FindGroup( SQString *tag, hgroup_t *idx ) + { + for ( hgroup_t i = m_Groups.size(); i--; ) + { + group_t &group = m_Groups[i]; + if ( group.tag == tag ) + { + *idx = i; + return &group; + } + } + + return NULL; + } + +public: + bool IsEnabled() + { + return m_State != kProfDisabled; + } + + bool IsActive() + { + return m_State == kProfActive; + } + + void Start( HSQUIRRELVM vm ) + { + Assert( !IsEnabled() ); + + if ( m_State != kProfDisabled ) + return; + + m_State = kProfActive; + + Assert( m_Nodes.capacity() == 0 ); + Assert( m_NodeTags.capacity() == 0 ); + Assert( m_CallStack.capacity() == 0 ); + Assert( m_GroupStack.capacity() == 0 ); + + m_Nodes.reserve( max( vm->_alloccallsstacksize, 256 ) ); + m_NodeTags.reserve( m_Nodes.capacity() ); + m_CallStack.reserve( max( vm->_alloccallsstacksize, 8 ) ); + + for ( int i = 0; i < vm->_callsstacksize; i++ ) + { + const SQVM::CallInfo &ci = vm->_callsstack[i]; + if ( sq_type(ci._closure) == OT_CLOSURE ) + CallBegin( _fp(_closure(ci._closure)->_function) ); + } + } + + void Stop() + { + Assert( IsEnabled() ); + + if ( m_State == kProfDisabled ) + return; + + m_State = kProfDisabled; + m_nPauseLevel = 0; + + for ( hnode_t i = 0; i < m_NodeTags.size(); i++ ) + { + nodetag_t *node = &m_NodeTags[i]; + __ObjRelease( node->funcsrc ); + __ObjRelease( node->funcname ); + } + + for ( hnode_t i = 0; i < m_Groups.size(); i++ ) + { + group_t *group = &m_Groups[i]; + __ObjRelease( group->tag ); + } + + m_Nodes.purge(); + m_NodeTags.purge(); + m_CallStack.purge(); + m_Groups.purge(); + m_GroupStack.purge(); + } + + void Reset( HSQUIRRELVM vm, SQString *tag ) + { + Assert( IsEnabled() ); + + if ( tag ) + { + hgroup_t idx; + group_t *group = FindGroup( tag, &idx ); + + if ( group ) + { + group->hits = 0; + group->peakHit = 0; + group->samples = 0.0; + group->peak = 0.0; + group->sampleStart = 0.0; + } + } + else + { + for ( hnode_t i = 0; i < m_Nodes.size(); i++ ) + { + node_t &node = m_Nodes[i]; + node.calls = 0; + node.samples = 0.0; + node.sampleStart = 0.0; + } + + for ( int i = 0; i < vm->_callsstacksize; i++ ) + { + const SQVM::CallInfo &ci = vm->_callsstack[i]; + if ( sq_type(ci._closure) == OT_CLOSURE ) + CallBegin( _fp(_closure(ci._closure)->_function) ); + } + } + } + + void GroupBegin( SQString *tag ) + { + Assert( IsActive() ); + + hgroup_t idx; + group_t *group = FindGroup( tag, &idx ); + + if ( group ) + { + m_GroupStack.append( idx ); + group->hits++; + group->sampleStart = Sample(); + return; + } + + m_GroupStack.append( m_Groups.size() ); + + group = &m_Groups.append(); + group->tag = tag; + __ObjAddRef( tag ); + group->hits = 1; + group->sampleStart = Sample(); + } + + void GroupEnd() + { + Assert( IsActive() ); + + sample_t sample = Sample(); + + if ( !m_GroupStack.size() ) + { + Assert(!"profiler group mismatch"); + return; + } + + hgroup_t idx = m_GroupStack.top(); + m_GroupStack.pop(); + + group_t *group = &m_Groups[idx]; + + // group was ended while profiler was paused + Assert( group->sampleStart != 0.0 ); + + sample_t dt = sample - group->sampleStart; + group->samples += dt; + + if ( group->peak < dt ) + { + group->peak = dt; + group->peakHit = group->hits; + } + } + + void Pause() + { + Assert( IsEnabled() ); + + sample_t sample = Sample(); + + ++m_nPauseLevel; + + if ( m_State != kProfPaused ) + { + m_State = kProfPaused; + + if ( m_CallStack.size() ) + { + hnode_t caller = m_CallStack.top(); + node_t *node = &m_Nodes[caller]; + node->samples += sample - node->sampleStart; +#ifdef _DEBUG + node->sampleStart = 0.0; +#endif + } + + if ( m_GroupStack.size() ) + { + hgroup_t idx = m_GroupStack.top(); + group_t *group = &m_Groups[idx]; + sample_t dt = sample - group->sampleStart; + group->samples += dt; +#ifdef _DEBUG + group->sampleStart = 0.0; +#endif + + if ( group->peak < dt ) + { + group->peak = dt; + group->peakHit = group->hits; + } + } + } + } + + void Resume() + { + Assert( IsEnabled() ); + + if ( m_State == kProfPaused && --m_nPauseLevel == 0 ) + { + m_State = kProfActive; + + sample_t sample = Sample(); + + if ( m_CallStack.size() ) + { + hnode_t caller = m_CallStack.top(); + node_t *node = &m_Nodes[caller]; + node->sampleStart = sample; + } + + if ( m_GroupStack.size() ) + { + hgroup_t idx = m_GroupStack.top(); + group_t *group = &m_Groups[idx]; + group->sampleStart = sample; + } + } + } + + void CallBegin( SQFunctionProto *func ) + { + Assert( IsActive() ); + + hnode_t caller = m_CallStack.size() ? m_CallStack.top() : INVALID_HANDLE; + + hnode_t id; + node_t *node = FindNode( caller, func, &id ); + + if ( node ) + { + m_CallStack.append( id ); + node->calls++; + node->sampleStart = Sample(); + return; + } + + SQString *funcname = ( sq_type(func->_name) == OT_STRING ) ? + _string(func->_name) : + NULL; + + SQString *funcsrc = ( sq_type(func->_sourcename) == OT_STRING ) ? + _string(func->_sourcename) : + NULL; + + id = m_Nodes.size(); + node = &m_Nodes.append(); + nodetag_t *tag = &m_NodeTags.append(); + + m_CallStack.append( id ); + + node->id = id; + node->func = func; + node->caller = caller; + node->calls = 1; + node->samples = 0.0; + +#if SQUIRREL_VERSION_NUMBER >= 300 + SQSharedState *ss = func->_sharedstate; +#else + // null sourcename is not possible in the compiler + // if it is, get SS from 'this': _string(func->_localvarinfos[0]._name)->_sharedstate; + // if there are no parameters either, + // the sharedstate needs to be accessible to the profiler + Assert( funcsrc ); + SQSharedState *ss = funcsrc->_sharedstate; +#endif + + if ( funcname ) + { + tag->funcname = funcname; + } + else + { + SQChar *tmp = ss->GetScratchPad( sq_rsl(FMT_PTR_LEN + 1) ); + int len = printhex( tmp, FMT_PTR_LEN, (uintptr_t)func ); + tmp[len] = 0; + tag->funcname = SQString::Create( ss, tmp, FMT_PTR_LEN ); + } + + if ( funcsrc ) + { + unsigned int len = funcsrc->_len + 1 + FMT_UINT32_LEN + 1; + SQChar *tmp = ss->GetScratchPad( sq_rsl(len) ); + len = funcsrc->_len; + memcpy( tmp, funcsrc->_val, sq_rsl(len) ); + +#ifdef SQDBG_SOURCENAME_HAS_PATH + StripFileName( &tmp, &len ); +#endif + + int line = GetFunctionDeclarationLine( func ); + if ( line ) + { + tmp[len++] = ':'; + len += printint( tmp + len, FMT_UINT32_LEN, line ); + } + + tmp[len] = 0; + tag->funcsrc = SQString::Create( ss, tmp, len ); + } + else + { + tag->funcsrc = CreateSQString( ss, _SC("??") ); + } + + __ObjAddRef( tag->funcname ); + __ObjAddRef( tag->funcsrc ); + + node->sampleStart = Sample(); + } + + void CallEnd() + { + Assert( IsActive() ); + + sample_t sample = Sample(); + + if ( !m_CallStack.size() ) + { + // metamethod calls don't execute debug hook pre-221 +#if SQUIRREL_VERSION_NUMBER >= 221 + Assert(!"profiler call mismatch"); +#endif + return; + } + + hnode_t id = m_CallStack.top(); + m_CallStack.pop(); + + node_t *node = &m_Nodes[id]; + + // call ended while profiler was paused + Assert( node->sampleStart != 0.0 ); + + node->samples += sample - node->sampleStart; + } + + void CallEndAll() + { + while ( m_CallStack.size() ) + CallEnd(); + } + +#define CALLGRAPH_MAX_DEPTH 10 +#define PROF_OUTPUT_HEADER " % total time time/call calls func\n" +// "100.00 100.00 ms 100.00 ms 0x7fffffff func\n" + +#define PROF_GROUP_OUTPUT_START \ + "(sqdbg) prof | " + +#define PROF_GROUP_OUTPUT_TEMPLATE \ + "(sqdbg) prof | : " \ + "total 100.00 ms, avg 100.00 ms, peak 100.00 ms(0x7fffffff), hits 0x7fffffff\n" + + // Returns character length + int GetMaxOutputLen( SQString *tag, int type ) + { + if ( tag ) + { + return STRLEN(PROF_GROUP_OUTPUT_TEMPLATE) + + ALIGN( tag->_len, 16 ) + + 1; + } + + switch ( type ) + { + // call graph + case 0: + { + const int header = STRLEN(PROF_OUTPUT_HEADER); + const int bufsize = header + m_Nodes.size() * + ( header - STRLEN("func") + + // depth[CALLGRAPH_MAX_DEPTH*3]func, src (addr)\n + CALLGRAPH_MAX_DEPTH * 3 + + /*func*/ 2 + + /*src*/ 1 + + 2 + FMT_PTR_LEN + + 1 ) + + 1; + + int strlen = 0; + + for ( hnode_t i = 0; i < m_NodeTags.size(); i++ ) + { + nodetag_t *node = &m_NodeTags[i]; + strlen += (int)node->funcsrc->_len; + strlen += (int)node->funcname->_len; + } + + return bufsize + strlen; + } + // flat + case 1: + { + const int header = STRLEN(PROF_OUTPUT_HEADER); + const int bufsize = header + m_Nodes.size() * + ( header - STRLEN("func") + + // func, src (addr)\n + /*func*/ 2 + + /*src*/ 1 + + 2 + FMT_PTR_LEN + + 1 ) + + 1; + + int strlen = 0; + + for ( hnode_t i = 0; i < m_NodeTags.size(); i++ ) + { + nodetag_t *node = &m_NodeTags[i]; + strlen += (int)node->funcsrc->_len; + strlen += (int)node->funcname->_len; + } + + return bufsize + strlen; + } + default: + { + return 0; + } + } + } + + // Returns character length + int Output( SQString *tag, int type, SQChar *buf, int size ) + { + if ( tag ) + { + hgroup_t idx; + const group_t *group = FindGroup( tag, &idx ); + + if ( !group ) + return 0; + + const SQChar *bufstart = buf; + + int len = STRLEN(PROF_GROUP_OUTPUT_START); + memcpy( buf, _SC(PROF_GROUP_OUTPUT_START), sq_rsl(len) ); + buf += len; size -= len; + + len = group->tag->_len; + memcpy( buf, group->tag->_val, sq_rsl(len) ); + buf += len; size -= len; + + for ( int i = ALIGN( len, 16 ) - len; i-- > 0; ) + { + *buf++ = ' '; + size--; + } + + *buf++ = ':'; size--; + *buf++ = ' '; size--; + + len = STRLEN("total "); + memcpy( buf, _SC("total "), sq_rsl(len) ); + buf += len; size -= len; + + sample_t time; + + if ( group->hits != 1 ) + { + time = group->samples - group->peak; + } + else + { + time = group->samples; + } + + PrintTime( time, buf, size ); + + *buf++ = ','; size--; + *buf++ = ' '; size--; + + len = STRLEN("avg "); + memcpy( buf, _SC("avg "), sq_rsl(len) ); + buf += len; size -= len; + + if ( group->hits != 1 ) + { + time = (group->samples - group->peak) / (sample_t)(group->hits - 1); + } + else + { + time = group->samples / (sample_t)group->hits; + } + + PrintTime( time, buf, size ); + + *buf++ = ','; size--; + *buf++ = ' '; size--; + + len = STRLEN("peak "); + memcpy( buf, _SC("peak "), sq_rsl(len) ); + buf += len; size -= len; + + PrintTime( group->peak, buf, size ); + + *buf++ = '('; size--; + len = printint( buf, size, group->peakHit ); + buf += len; size -= len; + *buf++ = ')'; size--; + + *buf++ = ','; size--; + *buf++ = ' '; size--; + + len = STRLEN("hits "); + memcpy( buf, _SC("hits "), sq_rsl(len) ); + buf += len; size -= len; + + len = printint( buf, size, group->hits ); + buf += len; size -= len; + + *buf++ = '\n'; + *buf = 0; + + return (int)( buf - bufstart ); + } + + vector< node_t > nodes( m_Nodes ); + + switch ( type ) + { + // call graph + case 0: + { + break; + } + // flat + // merge all calls of identical functions + case 1: + { + hnode_t c = nodes.size(); + + for ( hnode_t i = 0; i < c; i++ ) + { + node_t &node = nodes[i]; + node.caller = INVALID_HANDLE; + + for ( hnode_t j = i + 1; j < c; j++ ) + { + node_t &nj = nodes[j]; + + if ( nj.func == node.func ) + { + node.samples += nj.samples; + node.calls += nj.calls; + + nodes.remove(j); + j--; + c--; + } + } + } + + break; + } + default: + { + return 0; + } + } + + hnode_t nodecount = nodes.size(); + sample_t totalSamples = 0.0; + const SQChar *bufstart = buf; + + nodes.sort( _sort ); + + for ( hnode_t i = 0; i < nodecount; i++ ) + { + const node_t &node = nodes[i]; + + // Only accumulate parent call times + if ( node.caller == INVALID_HANDLE ) + { + if ( !m_CallStack.size() || m_CallStack.top() != node.id ) + { + totalSamples += node.samples; + } + // Within the call frame, accumulate children for a rough estimate + // This will miss any time spent in the body + // of the function excluding subcalls + else + { + for ( hnode_t j = i; j < nodecount; j++ ) + { + const node_t &nj = nodes[j]; + if ( nj.caller == node.id ) + totalSamples += nj.samples; + } + } + } + } + + int len = STRLEN(PROF_OUTPUT_HEADER); + memcpy( buf, _SC(PROF_OUTPUT_HEADER), sq_rsl(len) ); + buf += len; size -= len; + + for ( hnode_t i = 0; i < nodecount; i++ ) + { + const node_t &node = nodes[i]; + if ( node.caller != INVALID_HANDLE ) + break; + + Assert( size > 0 ); + DoPrint( nodes, i, totalSamples, 0, buf, size ); + } + + *buf = 0; + + Assert( (int)scstrlen( bufstart ) == (int)( buf - bufstart ) ); + + return (int)( buf - bufstart ); + } + +private: + void DoPrint( const vector< node_t > &nodes, hnode_t i, + sample_t totalSamples, int depth, SQChar *&buf, int &size ) + { + const node_t &node = nodes[i]; + + sample_t frac = ( node.samples / totalSamples ) * 100.0; + sample_t avg = node.samples / (sample_t)node.calls; + + int len; + + if ( node.samples && isfinite(frac) ) + { + if ( frac > 100.0 ) + frac = 100.0; + + len = scsprintf( buf, size, _SC("%6.2f"), frac ); + buf += len; size -= len; + } + else + { + *buf++ = ' '; size--; + *buf++ = ' '; size--; + *buf++ = ' '; size--; + + len = STRLEN("N/A"); + memcpy( buf, _SC("N/A"), sq_rsl(len) ); + buf += len; size -= len; + } + + *buf++ = ' '; size--; + *buf++ = ' '; size--; + + PrintTime( node.samples, buf, size ); + + *buf++ = ' '; size--; + *buf++ = ' '; size--; + + PrintTime( avg, buf, size ); + + *buf++ = ' '; size--; + + // right align + len = FMT_UINT32_LEN - countdigits( node.calls ); + + while ( len-- ) + { + *buf++ = ' '; + size--; + } + + len = printint( buf, size, node.calls ); + buf += len; size -= len; + + *buf++ = ' '; size--; + *buf++ = ' '; size--; + + if ( depth <= CALLGRAPH_MAX_DEPTH ) + { + for ( int d = depth; d--; ) + { + *buf++ = '|'; + *buf++ = ' '; + *buf++ = ' '; + } + + size -= depth * 3; + } + else + { + for ( int d = CALLGRAPH_MAX_DEPTH - 2; d--; ) + { + *buf++ = '|'; size--; + *buf++ = ' '; size--; + *buf++ = ' '; size--; + } + + buf--; size++; + + len = printint( buf, size, depth ); + buf += len; size -= len; + + for ( int d = ( ( len + 2 ) / 3 ) * 3; d-- > len; ) + { + *buf++ = ' '; + size--; + } + + *buf++ = ' '; size--; + + len = ( ( len + 2 ) / 3 ) * 3; + + for ( int d = ( ( 2 ) * 3 - len ) / 3; d--; ) + { + *buf++ = '|'; size--; + *buf++ = ' '; size--; + *buf++ = ' '; size--; + } + + *(buf-1) = '.'; + *(buf-2) = '.'; + } + + const nodetag_t &tag = m_NodeTags[node.id]; + + len = tag.funcname->_len; + memcpy( buf, tag.funcname->_val, sq_rsl(len) ); + buf += len; size -= len; + + *buf++ = ','; size--; + *buf++ = ' '; size--; + + len = tag.funcsrc->_len; + memcpy( buf, tag.funcsrc->_val, sq_rsl(len) ); + buf += len; size -= len; + + if ( tag.funcname->_val[0] != '0' ) + { + *buf++ = ' '; size--; + *buf++ = '('; size--; + len = printhex( buf, size, (uintptr_t)node.func ); + buf += len; size -= len; + *buf++ = ')'; size--; + } + + *buf++ = '\n'; size--; + + int more = 0; + avg = 0.0; + + for ( hnode_t j = i + 1; j < nodes.size(); j++ ) + { + const node_t &nj = nodes[j]; + if ( nj.caller == node.id ) + { + // Prevent stack overflow + // Limit should be at most 3 digits for the depth print + if ( depth < 100 ) + { + DoPrint( nodes, j, totalSamples, depth+1, buf, size ); + } + else + { + avg = ( more * avg + ( nj.samples / (sample_t)nj.calls ) ) / ( more + 1 ); + more++; + } + } + } + + if ( more ) + { + *buf++ = ' '; size--; + *buf++ = ' '; size--; + *buf++ = ' '; size--; + + len = STRLEN("N/A"); + memcpy( buf, _SC("N/A"), sq_rsl(len) ); + buf += len; size -= len; + + *buf++ = ' '; size--; + *buf++ = ' '; size--; + + PrintTime( NAN, buf, size ); + + *buf++ = ' '; size--; + *buf++ = ' '; size--; + + PrintTime( avg, buf, size ); + + *buf++ = ' '; size--; + + // right align + len = FMT_UINT32_LEN - STRLEN("N/A"); + + while ( len-- ) + { + *buf++ = ' '; + size--; + } + + len = STRLEN("N/A"); + memcpy( buf, _SC("N/A"), sq_rsl(len) ); + buf += len; size -= len; + + *buf++ = ' '; size--; + *buf++ = ' '; size--; + + for ( int d = CALLGRAPH_MAX_DEPTH; d--; ) + { + *buf++ = '|'; + *buf++ = ' '; + *buf++ = ' '; + } + + size -= CALLGRAPH_MAX_DEPTH * 3; + + *(buf-1) = '.'; + *(buf-2) = '.'; + + *buf++ = ' '; size--; + + len = printint( buf, size, more ); + buf += len; size -= len; + + len = STRLEN(" more"); + memcpy( buf, _SC(" more"), sq_rsl(len) ); + buf += len; size -= len; + + *buf++ = '\n'; size--; + } + } + + // Print time and its unit to 9 chars: "000.00 ms" + static void PrintTime( sample_t us, SQChar *&buf, int &size ) + { + if ( us < 1.0 ) + { + if ( us > 0.0 ) + { + int len = scsprintf( buf, size, _SC("%6.2f ns"), us * 1.e3 ); + buf += len; + size -= len; + } + else + { + goto LNAN; + } + } + else if ( us < 1.e3 ) + { + int len = scsprintf( buf, size, _SC("%6.2f us"), us ); + buf += len; + size -= len; + } + else if ( us < 1.e6 ) + { + int len = scsprintf( buf, size, _SC("%6.2f ms"), us / 1.e3 ); + buf += len; + size -= len; + } + else if ( us < 60.e6 * 15.0 ) // 900s + { + int len = scsprintf( buf, size, _SC("%6.2f s"), us / 1.e6 ); + buf += len; + size -= len; + } + else if ( us < 36.e8 ) // 60m + { + int len = scsprintf( buf, size, _SC("%6.2f m"), us / 6.e7 ); + buf += len; + size -= len; + } + else if ( us >= 36.e8 ) + { + if ( us < 6048.e8 ) // 24h * 7 + { + int len = scsprintf( buf, size, _SC("%6.2f h"), us / 36.e8 ); + buf += len; + size -= len; + } + else + { + if ( !isfinite(us) ) + goto LNAN; + + int len = 9 - STRLEN("> 1 w"); + while ( len-- ) + { + *buf++ = ' '; + size--; + } + + *buf++ = '>'; size--; + *buf++ = ' '; size--; + *buf++ = '1'; size--; + *buf++ = ' '; size--; + *buf++ = 'w'; size--; + } + } + else + { +LNAN: + int len = 9 - STRLEN("N/A"); + while ( len-- ) + { + *buf++ = ' '; + size--; + } + + *buf++ = 'N'; size--; + *buf++ = '/'; size--; + *buf++ = 'A'; size--; + } + } + + static int _sort( const node_t *a, const node_t *b ) + { + if ( a->caller != b->caller ) + { + if ( a->caller == INVALID_HANDLE ) + return -1; + + if ( b->caller == INVALID_HANDLE ) + return 1; + } + + if ( a->samples > b->samples ) + return -1; + + if ( b->samples > a->samples ) + return 1; + + return 0; + } +}; +#endif // !SQDBG_DISABLE_PROFILER + +// +// Longest return value is 16 bytes including nul byte +// +inline string_t GetType( const SQObjectPtr &obj ) +{ + switch ( _RAW_TYPE( sq_type(obj) ) ) + { + case _RT_NULL: return "null"; + case _RT_INTEGER: return "integer"; + case _RT_FLOAT: return "float"; + case _RT_BOOL: return "bool"; + case _RT_STRING: return "string"; + case _RT_TABLE: return "table"; + case _RT_ARRAY: return "array"; + case _RT_GENERATOR: return "generator"; + case _RT_CLOSURE: return "function"; + case _RT_NATIVECLOSURE: return "native function"; + case _RT_USERDATA: + case _RT_USERPOINTER: return "userdata"; + case _RT_THREAD: return "thread"; + case _RT_FUNCPROTO: return "function"; + case _RT_CLASS: return "class"; + case _RT_INSTANCE: return "instance"; + case _RT_WEAKREF: return "weakref"; +#if SQUIRREL_VERSION_NUMBER >= 300 + case _RT_OUTER: return "outer"; +#endif + default: Assert(!"unknown type"); return "unknown"; + } +} + +#if SQUIRREL_VERSION_NUMBER >= 300 +string_t const g_InstructionName[ _OP_CLOSE + 1 ]= +{ + "LINE", + "LOAD", + "LOADINT", + "LOADFLOAT", + "DLOAD", + "TAILCALL", + "CALL", + "PREPCALL", + "PREPCALLK", + "GETK", + "MOVE", + "NEWSLOT", + "DELETE", + "SET", + "GET", + "EQ", + "NE", + "ADD", + "SUB", + "MUL", + "DIV", + "MOD", + "BITW", + "RETURN", + "LOADNULLS", + "LOADROOT", + "LOADBOOL", + "DMOVE", + "JMP", + "JCMP", + "JZ", + "SETOUTER", + "GETOUTER", + "NEWOBJ", + "APPENDARRAY", + "COMPARITH", + "INC", + "INCL", + "PINC", + "PINCL", + "CMP", + "EXISTS", + "INSTANCEOF", + "AND", + "OR", + "NEG", + "NOT", + "BWNOT", + "CLOSURE", + "YIELD", + "RESUME", + "FOREACH", + "POSTFOREACH", + "CLONE", + "TYPEOF", + "PUSHTRAP", + "POPTRAP", + "THROW", + "NEWSLOTA", + "GETBASE", + "CLOSE", +}; +#elif SQUIRREL_VERSION_NUMBER >= 212 +string_t const g_InstructionName[ _OP_NEWSLOTA + 1 ]= +{ + "LINE", + "LOAD", + "LOADINT", + "LOADFLOAT", + "DLOAD", + "TAILCALL", + "CALL", + "PREPCALL", + "PREPCALLK", + "GETK", + "MOVE", + "NEWSLOT", + "DELETE", + "SET", + "GET", + "EQ", + "NE", + "ARITH", + "BITW", + "RETURN", + "LOADNULLS", + "LOADROOTTABLE", + "LOADBOOL", + "DMOVE", + "JMP", + "JNZ", + "JZ", + "LOADFREEVAR", + "VARGC", + "GETVARGV", + "NEWTABLE", + "NEWARRAY", + "APPENDARRAY", + "GETPARENT", + "COMPARITH", + "COMPARITHL", + "INC", + "INCL", + "PINC", + "PINCL", + "CMP", + "EXISTS", + "INSTANCEOF", + "AND", + "OR", + "NEG", + "NOT", + "BWNOT", + "CLOSURE", + "YIELD", + "RESUME", + "FOREACH", + "POSTFOREACH", + "DELEGATE", + "CLONE", + "TYPEOF", + "PUSHTRAP", + "POPTRAP", + "THROW", + "CLASS", + "NEWSLOTA", +}; +#endif + +string_t const g_MetaMethodName[ MT_LAST ] = +{ + "_add", + "_sub", + "_mul", + "_div", + "_unm", + "_modulo", + "_set", + "_get", + "_typeof", + "_nexti", + "_cmp", + "_call", + "_cloned", + "_newslot", + "_delslot", +#if SQUIRREL_VERSION_NUMBER >= 210 + "_tostring", + "_newmember", + "_inherited", +#endif +}; + + +#define KW_CALLFRAME "\xFF\xFF\xF0" +#define KW_DELEGATE "\xFF\xFF\xF1" +#ifdef CLOSURE_ROOT +#define KW_ROOT "\xFF\xFF\xF2" +#endif +#define KW_THIS "__this" +#define KW_VARGV "__vargv" +#define KW_VARGC "__vargc" + +#define IS_INTERNAL_TAG( str ) ((str)[0] == '$') +#define INTERNAL_TAG( name ) "$" name +#define ANONYMOUS_FUNCTION_BREAKPOINT_NAME "()" +#define INVALID_ID -1 +#define DUPLICATE_ID -2 +#define ISVALID_ID(i) ((i) > 0) +#define ARRAY_PAGE_LIMIT 1024 + +typedef enum +{ + kFS_None = 0x0000, + kFS_Hexadecimal = 0x0001, + kFS_Binary = 0x0002, + kFS_Decimal = 0x0004, + kFS_Float = 0x0008, + kFS_FloatE = 0x0010, + kFS_FloatG = 0x0020, + kFS_Octal = 0x0040, + kFS_Character = 0x0080, + kFS_NoQuote = 0x0100, + kFS_Uppercase = 0x0200, + kFS_Padding = 0x0400, + kFS_NoPrefix = 0x0800, + kFS_KeyVal = 0x1000, + kFS_NoAddr = 0x2000, + kFS_ListMembers = 0x4000, + kFS_Lock = 0x8000, +} VARSPEC; + +struct breakreason_t +{ + typedef enum + { + None = 0, + Step = 1, + Breakpoint, + Exception, + Pause, + Restart, + Goto, + FunctionBreakpoint, + DataBreakpoint, + } EBreakReason; + + EBreakReason reason; + string_t text; + int id; + + breakreason_t( EBreakReason r = None, const string_t &t = { 0, 0 }, int i = 0 ) : + reason(r), + text(t), + id(i) + {} +}; + +struct breakpoint_t +{ + int line; + sqstring_t src; + + sqstring_t funcsrc; + int funcline; + + SQObjectPtr conditionFn; + SQObjectPtr conditionEnv; + + int hitsTarget; + int hits; + string_t logMessage; + + int id; +}; + +typedef enum +{ + VARREF_OBJ = 0, + VARREF_SCOPE_LOCALS, + VARREF_SCOPE_OUTERS, + VARREF_STACK, + VARREF_INSTRUCTIONS, + VARREF_OUTERS, + VARREF_LITERALS, + VARREF_METAMETHODS, + VARREF_ATTRIBUTES, + VARREF_CALLSTACK, + VARREF_MAX +} EVARREF; + +inline bool IsScopeRef( EVARREF type ) +{ + Assert( type >= 0 && type < VARREF_MAX ); + return ( type == VARREF_SCOPE_LOCALS || + type == VARREF_SCOPE_OUTERS || + type == VARREF_STACK ); +} + +inline bool IsObjectRef( EVARREF type ) +{ + Assert( type >= 0 && type < VARREF_MAX ); + return ( type == VARREF_OBJ || + type == VARREF_INSTRUCTIONS || + type == VARREF_OUTERS || + type == VARREF_LITERALS || + type == VARREF_METAMETHODS || + type == VARREF_ATTRIBUTES || + type == VARREF_CALLSTACK ); +} + +struct varref_t +{ + EVARREF type; + + union + { + struct + { + SQWeakRef *thread; + int frame; + } scope; + + struct + { + union + { + SQWeakRef *weakref; + SQObject obj; + }; + + bool isWeak; + bool isStrong; // temporary strong reference for inspecting vars + bool hasNonStringMembers; + } obj; + }; + + int id; + + HSQUIRRELVM GetThread() const + { + Assert( IsScopeRef( type ) ); + Assert( scope.thread ); + Assert( sq_type(scope.thread->_obj) == OT_THREAD ); + return _thread(scope.thread->_obj); + } + + const SQObject &GetVar() const + { + Assert( IsObjectRef( type ) ); + Assert( !obj.isWeak || ( obj.isWeak && obj.weakref ) ); + Assert( !obj.isStrong || ( obj.isStrong && ISREFCOUNTED( sq_type(obj.obj) ) ) ); + Assert( ( !obj.isWeak && !obj.isStrong ) || ( obj.isWeak != obj.isStrong ) ); + return obj.isWeak ? obj.weakref->_obj : obj.obj; + } +}; + +struct watch_t +{ + string_t expression; + SQWeakRef *thread; + int frame; +}; + +struct classdef_t +{ + SQClass *base; + string_t name; + SQObjectPtr value; + SQObjectPtr metamembers; + SQObjectPtr custommembers; +}; + +struct frameid_t +{ + int frame; + int threadId; +}; + +struct script_t +{ + char *sourceptr; + char *scriptptr; + unsigned int sourcelen; + unsigned int scriptlen; +}; + +struct objref_t +{ + typedef enum + { + INVALID = 0, + TABLE, + INSTANCE, + CLASS, + ARRAY, + DELEGABLE_META, + CUSTOMMEMBER, + STACK, + INT, + PTR = 0x1000, + READONLY = 0x2000, + } EOBJREF; + + EOBJREF type; + + union + { + SQObjectPtr *ptr; + int val; + + // for data watches + struct + { + SQWeakRef *thread; + int frame; + int end; + int index; + } stack; + }; + + // Let src hold strong ref for compiler assignment expressions such as ( a.b = a = null ) + SQObjectPtr src; + // Let key hold strong ref for compiler newslot target + SQObjectPtr key; +}; + +struct datawatch_t +{ + SQWeakRef *container; + objref_t obj; + string_t name; + + // Hold strong ref to be able to print its value when it's a ref counted object + // if the old value is to be released, it will be done in CheckDataBreakpoints + SQObjectPtr oldvalue; + + int hitsTarget; + int hits; + + SQObjectPtr condition; + unsigned int condtype; + + int id; +}; + +struct returnvalue_t +{ + SQObjectPtr value; + SQString *funcname; + uintptr_t funcptr; +}; + +struct cachedinstr_t +{ + SQInstruction *ip; + SQInstruction instr; +}; + +#ifndef SQDBG_DISABLE_PROFILER +struct threadprofiler_t +{ + SQWeakRef *thread; + CProfiler prof; +}; +#endif + +typedef enum +{ + ThreadState_Running = 0, + ThreadState_Suspended, + ThreadState_NextStatement, + ThreadState_StepOver, + ThreadState_StepIn, + ThreadState_StepOut, + ThreadState_StepOverInstruction, + ThreadState_StepInInstruction, + ThreadState_StepOutInstruction, + ThreadState_SuspendNow, +} EThreadState; + +// +// Squirrel doesn't read files, it usually keeps file names passed in from host programs. +// DAP returns file path on breakpoints; try to construct file paths from these partial +// file names. This will not work for multiple files with identical names and for files +// where breakpoints were not set. +// +class CFilePathMap +{ +public: + struct pair_t + { + string_t name; + string_t path; + }; + + vector< pair_t > map; + + ~CFilePathMap() + { + Assert( map.size() == 0 ); + } + + void Add( CScratch<> *allocator, const string_t &name, const string_t &path ) + { + for ( unsigned int i = 0; i < map.size(); i++ ) + { + pair_t &v = map[i]; + if ( v.name.IsEqualTo( name ) ) + { + if ( !v.path.IsEqualTo( path ) ) + { + CopyString( allocator, path, &v.path ); + } + + return; + } + } + + pair_t &v = map.append(); + CopyString( allocator, name, &v.name ); + CopyString( allocator, path, &v.path ); + } + + pair_t *Get( const string_t &name ) + { + for ( unsigned int i = 0; i < map.size(); i++ ) + { + pair_t &v = map[i]; + if ( v.name.IsEqualTo( name ) ) + return &v; + } + + return NULL; + } + + void Clear( CScratch<> *allocator ) + { + for ( unsigned int i = 0; i < map.size(); i++ ) + { + pair_t &v = map[i]; + FreeString( allocator, &v.name ); + FreeString( allocator, &v.path ); + } + + map.purge(); + } +}; + +#define Print(...) \ + { \ + _OutputDebugStringFmt( __VA_ARGS__ ); \ + m_Print( m_pCurVM, __VA_ARGS__ ); \ + } + +#define PrintError(...) \ + { \ + _OutputDebugStringFmt( __VA_ARGS__ ); \ + m_PrintError( m_pCurVM, __VA_ARGS__ ); \ + } + +struct SQDebugServer +{ +private: + EThreadState m_State; + int m_nStateCalls; + int m_nCalls; + int m_Sequence; + +public: + HSQUIRRELVM m_pRootVM; + +private: + HSQUIRRELVM m_pCurVM; + HSQUIRRELVM m_pStateVM; + + SQPRINTFUNCTION m_Print; + SQPRINTFUNCTION m_PrintError; + SQObjectPtr m_ErrorHandler; + +#ifndef SQDBG_DISABLE_PROFILER + CProfiler *m_pProfiler; + vector< threadprofiler_t > m_Profilers; + bool m_bProfilerEnabled; +#endif + + bool m_bBreakOnExceptions; + bool m_bDebugHookGuard; +public: + bool m_bInREPL; +private: + bool m_bDebugHookGuardAlways; +#if SQUIRREL_VERSION_NUMBER < 300 + bool m_bInDebugHook; +#endif + bool m_bExceptionPause; +#ifndef SQDBG_DISABLE_COMPILER + bool m_bClientColumnOffset; +#endif + + HSQUIRRELVM m_pPausedThread; + + // Ignore debug hook calls from debugger executed scripts + class CCallGuard + { + public: + SQDebugServer *dbg; + HSQUIRRELVM vm; + SQObjectPtr temp_reg; + + CCallGuard( SQDebugServer *p, HSQUIRRELVM v ) : + dbg( p ), + vm( v ), + temp_reg( v->temp_reg ) + { + if ( dbg->m_bDebugHookGuardAlways || !dbg->m_bInREPL ) + { + dbg->m_bDebugHookGuard = true; + } + } + + ~CCallGuard() + { + dbg->m_bDebugHookGuard = false; + vm->temp_reg = temp_reg; + } + }; + +private: + SQObjectPtr m_sqfnGet; + SQObjectPtr m_sqfnSet; + SQObjectPtr m_sqfnNewSlot; + SQObjectPtr m_EnvGetVal; + + SQObjectPtr m_sqstrCallFrame; + SQObjectPtr m_sqstrDelegate; +#ifdef CLOSURE_ROOT + SQObjectPtr m_sqstrRoot; +#endif + + int m_nBreakpointIndex; + int m_nVarRefIndex; + unsigned int m_iYieldValues; + unsigned int m_nFunctionBreakpointsIdx; + + vector< cachedinstr_t > m_CachedInstructions; + vector< returnvalue_t > m_ReturnValues; + vector< varref_t > m_Vars; + vector< watch_t > m_LockedWatches; + vector< breakpoint_t > m_Breakpoints; + vector< datawatch_t > m_DataWatches; + vector< classdef_t > m_ClassDefinitions; + vector< SQWeakRef* > m_Threads; + vector< frameid_t > m_FrameIDs; + + CFilePathMap m_FilePathMap; + vector< script_t > m_Scripts; + + CBuffer m_SendBuf; + CScratch< JSON_SCRATCH_CHUNK_SIZE > m_ReadBuf; + CMemory m_Scratch; + CMemory m_VarMemberCache; + CScratch<> m_Strings; + + CServerSocket m_Server; + +public: + char *ScratchPad( unsigned int size ) + { + m_Scratch.Ensure( size ); + return m_Scratch.Base(); + } + + stringbufext_t ScratchPadBuf( unsigned int size ) + { + m_Scratch.Ensure( size ); + return { m_Scratch.Base(), m_Scratch.Base() ? size : 0 }; + } + +public: + void Attach( HSQUIRRELVM vm ); + void SetErrorHandler( bool state ); + void DoSetDebugHook( HSQUIRRELVM vm, _SQDEBUGHOOK fn ); + void SetDebugHook( _SQDEBUGHOOK fn ); + bool ListenSocket( unsigned short port ); + void Shutdown(); + void DisconnectClient(); + void OnClientConnected( const char *addr ); + void Frame(); + + bool IsClientConnected() { return m_Server.IsClientConnected(); } + +private: + void PrintLastServerMessage() + { +#ifdef SQUNICODE + int len = 0; + SQChar wcs[256]; + +#define _bs (int)( sizeof(wcs) - len * sizeof(SQChar) ) +#define _bl (int)( sizeof(wcs) / sizeof(SQChar) ) + + len = UTF8ToSQUnicode( wcs, _bs, m_Server.m_pszLastMsgFmt, strlen(m_Server.m_pszLastMsgFmt) ); + + if ( m_Server.m_pszLastMsg && len < _bl - 2 ) + { + wcs[len++] = ' '; + wcs[len++] = '('; + + len += UTF8ToSQUnicode( wcs + len, _bs, m_Server.m_pszLastMsg, strlen(m_Server.m_pszLastMsg) ); + + if ( len < _bl - 2 ) + { + wcs[len++] = ')'; + wcs[len++] = '\n'; + } + } + else if ( len < _bl - 1 ) + { + wcs[len++] = '\n'; + } + + wcs[ min( len, _bl - 1 ) ] = 0; + +#undef _bs +#undef _bl + + PrintError( wcs ); +#else + stringbuf_t< 256 > buf; + buf.Puts( { m_Server.m_pszLastMsgFmt, (unsigned int)strlen(m_Server.m_pszLastMsgFmt) } ); + + if ( m_Server.m_pszLastMsg ) + { + buf.Put(' '); + buf.Put('('); + buf.Puts( { m_Server.m_pszLastMsg, (unsigned int)strlen(m_Server.m_pszLastMsg) } ); + buf.Put(')'); + } + + buf.Put('\n'); + buf.Term(); + + PrintError( buf.ptr ); +#endif + + m_Server.m_pszLastMsg = NULL; + } + + void Recv() + { + if ( !m_Server.Recv() ) + { + PrintLastServerMessage(); + DisconnectClient(); + } + } + + void Parse() + { + if ( !m_Server.Parse< DAP_ReadHeader >() ) + { + PrintLastServerMessage(); + DisconnectClient(); + } + } + + void Send( const char *buf, int len ) + { + if ( m_Server.IsClientConnected() && !m_Server.Send( buf, len ) ) + { + PrintLastServerMessage(); + DisconnectClient(); + } + } + + void OnMessageReceived( char *ptr, int len ); + + void ProcessRequest( const json_table_t &table, int seq ); + void ProcessResponse( const json_table_t &table, int seq ); + void ProcessEvent( const json_table_t &table ); + + void OnRequest_Initialize( const json_table_t &arguments, int seq ); + void OnRequest_SetBreakpoints( const json_table_t &arguments, int seq ); + void OnRequest_SetFunctionBreakpoints( const json_table_t &arguments, int seq ); + void OnRequest_SetExceptionBreakpoints( const json_table_t &arguments, int seq ); + void OnRequest_SetDataBreakpoints( const json_table_t &arguments, int seq ); + void OnRequest_DataBreakpointInfo( const json_table_t &arguments, int seq ); +#ifndef SQDBG_DISABLE_COMPILER + void OnRequest_Completions( const json_table_t &arguments, int seq ); +#endif + void OnRequest_Evaluate( const json_table_t &arguments, int seq ); + void OnRequest_Scopes( const json_table_t &arguments, int seq ); + void OnRequest_Threads( int seq ); + void OnRequest_StackTrace( const json_table_t &arguments, int seq ); + void OnRequest_Variables( const json_table_t &arguments, int seq ); + void OnRequest_SetVariable( const json_table_t &arguments, int seq ); +#ifdef SQDBG_SUPPORTS_SET_INSTRUCTION + void OnRequest_SetVariable_Instruction( const varref_t *ref, string_t &strName, string_t &strValue, int seq ); +#endif + void OnRequest_SetExpression( const json_table_t &arguments, int seq ); + void OnRequest_Disassemble( const json_table_t &arguments, int seq ); +#ifdef SUPPORTS_RESTART_FRAME + void OnRequest_RestartFrame( const json_table_t &arguments, int seq ); +#endif + void OnRequest_GotoTargets( const json_table_t &arguments, int seq ); + void OnRequest_Goto( const json_table_t &arguments, int seq ); + void OnRequest_Next( const json_table_t &arguments, int seq ); + void OnRequest_StepIn( const json_table_t &arguments, int seq ); + void OnRequest_StepOut( const json_table_t &arguments, int seq ); + +private: + int AddBreakpoint( int line, const string_t &src, + const string_t &condition, int hitsTarget, const string_t &logMessage ); + int AddFunctionBreakpoint( const string_t &func, const string_t &funcsrc, int line, + const string_t &condition, int hitsTarget, const string_t &logMessage ); + + breakpoint_t *GetBreakpoint( int line, const sqstring_t &src ); + breakpoint_t *GetFunctionBreakpoint( const sqstring_t &func, const sqstring_t &funcsrc, int line ); + + void FreeBreakpoint( breakpoint_t &bp ); + static inline bool HasCondition( const breakpoint_t *bp ); + bool CheckBreakpointCondition( breakpoint_t *bp, HSQUIRRELVM vm, const SQVM::CallInfo *ci ); + + int EvalAndWriteExpr( HSQUIRRELVM vm, const SQVM::CallInfo *ci, string_t &expression, char *buf, int size ); + void TracePoint( breakpoint_t *bp, HSQUIRRELVM vm, int frame ); + + enum + { + ECMP_NONE = 0, + ECMP_EQ = 0x01, + ECMP_NE = 0x02, + ECMP_G = 0x04, + ECMP_GE = ECMP_G | ECMP_EQ, + ECMP_L = 0x08, + ECMP_LE = ECMP_L | ECMP_EQ, + ECMP_BWA = 0x10, + ECMP_BWAZ = 0x20, + ECMP_BWAEQ = 0x40, + }; + + int CompareObj( const SQObjectPtr &lhs, const SQObjectPtr &rhs ); + + bool CompileDataBreakpointCondition( string_t condition, SQObjectPtr &out, unsigned int &type ); + int AddDataBreakpoint( HSQUIRRELVM vm, const SQVM::CallInfo *ci, + const string_t &dataId, const string_t &condition, int hitsTarget ); + bool CheckDataBreakpoints( HSQUIRRELVM vm ); + void FreeDataWatch( datawatch_t &dw ); + + inline void RemoveAllBreakpoints(); + inline void RemoveBreakpoints( const string_t &source ); + inline void RemoveFunctionBreakpoints(); + inline void RemoveDataBreakpoints(); + + bool InstructionStep( HSQUIRRELVM vm, SQVM::CallInfo *ci, int instrOffset = 0 ); + bool Step( HSQUIRRELVM vm, SQVM::CallInfo *ci ); + + void CacheInstruction( SQInstruction *instr ); + void ClearCachedInstructions(); + void RestoreCachedInstructions(); + void UndoRestoreCachedInstructions(); + + void SetSource( wjson_table_t &source, SQString *sourcename ); + +public: + static inline bool IsJumpOp( const SQInstruction *instr ); + static inline int GetJumpCount( const SQInstruction *instr ); + static int DeduceJumpCount( const SQInstruction *instr ); + +private: + static inline bool IsValidStackFrame( HSQUIRRELVM vm, int id ); + static inline SQVM::CallInfo *GetStackFrame( HSQUIRRELVM vm, int id ); + static inline int GetStackBase( HSQUIRRELVM vm, const SQVM::CallInfo *ci ); + + static HSQUIRRELVM GetThread( SQWeakRef *wr ) + { + Assert( wr ); + Assert( sq_type(wr->_obj) == OT_THREAD ); + return _thread(wr->_obj); + } + + static SQWeakRef *GetWeakRef( HSQUIRRELVM vm ) { return GetWeakRef( vm, OT_THREAD ); } + static SQWeakRef *GetWeakRef( SQRefCounted *obj, SQObjectType type ) + { + return obj->GetWeakRef( type ); + } + + string_t GetValue( const SQObject &obj, int flags = 0 ); + + string_t GetValue( SQInteger val, int flags = 0 ) + { + SQObject obj; + obj._type = OT_INTEGER; + obj._unVal.nInteger = val; + return GetValue( obj, flags ); + } + + static SQObject ToSQObject( SQClass *val ) + { + SQObject obj; + obj._type = OT_CLASS; + obj._unVal.pClass = val; + return obj; + } + + static SQObject ToSQObject( SQTable *val ) + { + SQObject obj; + obj._type = OT_TABLE; + obj._unVal.pTable = val; + return obj; + } + + void JSONSetString( wjson_table_t &elem, const string_t &key, const SQObject &obj, int flags = 0 ); + +#ifndef SQDBG_DISABLE_COMPILER +public: + class CCompiler; + enum ECompileReturnCode + { + CompileReturnCode_Success, + // Lookup failed + CompileReturnCode_DoesNotExist, + // String/number parsing failed + CompileReturnCode_SyntaxError, + CompileReturnCode_Fallback, + CompileReturnCode_NoValue, + // Unrecognised token or token sequences + CompileReturnCode_Unsupported, + // Valid but too many parameters in a function call + CompileReturnCode_CallBufferFull, + // Valid but too many unary operators in a row + CompileReturnCode_OpBufferFull, + }; + + // NOTE: Expression string will be modified if it contains a string with escape characters + // This may cause RunExpression fallback after Evaluate to fail + // This can be avoided by exiting compilation before escaped strings are parsed + // by putting "0," at the beginning of the expression - this is valid squirrel while not allowed here + ECompileReturnCode Evaluate( string_t &expression, HSQUIRRELVM vm, const SQVM::CallInfo *ci, SQObjectPtr &ret ); + ECompileReturnCode Evaluate( string_t &expression, HSQUIRRELVM vm, int frame, SQObjectPtr &ret ) + { + return Evaluate( expression, vm, GetStackFrame( vm, frame ), ret ); + } + + ECompileReturnCode Evaluate( string_t &expression, HSQUIRRELVM vm, const SQVM::CallInfo *ci, SQObjectPtr &ret, + objref_t &obj ); + +private: + static inline SQTable *GetDefaultDelegate( HSQUIRRELVM vm, SQObjectType type ); + bool ArithOp( char op, const SQObjectPtr &lhs, const SQObjectPtr &rhs, SQObjectPtr &out ); + bool NewSlot( const objref_t &obj, const SQObjectPtr &value ); + bool Delete( const objref_t &obj, SQObjectPtr &value ); + bool Increment( const objref_t &obj, int amt ); +#endif + +private: + static inline void ConvertPtr( objref_t &obj ); + + bool GetObj_VarRef( const varref_t *ref, string_t &expression, + objref_t &out, SQObjectPtr &value ); + bool GetObj_Var( const SQObjectPtr &var, const SQObjectPtr &key, + objref_t &out, SQObjectPtr &value ); + bool GetObj_Var( const SQObjectPtr &var, string_t &expression, bool identifierIsString, + objref_t &out, SQObjectPtr &value ); + bool GetObj_Frame( HSQUIRRELVM vm, const SQVM::CallInfo *ci, const string_t &expression, + objref_t &out, SQObjectPtr &value ); + bool GetObj_Frame( HSQUIRRELVM vm, int frame, const string_t &expression, + objref_t &out, SQObjectPtr &value ) + { + return GetObj_Frame( vm, GetStackFrame( vm, frame ), expression, out, value ); + } + + bool Get( const objref_t &obj, SQObjectPtr &value ); + bool Set( const objref_t &obj, const SQObjectPtr &value ); + +private: + bool RunExpression( const string_t &expression, HSQUIRRELVM vm, const SQVM::CallInfo *ci, + SQObjectPtr &out, bool multiline = false ); + + bool RunExpression( const string_t &expression, HSQUIRRELVM vm, int frame, + SQObjectPtr &out, bool multiline = false ) + { + return RunExpression( expression, vm, GetStackFrame( vm, frame ), out, multiline ); + } + + bool CompileScript( const string_t &script, SQObjectPtr &out ); + bool RunScript( HSQUIRRELVM vm, const string_t &script, +#ifdef CLOSURE_ROOT + SQWeakRef *root, +#endif + const SQObject *env, SQObjectPtr &out, bool multiline = false ); + + bool RunClosure( const SQObjectPtr &closure, const SQObject *env, + SQObjectPtr &ret ) + { + CCallGuard cg( this, m_pCurVM ); + return RunClosure( m_pCurVM, closure, env, ret ); + } + + bool RunClosure( const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, SQObjectPtr &ret ) + { + CCallGuard cg( this, m_pCurVM ); + return RunClosure( m_pCurVM, closure, env, p1, ret ); + } + + bool RunClosure( const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, SQObjectPtr &ret ) + { + CCallGuard cg( this, m_pCurVM ); + return RunClosure( m_pCurVM, closure, env, p1, p2, ret ); + } + + bool RunClosure( const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, const SQObjectPtr &p3, SQObjectPtr &ret ) + { + CCallGuard cg( this, m_pCurVM ); + return RunClosure( m_pCurVM, closure, env, p1, p2, p3, ret ); + } + + bool RunClosure( const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, const SQObjectPtr &p3, const SQObjectPtr &p4, + SQObjectPtr &ret ) + { + CCallGuard cg( this, m_pCurVM ); + return RunClosure( m_pCurVM, closure, env, p1, p2, p3, p4, ret ); + } + + bool RunClosure( const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, const SQObjectPtr &p3, const SQObjectPtr &p4, + const SQObjectPtr &p5, SQObjectPtr &ret ) + { + CCallGuard cg( this, m_pCurVM ); + return RunClosure( m_pCurVM, closure, env, p1, p2, p3, p4, p5, ret ); + } + + static inline bool RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + SQObjectPtr &ret ); + static inline bool RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, SQObjectPtr &ret ); + static inline bool RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, SQObjectPtr &ret ); + static inline bool RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, const SQObjectPtr &p3, SQObjectPtr &ret ); + static inline bool RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, const SQObjectPtr &p3, const SQObjectPtr &p4, + SQObjectPtr &ret ); + static inline bool RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, const SQObjectPtr &p3, const SQObjectPtr &p4, + const SQObjectPtr &p5, SQObjectPtr &ret ); + + static SQInteger SQMM_Get( HSQUIRRELVM vm ); + static SQInteger SQMM_Set( HSQUIRRELVM vm ); + static SQInteger SQMM_NewSlot( HSQUIRRELVM vm ); + + static bool GetVariable( HSQUIRRELVM vm, const SQVM::CallInfo *ci, + const SQObject &mtenv, const SQObject &index, SQObjectPtr &out ); + static bool SetVariable( HSQUIRRELVM vm, const SQVM::CallInfo *ci, + const SQObject &mtenv, const SQObject &index, const SQObject &value ); + static bool NewSlot( HSQUIRRELVM vm, const SQObject &mtenv, const SQObject &index, const SQObject &value ); + +private: + script_t *GetScript( const string_t &source ); + void RemoveScripts(); + +public: + void OnScriptCompile( const SQChar *script, unsigned int scriptlen, + const SQChar *sourcename, unsigned int sourcenamelen ); + +private: + static bool ParseEvaluateName( const string_t &expression, HSQUIRRELVM vm, int frame, + objref_t &out, SQObjectPtr &value ); + static int ParseFormatSpecifiers( string_t &expression, char **ppComma = NULL ); + static bool ParseBinaryNumber( const string_t &value, SQObject &out ); + static inline bool ShouldParseEvaluateName( const string_t &expression ); + static inline bool ShouldPageArray( const SQObject &obj, unsigned int limit ); +#ifndef SQDBG_DISABLE_COMPILER + void FillCompletions( const SQObjectPtr &target, HSQUIRRELVM vm, const SQVM::CallInfo *ci, + int token, const string_t &partial, int start, int length, wjson_array_t &targets ); +#endif + +private: + void InitEnv_GetVal( SQObjectPtr &env ); + void SetCallFrame( SQObjectPtr &env, HSQUIRRELVM vm, const SQVM::CallInfo *ci ); + void SetEnvDelegate( SQObjectPtr &env, const SQObject &delegate ); + void ClearEnvDelegate( SQObjectPtr &env ); +#ifdef CLOSURE_ROOT + void SetEnvRoot( SQObjectPtr &env, const SQObjectPtr &root ); +#endif + +private: + static inline bool ShouldIgnoreStackFrame( const SQVM::CallInfo &ci ); + int ConvertToFrameID( int threadId, int stackFrame ); + bool TranslateFrameID( int frameId, HSQUIRRELVM *thread, int *stackFrame ); + + int ThreadToID( HSQUIRRELVM vm ); + HSQUIRRELVM ThreadFromID( int id ); + inline void RemoveThreads(); + +private: + int ToVarRef( EVARREF type, HSQUIRRELVM vm, int frame ); + int ToVarRef( EVARREF type, const SQObject &obj, bool isWeak = false, bool isStrong = false ); + int ToVarRef( const SQObject &obj, bool isWeak = false, bool isStrong = false ) + { + return ToVarRef( VARREF_OBJ, obj, isWeak, isStrong ); + } + + static inline void ConvertToWeakRef( varref_t &v ); + + inline varref_t *FromVarRef( int i ); + inline void RemoveVarRefs( bool all ); + inline void RemoveLockedWatches(); + inline void RemoveReturnValues(); + + void Suspend(); + void Break( HSQUIRRELVM vm, breakreason_t reason ); + void Continue( HSQUIRRELVM vm ); + + classdef_t *FindClassDef( SQClass *base ); + + // Fallback to base class + const SQObjectPtr *GetClassDefValue( SQClass *base ); + const SQObjectPtr *GetClassDefMetaMembers( SQClass *base ); + const SQObjectPtr *GetClassDefCustomMembers( SQClass *base ); + + bool CallCustomMembersGetFunc( const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &key, SQObjectPtr &ret ); + bool CallCustomMembersSetFunc( const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &key, const SQObjectPtr &val, SQObjectPtr &ret ); + + void DefineClass( SQClass *target, SQTable *params ); + inline void RemoveClassDefs(); + +private: + static SQUnsignedInteger s_nTargetAssignment; + static inline SQString *GetLocalVarName( const SQFunctionProto *func, const SQInstruction *instr, + unsigned int pos ); + static inline void PrintStackVar( const SQFunctionProto *func, const SQInstruction *instr, + unsigned int pos, stringbufext_t &buf ); + static inline void PrintOuter( const SQFunctionProto *func, int pos, stringbufext_t &buf ); + inline void PrintLiteral( const SQFunctionProto *func, int pos, stringbufext_t &buf ); + static inline void PrintStackTarget( const SQFunctionProto *func, const SQInstruction *instr, + stringbufext_t &buf ); + static inline void PrintStackTargetVar( const SQFunctionProto *func, const SQInstruction *instr, + stringbufext_t &buf ); + static inline void PrintDeref( const SQFunctionProto *func, const SQInstruction *instr, + unsigned int self, unsigned int key, stringbufext_t &buf ); + + void DescribeInstruction( const SQInstruction *instr, const SQFunctionProto *func, stringbufext_t &buf ); + inline int DisassemblyBufLen( SQClosure *target ); + sqstring_t PrintDisassembly( SQClosure *target, SQChar *scratch, int bufsize ); + +#ifndef SQDBG_DISABLE_PROFILER +public: + bool IsProfilerEnabled() { return m_bProfilerEnabled; } + CProfiler *GetProfiler( HSQUIRRELVM vm ); + inline CProfiler *GetProfilerFast( HSQUIRRELVM vm ); + void ProfSwitchThread( HSQUIRRELVM vm ); + void ProfStart(); + void ProfStop(); + void ProfPause( HSQUIRRELVM vm ); + void ProfResume( HSQUIRRELVM vm ); + void ProfReset( HSQUIRRELVM vm, SQString *tag ); + void ProfGroupBegin( HSQUIRRELVM vm, SQString *tag ); + void ProfGroupEnd( HSQUIRRELVM vm ); + sqstring_t ProfGets( HSQUIRRELVM vm, SQString *tag, int type ); + void ProfPrint( HSQUIRRELVM vm, SQString *tag, int type ); +#endif + +private: + void ErrorHandler( HSQUIRRELVM vm ); + void DebugHook( HSQUIRRELVM vm, SQInteger type, + const SQChar *sourcename, SQInteger line, const SQChar *funcname ); +#ifndef SQDBG_DISABLE_PROFILER + void ProfHook( HSQUIRRELVM vm, SQInteger type ); +#endif + void OnSQPrint( HSQUIRRELVM vm, const SQChar *buf, int len ); + void OnSQError( HSQUIRRELVM vm, const SQChar *buf, int len ); + + template < typename T > + void SendEvent_OutputStdOut( const T &strOutput, const SQVM::CallInfo *ci ); + +public: + static SQInteger SQDefineClass( HSQUIRRELVM vm ); + static SQInteger SQPrintDisassembly( HSQUIRRELVM vm ); + static SQInteger SQBreak( HSQUIRRELVM vm ); +#ifndef SQDBG_DISABLE_COMPILER + static SQInteger SQAddDataBreakpoint( HSQUIRRELVM vm ); +#endif +#ifndef SQDBG_DISABLE_PROFILER + static SQInteger SQProfStart( HSQUIRRELVM vm ); + static SQInteger SQProfStop( HSQUIRRELVM vm ); + static SQInteger SQProfPause( HSQUIRRELVM vm ); + static SQInteger SQProfResume( HSQUIRRELVM vm ); + static SQInteger SQProfReset( HSQUIRRELVM vm ); + static SQInteger SQProfGroupBegin( HSQUIRRELVM vm ); + static SQInteger SQProfGroupEnd( HSQUIRRELVM vm ); + static SQInteger SQProfGets( HSQUIRRELVM vm ); + static SQInteger SQProfPrint( HSQUIRRELVM vm ); +#endif + + static void SQPrint( HSQUIRRELVM vm, const SQChar *fmt, ... ); + static void SQError( HSQUIRRELVM vm, const SQChar *fmt, ... ); + +#ifndef SQDBG_CALL_DEFAULT_ERROR_HANDLER + static void SQErrorAtFrame( HSQUIRRELVM vm, const SQVM::CallInfo *ci, const SQChar *fmt, ... ); + static void PrintVar( HSQUIRRELVM vm, const SQChar *name, const SQObjectPtr &obj ); + static void PrintStack( HSQUIRRELVM vm ); +#endif + + static SQInteger SQErrorHandler( HSQUIRRELVM vm ); +#ifdef NATIVE_DEBUG_HOOK + static void SQDebugHook( HSQUIRRELVM vm, SQInteger type, + const SQChar *sourcename, SQInteger line, const SQChar *funcname ); +#else + static SQInteger SQDebugHook( HSQUIRRELVM vm ); +#endif +#ifndef SQDBG_DISABLE_PROFILER +#ifdef NATIVE_DEBUG_HOOK + static void SQProfHook( HSQUIRRELVM vm, SQInteger type, + const SQChar *sourcename, SQInteger line, const SQChar *funcname ); +#else + static SQInteger SQProfHook( HSQUIRRELVM vm ); +#endif +#endif +}; + +inline SQString *CreateSQString( SQDebugServer *dbg, const string_t &str ) +{ +#ifdef SQUNICODE + unsigned int len = SQUnicodeLength( str.ptr, str.len ) + 1; + SQChar *tmp = (SQChar*)dbg->ScratchPad( sq_rsl(len) ); + + if ( !tmp ) + return NULL; + + len = UTF8ToSQUnicode( tmp, sq_rsl(len), str.ptr, str.len ); + tmp[len] = 0; + + return SQString::Create( _ss(dbg->m_pRootVM), tmp, len ); +#else + return SQString::Create( _ss(dbg->m_pRootVM), str.ptr, str.len ); +#endif +} + +inline bool SQTable_Get( SQDebugServer *dbg, SQTable *table, const string_t &key, SQObjectPtr &val ) +{ +#ifdef SQUNICODE + unsigned int len = SQUnicodeLength( key.ptr, key.len ) + 1; + SQChar *tmp = (SQChar*)dbg->ScratchPad( sq_rsl(len) ); + + if ( !tmp ) + return NULL; + + len = UTF8ToSQUnicode( tmp, sq_rsl(len), key.ptr, key.len ); + tmp[len] = 0; + +#if SQUIRREL_VERSION_NUMBER >= 300 + return table->GetStr( tmp, len, val ); +#else + SQObjectPtr str = SQString::Create( table->_sharedstate, tmp, len ); + return table->Get( str, val ); +#endif +#else + (void)dbg; + return SQTable_Get( table, key, val ); +#endif +} + +static inline void sqdbg_get_debugger_ref( HSQUIRRELVM vm, SQObjectPtr &ref ); + +void SQDebugServer::Attach( HSQUIRRELVM vm ) +{ + if ( m_pRootVM ) + { + if ( m_pRootVM == _thread(_ss(vm)->_root_vm) ) + { + Print(_SC("(sqdbg) Debugger is already attached to this VM\n")); + } + else + { + Print(_SC("(sqdbg) Debugger is already attached to another VM\n")); + } + + return; + } + + m_pRootVM = _thread(_ss(vm)->_root_vm); + m_pCurVM = vm; + + Assert( m_Threads.size() == 0 ); + ThreadToID( m_pRootVM ); + +#if SQUIRREL_VERSION_NUMBER >= 300 + m_Print = sq_getprintfunc( m_pRootVM ); + m_PrintError = sq_geterrorfunc( m_pRootVM ); +#else + m_Print = sq_getprintfunc( m_pRootVM ); + m_PrintError = m_Print; +#endif + + Assert( m_Print && m_PrintError ); + + m_ErrorHandler = m_pRootVM->_errorhandler; + + if ( sq_type(m_ErrorHandler) != OT_NULL ) + sq_addref( m_pRootVM, &m_ErrorHandler ); + + sq_enabledebuginfo( m_pRootVM, 1 ); + + SQString *cached = CreateSQString( m_pRootVM, _SC("sqdbg") ); + __ObjAddRef( cached ); + m_sqstrCallFrame = CreateSQString( m_pRootVM, _SC(KW_CALLFRAME) ); + m_sqstrDelegate = CreateSQString( m_pRootVM, _SC(KW_DELEGATE) ); +#ifdef CLOSURE_ROOT + m_sqstrRoot = CreateSQString( m_pRootVM, _SC(KW_ROOT) ); +#endif + +#if SQUIRREL_VERSION_NUMBER >= 300 + m_sqfnGet = SQNativeClosure::Create( _ss(m_pRootVM), SQMM_Get, 0 ); + m_sqfnSet = SQNativeClosure::Create( _ss(m_pRootVM), SQMM_Set, 0 ); + m_sqfnNewSlot = SQNativeClosure::Create( _ss(m_pRootVM), SQMM_NewSlot, 0 ); +#else + m_sqfnGet = SQNativeClosure::Create( _ss(m_pRootVM), SQMM_Get ); + m_sqfnSet = SQNativeClosure::Create( _ss(m_pRootVM), SQMM_Set ); + m_sqfnNewSlot = SQNativeClosure::Create( _ss(m_pRootVM), SQMM_NewSlot ); +#endif + _nativeclosure(m_sqfnGet)->_nparamscheck = 2; + _nativeclosure(m_sqfnSet)->_nparamscheck = 3; + _nativeclosure(m_sqfnNewSlot)->_nparamscheck = 3; + + InitEnv_GetVal( m_EnvGetVal ); + sq_addref( m_pRootVM, &m_EnvGetVal ); + + { + CStackCheck stackcheck( m_pRootVM ); + + SQObjectPtr ref; + sqdbg_get_debugger_ref( m_pRootVM, ref ); + + sq_pushroottable( m_pRootVM ); + + sq_pushstring( m_pRootVM, _SC("sqdbg_define_class"), STRLEN("sqdbg_define_class") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQDefineClass, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_define_class") ); + sq_setparamscheck( m_pRootVM, 3, _SC(".yt") ); + sq_newslot( m_pRootVM, -3, SQFalse ); + + sq_pushstring( m_pRootVM, _SC("sqdbg_disassemble"), STRLEN("sqdbg_disassemble") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQPrintDisassembly, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_disassemble") ); + sq_setparamscheck( m_pRootVM, 2, _SC(".c") ); + sq_newslot( m_pRootVM, -3, SQFalse ); + + sq_pushstring( m_pRootVM, _SC("sqdbg_break"), STRLEN("sqdbg_break") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQBreak, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_break") ); + sq_setparamscheck( m_pRootVM, 1, NULL ); + sq_newslot( m_pRootVM, -3, SQFalse ); + +#ifndef SQDBG_DISABLE_COMPILER + sq_pushstring( m_pRootVM, _SC("sqdbg_watch"), STRLEN("sqdbg_watch") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQAddDataBreakpoint, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_watch") ); + sq_setparamscheck( m_pRootVM, -2, _SC(".ssn") ); + sq_newslot( m_pRootVM, -3, SQFalse ); +#endif + +#ifndef SQDBG_DISABLE_PROFILER + sq_pushstring( m_pRootVM, _SC("sqdbg_prof_start"), STRLEN("sqdbg_prof_start") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQProfStart, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_prof_start") ); + sq_setparamscheck( m_pRootVM, 1, NULL ); + sq_newslot( m_pRootVM, -3, SQFalse ); + + sq_pushstring( m_pRootVM, _SC("sqdbg_prof_stop"), STRLEN("sqdbg_prof_stop") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQProfStop, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_prof_stop") ); + sq_setparamscheck( m_pRootVM, 1, NULL ); + sq_newslot( m_pRootVM, -3, SQFalse ); + + sq_pushstring( m_pRootVM, _SC("sqdbg_prof_pause"), STRLEN("sqdbg_prof_pause") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQProfPause, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_prof_pause") ); + sq_setparamscheck( m_pRootVM, 1, NULL ); + sq_newslot( m_pRootVM, -3, SQFalse ); + + sq_pushstring( m_pRootVM, _SC("sqdbg_prof_resume"), STRLEN("sqdbg_prof_resume") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQProfResume, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_prof_resume") ); + sq_setparamscheck( m_pRootVM, 1, NULL ); + sq_newslot( m_pRootVM, -3, SQFalse ); + + sq_pushstring( m_pRootVM, _SC("sqdbg_prof_reset"), STRLEN("sqdbg_prof_reset") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQProfReset, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_prof_reset") ); + sq_setparamscheck( m_pRootVM, -1, _SC(".v|ss") ); + sq_newslot( m_pRootVM, -3, SQFalse ); + + sq_pushstring( m_pRootVM, _SC("sqdbg_prof_begin"), STRLEN("sqdbg_prof_begin") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQProfGroupBegin, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_prof_begin") ); + sq_setparamscheck( m_pRootVM, 2, _SC(".s") ); + sq_newslot( m_pRootVM, -3, SQFalse ); + + sq_pushstring( m_pRootVM, _SC("sqdbg_prof_end"), STRLEN("sqdbg_prof_end") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQProfGroupEnd, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_prof_end") ); + sq_setparamscheck( m_pRootVM, 1, NULL ); + sq_newslot( m_pRootVM, -3, SQFalse ); + + sq_pushstring( m_pRootVM, _SC("sqdbg_prof_gets"), STRLEN("sqdbg_prof_gets") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQProfGets, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_prof_gets") ); + sq_setparamscheck( m_pRootVM, -1, _SC(".v|i|si|s") ); + sq_newslot( m_pRootVM, -3, SQFalse ); + + sq_pushstring( m_pRootVM, _SC("sqdbg_prof_print"), STRLEN("sqdbg_prof_print") ); + sq_pushobject( m_pRootVM, ref ); + sq_newclosure( m_pRootVM, &SQDebugServer::SQProfPrint, 1 ); + sq_setnativeclosurename( m_pRootVM, -1, _SC("sqdbg_prof_print") ); + sq_setparamscheck( m_pRootVM, -2, _SC(".v|i|si|s") ); + sq_newslot( m_pRootVM, -3, SQFalse ); +#endif + + sq_pop( m_pRootVM, 1 ); + } + + Print(_SC("(sqdbg) [%d] Attached\n"), SQDBG_SV_VER); +} + +#define FOREACH_THREAD_BEGIN( _vm ) \ + for ( unsigned int i = m_Threads.size(); i--; ) \ + { \ + SQWeakRef *wr = m_Threads[i]; \ + if ( wr && sq_type(wr->_obj) == OT_THREAD ) \ + { \ + HSQUIRRELVM _vm = _thread(wr->_obj); + +#define FOREACH_THREAD_END() \ + } \ + } + +void SQDebugServer::SetErrorHandler( bool state ) +{ + FOREACH_THREAD_BEGIN( vm ) + if ( state ) + { + sq_newclosure( vm, &SQErrorHandler, 0 ); +#ifdef SQDBG_CALL_DEFAULT_ERROR_HANDLER + sq_setnativeclosurename( vm, -1, _SC("sqdbg") ); +#endif + sq_seterrorhandler( vm ); + } + else + { + sq_pushobject( vm, m_ErrorHandler ); + sq_seterrorhandler( vm ); + } + FOREACH_THREAD_END() +} + +#ifdef NATIVE_DEBUG_HOOK +#ifdef DEBUG_HOOK_CACHED_SQDBG +static inline void sqdbg_set_debugger_cached_debughook( HSQUIRRELVM vm, bool state ); +#else +#define sqdbg_set_debugger_cached_debughook( vm, state ) (void)0 +#endif +#endif + +void SQDebugServer::DoSetDebugHook( HSQUIRRELVM vm, _SQDEBUGHOOK fn ) +{ +#ifdef NATIVE_DEBUG_HOOK + sq_setnativedebughook( vm, fn ); + sqdbg_set_debugger_cached_debughook( vm, fn != NULL ); +#else + if ( fn ) + { + SQObjectPtr ref; + sqdbg_get_debugger_ref( vm, ref ); + sq_pushobject( vm, ref ); + sq_newclosure( vm, fn, 1 ); + } + else + { + sq_pushnull( vm ); + } + + sq_setdebughook( vm ); +#endif +} + +void SQDebugServer::SetDebugHook( _SQDEBUGHOOK fn ) +{ + FOREACH_THREAD_BEGIN( vm ) + DoSetDebugHook( vm, fn ); + FOREACH_THREAD_END() +} + +bool SQDebugServer::ListenSocket( unsigned short port ) +{ + Assert( m_pRootVM ); + + if ( m_Server.IsListening() ) + { + port = m_Server.GetServerPort(); + + if ( port ) + { + Print(_SC("(sqdbg) Socket already open on port %d\n"), port); + } + else + { + Print(_SC("(sqdbg) Socket already open\n")); + } + + return true; + } + + if ( !m_Server.ListenSocket( port ) ) + { + PrintLastServerMessage(); + return false; + } + + port = m_Server.GetServerPort(); + + Print(_SC("(sqdbg) Listening for connections on port %d\n"), port); + return true; +} + +void SQDebugServer::Shutdown() +{ + if ( !m_pRootVM ) + return; + + Print(_SC("(sqdbg) Shutdown\n")); + + if ( IsClientConnected() ) + { + m_Server.Execute< SQDebugServer, &SQDebugServer::OnMessageReceived >( this ); + + DAP_START_EVENT( ++m_Sequence, "terminated" ); + DAP_SEND(); + } + + m_Server.Shutdown(); + +#ifndef SQDBG_DISABLE_PROFILER + if ( IsProfilerEnabled() ) + ProfStop(); + + Assert( m_Profilers.size() == 0 ); + m_Profilers.purge(); +#endif + + SetErrorHandler( false ); + SetDebugHook( NULL ); + + m_State = ThreadState_Running; + m_Sequence = 0; + m_nBreakpointIndex = 0; + m_nVarRefIndex = 0; + m_nCalls = 0; + + m_bInREPL = false; + m_bDebugHookGuardAlways = false; + m_bDebugHookGuard = false; +#if SQUIRREL_VERSION_NUMBER < 300 + m_bInDebugHook = false; +#endif + m_bExceptionPause = false; + m_pPausedThread = NULL; + + RemoveReturnValues(); + RemoveVarRefs( true ); + RemoveLockedWatches(); + RemoveAllBreakpoints(); + RemoveDataBreakpoints(); + + RestoreCachedInstructions(); + ClearCachedInstructions(); + + m_CachedInstructions.purge(); + m_ReturnValues.purge(); + m_Breakpoints.purge(); + m_DataWatches.purge(); + + RemoveThreads(); + RemoveClassDefs(); + RemoveScripts(); + m_FrameIDs.purge(); + m_FilePathMap.Clear( &m_Strings ); + + m_SendBuf.purge(); + m_ReadBuf.Free(); + m_Scratch.Free(); + m_VarMemberCache.Free(); + m_Strings.Free(); + + sq_release( m_pRootVM, &m_EnvGetVal ); + m_EnvGetVal.Null(); + + m_sqfnGet.Null(); + m_sqfnSet.Null(); + m_sqfnNewSlot.Null(); + + SQString *cached = CreateSQString( m_pRootVM, _SC("sqdbg") ); + __ObjRelease( cached ); + m_sqstrCallFrame.Null(); + m_sqstrDelegate.Null(); +#ifdef CLOSURE_ROOT + m_sqstrRoot.Null(); +#endif + + if ( sq_type(m_ErrorHandler) != OT_NULL ) + { + sq_release( m_pRootVM, &m_ErrorHandler ); + m_ErrorHandler.Null(); + } + +#if SQUIRREL_VERSION_NUMBER >= 300 + sq_setprintfunc( m_pRootVM, m_Print, m_PrintError ); +#else + sq_setprintfunc( m_pRootVM, m_Print ); +#endif + + m_Print = m_PrintError = NULL; + + sq_enabledebuginfo( m_pRootVM, 0 ); + sq_notifyallexceptions( m_pRootVM, 0 ); + + m_pRootVM = m_pCurVM = NULL; +} + +void SQDebugServer::DisconnectClient() +{ + if ( IsClientConnected() ) + { + Print(_SC("(sqdbg) Client disconnected\n")); + + DAP_START_EVENT( ++m_Sequence, "terminated" ); + DAP_SEND(); + } + + m_Server.DisconnectClient(); + + SetErrorHandler( false ); +#ifndef SQDBG_DISABLE_PROFILER + SetDebugHook( IsProfilerEnabled() ? &SQProfHook : NULL ); +#else + SetDebugHook( NULL ); +#endif + + m_State = ThreadState_Running; + m_Sequence = 0; + m_nBreakpointIndex = 0; + m_nVarRefIndex = 0; + m_nCalls = 0; + + m_bInREPL = false; + m_bDebugHookGuardAlways = false; + m_bDebugHookGuard = false; +#if SQUIRREL_VERSION_NUMBER < 300 + m_bInDebugHook = false; +#endif + m_bExceptionPause = false; + m_pPausedThread = NULL; + + RemoveReturnValues(); + RemoveVarRefs( true ); + RemoveLockedWatches(); + RemoveAllBreakpoints(); + RemoveDataBreakpoints(); + + RestoreCachedInstructions(); + ClearCachedInstructions(); + + m_CachedInstructions.purge(); + m_ReturnValues.purge(); + m_Breakpoints.purge(); + m_DataWatches.purge(); + + m_SendBuf.purge(); + m_ReadBuf.Free(); + m_Scratch.Free(); + m_VarMemberCache.Free(); + + ClearEnvDelegate( m_EnvGetVal ); + +#if SQUIRREL_VERSION_NUMBER >= 300 + sq_setprintfunc( m_pRootVM, m_Print, m_PrintError ); +#else + sq_setprintfunc( m_pRootVM, m_Print ); +#endif +} + +void SQDebugServer::OnClientConnected( const char *addr ) +{ + Print(_SC("(sqdbg) Client connected from " FMT_CSTR "\n"), addr); + +#if SQUIRREL_VERSION_NUMBER >= 300 + sq_setprintfunc( m_pRootVM, SQPrint, SQError ); +#else + sq_setprintfunc( m_pRootVM, SQPrint ); +#endif + + SetErrorHandler( true ); + SetDebugHook( &SQDebugHook ); + + // Validate if user has manually ruined it + InitEnv_GetVal( m_EnvGetVal ); + +#define _check( var, size ) \ + if ( var.capacity() < size ) \ + var.reserve( size ); + + _check( m_ReturnValues, 1 ); + _check( m_Vars, 64 ); + _check( m_FrameIDs, 8 ); + + m_SendBuf.reserve( 16384 ); +#undef _check +} + +void SQDebugServer::Frame() +{ + if ( m_Server.IsClientConnected() ) + { + Recv(); + Parse(); + m_Server.Execute< SQDebugServer, &SQDebugServer::OnMessageReceived >( this ); + } + else if ( m_Server.Listen() ) + { + OnClientConnected( m_Server.m_pszLastMsg ); + m_Server.m_pszLastMsg = NULL; + } +} + +#define GET_OR_FAIL( _base, _val ) \ + if ( !(_base).Get( #_val, &_val ) ) \ + { \ + PrintError(_SC("(sqdbg) invalid DAP message, could not find '" #_val "'\n")); \ + DisconnectClient(); \ + return; \ + } + +#define GET_OR_ERROR_RESPONSE( _cmd, _base, _val ) \ + if ( !(_base).Get( #_val, &_val ) ) \ + { \ + PrintError(_SC("(sqdbg) invalid DAP message, could not find '" #_val "'\n")); \ + DAP_ERROR_RESPONSE( seq, _cmd ); \ + DAP_ERROR_BODY( 0, "invalid DAP message" ); \ + DAP_SEND(); \ + return; \ + } + +void SQDebugServer::OnMessageReceived( char *ptr, int len ) +{ + json_table_t table; + JSONParser parser( &m_ReadBuf, ptr, len, &table ); + + if ( parser.GetError() ) + { + PrintError(_SC("(sqdbg) Invalid JSON : " FMT_CSTR "\n"), parser.GetError()); + AssertMsg1( 0, "Invalid JSON : %s", parser.GetError() ); + DisconnectClient(); + return; + } + + string_t type; + table.GetString( "type", &type ); + + if ( type.IsEqualTo( "request" ) ) + { + int seq; + GET_OR_FAIL( table, seq ); + + ProcessRequest( table, seq ); + } + else if ( type.IsEqualTo( "response" ) ) + { + int request_seq; + GET_OR_FAIL( table, request_seq ); + + string_t command; + table.GetString( "command", &command ); + + PrintError(_SC("(sqdbg) Unrecognised response '" FMT_CSTR "'\n"), command.ptr); + AssertMsg1( 0, "Unrecognised response '%s'", command.ptr ); + } + else if ( type.IsEqualTo( "event" ) ) + { + string_t event; + table.GetString( "event", &event ); + + PrintError(_SC("(sqdbg) Unrecognised event '" FMT_CSTR "'\n"), event.ptr); + AssertMsg1( 0, "Unrecognised event '%s'", event.ptr ); + } + else + { + PrintError(_SC("(sqdbg) Invalid DAP type '" FMT_CSTR "'\n"), type.ptr); + AssertMsg1( 0, "Invalid DAP type '%s'", type.ptr ); + } + + m_ReadBuf.Release(); +} + +void SQDebugServer::ProcessRequest( const json_table_t &table, int seq ) +{ + string_t command; + table.GetString( "command", &command ); + + if ( command.IsEqualTo( "setBreakpoints" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "setBreakpoints", table, arguments ); + + OnRequest_SetBreakpoints( *arguments, seq ); + } + else if ( command.IsEqualTo( "setFunctionBreakpoints" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "setFunctionBreakpoints", table, arguments ); + + OnRequest_SetFunctionBreakpoints( *arguments, seq ); + } + else if ( command.IsEqualTo( "setExceptionBreakpoints" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "setExceptionBreakpoints", table, arguments ); + + OnRequest_SetExceptionBreakpoints( *arguments, seq ); + } + else if ( command.IsEqualTo( "setDataBreakpoints" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "setDataBreakpoints", table, arguments ); + + OnRequest_SetDataBreakpoints( *arguments, seq ); + } + else if ( command.IsEqualTo( "dataBreakpointInfo" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "dataBreakpointInfo", table, arguments ); + + OnRequest_DataBreakpointInfo( *arguments, seq ); + } + else if ( command.IsEqualTo( "evaluate" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "evaluate", table, arguments ); + + OnRequest_Evaluate( *arguments, seq ); + } +#ifndef SQDBG_DISABLE_COMPILER + else if ( command.IsEqualTo( "completions" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "completions", table, arguments ); + + OnRequest_Completions( *arguments, seq ); + } +#endif + else if ( command.IsEqualTo( "scopes" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "scopes", table, arguments ); + + OnRequest_Scopes( *arguments, seq ); + } + else if ( command.IsEqualTo( "threads" ) ) + { + OnRequest_Threads( seq ); + } + else if ( command.IsEqualTo( "stackTrace" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "stackTrace", table, arguments ); + + OnRequest_StackTrace( *arguments, seq ); + } + else if ( command.IsEqualTo( "variables" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "variables", table, arguments ); + + OnRequest_Variables( *arguments, seq ); + } + else if ( command.IsEqualTo( "setVariable" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "setVariable", table, arguments ); + + OnRequest_SetVariable( *arguments, seq ); + } + else if ( command.IsEqualTo( "setExpression" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "setExpression", table, arguments ); + + OnRequest_SetExpression( *arguments, seq ); + } + else if ( command.IsEqualTo( "setHitCount" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "setHitCount", table, arguments ); + + int breakpointId, hitCount; + arguments->GetInt( "breakpointId", &breakpointId ); + arguments->GetInt( "hitCount", &hitCount ); + + if ( hitCount < 0 ) + hitCount = 0; + + if ( breakpointId > 0 && breakpointId < m_nBreakpointIndex ) + { +#define _check( vec, type ) \ + for ( unsigned int i = 0; i < vec.size(); i++ ) \ + { \ + type &bp = vec[i]; \ + if ( bp.id == breakpointId ) \ + { \ + bp.hits = hitCount; \ + DAP_START_RESPONSE( seq, "setHitCount" ); \ + DAP_SEND(); \ + return; \ + } \ + } + + _check( m_Breakpoints, breakpoint_t ); + _check( m_DataWatches, datawatch_t ); +#undef _check + } + + DAP_ERROR_RESPONSE( seq, "setHitCount" ); + DAP_ERROR_BODY( 0, "invalid breakpoint {id}" ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetIntString( "id", breakpointId ); + DAP_SEND(); + } + else if ( command.IsEqualTo( "source" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "source", table, arguments ); + + json_table_t *source; + if ( arguments->GetTable( "source", &source ) ) + { + string_t srcname; + + if ( ( !source->GetString( "name", &srcname ) || srcname.IsEmpty() ) && + source->GetString( "path", &srcname ) ) + { + StripFileName( &srcname.ptr, &srcname.len ); + } + + script_t *scr = GetScript( srcname ); + if ( scr ) + { + DAP_START_RESPONSE( seq, "source" ); + DAP_SET_TABLE( body ); + body.SetString( "content", { scr->scriptptr, scr->scriptlen } ); + DAP_SEND(); + return; + } + } + + DAP_ERROR_RESPONSE( seq, "source" ); + DAP_SEND(); + } + else if ( command.IsEqualTo( "disassemble" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "disassemble", table, arguments ); + + OnRequest_Disassemble( *arguments, seq ); + } +#ifdef SUPPORTS_RESTART_FRAME + else if ( command.IsEqualTo( "restartFrame" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "restartFrame", table, arguments ); + + if ( m_bExceptionPause ) + { + DAP_START_RESPONSE( seq, "restartFrame" ); + DAP_SEND(); + + Continue( m_pCurVM ); + return; + } + +#ifndef SQDBG_DISABLE_PROFILER + // HACKHACK: Re-validate current profiler + if ( IsProfilerEnabled() ) + ProfSwitchThread( m_pCurVM ); +#endif + + OnRequest_RestartFrame( *arguments, seq ); + + RestoreCachedInstructions(); + ClearCachedInstructions(); + + RemoveReturnValues(); + } +#endif + else if ( command.IsEqualTo( "gotoTargets" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "gotoTargets", table, arguments ); + + OnRequest_GotoTargets( *arguments, seq ); + } + else if ( command.IsEqualTo( "goto" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "goto", table, arguments ); + + if ( m_bExceptionPause ) + { + DAP_START_RESPONSE( seq, "goto" ); + DAP_SEND(); + + Continue( m_pCurVM ); + return; + } + +#ifndef SQDBG_DISABLE_PROFILER + // HACKHACK: Re-validate current profiler + if ( IsProfilerEnabled() ) + ProfSwitchThread( m_pCurVM ); +#endif + + OnRequest_Goto( *arguments, seq ); + + RestoreCachedInstructions(); + ClearCachedInstructions(); + + RemoveReturnValues(); + } + else if ( command.IsEqualTo( "next" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "next", table, arguments ); + + if ( m_bExceptionPause ) + { + DAP_START_RESPONSE( seq, "next" ); + DAP_SEND(); + + Continue( m_pCurVM ); + return; + } + +#ifndef SQDBG_DISABLE_PROFILER + // HACKHACK: Re-validate current profiler + if ( IsProfilerEnabled() ) + ProfSwitchThread( m_pCurVM ); +#endif + + RestoreCachedInstructions(); + ClearCachedInstructions(); + + RemoveReturnValues(); + + OnRequest_Next( *arguments, seq ); + } + else if ( command.IsEqualTo( "stepIn" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "stepIn", table, arguments ); + + if ( m_bExceptionPause ) + { + DAP_START_RESPONSE( seq, "stepIn" ); + DAP_SEND(); + + Continue( m_pCurVM ); + return; + } + +#ifndef SQDBG_DISABLE_PROFILER + // HACKHACK: Re-validate current profiler + if ( IsProfilerEnabled() ) + ProfSwitchThread( m_pCurVM ); +#endif + + RestoreCachedInstructions(); + ClearCachedInstructions(); + + RemoveReturnValues(); + + OnRequest_StepIn( *arguments, seq ); + } + else if ( command.IsEqualTo( "stepOut" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "stepOut", table, arguments ); + + if ( m_bExceptionPause ) + { + DAP_START_RESPONSE( seq, "stepOut" ); + DAP_SEND(); + + Continue( m_pCurVM ); + return; + } + +#ifndef SQDBG_DISABLE_PROFILER + // HACKHACK: Re-validate current profiler + if ( IsProfilerEnabled() ) + ProfSwitchThread( m_pCurVM ); +#endif + + RestoreCachedInstructions(); + ClearCachedInstructions(); + + RemoveReturnValues(); + + OnRequest_StepOut( *arguments, seq ); + } + else if ( command.IsEqualTo( "continue" ) ) + { + DAP_START_RESPONSE( seq, "continue" ); + DAP_SET_TABLE( body ); + body.SetBool( "allThreadsContinued", true ); + DAP_SEND(); + + Continue( m_pCurVM ); + } + else if ( command.IsEqualTo( "pause" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "pause", table, arguments ); + + int threadId; + arguments->GetInt( "threadId", &threadId, -1 ); + + HSQUIRRELVM vm = ThreadFromID( threadId ); + + if ( vm ) + { + DAP_START_RESPONSE( seq, "pause" ); + DAP_SEND(); + + if ( m_State != ThreadState_Suspended ) + { + if ( m_pPausedThread ) + { + RestoreCachedInstructions(); + ClearCachedInstructions(); + } + + m_pPausedThread = vm; + } + } + else + { + DAP_ERROR_RESPONSE( seq, "pause" ); + DAP_ERROR_BODY( 0, "invalid thread" ); + DAP_SEND(); + } + } + else if ( command.IsEqualTo( "attach" ) ) + { + Print(_SC("(sqdbg) Client attached\n")); + + DAP_START_RESPONSE( seq, "attach" ); + DAP_SEND(); + + DAP_START_EVENT( seq, "process" ); + DAP_SET_TABLE( body ); + body.SetString( "name", "" ); + body.SetString( "startMethod", "attach" ); + body.SetInt( "pointerSize", (int)sizeof(void*) ); + DAP_SEND(); + } + else if ( command.IsEqualTo( "disconnect" ) || command.IsEqualTo( "terminate" ) ) + { + DAP_START_RESPONSE( seq, command ); + DAP_SEND(); + + DisconnectClient(); + } + else if ( command.IsEqualTo( "initialize" ) ) + { + json_table_t *arguments; + GET_OR_ERROR_RESPONSE( "initialize", table, arguments ); + + OnRequest_Initialize( *arguments, seq ); + } + else if ( command.IsEqualTo( "configurationDone" ) ) + { + DAP_START_RESPONSE( seq, "configurationDone" ); + DAP_SEND(); + } + else + { + DAP_ERROR_RESPONSE( seq, command ); + DAP_ERROR_BODY( 0, "Unrecognised request '{command}'" ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetString( "command", command ); + DAP_SEND(); + AssertMsg1( 0, "Unrecognised request '%s'", command.ptr ); + } +} + +void SQDebugServer::OnScriptCompile( const SQChar *script, unsigned int scriptlen, + const SQChar *sourcename, unsigned int sourcenamelen ) +{ + if ( !script || !scriptlen || !sourcename || !sourcenamelen ) + return; + +#ifdef SQDBG_SOURCENAME_HAS_PATH + StripFileName( &sourcename, &sourcenamelen ); +#endif + +#ifdef SQUNICODE + unsigned int size = UTF8Length( sourcename, sourcenamelen ); + stringbufext_t source = ScratchPadBuf( size ); + source.Puts( { sourcename, sourcenamelen } ); +#else + string_t source; + source.Assign( sourcename, sourcenamelen ); +#endif + + script_t *scr = GetScript( source ); + + unsigned int scriptbufsize = ALIGN( scstombslen( script, scriptlen ), 64 ); + + if ( !scr ) + { + scr = &m_Scripts.append(); + scr->sourceptr = m_Strings.Alloc( source.len, NULL, false ); + scr->scriptptr = m_Strings.Alloc( scriptbufsize, NULL, false ); + + if ( scr->sourceptr ) + { + memcpy( scr->sourceptr, source.ptr, source.len ); + scr->sourcelen = source.len; + } + } + else + { + unsigned int oldsize = ALIGN( scr->scriptlen, 64 ); + if ( oldsize != scriptbufsize ) + { + m_Strings.Free( scr->scriptptr ); + scr->scriptptr = m_Strings.Alloc( scriptbufsize, NULL, false ); + } + } + + if ( scr->scriptptr ) + { + scr->scriptlen = scstombs( scr->scriptptr, scriptbufsize, script, scriptlen ); + } +} + +script_t *SQDebugServer::GetScript( const string_t &source ) +{ + for ( unsigned int i = 0; i < m_Scripts.size(); i++ ) + { + script_t &scr = m_Scripts[i]; + if ( source.IsEqualTo( scr.sourceptr, scr.sourcelen ) ) + return &scr; + } + + return NULL; +} + +void SQDebugServer::RemoveScripts() +{ + for ( unsigned int i = 0; i < m_Scripts.size(); i++ ) + { + script_t &scr = m_Scripts[i]; + m_Strings.Free( scr.sourceptr ); + m_Strings.Free( scr.scriptptr ); + } + + m_Scripts.purge(); +} + +void SQDebugServer::OnRequest_Initialize( const json_table_t &arguments, int seq ) +{ + string_t clientID, clientName; + arguments.GetString( "clientID", &clientID, "" ); + arguments.GetString( "clientName", &clientName, "" ); + + if ( clientName.IsEqualTo( clientID ) ) + { + Print(_SC("(sqdbg) Client initialised: " FMT_CSTR "\n"), + clientName.ptr); + } + else + { + Print(_SC("(sqdbg) Client initialised: " FMT_CSTR " (" FMT_CSTR ")\n"), + clientName.ptr, clientID.ptr); + } + +#ifndef SQDBG_DISABLE_COMPILER + arguments.GetBool( "columnsStartAt1", &m_bClientColumnOffset ); +#endif + + DAP_START_RESPONSE( seq, "initialize" ); + DAP_SET_TABLE( body ); + body.SetBool( "supportsConfigurationDoneRequest", true ); + body.SetBool( "supportsFunctionBreakpoints", true ); + body.SetBool( "supportsConditionalBreakpoints", true ); + body.SetBool( "supportsHitConditionalBreakpoints", true ); + body.SetBool( "supportsEvaluateForHovers", true ); + { + wjson_array_t exceptionBreakpointFilters = body.SetArray( "exceptionBreakpointFilters" ); + { + wjson_table_t filter = exceptionBreakpointFilters.AppendTable(); + filter.SetString( "filter", "unhandled" ); + filter.SetString( "label", "Unhandled exceptions" ); + filter.SetString( "description", "Break on uncaught exceptions" ); + filter.SetBool( "default", true ); + } + { + wjson_table_t filter = exceptionBreakpointFilters.AppendTable(); + filter.SetString( "filter", "all" ); + filter.SetString( "label", "All exceptions" ); + filter.SetString( "description", "Break on both caught and uncaught exceptions" ); + } + } + body.SetBool( "supportsSetVariable", true ); +#ifdef SUPPORTS_RESTART_FRAME + body.SetBool( "supportsRestartFrame", true ); +#endif + body.SetBool( "supportsGotoTargetsRequest", true ); +#ifndef SQDBG_DISABLE_COMPILER + body.SetBool( "supportsCompletionsRequest", true ); +#endif + body.SetBool( "supportsSetExpression", true ); + body.SetBool( "supportsSetHitCount", true ); + { + wjson_array_t a = body.SetArray( "supportedChecksumAlgorithms" ); + } + body.SetBool( "supportsValueFormattingOptions", true ); + body.SetBool( "supportsDelayedStackTraceLoading", true ); + body.SetBool( "supportsLogPoints", true ); + body.SetBool( "supportsTerminateRequest", true ); + body.SetBool( "supportsDataBreakpoints", true ); + body.SetBool( "supportsDisassembleRequest", true ); + body.SetBool( "supportsSteppingGranularity", true ); + DAP_SEND(); + + DAP_START_EVENT( seq, "initialized" ); + DAP_SEND(); +} + +void SQDebugServer::SetSource( wjson_table_t &source, SQString *sourcename ) +{ + sqstring_t srcname; + srcname.Assign( sourcename ); + +#ifdef SQDBG_SOURCENAME_HAS_PATH + StripFileName( &srcname.ptr, &srcname.len ); +#endif + +#ifdef SQUNICODE + stringbufext_t strName = ScratchPadBuf( UTF8Length( srcname.ptr, srcname.len ) ); + strName.Puts( srcname ); + + CFilePathMap::pair_t *pair = m_FilePathMap.Get( strName ); +#else + CFilePathMap::pair_t *pair = m_FilePathMap.Get( srcname ); +#endif + + if ( pair ) + { + source.SetString( "path", pair->path ); +#ifdef SQUNICODE + source.SetString( "name", pair->name ); +#else + source.SetString( "name", srcname ); +#endif + } + else + { +#ifdef SQUNICODE + source.SetString( "name", strName ); +#else + source.SetString( "name", srcname ); +#endif + } +} + +void SQDebugServer::OnRequest_SetBreakpoints( const json_table_t &arguments, int seq ) +{ + json_array_t *breakpoints; + json_table_t *source; + + GET_OR_ERROR_RESPONSE( "setBreakpoints", arguments, breakpoints ); + GET_OR_ERROR_RESPONSE( "setBreakpoints", arguments, source ); + + string_t srcname, srcpath; + source->GetString( "path", &srcpath ); + + if ( ( !source->GetString( "name", &srcname ) || srcname.IsEmpty() ) && + !srcpath.IsEmpty() ) + { + srcname = srcpath; + StripFileName( &srcname.ptr, &srcname.len ); + } + + if ( !srcname.IsEmpty() && !srcpath.IsEmpty() ) + { + m_FilePathMap.Add( &m_Strings, srcname, srcpath ); + } + else if ( srcname.IsEmpty() && srcpath.IsEmpty() ) + { + DAP_ERROR_RESPONSE( seq, "setBreakpoints" ); + DAP_ERROR_BODY( 0, "invalid source" ); + DAP_SEND(); + return; + } + + RemoveBreakpoints( srcname ); + + DAP_START_RESPONSE( seq, "setBreakpoints" ); + DAP_SET_TABLE( body ); + wjson_array_t obps = body.SetArray( "breakpoints" ); + + for ( int i = 0; i < breakpoints->size(); i++ ) + { + json_table_t *bp; + + if ( !breakpoints->GetTable( i, &bp ) ) + { + wjson_table_t obp = obps.AppendTable(); + obp.SetBool( "verified", false ); + obp.SetString( "reason", "failed" ); + obp.SetString( "message", "invalid" ); + continue; + } + + int line, hitsTarget = 0; + string_t condition, hitCondition, logMessage; + + bp->GetInt( "line", &line ); + bp->GetString( "condition", &condition ); + bp->GetString( "logMessage", &logMessage ); + + if ( bp->GetString( "hitCondition", &hitCondition ) && !hitCondition.IsEmpty() ) + { + strtoint( hitCondition, &hitsTarget ); + + if ( hitsTarget < 0 ) + hitsTarget = 0; + } + + int id = AddBreakpoint( line, srcname, condition, hitsTarget, logMessage ); + + wjson_table_t obp = obps.AppendTable(); + obp.SetBool( "verified", ISVALID_ID(id) ); + + if ( ISVALID_ID(id) ) + { + obp.SetInt( "id", id ); + obp.SetInt( "line", line ); + } + else + { + obp.SetString( "reason", "failed" ); + obp.SetString( "message", GetValue( m_pCurVM->_lasterror, kFS_NoQuote ) ); + } + } + + DAP_SEND(); + +#ifdef _DEBUG + for ( unsigned int i = 0; i < m_nFunctionBreakpointsIdx; i++ ) + Assert( m_Breakpoints[i].line != 0 ); + + for ( unsigned int i = m_nFunctionBreakpointsIdx; i < m_Breakpoints.size(); i++ ) + Assert( m_Breakpoints[i].line == 0 ); +#endif +} + +void SQDebugServer::OnRequest_SetFunctionBreakpoints( const json_table_t &arguments, int seq ) +{ + json_array_t *breakpoints; + GET_OR_ERROR_RESPONSE( "setFunctionBreakpoints", arguments, breakpoints ); + + RemoveFunctionBreakpoints(); + + DAP_START_RESPONSE( seq, "setFunctionBreakpoints" ); + DAP_SET_TABLE( body ); + wjson_array_t obps = body.SetArray( "breakpoints" ); + + for ( int i = 0; i < breakpoints->size(); i++ ) + { + json_table_t *bp; + + if ( !breakpoints->GetTable( i, &bp ) ) + { + wjson_table_t obp = obps.AppendTable(); + obp.SetBool( "verified", false ); + obp.SetString( "reason", "failed" ); + obp.SetString( "message", "invalid" ); + continue; + } + + int hitsTarget = 0; + string_t name, condition, hitCondition, logMessage; + + bp->GetString( "name", &name ); + bp->GetString( "condition", &condition ); + bp->GetString( "logMessage", &logMessage ); + + if ( bp->GetString( "hitCondition", &hitCondition ) && !hitCondition.IsEmpty() ) + { + strtoint( hitCondition, &hitsTarget ); + + if ( hitsTarget < 0 ) + hitsTarget = 0; + } + + string_t funcsrc( "" ); + int line = 0; + + // function source: funcname,filename:line + for ( int j = name.len - 1; j > 1; j-- ) + { + if ( !line && name.ptr[j] == ':' ) + { + string_t sLine; + sLine.ptr = name.ptr + j + 1; + sLine.len = name.len - j - 1; + name.len = j; + + if ( !sLine.len || !atoi( sLine, &line ) || line <= 0 ) + line = -1; + } + else if ( name.ptr[j] == ',' ) + { + funcsrc.ptr = name.ptr + j + 1; + funcsrc.len = name.len - j - 1; + name.len = j; + break; + } + } + + if ( name.StartsWith( ANONYMOUS_FUNCTION_BREAKPOINT_NAME ) ) + name.Assign( "" ); + + int id = AddFunctionBreakpoint( name, funcsrc, line, condition, hitsTarget, logMessage ); + + wjson_table_t obp = obps.AppendTable(); + obp.SetBool( "verified", ISVALID_ID(id) ); + + if ( ISVALID_ID(id) ) + { + obp.SetInt( "id", id ); + } + else + { + obp.SetString( "reason", "failed" ); + obp.SetString( "message", GetValue( m_pCurVM->_lasterror, kFS_NoQuote ) ); + } + } + + DAP_SEND(); + +#ifdef _DEBUG + for ( unsigned int i = 0; i < m_nFunctionBreakpointsIdx; i++ ) + Assert( m_Breakpoints[i].line != 0 ); + + for ( unsigned int i = m_nFunctionBreakpointsIdx; i < m_Breakpoints.size(); i++ ) + Assert( m_Breakpoints[i].line == 0 ); +#endif +} + +void SQDebugServer::OnRequest_SetExceptionBreakpoints( const json_table_t &arguments, int seq ) +{ + bool bCaught = false, bUncaught = false; + + json_array_t *filters; + GET_OR_ERROR_RESPONSE( "setExceptionBreakpoints", arguments, filters ); + + for ( int i = 0; i < filters->size(); i++ ) + { + string_t filter; + + if ( !filters->GetString( i, &filter ) ) + continue; + + if ( filter.IsEqualTo( "unhandled" ) ) + { + bUncaught = true; + } + else if ( filter.IsEqualTo( "all" ) ) + { + bCaught = true; + } + } + + if ( filters->size() == 0 ) + { + m_bBreakOnExceptions = false; + sq_notifyallexceptions( m_pRootVM, 0 ); + } + else + { + m_bBreakOnExceptions = true; + + if ( bCaught ) + { + sq_notifyallexceptions( m_pRootVM, 1 ); + } + else + { + Assert( bUncaught ); + (void)bUncaught; + sq_notifyallexceptions( m_pRootVM, 0 ); + } + } + + DAP_START_RESPONSE( seq, "setExceptionBreakpoints" ); + DAP_SEND(); +} + +void SQDebugServer::OnRequest_SetDataBreakpoints( const json_table_t &arguments, int seq ) +{ + json_array_t *breakpoints; + GET_OR_ERROR_RESPONSE( "setDataBreakpoints", arguments, breakpoints ); + + RemoveDataBreakpoints(); + + DAP_START_RESPONSE( seq, "setDataBreakpoints" ); + DAP_SET_TABLE( body ); + wjson_array_t obps = body.SetArray( "breakpoints" ); + + for ( int i = 0; i < breakpoints->size(); i++ ) + { + json_table_t *bp; + + if ( !breakpoints->GetTable( i, &bp ) ) + { + wjson_table_t obp = obps.AppendTable(); + obp.SetBool( "verified", false ); + obp.SetString( "reason", "failed" ); + obp.SetString( "message", "invalid" ); + continue; + } + + int hitsTarget = 0; + string_t dataId, condition, hitCondition; + + bp->GetString( "dataId", &dataId ); + bp->GetString( "condition", &condition ); + + condition.Strip(); + + if ( bp->GetString( "hitCondition", &hitCondition ) && !hitCondition.IsEmpty() ) + { + strtoint( hitCondition, &hitsTarget ); + + if ( hitsTarget < 0 ) + hitsTarget = 0; + } + + int id = AddDataBreakpoint( m_pCurVM, m_pCurVM->ci, dataId, condition, hitsTarget ); + + wjson_table_t obp = obps.AppendTable(); + obp.SetBool( "verified", ISVALID_ID(id) ); + + if ( ISVALID_ID(id) ) + { + obp.SetInt( "id", id ); + } + else + { + obp.SetString( "reason", "failed" ); + obp.SetString( "message", GetValue( m_pCurVM->_lasterror, kFS_NoQuote ) ); + } + } + + DAP_SEND(); +} + +#ifndef MAX_DATA_WATCH_BUF_SIZE +#define MAX_DATA_WATCH_BUF_SIZE 512 +#endif +#define MAX_DATA_WATCH_NAME_LENGTH ( MAX_DATA_WATCH_BUF_SIZE - FMT_PTR_LEN - 32 ) + +void SQDebugServer::OnRequest_DataBreakpointInfo( const json_table_t &arguments, int seq ) +{ + int variablesReference; + string_t name; + + arguments.GetString( "name", &name ); + arguments.GetInt( "variablesReference", &variablesReference ); + + if ( name.len > MAX_DATA_WATCH_NAME_LENGTH ) + { + DAP_START_RESPONSE( seq, "dataBreakpointInfo" ); + DAP_SET_TABLE( body ); + body.SetNull( "dataId" ); + body.SetString( "description", "" ); + DAP_SEND(); + return; + } + + if ( variablesReference ) + { + objref_t obj; + SQObjectPtr dummy; + varref_t *ref = FromVarRef( variablesReference ); + + // don't modify name in GetObj + stringbuf_t< MAX_DATA_WATCH_BUF_SIZE > tmpbuf; + tmpbuf.Puts( name ); + string_t tmp = tmpbuf; + + if ( !ref || ref->type != VARREF_OBJ || + !GetObj_VarRef( ref, tmp, obj, dummy ) ) + { + DAP_START_RESPONSE( seq, "dataBreakpointInfo" ); + DAP_SET_TABLE( body ); + body.SetNull( "dataId" ); + body.SetString( "description", "" ); + DAP_SEND(); + return; + } + + DAP_START_RESPONSE( seq, "dataBreakpointInfo" ); + DAP_SET_TABLE( body ); + { + stringbuf_t< MAX_DATA_WATCH_BUF_SIZE > bufId; + + // 1:varref:name + bufId.Put('1'); + bufId.Put(':'); + bufId.PutInt( variablesReference ); + bufId.Put(':'); + bufId.Puts( name ); + + body.SetString( "dataId", bufId ); + } + { + stringbuf_t< MAX_DATA_WATCH_BUF_SIZE > bufName; + + bufName.Put('['); + bufName.PutHex( (uintptr_t)_refcounted(ref->GetVar()) ); + bufName.Put(' '); + bufName.Puts( GetType( ref->GetVar() ) ); + bufName.Put(']'); + bufName.Put('-'); + bufName.Put('>'); + bufName.Puts( name ); + + body.SetString( "description", bufName ); + } + wjson_array_t at = body.SetArray( "accessTypes" ); + at.Append( "write" ); + DAP_SEND(); + } + else + { +#ifndef SQDBG_DISABLE_COMPILER + // don't modify name in CCompiler::ParseString + stringbuf_t< MAX_DATA_WATCH_BUF_SIZE > tmpbuf; + tmpbuf.Puts( name ); + string_t tmp = tmpbuf; + + objref_t obj; + SQObjectPtr value; + ECompileReturnCode r = Evaluate( tmp, m_pCurVM, m_pCurVM->ci, value, obj ); + + // Check value again to see if the compiled expression was really a reference + SQObjectPtr val; + + if ( r != CompileReturnCode_Success || + obj.type == objref_t::INVALID || obj.type == objref_t::PTR || + !ISREFCOUNTED( sq_type(obj.src) ) || + !Get( obj, val ) || !IsEqual( val, value ) ) + { + DAP_START_RESPONSE( seq, "dataBreakpointInfo" ); + DAP_SET_TABLE( body ); + body.SetNull( "dataId" ); + body.SetString( "description", "" ); + DAP_SEND(); + return; + } + + DAP_START_RESPONSE( seq, "dataBreakpointInfo" ); + DAP_SET_TABLE( body ); + { + stringbuf_t< MAX_DATA_WATCH_BUF_SIZE > bufId; + + // 0:expr + bufId.Put('0'); + bufId.Put(':'); + bufId.Puts( name ); + + body.SetString( "dataId", bufId ); + } + { + stringbuf_t< MAX_DATA_WATCH_BUF_SIZE > bufName; + + bufName.Put('`'); + bufName.Puts( name ); + bufName.Put('`'); + + body.SetString( "description", bufName ); + } + wjson_array_t at = body.SetArray( "accessTypes" ); + at.Append( "write" ); + DAP_SEND(); +#else + DAP_START_RESPONSE( seq, "dataBreakpointInfo" ); + DAP_SET_TABLE( body ); + body.SetNull( "dataId" ); + body.SetString( "description", "expression data breakpoints not implemented" ); + DAP_SEND(); +#endif + } +} + +int SQDebugServer::AddDataBreakpoint( HSQUIRRELVM vm, const SQVM::CallInfo *ci, + const string_t &dataId, const string_t &strCondition, int hitsTarget ) +{ + if ( dataId.len < 2 || dataId.ptr[1] != ':' ) + return INVALID_ID; + + string_t name; + objref_t obj; + SQObjectPtr value; + SQWeakRef *pContainer; + + if ( dataId.ptr[0] == '1' ) + { + char *pEnd = strchr( dataId.ptr + 1 + 1, ':' ); + if ( !pEnd ) + return INVALID_ID; + + string_t container; + container.ptr = dataId.ptr + 1 + 1; + container.len = pEnd - container.ptr; + + name.ptr = pEnd + 1; + name.len = ( dataId.ptr + dataId.len ) - name.ptr; + + if ( name.len > MAX_DATA_WATCH_NAME_LENGTH ) + { + vm->_lasterror = CreateSQString( vm, _SC("name is too long") ); + return INVALID_ID; + } + + int variablesReference; + + if ( !atoi( container, &variablesReference ) ) + { + vm->_lasterror = CreateSQString( vm, _SC("invalid object") ); + return INVALID_ID; + } + + varref_t *ref = FromVarRef( variablesReference ); + + // don't modify name in GetObj + stringbuf_t< MAX_DATA_WATCH_BUF_SIZE > tmpbuf; + tmpbuf.Puts( name ); + string_t tmp = tmpbuf; + + if ( !ref || ref->type != VARREF_OBJ || + !GetObj_VarRef( ref, tmp, obj, value ) ) + { + vm->_lasterror = CreateSQString( vm, _SC("invalid object") ); + return INVALID_ID; + } + + ConvertPtr( obj ); + Assert( obj.type != objref_t::PTR ); + + ConvertToWeakRef( *ref ); + pContainer = ref->obj.weakref; + } +#ifndef SQDBG_DISABLE_COMPILER + else if ( dataId.ptr[0] == '0' ) + { + name.ptr = dataId.ptr + 1 + 1; + name.len = ( dataId.ptr + dataId.len ) - name.ptr; + + if ( name.len > MAX_DATA_WATCH_NAME_LENGTH ) + { + vm->_lasterror = CreateSQString( vm, _SC("name is too long") ); + return INVALID_ID; + } + + // don't modify name in CCompiler::ParseString + stringbuf_t< MAX_DATA_WATCH_BUF_SIZE > tmpbuf; + tmpbuf.Puts( name ); + string_t tmp = tmpbuf; + + ECompileReturnCode r = Evaluate( tmp, vm, ci, value, obj ); + + ConvertPtr( obj ); + Assert( obj.type != objref_t::PTR ); + + // Check value again to see if the compiled expression was really a reference + SQObjectPtr val; + + if ( r != CompileReturnCode_Success || + obj.type == objref_t::INVALID || obj.type == objref_t::PTR || + ( !ISREFCOUNTED( sq_type(obj.src) ) && obj.type != objref_t::STACK ) || + !Get( obj, val ) || !IsEqual( val, value ) ) + { + vm->_lasterror = CreateSQString( vm, _SC("invalid expression") ); + return INVALID_ID; + } + + pContainer = NULL; + } +#endif + else + { + return INVALID_ID; + } + +#ifdef SQDBG_DISABLE_COMPILER + (void)ci; +#endif + + bool duplicate = false; + + // Duplicate? + if ( obj.type != objref_t::STACK ) + for ( unsigned int i = m_DataWatches.size(); i--; ) + { + const datawatch_t &dw = m_DataWatches[i]; + if ( dw.container == pContainer && _refcounted(dw.obj.src) == _refcounted(obj.src) && + dw.obj.type == obj.type && IsEqual( dw.obj.key, obj.key ) ) + { + // Allow duplicate watches with different conditions + if ( dw.condtype != ECMP_NONE || !strCondition.IsEmpty() ) + { + duplicate = true; + break; + } + else + { + vm->_lasterror = CreateSQString( vm, _SC("duplicate breakpoint") ); + return DUPLICATE_ID; + } + } + } + + unsigned int condtype = ECMP_NONE; + SQObjectPtr condition; + + if ( !strCondition.IsEmpty() && + !CompileDataBreakpointCondition( strCondition, condition, condtype ) ) + return INVALID_ID; + + if ( duplicate ) + { + // Re-iterate through watches to match this exact compiled condition + for ( unsigned int i = m_DataWatches.size(); i--; ) + { + const datawatch_t &dw = m_DataWatches[i]; + if ( dw.container == pContainer && _refcounted(dw.obj.src) == _refcounted(obj.src) && + dw.obj.type == obj.type && IsEqual( dw.obj.key, obj.key ) && + dw.condtype == condtype && IsEqual( dw.condition, condition ) ) + { + vm->_lasterror = CreateSQString( vm, _SC("duplicate breakpoint") ); + return DUPLICATE_ID; + } + } + } + + Assert( m_nBreakpointIndex < INT_MAX ); + + datawatch_t &dw = m_DataWatches.append(); + dw.id = ++m_nBreakpointIndex; + CopyString( &m_Strings, name, &dw.name ); + + if ( pContainer ) + { + dw.container = pContainer; + __ObjAddRef( dw.container ); + } + + dw.obj = obj; + dw.oldvalue = value; + dw.hitsTarget = hitsTarget; + + if ( condtype != ECMP_NONE ) + { + dw.condtype = condtype; + dw.condition = condition; + + if ( is_delegable(dw.condition) && _delegable(dw.condition)->_delegate ) + { + sq_addref( vm, &dw.condition ); + } + } + + return dw.id; +} + +// +// Parse out condition type and evaluate the value. +// It would be possible to let the user do ops on the data with a pseudovariable like '$data', +// or allow late evaluation by making the condition a function to be executed or parsing it manually, +// but that would add too much complexity and runtime overhead. +// +bool SQDebugServer::CompileDataBreakpointCondition( string_t condition, SQObjectPtr &out, unsigned int &type ) +{ + if ( condition.StartsWith("==") ) + { + type = ECMP_EQ; + condition.ptr += 2; + condition.len -= 2; + } + else if ( condition.StartsWith("!=") ) + { + type = ECMP_NE; + condition.ptr += 2; + condition.len -= 2; + } + else if ( condition.StartsWith(">=") ) + { + type = ECMP_GE; + condition.ptr += 2; + condition.len -= 2; + } + else if ( condition.StartsWith("<=") ) + { + type = ECMP_LE; + condition.ptr += 2; + condition.len -= 2; + } + else if ( condition.StartsWith("=&") ) + { + type = ECMP_BWAEQ; + condition.ptr += 2; + condition.len -= 2; + } + else if ( condition.StartsWith("!&") ) + { + type = ECMP_BWAZ; + condition.ptr += 2; + condition.len -= 2; + } + else if ( condition.StartsWith(">") ) + { + type = ECMP_G; + condition.ptr += 1; + condition.len -= 1; + } + else if ( condition.StartsWith("<") ) + { + type = ECMP_L; + condition.ptr += 1; + condition.len -= 1; + } + else if ( condition.StartsWith("&") ) + { + type = ECMP_BWA; + condition.ptr += 1; + condition.len -= 1; + } + else + { + m_pCurVM->_lasterror = CreateSQString( m_pCurVM, _SC("missing comparator") ); + return false; + } + + // Compile the condition in current stack frame +#ifdef NATIVE_DEBUG_HOOK + if ( !RunExpression( condition, m_pCurVM, m_pCurVM->ci, out ) ) +#else + if ( !RunExpression( condition, + m_pCurVM, + // client could send this request while not suspended + m_State == ThreadState_Suspended ? m_pCurVM->ci - 1 : m_pCurVM->ci, + out ) ) +#endif + return false; + + // Condition can only be of certain types + // for comparisons and bitwise ops + switch ( type ) + { + case ECMP_G: + case ECMP_GE: + case ECMP_L: + case ECMP_LE: + switch ( sq_type(out) ) + { + case OT_INTEGER: + case OT_FLOAT: + return true; + + default: + if ( is_delegable(out) ) + { + // Should have valid cmp metamethod + if ( _delegable(out)->_delegate ) + { + SQObjectPtr mm; + if ( _delegable(out)->GetMetaMethod( m_pRootVM, MT_CMP, mm ) ) + { + SQObjectPtr ret; + if ( RunClosure( mm, &out, out, ret) && sq_type(ret) == OT_INTEGER ) + { + return true; + } + } + } + + m_pCurVM->_lasterror = CreateSQString( m_pCurVM, + _SC("invalid cmp metamethod") ); + return false; + } + + m_pCurVM->_lasterror = CreateSQString( m_pCurVM, + _SC("invalid type for comparator, expected integer|float|instance|table") ); + return false; + } + case ECMP_BWA: + case ECMP_BWAZ: + case ECMP_BWAEQ: + if ( sq_type(out) == OT_INTEGER ) + return true; + + m_pCurVM->_lasterror = CreateSQString( m_pCurVM, + _SC("invalid type for bitwise op, expected integer") ); + return false; + } + + return true; +} + +int SQDebugServer::CompareObj( const SQObjectPtr &lhs, const SQObjectPtr &rhs ) +{ + SQObjectType tl = sq_type(lhs); + SQObjectType tr = sq_type(rhs); + + if ( tl == tr ) + { + if ( _rawval(lhs) == _rawval(rhs) ) + return ECMP_EQ; + + switch ( tl ) + { + case OT_INTEGER: + case OT_BOOL: + return _integer(lhs) < _integer(rhs) ? ECMP_L : ECMP_G; + + case OT_FLOAT: + return _float(lhs) < _float(rhs) ? ECMP_L : ECMP_G; + + default: + if ( is_delegable(lhs) && _delegable(lhs)->_delegate ) + { + SQObjectPtr mm; + if ( _delegable(lhs)->GetMetaMethod( m_pRootVM, MT_CMP, mm ) ) + { + SQObjectPtr ret; + if ( RunClosure( mm, &lhs, rhs, ret ) && sq_type(ret) == OT_INTEGER ) + { + if ( _integer(ret) == 0 ) + return ECMP_EQ; + + if ( _integer(ret) < 0 ) + return ECMP_L; + + return ECMP_G; + } + } + } + + return ECMP_NONE; + } + } + else + { + if ( tl == OT_INTEGER ) + { + if ( tr == OT_FLOAT ) + { + if ( _integer(lhs) == (SQInteger)_float(rhs) ) + return ECMP_EQ; + + if ( _integer(lhs) < (SQInteger)_float(rhs) ) + return ECMP_L; + + return ECMP_G; + } + } + else if ( tl == OT_FLOAT ) + { + if ( tr == OT_INTEGER ) + { + if ( (SQInteger)_float(lhs) == _integer(rhs) ) + return ECMP_EQ; + + if ( (SQInteger)_float(lhs) < _integer(rhs) ) + return ECMP_L; + + return ECMP_G; + } + } + + // uncomparable + return ECMP_NONE; + } +} + +bool SQDebugServer::CheckDataBreakpoints( HSQUIRRELVM vm ) +{ + bool ret = false; + + for ( unsigned int i = m_DataWatches.size(); i--; ) + { + datawatch_t &dw = m_DataWatches[i]; + + if ( dw.container ) + { + bool rem = ( sq_type(dw.container->_obj) == OT_NULL ); + + // objref_t::src holds strong ref for the compiler. + // Manually check if the container is the source + // and if its only reference is in objref_t. + // Though this doesn't work when there are + // multiple breakpoints on a single container + if ( !rem && + _rawval(dw.container->_obj) == _rawval(dw.obj.src) && + _refcounted(dw.obj.src)->_uiRef == 1 ) + { + dw.obj.src.Null(); + rem = ( sq_type(dw.container->_obj) == OT_NULL ); + } + + if ( rem ) + { + DAP_START_EVENT( ++m_Sequence, "breakpoint" ); + DAP_SET_TABLE( body ); + body.SetString( "reason", "removed" ); + wjson_table_t bp = body.SetTable( "breakpoint" ); + bp.SetInt( "id", dw.id ); + bp.SetBool( "verified", false ); + DAP_SEND(); + + FreeDataWatch( dw ); + m_DataWatches.remove(i); + continue; + } + } + + SQObjectPtr value; + + if ( Get( dw.obj, value ) ) + { + if ( IsEqual( dw.oldvalue, value ) ) + continue; + + SQObjectPtr oldvalue = dw.oldvalue; + dw.oldvalue = value; + + switch ( dw.condtype ) + { + case ECMP_NONE: + break; + + case ECMP_EQ: + if ( IsEqual( value, dw.condition ) ) + break; + continue; + + case ECMP_NE: + if ( !IsEqual( value, dw.condition ) ) + break; + continue; + + case ECMP_G: + case ECMP_GE: + case ECMP_L: + case ECMP_LE: + if ( CompareObj( value, dw.condition ) & dw.condtype ) + break; + continue; + + case ECMP_BWA: + if ( sq_type(value) == OT_INTEGER && + ( _integer(value) & _integer(dw.condition) ) != 0 ) + break; + continue; + + case ECMP_BWAZ: + if ( sq_type(value) == OT_INTEGER && + ( _integer(value) & _integer(dw.condition) ) == 0 ) + break; + continue; + + case ECMP_BWAEQ: + if ( sq_type(value) == OT_INTEGER && + ( _integer(value) & _integer(dw.condition) ) == _integer(dw.condition) ) + break; + continue; + + default: UNREACHABLE(); + } + + if ( dw.hitsTarget ) + { + if ( ++dw.hits < dw.hitsTarget ) + continue; + + dw.hits = 0; + } + + stringbuf_t< 256 > buf; + + if ( dw.container ) + { + buf.Put('['); + buf.PutHex( (uintptr_t)_refcounted(dw.container->_obj) ); + buf.Put(' '); + buf.Puts( GetType( dw.container->_obj ) ); + buf.Put(']'); + buf.Put('-'); + buf.Put('>'); + buf.Puts( dw.name ); + } + else + { + buf.Put('`'); + buf.Puts( dw.name ); + buf.Put('`'); + } + + buf.Puts(" changed ("); + buf.Puts( GetValue( oldvalue ) ); + buf.Puts(")->("); + buf.Puts( GetValue( value ) ); + buf.Put(')'); + buf.Term(); + + SQPrint( vm, _SC("(sqdbg) Data breakpoint hit: " FMT_CSTR "\n"), buf.ptr ); + + Break( vm, { breakreason_t::DataBreakpoint, buf, dw.id } ); + ret = true; + } + else + { + DAP_START_EVENT( ++m_Sequence, "breakpoint" ); + DAP_SET_TABLE( body ); + body.SetString( "reason", "removed" ); + wjson_table_t bp = body.SetTable( "breakpoint" ); + bp.SetInt( "id", dw.id ); + bp.SetBool( "verified", false ); + DAP_SEND(); + + // don't break on stack watch expiration + if ( dw.obj.type != objref_t::STACK ) + { + stringbuf_t< 256 > buf; + + if ( dw.container ) + { + buf.Put('['); + buf.PutHex( (uintptr_t)_refcounted(dw.container->_obj) ); + buf.Put(' '); + buf.Puts( GetType( dw.container->_obj ) ); + buf.Put(']'); + buf.Put('-'); + buf.Put('>'); + buf.Puts( dw.name ); + } + else + { + buf.Put('`'); + buf.Puts( dw.name ); + buf.Put('`'); + } + + buf.Puts(" was removed"); + buf.Term(); + + SQPrint( vm, _SC("(sqdbg) Data breakpoint hit: " FMT_CSTR "\n"), buf.ptr ); + + Break( vm, { breakreason_t::DataBreakpoint, buf, dw.id } ); + ret = true; + } + + FreeDataWatch( dw ); + m_DataWatches.remove(i); + } + } + + return ret; +} + +void SQDebugServer::FreeDataWatch( datawatch_t &dw ) +{ + if ( dw.container ) + __ObjRelease( dw.container ); + + if ( dw.condtype != ECMP_NONE ) + { + if ( is_delegable(dw.condition) && _delegable(dw.condition)->_delegate ) + { + sq_release( m_pRootVM, &dw.condition ); + dw.condition.Null(); + } + } + + FreeString( &m_Strings, &dw.name ); +} + +void SQDebugServer::RemoveDataBreakpoints() +{ + for ( unsigned int i = 0; i < m_DataWatches.size(); i++ ) + FreeDataWatch( m_DataWatches[i] ); + + m_DataWatches.clear(); +} + +static inline bool HasEscapes( const SQChar *src, SQInteger len ) +{ + const SQChar *end = src + len; + + for ( ; src < end; src++ ) + { + switch ( *src ) + { + case '\"': case '\\': + case '\a': case '\b': case '\f': + case '\n': case '\r': case '\t': case '\v': + case '\0': + return true; + + default: +#ifdef SQUNICODE + if ( !IN_RANGE( *src, 0x20, 0x7E ) ) +#else + if ( !IN_RANGE_CHAR( *(unsigned char*)src, 0x20, 0x7E ) ) +#endif + { +#ifdef SQUNICODE + if ( *src < 0x80 ) + { + return true; + } + else + { + int ret = IsValidUnicode( src, end - src ); + if ( ret > 0 ) + { + src += ret - 1; + } + else + { + return true; + } + } +#else + if ( *(unsigned char*)src < 0x80 ) + { + return true; + } + else + { + int ret = IsValidUTF8( (unsigned char*)src, end - src ); + if ( ret != 0 ) + { + src += ret - 1; + } + else + { + return true; + } + } +#endif + } + } + } + + return false; +} + +#ifndef SQUNICODE +static inline int CountEscapes( const char *src, SQInteger len ) +{ + const char *end = src + len; + int count = 0; + + for ( ; src < end; src++ ) + { + switch ( *src ) + { + case '\"': case '\\': + case '\a': case '\b': case '\f': + case '\n': case '\r': case '\t': case '\v': + case '\0': + count++; + break; + + default: + if ( !IN_RANGE_CHAR( *(unsigned char*)src, 0x20, 0x7E ) ) + { + int ret = IsValidUTF8( (unsigned char*)src, end - src ); + if ( ret != 0 ) + { + src += ret - 1; + } + else + { + count += sizeof(char) * 2 + 1; + } + } + } + } + + return count; +} +#endif // !SQUNICODE + +#define _memmove( dst, src, count, bound ) \ + Assert( (dst) + (count) <= bound ); \ + memmove( (dst), (src), (count) ); + +static void Escape( char *dst, unsigned int *len, unsigned int size ) +{ + if ( size < *len ) + *len = size; + + char *strEnd = dst + *len; + char *memEnd = dst + size; + + for ( ; dst < strEnd; dst++ ) + { + switch ( *dst ) + { + case '\"': case '\\': + case '\a': case '\b': case '\f': + case '\n': case '\r': case '\t': case '\v': + case '\0': + { + if ( dst + 1 >= memEnd ) + { + dst[0] = '?'; + break; + } + + _memmove( dst + 1, dst, memEnd - ( dst + 1 ), memEnd ); + *dst++ = '\\'; + + switch ( *dst ) + { + case '\"': + case '\\': break; + case '\a': *dst = 'a'; break; + case '\b': *dst = 'b'; break; + case '\f': *dst = 'f'; break; + case '\n': *dst = 'n'; break; + case '\r': *dst = 'r'; break; + case '\t': *dst = 't'; break; + case '\v': *dst = 'v'; break; + case '\0': *dst = '0'; break; + default: UNREACHABLE(); + } + + if ( strEnd < memEnd ) + { + strEnd++; + (*len)++; + } + + break; + } + default: + if ( !IN_RANGE_CHAR( *(unsigned char*)dst, 0x20, 0x7E ) ) + { + int ret = 0; + if ( *(unsigned char*)dst < 0x80 || + ( ret = IsValidUTF8( (unsigned char*)dst, strEnd - dst ) ) == 0 ) + { + if ( dst + 1 + sizeof(SQChar) * 2 >= memEnd ) + { + do + { + *dst++ = '?'; + } + while ( dst < memEnd ); + + break; + } + + SQChar val = (SQChar)((unsigned char*)dst)[0]; + + _memmove( dst + 2 + sizeof(SQChar) * 2, + dst + 1, + memEnd - ( dst + 2 + sizeof(SQChar) * 2 ), + memEnd ); + + dst[0] = '\\'; + dst[1] = 'x'; + + int l = printhex< true, false >( dst + 2, memEnd - ( dst + 2 ), val ); + dst += 1 + l; + strEnd += 1 + l; + *len += 1 + l; + + if ( strEnd > memEnd ) + { + *len -= (unsigned int)( strEnd - memEnd ); + strEnd = memEnd; + } + } + else if ( ret ) + { + dst += ret - 1; + } + } + } + } +} + +#ifndef SQUNICODE +static void UndoEscape( char *dst, unsigned int *len ) +{ + char *end = dst + *len; + + for ( ; dst < end; dst++ ) + { + if ( *dst != '\\' ) + continue; + + switch ( dst[1] ) + { + case '\\': +shift_one: + memmove( dst, dst + 1, end - dst ); + end--; + (*len)--; + break; + case '\"': goto shift_one; + case 'a': dst[1] = '\a'; goto shift_one; + case 'b': dst[1] = '\b'; goto shift_one; + case 'f': dst[1] = '\f'; goto shift_one; + case 'n': dst[1] = '\n'; goto shift_one; + case 'r': dst[1] = '\r'; goto shift_one; + case 't': dst[1] = '\t'; goto shift_one; + case 'v': dst[1] = '\v'; goto shift_one; + case '0': dst[1] = '\0'; goto shift_one; + case 'x': + { + atox( { dst + 2, sizeof(SQChar) * 2 }, (SQChar*)dst ); + memmove( dst + 1, (dst + 2) + sizeof(SQChar) * 2, end - ( (dst + 2) + sizeof(SQChar) * 2 ) ); + end -= sizeof(SQChar) * 2 + 1; + *len -= sizeof(SQChar) * 2 + 1; + break; + } + } + } +} +#endif // !SQUNICODE + +#if 0 +template < typename T > +static int StringifiedBytesLength( int len ) +{ + return len * ( 2 + sizeof(T) * 2 ); +} + +template < typename T > +static int StringifyBytes( T *str, int strLen, char *dst, int size ) +{ + T *strEnd = str + strLen; + int len = 0; + + for ( ; str < strEnd && len <= size - (int)( 2 + sizeof(T) * 2 ); str++ ) + { + *dst++ = '\\'; + *dst++ = 'x'; + len++; + len++; + + int l = printhex< true, false >( dst, size - len, *str ); + dst += l; + len += l; + } + + return len; +} + +static bool ReadStringifiedBytes( char *dst, int *len ) +{ + char *end = dst + *len; + + do + { + if ( dst[0] == '\\' && dst[1] == 'x' ) + { + if ( !atox( { dst + 2, sizeof(SQChar) * 2 }, (SQChar*)dst ) ) + return false; + + memmove( dst + sizeof(SQChar), dst + 2 + sizeof(SQChar) * 2, end - ( dst + 2 + sizeof(SQChar) * 2 ) ); + end -= 2 + sizeof(SQChar) * 2 - sizeof(SQChar); + (*len) -= 2 + sizeof(SQChar) * 2 - sizeof(SQChar); + dst += sizeof(SQChar) - 1; + } + } + while ( ++dst < end ); + + return true; +} +#endif + +static inline string_t SpecialFloatValue( SQFloat val ) +{ +#ifdef SQUSEDOUBLE + if ( val == DBL_MAX ) + { + return "DBL_MAX"; + } + if ( val == DBL_MIN ) + { + return "DBL_MIN"; + } + if ( val == DBL_EPSILON ) + { + return "DBL_EPSILON"; + } +#endif + if ( val == FLT_MAX ) + { + return "FLT_MAX"; + } + if ( val == FLT_MIN ) + { + return "FLT_MIN"; + } + if ( val == FLT_EPSILON ) + { + return "FLT_EPSILON"; + } + return { 0, 0 }; +} + +string_t SQDebugServer::GetValue( const SQObject &obj, int flags ) +{ + switch ( sq_type(obj) ) + { + case OT_STRING: + { + if ( !( flags & kFS_NoQuote ) ) + { +#ifdef SQUNICODE + unsigned int size = 2 + UTF8Length< kUTFEscape >( _string(obj)->_val, _string(obj)->_len ); +#else + int escapes = CountEscapes( _string(obj)->_val, _string(obj)->_len ); + unsigned int size = 2 + _string(obj)->_len + escapes; +#endif + char *buf = ScratchPad( size ); + + if ( !buf ) + return { STR_NOMEM, STRLEN(STR_NOMEM) }; + + unsigned int len = 0; +#if 0 + if ( rawBytes ) + { + // Identify keys while sending regular strings for values + if ( flags & kFS_KeyVal ) + buf[len++] = 'R'; + + buf[len++] = '\"'; + len += StringifyBytes( + _string(obj)->_val, + min( ( size - len ) / sizeof(SQChar), (unsigned int)_string(obj)->_len ), + buf + len, + size - len - 1 ); + } +#endif + + buf[0] = '\"'; +#ifdef SQUNICODE + len = SQUnicodeToUTF8< kUTFEscape >( buf + 1, size - 2, _string(obj)->_val, _string(obj)->_len ); +#else + len = scstombs( buf + 1, size - 2, _string(obj)->_val, _string(obj)->_len ); + Escape( buf + 1, &len, size - 2 ); +#endif + len++; + + buf[len++] = '\"'; + Assert( len == size ); + + return { buf, len }; + } + else + { +#ifdef SQUNICODE + unsigned int size = UTF8Length( _string(obj)->_val, _string(obj)->_len ); + char *buf = ScratchPad( size ); + + if ( !buf ) + return { STR_NOMEM, STRLEN(STR_NOMEM) }; + + unsigned int len = SQUnicodeToUTF8( buf, size, _string(obj)->_val, _string(obj)->_len ); + return { buf, len }; +#else +#ifdef _SQ64 + string_t ret( _string(obj) ); + + if ( ret.len > INT_MAX ) + ret.len = INT_MAX; + + return ret; +#else + return _string(obj); +#endif +#endif + } + } + case OT_FLOAT: + { + if ( flags & kFS_Decimal ) + { + const int size = FMT_INT_LEN; + char *buf = ScratchPad( size ); + int len = printint( buf, size, _integer(obj) ); + return { buf, (unsigned int)len }; + } + else if ( flags & kFS_Binary ) + { + int len = 2 + ( sizeof(SQFloat) << 3 ); + char *buf = ScratchPad( len ); + + char *c = buf; + *c++ = '0'; + *c++ = 'b'; + + if ( flags & kFS_NoPrefix ) + { + len -= 2; + c -= 2; + } + + for ( int i = ( sizeof(SQFloat) << 3 ); i--; ) + *c++ = '0' + ( ( _integer(obj) & ( (SQUnsignedInteger)1 << i ) ) != 0 ); + + return { buf, (unsigned int)len }; + } + else + { +getfloat: + string_t val = SpecialFloatValue( _float(obj) ); + + if ( !val.ptr || ( flags & ( kFS_Float | kFS_FloatE | kFS_FloatG ) ) ) + { + const int size = FMT_FLT_LEN + 1; + val.ptr = ScratchPad( size ); + + if ( flags & kFS_FloatE ) + { + val.len = snprintf( val.ptr, size, "%e", _float(obj) ); + } + else if ( flags & kFS_FloatG ) + { + val.len = snprintf( val.ptr, size, "%g", _float(obj) ); + } + else + { + val.len = snprintf( val.ptr, size, "%f", _float(obj) ); + } + + Assert( val.len < INT_MAX ); + } + + return val; + } + } + case OT_INTEGER: + { + if ( flags & kFS_Binary ) + { + int i; + + if ( !( flags & kFS_Padding ) ) + { + // Print at 1, 2, 4 byte boundaries + if ( (SQUnsignedInteger)_integer(obj) > 0xFFFFFFFF ) + { + i = sizeof(SQInteger) * 8; + } + else if ( (SQUnsignedInteger)_integer(obj) > 0x0000FFFF ) + { + i = 4 * 8; + } + else if ( (SQUnsignedInteger)_integer(obj) > 0x000000FF ) + { + i = 2 * 8; + } + else + { + i = 1 * 8; + } + } + else + { + i = sizeof(SQInteger) * 8; + } + + unsigned int len = 2 + i; + char *buf = ScratchPad( len ); + char *c = buf; + *c++ = '0'; + *c++ = 'b'; + + if ( flags & kFS_NoPrefix ) + { + len -= 2; + c -= 2; + } + + while ( i-- ) + *c++ = '0' + ( ( _integer(obj) & ( (SQUnsignedInteger)1 << i ) ) != 0 ); + + return { buf, len }; + } + else if ( flags & kFS_Octal ) + { + const int size = FMT_OCT_LEN; + char *buf = ScratchPad( size ); + int len = printoct( buf, size, (SQUnsignedInteger)_integer(obj) ); + return { buf, (unsigned int)len }; + } + else if ( flags & kFS_Float ) + { + goto getfloat; + } + else if ( flags & kFS_Character ) + { + const int size = FMT_INT_LEN + FMT_PTR_LEN + 3; + char *buf = ScratchPad( size ); + int len; + + if ( _integer(obj) > (SQInteger)( 1 << ( ( sizeof(SQChar) << 3 ) - 1 ) ) ) + { + len = printint( buf, size, _integer(obj) ); + return { buf, (unsigned int)len }; + } + + SQChar ch = (SQChar)_integer(obj); + + if ( !(flags & kFS_Hexadecimal) ) + { +#ifdef SQUNICODE + len = printint( buf, size, ch ); +#else + len = printint( buf, size, (unsigned char)ch ); +#endif + } + else + { + len = printhex< false >( buf, size, ch ); + } + + buf[len++] = ' '; + buf[len++] = '\''; + + if ( IN_RANGE( ch, 0x20, 0x7E ) ) + { + switch ( ch ) + { + case '\'': buf[len++] = '\\'; break; + case '\\': buf[len++] = '\\'; break; + } + + buf[len++] = (char)ch; + } + else + { +#ifdef SQUNICODE + if ( ch <= (SQChar)0xFF ) + { + buf[len++] = '\\'; + buf[len++] = 'x'; + len += printhex< true, false, false >( buf + len, size, (char)ch ); + } + else if ( IsValidUnicode( &ch, 1 ) ) + { + len += SQUnicodeToUTF8( buf + len, size - len - 1, &ch, 1 ); + } + else +#endif + { + buf[len++] = '\\'; + buf[len++] = 'x'; + len += printhex< true, false, false >( buf + len, size, ch ); + } + } + + buf[len++] = '\''; + + return { buf, (unsigned int)len }; + } + // Check hex last to make watch format specifiers overwrite "format.hex" client option + else if ( ( flags & kFS_Hexadecimal ) && !( flags & kFS_Decimal ) ) + { + const int size = FMT_PTR_LEN; + char *buf = ScratchPad( size ); + int len; + + if ( !( flags & kFS_Uppercase ) ) + { + if ( !( flags & kFS_NoPrefix ) ) + { + if ( !( flags & kFS_Padding ) ) + { + len = printhex< false, true, false >( buf, size, (SQUnsignedInteger)_integer(obj) ); + } + else + { + len = printhex< true, true, false >( buf, size, (SQUnsignedInteger)_integer(obj) ); + } + } + else + { + if ( !( flags & kFS_Padding ) ) + { + len = printhex< false, false, false >( buf, size, (SQUnsignedInteger)_integer(obj) ); + } + else + { + len = printhex< true, false, false >( buf, size, (SQUnsignedInteger)_integer(obj) ); + } + } + } + else + { + if ( !( flags & kFS_NoPrefix ) ) + { + if ( !( flags & kFS_Padding ) ) + { + len = printhex< false, true, true >( buf, size, (SQUnsignedInteger)_integer(obj) ); + } + else + { + len = printhex< true, true, true >( buf, size, (SQUnsignedInteger)_integer(obj) ); + } + } + else + { + if ( !( flags & kFS_Padding ) ) + { + len = printhex< false, false, true >( buf, size, (SQUnsignedInteger)_integer(obj) ); + } + else + { + len = printhex< true, false, true >( buf, size, (SQUnsignedInteger)_integer(obj) ); + } + } + } + + return { buf, (unsigned int)len }; + } + else + { + const int size = FMT_INT_LEN; + char *buf = ScratchPad( size ); + int len = printint( buf, size, _integer(obj) ); + return { buf, (unsigned int)len }; + } + } + case OT_BOOL: + { + if ( _integer(obj) ) + return "true"; + + return "false"; + } + case OT_NULL: + { + return "null"; + } + case OT_ARRAY: + { + int size; + + if ( !( flags & kFS_ListMembers ) ) + { + size = STRLEN(" {size=}") + FMT_PTR_LEN + FMT_INT_LEN; + } + else + { + size = FMT_PTR_LEN + 128; + } + + if ( !( flags & kFS_ListMembers ) ) + { + stringbufext_t buf = ScratchPadBuf( size ); + + if ( !( flags & kFS_NoAddr ) ) + { + buf.PutHex( (uintptr_t)_refcounted(obj) ); + buf.Put(' '); + } + + buf.Puts("{size="); + + if ( !( flags & kFS_Hexadecimal ) ) + { + buf.PutInt( _array(obj)->_values.size() ); + } + else + { + buf.PutHex( _array(obj)->_values.size(), false ); + } + + buf.Put('}'); + return buf; + } + else + { + // HACKHACK: Just use an unused buffer + stringbufext_t buf( m_ReadBuf.Alloc( size ), size ); + + if ( !( flags & kFS_NoAddr ) ) + { + buf.PutHex( (uintptr_t)_refcounted(obj) ); + buf.Put(' '); + } + + buf.Put('['); + + for ( unsigned int i = 0; i < _array(obj)->_values.size(); i++ ) + { + string_t str = GetValue( _array(obj)->_values[i], flags & ~kFS_NoQuote ); + + buf.Puts( str ); + + if ( buf.BytesLeft() < 4 ) + { + buf.len -= 4 - buf.BytesLeft(); + buf.Puts("..."); + buf.Put(']'); + return buf; + } + + buf.Put(','); + buf.Put(' '); + } + + if ( _array(obj)->_values.size() ) + buf.len -= 2; + + buf.Put(']'); + return buf; + } + } + case OT_TABLE: + { + int size; + + if ( !( flags & kFS_ListMembers ) ) + { + size = STRLEN(" {size=}") + FMT_PTR_LEN + FMT_INT_LEN; + } + else + { + size = FMT_PTR_LEN + 128; + } + + if ( !( flags & kFS_ListMembers ) ) + { + stringbufext_t buf = ScratchPadBuf( size ); + + if ( !( flags & kFS_NoAddr ) ) + { + buf.PutHex( (uintptr_t)_refcounted(obj) ); + buf.Put(' '); + } + + buf.Puts("{size="); + + if ( !( flags & kFS_Hexadecimal ) ) + { + buf.PutInt( _table(obj)->CountUsed() ); + } + else + { + buf.PutHex( _table(obj)->CountUsed(), false ); + } + + buf.Put('}'); + return buf; + } + else + { + // HACKHACK: Just use an unused buffer + stringbufext_t buf( m_ReadBuf.Alloc( size ), size ); + + if ( !( flags & kFS_NoAddr ) ) + { + buf.PutHex( (uintptr_t)_refcounted(obj) ); + buf.Put(' '); + } + + buf.Put('{'); + + SQObjectPtr key, val; + FOREACH_SQTABLE( _table(obj), key, val ) + { + string_t str = GetValue( key, flags | kFS_NoQuote ); + + buf.Puts( str ); + buf.Put('='); + + str = GetValue( val, flags & ~kFS_NoQuote ); + buf.Puts( str ); + + if ( buf.BytesLeft() < 4 ) + { + buf.len -= 4 - buf.BytesLeft(); + buf.Puts("..."); + buf.Put('}'); + return buf; + } + + buf.Put(','); + buf.Put(' '); + } + + if ( _table(obj)->CountUsed() ) + buf.len -= 2; + + buf.Put('}'); + return buf; + } + } + case OT_INSTANCE: + { + SQClass *base = _instance(obj)->_class; + Assert( base ); + const SQObjectPtr *def = GetClassDefValue( base ); + + if ( def ) + { + SQObjectPtr res; + + if ( RunClosure( *def, &obj, res ) && sq_type(res) == OT_STRING ) + { + if ( !( flags & kFS_NoAddr ) ) + { + const int size = 1024; + + stringbufext_t buf = ScratchPadBuf( size ); + buf.PutHex( (uintptr_t)_refcounted(obj) ); + buf.Put(' '); + buf.Put('{'); + buf.Puts( _string(res) ); + buf.Put('}'); + + return buf; + } + else + { + unsigned int size = scstombslen( _string(res)->_val, _string(res)->_len ); + char *buf = ScratchPad( size ); + + if ( !buf ) + return { STR_NOMEM, STRLEN(STR_NOMEM) }; + + unsigned int len = scstombs( buf, size, _string(res)->_val, _string(res)->_len ); + return { buf, len }; + } + } + } + + goto default_label; + } + case OT_CLASS: + { + const classdef_t *def = FindClassDef( _class(obj) ); + + if ( def && def->name.ptr ) + { + if ( !( flags & kFS_NoAddr ) ) + { + return def->name; + } + else + { + Assert( def->name.len >= FMT_PTR_LEN + 1 ); + return { def->name.ptr + FMT_PTR_LEN + 1, def->name.len - FMT_PTR_LEN - 1 }; + } + } + + goto default_label; + } + case OT_CLOSURE: + case OT_NATIVECLOSURE: + { + const SQObjectPtr *name; + + if ( sq_type(obj) == OT_CLOSURE ) + { + name = &_fp(_closure(obj)->_function)->_name; + } + else + { + name = &_nativeclosure(obj)->_name; + } + + if ( sq_type(*name) == OT_STRING ) + { + int size = FMT_PTR_LEN + 1 + scstombslen( _string(*name)->_val, _string(*name)->_len ); + char *buf = ScratchPad( size ); + + if ( !buf ) + return { STR_NOMEM, STRLEN(STR_NOMEM) }; + + int len = printhex( buf, size, (uintptr_t)_refcounted(obj) ); + buf[len++] = ' '; + len += scstombs( buf + len, size - FMT_PTR_LEN - 1, _string(*name)->_val, _string(*name)->_len ); + return { buf, (unsigned int)len }; + } + + goto default_label; + } + default: + default_label: + { + const int size = FMT_PTR_LEN; + char *buf = ScratchPad( size ); + int len = printhex( buf, size, (uintptr_t)_refcounted(obj) ); + return { buf, (unsigned int)len }; + } + } +} + +// To make sure strings are not unnecessarily allocated and iterated through +// Escape and quote strings while writing to json +void SQDebugServer::JSONSetString( wjson_table_t &elem, const string_t &key, const SQObject &obj, int flags ) +{ + switch ( sq_type(obj) ) + { + case OT_STRING: + { + elem.SetString( key, _string(obj), !( flags & kFS_NoQuote ) ); + break; + } + default: + { + elem.SetString( key, GetValue( obj, flags ) ); + } + } +} + +bool SQDebugServer::IsJumpOp( const SQInstruction *instr ) +{ + return ( instr->op == _OP_JMP || + instr->op == _OP_AND || + instr->op == _OP_OR || +#if SQUIRREL_VERSION_NUMBER >= 300 + instr->op == _OP_JCMP || +#else + instr->op == _OP_JNZ || +#endif + instr->op == _OP_JZ || + instr->op == _OP_FOREACH || + instr->op == _OP_POSTFOREACH ); +} + +int SQDebugServer::GetJumpCount( const SQInstruction *instr ) +{ + Assert( IsJumpOp( instr ) ); + + if ( instr->op != _OP_POSTFOREACH ) + return instr->_arg1; + + return instr->_arg1 - 1; +} + +// Line ops are ignored in disassembly, but jump ops account for them. +// Count out all line ops in the jump +// Doing this for setting instructions adds too much complexity that is not worth the effort. +int SQDebugServer::DeduceJumpCount( const SQInstruction *instr ) +{ + Assert( IsJumpOp( instr ) ); + + int arg1 = GetJumpCount( instr ); + int sign = ( arg1 < 0 ); + if ( sign ) + arg1 = -arg1; + + for ( const SQInstruction *ip = instr + GetJumpCount( instr ); + ip != instr; + ip += sign ? 1 : -1 ) + { + if ( ip->op == _OP_LINE ) + arg1--; + } + + if ( sign ) + arg1 = -arg1; + + return arg1; +} + +// to display the local variable name only in the target on local variable declarations +SQUnsignedInteger SQDebugServer::s_nTargetAssignment = 0; + +SQString *SQDebugServer::GetLocalVarName( const SQFunctionProto *func, const SQInstruction *instr, + unsigned int pos ) +{ + SQUnsignedInteger ip = (SQUnsignedInteger)( instr - func->_instructions ); + + for ( int i = 0; i < func->_nlocalvarinfos; i++ ) + { + const SQLocalVarInfo &var = func->_localvarinfos[i]; + + if ( (unsigned int)var._pos == pos && + var._start_op <= ip + s_nTargetAssignment && var._end_op >= ip - 1 ) + { + return sq_type(var._name) == OT_STRING ? _string(var._name) : NULL; + } + } + + return NULL; +} + +void SQDebugServer::PrintStackVar( const SQFunctionProto *func, const SQInstruction *instr, + unsigned int pos, stringbufext_t &buf ) +{ + buf.Put( '[' ); + + SQString *var = GetLocalVarName( func, instr, pos ); + + if ( !var ) + { + buf.PutInt( (int)pos ); + } + else + { + buf.Puts( var ); + } + + buf.Put( ']' ); +} + +void SQDebugServer::PrintOuter( const SQFunctionProto *func, int pos, stringbufext_t &buf ) +{ + Assert( sq_type(func->_outervalues[pos]._name) == OT_STRING ); + SQString *val = _string(func->_outervalues[pos]._name); + + buf.Put( '[' ); + buf.Puts( val ); + buf.Put( ']' ); +} + +void SQDebugServer::PrintLiteral( const SQFunctionProto *func, int pos, stringbufext_t &buf ) +{ + string_t val = GetValue( func->_literals[pos] ); + + if ( val.len > 64 ) + val.len = 64; + + buf.Puts( val ); +} + +void SQDebugServer::PrintStackTarget( const SQFunctionProto *func, const SQInstruction *instr, + stringbufext_t &buf ) +{ + if ( instr->_arg0 != 0xFF ) + { + s_nTargetAssignment = 1; + PrintStackVar( func, instr, instr->_arg0, buf ); + s_nTargetAssignment = 0; + buf.Puts( " = " ); + } +} + +void SQDebugServer::PrintStackTargetVar( const SQFunctionProto *func, const SQInstruction *instr, + stringbufext_t &buf ) +{ + if ( instr->_arg0 != 0xFF ) + { + s_nTargetAssignment = 1; + PrintStackVar( func, instr, instr->_arg0, buf ); + s_nTargetAssignment = 0; + } +} + +void SQDebugServer::PrintDeref( const SQFunctionProto *func, const SQInstruction *instr, + unsigned int self, unsigned int key, stringbufext_t &buf ) +{ + PrintStackVar( func, instr, self, buf ); + buf.Puts( "->" ); + PrintStackVar( func, instr, key, buf ); +} + +void SQDebugServer::DescribeInstruction( const SQInstruction *instr, const SQFunctionProto *func, + stringbufext_t &buf ) +{ +#if SQUIRREL_VERSION_NUMBER < 212 + return; +#else + buf.Puts( g_InstructionName[ instr->op ] ); + buf.Put( ' ' ); + + switch ( instr->op ) + { + case _OP_LOADNULLS: + { + PrintStackVar( func, instr, instr->_arg0, buf ); + buf.Put( ' ' ); + buf.PutInt( instr->_arg1 ); + break; + } + case _OP_LOADINT: + { + PrintStackTarget( func, instr, buf ); + buf.PutInt( instr->_arg1 ); + break; + } + case _OP_LOADFLOAT: + { + PrintStackTarget( func, instr, buf ); +#if SQUIRREL_VERSION_NUMBER >= 300 +LFLOAT: +#endif + string_t val = SpecialFloatValue( *(SQFloat*)&instr->_arg1 ); + + if ( !val.ptr ) + { + int l = snprintf( buf.ptr + buf.len, buf.BytesLeft(), "%g", *(SQFloat*)&instr->_arg1 ); + if ( l < 0 || l > buf.BytesLeft() ) + l = buf.BytesLeft(); + + buf.len += l; + } + else + { + buf.Puts( val ); + } + + break; + } + case _OP_LOADBOOL: + { + PrintStackTarget( func, instr, buf ); +#if SQUIRREL_VERSION_NUMBER >= 300 +LBOOL: +#endif + if ( instr->_arg1 ) + { + buf.Puts( "true" ); + } + else + { + buf.Puts( "false" ); + } + + break; + } + case _OP_FOREACH: + { + PrintStackVar( func, instr, instr->_arg2, buf ); + buf.Puts( ", " ); + PrintStackVar( func, instr, instr->_arg2 + 1, buf ); + buf.Puts( " in " ); + PrintStackVar( func, instr, instr->_arg0, buf ); + buf.Puts( " jmp " ); + buf.PutInt( instr->_arg1 ); + break; + } + case _OP_POSTFOREACH: + { + PrintStackVar( func, instr, instr->_arg0, buf ); + buf.Puts( " jmp " ); + buf.PutInt( instr->_arg1 - 1 ); + break; + } + case _OP_PUSHTRAP: + { + PrintStackVar( func, instr, instr->_arg0, buf ); + buf.Puts( " jmp " ); + buf.PutInt( instr->_arg1 ); + break; + } + case _OP_TAILCALL: + case _OP_CALL: + { + PrintStackTarget( func, instr, buf ); + PrintStackVar( func, instr, instr->_arg1, buf ); + buf.Put( ' ' ); + buf.PutInt( instr->_arg3 ); + break; + } + case _OP_RETURN: + case _OP_YIELD: + { + if ( instr->_arg0 != 0xFF ) + { + PrintStackVar( func, instr, instr->_arg1, buf ); + } + else + { + buf.len--; + } + + break; + } + case _OP_MOVE: + case _OP_NEG: + case _OP_NOT: + case _OP_BWNOT: + case _OP_TYPEOF: + case _OP_RESUME: + case _OP_CLONE: + { + PrintStackTarget( func, instr, buf ); + + switch ( instr->op ) + { + case _OP_MOVE: break; + case _OP_NEG: buf.Put( '-' ); break; + case _OP_NOT: buf.Put( '!' ); break; + case _OP_BWNOT: buf.Put( '~' ); break; + case _OP_TYPEOF: buf.Puts( "typeof " ); break; + case _OP_RESUME: buf.Puts( "resume " ); break; + case _OP_CLONE: buf.Puts( "clone " ); break; + default: UNREACHABLE(); + } + + PrintStackVar( func, instr, instr->_arg1, buf ); + break; + } +#if SQUIRREL_VERSION_NUMBER >= 300 + case _OP_LOADROOT: +#else + case _OP_LOADROOTTABLE: +#endif + case _OP_THROW: + { + PrintStackVar( func, instr, instr->_arg0, buf ); + break; + } + case _OP_LOAD: + { + PrintStackTarget( func, instr, buf ); + PrintLiteral( func, instr->_arg1, buf ); + break; + } + case _OP_DLOAD: + { + PrintStackTarget( func, instr, buf ); + PrintLiteral( func, instr->_arg1, buf ); + buf.Puts( ", " ); + PrintStackVar( func, instr, instr->_arg2, buf ); + buf.Puts( " = " ); + PrintLiteral( func, instr->_arg3, buf ); + break; + } + case _OP_DMOVE: + { + PrintStackTarget( func, instr, buf ); + PrintStackVar( func, instr, instr->_arg1, buf ); + buf.Puts( ", " ); + PrintStackVar( func, instr, instr->_arg2, buf ); + buf.Puts( " = " ); + PrintStackVar( func, instr, instr->_arg3, buf ); + break; + } + case _OP_GETK: + case _OP_PREPCALLK: + { + PrintStackTarget( func, instr, buf ); + PrintStackVar( func, instr, instr->_arg2, buf ); + buf.Puts( "->" ); + PrintLiteral( func, instr->_arg1, buf ); + break; + } + case _OP_PREPCALL: + { + PrintStackTarget( func, instr, buf ); + PrintDeref( func, instr, instr->_arg2, instr->_arg1, buf ); + break; + } + case _OP_DELETE: + case _OP_GET: + case _OP_SET: + { + PrintStackTarget( func, instr, buf ); + PrintDeref( func, instr, instr->_arg1, instr->_arg2, buf ); + + if ( instr->op == _OP_SET ) + { + buf.Puts( " = " ); + PrintStackVar( func, instr, instr->_arg3, buf ); + } + + break; + } + case _OP_NEWSLOT: + { + PrintStackTarget( func, instr, buf ); + PrintDeref( func, instr, instr->_arg1, instr->_arg2, buf ); + buf.Puts( " = " ); + PrintStackVar( func, instr, instr->_arg3, buf ); + break; + } + case _OP_NEWSLOTA: + { + if ( instr->_arg0 & NEW_SLOT_STATIC_FLAG ) + buf.Puts( "static " ); + + PrintDeref( func, instr, instr->_arg1, instr->_arg2, buf ); + buf.Puts( " = " ); + PrintStackVar( func, instr, instr->_arg3, buf ); + + if ( instr->_arg0 & NEW_SLOT_ATTRIBUTES_FLAG ) + { + buf.Puts( ", " ); + PrintStackVar( func, instr, instr->_arg2 - 1, buf ); + } + + break; + } + case _OP_EXISTS: + { + PrintStackTarget( func, instr, buf ); + PrintStackVar( func, instr, instr->_arg2, buf ); + buf.Puts( " in " ); + PrintStackVar( func, instr, instr->_arg1, buf ); + break; + } + case _OP_INSTANCEOF: + { + PrintStackTarget( func, instr, buf ); + PrintStackVar( func, instr, instr->_arg2, buf ); + buf.Puts( " instanceof " ); + PrintStackVar( func, instr, instr->_arg1, buf ); + break; + } + case _OP_AND: + { + PrintStackTarget( func, instr, buf ); + PrintStackVar( func, instr, instr->_arg2, buf ); + buf.Puts( " jz " ); + buf.PutInt( DeduceJumpCount( instr ) ); + break; + } + case _OP_OR: + { + PrintStackTarget( func, instr, buf ); + PrintStackVar( func, instr, instr->_arg2, buf ); + buf.Puts( " jnz " ); + buf.PutInt( DeduceJumpCount( instr ) ); + break; + } + case _OP_JZ: +#if SQUIRREL_VERSION_NUMBER < 300 + case _OP_JNZ: +#endif + { + PrintStackVar( func, instr, instr->_arg0, buf ); + buf.Put( ' ' ); + case _OP_JMP: + buf.PutInt( DeduceJumpCount( instr ) ); + break; + } + case _OP_EQ: + case _OP_NE: + { + PrintStackTarget( func, instr, buf ); + PrintStackVar( func, instr, instr->_arg2, buf ); + + if ( instr->op == _OP_EQ ) + { + buf.Puts( " == " ); + } + else + { + buf.Puts( " != " ); + } + + if ( instr->_arg3 == 0 ) + { + PrintStackVar( func, instr, instr->_arg1, buf ); + } + else + { + PrintLiteral( func, instr->_arg1, buf ); + } + + break; + } +#if SQUIRREL_VERSION_NUMBER >= 300 + case _OP_JCMP: + case _OP_CMP: + { + if ( instr->op == _OP_CMP ) +#else + case _OP_CMP: + { +#endif + PrintStackTarget( func, instr, buf ); + + PrintStackVar( func, instr, instr->_arg2, buf ); + + switch ( instr->_arg3 ) + { + case CMP_G: buf.Puts( " > " ); break; + case CMP_GE: buf.Puts( " >= " ); break; + case CMP_L: buf.Puts( " < " ); break; + case CMP_LE: buf.Puts( " <= " ); break; +#if SQUIRREL_VERSION_NUMBER >= 300 + case CMP_3W: buf.Puts( " <=> " ); break; +#endif + default: UNREACHABLE(); + } + +#if SQUIRREL_VERSION_NUMBER >= 300 + if ( instr->op == _OP_JCMP ) + { + PrintStackVar( func, instr, instr->_arg0, buf ); + buf.Puts( " jz " ); + buf.PutInt( DeduceJumpCount( instr ) ); + } + else +#endif + { + PrintStackVar( func, instr, instr->_arg1, buf ); + } + + break; + } + case _OP_BITW: + { + unsigned char lhs = instr->_arg2; + unsigned char rhs = (unsigned char)instr->_arg1; + + if ( instr->_arg0 != lhs ) + PrintStackTarget( func, instr, buf ); + + PrintStackVar( func, instr, lhs, buf ); + buf.Put( ' ' ); + + switch ( instr->_arg3 ) + { + case BW_AND: buf.Put( '&' ); break; + case BW_OR: buf.Put( '|' ); break; + case BW_XOR: buf.Put( '^' ); break; + case BW_SHIFTL: buf.Puts( "<<" ); break; + case BW_SHIFTR: buf.Puts( ">>" ); break; + case BW_USHIFTR: buf.Puts( ">>>" ); break; + default: UNREACHABLE(); + } + + if ( instr->_arg0 == lhs ) + buf.Put( '=' ); + + buf.Put( ' ' ); + PrintStackVar( func, instr, rhs, buf ); + break; + } +#if SQUIRREL_VERSION_NUMBER >= 300 + case _OP_ADD: + case _OP_SUB: + case _OP_MUL: + case _OP_DIV: + case _OP_MOD: + { + unsigned char lhs = instr->_arg2; + unsigned char rhs = (unsigned char)instr->_arg1; + + if ( instr->_arg0 != lhs ) + PrintStackTarget( func, instr, buf ); + + PrintStackVar( func, instr, lhs, buf ); + buf.Put( ' ' ); + + switch ( instr->op ) + { + case _OP_ADD: buf.Put( '+' ); break; + case _OP_SUB: buf.Put( '-' ); break; + case _OP_MUL: buf.Put( '*' ); break; + case _OP_DIV: buf.Put( '/' ); break; + case _OP_MOD: buf.Put( '%' ); break; + default: UNREACHABLE(); + } + + if ( instr->_arg0 == lhs ) + buf.Put( '=' ); + + buf.Put( ' ' ); + PrintStackVar( func, instr, rhs, buf ); + break; + } +#else + case _OP_ARITH: + { + PrintStackTarget( func, instr, buf ); + PrintStackVar( func, instr, instr->_arg2, buf ); + buf.Put( ' ' ); + buf.Put( instr->_arg3 ); + buf.Put( ' ' ); + PrintStackVar( func, instr, instr->_arg1, buf ); + break; + } + case _OP_COMPARITHL: + { + PrintStackTarget( func, instr, buf ); + PrintStackVar( func, instr, instr->_arg1, buf ); + buf.Put( ' ' ); + buf.Put( instr->_arg3 ); + buf.Put( '=' ); + buf.Put( ' ' ); + PrintStackVar( func, instr, instr->_arg2, buf ); + break; + } +#endif + case _OP_COMPARITH: + { + PrintStackTarget( func, instr, buf ); + PrintDeref( func, instr, + ( (unsigned int)instr->_arg1 & 0xFFFF0000 ) >> 16, + instr->_arg2, + buf ); + buf.Put( ' ' ); + buf.Put( instr->_arg3 ); + buf.Put( '=' ); + buf.Put( ' ' ); + PrintStackVar( func, instr, ( instr->_arg1 & 0x0000FFFF ), buf ); + break; + } + case _OP_INCL: + case _OP_INC: + { + if ( instr->_arg0 != instr->_arg1 ) + PrintStackTarget( func, instr, buf ); + + if ( instr->_arg3 == (unsigned char)-1 ) + { + buf.Puts( "--" ); + } + else + { + buf.Puts( "++" ); + } + + if ( instr->op == _OP_INCL ) + { + PrintStackVar( func, instr, instr->_arg1, buf ); + } + else + { + PrintDeref( func, instr, instr->_arg1, instr->_arg2, buf ); + } + + break; + } + case _OP_PINCL: + case _OP_PINC: + { + PrintStackTarget( func, instr, buf ); + + if ( instr->op == _OP_PINCL ) + { + PrintStackVar( func, instr, instr->_arg1, buf ); + } + else + { + PrintDeref( func, instr, instr->_arg1, instr->_arg2, buf ); + } + + if ( instr->_arg3 == (unsigned char)-1 ) + { + buf.Puts( "--" ); + } + else + { + buf.Puts( "++" ); + } + + break; + } +#if SQUIRREL_VERSION_NUMBER >= 300 + case _OP_SETOUTER: + case _OP_GETOUTER: +#else + case _OP_LOADFREEVAR: +#endif + { + PrintStackTarget( func, instr, buf ); + PrintOuter( func, instr->_arg1, buf ); + +#if SQUIRREL_VERSION_NUMBER >= 300 + if ( instr->op == _OP_SETOUTER ) + { + buf.Puts( " = " ); + PrintStackVar( func, instr, instr->_arg2, buf ); + } +#endif + break; + } + case _OP_CLOSURE: + { + PrintStackTargetVar( func, instr, buf ); + +#if SQUIRREL_VERSION_NUMBER >= 300 + if ( instr->_arg2 != 0xFF ) + { + buf.Put( ' ' ); + PrintStackVar( func, instr, instr->_arg2, buf ); + } +#endif + break; + } +#if SQUIRREL_VERSION_NUMBER >= 300 + case _OP_APPENDARRAY: + { + PrintStackVar( func, instr, instr->_arg0, buf ); + buf.Puts( " <- " ); + + switch ( instr->_arg2 ) + { + case AAT_STACK: PrintStackVar( func, instr, instr->_arg1, buf ); break; + case AAT_INT: buf.PutInt( instr->_arg1 ); break; + case AAT_FLOAT: goto LFLOAT; + case AAT_BOOL: goto LBOOL; + case AAT_LITERAL: PrintLiteral( func, instr->_arg1, buf ); break; + default: UNREACHABLE(); + } + + break; + } + case _OP_NEWOBJ: + { + PrintStackTarget( func, instr, buf ); + + switch ( instr->_arg3 ) + { + case NOT_TABLE: + { + buf.Puts( "TABLE " ); + buf.PutInt( instr->_arg1 ); + break; + } + case NOT_ARRAY: + { + buf.Puts( "ARRAY " ); + buf.PutInt( instr->_arg1 ); + break; + } + case NOT_CLASS: + { + buf.Puts( "CLASS" ); + + if ( instr->_arg1 != -1 ) + { + buf.Puts( " extends " ); + PrintStackVar( func, instr, instr->_arg1, buf ); + } + + break; + } + default: UNREACHABLE(); + } + + break; + } +#else + case _OP_APPENDARRAY: + { + PrintStackVar( func, instr, instr->_arg0, buf ); + buf.Puts( " <- " ); + + if ( instr->_arg3 != 0 ) + { + PrintLiteral( func, instr->_arg1, buf ); + } + else + { + PrintStackVar( func, instr, instr->_arg1, buf ); + } + + break; + } + case _OP_CLASS: + { + PrintStackTargetVar( func, instr, buf ); + + if ( instr->_arg1 != -1 ) + { + buf.Puts( " extends " ); + PrintStackVar( func, instr, instr->_arg1, buf ); + } + + break; + } + case _OP_NEWTABLE: + case _OP_NEWARRAY: + { + PrintStackTargetVar( func, instr, buf ); + buf.Put( ' ' ); + buf.PutInt( instr->_arg1 ); + break; + } + case _OP_DELEGATE: + { + PrintStackTarget( func, instr, buf ); + PrintStackVar( func, instr, instr->_arg1, buf ); + buf.Puts( " <- " ); + PrintStackVar( func, instr, instr->_arg2, buf ); + break; + } + case _OP_VARGC: + { + PrintStackVar( func, instr, instr->_arg0, buf ); + break; + } + case _OP_GETVARGV: + { + PrintStackTarget( func, instr, buf ); + buf.Puts( "vargv" ); + buf.Puts( "->" ); + PrintStackVar( func, instr, instr->_arg1, buf ); + break; + } +#endif + default: + { + buf.len--; + } + } +#endif +} + +bool SQDebugServer::IsValidStackFrame( HSQUIRRELVM vm, int id ) +{ + Assert( !!vm->_callsstacksize == !!vm->ci ); + return id >= 0 && id < vm->_callsstacksize && vm->_callsstacksize > 0; +} + +SQVM::CallInfo *SQDebugServer::GetStackFrame( HSQUIRRELVM vm, int id ) +{ + Assert( !!vm->_callsstacksize == !!vm->ci ); + + if ( id >= 0 && id < vm->_callsstacksize && vm->_callsstacksize > 0 ) + return &vm->_callsstack[id]; + + return NULL; +} + +int SQDebugServer::GetStackBase( HSQUIRRELVM vm, const SQVM::CallInfo *ci ) +{ + Assert( ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize ); + + int stackbase = 0; + for ( ; ci >= vm->_callsstack; ci-- ) + stackbase += ci->_prevstkbase; + return stackbase; +} + +void SQDebugServer::InitEnv_GetVal( SQObjectPtr &env ) +{ + Assert( sq_type(env) == OT_NULL || + ( sq_type(env) == OT_TABLE && _table(env)->_delegate && !_table(env)->_delegate->_delegate ) ); + + SQTable *mt; + + if ( sq_type(env) != OT_TABLE ) + { + SQObjectPtr _null; + + mt = SQTable::Create( _ss(m_pRootVM), 6 ); + mt->NewSlot( m_sqstrCallFrame, _null ); + mt->NewSlot( m_sqstrDelegate, _null ); + + Assert( sq_type(env) == OT_NULL ); + env = SQTable::Create( _ss(m_pRootVM), 0 ); + _table(env)->SetDelegate( mt ); + } + else + { + mt = _table(env)->_delegate; + } + +#ifdef CLOSURE_ROOT + mt->NewSlot( m_sqstrRoot, m_pRootVM->_roottable ); +#endif + mt->NewSlot( CreateSQString( m_pRootVM, _SC("_get") ), m_sqfnGet ); + mt->NewSlot( CreateSQString( m_pRootVM, _SC("_set") ), m_sqfnSet ); + mt->NewSlot( CreateSQString( m_pRootVM, _SC("_newslot") ), m_sqfnNewSlot ); +} + +void SQDebugServer::SetCallFrame( SQObjectPtr &env, HSQUIRRELVM vm, const SQVM::CallInfo *ci ) +{ + Assert( sq_type(env) == OT_TABLE ); + Assert( _table(env)->_delegate ); + + SQObjectPtr frame = (SQInteger)( ci - vm->_callsstack ); + Assert( _integer(frame) >= 0 && _integer(frame) < vm->_callsstacksize ); + Verify( _table(env)->_delegate->Set( m_sqstrCallFrame, frame ) ); +} + +void SQDebugServer::SetEnvDelegate( SQObjectPtr &env, const SQObject &delegate ) +{ + Assert( sq_type(env) == OT_TABLE ); + Assert( _table(env)->_delegate ); + + SQObjectPtr weakref; + + if ( ISREFCOUNTED( sq_type(delegate) ) ) + weakref = GetWeakRef( _refcounted(delegate), sq_type(delegate) ); + + Verify( _table(env)->_delegate->Set( m_sqstrDelegate, weakref ) ); +} + +void SQDebugServer::ClearEnvDelegate( SQObjectPtr &env ) +{ + Assert( sq_type(env) == OT_TABLE ); + Assert( _table(env)->_delegate ); + + SQObjectPtr _null; + _table(env)->_delegate->Set( m_sqstrDelegate, _null ); +} + +#ifdef CLOSURE_ROOT +void SQDebugServer::SetEnvRoot( SQObjectPtr &env, const SQObjectPtr &root ) +{ + Assert( sq_type(env) == OT_TABLE ); + Assert( _table(env)->_delegate ); + Assert( sq_type(root) == OT_TABLE || + ( sq_type(root) == OT_WEAKREF && sq_type(_weakref(root)->_obj) == OT_TABLE ) ); + + Verify( _table(env)->_delegate->Set( m_sqstrRoot, root ) ); +} +#endif + +bool SQDebugServer::RunExpression( const string_t &expression, HSQUIRRELVM vm, const SQVM::CallInfo *ci, + SQObjectPtr &out, bool multiline ) +{ + Assert( !ci || ( ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize ) ); + + // Fallback to root on native stack frame + if ( !ci || sq_type(ci->_closure) != OT_CLOSURE ) +#ifdef CLOSURE_ROOT + return RunScript( vm, expression, NULL, NULL, out, multiline ); +#else + return RunScript( vm, expression, NULL, out, multiline ); +#endif + +#ifdef CLOSURE_ROOT + bool bRoot = _closure(ci->_closure)->_root && + _table(_closure(ci->_closure)->_root->_obj) != _table(m_pRootVM->_roottable); + + if ( bRoot ) + SetEnvRoot( m_EnvGetVal, _closure(ci->_closure)->_root ); +#endif + + SetCallFrame( m_EnvGetVal, vm, ci ); + SetEnvDelegate( m_EnvGetVal, vm->_stack._vals[ GetStackBase( vm, ci ) ] ); + +#ifdef CLOSURE_ROOT + SQWeakRef *root = bRoot ? _closure(ci->_closure)->_root : NULL; + bool ret = RunScript( vm, expression, root, &m_EnvGetVal, out, multiline ); +#else + bool ret = RunScript( vm, expression, &m_EnvGetVal, out, multiline ); +#endif + +#ifdef CLOSURE_ROOT + if ( bRoot ) + SetEnvRoot( m_EnvGetVal, m_pRootVM->_roottable ); +#endif + + return ret; +} + +bool SQDebugServer::CompileScript( const string_t &script, SQObjectPtr &out ) +{ + const bool multiline = false; + unsigned int size; + SQChar *buf, *scratch; + + if ( !multiline ) + { +#ifdef SQUNICODE + size = sq_rsl( STRLEN("return()") + SQUnicodeLength( script.ptr, script.len ) + 1 ); +#else + size = STRLEN("return()") + script.len + 1; +#endif + buf = (SQChar*)ScratchPad( size ); + scratch = buf; + + if ( !buf ) + return false; + + memcpy( buf, _SC("return("), sq_rsl( STRLEN("return(") ) ); // )) + buf += STRLEN("return("); + } + else + { +#ifdef SQUNICODE + size = sq_rsl( SQUnicodeLength( script.ptr, script.len ) + 1 ); +#else + size = script.len + 1; +#endif + buf = (SQChar*)ScratchPad( size ); + scratch = buf; + + if ( !buf ) + return false; + } + +#ifdef SQUNICODE + buf += UTF8ToSQUnicode( buf, size - ( (char*)buf - (char*)scratch ), script.ptr, script.len ); +#else + memcpy( buf, script.ptr, script.len ); + buf += script.len; +#endif + + if ( !multiline ) + *buf++ = _SC(')'); + + *buf = 0; + + Assert( ( (char*)buf - (char*)scratch ) == (int)( size - sq_rsl(1) ) ); + + if ( SQ_SUCCEEDED( sq_compilebuffer( m_pCurVM, scratch, size, _SC("sqdbg"), SQFalse ) ) ) + { + // Don't create varargs on calls + SQFunctionProto *fn = _fp(_closure(m_pCurVM->Top())->_function); + if ( fn->_varparams ) + { + fn->_varparams = false; +#if SQUIRREL_VERSION_NUMBER >= 300 + fn->_nparameters--; +#endif + } + + out = m_pCurVM->Top(); + m_pCurVM->Pop(); + return true; + } + + return false; +} + +bool SQDebugServer::RunScript( HSQUIRRELVM vm, const string_t &script, +#ifdef CLOSURE_ROOT + SQWeakRef *root, +#endif + const SQObject *env, SQObjectPtr &out, bool multiline ) +{ + unsigned int size; + SQChar *buf, *scratch; + + if ( !multiline ) + { +#ifdef SQUNICODE + size = sq_rsl( STRLEN("return()") + SQUnicodeLength( script.ptr, script.len ) + 1 ); +#else + size = STRLEN("return()") + script.len + 1; +#endif + buf = (SQChar*)ScratchPad( size ); + scratch = buf; + + if ( !buf ) + return false; + + memcpy( buf, _SC("return("), sq_rsl( STRLEN("return(") ) ); // )) + buf += STRLEN("return("); + } + else + { +#ifdef SQUNICODE + size = sq_rsl( SQUnicodeLength( script.ptr, script.len ) + 1 ); +#else + size = script.len + 1; +#endif + buf = (SQChar*)ScratchPad( size ); + scratch = buf; + + if ( !buf ) + return false; + } + +#ifdef SQUNICODE + buf += UTF8ToSQUnicode( buf, size - ( (char*)buf - (char*)scratch ), script.ptr, script.len ); +#else + memcpy( buf, script.ptr, script.len ); + buf += script.len; +#endif + + if ( !multiline ) + *buf++ = _SC(')'); + + *buf = 0; + + Assert( ( (char*)buf - (char*)scratch ) == (int)( size - sq_rsl(1) ) ); + + if ( SQ_SUCCEEDED( sq_compilebuffer( vm, scratch, size, _SC("sqdbg"), SQFalse ) ) ) + { + // Don't create varargs on calls + SQFunctionProto *fn = _fp(_closure(vm->Top())->_function); + if ( fn->_varparams ) + { + fn->_varparams = false; +#if SQUIRREL_VERSION_NUMBER >= 300 + fn->_nparameters--; +#endif + } + + CCallGuard cg( this, vm ); + + // m_pCurVM will incorrectly change if a script is executed on a different thread. + // save and restore + HSQUIRRELVM curvm = m_pCurVM; + +#ifdef CLOSURE_ROOT + if ( root ) + _closure(vm->Top())->SetRoot( root ); +#endif + + vm->Push( env ? *env : vm->_roottable ); + + if ( SQ_SUCCEEDED( sq_call( vm, 1, SQTrue, SQFalse ) ) ) + { + m_pCurVM = curvm; + out = vm->Top(); + vm->Pop(); + vm->Pop(); + return true; + } + + m_pCurVM = curvm; + vm->Pop(); + } + + return false; +} + +bool SQDebugServer::RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + SQObjectPtr &ret ) +{ + vm->Push( closure ); + vm->Push( env ? *env : vm->_roottable ); + + if ( SQ_SUCCEEDED( sq_call( vm, 1, SQTrue, SQFalse ) ) ) + { + ret = vm->Top(); + vm->Pop(); + vm->Pop(); + return true; + } + + vm->Pop(); + return false; +} + +bool SQDebugServer::RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, SQObjectPtr &ret ) +{ + vm->Push( closure ); + vm->Push( env ? *env : vm->_roottable ); + vm->Push( p1 ); + + if ( SQ_SUCCEEDED( sq_call( vm, 2, SQTrue, SQFalse ) ) ) + { + ret = vm->Top(); + vm->Pop(); + vm->Pop(); + return true; + } + + vm->Pop(); + return false; +} + +bool SQDebugServer::RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, SQObjectPtr &ret ) +{ + vm->Push( closure ); + vm->Push( env ? *env : vm->_roottable ); + vm->Push( p1 ); + vm->Push( p2 ); + + if ( SQ_SUCCEEDED( sq_call( vm, 3, SQTrue, SQFalse ) ) ) + { + ret = vm->Top(); + vm->Pop(); + vm->Pop(); + return true; + } + + vm->Pop(); + return false; +} + +bool SQDebugServer::RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, const SQObjectPtr &p3, SQObjectPtr &ret ) +{ + vm->Push( closure ); + vm->Push( env ? *env : vm->_roottable ); + vm->Push( p1 ); + vm->Push( p2 ); + vm->Push( p3 ); + + if ( SQ_SUCCEEDED( sq_call( vm, 4, SQTrue, SQFalse ) ) ) + { + ret = vm->Top(); + vm->Pop(); + vm->Pop(); + return true; + } + + vm->Pop(); + return false; +} + +bool SQDebugServer::RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, const SQObjectPtr &p3, const SQObjectPtr &p4, + SQObjectPtr &ret ) +{ + vm->Push( closure ); + vm->Push( env ? *env : vm->_roottable ); + vm->Push( p1 ); + vm->Push( p2 ); + vm->Push( p3 ); + vm->Push( p4 ); + + if ( SQ_SUCCEEDED( sq_call( vm, 5, SQTrue, SQFalse ) ) ) + { + ret = vm->Top(); + vm->Pop(); + vm->Pop(); + return true; + } + + vm->Pop(); + return false; +} + +bool SQDebugServer::RunClosure( HSQUIRRELVM vm, const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &p1, const SQObjectPtr &p2, const SQObjectPtr &p3, const SQObjectPtr &p4, + const SQObjectPtr &p5, SQObjectPtr &ret ) +{ + vm->Push( closure ); + vm->Push( env ? *env : vm->_roottable ); + vm->Push( p1 ); + vm->Push( p2 ); + vm->Push( p3 ); + vm->Push( p4 ); + vm->Push( p5 ); + + if ( SQ_SUCCEEDED( sq_call( vm, 6, SQTrue, SQFalse ) ) ) + { + ret = vm->Top(); + vm->Pop(); + vm->Pop(); + return true; + } + + vm->Pop(); + return false; +} + +SQInteger SQDebugServer::SQMM_Get( HSQUIRRELVM vm ) +{ + SQObjectPtr value; + HSQOBJECT mtenv, index; + sq_getstackobj( vm, -2, &mtenv ); + sq_getstackobj( vm, -1, &index ); + + Assert( sq_type(mtenv) == OT_TABLE && _table(mtenv)->_delegate ); + SQObjectPtr frame; + Verify( SQTable_Get( _table(mtenv)->_delegate, _SC(KW_CALLFRAME), frame ) ); + Assert( sq_type(frame) == OT_INTEGER && _integer(frame) >= 0 && _integer(frame) < vm->_callsstacksize ); + + if ( !( _integer(frame) >= 0 && _integer(frame) < vm->_callsstacksize ) ) + { + vm->Raise_Error( _SC("invalid call frame") ); + return -1; + } + + if ( GetVariable( vm, vm->_callsstack + _integer(frame), mtenv, index, value ) ) + { + sq_pushobject( vm, value ); + return 1; + } + + vm->Raise_IdxError( index ); + return -1; +} + +SQInteger SQDebugServer::SQMM_Set( HSQUIRRELVM vm ) +{ + HSQOBJECT mtenv, index, value; + sq_getstackobj( vm, -3, &mtenv ); + sq_getstackobj( vm, -2, &index ); + sq_getstackobj( vm, -1, &value ); + + Assert( sq_type(mtenv) == OT_TABLE && _table(mtenv)->_delegate ); + SQObjectPtr frame; + Verify( SQTable_Get( _table(mtenv)->_delegate, _SC(KW_CALLFRAME), frame ) ); + Assert( sq_type(frame) == OT_INTEGER && _integer(frame) >= 0 && _integer(frame) < vm->_callsstacksize ); + + if ( !( _integer(frame) >= 0 && _integer(frame) < vm->_callsstacksize ) ) + { + vm->Raise_Error( _SC("invalid call frame") ); + return -1; + } + + if ( SetVariable( vm, vm->_callsstack + _integer(frame), mtenv, index, value ) ) + return 0; + + vm->Raise_IdxError( index ); + return -1; +} + +SQInteger SQDebugServer::SQMM_NewSlot( HSQUIRRELVM vm ) +{ + HSQOBJECT mtenv, index, value; + sq_getstackobj( vm, -3, &mtenv ); + sq_getstackobj( vm, -2, &index ); + sq_getstackobj( vm, -1, &value ); + + if ( NewSlot( vm, mtenv, index, value ) ) + return 0; + + vm->Raise_Error( _SC("could not create new slot") ); + return -1; +} + +bool SQDebugServer::GetVariable( HSQUIRRELVM vm, const SQVM::CallInfo *ci, + const SQObject &mtenv, const SQObject &index, SQObjectPtr &value ) +{ + Assert( !ci || ( ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize ) ); + + // locals + if ( ci && sq_type(ci->_closure) == OT_CLOSURE && sq_type(index) == OT_STRING ) + { + SQClosure *pClosure = _closure(ci->_closure); + SQFunctionProto *func = _fp(pClosure->_function); + SQUnsignedInteger ip = (SQUnsignedInteger)( ci->_ip - func->_instructions - 1 ); + + for ( int i = 0; i < func->_nlocalvarinfos; i++ ) + { + const SQLocalVarInfo &var = func->_localvarinfos[i]; + if ( var._start_op <= ip + 1 && var._end_op >= ip && + _string(index) == _string(var._name) ) + { + int stackbase = GetStackBase( vm, ci ); + value = vm->_stack._vals[ stackbase + var._pos ]; + return true; + } + } + + for ( int i = 0; i < func->_noutervalues; i++ ) + { + const SQOuterVar &var = func->_outervalues[i]; + if ( _string(index) == _string(var._name) ) + { + value = *_outervalptr( pClosure->_outervalues[i] ); + return true; + } + } + + // this/vargv keywords compile in the temp env, add custom keywords to redirect + // Having locals named __this/__vargv will break this hack + if ( _string(index)->_len == 6 || _string(index)->_len == 7 ) + { + if ( sqstring_t(_SC(KW_THIS)).IsEqualTo( _string(index) ) ) + { + int stackbase = GetStackBase( vm, ci ); + value = vm->_stack._vals[ stackbase ]; + return true; + } +#if SQUIRREL_VERSION_NUMBER >= 300 + else if ( func->_varparams && func->_nlocalvarinfos >= 2 ) + { + if ( sqstring_t(_SC(KW_VARGV)).IsEqualTo( _string(index) ) ) + { + const SQLocalVarInfo &var = func->_localvarinfos[ func->_nlocalvarinfos - 2 ]; + if ( sqstring_t(_SC("vargv")).IsEqualTo( _string(var._name) ) ) + { + int stackbase = GetStackBase( vm, ci ); + value = vm->_stack._vals[ stackbase + 1 ]; + return true; + } + } + else if ( sqstring_t(_SC(KW_VARGC)).IsEqualTo( _string(index) ) ) + { + const SQLocalVarInfo &var = func->_localvarinfos[ func->_nlocalvarinfos - 2 ]; + if ( sqstring_t(_SC("vargv")).IsEqualTo( _string(var._name) ) ) + { + int stackbase = GetStackBase( vm, ci ); + value = vm->_stack._vals[ stackbase + 1 ]; + + if ( sq_type(value) == OT_ARRAY ) + { + value = _array(value)->Size(); + return true; + } + + value.Null(); + return false; + } + } + } +#else + else if ( func->_varparams ) + { + if ( sqstring_t(_SC(KW_VARGV)).IsEqualTo( _string(index) ) ) + { + int size = ci->_vargs.size; + if ( !size ) + return false; + + // This could alternatively register a userdata or table with _get that returns varg params + SQArray *arr = SQArray::Create( _ss(vm), size ); + + Assert( arr->Size() == size ); + + for ( int i = 0; i < size; i++ ) + { + const SQObjectPtr &val = vm->_vargsstack[ ci->_vargs.base + i ]; + arr->_values[i] = val; + } + + value = arr; + return true; + } + else if ( sqstring_t(_SC(KW_VARGC)).IsEqualTo( _string(index) ) ) + { + value = (SQInteger)ci->_vargs.size; + return true; + } + } +#endif + } + } + + Assert( sq_type(mtenv) == OT_TABLE && _table(mtenv)->_delegate ); + + // env + SQObjectPtr env; + Verify( SQTable_Get( _table(mtenv)->_delegate, _SC(KW_DELEGATE), env ) ); + + switch ( sq_type(env) ) + { + case OT_TABLE: + { + SQTable *t = _table(env); + do + { + if ( t->Get( index, value ) ) + { + return true; + } + } + while ( ( t = t->_delegate ) != NULL ); + + break; + } + case OT_INSTANCE: + { + if ( _instance(env)->Get( index, value ) ) + { + return true; + } + + break; + } + case OT_CLASS: + { + if ( _class(env)->Get( index, value ) ) + { + return true; + } + + break; + } + default: break; + } + + // metamethods + if ( is_delegable(env) && _delegable(env)->_delegate ) + { + SQObjectPtr mm; + if ( _delegable(env)->GetMetaMethod( vm, MT_GET, mm ) ) + { + if ( RunClosure( vm, mm, &env, index, value ) ) + { + return true; + } + } + } + + // root +#ifdef CLOSURE_ROOT + SQObjectPtr root; + Verify( SQTable_Get( _table(mtenv)->_delegate, _SC(KW_ROOT), root ) ); + + if ( sq_type(root) == OT_TABLE ) // else the user invalidated it, reconnect the client to fix +#else + const SQObjectPtr &root = vm->_roottable; +#endif + { + SQTable *t = _table(root); + do + { + if ( t->Get( index, value ) ) + { + return true; + } + } + while ( ( t = t->_delegate ) != NULL ); + } + + return false; +} + +bool SQDebugServer::SetVariable( HSQUIRRELVM vm, const SQVM::CallInfo *ci, + const SQObject &mtenv, const SQObject &index, const SQObject &value ) +{ + Assert( !ci || ( ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize ) ); + + // locals + if ( ci && sq_type(ci->_closure) == OT_CLOSURE && sq_type(index) == OT_STRING ) + { + SQClosure *pClosure = _closure(ci->_closure); + SQFunctionProto *func = _fp(pClosure->_function); + SQUnsignedInteger ip = (SQUnsignedInteger)( ci->_ip - func->_instructions - 1 ); + + for ( int i = 0; i < func->_nlocalvarinfos; i++ ) + { + const SQLocalVarInfo &var = func->_localvarinfos[i]; + if ( var._start_op <= ip + 1 && var._end_op >= ip && + _string(index) == _string(var._name) ) + { + int stackbase = GetStackBase( vm, ci ); + vm->_stack._vals[ stackbase + var._pos ] = value; + return true; + } + } + + for ( int i = 0; i < func->_noutervalues; i++ ) + { + const SQOuterVar &var = func->_outervalues[i]; + if ( _string(index) == _string(var._name) ) + { + *_outervalptr( pClosure->_outervalues[i] ) = value; + return true; + } + } + } + + Assert( sq_type(mtenv) == OT_TABLE && _table(mtenv)->_delegate ); + + // env + SQObjectPtr env; + Verify( SQTable_Get( _table(mtenv)->_delegate, _SC(KW_DELEGATE), env ) ); + + switch ( sq_type(env) ) + { + case OT_TABLE: + { + SQTable *t = _table(env); + do + { + if ( t->Set( index, value ) ) + { + return true; + } + } + while ( ( t = t->_delegate ) != NULL ); + + break; + } + case OT_INSTANCE: + { + if ( _instance(env)->Set( index, value ) ) + { + return true; + } + + break; + } + case OT_CLASS: + { + return _class(env)->NewSlot( _ss(vm), index, value, false ); + } + default: break; + } + + // metamethods + if ( is_delegable(env) && _delegable(env)->_delegate ) + { + SQObjectPtr mm; + if ( _delegable(env)->GetMetaMethod( vm, MT_SET, mm ) ) + { + SQObjectPtr dummy; + if ( RunClosure( vm, mm, &env, index, value, dummy ) ) + { + return true; + } + } + } + + // root +#ifdef CLOSURE_ROOT + SQObjectPtr root; + Verify( SQTable_Get( _table(mtenv)->_delegate, _SC(KW_ROOT), root ) ); + + if ( sq_type(root) == OT_TABLE ) // else the user invalidated it, reconnect the client to fix +#else + const SQObjectPtr &root = vm->_roottable; +#endif + { + SQTable *t = _table(root); + do + { + if ( t->Set( index, value ) ) + { + return true; + } + } + while ( ( t = t->_delegate ) != NULL ); + } + + return false; +} + +bool SQDebugServer::NewSlot( HSQUIRRELVM vm, const SQObject &mtenv, const SQObject &index, const SQObject &value ) +{ + Assert( sq_type(mtenv) == OT_TABLE && _table(mtenv)->_delegate ); + + // env + SQObjectPtr env; + Verify( SQTable_Get( _table(mtenv)->_delegate, _SC(KW_DELEGATE), env ) ); + + Assert( sq_type(env) != OT_ARRAY ); + + if ( is_delegable(env) && _delegable(env)->_delegate ) + { + SQObjectPtr mm; + if ( _delegable(env)->GetMetaMethod( vm, MT_NEWSLOT, mm ) ) + { + SQObjectPtr dummy; + if ( RunClosure( vm, mm, &env, index, value, dummy ) ) + { + return true; + } + } + } + + switch ( sq_type(env) ) + { + case OT_TABLE: + _table(env)->NewSlot( index, value ); + return true; + case OT_CLASS: + return _class(env)->NewSlot( _ss(vm), index, value, false ); + default: + return false; + } +} + +bool SQDebugServer::Get( const objref_t &obj, SQObjectPtr &value ) +{ + if ( obj.type & objref_t::PTR ) + { + value = *obj.ptr; + return true; + } + + Assert( !( obj.type & objref_t::READONLY ) ); + + switch ( obj.type ) + { + case objref_t::TABLE: + { + return sq_type(obj.src) == OT_TABLE && + _table(obj.src)->Get( obj.key, value ); + } + case objref_t::INSTANCE: + { + return sq_type(obj.src) == OT_INSTANCE && + _instance(obj.src)->Get( obj.key, value ); + } + case objref_t::CLASS: + { + return sq_type(obj.src) == OT_CLASS && + _class(obj.src)->Get( obj.key, value ); + } + case objref_t::DELEGABLE_META: + { + if ( is_delegable(obj.src) && _delegable(obj.src)->_delegate ) + { + SQObjectPtr mm; + if ( _delegable(obj.src)->GetMetaMethod( m_pRootVM, MT_GET, mm ) ) + { + return RunClosure( mm, &obj.src, obj.key, value ); + } + } + + return false; + } + case objref_t::ARRAY: + { + Assert( sq_type(obj.key) == OT_INTEGER ); + + if ( sq_type(obj.src) == OT_ARRAY && + _integer(obj.key) >= 0 && + _integer(obj.key) < _array(obj.src)->Size() ) + { + value = _array(obj.src)->_values[ _integer(obj.key) ]; + return true; + } + + return false; + } + case objref_t::CUSTOMMEMBER: + { + if ( sq_type(obj.src) == OT_INSTANCE ) + { + const SQObjectPtr *def = GetClassDefCustomMembers( _instance(obj.src)->_class ); + + if ( def ) + { + SQObjectPtr custommembers = *def; + + if ( sq_type(custommembers) == OT_CLOSURE ) + RunClosure( custommembers, &obj.src, custommembers ); + + if ( sq_type(custommembers) == OT_ARRAY ) + { + objref_t tmp; + SQObjectPtr strName = CreateSQString( m_pRootVM, _SC("name") ); + SQObjectPtr strGet = CreateSQString( m_pRootVM, _SC("get") ); + SQObjectPtr name, get; + + for ( unsigned int i = 0; i < _array(custommembers)->_values.size(); i++ ) + { + const SQObjectPtr &memdef = _array(custommembers)->_values[i]; + + if ( GetObj_Var( memdef, strName, tmp, name ) && + IsEqual( obj.key, name ) ) + { + return GetObj_Var( memdef, strGet, tmp, get ) && + CallCustomMembersGetFunc( get, &obj.src, obj.key, value ); + } + } + } + } + } + + return false; + } + case objref_t::STACK: + { + HSQUIRRELVM vm = GetThread( obj.stack.thread ); + + if ( vm && obj.stack.frame >= 0 && obj.stack.frame <= vm->ci - vm->_callsstack ) + { + const SQVM::CallInfo &ci = vm->_callsstack[ obj.stack.frame ]; + if ( sq_type(ci._closure) == OT_CLOSURE && + ( ci._ip - _fp(_closure(ci._closure)->_function)->_instructions <= obj.stack.end ) && + obj.stack.index >= 0 && obj.stack.index < (int)vm->_stack.size() ) + { + value = vm->_stack._vals[ obj.stack.index ]; + return true; + } + } + + return false; + } + case objref_t::INT: + { + value = (SQInteger)obj.val; + return true; + } + default: UNREACHABLE(); + } +} + +bool SQDebugServer::Set( const objref_t &obj, const SQObjectPtr &value ) +{ + if ( obj.type & objref_t::READONLY ) + return false; + + if ( obj.type & objref_t::PTR ) + { + *(obj.ptr) = value; + return true; + } + + switch ( obj.type ) + { + case objref_t::TABLE: + { + return sq_type(obj.src) == OT_TABLE && + _table(obj.src)->Set( obj.key, value ); + } + case objref_t::INSTANCE: + { + return sq_type(obj.src) == OT_INSTANCE && + _instance(obj.src)->Set( obj.key, value ); + } + case objref_t::CLASS: + { + SQObjectPtr idx; + + if ( sq_type(obj.src) == OT_CLASS && + _class(obj.src)->_members->Get( obj.key, idx ) ) + { + if ( _isfield(idx) ) + { + _class(obj.src)->_defaultvalues[ _member_idx(idx) ].val = value; + } + else + { + _class(obj.src)->_methods[ _member_idx(idx) ].val = value; + } + + return true; + } + + return false; + } + case objref_t::DELEGABLE_META: + { + if ( is_delegable(obj.src) && _delegable(obj.src)->_delegate ) + { + SQObjectPtr mm; + if ( _delegable(obj.src)->GetMetaMethod( m_pRootVM, MT_SET, mm ) ) + { + SQObjectPtr dummy; + return RunClosure( mm, &obj.src, obj.key, value, dummy ); + } + } + + return false; + } + case objref_t::ARRAY: + { + Assert( sq_type(obj.key) == OT_INTEGER ); + + if ( sq_type(obj.src) == OT_ARRAY && + _integer(obj.key) >= 0 && + _integer(obj.key) < _array(obj.src)->Size() ) + { + _array(obj.src)->_values[ _integer(obj.key) ] = value; + return true; + } + + return false; + } + case objref_t::CUSTOMMEMBER: + { + if ( sq_type(obj.src) == OT_INSTANCE ) + { + const SQObjectPtr *def = GetClassDefCustomMembers( _instance(obj.src)->_class ); + + if ( def ) + { + SQObjectPtr custommembers = *def; + + if ( sq_type(custommembers) == OT_CLOSURE ) + RunClosure( custommembers, &obj.src, custommembers ); + + if ( sq_type(custommembers) == OT_ARRAY ) + { + objref_t tmp; + SQObjectPtr strName = CreateSQString( m_pRootVM, _SC("name") ); + SQObjectPtr strSet = CreateSQString( m_pRootVM, _SC("set") ); + SQObjectPtr name, set; + + for ( unsigned int i = 0; i < _array(custommembers)->_values.size(); i++ ) + { + const SQObjectPtr &memdef = _array(custommembers)->_values[i]; + + if ( GetObj_Var( memdef, strName, tmp, name ) && + IsEqual( obj.key, name ) ) + { + return GetObj_Var( memdef, strSet, tmp, set ) && + CallCustomMembersSetFunc( set, &obj.src, obj.key, value, set ); + } + } + } + } + } + + return false; + } + case objref_t::STACK: + { + HSQUIRRELVM vm = GetThread( obj.stack.thread ); + + if ( vm && obj.stack.frame >= 0 && obj.stack.frame <= vm->ci - vm->_callsstack ) + { + const SQVM::CallInfo &ci = vm->_callsstack[ obj.stack.frame ]; + if ( sq_type(ci._closure) == OT_CLOSURE && + ( ci._ip - _fp(_closure(ci._closure)->_function)->_instructions <= obj.stack.end ) && + obj.stack.index >= 0 && obj.stack.index < (int)vm->_stack.size() ) + { + vm->_stack._vals[ obj.stack.index ] = value; + return true; + } + } + + return false; + } + case objref_t::INT: + { + return false; + } + default: UNREACHABLE(); + } +} + +#ifndef SQDBG_DISABLE_COMPILER +bool SQDebugServer::NewSlot( const objref_t &obj, const SQObjectPtr &value ) +{ + if ( obj.type & objref_t::READONLY ) + return false; + + if ( is_delegable(obj.src) && _delegable(obj.src)->_delegate ) + { + SQObjectPtr mm; + if ( _delegable(obj.src)->GetMetaMethod( m_pRootVM, MT_NEWSLOT, mm ) ) + { + SQObjectPtr dummy; + if ( RunClosure( mm, &obj.src, obj.key, value, dummy ) ) + return CompileReturnCode_Success; + } + } + + switch ( obj.type ) + { + case objref_t::TABLE: + { + if ( sq_type(obj.src) == OT_TABLE ) + { + _table(obj.src)->NewSlot( obj.key, value ); + return true; + } + + return false; + } + case objref_t::CLASS: + { + return sq_type(obj.src) == OT_CLASS && + _class(obj.src)->NewSlot( _ss(m_pRootVM), obj.key, value, false ); + } + default: + return false; + } +} + +bool SQDebugServer::Delete( const objref_t &obj, SQObjectPtr &value ) +{ + if ( obj.type & objref_t::READONLY ) + return false; + + if ( is_delegable(obj.src) && _delegable(obj.src)->_delegate ) + { + SQObjectPtr mm; + if ( _delegable(obj.src)->GetMetaMethod( m_pRootVM, MT_DELSLOT, mm ) ) + { + return RunClosure( mm, &obj.src, obj.key, value ); + } + } + + switch ( obj.type ) + { + case objref_t::TABLE: + { + if ( sq_type(obj.src) == OT_TABLE && + _table(obj.src)->Get( obj.key, value ) ) + { + _table(obj.src)->Remove( obj.key ); + return true; + } + } + default: + return false; + } +} + +bool SQDebugServer::Increment( const objref_t &obj, int amt ) +{ +#define _check(var) \ + switch ( sq_type(var) ) \ + { \ + case OT_INTEGER: \ + _integer(var) += (SQInteger)amt; \ + break; \ + case OT_FLOAT: \ + _float(var) += (SQFloat)amt; \ + break; \ + default: \ + return false; \ + } + + if ( obj.type & objref_t::READONLY ) + return false; + + if ( obj.type & objref_t::PTR ) + { + _check( *(obj.ptr) ); + return true; + } + + switch ( obj.type ) + { + case objref_t::TABLE: + { + if ( sq_type(obj.src) == OT_TABLE ) + { + SQObjectPtr value; + if ( _table(obj.src)->Get( obj.key, value ) ) + { + _check( value ); + return _table(obj.src)->Set( obj.key, value ); + } + } + + return false; + } + case objref_t::INSTANCE: + { + if ( sq_type(obj.src) == OT_INSTANCE ) + { + SQObjectPtr value; + if ( _instance(obj.src)->Get( obj.key, value ) ) + { + _check( value ); + return _instance(obj.src)->Set( obj.key, value ); + } + } + + return false; + } + case objref_t::ARRAY: + { + Assert( sq_type(obj.key) == OT_INTEGER ); + + if ( sq_type(obj.src) == OT_ARRAY && + _integer(obj.key) >= 0 && + _integer(obj.key) < _array(obj.src)->Size() ) + { + SQObjectPtr &value = _array(obj.src)->_values[ _integer(obj.key) ]; + _check( value ); + return true; + } + + return false; + } + case objref_t::STACK: + { + HSQUIRRELVM vm = GetThread( obj.stack.thread ); + + if ( vm && obj.stack.frame >= 0 && obj.stack.frame <= vm->ci - vm->_callsstack ) + { + const SQVM::CallInfo &ci = vm->_callsstack[ obj.stack.frame ]; + if ( sq_type(ci._closure) == OT_CLOSURE && + ( ci._ip - _fp(_closure(ci._closure)->_function)->_instructions <= obj.stack.end ) && + obj.stack.index >= 0 && obj.stack.index < (int)vm->_stack.size() ) + { + SQObjectPtr &value = vm->_stack._vals[ obj.stack.index ]; + _check( value ); + return true; + } + } + + return false; + } + default: + return false; + } + +#undef _check +} + +// +// A very basic compiler that parses strings, characters, numbers (dec, hex, oct, bin, flt), identifiers, +// keywords (this, null, true, false), +// unary operators (-, ~, !, typeof, delete, clone), +// binary operators (+, -, *, /, %, <<, >>, >>>, &, |, ^, <, >, <=, >=, <=>, ==, !=, &&, ||, in, instanceof), +// prefix/postfix increment/decrement operators, +// newslot and (compound) assignment operators, +// root (::) and identifier access (a.b, a[b]), function calls and grouping parantheses. +// +// Variable evaluation is done in given stack frame as they are parsed, state is not kept +// +class SQDebugServer::CCompiler +{ +public: + enum + { + Err_InvalidToken = -50, + Err_UnfinishedString, + Err_UnfinishedChar, + Err_InvalidEscape, + Err_InvalidXEscape, + Err_InvalidU16Escape, + Err_InvalidU32Escape, + Err_InvalidDecimal, + Err_InvalidOctal, + Err_InvalidHexadecimal, + Err_InvalidBinary, + Err_InvalidFloat, + + Token_End = 1, + Token_Ref, + Token_Identifier, + Token_String, + Token_Integer, + Token_Float, + Token_Null, + Token_False, + Token_True, + Token_This, + Token_DoubleColon, + Token_Delete, +#if SQUIRREL_VERSION_NUMBER < 300 + Token_Parent, + Token_Vargv, + Token_Vargc, +#endif + Token_File, + Token_Line, + + Token_NewSlot, + Token_PendingKey, + Token_Operator, + Token_Value, + + _op = 0xff00, + + // upper 4 bits : precedence + // lower 4 bits : unique id + Token_Not = 0x00 | _op, + Token_BwNot = 0x01 | _op, + Token_Typeof = 0x02 | _op, + Token_Clone = 0x03 | _op, + + Token_Increment = 0x10 | _op, + Token_Decrement = 0x11 | _op, + + Token_Mul = 0x20 | _op, + Token_Div = 0x21 | _op, + Token_Mod = 0x22 | _op, + + Token_Add = 0x30 | _op, + Token_Sub = 0x31 | _op, + + Token_LShift = 0x40 | _op, + Token_RShift = 0x41 | _op, + Token_URShift = 0x42 | _op, + + Token_Cmp3W = 0x50 | _op, + + Token_Less = 0x60 | _op, + Token_LessEq = 0x61 | _op, + Token_Greater = 0x62 | _op, + Token_GreaterEq = 0x63 | _op, + + Token_Eq = 0x70 | _op, + Token_NotEq = 0x71 | _op, + + Token_BwAnd = 0x80 | _op, + Token_BwXor = 0x90 | _op, + Token_BwOr = 0xA0 | _op, + + Token_In = 0xB0 | _op, + Token_InstanceOf = 0xB1 | _op, + + Token_LogicalAnd = 0xC0 | _op, + Token_LogicalOr = 0xD0 | _op, + + _assign = 0xff0000, + + Token_Assign, + Token_AssignAdd = _assign | Token_Add, + Token_AssignSub = _assign | Token_Sub, + Token_AssignMul = _assign | Token_Mul, + Token_AssignDiv = _assign | Token_Div, + Token_AssignMod = _assign | Token_Mod, + Token_AssignAnd = _assign | Token_BwAnd, + Token_AssignXor = _assign | Token_BwXor, + Token_AssignOr = _assign | Token_BwOr, + Token_AssignLS = _assign | Token_LShift, + Token_AssignRS = _assign | Token_RShift, + Token_AssignURS = _assign | Token_URShift, + }; + + struct token_t + { + token_t() : type(0) {} + + int type; + union + { + string_t _string; + SQInteger _integer; + SQFloat _float; + }; + }; + +#ifndef SQDBG_COMPILER_MAX_PARAMETER_COUNT +#define SQDBG_COMPILER_MAX_PARAMETER_COUNT 6 +#endif +#ifndef SQDBG_COMPILER_MAX_UNARY_STACK +#define SQDBG_COMPILER_MAX_UNARY_STACK 4 +#endif + +private: + struct callparams_t + { + SQObjectPtr params[ SQDBG_COMPILER_MAX_PARAMETER_COUNT ]; + SQObjectPtr func; + int paramCount; + }; + + string_t &m_expr; + char *m_cur; + char *m_end; + int m_prevToken; + +public: + // To get partial matches for completions request + token_t m_lastToken; + + // To add expression watchpoints + // Only returns the last checked ref regardless of any other operations that come after + // This is fine because adding illogical watchpoints doesn't have significant side effects - + // prevention is at the user's discretion, + // and the ability to add on expressions is useful + objref_t m_lastRef; + +public: + CCompiler( string_t &expression ) : + m_expr(expression), + m_cur(expression.ptr), + m_end(expression.ptr + expression.len), + m_prevToken(0), + m_lastToken(), + m_lastRef() + { + } + + CCompiler( const CCompiler & ); + CCompiler &operator=( const CCompiler & ); + + ECompileReturnCode Evaluate( SQDebugServer *dbg, HSQUIRRELVM vm, const SQVM::CallInfo *ci, SQObjectPtr &val, + int closer = 0 ) + { + token_t token; + int prevtoken = 0; + + int opbufidx = -1; + int unaryidx = -1; + + // Recursively parsing unary operators adds the complexity + // of keeping track of and reverting to the previous token + // Using an operator stack is simpler but limits the amount + unsigned char unarybuf[ SQDBG_COMPILER_MAX_UNARY_STACK ]; + unsigned char opbuf[2]; + char deleteop = 0; + char incrop = 0; + + SQObjectPtr callenv; + SQObjectPtr valbuf[2]; + + objref_t obj; + obj.type = objref_t::INVALID; + + for ( ;; m_prevToken = token.type ) + { + token = Lex(); + + switch ( token.type ) + { + case Token_Identifier: + { + if ( !ExpectsIdentifier( prevtoken ) ) + return CompileReturnCode_Unsupported; + + m_lastToken = token; + + if ( !dbg->GetObj_Frame( vm, ci, token._string, obj, val ) ) + { + // allow non-existent key + if ( Next() != Token_NewSlot ) + return CompileReturnCode_DoesNotExist; + + // implicit this. + val = ci ? vm->_stack._vals[ GetStackBase( vm, ci ) ] : vm->_roottable; + + switch ( sq_type(val) ) + { + case OT_TABLE: + obj.type = objref_t::TABLE; + break; + case OT_CLASS: + obj.type = objref_t::CLASS; + break; + default: + if ( is_delegable(val) && _delegable(val)->_delegate ) + { + obj.type = objref_t::DELEGABLE_META; + break; + } + + return CompileReturnCode_DoesNotExist; + } + + obj.src = val; + obj.key = CreateSQString( dbg, token._string ); + prevtoken = Token_PendingKey; + } + else + { + ConvertPtr( obj ); + m_lastRef = obj; + prevtoken = Token_Ref; + } + + break; + } + case Token_String: + { + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + val = CreateSQString( dbg, token._string ); + + prevtoken = Token_Value; + break; + } + case Token_Integer: + { + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + val.Null(); + val._type = OT_INTEGER; + val._unVal.nInteger = token._integer; + + prevtoken = Token_Integer; + break; + } + case Token_Float: + { + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + val.Null(); + val._type = OT_FLOAT; + val._unVal.fFloat = token._float; + + prevtoken = Token_Float; + break; + } + case Token_Null: + { + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + val.Null(); + + prevtoken = Token_Value; + break; + } + case Token_False: + case Token_True: + { + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + val.Null(); + val._type = OT_BOOL; + val._unVal.nInteger = token._integer; + + prevtoken = Token_Value; + break; + } + case Token_This: + { + if ( !ExpectsIdentifier( prevtoken ) ) + return CompileReturnCode_Unsupported; + + val = ci ? vm->_stack._vals[ GetStackBase( vm, ci ) ] : vm->_roottable; + + prevtoken = Token_Value; + break; + } + case Token_BwNot: + case Token_Not: + case Token_Typeof: + case Token_Clone: + { +unary: + // identifier boundary + if ( prevtoken == Token_Ref ) + { + if ( deleteop ) + { + if ( !dbg->Delete( obj, val ) ) + return CompileReturnCode_Unsupported; + + deleteop = 0; + prevtoken = Token_Value; + } + else if ( incrop ) + { + if ( !dbg->Increment( obj, ( incrop & 0x1 ) ? 1 : -1 ) ) + return CompileReturnCode_Unsupported; + + if ( incrop & 0x8 ) // prefix + dbg->Get( obj, val ); + + incrop = 0; + prevtoken = Token_Value; + } + } + + if ( IsValue( prevtoken ) ) + { + while ( unaryidx != -1 ) + { + if ( !UnaryOp( dbg, unarybuf[unaryidx] | _op, val ) ) + return CompileReturnCode_Unsupported; + + unaryidx--; + } + } + + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + if ( unaryidx + 1 >= (int)sizeof(unarybuf) ) + return CompileReturnCode_OpBufferFull; + + unaryidx++; + unarybuf[unaryidx] = (unsigned char)( token.type & ~_op ); + + prevtoken = Token_Operator; + break; + } + case Token_Sub: + { + if ( ExpectsValue( prevtoken ) ) + goto unary; + } + case Token_In: + case Token_InstanceOf: + case Token_Add: + case Token_Mul: + case Token_Div: + case Token_Mod: + case Token_LShift: + case Token_RShift: + case Token_URShift: + case Token_BwAnd: + case Token_BwXor: + case Token_BwOr: + case Token_LogicalAnd: + case Token_LogicalOr: + case Token_Greater: + case Token_GreaterEq: + case Token_Less: + case Token_LessEq: + case Token_Cmp3W: + case Token_Eq: + case Token_NotEq: + { + // identifier boundary + if ( !IsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + if ( prevtoken == Token_Ref ) + { + if ( deleteop ) + { + if ( !dbg->Delete( obj, val ) ) + return CompileReturnCode_Unsupported; + + deleteop = 0; + prevtoken = Token_Value; + } + else if ( incrop ) + { + if ( !dbg->Increment( obj, ( incrop & 0x1 ) ? 1 : -1 ) ) + return CompileReturnCode_Unsupported; + + if ( incrop & 0x8 ) // prefix + dbg->Get( obj, val ); + + incrop = 0; + prevtoken = Token_Value; + } + } + + while ( unaryidx != -1 ) + { + if ( !UnaryOp( dbg, unarybuf[unaryidx] | _op, val ) ) + return CompileReturnCode_Unsupported; + + unaryidx--; + } + + while ( opbufidx != -1 ) + { + int prev = opbuf[opbufidx] | _op; + + // Higher or equal precedence + if ( ( prev & 0xf0 ) <= ( token.type & 0xf0 ) ) + { + SQObjectPtr &lhs = valbuf[opbufidx]; + + if ( !TwoArgOp( dbg, prev, lhs, val, val ) ) + return CompileReturnCode_Unsupported; + + lhs.Null(); + opbufidx--; + } + else + { + break; + } + } + + // Don't evaluate both sides + // In both shortcuts, lhs is returned + switch ( token.type ) + { + case Token_LogicalAnd: if ( IsFalse( val ) ) return LexAll( closer ); break; + case Token_LogicalOr: if ( !IsFalse( val ) ) return LexAll( closer ); break; + } + + Assert( opbufidx + 1 < 2 ); + + if ( opbufidx + 1 >= 2 ) + return CompileReturnCode_Unsupported; + + opbufidx++; + opbuf[opbufidx] = (unsigned char)( token.type & ~_op ); + valbuf[opbufidx] = val; + + prevtoken = Token_Operator; + break; + } + case Token_Increment: + case Token_Decrement: + { + // decrement x000 + // increment x001 + // prefix 100x + // postfix 000x + if ( prevtoken == Token_Ref ) + { + incrop = ( token.type == Token_Increment ? 0x11 : 0x10 ); + } + else if ( ExpectsIdentifier( prevtoken ) ) + { + incrop = ( token.type == Token_Increment ? 0x19 : 0x18 ); + } + else + { + return CompileReturnCode_Unsupported; + } + + break; + } + case Token_DoubleColon: + { + if ( !ExpectsIdentifier( prevtoken ) ) + return CompileReturnCode_Unsupported; + + token_t next = Lex(); + + if ( next.type != Token_Identifier ) + { + m_lastToken.type = 0; + val = vm->_roottable; + return CompileReturnCode_Unsupported; + } + + m_lastToken = next; + + if ( !dbg->GetObj_Var( vm->_roottable, next._string, true, obj, val ) ) + { + // allow non-existent key + if ( Next() != Token_NewSlot ) + return CompileReturnCode_DoesNotExist; + + Assert( sq_type(vm->_roottable) == OT_TABLE ); + obj.type = objref_t::TABLE; + obj.src = vm->_roottable; + obj.key = CreateSQString( dbg, next._string ); + prevtoken = Token_PendingKey; + } + else + { + ConvertPtr( obj ); + m_lastRef = obj; + prevtoken = Token_Ref; + } + + break; + } + case '.': + { + if ( !IsValue( prevtoken ) || prevtoken == Token_Integer || prevtoken == Token_Float ) + return CompileReturnCode_Unsupported; + + token_t next = Lex(); + + if ( next.type != Token_Identifier ) + { + m_lastToken = token; + return CompileReturnCode_Unsupported; + } + + m_lastToken = next; + + int nexttoken = Next(); + + // cur.next() - save 'cur' as the call env + if ( nexttoken =='(' ) + callenv = val; + + SQObjectPtr tmp; + + if ( !dbg->GetObj_Var( val, next._string, true, obj, tmp ) ) + { + SQTable *del = GetDefaultDelegate( vm, sq_type(val) ); + if ( !del || !SQTable_Get( dbg, del, next._string, tmp ) ) + { + // allow non-existent key + if ( nexttoken != Token_NewSlot ) + return CompileReturnCode_DoesNotExist; + + switch ( sq_type(val) ) + { + case OT_TABLE: + obj.type = objref_t::TABLE; + break; + case OT_CLASS: + obj.type = objref_t::CLASS; + break; + default: + if ( is_delegable(val) && _delegable(val)->_delegate ) + { + obj.type = objref_t::DELEGABLE_META; + break; + } + + return CompileReturnCode_DoesNotExist; + } + + obj.src = val; + obj.key = CreateSQString( dbg, next._string ); + prevtoken = Token_PendingKey; + } + else + { + callenv = val; + val = tmp; + prevtoken = Token_Value; + } + } + else + { + ConvertPtr( obj ); + m_lastRef = obj; + val = tmp; + prevtoken = Token_Ref; + } + + break; + } + case '[': + { + if ( !IsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + SQObjectPtr inner, tmp; + + ECompileReturnCode res = Evaluate( dbg, vm, ci, inner, ']' ); + + if ( res != CompileReturnCode_Success ) + return res; + + if ( m_prevToken != ']' ) + return CompileReturnCode_Unsupported; + + if ( !dbg->GetObj_Var( val, inner, obj, tmp ) ) + { + SQTable *del = GetDefaultDelegate( vm, sq_type(val) ); + if ( !del || !del->Get( inner, inner ) ) + { + // allow non-existent key + if ( Next() != Token_NewSlot ) + return CompileReturnCode_DoesNotExist; + + switch ( sq_type(val) ) + { + case OT_TABLE: + obj.type = objref_t::TABLE; + break; + case OT_CLASS: + obj.type = objref_t::CLASS; + break; + default: + if ( is_delegable(val) && _delegable(val)->_delegate ) + { + obj.type = objref_t::DELEGABLE_META; + break; + } + + return CompileReturnCode_DoesNotExist; + } + + obj.src = val; + obj.key = inner; + prevtoken = Token_PendingKey; + } + else + { + callenv = val; + val = inner; + prevtoken = Token_Value; + } + } + else + { + ConvertPtr( obj ); + m_lastRef = obj; + val = tmp; + prevtoken = Token_Ref; + } + + break; + } + case '(': + { + if ( !IsValue( prevtoken ) ) + { + ECompileReturnCode res = Evaluate( dbg, vm, ci, val, ')' ); + + if ( res != CompileReturnCode_Success ) + return res; + + if ( m_prevToken != ')' ) + return CompileReturnCode_Unsupported; + + prevtoken = Token_Value; + break; + } + + callparams_t cp{}; + + switch ( sq_type(val) ) + { + case OT_CLOSURE: + case OT_NATIVECLOSURE: + case OT_CLASS: + { + cp.func = val; + break; + } + default: + if ( is_delegable(val) ) + { + if ( !_delegable(val)->_delegate || + !_delegable(val)->GetMetaMethod( vm, MT_CALL, cp.func ) ) + return CompileReturnCode_DoesNotExist; + + // in MT_CALL the object itself is the env, + // and the env is passed as an extra parameter + cp.params[ cp.paramCount++ ] = val; + break; + } + + return CompileReturnCode_Unsupported; + } + + if ( !_rawval(callenv) ) + { + const SQObjectPtr &env = ci ? + vm->_stack._vals[ GetStackBase( vm, ci ) ] : + vm->_roottable; + cp.params[ cp.paramCount++ ] = env; + } + else + { + cp.params[ cp.paramCount++ ] = callenv; + callenv.Null(); + } + + // call parameters + for (;;) + { + ECompileReturnCode res = Evaluate( dbg, vm, ci, val, ')' ); + + if ( res == CompileReturnCode_Success ) + { + if ( cp.paramCount >= (int)_ArraySize(cp.params) ) + return CompileReturnCode_CallBufferFull; + + cp.params[ cp.paramCount++ ] = val; + } + else if ( res != CompileReturnCode_NoValue ) + { + return res; + } + + if ( m_prevToken == ',' ) + continue; + + if ( m_prevToken == ')' ) + break; + + return CompileReturnCode_Unsupported; + } + + switch ( cp.paramCount ) + { + case 1: + { + if ( !dbg->RunClosure( cp.func, &cp.params[0], val ) ) + return CompileReturnCode_DoesNotExist; + break; + } + case 2: + { + if ( !dbg->RunClosure( cp.func, + &cp.params[0], + cp.params[1], + val ) ) + return CompileReturnCode_DoesNotExist; + break; + } + case 3: + { + if ( !dbg->RunClosure( cp.func, + &cp.params[0], + cp.params[1], + cp.params[2], + val ) ) + return CompileReturnCode_DoesNotExist; + break; + } + case 4: + { + if ( !dbg->RunClosure( cp.func, + &cp.params[0], + cp.params[1], + cp.params[2], + cp.params[3], + val ) ) + return CompileReturnCode_DoesNotExist; + break; + } + case 5: + { + if ( !dbg->RunClosure( cp.func, + &cp.params[0], + cp.params[1], + cp.params[2], + cp.params[3], + cp.params[4], + val ) ) + return CompileReturnCode_DoesNotExist; + break; + } + case 6: + { + if ( !dbg->RunClosure( cp.func, + &cp.params[0], + cp.params[1], + cp.params[2], + cp.params[3], + cp.params[4], + cp.params[5], + val ) ) + return CompileReturnCode_DoesNotExist; + break; + } + default: UNREACHABLE(); + } + + prevtoken = Token_Value; + break; + } + case Token_Delete: + { + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + deleteop = 1; + prevtoken = Token_Delete; + break; + } +#if SQUIRREL_VERSION_NUMBER < 300 + case Token_Parent: + { + if ( !IsValue( prevtoken ) ) + { + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + // implicit this. + val = ci ? vm->_stack._vals[ GetStackBase( vm, ci ) ] : vm->_roottable; + } + + switch ( sq_type(val) ) + { + case OT_TABLE: + { + if ( _table(val)->_delegate ) + { + val = _table(val)->_delegate; + } + else + { + val.Null(); + } + + break; + } + case OT_CLASS: + { + if ( _class(val)->_base ) + { + val = _class(val)->_base; + } + else + { + val.Null(); + } + + break; + } + default: + return CompileReturnCode_DoesNotExist; + } + + prevtoken = Token_Value; + break; + } + case Token_Vargv: + { + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + if ( !ci ) + return CompileReturnCode_DoesNotExist; + + token_t next = Lex(); + + if ( next.type != '[' ) + return CompileReturnCode_DoesNotExist; + + ECompileReturnCode res = Evaluate( dbg, vm, ci, val, ']' ); + + if ( res != CompileReturnCode_Success ) + return res; + + if ( m_prevToken != ']' ) + return CompileReturnCode_Unsupported; + + if ( sq_type(val) != OT_INTEGER ) + return CompileReturnCode_DoesNotExist; + + if ( _integer(val) < 0 || _integer(val) >= ci->_vargs.size ) + return CompileReturnCode_DoesNotExist; + + val = vm->_vargsstack[ ci->_vargs.base + _integer(val) ]; + + prevtoken = Token_Value; + break; + } + case Token_Vargc: + { + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + if ( !ci ) + return CompileReturnCode_DoesNotExist; + + val.Null(); + val._type = OT_INTEGER; + val._unVal.nInteger = ci->_vargs.size; + + prevtoken = Token_Value; + break; + } +#endif + case Token_File: + { + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + if ( ci && sq_type(ci->_closure) == OT_CLOSURE ) + { + val = _fp(_closure(ci->_closure)->_function)->_sourcename; + } + else + { + val = CreateSQString( vm, _SC("") ); + } + + prevtoken = Token_Value; + break; + } + case Token_Line: + { + if ( !ExpectsValue( prevtoken ) ) + return CompileReturnCode_Unsupported; + + val.Null(); + val._type = OT_INTEGER; + + if ( ci && sq_type(ci->_closure) == OT_CLOSURE ) + { + Assert( ci->_ip >= _fp(_closure(ci->_closure)->_function)->_instructions && + ci->_ip < _fp(_closure(ci->_closure)->_function)->_instructions + + _fp(_closure(ci->_closure)->_function)->_ninstructions ); + val._unVal.nInteger = _fp(_closure(ci->_closure)->_function)->GetLine( ci->_ip ); + } + else + { + val._unVal.nInteger = 0; + } + + prevtoken = Token_Value; + break; + } + case Token_Assign: + case Token_AssignAdd: + case Token_AssignSub: + case Token_AssignMul: + case Token_AssignDiv: + case Token_AssignMod: + case Token_AssignAnd: + case Token_AssignXor: + case Token_AssignOr: + case Token_AssignLS: + case Token_AssignRS: + case Token_AssignURS: + case Token_NewSlot: + { + if ( prevtoken != Token_Ref && + !( token.type == Token_NewSlot && prevtoken == Token_PendingKey ) ) + return CompileReturnCode_Unsupported; + + if ( unaryidx != -1 || opbufidx != -1 ) + return CompileReturnCode_Unsupported; + + SQObjectPtr target = val; + + ECompileReturnCode res = Evaluate( dbg, vm, ci, val, closer ); + + if ( res != CompileReturnCode_Success ) + return res; + + switch ( token.type ) + { + case Token_AssignAdd: + case Token_AssignSub: + case Token_AssignMul: + case Token_AssignDiv: + case Token_AssignMod: + case Token_AssignAnd: + case Token_AssignXor: + case Token_AssignOr: + case Token_AssignLS: + case Token_AssignRS: + case Token_AssignURS: + { + if ( !TwoArgOp( dbg, token.type & ~_assign, target, val, val ) ) + return CompileReturnCode_Unsupported; + } + case Token_Assign: + { + if ( !dbg->Set( obj, val ) ) + return CompileReturnCode_Unsupported; + + return CompileReturnCode_Success; + } + case Token_NewSlot: + { + if ( !dbg->NewSlot( obj, val ) ) + return CompileReturnCode_Unsupported; + + return CompileReturnCode_Success; + } + default: UNREACHABLE(); + } + } + default: + { + // identifier boundary + if ( token.type > 0 ) + { + m_prevToken = token.type; + + if ( ExpectsValue( prevtoken ) ) + return CompileReturnCode_NoValue; + + if ( prevtoken == Token_PendingKey ) + return CompileReturnCode_Unsupported; + + if ( prevtoken == Token_Ref ) + { + if ( deleteop ) + { + if ( !dbg->Delete( obj, val ) ) + return CompileReturnCode_Unsupported; + + deleteop = 0; + prevtoken = Token_Value; + } + else if ( incrop ) + { + if ( !dbg->Increment( obj, ( incrop & 0x1 ) ? 1 : -1 ) ) + return CompileReturnCode_Unsupported; + + if ( incrop & 0x8 ) // prefix + dbg->Get( obj, val ); + + incrop = 0; + prevtoken = Token_Value; + } + } + + while ( unaryidx != -1 ) + { + if ( !UnaryOp( dbg, unarybuf[unaryidx] | _op, val ) ) + return CompileReturnCode_Unsupported; + + unaryidx--; + } + + while ( opbufidx != -1 ) + { + if ( !TwoArgOp( dbg, opbuf[opbufidx] | _op, valbuf[opbufidx], val, val ) ) + return CompileReturnCode_Unsupported; + + opbufidx--; + } + + switch ( token.type ) + { + case ']': + { + if ( closer != ']' ) + return CompileReturnCode_Unsupported; + break; + } + case ')': + { + if ( closer != ')' ) + return CompileReturnCode_Unsupported; + break; + } + case ',': + { + if ( closer != ')' ) + return CompileReturnCode_Unsupported; + break; + } + } + + return CompileReturnCode_Success; + } + else if ( token.type == Err_InvalidToken ) + { + return CompileReturnCode_Unsupported; + } + else + { + return CompileReturnCode_SyntaxError; + } + } + } + } + } + +private: + ECompileReturnCode LexAll( int closer ) + { + // fast shortcut - ignore everything + if ( closer == 0 ) + return CompileReturnCode_Success; + + m_prevToken = closer; + + int depth = 0; + int opener; + + switch ( closer ) + { + case ')': opener = '('; break; + case ']': opener = '['; break; + default: UNREACHABLE(); + } + + for (;;) + { + token_t token = Lex(); + + if ( token.type > 0 ) + { + if ( token.type == opener ) + { + depth++; + } + else if ( token.type == closer ) + { + if ( depth ) + { + depth--; + } + else + { + return CompileReturnCode_Success; + } + } + } + else if ( token.type == Err_InvalidToken ) + { + return CompileReturnCode_Unsupported; + } + else + { + return CompileReturnCode_SyntaxError; + } + } + } + +private: + static bool IsValue( int token ) + { + switch ( token ) + { + case Token_Ref: + case Token_Value: + case Token_Integer: + case Token_Float: + return true; + default: + return false; + } + } + + static bool ExpectsValue( int token ) + { + switch ( token ) + { + case 0: + case '(': + case ',': + case Token_Operator: + return true; + default: + return false; + } + } + + static bool ExpectsIdentifier( int token ) + { + switch ( token ) + { + case 0: + case '(': + case ',': + case Token_Operator: + case Token_Delete: + return true; + default: + return false; + } + } + + static bool TwoArgOp( SQDebugServer *dbg, + int op, const SQObjectPtr &lhs, const SQObjectPtr &rhs, SQObjectPtr &out ) + { + Assert( ( op & _op ) == _op ); + + switch ( op ) + { + case Token_Add: return dbg->ArithOp( '+', lhs, rhs, out ); + case Token_Sub: return dbg->ArithOp( '-', lhs, rhs, out ); + case Token_Mul: return dbg->ArithOp( '*', lhs, rhs, out ); + case Token_Div: return dbg->ArithOp( '/', lhs, rhs, out ); + case Token_Mod: return dbg->ArithOp( '%', lhs, rhs, out ); + case Token_In: + { + switch ( sq_type(rhs) ) + { + case OT_TABLE: + SetBool( out, _table(rhs)->Get( lhs, out ) ); + break; + case OT_CLASS: + SetBool( out, _class(rhs)->Get( lhs, out ) ); + break; + case OT_INSTANCE: + Assert( _instance(rhs)->_class ); + SetBool( out, _instance(rhs)->_class->Get( lhs, out ) ); + break; + case OT_ARRAY: + SetBool( out, sq_type(lhs) == OT_INTEGER && + _integer(lhs) >= 0 && _integer(lhs) < _array(rhs)->Size() ); + break; + case OT_STRING: + SetBool( out, sq_type(lhs) == OT_INTEGER && + _integer(lhs) >= 0 && _integer(lhs) < _string(rhs)->_len ); + break; + default: + SetBool( out, false ); + } + + return true; + } + case Token_InstanceOf: + { + if ( sq_type(rhs) == OT_CLASS ) + { + Assert( sq_type(lhs) != OT_INSTANCE || _instance(lhs)->_class ); + SetBool( out, sq_type(lhs) == OT_INSTANCE && _instance(lhs)->_class == _class(rhs) ); + return true; + } + + return false; + } + case Token_LShift: + { + if ( ( sq_type(lhs) | sq_type(rhs) ) == OT_INTEGER ) + { + out = (SQInteger)( _integer(lhs) << _integer(rhs) ); + return true; + } + + return false; + } + case Token_RShift: + { + if ( ( sq_type(lhs) | sq_type(rhs) ) == OT_INTEGER ) + { + out = (SQInteger)( _integer(lhs) >> _integer(rhs) ); + return true; + } + + return false; + } + case Token_URShift: + { + if ( ( sq_type(lhs) | sq_type(rhs) ) == OT_INTEGER ) + { + out = (SQInteger)( *(SQUnsignedInteger*)&_integer(lhs) >> _integer(rhs) ); + return true; + } + + return false; + } + case Token_BwAnd: + { + if ( ( sq_type(lhs) | sq_type(rhs) ) == OT_INTEGER ) + { + out = (SQInteger)( _integer(lhs) & _integer(rhs) ); + return true; + } + + return false; + } + case Token_BwXor: + { + if ( ( sq_type(lhs) | sq_type(rhs) ) == OT_INTEGER ) + { + out = (SQInteger)( _integer(lhs) ^ _integer(rhs) ); + return true; + } + + return false; + } + case Token_BwOr: + { + if ( ( sq_type(lhs) | sq_type(rhs) ) == OT_INTEGER ) + { + out = (SQInteger)( _integer(lhs) | _integer(rhs) ); + return true; + } + + return false; + } + case Token_LogicalAnd: + { + out = IsFalse( lhs ) ? lhs : rhs; + return true; + } + case Token_LogicalOr: + { + out = !IsFalse( lhs ) ? lhs : rhs; + return true; + } + case Token_Greater: + { + int res = dbg->CompareObj( lhs, rhs ); + if ( res != ECMP_NONE ) + { + SetBool( out, ( res & ECMP_G ) != 0 ); + return true; + } + + return false; + } + case Token_GreaterEq: + { + int res = dbg->CompareObj( lhs, rhs ); + if ( res != ECMP_NONE ) + { + SetBool( out, ( res & ECMP_GE ) != 0 ); + return true; + } + + return false; + } + case Token_Less: + { + int res = dbg->CompareObj( lhs, rhs ); + if ( res != ECMP_NONE ) + { + SetBool( out, ( res & ECMP_L ) != 0 ); + return true; + } + + return false; + } + case Token_LessEq: + { + int res = dbg->CompareObj( lhs, rhs ); + if ( res != ECMP_NONE ) + { + SetBool( out, ( res & ECMP_LE ) != 0 ); + return true; + } + + return false; + } + case Token_Cmp3W: + { + int res = dbg->CompareObj( lhs, rhs ); + if ( res != ECMP_NONE ) + { + switch ( res ) + { + case ECMP_EQ: res = 0; break; + case ECMP_G: + case ECMP_GE: res = 1; break; + case ECMP_L: + case ECMP_LE: res = -1; break; + default: UNREACHABLE(); + } + out = (SQInteger)res; + return true; + } + + return false; + } + case Token_Eq: + { + bool res; +#if SQUIRREL_VERSION_NUMBER >= 300 + dbg->m_pRootVM->IsEqual( lhs, rhs, res ); +#else + dbg->m_pRootVM->IsEqual( (SQObjectPtr&)lhs, (SQObjectPtr&)rhs, res ); +#endif + SetBool( out, res ); + return true; + } + case Token_NotEq: + { + bool res; +#if SQUIRREL_VERSION_NUMBER >= 300 + dbg->m_pRootVM->IsEqual( lhs, rhs, res ); +#else + dbg->m_pRootVM->IsEqual( (SQObjectPtr&)lhs, (SQObjectPtr&)rhs, res ); +#endif + SetBool( out, !res ); + return true; + } + default: UNREACHABLE(); + } + } + + static bool UnaryOp( SQDebugServer *dbg, int op, SQObjectPtr &val ) + { + Assert( ( op & _op ) == _op ); + + switch ( op ) + { + case Token_Sub: + { + if ( sq_type(val) == OT_INTEGER ) + { + _integer(val) = -_integer(val); + return true; + } + else if ( sq_type(val) == OT_FLOAT ) + { + _float(val) = -_float(val); + return true; + } + else if ( is_delegable(val) && _delegable(val)->_delegate ) + { + SQObjectPtr mm; + if ( _delegable(val)->GetMetaMethod( dbg->m_pRootVM, MT_UNM, mm ) ) + { + return dbg->RunClosure( mm, &val, val ); + } + } + + return false; + } + case Token_BwNot: + { + if ( sq_type(val) == OT_INTEGER ) + { + _integer(val) = ~_integer(val); + return true; + } + + return false; + } + case Token_Not: + { + SetBool( val, IsFalse( val ) ); + return true; + } + case Token_Typeof: + { + if ( is_delegable(val) && _delegable(val)->_delegate ) + { + SQObjectPtr mm; + if ( _delegable(val)->GetMetaMethod( dbg->m_pRootVM, MT_TYPEOF, mm ) ) + { + return dbg->RunClosure( mm, &val, val ); + } + } + + extern const SQChar *GetTypeName( const SQObjectPtr & ); + const SQChar *tname = GetTypeName(val); + + if ( tname ) + { + val = SQString::Create( _ss(dbg->m_pRootVM), tname, scstrlen(tname) ); + return true; + } + + return false; + } + case Token_Clone: + { + switch ( sq_type(val) ) + { + case OT_TABLE: + case OT_INSTANCE: + case OT_ARRAY: + return dbg->m_pRootVM->Clone( val, val ); + default: + return false; + } + } + default: UNREACHABLE(); + } + } + +private: + int Next() + { + while ( m_cur < m_end ) + { + switch ( *m_cur ) + { + case ' ': case '\t': case '\r': case '\n': + { + m_cur++; + continue; + } + case '\"': + { + return *m_cur; + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + return *m_cur; + } + case '.': case ',': + case '[': case ']': + case '(': case ')': + { + return *m_cur; + } + case '+': + { + if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + return Token_AssignAdd; + } + else if ( m_cur + 1 < m_end && m_cur[1] == '+' ) + { + return Token_Increment; + } + else + { + return Token_Add; + } + } + case '-': + { + if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + return Token_AssignSub; + } + else if ( m_cur + 1 < m_end && m_cur[1] == '-' ) + { + return Token_Decrement; + } + else + { + return Token_Sub; + } + } + case '*': + { + if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + return Token_AssignMul; + } + else + { + return Token_Mul; + } + } + case '/': + { + if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + return Token_AssignDiv; + } + else + { + return Token_Div; + } + } + case '%': + { + if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + return Token_AssignMod; + } + else + { + return Token_Mod; + } + } + case '~': + { + return Token_BwNot; + } + case '^': + { + if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + return Token_AssignXor; + } + else + { + return Token_BwXor; + } + } + case '=': + { + if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + return Token_Eq; + } + else + { + return Token_Assign; + } + } + case '!': + { + if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + return Token_NotEq; + } + else + { + return Token_Not; + } + } + case '<': + { + if ( m_cur + 1 < m_end && m_cur[1] == '<' ) + { + if ( m_cur + 2 < m_end && m_cur[2] == '=' ) + { + return Token_AssignLS; + } + else + { + return Token_LShift; + } + } + else if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + if ( m_cur + 2 < m_end && m_cur[2] == '>' ) + { + return Token_Cmp3W; + } + else + { + return Token_LessEq; + } + } + else if ( m_cur + 1 < m_end && m_cur[1] == '-' ) + { + return Token_NewSlot; + } + else + { + return Token_Less; + } + } + case '>': + { + if ( m_cur + 1 < m_end && m_cur[1] == '>' ) + { + if ( m_cur + 2 < m_end && m_cur[2] == '>' ) + { + if ( m_cur + 3 < m_end && m_cur[3] == '=' ) + { + return Token_AssignURS; + } + else + { + return Token_URShift; + } + } + else if ( m_cur + 2 < m_end && m_cur[2] == '=' ) + { + return Token_AssignRS; + } + else + { + return Token_RShift; + } + } + else if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + return Token_GreaterEq; + } + else + { + return Token_Greater; + } + } + case '&': + { + if ( m_cur + 1 < m_end && m_cur[1] == '&' ) + { + return Token_LogicalAnd; + } + else if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + return Token_AssignAnd; + } + else + { + return Token_BwAnd; + } + } + case '|': + { + if ( m_cur + 1 < m_end && m_cur[1] == '|' ) + { + return Token_LogicalOr; + } + else if ( m_cur + 1 < m_end && m_cur[1] == '=' ) + { + return Token_AssignOr; + } + else + { + return Token_BwOr; + } + } + case ':': + { + if ( m_cur + 1 < m_end && m_cur[1] == ':' ) + { + return Token_DoubleColon; + } + else + { + return Err_InvalidToken; + } + } + case '\'': + { + return *m_cur; + } + default: + { + if ( _isalpha( *(unsigned char*)m_cur ) || *m_cur == '_' ) + { + return *m_cur; + } + else + { + return Err_InvalidToken; + } + } + } + } + + return Token_End; + } + + token_t Lex() + { + token_t token; + + while ( m_cur < m_end ) + { + switch ( *m_cur ) + { + case ' ': case '\t': case '\r': case '\n': + { + m_cur++; + continue; + } + case '\"': + { + ParseString( token ); + return token; + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + ParseNumber( token, false ); + return token; + } + case '.': case ',': + case '[': case ']': + case '(': case ')': + { + token.type = *m_cur++; + return token; + } + case '+': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_AssignAdd; + } + else if ( m_cur < m_end && *m_cur == '+' ) + { + m_cur++; + token.type = Token_Increment; + } + else + { + token.type = Token_Add; + } + + return token; + } + case '-': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_AssignSub; + } + else if ( m_cur < m_end && *m_cur == '-' ) + { + m_cur++; + token.type = Token_Decrement; + } + else + { + token.type = Token_Sub; + } + + return token; + } + case '*': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_AssignMul; + } + else + { + token.type = Token_Mul; + } + + return token; + } + case '/': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_AssignDiv; + } + else + { + token.type = Token_Div; + } + + return token; + } + case '%': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_AssignMod; + } + else + { + token.type = Token_Mod; + } + + return token; + } + case '~': + { + m_cur++; + token.type = Token_BwNot; + return token; + } + case '^': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_AssignXor; + } + else + { + token.type = Token_BwXor; + } + + return token; + } + case '=': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_Eq; + } + else + { + token.type = Token_Assign; + } + + return token; + } + case '!': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_NotEq; + } + else + { + token.type = Token_Not; + } + + return token; + } + case '<': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '<' ) + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_AssignLS; + } + else + { + token.type = Token_LShift; + } + } + else if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '>' ) + { + m_cur++; + token.type = Token_Cmp3W; + } + else + { + token.type = Token_LessEq; + } + } + else if ( m_cur < m_end && *m_cur == '-' ) + { + m_cur++; + token.type = Token_NewSlot; + } + else + { + token.type = Token_Less; + } + + return token; + } + case '>': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '>' ) + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '>' ) + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_AssignURS; + } + else + { + token.type = Token_URShift; + } + } + else if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_AssignRS; + } + else + { + token.type = Token_RShift; + } + } + else if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_GreaterEq; + } + else + { + token.type = Token_Greater; + } + + return token; + } + case '&': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '&' ) + { + m_cur++; + token.type = Token_LogicalAnd; + } + else if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_AssignAnd; + } + else + { + token.type = Token_BwAnd; + } + + return token; + } + case '|': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == '|' ) + { + m_cur++; + token.type = Token_LogicalOr; + } + else if ( m_cur < m_end && *m_cur == '=' ) + { + m_cur++; + token.type = Token_AssignOr; + } + else + { + token.type = Token_BwOr; + } + + return token; + } + case ':': + { + m_cur++; + + if ( m_cur < m_end && *m_cur == ':' ) + { + m_cur++; + token.type = Token_DoubleColon; + } + else + { + token.type = Err_InvalidToken; + } + + return token; + } + case '\'': + { + ParseChar( token ); + return token; + } + default: + { + if ( _isalpha( *(unsigned char*)m_cur ) || *m_cur == '_' ) + { + char *pStart = m_cur++; + while ( m_cur < m_end && ( _isalnum( *(unsigned char*)m_cur ) || *m_cur == '_' ) ) + m_cur++; + + token.type = Token_Identifier; + token._string.Assign( pStart, m_cur - pStart ); + + if ( token._string.len == 2 || + token._string.len == 4 || token._string.len == 5 || token._string.len == 6 || + token._string.len == 8 ) + { + if ( token._string.IsEqualTo("in") ) + { + token.type = Token_In; + } + else if ( token._string.IsEqualTo("this") ) + { + token.type = Token_This; + } + else if ( token._string.IsEqualTo("null") ) + { + token.type = Token_Null; + } + else if ( token._string.IsEqualTo("true") ) + { + token.type = Token_True; + token._integer = 1; + } + else if ( token._string.IsEqualTo("false") ) + { + token.type = Token_False; + token._integer = 0; + } + else if ( token._string.IsEqualTo("typeof") ) + { + token.type = Token_Typeof; + } + else if ( token._string.IsEqualTo("clone") ) + { + token.type = Token_Clone; + } + else if ( token._string.IsEqualTo("delete") ) + { + token.type = Token_Delete; + } +#if SQUIRREL_VERSION_NUMBER < 300 + else if ( token._string.IsEqualTo("parent") ) + { + token.type = Token_Parent; + } + else if ( token._string.IsEqualTo("vargv") ) + { + token.type = Token_Vargv; + } + else if ( token._string.IsEqualTo("vargc") ) + { + token.type = Token_Vargc; + } +#endif + else if ( token._string.IsEqualTo("__FILE__") ) + { + token.type = Token_File; + } + else if ( token._string.IsEqualTo("__LINE__") ) + { + token.type = Token_Line; + } + } + } + else + { + token.type = Err_InvalidToken; + } + + return token; + } + } + } + + token.type = Token_End; + return token; + } + + void ParseString( token_t &token ) + { + char *pStart = ++m_cur; + + for (;;) + { + if ( m_cur >= m_end ) + { + token.type = Err_UnfinishedString; + return; + } + + // end + if ( *m_cur == '\"' ) + { + token.type = Token_String; + token._string.Assign( pStart, m_cur - pStart ); + m_cur++; + return; + } + + if ( *m_cur != '\\' ) + { + m_cur++; + continue; + } + + if ( m_cur + 1 >= m_end ) + { + token.type = Err_UnfinishedString; + return; + } + +#define _shift( bytesWritten, bytesRead ) \ + Assert( (bytesWritten) < (bytesRead) ); \ + memmove( m_cur + (bytesWritten), m_cur + (bytesRead), m_end - ( m_cur + (bytesRead) ) ); \ + m_cur += (bytesWritten); \ + m_end -= (bytesRead) - (bytesWritten); \ + m_expr.len -= (bytesRead) - (bytesWritten); + + switch ( m_cur[1] ) + { + case '\\': +shift_one: + _shift( 0, 1 ); + m_cur++; + break; + case '\"': goto shift_one; + case '\'': goto shift_one; + case 'a': m_cur[1] = '\a'; goto shift_one; + case 'b': m_cur[1] = '\b'; goto shift_one; + case 'f': m_cur[1] = '\f'; goto shift_one; + case 'n': m_cur[1] = '\n'; goto shift_one; + case 'r': m_cur[1] = '\r'; goto shift_one; + case 't': m_cur[1] = '\t'; goto shift_one; + case 'v': m_cur[1] = '\v'; goto shift_one; + case '0': m_cur[1] = '\0'; goto shift_one; + case 'x': +#ifndef SQUNICODE + { + if ( m_cur + 2 >= m_end ) + { + token.type = Err_InvalidXEscape; + return; + } + + unsigned int len = 0; + for ( char *x = min( m_cur + 2, m_end ), *end = min( x + 2, m_end ); + x < end && _isxdigit( *(unsigned char*)x ); + x++ ) + { + len++; + } + + if ( len == 0 ) + { + token.type = Err_InvalidXEscape; + return; + } + + unsigned int val; + if ( !atox( { m_cur + 2, len }, &val ) ) + { + token.type = Err_InvalidXEscape; + return; + } + + m_cur[0] = (char)val; + + _shift( 1, len + 2 ); + break; + } +#endif + case 'u': + { + if ( m_cur + 2 >= m_end ) + { + token.type = Err_InvalidU16Escape; + return; + } + + unsigned int len = 0; + for ( char *x = min( m_cur + 2, m_end ), *end = min( x + 4, m_end ); + x < end && _isxdigit( *(unsigned char*)x ); + x++ ) + { + len++; + } + + if ( len == 0 ) + { + token.type = Err_InvalidU16Escape; + return; + } + + unsigned int val; + if ( !atox( { m_cur + 2, len }, &val ) ) + { + token.type = Err_InvalidU16Escape; + return; + } + + if ( val <= 0xFF ) + { + m_cur[0] = (char)val; + + _shift( 1, len + 2 ); + break; + } + else if ( val <= 0x7FF ) + { + UTF8_2_FROM_UTF32( (unsigned char*)m_cur, val ); + + _shift( 2, len + 2 ); + break; + } + else if ( UTF_SURROGATE(val) ) + { + Assert( len == 4 ); + + if ( UTF_SURROGATE_LEAD(val) ) + { + if ( m_cur + 11 < m_end && + m_cur[6] == '\\' && m_cur[7] == m_cur[1] && + _isxdigit( m_cur[8] ) && _isxdigit( m_cur[9] ) && + _isxdigit( m_cur[10] ) && _isxdigit( m_cur[11] ) ) + { + unsigned int low; + Verify( atox( { m_cur + 8, 4 }, &low ) ); + + if ( UTF_SURROGATE_TRAIL( low ) ) + { + val = UTF32_FROM_UTF16_SURROGATE( val, low ); + UTF8_4_FROM_UTF32( (unsigned char*)m_cur, val ); + + _shift( 4, len + 2 + 6 ); + break; + } + } + } + } + + UTF8_3_FROM_UTF32( (unsigned char*)m_cur, val ); + + _shift( 3, len + 2 ); + break; + } + case 'U': + { + if ( m_cur + 2 >= m_end ) + { + token.type = Err_InvalidU32Escape; + return; + } + + unsigned int len = 0; + for ( char *x = min( m_cur + 2, m_end ), *end = min( x + 8, m_end ); + x < end && _isxdigit( *(unsigned char*)x ); + x++ ) + { + len++; + } + + if ( len == 0 ) + { + token.type = Err_InvalidU32Escape; + return; + } + + unsigned int val; + if ( !atox( { m_cur + 2, len }, &val ) ) + { + token.type = Err_InvalidU32Escape; + return; + } + + if ( val <= 0xFF ) + { + m_cur[0] = (char)val; + + _shift( 1, len + 2 ); + break; + } + else if ( val <= 0x7FF ) + { + UTF8_2_FROM_UTF32( (unsigned char*)m_cur, val ); + + _shift( 2, len + 2 ); + break; + } + else if ( UTF_SURROGATE(val) ) + { + // next could be \u or \U with differing lengths, + // just ignore surrogates + token.type = Err_InvalidToken; + return; + } + else if ( val <= 0xFFFF ) + { + UTF8_3_FROM_UTF32( (unsigned char*)m_cur, val ); + + _shift( 3, len + 2 ); + break; + } + else + { + UTF8_4_FROM_UTF32( (unsigned char*)m_cur, val ); + + _shift( 4, len + 2 ); + break; + } + } + default: + token.type = Err_InvalidEscape; + return; + } + +#undef _shift + } + } + + void ParseChar( token_t &token ) + { + m_cur++; + + if ( m_cur + 1 >= m_end ) + { + token.type = Err_UnfinishedChar; + return; + } + + unsigned char c = *m_cur; + + if ( c == '\\' ) + { + m_cur++; + + if ( m_cur + 1 >= m_end ) + { + token.type = Err_UnfinishedChar; + return; + } + + switch ( *m_cur ) + { + case '\\': break; + case '\"': c = '\"'; break; + case '\'': c = '\''; break; + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '0': c = '\0'; break; + case 'x': + { + char *pStart = ++m_cur; + while ( m_cur < m_end && _isxdigit( *m_cur ) ) + m_cur++; + + if ( m_cur == pStart || + (int)( m_cur - pStart ) > (int)sizeof(SQChar) * 2 || + m_cur >= m_end || + *m_cur != '\'' ) + { + token.type = Err_UnfinishedChar; + return; + } + + unsigned int val; + if ( !atox( { pStart, (unsigned int)( m_cur - pStart ) }, &val ) ) + { + token.type = Err_UnfinishedChar; + return; + } + + token.type = Token_Integer; + token._integer = val; + m_cur++; + return; + } + default: + token.type = Err_UnfinishedChar; + return; + } + } + + m_cur++; + + if ( *m_cur == '\'' ) + { + token.type = Token_Integer; + token._integer = c; + m_cur++; + } + else + { + token.type = Err_UnfinishedChar; + } + } + + void ParseNumber( token_t &token, bool neg ) + { + const char *pStart = m_cur; + static const int DECIMAL = 0; + static const int OCTAL = 1; + static const int FLOAT = 2; + char type = DECIMAL; + + if ( *m_cur == '0' ) + { + m_cur++; + + switch ( *m_cur ) + { + case 'x': + { + m_cur++; + + while ( m_cur < m_end && _isxdigit( *(unsigned char*)m_cur ) ) + m_cur++; + + string_t str; + str.Assign( pStart, m_cur - pStart ); + + if ( str.len < 3 || str.len > sizeof(SQInteger) * 2 + 2 ) + { + token.type = Err_InvalidHexadecimal; + return; + } + + Verify( atox( str, &token._integer ) ); + token.type = Token_Integer; + + if ( neg ) + token._integer = -token._integer; + + return; + } + case 'b': + { + m_cur++; + + while ( m_cur < m_end && IN_RANGE_CHAR( *(unsigned char*)m_cur, '0', '1' ) ) + m_cur++; + + string_t str; + str.Assign( pStart, m_cur - pStart ); + + if ( str.len < 3 || str.len > sizeof(SQInteger) * 8 + 2 ) + { + token.type = Err_InvalidBinary; + return; + } + + token.type = Token_Integer; + token._integer = 0; + + int inputbitlen = str.len - 2; + + for ( int i = str.len - 1; i >= 2; i-- ) + { + switch ( str.ptr[i] ) + { + case '1': + token._integer |= ( (SQUnsignedInteger)1 << ( ( inputbitlen - 1 ) - ( i - 2 ) ) ); + case '0': + continue; + default: UNREACHABLE(); + } + } + + if ( neg ) + token._integer = -token._integer; + + return; + } + default: + { + while ( m_cur < m_end && IN_RANGE_CHAR( *(unsigned char*)m_cur, '0', '7' ) ) + m_cur++; + + type = OCTAL; + break; + } + } + } + else + { + Assert( IN_RANGE_CHAR( *(unsigned char*)m_cur, '1', '9' ) ); + + do + { + m_cur++; + } + while ( IN_RANGE_CHAR( *(unsigned char*)m_cur, '0', '9' ) ); + + type = DECIMAL; + } + + if ( *m_cur == '.' ) + { + type = FLOAT; + m_cur++; + + while ( m_cur < m_end && IN_RANGE_CHAR( *(unsigned char*)m_cur, '0', '9' ) ) + m_cur++; + } + + if ( m_cur < m_end && ( *m_cur == 'e' || *m_cur == 'E' ) ) + { + type = FLOAT; + m_cur++; + + if ( m_cur >= m_end ) + { + token.type = Err_InvalidFloat; + return; + } + + if ( *m_cur == '-' || *m_cur == '+' ) + { + m_cur++; + + if ( m_cur >= m_end ) + { + token.type = Err_InvalidFloat; + return; + } + } + + while ( m_cur < m_end && IN_RANGE_CHAR( *(unsigned char*)m_cur, '0', '9' ) ) + m_cur++; + } + + string_t str; + str.Assign( pStart, m_cur - pStart ); + + switch ( type ) + { + case DECIMAL: + { + if ( str.len > FMT_INT_LEN - 1 ) + { + token.type = Err_InvalidDecimal; + return; + } + + Verify( atoi( str, &token._integer ) ); + token.type = Token_Integer; + + if ( neg ) + token._integer = -token._integer; + + return; + } + case OCTAL: + { + if ( str.len > FMT_OCT_LEN ) + { + token.type = Err_InvalidOctal; + return; + } + + Verify( atoo( str, &token._integer ) ); + token.type = Token_Integer; + + if ( neg ) + token._integer = -token._integer; + + return; + } + case FLOAT: + { + char c = str.ptr[str.len]; + str.ptr[str.len] = 0; + token.type = Token_Float; + token._float = (SQFloat)strtod( str.ptr, NULL ); + str.ptr[str.len] = c; + + if ( neg ) + token._float = -token._float; + + return; + } + default: UNREACHABLE(); + } + } +}; + +SQDebugServer::ECompileReturnCode SQDebugServer::Evaluate( string_t &expression, + HSQUIRRELVM vm, const SQVM::CallInfo *ci, SQObjectPtr &ret ) +{ + CCompiler c( expression ); + ECompileReturnCode r = c.Evaluate( this, vm, ci, ret ); + return r; +} + +SQDebugServer::ECompileReturnCode SQDebugServer::Evaluate( string_t &expression, + HSQUIRRELVM vm, const SQVM::CallInfo *ci, SQObjectPtr &ret, objref_t &obj ) +{ + CCompiler c( expression ); + ECompileReturnCode r = c.Evaluate( this, vm, ci, ret ); + obj = c.m_lastRef; + return r; +} + +SQTable *SQDebugServer::GetDefaultDelegate( HSQUIRRELVM vm, SQObjectType type ) +{ + SQTable *del; + + switch ( type ) + { + case OT_TABLE: del = _table(_ss(vm)->_table_default_delegate); break; + case OT_ARRAY: del = _table(_ss(vm)->_array_default_delegate); break; + case OT_STRING: del = _table(_ss(vm)->_string_default_delegate); break; + case OT_INTEGER: + case OT_FLOAT: + case OT_BOOL: del = _table(_ss(vm)->_number_default_delegate); break; + case OT_GENERATOR: del = _table(_ss(vm)->_generator_default_delegate); break; + case OT_NATIVECLOSURE: + case OT_CLOSURE: del = _table(_ss(vm)->_closure_default_delegate); break; + case OT_THREAD: del = _table(_ss(vm)->_thread_default_delegate); break; + case OT_CLASS: del = _table(_ss(vm)->_class_default_delegate); break; + case OT_INSTANCE: del = _table(_ss(vm)->_instance_default_delegate); break; + case OT_WEAKREF: del = _table(_ss(vm)->_weakref_default_delegate); break; + default: del = NULL; + } + + return del; +} + +bool SQDebugServer::ArithOp( char op, const SQObjectPtr &lhs, const SQObjectPtr &rhs, SQObjectPtr &out ) +{ + SQObjectType tl = sq_type(lhs); + SQObjectType tr = sq_type(rhs); + + if ( ( tl | tr ) == OT_INTEGER ) + { + switch ( op ) + { + case '+': + out = _integer(lhs) + _integer(rhs); + return true; + case '-': + out = _integer(lhs) - _integer(rhs); + return true; + case '*': + out = _integer(lhs) * _integer(rhs); + return true; + case '/': + if ( _integer(rhs) == 0 || ( _integer(rhs) == -1 && _integer(lhs) == INT_MIN ) ) + return false; + out = _integer(lhs) / _integer(rhs); + return true; + case '%': + if ( _integer(rhs) == 0 || ( _integer(rhs) == -1 && _integer(lhs) == INT_MIN ) ) + return false; + out = _integer(lhs) % _integer(rhs); + return true; + default: UNREACHABLE(); + } + } + + if ( ( tl | tr ) == OT_FLOAT ) + { + switch ( op ) + { + case '+': + out = _float(lhs) + _float(rhs); + return true; + case '-': + out = _float(lhs) - _float(rhs); + return true; + case '*': + out = _float(lhs) * _float(rhs); + return true; + case '/': + out = _float(lhs) / _float(rhs); + return true; + case '%': + out = (SQFloat)fmod( _float(lhs), _float(rhs) ); + return true; + default: UNREACHABLE(); + } + } + + if ( ( tl | tr ) == ( OT_INTEGER | OT_FLOAT ) ) + { + SQFloat fl, fr; + + if ( tl == OT_INTEGER ) + { + fl = (SQFloat)_integer(lhs); + fr = _float(rhs); + } + else if ( tr == OT_INTEGER ) + { + fl = _float(lhs); + fr = (SQFloat)_integer(rhs); + } + else UNREACHABLE(); + + switch ( op ) + { + case '+': + out = fl + fr; + return true; + case '-': + out = fl - fr; + return true; + case '*': + out = fl * fr; + return true; + case '/': + out = fl / fr; + return true; + case '%': + out = (SQFloat)fmod( fl, fr ); + return true; + default: UNREACHABLE(); + } + } + + if ( ( ( tl | tr ) & OT_STRING ) == OT_STRING && op == '+' ) + { + // NOTE: calls MT_TOSTRING + return m_pRootVM->StringCat( lhs, rhs, out ); + } + + if ( is_delegable(lhs) && _delegable(lhs)->_delegate ) + { + SQMetaMethod emm; + switch ( op ) + { + case '+': emm = MT_ADD; break; + case '-': emm = MT_SUB; break; + case '*': emm = MT_MUL; break; + case '/': emm = MT_DIV; break; + case '%': emm = MT_MODULO; break; + default: UNREACHABLE(); + } + + SQObjectPtr mm; + if ( _delegable(lhs)->GetMetaMethod( m_pRootVM, emm, mm ) ) + { + return RunClosure( mm, &lhs, rhs, out ); + } + } + + // String multiplication + if ( tl == OT_STRING && tr == OT_INTEGER ) + { + int len = _string(lhs)->_len * _integer(rhs); + + if ( len == 1 ) + { + out = lhs; + return true; + } + + if ( len <= 0 ) + return false; + + if ( (unsigned int)sq_rsl(len) > (unsigned int)INT_MAX ) + { + _OutputDebugStringFmt( _SC("**(sqdbg) Truncated string multiplication (%u)\n" ), + sq_rsl( _string(lhs)->_len * _integer(rhs) ) ); + len = INT_MAX / sizeof(SQChar); + } + + SQChar *tmp = (SQChar*)ScratchPad( sq_rsl(len) ); + + if ( !tmp ) + { + out = CreateSQString( _ss(m_pRootVM), _SC(STR_NOMEM) ); + return _string(out) != NULL; + } + + for ( int i = 0; i < len; i += (int)_string(lhs)->_len ) + { + memcpy( tmp + i, _string(lhs)->_val, sq_rsl(_string(lhs)->_len) ); + } + + out = SQString::Create( _ss(m_pRootVM), tmp, len ); + return true; + } + + return false; +} +#endif + +void SQDebugServer::ConvertPtr( objref_t &obj ) +{ + // doesn't convert outer vars + if ( obj.type & ~objref_t::PTR ) + obj.type = (objref_t::EOBJREF)( obj.type & ~objref_t::PTR ); +} + +bool SQDebugServer::GetObj_Var( const SQObjectPtr &var, const SQObjectPtr &key, objref_t &out, SQObjectPtr &value ) +{ + Assert( &key != &value ); + + switch ( sq_type(var) ) + { + case OT_TABLE: + { + SQTable *t = _table(var); + do + { + if ( t->Get( key, value ) ) + { + out.type = objref_t::TABLE; + out.src = t; + out.key = key; + return true; + } + } + while ( ( t = t->_delegate ) != NULL ); + + break; + } + case OT_INSTANCE: + { + if ( _instance(var)->Get( key, value ) ) + { + out.type = objref_t::INSTANCE; + out.src = var; + out.key = key; + return true; + } + + break; + } + case OT_CLASS: + { + if ( _class(var)->_members->Get( key, value ) ) + { + out.type = (objref_t::EOBJREF)( objref_t::PTR | objref_t::CLASS ); + + if ( _isfield(value) ) + { + out.ptr = &_class(var)->_defaultvalues[ _member_idx(value) ].val; + } + else + { + out.ptr = &_class(var)->_methods[ _member_idx(value) ].val; + } + + out.src = var; + out.key = key; + + value = *out.ptr; + return true; + } + + return false; + } + case OT_ARRAY: + { + if ( sq_type(key) == OT_INTEGER && + _integer(key) >= 0 && _integer(key) < _array(var)->Size() ) + { + out.type = (objref_t::EOBJREF)( objref_t::PTR | objref_t::ARRAY ); + out.ptr = &_array(var)->_values[ _integer(key) ]; + + out.src = var; + out.key = _integer(key); + + value = *out.ptr; + return true; + } + + return false; + } + case OT_STRING: + { + if ( sq_type(key) == OT_INTEGER && + _integer(key) >= 0 && _integer(key) < _string(var)->_len ) + { + out.type = objref_t::INT; +#ifdef SQUNICODE + out.val = (int)_string(var)->_val[ _integer(key) ]; +#else + out.val = (int)(unsigned char)_string(var)->_val[ _integer(key) ]; +#endif + value = (SQInteger)out.val; + return true; + } + + return false; + } + case OT_USERDATA: + break; + default: + return false; + } + + // metamethods + switch ( sq_type(var) ) + { + case OT_INSTANCE: + case OT_TABLE: + case OT_USERDATA: + { + SQObjectPtr mm; + if ( _delegable(var)->GetMetaMethod( m_pRootVM, MT_GET, mm ) ) + { + if ( RunClosure( mm, &var, key, value ) ) + { + out.type = objref_t::DELEGABLE_META; + out.src = var; + out.key = key; + return true; + } + } + + break; + } + default: UNREACHABLE(); + } + + // user defined + if ( sq_type(var) == OT_INSTANCE ) + { + const SQObjectPtr *def = GetClassDefCustomMembers( _instance(var)->_class ); + + if ( def ) + { + SQObjectPtr custommembers = *def; + + if ( sq_type(custommembers) == OT_CLOSURE ) + RunClosure( custommembers, &var, custommembers ); + + if ( sq_type(custommembers) == OT_ARRAY ) + { + objref_t tmp; + SQObjectPtr strName = CreateSQString( m_pRootVM, _SC("name") ); + SQObjectPtr strGet = CreateSQString( m_pRootVM, _SC("get") ); + SQObjectPtr name; + + for ( unsigned int i = 0; i < _array(custommembers)->_values.size(); i++ ) + { + const SQObjectPtr &memdef = _array(custommembers)->_values[i]; + + if ( GetObj_Var( memdef, strName, tmp, name ) && + IsEqual( key, name ) ) + { + if ( GetObj_Var( memdef, strGet, tmp, value ) && + CallCustomMembersGetFunc( value, &var, key, value ) ) + { + out.type = objref_t::CUSTOMMEMBER; + out.src = var; + out.key = key; + return true; + } + + return false; + } + } + } + } + } + + // could be a default delegate func + return false; +} + +bool SQDebugServer::GetObj_Var( const SQObjectPtr &var, string_t &expression, bool identifierIsString, + objref_t &out, SQObjectPtr &value ) +{ + switch ( sq_type(var) ) + { + case OT_ARRAY: + { + if ( expression.len <= 2 || !( expression.ptr[0] == '[' && expression.ptr[expression.len-1] == ']' ) ) + return false; + + expression.ptr++; + expression.len -= 2; + + SQInteger idx; + if ( strtoint( expression, &idx ) && + idx >= 0 && idx < _array(var)->Size() ) + { + out.type = (objref_t::EOBJREF)( objref_t::PTR | objref_t::ARRAY ); + out.ptr = &_array(var)->_values[idx]; + + out.src = var; + out.key = idx; + + value = *out.ptr; + return true; + } + + return false; + } + // Used in local indexing + case OT_STRING: + { + if ( expression.len <= 2 || !( expression.ptr[0] == '[' && expression.ptr[expression.len-1] == ']' ) ) + return false; + + expression.ptr++; + expression.len -= 2; + + SQInteger idx; + if ( atoi( expression, &idx ) && + idx >= 0 && idx < _string(var)->_len ) + { + out.type = objref_t::INT; +#ifdef SQUNICODE + out.val = (int)_string(var)->_val[idx]; +#else + out.val = (int)(unsigned char)_string(var)->_val[idx]; +#endif + value = (SQInteger)out.val; + return true; + } + + return false; + } + case OT_TABLE: + case OT_INSTANCE: + case OT_CLASS: + case OT_USERDATA: + break; + default: + return false; + } + + SQObjectPtr identifier; + + if ( identifierIsString ) + { + identifier = CreateSQString( this, expression ); + } + else + { + switch ( expression.ptr[0] ) + { + // string + case '\"': + { + Assert( expression.len > 2 ); + + if ( expression.len <= 2 ) + return false; + + expression.ptr++; + expression.len -= 2; + +#ifdef SQUNICODE + int len = sq_rsl( SQUnicodeLength< true >( expression.ptr, expression.len ) + 1 ); + SQChar *tmp = (SQChar*)ScratchPad( len ); + + if ( !tmp ) + return false; + + len = UTF8ToSQUnicode< true >( tmp, len, expression.ptr, expression.len ); + tmp[len] = 0; + identifier = SQString::Create( _ss(m_pRootVM), tmp, len ); +#else + UndoEscape( expression.ptr, &expression.len ); + identifier = CreateSQString( this, expression ); +#endif + break; + } + // integer + case '[': + { + Assert( expression.len > 2 ); + + if ( expression.len <= 2 ) + return false; + + expression.ptr++; + expression.len -= 2; + + SQInteger val; + if ( !strtoint( expression, &val ) ) + return false; + + identifier = val; + break; + } +#if 0 + // raw bytes + case 'R': + { + Assert( expression.len > 4 ); + + if ( expression.len <= 4 ) + return false; + + Assert( expression.ptr[1] == '\"' ); + + expression.ptr += 2; + expression.len -= 3; + + if ( !ReadStringifiedBytes( expression.ptr, &expression.len ) ) + return false; + + identifier = SQString::Create( _ss(m_pRootVM), + (SQChar*)expression.ptr, expression.len / sizeof(SQChar) ); + break; + } +#endif + default: + { + // object, check every member + if ( expression.StartsWith("0x") ) + { + Assert( expression.len >= FMT_PTR_LEN ); + + if ( expression.len < FMT_PTR_LEN ) + return false; + + expression.len = FMT_PTR_LEN; + + uintptr_t pKey; + if ( !atox( expression, &pKey ) ) + return false; + + SQObjectPtr obj = var; + SQ_FOREACH_OP( obj, key, val ) + { + if ( _rawval(key) == (SQRawObjectVal)pKey ) + { + switch ( sq_type(var) ) + { + case OT_TABLE: out.type = objref_t::TABLE; break; + case OT_INSTANCE: out.type = objref_t::INSTANCE; break; + case OT_CLASS: + { + if ( _class(var)->_members->Get( key, val ) ) + { + out.type = (objref_t::EOBJREF)( objref_t::PTR | objref_t::CLASS ); + + if ( _isfield(val) ) + { + out.ptr = &_class(var)->_defaultvalues[ _member_idx(val) ].val; + } + else + { + out.ptr = &_class(var)->_methods[ _member_idx(val) ].val; + } + + out.src = var; + out.key = key; + + value = *out.ptr; + return true; + } + + return false; + } + default: UNREACHABLE(); + } + + out.src = var; + out.key = key; + + value = val; + return true; + } + } + SQ_FOREACH_END() + + return false; + } + // float + // NOTE: float keys are broken in pre 225 if sizeof(SQInteger) != sizeof(SQFloat), + // this is fixed in SQ 225 with SQ_OBJECT_RAWINIT in SQObjectPtr::operator=(SQFloat) + else + { + identifier = (SQFloat)strtod( expression.ptr, NULL ); + } + } + } + } + + return GetObj_Var( var, identifier, out, value ); +} + +bool SQDebugServer::GetObj_Frame( HSQUIRRELVM vm, const SQVM::CallInfo *ci, const string_t &expression, + objref_t &out, SQObjectPtr &value ) +{ + Assert( expression.len ); + Assert( !ci || ( ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize ) ); + + if ( ci && sq_type(ci->_closure) == OT_CLOSURE ) + { + SQClosure *pClosure = _closure(ci->_closure); + SQFunctionProto *func = _fp(pClosure->_function); + SQUnsignedInteger ip = (SQUnsignedInteger)( ci->_ip - func->_instructions - 1 ); + + for ( int i = 0; i < func->_nlocalvarinfos; i++ ) + { + const SQLocalVarInfo &var = func->_localvarinfos[i]; + if ( var._start_op <= ip + 1 && var._end_op >= ip && + expression.IsEqualTo( _string(var._name) ) ) + { + int stackbase = GetStackBase( vm, ci ); + out.type = objref_t::STACK; + out.stack.thread = GetWeakRef( vm ); + out.stack.frame = ci - vm->_callsstack; + Assert( var._end_op < INT_MAX ); + out.stack.end = var._end_op + 1; + out.stack.index = stackbase + var._pos; + value = vm->_stack._vals[ out.stack.index ]; + return true; + } + } + + for ( int i = 0; i < func->_noutervalues; i++ ) + { + const SQOuterVar &var = func->_outervalues[i]; + if ( expression.IsEqualTo( _string(var._name) ) ) + { + out.type = objref_t::PTR; + out.ptr = _outervalptr( pClosure->_outervalues[i] ); + value = *out.ptr; + return true; + } + } + + if ( expression.len == 6 || expression.len == 7 ) + { + if ( expression.IsEqualTo( KW_THIS ) ) + { + int stackbase = GetStackBase( vm, ci ); + out.type = objref_t::PTR; + out.ptr = &vm->_stack._vals[ stackbase ]; + value = *out.ptr; + return true; + } +#if SQUIRREL_VERSION_NUMBER >= 300 + else if ( func->_varparams && func->_nlocalvarinfos >= 2 ) + { + if ( expression.IsEqualTo( KW_VARGV ) ) + { + const SQLocalVarInfo &var = func->_localvarinfos[ func->_nlocalvarinfos - 2 ]; + if ( sqstring_t(_SC("vargv")).IsEqualTo( _string(var._name) ) ) + { + int stackbase = GetStackBase( vm, ci ); + out.type = objref_t::PTR; + out.ptr = &vm->_stack._vals[ stackbase + 1 ]; + value = *out.ptr; + return true; + } + } + else if ( expression.IsEqualTo( KW_VARGC ) ) + { + const SQLocalVarInfo &var = func->_localvarinfos[ func->_nlocalvarinfos - 2 ]; + if ( sqstring_t(_SC("vargv")).IsEqualTo( _string(var._name) ) ) + { + int stackbase = GetStackBase( vm, ci ); + const SQObjectPtr &val = vm->_stack._vals[ stackbase + 1 ]; + + if ( sq_type(val) == OT_ARRAY ) + { + out.type = objref_t::INT; + out.val = (int)_array(val)->Size(); + value = _array(val)->Size(); + return true; + } + + return false; + } + } + } +#else + else if ( func->_varparams ) + { + // Returning a temporary vargv array here would be pointless in general, + // and extra work for completions + if ( expression.IsEqualTo( KW_VARGC ) ) + { + out.type = objref_t::INT; + out.val = (int)ci->_vargs.size; + value = (SQInteger)ci->_vargs.size; + return true; + } + } +#endif + } + } + else + { + if ( expression.IsEqualTo( "this" ) ) + { + out.type = (objref_t::EOBJREF)( objref_t::PTR | objref_t::READONLY ); + out.ptr = &vm->_roottable; + value = *out.ptr; + return true; + } + } + +#if SQUIRREL_VERSION_NUMBER >= 220 + SQTable *pConstTable = _table(_ss(vm)->_consts); + if ( SQTable_Get( this, pConstTable, expression, value ) ) + { + out.type = (objref_t::EOBJREF)( objref_t::TABLE | objref_t::READONLY ); + out.src = pConstTable; + out.key = CreateSQString( this, expression ); + return true; + } +#endif + + if ( ci ) + { + const SQObjectPtr &env = vm->_stack._vals[ GetStackBase( vm, ci ) ]; + if ( sq_type(env) == OT_TABLE ) + { + SQTable *t = _table(env); + do + { + if ( SQTable_Get( this, t, expression, value ) ) + { + out.type = objref_t::TABLE; + out.src = t; + out.key = CreateSQString( this, expression ); + return true; + } + } + while ( ( t = t->_delegate ) != NULL ); + } + else if ( sq_type(env) == OT_INSTANCE ) + { + SQObjectPtr pExpression = CreateSQString( this, expression ); + if ( _instance(env)->Get( pExpression, value ) ) + { + out.type = objref_t::INSTANCE; + out.src = env; + out.key = pExpression; + return true; + } + } + } + + SQTable *root = _table(vm->_roottable); + +#ifdef CLOSURE_ROOT + if ( ci && sq_type(ci->_closure) == OT_CLOSURE && + _closure(ci->_closure)->_root && + _table(_closure(ci->_closure)->_root->_obj) != root ) + { + Assert( sq_type(_closure(ci->_closure)->_root->_obj) == OT_TABLE ); + root = _table(_closure(ci->_closure)->_root->_obj); + } +#endif + + do + { + if ( SQTable_Get( this, root, expression, value ) ) + { + out.type = objref_t::TABLE; + out.src = root; + out.key = CreateSQString( this, expression ); + return true; + } + } + while ( ( root = root->_delegate ) != NULL ); + + return false; +} + +bool SQDebugServer::GetObj_VarRef( const varref_t *ref, string_t &expression, objref_t &out, SQObjectPtr &value ) +{ + switch ( ref->type ) + { + case VARREF_OBJ: + { + return GetObj_Var( ref->GetVar(), expression, !ref->obj.hasNonStringMembers, out, value ); + } + case VARREF_SCOPE_LOCALS: + case VARREF_SCOPE_OUTERS: + { + return GetObj_Frame( ref->GetThread(), ref->scope.frame, expression, out, value ); + } + case VARREF_OUTERS: + { + Assert( sq_type(ref->GetVar()) == OT_CLOSURE ); + + if ( sq_type(ref->GetVar()) == OT_CLOSURE ) + { + SQClosure *pClosure = _closure(ref->GetVar()); + SQFunctionProto *func = _fp(pClosure->_function); + + for ( int i = 0; i < func->_noutervalues; i++ ) + { + const SQOuterVar &var = func->_outervalues[i]; + if ( expression.IsEqualTo( _string(var._name) ) ) + { + out.type = objref_t::PTR; + out.ptr = _outervalptr( pClosure->_outervalues[i] ); + value = *out.ptr; + return true; + } + } + } + + return false; + } + case VARREF_LITERALS: + { + Assert( sq_type(ref->GetVar()) == OT_CLOSURE ); + + if ( sq_type(ref->GetVar()) == OT_CLOSURE ) + { + int idx; + if ( atoi( expression, &idx ) && + idx >= 0 && idx < (int)_fp(_closure(ref->GetVar())->_function)->_nliterals ) + { + out.type = objref_t::PTR; + out.ptr = &_fp(_closure(ref->GetVar())->_function)->_literals[idx]; + value = *out.ptr; + return true; + } + } + + return false; + } + case VARREF_METAMETHODS: + { + int mm = -1; + + for ( int i = 0; i < MT_LAST; i++ ) + { + if ( expression.IsEqualTo( g_MetaMethodName[i] ) ) + { + mm = i; + break; + } + } + + Assert( mm != -1 ); + + if ( mm != -1 ) + { + switch ( sq_type(ref->GetVar()) ) + { + case OT_CLASS: + { + out.type = objref_t::PTR; + out.ptr = &_class(ref->GetVar())->_metamethods[mm]; + value = *out.ptr; + return true; + } + case OT_TABLE: + { + // metamethods are regular members of tables + Assert( _table(ref->GetVar())->_delegate ); + + out.type = objref_t::TABLE; + out.src = _table(ref->GetVar())->_delegate; + out.key = CreateSQString( this, expression ); + return _table(out.src)->Get( out.key, value ); + } + default: Assert(0); + } + } + + return false; + } + case VARREF_STACK: + { + Assert( expression.len > 2 ); + + if ( expression.len <= 2 ) + return false; + + while ( expression.len > 3 && expression.ptr[expression.len-1] != ']' ) + expression.len--; + + expression.ptr++; + expression.len -= 2; + + int idx; + if ( strtoint( expression, &idx ) && + idx >= 0 && idx < (int)ref->GetThread()->_stack.size() ) + { + out.type = objref_t::PTR; + out.ptr = &ref->GetThread()->_stack._vals[idx]; + value = *out.ptr; + return true; + } + + return false; + } + case VARREF_INSTRUCTIONS: + case VARREF_CALLSTACK: + { + return false; + } + default: + { + PrintError(_SC("(sqdbg) Invalid varref requested (%d)\n"), ref->type); + AssertMsg1( 0, "Invalid varref requested (%d)", ref->type ); + return false; + } + } +} + +static inline string_t GetPresentationHintKind( const SQObjectPtr &obj ) +{ + switch ( sq_type(obj) ) + { + case OT_CLOSURE: + case OT_NATIVECLOSURE: + return "method"; + case OT_CLASS: + return "class"; + default: + return "property"; + } +} + +bool SQDebugServer::ShouldPageArray( const SQObject &obj, unsigned int limit ) +{ + return ( sq_type(obj) == OT_ARRAY && _array(obj)->_values.size() > limit ); +} + +bool SQDebugServer::ShouldParseEvaluateName( const string_t &expression ) +{ + return ( expression.len >= 4 && expression.ptr[0] == '@' && expression.ptr[2] == '@' ); +} + +bool SQDebugServer::ParseEvaluateName( const string_t &expression, HSQUIRRELVM vm, int frame, + objref_t &out, SQObjectPtr &value ) +{ + Assert( ShouldParseEvaluateName( expression ) ); + + if ( expression.ptr[1] == 'L' ) + { + int idx; + if ( !atoi( { expression.ptr + 3, expression.len - 3 }, &idx ) ) + return false; + + if ( !IsValidStackFrame( vm, frame ) ) + return false; + + const SQVM::CallInfo &ci = vm->_callsstack[ frame ]; + + if ( sq_type(ci._closure) != OT_CLOSURE ) + return false; + + SQClosure *pClosure = _closure(ci._closure); + SQFunctionProto *func = _fp(pClosure->_function); + SQUnsignedInteger ip = (SQUnsignedInteger)( ci._ip - func->_instructions - 1 ); + + idx = func->_nlocalvarinfos - idx - 1; + + if ( idx < 0 || idx >= func->_nlocalvarinfos ) + return false; + + const SQLocalVarInfo &var = func->_localvarinfos[idx]; + if ( var._start_op <= ip + 1 && var._end_op >= ip ) + { + int stackbase = GetStackBase( vm, &ci ); + out.type = objref_t::PTR; + out.ptr = &vm->_stack._vals[ stackbase + var._pos ]; + value = *out.ptr; + return true; + } + } + + return false; +} + +bool SQDebugServer::ParseBinaryNumber( const string_t &value, SQObject &out ) +{ + const int maxbitlen = max( sizeof(SQInteger), sizeof(SQFloat) ) * 8; + + // expect 0b prefix + if ( value.len <= 2 || value.len > maxbitlen + 2 || value.ptr[0] != '0' || value.ptr[1] != 'b' ) + return false; + + out._type = OT_INTEGER; + out._unVal.nInteger = 0; + + int inputbitlen = value.len - 2; + Assert( inputbitlen > 0 && inputbitlen <= maxbitlen ); + + for ( int i = value.len - 1; i >= 2; i-- ) + { + switch ( value.ptr[i] ) + { + case '1': + out._unVal.nInteger |= ( (SQUnsignedInteger)1 << ( ( inputbitlen - 1 ) - ( i - 2 ) ) ); + case '0': + continue; + default: + return false; + } + } + + return true; +} + +int SQDebugServer::ParseFormatSpecifiers( string_t &expression, char **ppComma ) +{ + if ( expression.len <= 2 ) + return 0; + + int flags = 0; + + // 7 flags at most ",*lnax0b\0" + char *start = expression.ptr + expression.len - 9; + char *end = expression.ptr + expression.len; + char *c = end - 1; + char *comma = NULL; + + if ( start < expression.ptr ) + start = expression.ptr; + + for ( ; c > start; c-- ) + { + if ( *c == ',' ) + { + comma = c; + c++; + break; + } + } + + // have flags + if ( comma ) + { + // has to be the first flag + if ( *c == '*' ) + { + flags |= kFS_Lock; + c++; + } + + if ( c < end ) + { +check: + switch ( *c++ ) + { + case 'x': flags |= kFS_Hexadecimal; break; + case 'X': flags |= kFS_Hexadecimal | kFS_Uppercase; break; + case 'b': flags |= kFS_Binary; break; + case 'd': flags |= kFS_Decimal; break; + case 'o': flags |= kFS_Octal; break; + case 'c': flags |= kFS_Character; break; + case 'f': flags |= kFS_Float; break; + case 'e': flags |= kFS_FloatE; break; + case 'g': flags |= kFS_FloatG; break; + case 'l': + if ( flags & kFS_ListMembers ) + return 0; + + flags |= kFS_ListMembers; + + if ( c < end ) + goto check; + + break; + case 'n': + if ( flags & kFS_NoAddr ) + return 0; + + if ( c < end && *c++ == 'a' ) + { + flags |= kFS_NoAddr; + + if ( c < end ) + goto check; + + break; + } + default: return 0; // Invalid flag + } + + // modifier + if ( ( flags & ( kFS_Hexadecimal | kFS_Binary ) ) && c < end ) + { + switch ( *c++ ) + { + case '0': flags |= kFS_Padding; break; + case 'b': flags |= kFS_NoPrefix; break; + default: return 0; + } + + if ( ( flags & kFS_Padding ) && c < end ) + { + switch ( *c++ ) + { + case 'b': flags |= kFS_NoPrefix; break; + default: return 0; + } + } + } + + // there should be no more flags + if ( c < end ) + return 0; + } + + if ( flags ) + { + expression.len = comma - expression.ptr; + + // Terminate here, this expression might be passed to SQTable::Get through GetObj, + // which compares strings disregarding length + *comma = 0; + + if ( ppComma ) + *ppComma = comma; + } + } + + return flags; +} + +void SQDebugServer::OnRequest_Evaluate( const json_table_t &arguments, int seq ) +{ + HSQUIRRELVM vm; + int frame; + string_t context, expression; + + arguments.GetString( "context", &context ); + arguments.GetString( "expression", &expression ); + arguments.GetInt( "frameId", &frame, -1 ); + + if ( expression.IsEmpty() ) + { + DAP_ERROR_RESPONSE( seq, "evaluate" ); + DAP_ERROR_BODY( 0, "empty expression" ); + DAP_SEND(); + return; + } + + if ( !TranslateFrameID( frame, &vm, &frame ) ) + { + vm = m_pCurVM; + frame = -1; + } + + int flags = 0; + { + json_table_t *format; + if ( arguments.GetTable( "format", &format ) ) + { + bool hex; + format->GetBool( "hex", &hex ); + if ( hex ) + flags |= kFS_Hexadecimal; + } + } + + if ( IS_INTERNAL_TAG( expression.ptr ) ) + { + if ( expression.IsEqualTo( INTERNAL_TAG("stack") ) ) + { + DAP_START_RESPONSE( seq, "evaluate" ); + DAP_SET_TABLE( body ); + body.SetIntBrackets( "result", (SQInteger)vm->_stack.size(), ( flags & kFS_Hexadecimal ) != 0 ); + body.SetInt( "variablesReference", ToVarRef( VARREF_STACK, vm, frame ) ); + wjson_table_t hint = body.SetTable( "presentationHint" ); + wjson_array_t attributes = hint.SetArray( "attributes" ); + attributes.Append( "readOnly" ); + DAP_SEND(); + + return; + } + else if ( expression.IsEqualTo( INTERNAL_TAG("function") ) ) + { + if ( IsValidStackFrame( vm, frame ) ) + { + const SQObjectPtr &res = vm->_callsstack[ frame ]._closure; + + DAP_START_RESPONSE( seq, "evaluate" ); + DAP_SET_TABLE( body ); + body.SetString( "result", GetValue( res, flags ) ); + body.SetString( "type", GetType( res ) ); + body.SetInt( "variablesReference", ToVarRef( res, true ) ); + wjson_table_t hint = body.SetTable( "presentationHint" ); + wjson_array_t attributes = hint.SetArray( "attributes" ); + attributes.Append( "readOnly" ); + DAP_SEND(); + } + else + { + DAP_ERROR_RESPONSE( seq, "evaluate" ); + DAP_ERROR_BODY( 0, "could not evaluate expression" ); + DAP_SEND(); + } + + return; + } + else if ( expression.IsEqualTo( INTERNAL_TAG("caller") ) ) + { + if ( IsValidStackFrame( vm, frame - 1 ) ) + { + const SQObjectPtr &res = vm->_callsstack[ frame - 1 ]._closure; + + DAP_START_RESPONSE( seq, "evaluate" ); + DAP_SET_TABLE( body ); + body.SetString( "result", GetValue( res, flags ) ); + body.SetString( "type", GetType( res ) ); + body.SetInt( "variablesReference", ToVarRef( res, true ) ); + wjson_table_t hint = body.SetTable( "presentationHint" ); + wjson_array_t attributes = hint.SetArray( "attributes" ); + attributes.Append( "readOnly" ); + DAP_SEND(); + } + else + { + DAP_ERROR_RESPONSE( seq, "evaluate" ); + DAP_ERROR_BODY( 0, "could not evaluate expression" ); + DAP_SEND(); + } + + return; + } + } + + if ( context.IsEqualTo( "repl" ) ) + { + // Don't hit breakpoints unless it's repl + m_bInREPL = true; + + // Don't print quotes in repl + flags |= kFS_NoQuote; + } + else + { + m_bDebugHookGuardAlways = true; + } + + SQObjectPtr res; + + if ( context.IsEqualTo( "watch" ) || context.IsEqualTo( "clipboard" ) || context.IsEqualTo( "hover" ) ) + { + flags |= ParseFormatSpecifiers( expression ); + + objref_t obj; + + if ( ShouldParseEvaluateName( expression ) ) + { + if ( ParseEvaluateName( expression, vm, frame, obj, res ) ) + { + DAP_START_RESPONSE( seq, "evaluate" ); + DAP_SET_TABLE( body ); + JSONSetString( body, "result", res, flags ); + body.SetString( "type", GetType( res ) ); + body.SetInt( "variablesReference", ToVarRef( res, true ) ); + if ( ShouldPageArray( res, ARRAY_PAGE_LIMIT ) ) + body.SetInt( "indexedVariables", (int)_array(res)->_values.size() ); + wjson_table_t hint = body.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( res ) ); + DAP_SEND(); + } + else + { + DAP_ERROR_RESPONSE( seq, "evaluate" ); + DAP_ERROR_BODY( 0, "could not evaluate expression" ); + DAP_SEND(); + } + + return; + } + + if ( flags & kFS_Lock ) + { + bool foundWatch = false; + + for ( unsigned int i = 0; i < m_LockedWatches.size(); i++ ) + { + const watch_t &w = m_LockedWatches[i]; + if ( w.expression.IsEqualTo( expression ) ) + { + vm = GetThread( w.thread ); + frame = w.frame; + foundWatch = true; + break; + } + } + + if ( !foundWatch ) + { + watch_t &w = m_LockedWatches.append(); + CopyString( &m_Strings, expression, &w.expression ); + w.thread = GetWeakRef( vm ); + w.frame = frame; + } + } + +#ifndef SQDBG_DISABLE_COMPILER + ECompileReturnCode cres = Evaluate( expression, vm, frame, res ); + + if ( cres == CompileReturnCode_Success ) + { + DAP_START_RESPONSE( seq, "evaluate" ); + DAP_SET_TABLE( body ); + JSONSetString( body, "result", res, flags ); + body.SetString( "type", GetType( res ) ); + body.SetInt( "variablesReference", ToVarRef( res, true ) ); + if ( ShouldPageArray( res, ARRAY_PAGE_LIMIT ) ) + body.SetInt( "indexedVariables", (int)_array(res)->_values.size() ); + wjson_table_t hint = body.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( res ) ); + DAP_SEND(); + } +#else + if ( GetObj_Frame( vm, frame, expression, obj, res ) ) + { + DAP_START_RESPONSE( seq, "evaluate" ); + DAP_SET_TABLE( body ); + JSONSetString( body, "result", res, flags ); + body.SetString( "type", GetType( res ) ); + body.SetInt( "variablesReference", ToVarRef( res, true ) ); + if ( ShouldPageArray( res, ARRAY_PAGE_LIMIT ) ) + body.SetInt( "indexedVariables", (int)_array(res)->_values.size() ); + wjson_table_t hint = body.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( res ) ); + DAP_SEND(); + } +#endif +#ifndef SQDBG_DISABLE_COMPILER + else if ( cres > CompileReturnCode_Fallback && + RunExpression( expression, vm, frame, res ) ) +#else + else if ( RunExpression( expression, vm, frame, res ) || + ParseBinaryNumber( expression, res ) ) +#endif + { + DAP_START_RESPONSE( seq, "evaluate" ); + DAP_SET_TABLE( body ); + JSONSetString( body, "result", res, flags ); + body.SetString( "type", GetType( res ) ); + body.SetInt( "variablesReference", ToVarRef( res, true ) ); + if ( ShouldPageArray( res, ARRAY_PAGE_LIMIT ) ) + body.SetInt( "indexedVariables", (int)_array(res)->_values.size() ); + wjson_table_t hint = body.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( res ) ); + wjson_array_t attributes = hint.SetArray( "attributes" ); + attributes.Append( "readOnly" ); + DAP_SEND(); + } + else + { + if ( flags & kFS_Lock ) + { + for ( unsigned int i = 0; i < m_LockedWatches.size(); i++ ) + { + watch_t &w = m_LockedWatches[i]; + if ( w.expression.IsEqualTo( expression ) ) + { + FreeString( &m_Strings, &w.expression ); + m_LockedWatches.remove( i ); + break; + } + } + } + + DAP_ERROR_RESPONSE( seq, "evaluate" ); + DAP_ERROR_BODY( 0, "could not evaluate expression" ); + DAP_SEND(); + } + } + else + { + Assert( context.IsEqualTo( "repl" ) || context.IsEqualTo( "variables" ) ); + + if ( RunExpression( expression, vm, frame, res, expression.Contains('\n') ) || + ParseBinaryNumber( expression, res ) ) + { + DAP_START_RESPONSE( seq, "evaluate" ); + DAP_SET_TABLE( body ); + JSONSetString( body, "result", res, flags ); + body.SetInt( "variablesReference", ToVarRef( res, context.IsEqualTo( "repl" ) ) ); + if ( ShouldPageArray( res, ARRAY_PAGE_LIMIT ) ) + body.SetInt( "indexedVariables", (int)_array(res)->_values.size() ); + DAP_SEND(); + } + else + { + DAP_ERROR_RESPONSE( seq, "evaluate" ); + DAP_ERROR_BODY( 0, "{reason}" ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetString( "reason", GetValue( vm->_lasterror, kFS_NoQuote ) ); + DAP_SEND(); + } + } + + if ( context.IsEqualTo( "repl" ) ) + { + m_bInREPL = false; + } + else + { + m_bDebugHookGuardAlways = false; + } +} + +#ifndef SQDBG_DISABLE_COMPILER +// A very simple and incomplete completions implementation +// using the extra information from the sqdbg compiler. +// Easily breaks within parantheses and brackets, works best with simple expressions +void SQDebugServer::OnRequest_Completions( const json_table_t &arguments, int seq ) +{ + HSQUIRRELVM vm; + int frame; + string_t text; + int column; + + arguments.GetString( "text", &text ); + arguments.GetInt( "frameId", &frame, -1 ); + arguments.GetInt( "column", &column ); + + column -= (int)m_bClientColumnOffset; + + if ( column < 0 || column > (int)text.len ) + { + DAP_ERROR_RESPONSE( seq, "completions" ); + DAP_ERROR_BODY( 0, "invalid column" ); + DAP_SEND(); + return; + } + + if ( !TranslateFrameID( frame, &vm, &frame ) ) + { + vm = m_pCurVM; + frame = -1; + } + + SQObjectPtr target; + + string_t expr( text.ptr, column ); + CCompiler c( expr ); + ECompileReturnCode r = c.Evaluate( this, vm, GetStackFrame( vm, frame ), target ); + + CCompiler::token_t token = c.m_lastToken; + + if ( ( r != CompileReturnCode_Success && + r != CompileReturnCode_DoesNotExist && + r != CompileReturnCode_NoValue ) && + token.type != 0 && + token.type != CCompiler::Token_End && + token.type != '.' && + token.type != CCompiler::Token_Identifier ) + { + DAP_ERROR_RESPONSE( seq, "completions" ); + DAP_ERROR_BODY( 0, "" ); + DAP_SEND(); + return; + } + + if ( token.type != '.' && token.type != CCompiler::Token_Identifier ) + { + token.type = 0; + target.Null(); + } + + int start = column; + + if ( token.type == CCompiler::Token_Identifier ) + start = token._string.ptr - text.ptr; + + int length = text.len - start; + + DAP_START_RESPONSE( seq, "completions" ); + DAP_SET_TABLE( body ); + wjson_array_t targets = body.SetArray( "targets" ); + FillCompletions( target, + vm, + GetStackFrame( vm, frame ), + token.type, + token._string, + start == column ? -1 : start, + length, + targets ); + DAP_SEND(); +} + +void SQDebugServer::FillCompletions( const SQObjectPtr &target, HSQUIRRELVM vm, const SQVM::CallInfo *ci, + int token, const string_t &partial, int start, int length, wjson_array_t &targets ) +{ +#define _check( key ) \ + ( token == '.' || token == 0 || \ + ( token == CCompiler::Token_Identifier && \ + sqstring_t( (key) ).StartsWith( partial ) ) ) + +#define _set( priority, label, val ) \ + wjson_table_t elem = targets.AppendTable(); \ + elem.SetString( "label", label ); \ + stringbuf_t< 64 > sortbuf; \ + sortbuf.PutInt( priority ); \ + sortbuf.Puts( label ); \ + elem.SetString( "sortText", sortbuf ); \ + switch ( sq_type(val) ) \ + { \ + case OT_CLOSURE: \ + case OT_NATIVECLOSURE: \ + elem.SetString( "type", "function" ); \ + elem.SetString( "detail", "function" ); \ + break; \ + case OT_CLASS: \ + { \ + elem.SetString( "type", "class" ); \ + const classdef_t *valdef = FindClassDef( _class(val) ); \ + elem.SetString( "detail", valdef && valdef->name.ptr ? \ + string_t( valdef->name.ptr + FMT_PTR_LEN + 1, valdef->name.len - FMT_PTR_LEN - 1 ) : \ + string_t( "class" ) ); \ + break; \ + } \ + case OT_INSTANCE: \ + elem.SetString( "type", "field" ); \ + elem.SetString( "detail", "instance" ); \ + break; \ + default: \ + elem.SetString( "type", "variable" ); \ + elem.SetString( "detail", GetType( val ) ); \ + } \ + if ( start != -1 ) \ + elem.SetInt( "start", start ); \ + if ( length ) \ + elem.SetInt( "length", length ); + + switch ( sq_type(target) ) + { + case OT_TABLE: + { + SQTable *t = _table(target); + do + { + SQObjectPtr key, val; + FOREACH_SQTABLE( t, key, val ) + { + if ( sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + _set( 0, _string(key), val ); + } + } + } + while ( ( t = t->_delegate ) != NULL ); + + break; + } + case OT_INSTANCE: + { + SQClass *base = _instance(target)->_class; + Assert( base ); + + // metamembers + SQObjectPtr mm; + const classdef_t *def = FindClassDef( base ); + + if ( def && + sq_type(def->metamembers) != OT_NULL && + _instance(target)->GetMetaMethod( vm, MT_GET, mm ) ) + { + for ( unsigned int i = 0; i < _array(def->metamembers)->_values.size(); i++ ) + { + const SQObjectPtr &key = _array(def->metamembers)->_values[i]; + SQObjectPtr val; + + if ( sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + RunClosure( mm, &target, key, val ); + _set( 0, _string(key), val ); + } + } + } + + SQObjectPtr key, val; + + // values + { + FOREACH_SQTABLE( base->_members, key, val ) + { + if ( _isfield(val) && sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + _instance(target)->Get( key, val ); + _set( 0, _string(key), val ); + } + } + } + + // methods + { + FOREACH_SQTABLE( base->_members, key, val ) + { + if ( !_isfield(val) && sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + _instance(target)->Get( key, val ); + _set( 1, _string(key), val ); + } + } + } + + break; + } + case OT_CLASS: + { + SQObjectPtr key, val; + + // values + { + FOREACH_SQTABLE( _class(target)->_members, key, val ) + { + if ( _isfield(val) && sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + _class(target)->Get( key, val ); + _set( 0, _string(key), val ); + } + } + } + + // methods + { + FOREACH_SQTABLE( _class(target)->_members, key, val ) + { + if ( !_isfield(val) && sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + _class(target)->Get( key, val ); + _set( 1, _string(key), val ); + } + } + } + + break; + } + default: break; + } + + SQTable *del = GetDefaultDelegate( vm, sq_type(target) ); + if ( del ) + { + SQObjectPtr key, val; + FOREACH_SQTABLE( del, key, val ) + { + if ( sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + _set( 2, _string(key), val ); + } + } + } + + SQTable *pEnvTable = NULL; + + if ( sq_type(target) == OT_NULL ) + { + // locals + if ( ci && sq_type(ci->_closure) == OT_CLOSURE ) + { + SQClosure *pClosure = _closure(ci->_closure); + SQFunctionProto *func = _fp(pClosure->_function); + SQUnsignedInteger ip = (SQUnsignedInteger)( ci->_ip - func->_instructions - 1 ); + + for ( int i = 0; i < func->_nlocalvarinfos; i++ ) + { + const SQLocalVarInfo &var = func->_localvarinfos[i]; + if ( var._start_op <= ip + 1 && var._end_op >= ip && + _check( _string(var._name) ) ) + { + _set( 0, _string(var._name), vm->_stack._vals[ GetStackBase( vm, ci ) + var._pos ] ); + + if ( sqstring_t( _string(var._name) ).IsEqualTo(_SC("this")) ) + { + elem.SetString( "text", KW_THIS ); + } +#if SQUIRREL_VERSION_NUMBER >= 300 + else if ( sqstring_t( _string(var._name) ).IsEqualTo(_SC("vargv")) ) + { + elem.SetString( "text", KW_VARGV ); + } +#endif + } + } + + for ( int i = 0; i < func->_noutervalues; i++ ) + { + const SQOuterVar &var = func->_outervalues[i]; + if ( _check( _string(var._name) ) ) + { + _set( 0, _string(var._name), *_outervalptr( pClosure->_outervalues[i] ) ); + } + } + +#if SQUIRREL_VERSION_NUMBER < 300 + if ( func->_varparams ) + { + if ( token == CCompiler::Token_Identifier && string_t("vargv").StartsWith( partial ) ) + { + _set( 0, "vargv", SQObjectPtr() ); + elem.SetString( "text", KW_VARGV ); + } + + if ( token == CCompiler::Token_Identifier && string_t("vargc").StartsWith( partial ) ) + { + _set( 0, "vargc", SQObjectPtr((SQInteger)0) ); + elem.SetString( "text", KW_VARGC ); + } + } +#endif + } + + // env + if ( ci ) + { + const SQObjectPtr &env = vm->_stack._vals[ GetStackBase( vm, ci ) ]; + if ( sq_type(env) == OT_TABLE ) + { + pEnvTable = _table(env); + SQTable *t = _table(env); + do + { + SQObjectPtr key, val; + FOREACH_SQTABLE( t, key, val ) + { + if ( sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + _set( 1, _string(key), val ); + } + } + } + while ( ( t = t->_delegate ) != NULL ); + } + else if ( sq_type(env) == OT_INSTANCE ) + { + SQClass *base = _instance(env)->_class; + Assert( base ); + + // metamembers + SQObjectPtr mm; + const classdef_t *def = FindClassDef( base ); + + if ( def && + sq_type(def->metamembers) != OT_NULL && + _instance(target)->GetMetaMethod( vm, MT_GET, mm ) ) + { + for ( unsigned int i = 0; i < _array(def->metamembers)->_values.size(); i++ ) + { + const SQObjectPtr &key = _array(def->metamembers)->_values[i]; + SQObjectPtr val; + + if ( sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + RunClosure( mm, &env, key, val ); + _set( 1, _string(key), val ); + } + } + } + + SQObjectPtr key, val; + + // values + { + FOREACH_SQTABLE( base->_members, key, val ) + { + if ( _isfield(val) && sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + _instance(env)->Get( key, val ); + _set( 1, _string(key), val ); + } + } + } + + // methods + { + FOREACH_SQTABLE( base->_members, key, val ) + { + if ( !_isfield(val) && sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + _instance(env)->Get( key, val ); + _set( 2, _string(key), val ); + } + } + } + } + } + + SQTable *root = _table(vm->_roottable); + +#ifdef CLOSURE_ROOT + if ( ci && sq_type(ci->_closure) == OT_CLOSURE && + _closure(ci->_closure)->_root && + _table(_closure(ci->_closure)->_root->_obj) != root ) + { + Assert( sq_type(_closure(ci->_closure)->_root->_obj) == OT_TABLE ); + root = _table(_closure(ci->_closure)->_root->_obj); + } +#endif + + if ( root != pEnvTable ) + { + do + { + SQObjectPtr key, val; + FOREACH_SQTABLE( root, key, val ) + { + if ( sq_type(key) == OT_STRING && _check( _string(key) ) ) + { + _set( 3, _string(key), val ); + } + } + } + while ( ( root = root->_delegate ) != NULL ); + } + } + +#undef _set +#undef _check +} +#endif + +void SQDebugServer::OnRequest_Scopes( const json_table_t &arguments, int seq ) +{ + HSQUIRRELVM vm; + int frame; + arguments.GetInt( "frameId", &frame, -1 ); + + if ( !TranslateFrameID( frame, &vm, &frame ) ) + { + DAP_ERROR_RESPONSE( seq, "scopes" ); + DAP_ERROR_BODY( 0, "invalid stack frame {id}" ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetIntString( "id", frame ); + DAP_SEND(); + return; + } + + const SQVM::CallInfo &ci = vm->_callsstack[ frame ]; + if ( sq_type(ci._closure) != OT_CLOSURE ) + { + DAP_ERROR_RESPONSE( seq, "scopes" ); + DAP_ERROR_BODY( 0, "native call frame" ); + DAP_SEND(); + return; + } + + SQClosure *pClosure = _closure(ci._closure); + SQFunctionProto *func = _fp(pClosure->_function); + + DAP_START_RESPONSE( seq, "scopes" ); + DAP_SET_TABLE( body ); + wjson_array_t scopes = body.SetArray( "scopes" ); + { + wjson_table_t locals = scopes.AppendTable(); + locals.SetString( "name", "Locals" ); + locals.SetString( "presentationHint", "locals" ); + locals.SetBool( "expensive", false ); + locals.SetInt( "variablesReference", ToVarRef( VARREF_SCOPE_LOCALS, vm, frame ) ); + } + if ( func->_noutervalues ) + { + wjson_table_t outers = scopes.AppendTable(); + outers.SetString( "name", "Outers" ); + outers.SetString( "presentationHint", "locals" ); + outers.SetBool( "expensive", false ); + outers.SetInt( "variablesReference", ToVarRef( VARREF_SCOPE_OUTERS, vm, frame ) ); + } + DAP_SEND(); +} + +int SQDebugServer::ThreadToID( HSQUIRRELVM vm ) +{ + if ( m_Threads.size() >= INT_MAX - 1 ) + { + RemoveThreads(); + ThreadToID( m_pRootVM ); + } + + for ( int i = m_Threads.size(); i--; ) + { + SQWeakRef *wr = m_Threads[i]; + + if ( wr && sq_type(wr->_obj) == OT_THREAD ) + { + if ( _thread(wr->_obj) == vm ) + return i; + } + else + { + __ObjRelease( wr ); + m_Threads.remove(i); + } + } + + SQWeakRef *wr = GetWeakRef( vm ); + __ObjAddRef( wr ); + + int i = m_Threads.size(); + m_Threads.append( wr ); + return i; +} + +HSQUIRRELVM SQDebugServer::ThreadFromID( int id ) +{ + if ( id >= 0 && id < (int)m_Threads.size() ) + { + SQWeakRef *wr = m_Threads[id]; + + if ( wr && sq_type(wr->_obj) == OT_THREAD ) + return _thread(wr->_obj); + + __ObjRelease( wr ); + m_Threads.remove(id); + } + + return NULL; +} + +void SQDebugServer::RemoveThreads() +{ + for ( int i = m_Threads.size(); i--; ) + { + SQWeakRef *wr = m_Threads[i]; + __ObjRelease( wr ); + } + + m_Threads.purge(); +} + +void SQDebugServer::OnRequest_Threads( int seq ) +{ + DAP_START_RESPONSE( seq, "threads" ); + DAP_SET_TABLE( body ); + wjson_array_t threads = body.SetArray( "threads" ); + + for ( int i = 0; i < (int)m_Threads.size(); i++ ) + { + SQWeakRef *wr = m_Threads[i]; + + if ( wr && sq_type(wr->_obj) == OT_THREAD ) + { + wjson_table_t thread = threads.AppendTable(); + thread.SetInt( "id", i ); + + if ( _thread(wr->_obj) == m_pRootVM ) + { + thread.SetString( "name", "MainThread" ); + } + else + { + stringbuf_t< STRLEN("Thread ") + FMT_PTR_LEN > name; + name.Puts("Thread "); + name.PutHex( (uintptr_t)_thread(wr->_obj) ); + thread.SetString( "name", name ); + } + } + else + { + __ObjRelease( wr ); + m_Threads.remove(i); + i--; + } + } + DAP_SEND(); +} + +bool SQDebugServer::ShouldIgnoreStackFrame( const SQVM::CallInfo &ci ) +{ + // Ignore RunScript (first frame) + if ( sq_type(ci._closure) == OT_CLOSURE && + sq_type(_fp(_closure(ci._closure)->_function)->_sourcename) == OT_STRING && + sqstring_t(_SC("sqdbg")).IsEqualTo( _string(_fp(_closure(ci._closure)->_function)->_sourcename) ) ) + return true; + + // Ignore error handler / debug hook (last frame) + if ( sq_type(ci._closure) == OT_NATIVECLOSURE && ( + _nativeclosure(ci._closure)->_function == &SQDebugServer::SQErrorHandler +#ifndef NATIVE_DEBUG_HOOK + || _nativeclosure(ci._closure)->_function == &SQDebugServer::SQDebugHook +#endif + ) ) + return true; + + return false; +} + +int SQDebugServer::ConvertToFrameID( int threadId, int stackFrame ) +{ + for ( int i = 0; i < (int)m_FrameIDs.size(); i++ ) + { + const frameid_t &v = m_FrameIDs[i]; + if ( v.threadId == threadId && v.frame == stackFrame ) + return i; + } + + int i = m_FrameIDs.size(); + frameid_t &v = m_FrameIDs.append(); + v.threadId = threadId; + v.frame = stackFrame; + + return i; +} + +bool SQDebugServer::TranslateFrameID( int frameId, HSQUIRRELVM *thread, int *stackFrame ) +{ + if ( frameId >= 0 && frameId < (int)m_FrameIDs.size() ) + { + frameid_t &v = m_FrameIDs[frameId]; + *thread = ThreadFromID( v.threadId ); + *stackFrame = v.frame; + + return thread && *thread && IsValidStackFrame( *thread, *stackFrame ); + } + + return false; +} + +void SQDebugServer::OnRequest_StackTrace( const json_table_t &arguments, int seq ) +{ + int threadId, startFrame, levels; + json_table_t *format; + bool parameters = true; + + arguments.GetInt( "threadId", &threadId, -1 ); + arguments.GetInt( "startFrame", &startFrame ); + arguments.GetInt( "levels", &levels ); + + if ( arguments.GetTable( "format", &format ) ) + format->GetBool( "parameters", ¶meters ); + + HSQUIRRELVM vm = ThreadFromID( threadId ); + + if ( !vm ) + { + DAP_ERROR_RESPONSE( seq, "stackTrace" ); + DAP_ERROR_BODY( 0, "invalid thread {id}" ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetIntString( "id", threadId ); + DAP_SEND(); + return; + } + +#ifdef NATIVE_DEBUG_HOOK + int lastFrame = vm->_callsstacksize - 1; +#else + int lastFrame = vm->_callsstacksize - 1 - 1; +#endif + + if ( startFrame > lastFrame ) + { + DAP_ERROR_RESPONSE( seq, "stackTrace" ); + DAP_ERROR_BODY( 0, "" ); + DAP_SEND(); + return; + } + + if ( startFrame < 0 ) + startFrame = 0; + + // reverse + startFrame = lastFrame - startFrame; + + if ( levels <= 0 || levels > lastFrame ) + levels = lastFrame; + + int targetFrame = startFrame - levels; + + if ( targetFrame < 0 ) + targetFrame = 0; + + DAP_START_RESPONSE( seq, "stackTrace" ); + DAP_SET_TABLE( body ); + { + wjson_array_t stackFrames = body.SetArray( "stackFrames" ); + stringbufext_t buf = ScratchPadBuf( 256 ); + + for ( int i = startFrame; i >= targetFrame; i-- ) + { + const SQVM::CallInfo &ci = vm->_callsstack[i]; + + if ( ShouldIgnoreStackFrame(ci) ) + continue; + + if ( sq_type(ci._closure) == OT_CLOSURE ) + { + SQFunctionProto *func = _fp(_closure(ci._closure)->_function); + + wjson_table_t frame = stackFrames.AppendTable(); + frame.SetInt( "id", ConvertToFrameID( threadId, i ) ); + + buf.len = 0; + + if ( sq_type(func->_name) == OT_STRING ) + { + buf.Puts( _string(func->_name) ); + } + else + { + buf.PutHex( (uintptr_t)func ); + } + + if ( parameters ) + { + buf.Put('('); + + Assert( func->_nparameters ); + + int nparams = func->_nparameters; +#if SQUIRREL_VERSION_NUMBER >= 300 + if ( nparams > 1 ) +#else + if ( nparams > 1 || func->_varparams ) +#endif + { +#if SQUIRREL_VERSION_NUMBER >= 300 + if ( func->_varparams ) + nparams--; +#endif + for ( int j = 1; j < nparams; j++ ) + { + const SQObjectPtr ¶m = func->_parameters[j]; + Assert( sq_type(param) == OT_STRING ); + + buf.Puts( _string(param) ); + buf.Put(','); + buf.Put(' '); + } + + if ( !func->_varparams ) + { + buf.len -= 2; + } + else + { + buf.Put('.'); + buf.Put('.'); + buf.Put('.'); + } + } + + buf.Put(')'); + } + + frame.SetString( "name", buf ); + + if ( sq_type(func->_sourcename) == OT_STRING ) + { + wjson_table_t source = frame.SetTable( "source" ); + SetSource( source, _string(func->_sourcename) ); + } + + frame.SetInt( "line", (int)func->GetLine( ci._ip ) ); + frame.SetInt( "column", 1 ); + + buf.len = 0; + buf.PutHex( (uintptr_t)ci._ip ); + frame.SetString( "instructionPointerReference", buf ); + } + else if ( sq_type(ci._closure) == OT_NATIVECLOSURE ) + { + SQNativeClosure *closure = _nativeclosure(ci._closure); + + wjson_table_t frame = stackFrames.AppendTable(); + frame.SetInt( "id", ConvertToFrameID( threadId, i ) ); + + { + wjson_table_t source = frame.SetTable( "source" ); + source.SetString( "name", "NATIVE" ); + } + + buf.len = 0; + + if ( sq_type(closure->_name) == OT_STRING ) + { + buf.Puts( _string(closure->_name) ); + } + else + { + buf.PutHex( (uintptr_t)closure ); + } + + if ( parameters ) + { + buf.Put('('); + buf.Put(')'); + } + + frame.SetString( "name", buf ); + frame.SetInt( "line", -1 ); + frame.SetInt( "column", 1 ); + frame.SetString( "presentationHint", "subtle" ); + } + else UNREACHABLE(); + } + } + DAP_SET( "totalFrames", lastFrame + 1 ); + DAP_SEND(); +} + +static bool HasMetaMethods( HSQUIRRELVM vm, const SQObject &obj ) +{ + switch ( sq_type(obj) ) + { + case OT_CLASS: + { + for ( unsigned int i = 0; i < MT_LAST; i++ ) + { + if ( sq_type(_class(obj)->_metamethods[i]) != OT_NULL ) + { + return true; + } + } + + return false; + } + default: + { + if ( is_delegable(obj) && _delegable(obj)->_delegate ) + { + SQObjectPtr dummy; + for ( unsigned int i = 0; i < MT_LAST; i++ ) + { + if ( _delegable(obj)->GetMetaMethod( vm, (SQMetaMethod)i, dummy ) ) + { + return true; + } + } + } + + return false; + } + } +} + +static inline void SetVirtualHint( wjson_table_t &elem ) +{ + wjson_table_t hint = elem.SetTable( "presentationHint" ); + hint.SetString( "kind", "virtual" ); + wjson_array_t attributes = hint.SetArray( "attributes" ); + attributes.Append( "readOnly" ); +} + +static int _sortkeys( const SQObjectPtr *a, const SQObjectPtr *b ) +{ + if ( sq_type(*a) == OT_STRING ) + { + if ( sq_type(*b) == OT_STRING ) + { + return scstricmp( _string(*a)->_val, _string(*b)->_val ); + } + else + { + return 1; + } + } + else + { + if ( sq_type(*b) == OT_STRING ) + { + return -1; + } + else + { + return ( _integer(*a) >= _integer(*b) ); + } + } +} + +#define _checkNonStringMembers(key) \ + ( sq_type(key) != OT_STRING || HasEscapes( _string(key)->_val, _string(key)->_len ) ) + +static inline void SortKeys( SQTable *table, + vector< SQObjectPtr, true > *values, + bool *hasNonStringMembers ) +{ + bool nsm = false; + + SQObjectPtr key, val; + FOREACH_SQTABLE( table, key, val ) + { + values->append( key ); + + if ( !nsm ) + nsm = _checkNonStringMembers( key ); + } + + values->sort( _sortkeys ); + *hasNonStringMembers = nsm; +} + +static inline void SortKeys( SQClass *pClass, + int *nAttributes, + vector< SQObjectPtr, true > *values, + vector< SQObjectPtr, true > *methods, + bool *hasNonStringMembers ) +{ + bool nsm = false; + *nAttributes = 0; + + SQObjectPtr key, idx; + FOREACH_SQTABLE( pClass->_members, key, idx ) + { + // Ignore inherited fields + if ( pClass->_base ) + { + SQObjectPtr baseval; + if ( pClass->_base->Get( key, baseval ) ) + { + const SQObjectPtr &val = _isfield(idx) ? + pClass->_defaultvalues[ _member_idx(idx) ].val : + pClass->_methods[ _member_idx(idx) ].val; + + if ( IsEqual( val, baseval ) ) + continue; + } + } + + if ( _isfield(idx) ) + { + values->append( key ); + } + else + { + methods->append( key ); + } + + const SQObjectPtr &attr = _isfield(idx) ? + pClass->_defaultvalues[ _member_idx(idx) ].attrs : + pClass->_methods[ _member_idx(idx) ].attrs; + + if ( sq_type(attr) != OT_NULL ) + (*nAttributes)++; + + if ( !nsm ) + nsm = _checkNonStringMembers( key ); + } + + if ( values->size() ) + values->sort( _sortkeys ); + + if ( methods->size() ) + methods->sort( _sortkeys ); + + *hasNonStringMembers = nsm; +} + +static inline void SortKeys( SQClass *pClass, + vector< SQObjectPtr, true > *values, + bool *hasNonStringMembers ) +{ + bool nsm = false; + + SQObjectPtr key, idx; + FOREACH_SQTABLE( pClass->_members, key, idx ) + { + if ( _isfield(idx) ) + { + values->append( key ); + + if ( !nsm ) + nsm = _checkNonStringMembers( key ); + } + } + + if ( values->size() ) + values->sort( _sortkeys ); + + *hasNonStringMembers = nsm; +} + +#undef _checkNonStringMembers + +void SQDebugServer::OnRequest_Variables( const json_table_t &arguments, int seq ) +{ + int variablesReference; + arguments.GetInt( "variablesReference", &variablesReference ); + + varref_t *ref = FromVarRef( variablesReference ); + + if ( !ref ) + { + DAP_ERROR_RESPONSE( seq, "variables" ); + DAP_ERROR_BODY( 0, "failed to find variable" ); + DAP_SEND(); + return; + } + + int flags = 0; + { + json_table_t *format; + if ( arguments.GetTable( "format", &format ) ) + { + bool hex; + format->GetBool( "hex", &hex ); + if ( hex ) + flags |= kFS_Hexadecimal; + } + } + + switch ( ref->type ) + { + case VARREF_SCOPE_LOCALS: + { + HSQUIRRELVM vm = ref->GetThread(); + int frame = ref->scope.frame; + + if ( !IsValidStackFrame( vm, frame ) || + sq_type(vm->_callsstack[frame]._closure) != OT_CLOSURE ) + { + DAP_ERROR_RESPONSE( seq, "variables" ); + DAP_ERROR_BODY( 0, "invalid stack frame {id}" ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetIntString( "id", frame ); + DAP_SEND(); + break; + } + + const SQVM::CallInfo &ci = vm->_callsstack[ frame ]; + int stackbase = GetStackBase( vm, &ci ); + SQClosure *pClosure = _closure(ci._closure); + SQFunctionProto *func = _fp(pClosure->_function); + SQUnsignedInteger ip = (SQUnsignedInteger)( ci._ip - func->_instructions - 1 ); + + DAP_START_RESPONSE( seq, "variables" ); + DAP_SET_TABLE( body ); + wjson_array_t variables = body.SetArray( "variables" ); + stringbufext_t buf = ScratchPadBuf( 256 ); + + for ( unsigned int i = 0; i < m_ReturnValues.size(); i++ ) + { + const returnvalue_t &rv = m_ReturnValues[i]; + wjson_table_t elem = variables.AppendTable(); + + buf.len = 0; + + if ( !( m_iYieldValues & (1<<(i+1)) ) ) + { + buf.Puts( "return@" ); + } + else + { + buf.Puts( "yield@" ); + } + + if ( rv.funcname ) + { + buf.Puts( rv.funcname ); + } + else + { + buf.PutHex( rv.funcptr ); + } + + elem.SetString( "name", buf ); + JSONSetString( elem, "value", rv.value, flags ); + elem.SetString( "type", GetType( rv.value ) ); + elem.SetInt( "variablesReference", ToVarRef( rv.value ) ); + if ( ShouldPageArray( rv.value, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(rv.value)->_values.size() ); + SetVirtualHint( elem ); + } + + for ( int i = func->_nlocalvarinfos; i--; ) + { + const SQLocalVarInfo &var = func->_localvarinfos[i]; + Assert( sq_type(var._name) == OT_STRING ); + + if ( var._start_op <= ip + 1 && var._end_op >= ip ) + { + buf.len = 0; + buf.Put('@'); + buf.Put('L'); + buf.Put('@'); + buf.PutInt( (int)func->_nlocalvarinfos - i - 1 ); + + const SQObjectPtr &val = vm->_stack._vals[ stackbase + var._pos ]; + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", _string(var._name) ); + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetString( "evaluateName", buf ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + } + } + DAP_SEND(); + + break; + } + case VARREF_SCOPE_OUTERS: + { + HSQUIRRELVM vm = ref->GetThread(); + int frame = ref->scope.frame; + + if ( !IsValidStackFrame( vm, frame ) || + sq_type(vm->_callsstack[frame]._closure) != OT_CLOSURE ) + { + DAP_ERROR_RESPONSE( seq, "variables" ); + DAP_ERROR_BODY( 0, "invalid stack frame {id}" ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetIntString( "id", frame ); + DAP_SEND(); + break; + } + + const SQVM::CallInfo &ci = vm->_callsstack[ frame ]; + SQClosure *pClosure = _closure(ci._closure); + SQFunctionProto *func = _fp(pClosure->_function); + + DAP_START_RESPONSE( seq, "variables" ); + DAP_SET_TABLE( body ); + wjson_array_t variables = body.SetArray( "variables" ); + + for ( int i = 0; i < func->_noutervalues; i++ ) + { + const SQOuterVar &var = func->_outervalues[i]; + const SQObjectPtr &val = *_outervalptr( pClosure->_outervalues[i] ); + Assert( sq_type(var._name) == OT_STRING ); + + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", _string(var._name) ); + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + } + DAP_SEND(); + + break; + } + case VARREF_OBJ: + { + SQObject target = ref->GetVar(); + + if ( !ISREFCOUNTED( sq_type(target) ) ) + { + DAP_ERROR_RESPONSE( seq, "variables" ); + DAP_ERROR_BODY( 0, "invalid object" ); + DAP_SEND(); + return; + } + + const SQObjectPtr *dataWatchKey = NULL; + + if ( _refcounted(target)->_weakref ) + { + for ( int i = m_DataWatches.size(); i--; ) + { + const datawatch_t &dw = m_DataWatches[i]; + if ( _refcounted(target)->_weakref == dw.container ) + { + dataWatchKey = &dw.obj.key; + break; + } + } + } + + DAP_START_RESPONSE( seq, "variables" ); + DAP_SET_TABLE( body ); + wjson_array_t variables = body.SetArray( "variables" ); + + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("refs") ); + + if ( sq_type(target) != OT_WEAKREF ) + { + elem.SetString( "value", GetValue( _refcounted(target)->_uiRef, flags ) ); + } + else + { + stringbufext_t buf = ScratchPadBuf( 256 ); + buf.PutInt( _refcounted(target)->_uiRef ); + + do + { + target = _weakref(target)->_obj; + buf.Put(' '); + buf.Put('>'); + buf.Put( ( sq_type(target) != OT_WEAKREF ) ? '*' : ' ' ); + buf.PutInt( _refcounted(target)->_uiRef ); + + if ( buf.len >= sizeof(buf.ptr) - 4 ) + break; + } + while ( sq_type(target) == OT_WEAKREF ); + + buf.Term(); + + elem.SetString( "value", buf ); + } + + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + if ( sq_type(target) == OT_ARRAY ) + { + const SQObjectPtrVec &vals = _array(target)->_values; + + Assert( vals.size() <= INT_MAX ); + + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("allocated") ); + elem.SetString( "value", GetValue( ((SQObjectPtrVec&)vals).capacity(), flags ) ); + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + int idx, end; + string_t filter; + + if ( arguments.GetString( "filter", &filter ) && filter.IsEqualTo("indexed") ) + { + arguments.GetInt( "start", &idx ); + arguments.GetInt( "count", &end ); + + if ( idx < 0 ) + idx = 0; + + if ( end <= 0 ) + { + end = vals.size(); + } + else + { + end += idx; + if ( end > (int)vals.size() ) + end = vals.size(); + } + } + else + { + idx = 0; + end = vals.size(); + } + + for ( ; idx < end; idx++ ) + { + const SQObjectPtr &val = vals[idx]; + + wjson_table_t elem = variables.AppendTable(); + elem.SetIntBrackets( "name", (SQInteger)idx, ( flags & kFS_Hexadecimal ) != 0 ); + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + wjson_table_t hint = elem.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( val ) ); + + if ( dataWatchKey && + sq_type(*dataWatchKey) == OT_INTEGER && + _integer(*dataWatchKey) == idx ) + { + wjson_array_t attributes = hint.SetArray( "attributes" ); + attributes.Append( "hasDataBreakpoint" ); + } + } + + // done with arrays + } + + // delegates + switch ( sq_type(target) ) + { + case OT_INSTANCE: + { + if ( _instance(target)->_class ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("class") ); + elem.SetString( "value", GetValue( ToSQObject( _instance(target)->_class ) ) ); + elem.SetInt( "variablesReference", ToVarRef( ToSQObject( _instance(target)->_class ) ) ); + SetVirtualHint( elem ); + } + + break; + } + case OT_CLASS: + { + if ( _class(target)->_base ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("base") ); + elem.SetString( "value", GetValue( ToSQObject( _class(target)->_base ) ) ); + elem.SetInt( "variablesReference", ToVarRef( ToSQObject( _class(target)->_base ) ) ); + SetVirtualHint( elem ); + } + + break; + } + case OT_TABLE: + { + if ( _table(target)->_delegate ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("delegate") ); + elem.SetString( "value", GetValue( ToSQObject( _table(target)->_delegate ) ) ); + elem.SetInt( "variablesReference", ToVarRef( ToSQObject( _table(target)->_delegate ) ) ); + SetVirtualHint( elem ); + } + + break; + } + default: break; + } + + // metamethods + if ( sq_type(target) != OT_INSTANCE && HasMetaMethods( m_pRootVM, target ) ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("metamethods") ); + elem.SetString( "value", "{...}" ); + elem.SetInt( "variablesReference", ToVarRef( VARREF_METAMETHODS, target ) ); + SetVirtualHint( elem ); + } + + bool shouldQuoteKeys; + + // members + switch ( sq_type(target) ) + { + case OT_TABLE: + { + int keyflags = flags | kFS_NoQuote | kFS_KeyVal; + + Assert( _table(target)->CountUsed() <= INT_MAX ); + + m_VarMemberCache.Ensure( _table(target)->CountUsed() * sizeof(SQObjectPtr) ); + + vector< SQObjectPtr, true > keys( m_VarMemberCache ); + SortKeys( _table(target), &keys, &shouldQuoteKeys ); + + ref->obj.hasNonStringMembers = shouldQuoteKeys; + + if ( shouldQuoteKeys ) + keyflags &= ~kFS_NoQuote; + + for ( unsigned int i = 0; i < keys.size(); i++ ) + { + const SQObjectPtr &key = keys[i]; + SQObjectPtr val; + _table(target)->Get( key, val ); + + wjson_table_t elem = variables.AppendTable(); + + if ( shouldQuoteKeys && sq_type(key) == OT_INTEGER ) + { + elem.SetIntBrackets( "name", _integer(key), ( keyflags & kFS_Hexadecimal ) != 0 ); + } + else + { + JSONSetString( elem, "name", key, keyflags ); + } + + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + wjson_table_t hint = elem.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( val ) ); + + if ( dataWatchKey && IsEqual( *dataWatchKey, key ) ) + { + wjson_array_t attributes = hint.SetArray( "attributes" ); + attributes.Append( "hasDataBreakpoint" ); + } + } + + break; + } + case OT_CLASS: + { + int keyflags = flags | kFS_NoQuote | kFS_KeyVal; + int nAttributes; + + Assert( _class(target)->_members ); + Assert( _class(target)->_members->CountUsed() <= INT_MAX ); + + int nMemberCount = _class(target)->_members->CountUsed(); + + // Lazy way to ensure there is enough memory for values & methods + m_VarMemberCache.Ensure( nMemberCount * 2 * sizeof(SQObjectPtr) ); + + CMemory temp = m_VarMemberCache; + temp.memory.ptr += nMemberCount * sizeof(SQObjectPtr); + + vector< SQObjectPtr, true > values( m_VarMemberCache ), methods( temp ); + SortKeys( _class(target), &nAttributes, &values, &methods, &shouldQuoteKeys ); + + ref->obj.hasNonStringMembers = shouldQuoteKeys; + + if ( shouldQuoteKeys ) + keyflags &= ~kFS_NoQuote; + + if ( sq_type(_class(target)->_attributes) != OT_NULL ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("attributes") ); + elem.SetString( "value", GetValue( _class(target)->_attributes, flags ) ); + elem.SetInt( "variablesReference", ToVarRef( _class(target)->_attributes ) ); + SetVirtualHint( elem ); + } + else if ( nAttributes ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("attributes") ); + elem.SetString( "value", "{...}" ); + elem.SetInt( "variablesReference", ToVarRef( VARREF_ATTRIBUTES, target ) ); + SetVirtualHint( elem ); + } + + for ( unsigned int i = 0; i < values.size(); i++ ) + { + const SQObjectPtr &key = values[i]; + SQObjectPtr val; + _class(target)->Get( key, val ); + + wjson_table_t elem = variables.AppendTable(); + + if ( shouldQuoteKeys && sq_type(key) == OT_INTEGER ) + { + elem.SetIntBrackets( "name", _integer(key), ( keyflags & kFS_Hexadecimal ) != 0 ); + } + else + { + JSONSetString( elem, "name", key, keyflags ); + } + + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + wjson_table_t hint = elem.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( val ) ); + } + + for ( unsigned int i = 0; i < methods.size(); i++ ) + { + const SQObjectPtr &key = methods[i]; + SQObjectPtr val; + _class(target)->Get( key, val ); + + wjson_table_t elem = variables.AppendTable(); + + if ( shouldQuoteKeys && sq_type(key) == OT_INTEGER ) + { + elem.SetIntBrackets( "name", _integer(key), ( keyflags & kFS_Hexadecimal ) != 0 ); + } + else + { + JSONSetString( elem, "name", key, keyflags ); + } + + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + wjson_table_t hint = elem.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( val ) ); + } + + break; + } + case OT_INSTANCE: + { + int keyflags = flags | kFS_NoQuote | kFS_KeyVal; + SQClass *base = _instance(target)->_class; + + Assert( base ); + Assert( base->_members ); + Assert( base->_members->CountUsed() <= INT_MAX ); + + m_VarMemberCache.Ensure( base->_members->CountUsed() * sizeof(SQObjectPtr) ); + + vector< SQObjectPtr, true > values( m_VarMemberCache ); + SortKeys( base, &values, &shouldQuoteKeys ); + + ref->obj.hasNonStringMembers = shouldQuoteKeys; + + if ( shouldQuoteKeys ) + keyflags &= ~kFS_NoQuote; + + // metamembers + SQObjectPtr mm; + const SQObjectPtr *def = GetClassDefMetaMembers( base ); + + if ( def && _instance(target)->GetMetaMethod( m_pRootVM, MT_GET, mm ) ) + { + for ( unsigned int i = 0; i < _array(*def)->_values.size(); i++ ) + { + const SQObjectPtr &key = _array(*def)->_values[i]; + SQObjectPtr val; + + if ( RunClosure( mm, &target, key, val ) ) + { + wjson_table_t elem = variables.AppendTable(); + JSONSetString( elem, "name", key, keyflags ); + // NOTE: val can be temporary, keep strong ref for inspection + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val, false, true ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + wjson_table_t hint = elem.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( val ) ); + + if ( dataWatchKey && IsEqual( *dataWatchKey, key ) ) + { + wjson_array_t attributes = hint.SetArray( "attributes" ); + attributes.Append( "hasDataBreakpoint" ); + } + } + } + } + + // user defined + def = GetClassDefCustomMembers( base ); + + if ( def ) + { + SQObjectPtr custommembers = *def; + + if ( sq_type(custommembers) == OT_CLOSURE ) + RunClosure( custommembers, &target, custommembers ); + + if ( sq_type(custommembers) == OT_ARRAY ) + { + objref_t tmp; + SQObjectPtr strName = CreateSQString( m_pRootVM, _SC("name") ); + SQObjectPtr strGet = CreateSQString( m_pRootVM, _SC("get") ); + SQObjectPtr strSet = CreateSQString( m_pRootVM, _SC("set") ); + SQObjectPtr key, val; + + for ( unsigned int i = 0; i < _array(custommembers)->_values.size(); i++ ) + { + const SQObjectPtr &memdef = _array(custommembers)->_values[i]; + + if ( GetObj_Var( memdef, strName, tmp, key ) && + GetObj_Var( memdef, strGet, tmp, val ) && + CallCustomMembersGetFunc( val, &target, key, val ) ) + { + wjson_table_t elem = variables.AppendTable(); + + if ( shouldQuoteKeys && sq_type(key) == OT_INTEGER ) + { + elem.SetIntBrackets( "name", _integer(key), + ( keyflags & kFS_Hexadecimal ) != 0 ); + } + else + { + JSONSetString( elem, "name", key, keyflags ); + } + + // NOTE: val can be temporary, keep strong ref for inspection + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val, false, true ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + wjson_table_t hint = elem.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( val ) ); + + wjson_array_t attributes = hint.SetArray( "attributes" ); + + if ( !GetObj_Var( memdef, strSet, tmp, val ) || + ( sq_type(val) != OT_CLOSURE && sq_type(val) != OT_NATIVECLOSURE ) ) + { + attributes.Append( "readOnly" ); + } + + if ( dataWatchKey && IsEqual( *dataWatchKey, key ) ) + { + attributes.Append( "hasDataBreakpoint" ); + } + } + } + } + } + + for ( unsigned int i = 0; i < values.size(); i++ ) + { + const SQObjectPtr &key = values[i]; + SQObjectPtr val; + _instance(target)->Get( key, val ); + + wjson_table_t elem = variables.AppendTable(); + + if ( shouldQuoteKeys && sq_type(key) == OT_INTEGER ) + { + elem.SetIntBrackets( "name", _integer(key), ( keyflags & kFS_Hexadecimal ) != 0 ); + } + else + { + JSONSetString( elem, "name", key, keyflags ); + } + + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + wjson_table_t hint = elem.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( val ) ); + + if ( dataWatchKey && IsEqual( *dataWatchKey, key ) ) + { + wjson_array_t attributes = hint.SetArray( "attributes" ); + attributes.Append( "hasDataBreakpoint" ); + } + } + + break; + } + case OT_CLOSURE: + { + SQFunctionProto *func = _fp(_closure(target)->_function); + + Assert( func->_ninstructions <= INT_MAX ); + Assert( func->GetLine( &func->_instructions[ func->_ninstructions - 1 ] ) <= INT_MAX ); + Assert( func->_nliterals <= INT_MAX ); + Assert( func->_noutervalues <= INT_MAX ); + Assert( func->_nlocalvarinfos <= INT_MAX ); + Assert( func->_nlineinfos <= INT_MAX ); + + if ( sq_type(func->_name) == OT_STRING ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("name") ); + elem.SetString( "value", _string(func->_name) ); + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + if ( sq_type(func->_sourcename) == OT_STRING ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("source") ); + + int line = GetFunctionDeclarationLine( func ); + if ( line ) + { + stringbufext_t buf = ScratchPadBuf( + scstombslen( + _string(func->_sourcename)->_val, + _string(func->_sourcename)->_len ) + + FMT_INT_LEN ); + buf.Puts( _string(func->_sourcename) ); + buf.Put(':'); + buf.PutInt( line ); + elem.SetString( "value", buf ); + } + else + { + elem.SetString( "value", _string(func->_sourcename) ); + } + + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + if ( func->_bgenerator ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("generator") ); + elem.SetString( "value", "1" ); + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + int nparams = func->_nparameters; + +#if SQUIRREL_VERSION_NUMBER >= 300 + if ( nparams > 1 ) +#else + if ( nparams > 1 || func->_varparams ) +#endif + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("parameters") ); + + if ( !func->_varparams ) + { + elem.SetString( "value", GetValue( nparams, flags ) ); + } + else + { +#if SQUIRREL_VERSION_NUMBER >= 300 + nparams--; +#endif + stringbuf_t< 16 > buf; + + if ( !( flags & kFS_Hexadecimal ) ) + { + buf.PutInt( nparams ); + } + else + { + buf.PutHex( nparams, false ); + } + + buf.Puts("..."); + elem.SetString( "value", buf ); + } + + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("stacksize") ); + elem.SetString( "value", GetValue( func->_stacksize, flags ) ); + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + { + RestoreCachedInstructions(); + + // ignore line ops + int c = func->_ninstructions; + for ( int i = c; i--; ) + if ( func->_instructions[i].op == _OP_LINE ) + c--; + + UndoRestoreCachedInstructions(); + + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("instructions") ); + elem.SetString( "value", GetValue( c, flags ) ); + elem.SetInt( "variablesReference", ToVarRef( VARREF_INSTRUCTIONS, target ) ); + SetVirtualHint( elem ); + } + + if ( func->_nliterals ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("literals") ); + elem.SetString( "value", GetValue( func->_nliterals, flags ) ); + elem.SetInt( "variablesReference", ToVarRef( VARREF_LITERALS, target ) ); + SetVirtualHint( elem ); + } + + if ( func->_noutervalues ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("outervalues") ); + elem.SetString( "value", GetValue( func->_noutervalues, flags ) ); + elem.SetInt( "variablesReference", ToVarRef( VARREF_OUTERS, target ) ); + SetVirtualHint( elem ); + } +#ifdef CLOSURE_ENV_ISVALID + if ( CLOSURE_ENV_ISVALID( _closure(target)->_env ) ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("env") ); + elem.SetString( "value", GetValue( + CLOSURE_ENV_OBJ( _closure(target)->_env ) ) ); + elem.SetInt( "variablesReference", ToVarRef( + CLOSURE_ENV_OBJ( _closure(target)->_env ) ) ); + SetVirtualHint( elem ); + } +#endif +#ifdef CLOSURE_ROOT + if ( _closure(target)->_root && + _table(_closure(target)->_root->_obj) != _table(m_pRootVM->_roottable) ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("root") ); + elem.SetString( "value", GetValue( _closure(target)->_root->_obj ) ); + elem.SetInt( "variablesReference", ToVarRef( _closure(target)->_root->_obj ) ); + SetVirtualHint( elem ); + } +#endif + break; + } + case OT_NATIVECLOSURE: + { + if ( sq_type(_nativeclosure(target)->_name) == OT_STRING ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("name") ); + elem.SetString( "value", _string(_nativeclosure(target)->_name) ); + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + Assert( _nativeclosure(target)->_nparamscheck >= 0 || + -_nativeclosure(target)->_nparamscheck <= INT_MAX ); + + int nparams = _nativeclosure(target)->_nparamscheck; + + if ( nparams != 0 && + // if only parameter has no type check, ignore + !( nparams == 1 && _nativeclosure(target)->_typecheck.size() == 0 ) ) + { + stringbuf_t< 64 > buf; + + if ( !( flags & kFS_Hexadecimal ) ) + { + buf.PutInt( nparams < 0 ? -nparams : nparams ); + } + else + { + buf.PutHex( nparams < 0 ? -nparams : nparams, false ); + } + + if ( nparams < 0 ) + buf.Puts("..."); + + if ( _nativeclosure(target)->_typecheck.size() ) + { + buf.Put(' '); + buf.Put('('); + + for ( int i = 0; i < (int)_nativeclosure(target)->_typecheck.size(); i++ ) + { + int mask = _nativeclosure(target)->_typecheck[i]; + Assert( mask ); + + if ( mask == -1 ) + { + buf.Put('.'); + buf.Put(','); + buf.Put(' '); + continue; + } + + #define _check( t, c ) \ + if ( mask & (t) ) \ + { \ + buf.Put((c)); \ + buf.Put('|'); \ + } + + #define _check_match( t, c ) \ + if ( ( mask & (t) ) == (t) ) \ + { \ + buf.Put((c)); \ + buf.Put('|'); \ + } + + _check( _RT_NULL, 'o' ) + _check_match( _RT_INTEGER | _RT_FLOAT, 'n' ) + else + { + _check( _RT_INTEGER, 'i' ) + else + _check( _RT_FLOAT, 'f' ) + } + _check( _RT_BOOL, 'b' ) + _check( _RT_STRING, 's' ) + _check( _RT_CLOSURE | _RT_NATIVECLOSURE, 'c' ) + _check( _RT_TABLE, 't' ) + _check( _RT_ARRAY, 'a' ) + _check( _RT_INSTANCE, 'x' ) + _check( _RT_CLASS, 'y' ) + _check( _RT_USERDATA, 'u' ) + _check( _RT_USERPOINTER, 'p' ) + _check( _RT_GENERATOR, 'g' ) + _check( _RT_THREAD, 'v' ) + _check( _RT_WEAKREF, 'r' ) + + #undef _check + #undef _check_match + + buf.len--; + buf.Put(','); + buf.Put(' '); + } + + buf.len -= 2; + buf.Put(')'); + } + + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("parameters") ); + elem.SetString( "value", buf ); + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + if ( _nativenoutervalues(_nativeclosure(target)) ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("outervalues") ); + elem.SetString( "value", GetValue( + _nativenoutervalues(_nativeclosure(target)), flags ) ); + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } +#ifdef CLOSURE_ENV_ISVALID + if ( CLOSURE_ENV_ISVALID( _nativeclosure(target)->_env ) ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("env") ); + elem.SetString( "value", GetValue( + CLOSURE_ENV_OBJ( _nativeclosure(target)->_env ) ) ); + elem.SetInt( "variablesReference", ToVarRef( + CLOSURE_ENV_OBJ( _nativeclosure(target)->_env ) ) ); + SetVirtualHint( elem ); + } +#endif + break; + } + case OT_THREAD: + { + Assert( _thread(_ss(_thread(target))->_root_vm) == m_pRootVM ); + + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("state") ); + switch ( sq_getvmstate( _thread(target) ) ) + { + case SQ_VMSTATE_IDLE: elem.SetString( "value", "idle" ); break; + case SQ_VMSTATE_RUNNING: elem.SetString( "value", "running" ); break; + case SQ_VMSTATE_SUSPENDED: elem.SetString( "value", "suspended" ); break; + default: UNREACHABLE(); + } + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + if ( _table(_thread(target)->_roottable) != _table(m_pRootVM->_roottable) ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("root") ); + elem.SetString( "value", GetValue( _thread(target)->_roottable ) ); + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + if ( _thread(target) != m_pRootVM ) + { + const SQObjectPtr &val = _thread(target)->_stack._vals[0]; + Assert( sq_type(val) == OT_CLOSURE || sq_type(val) == OT_NATIVECLOSURE ); + + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("function") ); + elem.SetString( "value", GetValue( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + SetVirtualHint( elem ); + } + + if ( _thread(target)->_callsstacksize != 0 ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("callstack") ); + elem.SetString( "value", GetValue( _thread(target)->_callsstacksize ) ); + elem.SetInt( "variablesReference", ToVarRef( VARREF_CALLSTACK, target ) ); + SetVirtualHint( elem ); + } + + break; + } + case OT_GENERATOR: + { + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("state") ); + switch ( _generator(target)->_state ) + { + case SQGenerator::eSuspended: elem.SetString( "value", "suspended" ); break; + case SQGenerator::eRunning: elem.SetString( "value", "running" ); break; + case SQGenerator::eDead: elem.SetString( "value", "dead" ); break; + default: UNREACHABLE(); + } + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + if ( _generator(target)->_state != SQGenerator::eDead ) + { + const SQVM::CallInfo &ci = _generator(target)->_ci; + Assert( sq_type(ci._closure) == OT_CLOSURE ); + + SQFunctionProto *func = _fp(_closure(ci._closure)->_function); + + sqstring_t source = sq_type(func->_sourcename) == OT_STRING ? + sqstring_t(_string(func->_sourcename)) : + sqstring_t(_SC("??")); + + stringbufext_t buf = ScratchPadBuf( scstombslen( source.ptr, source.len ) + FMT_INT_LEN ); + buf.Puts( source ); + buf.Put(':'); + buf.PutInt( (int)func->GetLine( ci._ip ) ); + + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("frame") ); + elem.SetString( "value", buf ); + elem.SetInt( "variablesReference", -1 ); + SetVirtualHint( elem ); + } + + if ( sq_type(_generator(target)->_closure) != OT_NULL ) + { + const SQObjectPtr &val = _generator(target)->_closure; + Assert( sq_type(val) == OT_CLOSURE || sq_type(val) == OT_NATIVECLOSURE ); + + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", INTERNAL_TAG("function") ); + elem.SetString( "value", GetValue( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + SetVirtualHint( elem ); + } + + break; + } + case OT_STRING: + case OT_ARRAY: + case OT_WEAKREF: + case OT_USERDATA: + break; + default: + Assert(!"unknown type"); + } + DAP_SEND(); + break; + } + case VARREF_INSTRUCTIONS: + { + SQObject target = ref->GetVar(); + + if ( sq_type(target) != OT_CLOSURE ) + { + DAP_ERROR_RESPONSE( seq, "variables" ); + DAP_ERROR_BODY( 0, "invalid object" ); + DAP_SEND(); + return; + } + + RestoreCachedInstructions(); + + SQFunctionProto *func = _fp(_closure(target)->_function); + int ninstructions = func->_ninstructions; + + DAP_START_RESPONSE( seq, "variables" ); + DAP_SET_TABLE( body ); + wjson_array_t variables = body.SetArray( "variables" ); + + // ignore line ops + int lines = 0; + + for ( int i = 0; i < ninstructions; i++ ) + { + SQInstruction *instr = func->_instructions + i; + + if ( instr->op == _OP_LINE ) + { + lines++; + continue; + } + + wjson_table_t elem = variables.AppendTable(); + { + stringbuf_t< 32 > instrBytes; // "0xFF -2147483648 255 255 255" + instrBytes.PutHex( instr->op ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg0 ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg1 ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg2 ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg3 ); + elem.SetString( "value", instrBytes ); + } + { + stringbuf_t< FMT_UINT32_LEN * 2 + 1 > name; // index:line + name.PutInt( i - lines ); + name.Put(':'); + name.PutInt( (int)func->GetLine( instr ) ); + elem.SetString( "name", name ); + } + elem.SetInt( "variablesReference", -1 ); +#ifndef SQDBG_SUPPORTS_SET_INSTRUCTION + wjson_table_t hint = elem.SetTable( "presentationHint" ); + wjson_array_t attributes = hint.SetArray( "attributes" ); + attributes.Append( "readOnly" ); +#endif + } + DAP_SEND(); + + UndoRestoreCachedInstructions(); + + break; + } + case VARREF_OUTERS: + { + SQObject target = ref->GetVar(); + + if ( sq_type(target) != OT_CLOSURE ) + { + DAP_ERROR_RESPONSE( seq, "variables" ); + DAP_ERROR_BODY( 0, "invalid object" ); + DAP_SEND(); + return; + } + + SQClosure *pClosure = _closure(target); + SQFunctionProto *func = _fp(pClosure->_function); + + DAP_START_RESPONSE( seq, "variables" ); + DAP_SET_TABLE( body ); + wjson_array_t variables = body.SetArray( "variables" ); + + for ( int i = 0; i < func->_noutervalues; i++ ) + { + const SQOuterVar &var = func->_outervalues[i]; + const SQObjectPtr &val = *_outervalptr( pClosure->_outervalues[i] ); + Assert( sq_type(var._name) == OT_STRING ); + + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", _string(var._name) ); + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + } + DAP_SEND(); + + break; + } + case VARREF_LITERALS: + { + SQObject target = ref->GetVar(); + + if ( sq_type(target) != OT_CLOSURE ) + { + DAP_ERROR_RESPONSE( seq, "variables" ); + DAP_ERROR_BODY( 0, "invalid object" ); + DAP_SEND(); + return; + } + + SQClosure *pClosure = _closure(target); + SQFunctionProto *func = _fp(pClosure->_function); + + DAP_START_RESPONSE( seq, "variables" ); + DAP_SET_TABLE( body ); + wjson_array_t variables = body.SetArray( "variables" ); + + for ( int i = 0; i < func->_nliterals; i++ ) + { + const SQObjectPtr &val = func->_literals[i]; + + wjson_table_t elem = variables.AppendTable(); + elem.SetIntString( "name", i ); + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + } + DAP_SEND(); + + break; + } + case VARREF_METAMETHODS: + { + SQObject target = ref->GetVar(); + + if ( !ISREFCOUNTED( sq_type(target) ) ) + { + DAP_ERROR_RESPONSE( seq, "variables" ); + DAP_ERROR_BODY( 0, "invalid object" ); + DAP_SEND(); + return; + } + + DAP_START_RESPONSE( seq, "variables" ); + DAP_SET_TABLE( body ); + wjson_array_t variables = body.SetArray( "variables" ); + + switch ( sq_type(target) ) + { + case OT_INSTANCE: _class(target) = _instance(target)->_class; + case OT_CLASS: + { + for ( unsigned int i = 0; i < MT_LAST; i++ ) + { + const SQObjectPtr &val = _class(target)->_metamethods[i]; + if ( sq_type(val) != OT_NULL ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", g_MetaMethodName[i] ); + elem.SetString( "value", GetValue( val ) ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + } + } + + break; + } + default: + { + Assert( is_delegable(target) && _delegable(target)->_delegate ); + + if ( is_delegable(target) && _delegable(target)->_delegate ) + { + SQObjectPtr val; + for ( unsigned int i = 0; i < MT_LAST; i++ ) + { + if ( _delegable(target)->GetMetaMethod( m_pRootVM, (SQMetaMethod)i, val ) ) + { + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", g_MetaMethodName[i] ); + elem.SetString( "value", GetValue( val ) ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + } + } + } + + break; + } + } + DAP_SEND(); + + break; + } + case VARREF_ATTRIBUTES: + { + SQObject target = ref->GetVar(); + bool shouldQuoteKeys = ref->obj.hasNonStringMembers; + + if ( sq_type(target) != OT_CLASS ) + { + DAP_ERROR_RESPONSE( seq, "variables" ); + DAP_ERROR_BODY( 0, "invalid object" ); + DAP_SEND(); + return; + } + + int keyflags = flags | kFS_NoQuote | kFS_KeyVal; + if ( shouldQuoteKeys ) + keyflags &= ~kFS_NoQuote; + + DAP_START_RESPONSE( seq, "variables" ); + DAP_SET_TABLE( body ); + wjson_array_t variables = body.SetArray( "variables" ); + + SQObjectPtr key, idx; + FOREACH_SQTABLE( _class(target)->_members, key, idx ) + { + const SQObjectPtr &val = _isfield(idx) ? + _class(target)->_defaultvalues[ _member_idx(idx) ].attrs : + _class(target)->_methods[ _member_idx(idx) ].attrs; + + if ( sq_type(val) != OT_NULL ) + { + wjson_table_t elem = variables.AppendTable(); + + if ( shouldQuoteKeys && sq_type(key) == OT_INTEGER ) + { + elem.SetIntBrackets( "name", _integer(key), ( keyflags & kFS_Hexadecimal ) != 0 ); + } + else + { + JSONSetString( elem, "name", key, keyflags ); + } + + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + wjson_table_t hint = elem.SetTable( "presentationHint" ); + hint.SetString( "kind", GetPresentationHintKind( val ) ); + } + } + DAP_SEND(); + + break; + } + case VARREF_CALLSTACK: + { + SQObject target = ref->GetVar(); + + if ( sq_type(target) != OT_THREAD ) + { + DAP_ERROR_RESPONSE( seq, "variables" ); + DAP_ERROR_BODY( 0, "invalid object" ); + DAP_SEND(); + return; + } + + DAP_START_RESPONSE( seq, "variables" ); + DAP_SET_TABLE( body ); + int i = _thread(target)->_callsstacksize; + wjson_array_t variables = body.SetArray( "variables" ); + stringbufext_t buf = ScratchPadBuf( 256 ); + + while ( i-- ) + { + const SQVM::CallInfo &ci = _thread(target)->_callsstack[i]; + + if ( ShouldIgnoreStackFrame(ci) ) + continue; + + if ( sq_type(ci._closure) == OT_CLOSURE ) + { + SQFunctionProto *func = _fp(_closure(ci._closure)->_function); + + buf.len = 0; + + if ( sq_type(func->_sourcename) == OT_STRING ) + { + buf.Puts( _string(func->_sourcename) ); + } + else + { + buf.Puts("??"); + } + + buf.Put(':'); + buf.PutInt( (int)func->GetLine( ci._ip ) ); + } + else if ( sq_type(ci._closure) == OT_NATIVECLOSURE ) + { + buf.Puts("NATIVE"); + } + else UNREACHABLE(); + + wjson_table_t elem = variables.AppendTable(); + elem.SetIntBrackets( "name", i ); + elem.SetString( "value", buf ); + elem.SetInt( "variablesReference", ToVarRef( ci._closure ) ); + wjson_table_t hint = elem.SetTable( "presentationHint" ); + wjson_array_t attributes = hint.SetArray( "attributes" ); + attributes.Append( "readOnly" ); + } + DAP_SEND(); + + break; + } + case VARREF_STACK: + { + HSQUIRRELVM vm = ref->GetThread(); + int frame = ref->scope.frame; + + if ( !IsValidStackFrame( vm, frame ) ) + frame = -1; + + DAP_START_RESPONSE( seq, "variables" ); + DAP_SET_TABLE( body ); + wjson_array_t variables = body.SetArray( "variables" ); + + int stackbase = -1; + int callframe = 0; + + if ( vm->_callsstacksize ) + stackbase = vm->_callsstack[callframe]._prevstkbase; + + for ( int i = 0; i < (int)vm->_stack.size(); i++ ) + { + const SQObjectPtr &val = vm->_stack._vals[i]; + + if ( i > vm->_top && sq_type(val) == OT_NULL ) + continue; + + stringbuf_t< 2 + FMT_UINT32_LEN + 2 > name; + name.Put('['); + + if ( !( flags & kFS_Hexadecimal ) ) + { + name.PutInt( i ); + } + else + { + name.PutHex( i, false ); + } + + name.Put(']'); + + if ( stackbase == i ) + { + name.Put('*'); + + if ( callframe == frame ) + { + name.Put('~'); + } +#ifndef NATIVE_DEBUG_HOOK + else if ( m_State == ThreadState_Suspended && + callframe == vm->_callsstacksize - 1 ) + { + if ( sq_type(vm->_callsstack[callframe]._closure) == OT_NATIVECLOSURE && + _nativeclosure(vm->_callsstack[callframe]._closure)->_function == + &SQDebugServer::SQDebugHook ) + { + name.Put('d'); + } + } +#endif + + if ( ++callframe < vm->_callsstacksize ) + stackbase += vm->_callsstack[callframe]._prevstkbase; + } + else if ( vm->_top == i ) + { + name.Put('_'); + } + + wjson_table_t elem = variables.AppendTable(); + elem.SetString( "name", name ); + JSONSetString( elem, "value", val, flags ); + elem.SetString( "type", GetType( val ) ); + elem.SetInt( "variablesReference", ToVarRef( val ) ); + if ( ShouldPageArray( val, 16 * ARRAY_PAGE_LIMIT ) ) + elem.SetInt( "indexedVariables", (int)_array(val)->_values.size() ); + } + DAP_SEND(); + + break; + } + default: UNREACHABLE(); + } +} + +// +// If the client supports SetExpression and the target variable has "evaluateName", +// it will send target "evaluateName" and stack frame to SetExpression. +// Client can choose to send variable "name" to SetExpression for watch variables. +// If the client doesn't support SetExpression or the target variable does not have "evaluateName", +// it will send target "name" and container "variableReference" to SetVariable. +// +// SetExpression needs to parse watch flags and "evaluateName", +// SetVariable only gets identifiers. +// +void SQDebugServer::OnRequest_SetVariable( const json_table_t &arguments, int seq ) +{ + int variablesReference; + string_t strName, strValue; + + arguments.GetInt( "variablesReference", &variablesReference ); + arguments.GetString( "name", &strName ); + arguments.GetString( "value", &strValue ); + + bool hex = false; + json_table_t *format; + if ( arguments.GetTable( "format", &format ) ) + format->GetBool( "hex", &hex ); + + varref_t *ref = FromVarRef( variablesReference ); + + if ( !ref ) + { + DAP_ERROR_RESPONSE( seq, "setVariable" ); + DAP_ERROR_BODY( 0, "failed to find variable" ); + DAP_SEND(); + return; + } + + HSQUIRRELVM vm; + int frame; + + if ( IsScopeRef( ref->type ) ) + { + vm = ref->GetThread(); + frame = ref->scope.frame; + } + else + { + vm = m_pCurVM; + frame = -1; + } + + // Requires special value parsing + switch ( ref->type ) + { + case VARREF_INSTRUCTIONS: + { +#ifdef SQDBG_SUPPORTS_SET_INSTRUCTION + OnRequest_SetVariable_Instruction( ref, strName, strValue, seq ); +#else + DAP_ERROR_RESPONSE( seq, "setVariable" ); + DAP_ERROR_BODY( 0, "" ); + DAP_SEND(); +#endif + return; + } + default: break; + } + + if ( strName.IsEmpty() || strValue.IsEmpty() ) + { + DAP_ERROR_RESPONSE( seq, "setVariable" ); + DAP_ERROR_BODY( 0, "empty expression" ); + DAP_SEND(); + return; + } + + objref_t obj; + SQObjectPtr value, dummy; + + if ( !GetObj_VarRef( ref, strName, obj, dummy ) ) + { + DAP_ERROR_RESPONSE( seq, "setVariable" ); + DAP_ERROR_BODY( 0, "identifier '{name}' not found" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + + // If the string is escapable, it was undone + if ( ref->type == VARREF_OBJ && ref->obj.hasNonStringMembers && strName.ptr[-1] == '\"' ) + { + // there is enough space to re-escape + Escape( strName.ptr, &strName.len, strName.len * ( sizeof(SQChar) * 2 + 2 ) ); + } + + variables.SetString( "name", strName ); + DAP_SEND(); + return; + } + +#ifndef SQDBG_DISABLE_COMPILER + ECompileReturnCode cres = Evaluate( strValue, vm, frame, value ); + + if ( cres != CompileReturnCode_Success && + !( cres > CompileReturnCode_Fallback && RunExpression( strValue, vm, frame, value ) ) ) +#else + if ( !RunExpression( strValue, vm, frame, value ) ) +#endif + { + DAP_ERROR_RESPONSE( seq, "setVariable" ); + DAP_ERROR_BODY( 0, "failed to evaluate value '{name}'\n\n[{reason}]" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetString( "name", strValue ); + variables.SetString( "reason", GetValue( vm->_lasterror, kFS_NoQuote ) ); + DAP_SEND(); + return; + } + + if ( !Set( obj, value ) ) + { + DAP_ERROR_RESPONSE( seq, "setVariable" ); + DAP_ERROR_BODY( 0, "could not set '{name}'" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetString( "name", GetValue( obj.key ) ); + DAP_SEND(); + return; + } + + // Update data watch + if ( IsObjectRef( ref->type ) && ref->obj.isWeak && + _refcounted(ref->GetVar())->_weakref ) + { + for ( int i = m_DataWatches.size(); i--; ) + { + datawatch_t &dw = m_DataWatches[i]; + if ( _refcounted(ref->GetVar())->_weakref == dw.container && + dw.name.IsEqualTo( strName ) ) + { + dw.oldvalue = value; + break; + } + } + } + + DAP_START_RESPONSE( seq, "setVariable" ); + DAP_SET_TABLE( body ); + JSONSetString( body, "value", value, hex ? kFS_Hexadecimal : 0 ); + body.SetString( "type", GetType( value ) ); + body.SetInt( "variablesReference", ToVarRef( value ) ); + if ( ShouldPageArray( value, 16 * ARRAY_PAGE_LIMIT ) ) + body.SetInt( "indexedVariables", (int)_array(value)->_values.size() ); + DAP_SEND(); +} + +#ifdef SQDBG_SUPPORTS_SET_INSTRUCTION +void SQDebugServer::OnRequest_SetVariable_Instruction( const varref_t *ref, + string_t &strName, + string_t &strValue, + int seq ) +{ + if ( sq_type(ref->GetVar()) != OT_CLOSURE ) + { + DAP_ERROR_RESPONSE( seq, "setVariable" ); + DAP_ERROR_BODY( 0, "invalid object" ); + DAP_SEND(); + return; + } + + int index, line; + int op, arg0, arg1, arg2, arg3; + + int c1 = sscanf( strName.ptr, "%d:%d", &index, &line ); + int c2 = sscanf( strValue.ptr, "0x%02x %d %d %d %d", &op, &arg0, &arg1, &arg2, &arg3 ); + + bool fail = ( c1 != 2 ); + + if ( !fail && c2 != 5 ) + { + // Check for floats + if ( strchr( strValue.ptr, '.' ) ) + { + float farg1; + c2 = sscanf( strValue.ptr, "0x%02x %d %f %d %d", &op, &arg0, &farg1, &arg2, &arg3 ); + if ( c2 != 5 ) + { + fail = true; + } + else + { + arg1 = *(int*)&farg1; + + if ( op != _OP_LOADFLOAT ) + { + char buf[96]; + int len = snprintf( buf, sizeof(buf), + "Warning: Setting float value (%.2f) to non-float instruction\n", + farg1 ); + SendEvent_OutputStdOut( string_t( buf, min( len, (int)sizeof(buf) ) ), NULL ); + } + } + } + else + { + fail = true; + } + } + + if ( fail ) + { + DAP_ERROR_RESPONSE( seq, "setVariable" ); + DAP_ERROR_BODY( 0, "invalid amount of parameters in input" ); + error.SetBool( "showUser", true ); + DAP_SEND(); + return; + } + + RestoreCachedInstructions(); + + SQFunctionProto *func = _fp(_closure(ref->GetVar())->_function); + + // line ops are ignored in the index + for ( int c = 0; c < func->_ninstructions; c++ ) + { + if ( func->_instructions[c].op == _OP_LINE ) + index++; + + if ( c == index ) + break; + } + + UndoRestoreCachedInstructions(); + + // index will be wrong if user manualy set line ops + if ( index >= func->_ninstructions ) + { + DAP_ERROR_RESPONSE( seq, "setVariable" ); + DAP_ERROR_BODY( 0, "failed to set instruction" ); + error.SetBool( "showUser", true ); + DAP_SEND(); + return; + } + + SQInstruction *instr = func->_instructions + index; + + instr->op = op & 0xff; + instr->_arg0 = arg0 & 0xff; + instr->_arg1 = arg1; + instr->_arg2 = arg2 & 0xff; + instr->_arg3 = arg3 & 0xff; + + stringbuf_t< 32 > instrBytes; + instrBytes.PutHex( instr->op ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg0 ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg1 ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg2 ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg3 ); + + DAP_START_RESPONSE( seq, "setVariable" ); + DAP_SET_TABLE( body ); + body.SetString( "value", instrBytes ); + body.SetInt( "variablesReference", -1 ); + DAP_SEND(); +} +#endif + +void SQDebugServer::OnRequest_SetExpression( const json_table_t &arguments, int seq ) +{ + HSQUIRRELVM vm; + int frame; + + string_t expression, strValue; + + arguments.GetString( "expression", &expression ); + arguments.GetString( "value", &strValue ); + arguments.GetInt( "frameId", &frame, -1 ); + + if ( expression.IsEmpty() || strValue.IsEmpty() ) + { + DAP_ERROR_RESPONSE( seq, "evaluate" ); + DAP_ERROR_BODY( 0, "empty expression" ); + DAP_SEND(); + return; + } + + if ( !TranslateFrameID( frame, &vm, &frame ) ) + { + vm = m_pCurVM; + frame = -1; + } + + int flags = ParseFormatSpecifiers( expression ); + { + json_table_t *format; + if ( arguments.GetTable( "format", &format ) ) + { + bool hex; + format->GetBool( "hex", &hex ); + if ( hex ) + flags |= kFS_Hexadecimal; + } + } + + SQObjectPtr value; + + // Evaluate value in current stack frame even if the expression has frame lock +#ifndef SQDBG_DISABLE_COMPILER + ECompileReturnCode cres = Evaluate( strValue, vm, frame, value ); + + if ( cres != CompileReturnCode_Success && + !( cres > CompileReturnCode_Fallback && RunExpression( strValue, vm, frame, value ) ) ) +#else + if ( !( ( flags & kFS_Binary ) && ParseBinaryNumber( strValue, value ) ) && + !RunExpression( strValue, vm, frame, value ) ) +#endif + { + DAP_ERROR_RESPONSE( seq, "setExpression" ); + DAP_ERROR_BODY( 0, "failed to evaluate value '{name}'" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetString( "name", strValue ); + DAP_SEND(); + return; + } + + if ( flags & kFS_Lock ) + { +#ifdef _DEBUG + bool foundWatch = false; +#endif + for ( unsigned int i = 0; i < m_LockedWatches.size(); i++ ) + { + const watch_t &w = m_LockedWatches[i]; + if ( w.expression.IsEqualTo( expression ) ) + { + vm = GetThread( w.thread ); + frame = w.frame; +#ifdef _DEBUG + foundWatch = true; +#endif + break; + } + } + + Assert( foundWatch ); + } + + objref_t obj; + obj.type = objref_t::INVALID; + + // Try to get identifier + if ( ShouldParseEvaluateName( expression ) ) + { + SQObjectPtr dummy; + if ( !ParseEvaluateName( expression, vm, frame, obj, dummy ) ) + { + DAP_ERROR_RESPONSE( seq, "setExpression" ); + DAP_ERROR_BODY( 0, "invalid variable reference '{name}'" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetString( "name", expression ); + DAP_SEND(); + return; + } + } + else + { + SQObjectPtr dummy; + GetObj_Frame( vm, frame, expression, obj, dummy ); + } + + // Found identifier + if ( obj.type != objref_t::INVALID ) + { + if ( Set( obj, value ) ) + { + DAP_START_RESPONSE( seq, "setExpression" ); + DAP_SET_TABLE( body ); + JSONSetString( body, "value", value, flags ); + body.SetString( "type", GetType( value ) ); + body.SetInt( "variablesReference", ToVarRef( value ) ); + if ( ShouldPageArray( value, 16 * ARRAY_PAGE_LIMIT ) ) + body.SetInt( "indexedVariables", (int)_array(value)->_values.size() ); + DAP_SEND(); + } + else + { + DAP_ERROR_RESPONSE( seq, "setExpression" ); + DAP_ERROR_BODY( 0, "could not set '{name}'" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetString( "name", GetValue( obj.key ) ); + DAP_SEND(); + return; + } + } + // No identifier, run the script ( exp = val ) + else + { + int len = expression.len + 1; + +#ifndef SQDBG_DISABLE_COMPILER + len += strValue.len; +#else + // Using sq compiler + // If value was binary literal, put int + if ( !( flags & kFS_Binary ) ) + { + len += strValue.len; + } + else + { + len += 2 + countdigits<16>( (SQUnsignedInteger)_integer(value) ); + } +#endif + + stringbufext_t buf = ScratchPadBuf( len ); + + buf.Puts( expression ); + buf.Put('='); + +#ifndef SQDBG_DISABLE_COMPILER + buf.Puts( strValue ); +#else + if ( !( flags & kFS_Binary ) ) + { + buf.Puts( strValue ); + } + else + { + buf.PutHex( (SQUnsignedInteger)_integer(value), false ); + } + + buf.Term(); +#endif + +#ifndef SQDBG_DISABLE_COMPILER + string_t expr; + expr.Assign( buf.ptr, buf.len ); + + cres = Evaluate( expr, vm, frame, value ); + + if ( cres == CompileReturnCode_Success || + ( cres > CompileReturnCode_Fallback && RunExpression( buf, vm, frame, value ) ) ) +#else + if ( RunExpression( buf, vm, frame, value ) ) +#endif + { + DAP_START_RESPONSE( seq, "setExpression" ); + DAP_SET_TABLE( body ); + JSONSetString( body, "value", value, flags ); + body.SetString( "type", GetType( value ) ); + body.SetInt( "variablesReference", ToVarRef( value ) ); + if ( ShouldPageArray( value, 16 * ARRAY_PAGE_LIMIT ) ) + body.SetInt( "indexedVariables", (int)_array(value)->_values.size() ); + DAP_SEND(); + } + else + { + DAP_ERROR_RESPONSE( seq, "setExpression" ); + DAP_ERROR_BODY( 0, "failed to evaluate expression: {exp} = {val}\n\n[{reason}]" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetString( "exp", expression ); + variables.SetString( "val", strValue ); + variables.SetString( "reason", GetValue( vm->_lasterror, kFS_NoQuote ) ); + DAP_SEND(); + return; + } + } + + // Update data watch + for ( int i = m_DataWatches.size(); i--; ) + { + datawatch_t &dw = m_DataWatches[i]; + + if ( dw.container && sq_type(dw.container->_obj) == OT_NULL ) + { + FreeDataWatch( dw ); + m_DataWatches.remove(i); + continue; + } + + if ( Get( dw.obj, value ) && + _rawval(dw.oldvalue) != _rawval(value) ) + { + dw.oldvalue = value; + } + } +} + +void SQDebugServer::OnRequest_Disassemble( const json_table_t &arguments, int seq ) +{ + string_t memoryReference; + int instructionOffset, instructionCount; + + arguments.GetString( "memoryReference", &memoryReference ); + arguments.GetInt( "instructionOffset", &instructionOffset ); + arguments.GetInt( "instructionCount", &instructionCount ); + + SQFunctionProto *func = NULL; + int instrIdx = -1; + + SQInstruction *ip; + atox( memoryReference, (uintptr_t*)&ip ); + + for ( int i = m_pCurVM->_callsstacksize; i--; ) + { + const SQVM::CallInfo &ci = m_pCurVM->_callsstack[i]; + if ( sq_type(ci._closure) == OT_CLOSURE ) + { + func = _fp(_closure(ci._closure)->_function); + if ( ip >= func->_instructions && + ip < func->_instructions + func->_ninstructions ) + { + instrIdx = ci._ip - func->_instructions; + break; + } + } + } + + if ( instrIdx == -1 ) + { + DAP_ERROR_RESPONSE( seq, "disassemble" ); + DAP_ERROR_BODY( 0, "invalid instruction pointer" ); + DAP_SEND(); + return; + } + + RestoreCachedInstructions(); + + int targetStart = instrIdx + instructionOffset; + int targetEnd = targetStart + instructionCount; + + int validStart = max( 0, targetStart ); + int validEnd = min( func->_ninstructions - 1, targetEnd ); + + DAP_START_RESPONSE( seq, "disassemble" ); + DAP_SET_TABLE( body ); + wjson_array_t instructions = body.SetArray( "instructions" ); + + for ( int index = targetStart; index < targetEnd; index++ ) + { + wjson_table_t elem = instructions.AppendTable(); + + SQInstruction *instr = func->_instructions + index; + + stringbuf_t< FMT_PTR_LEN > addr; + addr.PutHex( (uintptr_t)instr ); + elem.SetString( "address", addr ); + + if ( index >= validStart && index <= validEnd ) + { + if ( instr->op != _OP_LINE ) + { + { + stringbuf_t< 128 > instrStr; + DescribeInstruction( instr, func, instrStr ); + elem.SetString( "instruction", instrStr ); + } + { + stringbuf_t< 32 > instrBytes; + instrBytes.PutHex( instr->op ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg0 ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg1 ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg2 ); instrBytes.Put(' '); + instrBytes.PutInt( instr->_arg3 ); + elem.SetString( "instructionBytes", instrBytes ); + } + + elem.SetInt( "line", (int)func->GetLine( instr ) ); + } + else + { + elem.SetString( "instruction", "" ); + } + + elem.SetString( "presentationHint", "normal" ); + } + else + { + elem.SetString( "instruction", "??" ); + elem.SetString( "presentationHint", "invalid" ); + } + + if ( index == targetStart ) + { + wjson_table_t source = elem.SetTable( "location" ); + SetSource( source, _string(func->_sourcename) ); + } + } + + Assert( instructions.size() == instructionCount ); + DAP_SEND(); + + UndoRestoreCachedInstructions(); +} + +#ifdef SUPPORTS_RESTART_FRAME +void SQDebugServer::OnRequest_RestartFrame( const json_table_t &arguments, int seq ) +{ + Assert( m_State == ThreadState_Suspended ); + + HSQUIRRELVM vm; + int frame; + arguments.GetInt( "frameId", &frame, -1 ); + + if ( !TranslateFrameID( frame, &vm, &frame ) || !vm->ci ) + { + DAP_ERROR_RESPONSE( seq, "restartFrame" ); + DAP_ERROR_BODY( 0, "invalid stack frame {id}" ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetIntString( "id", frame ); + DAP_SEND(); + return; + } + + if ( vm != m_pCurVM ) + { + DAP_ERROR_RESPONSE( seq, "restartFrame" ); + DAP_ERROR_BODY( 0, "cannot restart frame on a different thread" ); + DAP_SEND(); + return; + } + + const SQVM::CallInfo *pCurrent = vm->ci; + const SQVM::CallInfo *pTarget = vm->_callsstack + frame; + + for ( const SQVM::CallInfo *f = pCurrent; f >= pTarget; f-- ) + { + if ( sq_type(f->_closure) != OT_CLOSURE ) + { + DAP_ERROR_RESPONSE( seq, "restartFrame" ); + DAP_ERROR_BODY( 0, "cannot restart native call frame" ); + DAP_SEND(); + return; + } + } + + while ( pCurrent > pTarget ) + { + vm->LeaveFrame(); + pCurrent = vm->ci; + } + + Assert( sq_type(vm->ci->_closure) == OT_CLOSURE ); + + SQFunctionProto *func = _fp(_closure(vm->ci->_closure)->_function); + + vm->ci->_ip = func->_instructions; + + int top = vm->_top; + int target = top - func->_stacksize + func->_nparameters; + + while ( top --> target ) + { + vm->_stack._vals[top].Null(); + } + + DAP_START_RESPONSE( seq, "restartFrame" ); + DAP_SEND(); + + Break( vm, breakreason_t::Restart ); +} +#endif + +static inline int GetOpAtLine( SQFunctionProto *func, int line ) +{ + for ( int i = 0; i < (int)func->_nlineinfos; i++ ) + { + const SQLineInfo &li = func->_lineinfos[i]; + if ( line <= (int)li._line ) + return li._op; + } + + return -1; +} + +static inline int GetOpAtNextLine( SQFunctionProto *func, int curop ) +{ + Assert( func->_nlineinfos > 0 ); + + int hi = func->_nlineinfos - 1; + int lo = 0; + int mid = 0; + + while ( lo <= hi ) + { + mid = lo + ( ( hi - lo ) >> 1 ); + + int op = func->_lineinfos[mid]._op; + + if ( curop > op ) + { + lo = mid + 1; + } + else if ( curop < op ) + { + hi = mid - 1; + } + else + { + break; + } + } + + while ( mid > 0 && func->_lineinfos[mid]._op > curop ) + mid--; + + while ( ++mid < (int)func->_nlineinfos ) + { + int op = func->_lineinfos[mid]._op; + if ( op != 0 && op != curop ) + return op; + } + + return -1; +} + +// +// Only allow goto if the requested source,line matches current executing function +// +void SQDebugServer::OnRequest_GotoTargets( const json_table_t &arguments, int seq ) +{ + if ( m_State != ThreadState_Suspended ) + { + DAP_ERROR_RESPONSE( seq, "gotoTargets" ); + DAP_ERROR_BODY( 0, "thread is not suspended" ); + DAP_SEND(); + return; + } + + json_table_t *source; + string_t srcname; + int line; + arguments.GetInt( "line", &line ); + + GET_OR_ERROR_RESPONSE( "gotoTargets", arguments, source ); + + if ( ( !source->GetString( "name", &srcname ) || srcname.IsEmpty() ) && + source->GetString( "path", &srcname ) ) + { + StripFileName( &srcname.ptr, &srcname.len ); + } + + if ( !m_pCurVM->ci ) + { + DAP_ERROR_RESPONSE( seq, "gotoTargets" ); + DAP_ERROR_BODY( 0, "thread is not suspended" ); + DAP_SEND(); + return; + } + +#ifdef NATIVE_DEBUG_HOOK + const SQVM::CallInfo *ci = m_pCurVM->ci; +#else + const SQVM::CallInfo *ci = m_pCurVM->ci - 1; +#endif + + if ( sq_type(ci->_closure) != OT_CLOSURE ) + { + DAP_ERROR_RESPONSE( seq, "gotoTargets" ); + DAP_ERROR_BODY( 0, "goto on native call frame" ); + DAP_SEND(); + return; + } + + SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); + + if ( func->_nlineinfos == 0 ) + { + DAP_ERROR_RESPONSE( seq, "gotoTargets" ); + DAP_ERROR_BODY( 0, "no lineinfos" ); + DAP_SEND(); + return; + } + + if ( sq_type(func->_sourcename) == OT_STRING ) + { + sqstring_t wfuncsrc; + wfuncsrc.Assign( _string(func->_sourcename) ); + +#ifdef SQDBG_SOURCENAME_HAS_PATH + StripFileName( &wfuncsrc.ptr, &wfuncsrc.len ); +#endif + + if ( !srcname.IsEqualTo( wfuncsrc ) ) + { + DAP_ERROR_RESPONSE( seq, "gotoTargets" ); + DAP_ERROR_BODY( 0, "requested source '{src1}' does not match executing function '{src2}'" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetString( "src1", srcname ); + variables.SetString( "src2", wfuncsrc ); + DAP_SEND(); + return; + } + } + + if ( line < 0 || line > (int)func->_lineinfos[func->_nlineinfos-1]._line ) + { + DAP_ERROR_RESPONSE( seq, "gotoTargets" ); + DAP_ERROR_BODY( 0, "line {line} is out of range" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetIntString( "line", line ); + DAP_SEND(); + return; + } + + int op = GetOpAtLine( func, line ); + + if ( op == -1 ) + { + DAP_ERROR_RESPONSE( seq, "gotoTargets" ); + DAP_ERROR_BODY( 0, "could not find line {line} in function" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetIntString( "line", line ); + DAP_SEND(); + return; + } + + stringbuf_t< FMT_INT_LEN > label; + label.Put('L'); + label.PutInt( line ); + + DAP_START_RESPONSE( seq, "gotoTargets" ); + DAP_SET_TABLE( body ); + wjson_array_t targets = body.SetArray( "targets" ); + wjson_table_t elem = targets.AppendTable(); + elem.SetString( "label", label ); + elem.SetInt( "line", line ); + elem.SetInt( "id", line ); + DAP_SEND(); +} + +void SQDebugServer::OnRequest_Goto( const json_table_t &arguments, int seq ) +{ + if ( m_State != ThreadState_Suspended ) + { + DAP_ERROR_RESPONSE( seq, "goto" ); + DAP_ERROR_BODY( 0, "thread is not suspended" ); + DAP_SEND(); + return; + } + + int threadId, line; + arguments.GetInt( "threadId", &threadId, -1 ); + arguments.GetInt( "targetId", &line ); + + HSQUIRRELVM vm = ThreadFromID( threadId ); + + if ( vm != m_pCurVM ) + { + DAP_ERROR_RESPONSE( seq, "goto" ); + DAP_ERROR_BODY( 0, "cannot change execution on a different thread" ); + DAP_SEND(); + return; + } + + if ( !vm->ci ) + { + DAP_ERROR_RESPONSE( seq, "goto" ); + DAP_ERROR_BODY( 0, "thread is not suspended" ); + DAP_SEND(); + return; + } + +#ifdef NATIVE_DEBUG_HOOK + SQVM::CallInfo *ci = vm->ci; +#else + SQVM::CallInfo *ci = vm->ci - 1; +#endif + + if ( sq_type(ci->_closure) != OT_CLOSURE ) + { + DAP_ERROR_RESPONSE( seq, "goto" ); + DAP_ERROR_BODY( 0, "goto on native call frame" ); + DAP_SEND(); + return; + } + + SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); + + if ( func->_nlineinfos == 0 ) + { + DAP_ERROR_RESPONSE( seq, "goto" ); + DAP_ERROR_BODY( 0, "no lineinfos" ); + DAP_SEND(); + return; + } + + if ( line < 0 || line > (int)func->_lineinfos[func->_nlineinfos-1]._line ) + { + DAP_ERROR_RESPONSE( seq, "goto" ); + DAP_ERROR_BODY( 0, "line {line} is out of range" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetIntString( "line", line ); + DAP_SEND(); + return; + } + + int op = GetOpAtLine( func, line ); + + if ( op == -1 ) + { + DAP_ERROR_RESPONSE( seq, "goto" ); + DAP_ERROR_BODY( 0, "could not find line {line} in function" ); + error.SetBool( "showUser", true ); + wjson_table_t variables = error.SetTable( "variables" ); + variables.SetIntString( "line", line ); + DAP_SEND(); + return; + } + + ci->_ip = func->_instructions + op; + + while ( ci->_ip->op == _OP_LINE && ci->_ip + 1 < func->_instructions + func->_ninstructions ) + ci->_ip++; + + DAP_START_RESPONSE( seq, "goto" ); + DAP_SEND(); + + Break( vm, breakreason_t::Goto ); +} + +void SQDebugServer::OnRequest_Next( const json_table_t &arguments, int seq ) +{ + if ( m_State == ThreadState_Running ) + { + DAP_START_RESPONSE( seq, "next" ); + DAP_SEND(); + return; + } + + int threadId; + arguments.GetInt( "threadId", &threadId, -1 ); + + HSQUIRRELVM vm = ThreadFromID( threadId ); + + if ( vm != m_pCurVM ) + { + DAP_START_RESPONSE( seq, "next" ); + DAP_SEND(); + return; + } + + if ( !vm->ci ) + { + DAP_ERROR_RESPONSE( seq, "next" ); + DAP_ERROR_BODY( 0, "thread is not suspended" ); + DAP_SEND(); + return; + } + +#ifdef NATIVE_DEBUG_HOOK + SQVM::CallInfo *ci = vm->ci; +#else + SQVM::CallInfo *ci = vm->ci - 1; +#endif + + string_t granularity; + arguments.GetString( "granularity", &granularity ); + + if ( ( granularity.IsEqualTo( "instruction" ) && InstructionStep( vm, ci ) ) + || Step( vm, ci ) ) + { + m_State = ThreadState_StepOverInstruction; + } + else + { + m_State = ThreadState_StepOver; + } + + m_nStateCalls = m_nCalls; + + DAP_START_RESPONSE( seq, "next" ); + DAP_SEND(); +} + +void SQDebugServer::OnRequest_StepIn( const json_table_t &arguments, int seq ) +{ + if ( m_State == ThreadState_Running ) + { + DAP_START_RESPONSE( seq, "stepIn" ); + DAP_SEND(); + return; + } + + int threadId; + arguments.GetInt( "threadId", &threadId, -1 ); + + HSQUIRRELVM vm = ThreadFromID( threadId ); + + if ( vm != m_pCurVM ) + { + DAP_START_RESPONSE( seq, "stepIn" ); + DAP_SEND(); + return; + } + + if ( !vm->ci ) + { + DAP_ERROR_RESPONSE( seq, "stepIn" ); + DAP_ERROR_BODY( 0, "thread is not suspended" ); + DAP_SEND(); + return; + } + +#ifdef NATIVE_DEBUG_HOOK + SQVM::CallInfo *ci = vm->ci; +#else + SQVM::CallInfo *ci = vm->ci - 1; +#endif + + string_t granularity; + arguments.GetString( "granularity", &granularity ); + + if ( ( granularity.IsEqualTo( "instruction" ) && InstructionStep( vm, ci ) ) + || Step( vm, ci ) ) + { + m_State = ThreadState_StepInInstruction; + } + else + { + m_State = ThreadState_StepIn; + } + + DAP_START_RESPONSE( seq, "stepIn" ); + DAP_SEND(); +} + +void SQDebugServer::OnRequest_StepOut( const json_table_t &arguments, int seq ) +{ + if ( m_State == ThreadState_Running ) + { + DAP_START_RESPONSE( seq, "stepOut" ); + DAP_SEND(); + return; + } + + int threadId; + arguments.GetInt( "threadId", &threadId, -1 ); + + HSQUIRRELVM vm = ThreadFromID( threadId ); + + if ( vm != m_pCurVM ) + { + DAP_START_RESPONSE( seq, "stepOut" ); + DAP_SEND(); + return; + } + + if ( !vm->ci ) + { + DAP_ERROR_RESPONSE( seq, "stepOut" ); + DAP_ERROR_BODY( 0, "thread is not suspended" ); + DAP_SEND(); + return; + } + +#ifdef NATIVE_DEBUG_HOOK + SQVM::CallInfo *ci = vm->ci; +#else + SQVM::CallInfo *ci = vm->ci - 1; +#endif + + string_t granularity; + arguments.GetString( "granularity", &granularity ); + + if ( ( granularity.IsEqualTo( "instruction" ) && + ci != vm->_callsstack && InstructionStep( vm, ci - 1, 1 ) ) + || ( ci != vm->_callsstack && Step( vm, ci - 1 ) ) ) + { + m_State = ThreadState_StepOutInstruction; + } + else + { + m_State = ThreadState_StepOut; + } + + m_nStateCalls = m_nCalls; + m_pStateVM = vm; + + DAP_START_RESPONSE( seq, "stepOut" ); + DAP_SEND(); +} + +bool SQDebugServer::InstructionStep( HSQUIRRELVM vm, SQVM::CallInfo *ci, int instrOffset ) +{ + Assert( m_State == ThreadState_Suspended || + m_State == ThreadState_StepOverInstruction || + m_State == ThreadState_StepInInstruction || + m_State == ThreadState_StepOutInstruction || + m_pPausedThread == vm ); + + Assert( ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize ); + + while ( sq_type(ci->_closure) != OT_CLOSURE && ci > vm->_callsstack ) + --ci; + + Assert( ci >= vm->_callsstack ); + + if ( sq_type(ci->_closure) != OT_CLOSURE ) + return false; + + RestoreCachedInstructions(); + ClearCachedInstructions(); + + SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); + SQInstruction *instrEnd = func->_instructions + func->_ninstructions; + SQInstruction *ip = ci->_ip - instrOffset; + + Assert( ip >= func->_instructions && ip < instrEnd ); + + { + for (;;) + { + ++ip; + + if ( ip >= instrEnd ) + { + // Step out + if ( ci != vm->_callsstack ) + return InstructionStep( vm, ci - 1, 1 ); + + return false; + } + + if ( ip->op != _OP_LINE ) + break; + } + + if ( ci->_etraps > 0 && ip->op == _OP_POPTRAP ) + { + SQInstruction *trapIp = vm->_etraps.top()._ip; + + if ( trapIp > func->_instructions && trapIp < instrEnd ) + { + for ( ; trapIp < instrEnd; trapIp++ ) + { + if ( trapIp->op != _OP_LINE ) + { + CacheInstruction( trapIp ); + break; + } + } + } + } + + CacheInstruction( ip ); + } + + ip = ci->_ip - instrOffset; + + // Set break point at jump target as well + // A simple alternative to evaluating the jump op + if ( IsJumpOp( ip ) && GetJumpCount( ip ) != 0 ) + { + if ( ip->op == _OP_FOREACH ) + { + for ( SQInstruction *p = ip + 1; p < instrEnd; p++ ) + { + if ( p->op != _OP_LINE ) + { + CacheInstruction( p ); + break; + } + } + } + + ip += GetJumpCount( ip ); + + for (;;) + { + ++ip; + + if ( ip >= instrEnd ) + { + // Step out + if ( ci != vm->_callsstack ) + return InstructionStep( vm, ci - 1, 1 ); + + RestoreCachedInstructions(); + ClearCachedInstructions(); + return false; + } + + if ( ip->op != _OP_LINE ) + break; + } + + if ( ci->_etraps > 0 && ip->op == _OP_POPTRAP ) + { + SQInstruction *trapIp = vm->_etraps.top()._ip; + + if ( trapIp > func->_instructions && trapIp < instrEnd ) + { + for ( ; trapIp < instrEnd; trapIp++ ) + { + if ( trapIp->op != _OP_LINE ) + { + CacheInstruction( trapIp ); + break; + } + } + } + } + + CacheInstruction( ip ); + } + + return true; +} + +bool SQDebugServer::Step( HSQUIRRELVM vm, SQVM::CallInfo *ci ) +{ + Assert( m_State == ThreadState_Suspended || + m_State == ThreadState_StepOverInstruction || + m_State == ThreadState_StepInInstruction || + m_State == ThreadState_StepOutInstruction || + m_State == ThreadState_StepOver || + m_State == ThreadState_StepIn || + m_State == ThreadState_StepOut || + m_pPausedThread == vm ); + + Assert( ci >= vm->_callsstack && ci < vm->_callsstack + vm->_callsstacksize ); + + while ( sq_type(ci->_closure) != OT_CLOSURE && ci > vm->_callsstack ) + --ci; + + Assert( ci >= vm->_callsstack ); + + if ( sq_type(ci->_closure) != OT_CLOSURE ) + return false; + + RestoreCachedInstructions(); + ClearCachedInstructions(); + + SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); + Assert( ci->_ip >= func->_instructions && ci->_ip < func->_instructions + func->_ninstructions ); + + // Check if this function was compiled with debug info + // The first op is going to be a line op + if ( func->_instructions->op == _OP_LINE ) + { + Assert( func->_instructions->_arg1 != 0 ); + return false; + } + + int op = GetOpAtNextLine( func, ci->_ip - func->_instructions ); + + if ( op == -1 ) + { + // Step out + if ( ci != vm->_callsstack ) + return Step( vm, ci - 1 ); + + return false; + } + + Assert( op < func->_ninstructions ); + Assert( func->_instructions + op != ci->_ip ); + + CacheInstruction( func->_instructions + op ); + + // Set break point at every possible jump target + for ( SQInstruction *ip = ci->_ip + 1; ip <= func->_instructions + op; ip++ ) + { + if ( IsJumpOp( ip ) && GetJumpCount( ip ) != 0 ) + { + if ( ip->op == _OP_FOREACH ) + CacheInstruction( ip + 1 ); + + CacheInstruction( ip + GetJumpCount( ip ) + 1 ); + } + } + + return true; +} + +void SQDebugServer::CacheInstruction( SQInstruction *instr ) +{ + // A way to keep a weak ref to this pointer would be keeping SQFunctionProto as SQWeakRef + // This would only work in SQ3 because SQFunctionProto is ref counted since then + cachedinstr_t &cached = m_CachedInstructions.append(); + cached.ip = instr; + cached.instr = *cached.ip; + memzero( cached.ip ); + Assert( cached.ip->op == _OP_LINE ); +} + +void SQDebugServer::ClearCachedInstructions() +{ + m_CachedInstructions.clear(); +} + +void SQDebugServer::RestoreCachedInstructions() +{ + for ( int i = m_CachedInstructions.size(); i--; ) + { + cachedinstr_t &cached = m_CachedInstructions[i]; + if ( cached.ip ) + *cached.ip = cached.instr; + } +} + +void SQDebugServer::UndoRestoreCachedInstructions() +{ + for ( int i = m_CachedInstructions.size(); i--; ) + { + cachedinstr_t &cached = m_CachedInstructions[i]; + if ( cached.ip ) + memzero( cached.ip ); + } +} + +int SQDebugServer::ToVarRef( EVARREF type, HSQUIRRELVM vm, int frame ) +{ + Assert( IsScopeRef( type ) ); + + SQWeakRef *thread = GetWeakRef( vm ); + + for ( unsigned int i = 0; i < m_Vars.size(); i++ ) + { + varref_t &v = m_Vars[i]; + if ( v.type == type && v.scope.frame == frame && v.scope.thread == thread ) + return v.id; + } + + varref_t &var = m_Vars.append(); + var.id = ++m_nVarRefIndex; + var.type = type; + var.scope.frame = frame; + var.scope.thread = thread; + + return var.id; +} + +void SQDebugServer::ConvertToWeakRef( varref_t &v ) +{ + Assert( IsObjectRef( v.type ) ); + + if ( !v.obj.isWeak ) + { + v.obj.weakref = GetWeakRef( _refcounted(v.obj.obj), sq_type(v.obj.obj) ); + __ObjAddRef( v.obj.weakref ); + v.obj.isWeak = true; + + if ( v.obj.isStrong ) + { + __ObjRelease( _refcounted(v.obj.obj) ); + v.obj.isStrong = false; + } + } +} + +int SQDebugServer::ToVarRef( EVARREF type, const SQObject &obj, bool isWeak, bool isStrong ) +{ + Assert( IsObjectRef( type ) ); + Assert( ( !isWeak && !isStrong ) || ( isWeak != isStrong ) ); + + if ( !ISREFCOUNTED( sq_type(obj) ) ) + return INVALID_ID; + + if ( sq_type(obj) == OT_WEAKREF && isWeak ) + { + if ( sq_type(_weakref(obj)->_obj) == OT_NULL ) + return INVALID_ID; + } + + for ( int i = m_Vars.size(); i--; ) + { + varref_t &v = m_Vars[i]; + if ( v.type == type && _rawval(v.GetVar()) == _rawval(obj) ) + { + if ( isWeak ) + ConvertToWeakRef( v ); + + return v.id; + } + } + + Assert( m_nVarRefIndex < INT_MAX ); + + varref_t *var = &m_Vars.append(); + var->id = ++m_nVarRefIndex; + var->type = type; + var->obj.isWeak = isWeak; + var->obj.isStrong = isStrong; + + if ( isWeak ) + { + var->obj.weakref = GetWeakRef( _refcounted(obj), sq_type(obj) ); + __ObjAddRef( var->obj.weakref ); + + Assert( sq_type(var->obj.weakref->_obj) != OT_NULL ); + } + else + { + var->obj.obj = obj; + + if ( isStrong ) + { + __ObjAddRef( _refcounted(var->obj.obj) ); + } + } + + return var->id; +} + +varref_t *SQDebugServer::FromVarRef( int id ) +{ + int hi = m_Vars.size() - 1; + int lo = 0; + + while ( lo <= hi ) + { + int mid = lo + ( ( hi - lo ) >> 1 ); + + varref_t *var = &m_Vars[mid]; + + if ( id > var->id ) + { + lo = mid + 1; + } + else if ( id < var->id ) + { + hi = mid - 1; + } + else + { + Assert( var->type >= 0 && var->type < VARREF_MAX ); + + if ( IsScopeRef( var->type ) || + ( !var->obj.isWeak || sq_type(var->GetVar()) != OT_NULL ) ) + return var; + + Assert( var->obj.isWeak ); + Assert( var->obj.weakref ); + + __ObjRelease( var->obj.weakref ); + m_Vars.remove(mid); + + return NULL; + } + } + + return NULL; +} + +void SQDebugServer::RemoveVarRefs( bool all ) +{ + if ( !all ) + { + for ( int i = m_Vars.size(); i--; ) + { + varref_t &v = m_Vars[i]; + + // Keep living weakrefs, client might refer to them later + if ( IsScopeRef( v.type ) ) + { + m_Vars.remove(i); + } + else if ( IsObjectRef( v.type ) && !v.obj.isWeak ) + { + if ( v.obj.isStrong ) + { + Assert( ISREFCOUNTED( sq_type(v.obj.obj) ) ); + __ObjRelease( _refcounted(v.obj.obj) ); + } + + m_Vars.remove(i); + } + else + { + Assert( v.obj.isWeak ); + Assert( v.obj.weakref ); + + if ( sq_type(v.obj.weakref->_obj) == OT_NULL ) + { + __ObjRelease( v.obj.weakref ); + m_Vars.remove(i); + } + } + } + } + else + { + for ( int i = m_Vars.size(); i--; ) + { + varref_t &v = m_Vars[i]; + + // Release all refs the debugger is holding + if ( IsObjectRef( v.type ) ) + { + Assert( ( !v.obj.isWeak && !v.obj.isStrong ) || ( v.obj.isWeak != v.obj.isStrong ) ); + + if ( v.obj.isWeak ) + { + Assert( v.obj.weakref ); + __ObjRelease( v.obj.weakref ); + } + else if ( v.obj.isStrong ) + { + Assert( ISREFCOUNTED( sq_type(v.obj.obj) ) ); + __ObjRelease( _refcounted(v.obj.obj) ); + } + } + } + + m_Vars.purge(); + } +} + +void SQDebugServer::RemoveLockedWatches() +{ + for ( unsigned int i = 0; i < m_LockedWatches.size(); i++ ) + FreeString( &m_Strings, &m_LockedWatches[i].expression ); + + m_LockedWatches.purge(); +} + +int SQDebugServer::AddBreakpoint( int line, const string_t &src, + const string_t &condition, int hitsTarget, const string_t &logMessage ) +{ + Assert( line > 0 && !src.IsEmpty() ); + +#ifdef SQUNICODE + int size = sq_rsl( SQUnicodeLength( src.ptr, src.len ) ); + SQChar *pSrc = (SQChar*)ScratchPad( size ); + sqstring_t wsrc; + wsrc.Assign( pSrc, UTF8ToSQUnicode( pSrc, size, src.ptr, src.len ) ); + + breakpoint_t *bp = GetBreakpoint( line, wsrc ); +#else + breakpoint_t *bp = GetBreakpoint( line, src ); +#endif + + if ( bp ) + { + m_pCurVM->_lasterror = CreateSQString( m_pCurVM, _SC("duplicate breakpoint") ); + return DUPLICATE_ID; + } + + SQObjectPtr condFn; + + if ( !condition.IsEmpty() && !CompileScript( condition, condFn ) ) + return INVALID_ID; + + Assert( m_nBreakpointIndex < INT_MAX ); + + bp = &m_Breakpoints.insert( m_nFunctionBreakpointsIdx++ ); + bp->id = ++m_nBreakpointIndex; + CopyString( &m_Strings, src, &bp->src ); + + bp->line = line; + bp->hitsTarget = hitsTarget; + + if ( !logMessage.IsEmpty() ) + CopyString( &m_Strings, logMessage, &bp->logMessage ); + + if ( sq_type(condFn) != OT_NULL ) + { + bp->conditionFn = condFn; + InitEnv_GetVal( bp->conditionEnv ); + + sq_addref( m_pRootVM, &bp->conditionFn ); + sq_addref( m_pRootVM, &bp->conditionEnv ); + } + + return bp->id; +} + +int SQDebugServer::AddFunctionBreakpoint( const string_t &func, const string_t &funcsrc, int line, + const string_t &condition, int hitsTarget, const string_t &logMessage ) +{ + if ( line == -1 ) + { + m_pCurVM->_lasterror = CreateSQString( m_pCurVM, _SC("invalid function line") ); + return INVALID_ID; + } + +#ifdef SQUNICODE + int funcsize = sq_rsl( SQUnicodeLength( func.ptr, func.len ) ); + int srcsize = funcsrc.IsEmpty() ? 0 : sq_rsl( SQUnicodeLength( funcsrc.ptr, funcsrc.len ) ); + + SQChar *pFunc = (SQChar*)ScratchPad( funcsize + srcsize ); + SQChar *pSrc = pFunc + funcsize; + + sqstring_t wfunc, wsrc; + wfunc.Assign( pFunc, UTF8ToSQUnicode( pFunc, funcsize, func.ptr, func.len ) ); + + if ( funcsrc.IsEmpty() ) + { + wsrc.Assign( _SC("") ); + } + else + { + wsrc.Assign( pSrc, UTF8ToSQUnicode( pSrc, srcsize, funcsrc.ptr, funcsrc.len ) ); + } + + breakpoint_t *bp = GetFunctionBreakpoint( wfunc, wsrc, line ); +#else + breakpoint_t *bp = GetFunctionBreakpoint( func, funcsrc, line ); +#endif + + if ( bp ) + { + m_pCurVM->_lasterror = CreateSQString( m_pCurVM, _SC("duplicate breakpoint") ); + return DUPLICATE_ID; + } + + SQObjectPtr condFn; + + if ( !condition.IsEmpty() && !CompileScript( condition, condFn ) ) + return INVALID_ID; + + Assert( m_nBreakpointIndex < INT_MAX ); + + bp = &m_Breakpoints.append(); + bp->id = ++m_nBreakpointIndex; + + if ( !func.IsEmpty() ) + { + CopyString( &m_Strings, func, &bp->src ); + } + else + { + bp->src.Assign( _SC("") ); + } + + if ( !funcsrc.IsEmpty() ) + { + CopyString( &m_Strings, funcsrc, &bp->funcsrc ); + } + else + { + bp->funcsrc.Assign( _SC("") ); + } + + bp->funcline = line; + bp->hitsTarget = hitsTarget; + + if ( !logMessage.IsEmpty() ) + CopyString( &m_Strings, logMessage, &bp->logMessage ); + + if ( sq_type(condFn) != OT_NULL ) + { + bp->conditionFn = condFn; + InitEnv_GetVal( bp->conditionEnv ); + + sq_addref( m_pRootVM, &bp->conditionFn ); + sq_addref( m_pRootVM, &bp->conditionEnv ); + } + + return bp->id; +} + +breakpoint_t *SQDebugServer::GetBreakpoint( int line, const sqstring_t &src ) +{ + Assert( line && src.ptr ); + + for ( unsigned int i = 0; i < m_nFunctionBreakpointsIdx; i++ ) + { + breakpoint_t &bp = m_Breakpoints[i]; + Assert( bp.line != 0 ); + + if ( bp.line == line && bp.src.IsEqualTo( src ) ) + { + return &bp; + } + } + + return NULL; +} + +breakpoint_t *SQDebugServer::GetFunctionBreakpoint( const sqstring_t &func, const sqstring_t &funcsrc, int line ) +{ + Assert( func.ptr ); + + for ( unsigned int i = m_nFunctionBreakpointsIdx; i < m_Breakpoints.size(); i++ ) + { + breakpoint_t &bp = m_Breakpoints[i]; + Assert( bp.line == 0 ); + + if ( bp.src.IsEqualTo( func ) && + ( bp.funcsrc.IsEmpty() || bp.funcsrc.IsEqualTo( funcsrc ) ) && + ( bp.funcline == 0 || bp.funcline == line ) ) + { + return &bp; + } + } + + return NULL; +} + +void SQDebugServer::FreeBreakpoint( breakpoint_t &bp ) +{ + FreeString( &m_Strings, &bp.src ); + FreeString( &m_Strings, &bp.funcsrc ); + + if ( sq_type(bp.conditionFn) != OT_NULL ) + { + sq_release( m_pRootVM, &bp.conditionFn ); + bp.conditionFn.Null(); + } + + if ( sq_type(bp.conditionEnv) != OT_NULL ) + { + ClearEnvDelegate( bp.conditionEnv ); + + sq_release( m_pRootVM, &bp.conditionEnv ); + bp.conditionEnv.Null(); + } + + FreeString( &m_Strings, &bp.logMessage ); + bp.hits = bp.hitsTarget = 0; +} + +void SQDebugServer::RemoveAllBreakpoints() +{ + for ( int i = m_Breakpoints.size(); i--; ) + FreeBreakpoint( m_Breakpoints[i] ); + + m_Breakpoints.clear(); + m_nFunctionBreakpointsIdx = 0; +} + +void SQDebugServer::RemoveBreakpoints( const string_t &source ) +{ + sqstring_t src; + +#ifdef SQUNICODE + int size = sq_rsl( SQUnicodeLength( source.ptr, source.len ) ); + SQChar *tmp = (SQChar*)ScratchPad( size ); + src.Assign( tmp, UTF8ToSQUnicode( tmp, size, source.ptr, source.len ) ); +#else + src = source; +#endif + + for ( unsigned int i = 0; i < m_nFunctionBreakpointsIdx; ) + { + breakpoint_t &bp = m_Breakpoints[i]; + Assert( bp.line != 0 ); + + if ( bp.src.IsEqualTo( src ) ) + { + FreeBreakpoint( bp ); + m_Breakpoints.remove(i); + m_nFunctionBreakpointsIdx--; + } + else + { + i++; + } + } +} + +void SQDebugServer::RemoveFunctionBreakpoints() +{ + for ( unsigned int i = m_nFunctionBreakpointsIdx; i < m_Breakpoints.size(); ) + { + breakpoint_t &bp = m_Breakpoints[i]; + Assert( bp.line == 0 ); + + FreeBreakpoint( bp ); + m_Breakpoints.remove(i); + } + + m_nFunctionBreakpointsIdx = m_Breakpoints.size(); +} + +classdef_t *SQDebugServer::FindClassDef( SQClass *base ) +{ + for ( unsigned int i = 0; i < m_ClassDefinitions.size(); i++ ) + { + classdef_t &def = m_ClassDefinitions[i]; + if ( def.base == base ) + return &def; + } + + return NULL; +} + +const SQObjectPtr *SQDebugServer::GetClassDefValue( SQClass *base ) +{ + const classdef_t *def; + + do + { + if ( ( def = FindClassDef( base ) ) != NULL && sq_type(def->value) != OT_NULL ) + return &def->value; + } + while ( ( base = base->_base ) != NULL ); + + return NULL; +} + +const SQObjectPtr *SQDebugServer::GetClassDefMetaMembers( SQClass *base ) +{ + const classdef_t *def; + + do + { + if ( ( def = FindClassDef( base ) ) != NULL && sq_type(def->metamembers) != OT_NULL ) + return &def->metamembers; + } + while ( ( base = base->_base ) != NULL ); + + return NULL; +} + +const SQObjectPtr *SQDebugServer::GetClassDefCustomMembers( SQClass *base ) +{ + const classdef_t *def; + + do + { + if ( ( def = FindClassDef( base ) ) != NULL && sq_type(def->custommembers) != OT_NULL ) + return &def->custommembers; + } + while ( ( base = base->_base ) != NULL ); + + return NULL; +} + +void SQDebugServer::DefineClass( SQClass *target, SQTable *params ) +{ + classdef_t *def = FindClassDef( target ); + + if ( !def ) + { + def = &m_ClassDefinitions.append(); + def->base = target; + } + + SQObjectPtr name; + + if ( SQTable_Get( params, _SC("name"), name ) ) + { + if ( sq_type(name) == OT_STRING && _string(name)->_len ) + { + stringbufext_t buf = ScratchPadBuf( 1024 ); + buf.PutHex( (uintptr_t)target ); + buf.Put(' '); + buf.Puts( _string(name) ); + + CopyString< string_t >( &m_Strings, buf, &def->name ); + } + else + { + FreeString( &m_Strings, &def->name ); + } + } + + SQObjectPtr value; + + if ( SQTable_Get( params, _SC("value"), value ) ) + { + if ( sq_type(def->value) != OT_NULL ) + { + Assert( sq_type(def->value) == OT_CLOSURE || sq_type(def->value) == OT_NATIVECLOSURE ); + sq_release( m_pRootVM, &def->value ); + def->value.Null(); + } + + if ( sq_type(value) == OT_CLOSURE || sq_type(value) == OT_NATIVECLOSURE ) + { + def->value = value; + sq_addref( m_pRootVM, &def->value ); + } + } + + SQObjectPtr metamembers; + + if ( SQTable_Get( params, _SC("metamembers"), metamembers ) ) + { + if ( sq_type(def->metamembers) != OT_NULL ) + { + Assert( sq_type(def->metamembers) == OT_ARRAY ); + sq_release( m_pRootVM, &def->metamembers ); + def->metamembers.Null(); + } + + if ( sq_type(metamembers) == OT_ARRAY ) + { + def->metamembers = metamembers; + sq_addref( m_pRootVM, &def->metamembers ); + } + } + + SQObjectPtr custommembers; + + if ( SQTable_Get( params, _SC("custommembers"), custommembers ) ) + { + if ( sq_type(def->custommembers) != OT_NULL ) + { + Assert( sq_type(def->custommembers) == OT_ARRAY || sq_type(def->custommembers) == OT_CLOSURE ); + sq_release( m_pRootVM, &def->custommembers ); + def->custommembers.Null(); + } + + if ( sq_type(custommembers) == OT_ARRAY || sq_type(custommembers) == OT_CLOSURE ) + { + def->custommembers = custommembers; + sq_addref( m_pRootVM, &def->custommembers ); + } + } +} + +bool SQDebugServer::CallCustomMembersGetFunc( const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &key, SQObjectPtr &ret ) +{ + int nparams; + + if ( sq_type(closure) == OT_CLOSURE ) + { + nparams = _fp(_closure(closure)->_function)->_nparameters; + } + else if ( sq_type(closure) == OT_NATIVECLOSURE ) + { + nparams = _nativeclosure(closure)->_nparamscheck; + } + else + { + return false; + } + + if ( nparams == 1 ) + { + return RunClosure( closure, env, ret ); + } + else if ( nparams == 2 ) + { + return RunClosure( closure, env, key, ret ); + } + + return false; +} + +bool SQDebugServer::CallCustomMembersSetFunc( const SQObjectPtr &closure, const SQObject *env, + const SQObjectPtr &key, const SQObjectPtr &val, SQObjectPtr &ret ) +{ + int nparams; + + if ( sq_type(closure) == OT_CLOSURE ) + { + nparams = _fp(_closure(closure)->_function)->_nparameters; + } + else if ( sq_type(closure) == OT_NATIVECLOSURE ) + { + nparams = _nativeclosure(closure)->_nparamscheck; + } + else + { + return false; + } + + if ( nparams == 2 ) + { + return RunClosure( closure, env, val, ret ); + } + else if ( nparams == 3 ) + { + return RunClosure( closure, env, key, val, ret ); + } + + return false; +} + +void SQDebugServer::RemoveClassDefs() +{ + for ( unsigned int i = 0; i < m_ClassDefinitions.size(); i++ ) + { + classdef_t &def = m_ClassDefinitions[i]; + + FreeString( &m_Strings, &def.name ); + + if ( sq_type(def.value) != OT_NULL ) + { + Assert( sq_type(def.value) == OT_CLOSURE || sq_type(def.value) == OT_NATIVECLOSURE ); + sq_release( m_pRootVM, &def.value ); + def.value.Null(); + } + + if ( sq_type(def.metamembers) != OT_NULL ) + { + Assert( sq_type(def.metamembers) == OT_ARRAY ); + sq_release( m_pRootVM, &def.metamembers ); + def.metamembers.Null(); + } + + if ( sq_type(def.custommembers) != OT_NULL ) + { + Assert( sq_type(def.custommembers) == OT_ARRAY || sq_type(def.custommembers) == OT_CLOSURE ); + sq_release( m_pRootVM, &def.custommembers ); + def.custommembers.Null(); + } + } + + m_ClassDefinitions.purge(); +} + +int SQDebugServer::DisassemblyBufLen( SQClosure *target ) +{ + SQFunctionProto *func = _fp(target->_function); + const int maxParamNameLen = 64; + const int buflen = + STRLEN("stacksize \n") + FMT_INT_LEN + + STRLEN("instructions \n") + FMT_INT_LEN + + STRLEN("literals \n") + FMT_INT_LEN + + STRLEN("localvarinfos \n") + FMT_INT_LEN + + 6 + 1 + + STRLEN("parameters \n") + FMT_INT_LEN + + func->_nparameters * ( maxParamNameLen + 2 ) - 2 + 1 + +#if SQUIRREL_VERSION_NUMBER > 212 + func->_ndefaultparams * ( maxParamNameLen + 3 ) + +#endif + 6 + 1 + + func->_ninstructions * ( 6 + 30 + 128 + 1 ) - 1 + + 1; + + return buflen; +} + +sqstring_t SQDebugServer::PrintDisassembly( SQClosure *target, SQChar *scratch, int bufsize ) +{ + SQFunctionProto *func = _fp(target->_function); + const int maxParamNameLen = 64; + SQChar *buf = scratch; + +#define _bs (bufsize - (int)((char*)buf - (char*)scratch)) + + int instrCount = func->_ninstructions; + for ( int i = instrCount; i--; ) + if ( func->_instructions[i].op == _OP_LINE ) + instrCount--; + +#define putint( str, val ) \ + { \ + int len = STRLEN(str); \ + memcpy( buf, _SC(str), sq_rsl(len) ); \ + buf += len; \ + buf += printint( buf, _bs, val ); \ + *buf++ = '\n'; \ + } + + putint( "stacksize ", (int)func->_stacksize ); + putint( "instructions ", instrCount ); + putint( "literals ", (int)func->_nliterals ); + putint( "localvarinfos ", (int)func->_nlocalvarinfos ); + + int nparams = func->_nparameters; + +#if SQUIRREL_VERSION_NUMBER >= 300 + if ( func->_varparams ) + nparams--; +#endif + + putint( "parameters ", nparams ); + +#undef putint + + for ( int i = 6; i--; ) + *buf++ = '-'; + + *buf++ = '\n'; + + for ( int i = 0; i < nparams; i++ ) + { + const SQObjectPtr ¶m = func->_parameters[i]; + Assert( sq_type(param) == OT_STRING ); + + int len = min( (int)_string(param)->_len, maxParamNameLen ); + memcpy( buf, _string(param)->_val, sq_rsl(len) ); + buf += len; + +#if SQUIRREL_VERSION_NUMBER > 212 + int idx; + if ( func->_ndefaultparams && ( idx = (int)func->_ndefaultparams - ( nparams - i ) ) >= 0 ) + { + const SQObjectPtr &val = target->_defaultparams[idx]; + string_t str; + + switch ( sq_type(val) ) + { + case OT_INTEGER: + case OT_FLOAT: + case OT_BOOL: + case OT_NULL: + case OT_TABLE: + case OT_ARRAY: + case OT_CLASS: + str = GetValue( val, kFS_NoAddr ); + break; + default: + str = GetType( val ); + } + + len = STRLEN(" = "); + memcpy( buf, _SC(" = "), sq_rsl(len) ); + buf += len; + + len = min( str.len, maxParamNameLen - 3 ); +#ifdef SQUNICODE + UTF8ToSQUnicode( buf, _bs, str.ptr, str.len ); +#else + memcpy( buf, str.ptr, sq_rsl(len) ); +#endif + buf += len; + } +#endif + + *buf++ = ','; + *buf++ = ' '; + } + + if ( !func->_varparams ) + { + buf -= 2; + } + else + { + *buf++ = '.'; + *buf++ = '.'; + *buf++ = '.'; + } + + *buf++ = '\n'; + + for ( int i = 6; i--; ) + *buf++ = '-'; + + *buf++ = '\n'; + + RestoreCachedInstructions(); + + for ( int i = 0, index = 0; i < func->_ninstructions; i++ ) + { + SQInstruction *instr = func->_instructions + i; + if ( instr->op == _OP_LINE ) + continue; + + stringbuf_t< 128 > tmp; + tmp.PutHex( instr->op ); tmp.Put(' '); + tmp.PutInt( instr->_arg0 ); tmp.Put(' '); + tmp.PutInt( instr->_arg1 ); tmp.Put(' '); + tmp.PutInt( instr->_arg2 ); tmp.Put(' '); + tmp.PutInt( instr->_arg3 ); + tmp.Term(); + +#ifdef SQUNICODE + int len = scsprintf( buf, _bs / (int)sizeof(SQChar), _SC("%-6d %-29hs"), index++, tmp.ptr ); + + if ( len < 0 || len > _bs / (int)sizeof(SQChar) ) + len = _bs / (int)sizeof(SQChar); + + buf += len; + + tmp.len = 0; + DescribeInstruction( instr, func, tmp ); + + buf += UTF8ToSQUnicode( buf, _bs, tmp.ptr, tmp.len ); +#else + int len = scsprintf( buf, _bs / (int)sizeof(SQChar), _SC("%-6d %-29s"), index++, tmp.ptr ); + + if ( len < 0 || len > _bs / (int)sizeof(SQChar) ) + len = _bs / (int)sizeof(SQChar); + + buf += len; + + stringbufext_t sbuf( buf, _bs ); + DescribeInstruction( instr, func, sbuf ); + buf += sbuf.len; +#endif + + if ( _bs <= 0 ) + { + buf--; + break; + } + + *buf++ = '\n'; + } + + UndoRestoreCachedInstructions(); + + *buf-- = 0; + +#undef _bs + + return { scratch, (unsigned int)( buf - scratch ) }; +} + +#ifndef SQDBG_DISABLE_PROFILER +CProfiler *SQDebugServer::GetProfiler( HSQUIRRELVM vm ) +{ + for ( unsigned int i = 0; i < m_Profilers.size(); i++ ) + { + threadprofiler_t &tp = m_Profilers[i]; + if ( tp.thread && sq_type(tp.thread->_obj) == OT_THREAD ) + { + if ( _thread(tp.thread->_obj) == vm ) + { + return &tp.prof; + } + } + } + + return NULL; +} + +CProfiler *SQDebugServer::GetProfilerFast( HSQUIRRELVM vm ) +{ + if ( m_pCurVM == vm ) + { + Assert( m_pProfiler == GetProfiler(vm) ); + return m_pProfiler; + } + + return GetProfiler(vm); +} + +void SQDebugServer::ProfSwitchThread( HSQUIRRELVM vm ) +{ + Assert( IsProfilerEnabled() ); + + if ( m_Profilers.size() == 0 ) + m_Profilers.reserve(1); + + for ( unsigned int i = 0; i < m_Profilers.size(); i++ ) + { + threadprofiler_t &tp = m_Profilers[i]; + if ( tp.thread && sq_type(tp.thread->_obj) == OT_THREAD ) + { + if ( _thread(tp.thread->_obj) == vm ) + { + m_pProfiler = tp.prof.IsEnabled() ? &tp.prof : NULL; + return; + } + } + else + { + __ObjRelease( tp.thread ); + m_Profilers.remove(i); + i--; + } + } + + threadprofiler_t &tp = m_Profilers.append(); + tp.thread = GetWeakRef( vm ); + __ObjAddRef( tp.thread ); + + m_pProfiler = &tp.prof; + m_pProfiler->Start( vm ); +} + +void SQDebugServer::ProfStart() +{ + if ( !IsClientConnected() ) + SetDebugHook( &SQProfHook ); + + m_bProfilerEnabled = true; + ProfSwitchThread( m_pCurVM ); +} + +void SQDebugServer::ProfStop() +{ + if ( !IsClientConnected() ) + SetDebugHook( NULL ); + + for ( unsigned int i = 0; i < m_Profilers.size(); i++ ) + { + threadprofiler_t &tp = m_Profilers[i]; + __ObjRelease( tp.thread ); + + tp.prof.Stop(); + } + + m_Profilers.clear(); + m_pProfiler = NULL; + m_bProfilerEnabled = false; +} + +void SQDebugServer::ProfPause( HSQUIRRELVM vm ) +{ + CProfiler *prof = GetProfilerFast( vm ); + if ( prof && prof->IsEnabled() ) + { + prof->Pause(); + } +} + +void SQDebugServer::ProfResume( HSQUIRRELVM vm ) +{ + CProfiler *prof = GetProfilerFast( vm ); + if ( prof && prof->IsEnabled() ) + { + prof->Resume(); + } +} + +void SQDebugServer::ProfReset( HSQUIRRELVM vm, SQString *tag ) +{ + CProfiler *prof = GetProfilerFast( vm ); + if ( prof && prof->IsEnabled() ) + { + prof->Reset( vm, tag ); + } +} + +void SQDebugServer::ProfGroupBegin( HSQUIRRELVM vm, SQString *tag ) +{ + CProfiler *prof = GetProfilerFast( vm ); + if ( prof && prof->IsActive() ) + { + prof->GroupBegin( tag ); + } +} + +void SQDebugServer::ProfGroupEnd( HSQUIRRELVM vm ) +{ + CProfiler *prof = GetProfilerFast( vm ); + if ( prof && prof->IsActive() ) + { + prof->GroupEnd(); + } +} + +sqstring_t SQDebugServer::ProfGets( HSQUIRRELVM vm, SQString *tag, int type ) +{ + Assert( IsProfilerEnabled() ); + + CProfiler *pProfiler = NULL; + + for ( unsigned int i = 0; i < m_Profilers.size(); i++ ) + { + threadprofiler_t &tp = m_Profilers[i]; + if ( tp.thread && sq_type(tp.thread->_obj) == OT_THREAD ) + { + if ( _thread(tp.thread->_obj) == vm ) + { + pProfiler = &tp.prof; + break; + } + } + else + { + __ObjRelease( tp.thread ); + m_Profilers.remove(i); + i--; + } + } + + if ( !pProfiler ) + return { 0, 0 }; + + const int size = pProfiler->GetMaxOutputLen( tag, type ); + SQChar *buf = _ss(m_pRootVM)->GetScratchPad( sq_rsl(size) ); + int len = pProfiler->Output( tag, type, buf, size ); + Assert( len >= 0 ); + + return { buf, (unsigned int)len }; +} + +void SQDebugServer::ProfPrint( HSQUIRRELVM vm, SQString *tag, int type ) +{ + sqstring_t str = ProfGets( vm, tag, type ); + if ( str.IsEmpty() ) + return; + + // Print each line + for ( SQChar *start = str.ptr; ; ) + { + SQChar *end = scstrchr( start, '\n' ); + if ( !end ) + break; + + int linelen = (int)( end + 1 - start ); + + _OutputDebugStringFmt( _SC(FMT_VSTR), linelen, start ); + m_Print( vm, _SC(FMT_VSTR), linelen, start ); + SendEvent_OutputStdOut( sqstring_t( start, linelen ), NULL ); + + if ( end + 1 >= str.ptr + str.len ) + break; + + start = end + 1; + } +} +#endif + +#ifndef SQDBG_CALL_DEFAULT_ERROR_HANDLER +void SQDebugServer::PrintVar( HSQUIRRELVM vm, const SQChar *name, const SQObjectPtr &obj ) +{ + switch ( sq_type(obj) ) + { + case OT_NULL: + SQErrorAtFrame( vm, NULL, _SC("[%s] NULL\n"), name ); + break; + case OT_INTEGER: + SQErrorAtFrame( vm, NULL, _SC("[%s] " FMT_INT "\n"), name, _integer(obj) ); + break; + case OT_FLOAT: + SQErrorAtFrame( vm, NULL, _SC("[%s] %.14g\n"), name, _float(obj) ); + break; + case OT_USERPOINTER: + SQErrorAtFrame( vm, NULL, _SC("[%s] USERPOINTER\n"), name ); + break; + case OT_STRING: + SQErrorAtFrame( vm, NULL, _SC("[%s] \"%s\"\n"), name, _string(obj)->_val ); + break; + case OT_TABLE: + SQErrorAtFrame( vm, NULL, _SC("[%s] TABLE\n"), name ); + break; + case OT_ARRAY: + SQErrorAtFrame( vm, NULL, _SC("[%s] ARRAY\n"), name ); + break; + case OT_CLOSURE: + SQErrorAtFrame( vm, NULL, _SC("[%s] CLOSURE\n"), name ); + break; + case OT_NATIVECLOSURE: + SQErrorAtFrame( vm, NULL, _SC("[%s] NATIVECLOSURE\n"), name ); + break; + case OT_GENERATOR: + SQErrorAtFrame( vm, NULL, _SC("[%s] GENERATOR\n"), name ); + break; + case OT_USERDATA: + SQErrorAtFrame( vm, NULL, _SC("[%s] USERDATA\n"), name ); + break; + case OT_THREAD: + SQErrorAtFrame( vm, NULL, _SC("[%s] THREAD\n"), name ); + break; + case OT_CLASS: + SQErrorAtFrame( vm, NULL, _SC("[%s] CLASS\n"), name ); + break; + case OT_INSTANCE: + SQErrorAtFrame( vm, NULL, _SC("[%s] INSTANCE\n"), name ); + break; + case OT_WEAKREF: + PrintVar( vm, name, _weakref(obj)->_obj ); + break; + case OT_BOOL: + SQErrorAtFrame( vm, NULL, _SC("[%s] %s\n"), name, _integer(obj) ? _SC("true") : _SC("false") ); + break; + default: UNREACHABLE(); + } +} + +void SQDebugServer::PrintStack( HSQUIRRELVM vm ) +{ + SQErrorAtFrame( vm, NULL, _SC("\nCALLSTACK\n") ); + + int i = vm->_callsstacksize; + while ( i-- ) + { + const SQVM::CallInfo &ci = vm->_callsstack[i]; + + if ( ShouldIgnoreStackFrame(ci) ) + continue; + + if ( sq_type(ci._closure) == OT_CLOSURE ) + { + SQFunctionProto *func = _fp(_closure(ci._closure)->_function); + + const SQChar *fn = _SC("??"); + const SQChar *src = _SC("??"); + int line = func->GetLine( ci._ip ); + + if ( sq_type(func->_name) == OT_STRING ) + fn = _string(func->_name)->_val; + + if ( sq_type(func->_sourcename) == OT_STRING ) + src = _string(func->_sourcename)->_val; + + SQErrorAtFrame( vm, &ci, _SC("*FUNCTION [%s()] %s line [%d]\n"), fn, src, line ); + } + else if ( sq_type(ci._closure) == OT_NATIVECLOSURE ) + { + SQNativeClosure *closure = _nativeclosure(ci._closure); + + const SQChar *fn = _SC("??"); + const SQChar *src = _SC("NATIVE"); + int line = -1; + + if ( sq_type(closure->_name) == OT_STRING ) + fn = _string(closure->_name)->_val; + + SQErrorAtFrame( vm, NULL, _SC("*FUNCTION [%s()] %s line [%d]\n"), fn, src, line ); + } + else UNREACHABLE(); + } + + SQErrorAtFrame( vm, NULL, _SC("\nLOCALS\n") ); + + i = vm->_callsstacksize; + if ( i > 10 ) + i = 10; + + while ( i-- ) + { + const SQVM::CallInfo &ci = vm->_callsstack[i]; + + if ( sq_type(ci._closure) != OT_CLOSURE ) + continue; + + if ( ShouldIgnoreStackFrame(ci) ) + continue; + + int stackbase = GetStackBase( vm, &ci ); + SQClosure *pClosure = _closure(ci._closure); + SQFunctionProto *func = _fp(pClosure->_function); + SQUnsignedInteger ip = (SQUnsignedInteger)( ci._ip - func->_instructions - 1 ); + + for ( int i = 0; i < func->_nlocalvarinfos; i++ ) + { + const SQLocalVarInfo &var = func->_localvarinfos[i]; + if ( var._start_op <= ip + 1 && var._end_op >= ip ) + { + PrintVar( vm, _string(var._name)->_val, vm->_stack._vals[ stackbase + var._pos ] ); + } + } + + for ( int i = 0; i < func->_noutervalues; i++ ) + { + const SQOuterVar &var = func->_outervalues[i]; + PrintVar( vm, _string(var._name)->_val, *_outervalptr( pClosure->_outervalues[i] ) ); + } + } +} +#endif + +void SQDebugServer::ErrorHandler( HSQUIRRELVM vm ) +{ + if ( m_bDebugHookGuard ) + return; + + string_t err; + + // Custom error handler and PrintStack can reallocate sqdbg scratch buf, + // get value when it is needed + SQObjectPtr oe; + + if ( sq_gettop( vm ) >= 1 ) + { + HSQOBJECT o; + sq_getstackobj( vm, 2, &o ); + err.ptr = 0; + oe = o; + } + else + { + err.Assign( "??" ); + } + + // An error handler is required to detect exceptions. + // The downside of calling the default error handler instead of + // replicating it in the debugger is the extra stack frame and redundant print locations. + // Otherwise this would be preferrable for preserving custom error handlers. +#ifdef SQDBG_CALL_DEFAULT_ERROR_HANDLER + SQObjectPtr dummy; + vm->Call( m_ErrorHandler, 2, vm->_top-2, dummy, SQFalse ); + + if ( !err.ptr ) + err = GetValue( oe, kFS_NoQuote ); +#else + bool getError = !err.ptr; + if ( getError ) + err = GetValue( oe, kFS_NoQuote ); + + SQErrorAtFrame( vm, NULL, _SC("\nAN ERROR HAS OCCURRED [" FMT_VCSTR "]\n"), STR_EXPAND(err) ); + PrintStack( vm ); + + if ( getError ) + err = GetValue( oe, kFS_NoQuote ); +#endif + + if ( m_bBreakOnExceptions ) + { + Break( vm, { breakreason_t::Exception, err } ); + +#if SQUIRREL_VERSION_NUMBER < 300 + // SQ2 doesn't notify the debug hook of returns via errors, + // have to suspend and finish profiler calls here +#ifndef SQDBG_DISABLE_PROFILER + if ( IsProfilerEnabled() ) + { + CProfiler *prof = GetProfiler( vm ); + if ( prof && prof->IsActive() ) + { + prof->CallEndAll(); + } + } +#endif + // Use m_bExceptionPause to make stepping while suspended here continue the thread. + if ( m_State == ThreadState_SuspendNow ) + { + m_bExceptionPause = true; + m_bInDebugHook = true; + Suspend(); + m_bExceptionPause = false; + m_bInDebugHook = false; + } +#else + // To fix instruction stepping in exception + m_bExceptionPause = true; +#endif + } +} + +void SQDebugServer::Break( HSQUIRRELVM vm, breakreason_t reason ) +{ + Assert( IsClientConnected() ); + Assert( reason.reason != breakreason_t::None ); + + DAP_START_EVENT( ++m_Sequence, "stopped" ); + DAP_SET_TABLE( body ); + body.SetInt( "threadId", ThreadToID( vm ) ); + body.SetBool( "allThreadsStopped", true ); + + switch ( reason.reason ) + { + case breakreason_t::Step: + body.SetString( "reason", "step" ); + break; + case breakreason_t::Breakpoint: + body.SetString( "reason", "breakpoint" ); + break; + case breakreason_t::Exception: + body.SetString( "reason", "exception" ); + break; + case breakreason_t::Pause: + body.SetString( "reason", "pause" ); + break; + case breakreason_t::Restart: + body.SetString( "reason", "restart" ); + break; + case breakreason_t::Goto: + body.SetString( "reason", "goto" ); + break; + case breakreason_t::FunctionBreakpoint: + body.SetString( "reason", "function breakpoint" ); + break; + case breakreason_t::DataBreakpoint: + body.SetString( "reason", "data breakpoint" ); + break; + default: UNREACHABLE(); + } + + if ( !reason.text.IsEmpty() ) + body.SetString( "text", reason.text ); + + if ( reason.reason == breakreason_t::Breakpoint || + reason.reason == breakreason_t::FunctionBreakpoint || + reason.reason == breakreason_t::DataBreakpoint ) + { + wjson_array_t ids = body.SetArray( "hitBreakpointIds" ); + ids.Append( reason.id ); + } + DAP_SEND(); + + if ( m_State != ThreadState_Suspended ) + m_State = ThreadState_SuspendNow; +} + +void SQDebugServer::Suspend() +{ + Assert( m_State == ThreadState_SuspendNow ); + + m_State = ThreadState_Suspended; + + do + { + if ( IsClientConnected() ) + { + Frame(); + } + else + { + Continue( NULL ); + break; + } + + sqdbg_sleep( 5 ); + } + while ( m_State == ThreadState_Suspended ); +} + +void SQDebugServer::Continue( HSQUIRRELVM vm ) +{ + if ( m_State == ThreadState_SuspendNow ) + return; + + if ( IsClientConnected() && m_State != ThreadState_Running ) + { + DAP_START_EVENT( ++m_Sequence, "continued" ); + DAP_SET_TABLE( body ); + body.SetInt( "threadId", ThreadToID( vm ) ); + body.SetBool( "allThreadsContinued", true ); + DAP_SEND(); + } + + m_State = ThreadState_Running; + m_pPausedThread = NULL; + RemoveReturnValues(); +#if SQUIRREL_VERSION_NUMBER >= 300 + m_bExceptionPause = false; +#endif + RestoreCachedInstructions(); + ClearCachedInstructions(); + RemoveVarRefs( false ); + RemoveLockedWatches(); + +#ifndef SQDBG_DISABLE_PROFILER + // HACKHACK: Re-validate current profiler + if ( IsProfilerEnabled() ) + ProfSwitchThread( m_pCurVM ); +#endif + + ClearEnvDelegate( m_EnvGetVal ); + + if ( m_Scratch.Size() > 1024 ) + m_Scratch.Alloc( 1024 ); +} + +void SQDebugServer::RemoveReturnValues() +{ + for ( unsigned int i = 0; i < m_ReturnValues.size(); i++ ) + { + returnvalue_t &rv = m_ReturnValues[i]; + if ( rv.funcname ) + __ObjRelease( rv.funcname ); + } + + m_ReturnValues.clear(); + m_iYieldValues = 0; +} + +int SQDebugServer::EvalAndWriteExpr( HSQUIRRELVM vm, const SQVM::CallInfo *ci, string_t &expression, + char *buf, int size ) +{ + // Don't modify logMessage + char *comma = NULL; + + // LOCK flag is ignored, it's ok + int flags = ParseFormatSpecifiers( expression, &comma ); + + // Write to buffer in here because + // value could be holding the only ref to a SQString + // which the result string_t will point to + SQObjectPtr value; + +#ifndef SQDBG_DISABLE_COMPILER + bool res; + + if ( expression.len <= 256 ) + { + // 'expression' is a substring of breakpoint_t::logMessage + // don't modify its bytes in CCompiler::ParseString + char cpy[256]; + string_t expr; + expr.Assign( cpy, expression.len ); + memcpy( cpy, expression.ptr, expression.len ); + + ECompileReturnCode cres = Evaluate( expr, vm, ci, value ); + res = ( cres == CompileReturnCode_Success || + ( cres > CompileReturnCode_Fallback && RunExpression( expression, vm, ci, value ) ) ); + } + else + { + res = RunExpression( expression, vm, ci, value ); + } + + if ( res ) +#else + objref_t obj; + if ( GetObj_Frame( vm, ci, expression, obj, value ) || RunExpression( expression, vm, ci, value ) ) +#endif + { + if ( comma ) + *comma = ','; + + string_t result = GetValue( value, flags ); + + int writelen = min( (int)result.len, size ); + memcpy( buf, result.ptr, writelen ); + return writelen; + } + + if ( comma ) + *comma = ','; + + return 0; +} + +// +// Expressions within `{}` are evaluated. +// Escape the opening bracket to print brackets `\{` +// +// Special keywords: $FUNCTION, $CALLER, $HITCOUNT +// +void SQDebugServer::TracePoint( breakpoint_t *bp, HSQUIRRELVM vm, int frame ) +{ + char buf[512]; + int bufsize = sizeof(buf) - 2; // \n\0 + int readlen = min( (int)bp->logMessage.len, bufsize ); + char *pWrite = buf; + char *logMessage = bp->logMessage.ptr; + + // if logMessage is surrounded with \{ and }/, + // evaluate the expression but don't print. + // A simple way to inject expression evaluation without side effects + // although still limited by print buffer size + bool escapePrint = readlen > 4 && + logMessage[0] == '\\' && + logMessage[1] == '{' && + logMessage[readlen-2] == '}' && + logMessage[readlen-1] == '/'; + + if ( escapePrint ) + logMessage[0] = 0; + + for ( int iRead = 0; iRead < readlen; iRead++ ) + { + switch ( logMessage[iRead] ) + { + case '{': + { + // '\' preceeds '{' + if ( iRead && logMessage[iRead-1] == '\\' ) + { + pWrite[-1] = '{'; + continue; + } + + int depth = 1; + for ( int j = iRead + 1; j < readlen; j++ ) + { + switch ( logMessage[j] ) + { + case '}': + { + depth--; + + // Found expression + if ( depth == 0 ) + { + const SQVM::CallInfo *ci = vm->_callsstack + frame; + + string_t expression; + expression.Assign( logMessage + iRead + 1, j - iRead - 1 ); + + if ( expression.len ) + pWrite += EvalAndWriteExpr( vm, ci, expression, pWrite, bufsize - j ); + + iRead = j; + goto exit; + } + + break; + } + case '{': + { + depth++; + break; + } + } + } + exit:; + break; + } + case '$': + { + if ( iRead && logMessage[iRead-1] == '\\' ) + { + pWrite[-1] = '$'; + continue; + } + + #define STRCMP( s, StrLiteral ) \ + memcmp( (s), (StrLiteral), sizeof(StrLiteral)-1 ) + + #define CHECK_KEYWORD(s) \ + ( ( iRead + (int)STRLEN(s) < readlen ) && \ + !STRCMP( logMessage + iRead + 1, s ) ) + + if ( CHECK_KEYWORD("FUNCTION") ) + { + const SQVM::CallInfo *ci = vm->_callsstack + frame; + const SQObjectPtr &funcname = _fp(_closure(ci->_closure)->_function)->_name; + + if ( sq_type(funcname) == OT_STRING ) + { + SQString *name = _string(funcname); + + int writelen = scstombs( pWrite, bufsize - iRead, name->_val, name->_len ); + pWrite += writelen; + } + + iRead += STRLEN("FUNCTION"); + break; + } + else if ( CHECK_KEYWORD("CALLER") ) + { + const SQVM::CallInfo *ci = vm->_callsstack + frame; + if ( ci > vm->_callsstack ) + { + const SQVM::CallInfo *cii = ci - 1; + + if ( sq_type(cii->_closure) == OT_CLOSURE ) + { + if ( sq_type(_fp(_closure(cii->_closure)->_function)->_name) == OT_STRING ) + { + SQString *name = _string(_fp(_closure(cii->_closure)->_function)->_name); + + int writelen = scstombs( pWrite, bufsize - iRead, name->_val, name->_len ); + pWrite += writelen; + } + } + else if ( sq_type(cii->_closure) == OT_NATIVECLOSURE ) + { + if ( sq_type(_nativeclosure(cii->_closure)->_name) == OT_STRING ) + { + SQString *name = _string(_nativeclosure(cii->_closure)->_name); + + int writelen = scstombs( pWrite, bufsize - iRead, name->_val, name->_len ); + pWrite += writelen; + } + } + else UNREACHABLE(); + } + + iRead += STRLEN("CALLER"); + break; + } + else if ( CHECK_KEYWORD("HITCOUNT") ) + { + // lazy hack, hit count was reset after hitting the target + // if this count is to ignore hit target, keep trace hit count separately + int hits = bp->hits ? bp->hits : bp->hitsTarget; + pWrite += printint( pWrite, bufsize - iRead, hits ); + iRead += STRLEN("HITCOUNT"); + break; + } + // else fallthrough + + #undef STRCMP + #undef CHECK_KEYWORD + } + default: + *pWrite++ = logMessage[iRead]; + } + } + + if ( escapePrint ) + { + logMessage[0] = '\\'; + return; + } + + *pWrite++ = '\n'; + *pWrite = 0; + + const SQVM::CallInfo *ci = vm->_callsstack + frame; + + _OutputDebugStringA( buf ); +#ifdef SQUNICODE + m_Print( vm, _SC(FMT_CSTR), buf ); +#else + m_Print( vm, buf ); +#endif + SendEvent_OutputStdOut( string_t( buf, (int)( pWrite - buf ) ), ci ); +} + +bool SQDebugServer::HasCondition( const breakpoint_t *bp ) +{ + return ( sq_type(bp->conditionFn) != OT_NULL ); +} + +bool SQDebugServer::CheckBreakpointCondition( breakpoint_t *bp, HSQUIRRELVM vm, const SQVM::CallInfo *ci ) +{ + Assert( HasCondition( bp ) ); + Assert( sq_type(bp->conditionFn) != OT_NULL && + sq_type(bp->conditionEnv) != OT_NULL ); + + SetCallFrame( bp->conditionEnv, vm, ci ); + SetEnvDelegate( bp->conditionEnv, vm->_stack._vals[ GetStackBase( vm, ci ) ] ); + + SQObjectPtr res; + + // Using sqdbg compiler here is faster for simple expressions, + // but gets increasingly slower as the expression grows larger and more complex + // while executing precompiled sq function is constant time + if ( RunClosure( bp->conditionFn, &bp->conditionEnv, res ) ) + { + return !IsFalse( res ); + } + else + { + // Invalid condition, remove it + ClearEnvDelegate( bp->conditionEnv ); + + sq_release( m_pRootVM, &bp->conditionFn ); + sq_release( m_pRootVM, &bp->conditionEnv ); + + bp->conditionFn.Null(); + bp->conditionEnv.Null(); + + return false; + } +} + +#define SQ_HOOK_LINE 'l' +#define SQ_HOOK_CALL 'c' +#define SQ_HOOK_RETURN 'r' + +void SQDebugServer::DebugHook( HSQUIRRELVM vm, SQInteger type, + const SQChar *sourcename, SQInteger line, const SQChar *funcname ) +{ + Assert( IsClientConnected() ); + + if ( m_bDebugHookGuard ) + return; + +#if SQUIRREL_VERSION_NUMBER < 300 + // Debug hook re-entry guard doesn't exist in SQ2 + if ( m_bInDebugHook ) + return; + + m_bInDebugHook = true; +#endif + +#ifdef NATIVE_DEBUG_HOOK + SQVM::CallInfo *ci = vm->ci; +#else + SQVM::CallInfo *ci = vm->ci - 1; +#endif + + // The only way to detect thread change, not ideal + if ( m_pCurVM != vm ) + { + // HACKHACK: Detect thread.call, wakeup and suspend calls to make StepOver/StepOut make sense + // This is not perfect as it is only detected on the first debug hook call + // *after* the thread functions were called, which means it will miss instructions. + if ( m_pCurVM->ci ) + { + if ( m_pCurVM->_suspended ) + { + SQInstruction *pip = m_pCurVM->ci->_ip - 1; + + if ( pip->op == _OP_CALL ) + { + const SQObjectPtr &val = m_pCurVM->_stack._vals[ m_pCurVM->_stackbase + pip->_arg1 ]; + if ( sq_type(val) == OT_NATIVECLOSURE && + sq_type(_nativeclosure(val)->_name) == OT_STRING && + sqstring_t(_SC("suspend")).IsEqualTo( _string(_nativeclosure(val)->_name) ) ) + { + m_nCalls -= (int)( m_pCurVM->ci - m_pCurVM->_callsstack ) + 1; + + switch ( m_State ) + { + case ThreadState_StepOut: + + if ( m_pStateVM != m_pCurVM ) + break; + + case ThreadState_StepOver: + + m_State = ThreadState_StepIn; + break; + + case ThreadState_StepOutInstruction: + + if ( m_pStateVM != m_pCurVM ) + break; + + case ThreadState_StepOverInstruction: + case ThreadState_StepInInstruction: + + RestoreCachedInstructions(); + ClearCachedInstructions(); + + if ( InstructionStep( vm, ci, 2 ) ) + { + m_State = ThreadState_StepInInstruction; + } + else + { + m_State = ThreadState_NextStatement; + } + + default: + break; + } + } + } + } + else + { + if ( sq_type(m_pCurVM->ci->_closure) == OT_NATIVECLOSURE && + sq_type(_nativeclosure(m_pCurVM->ci->_closure)->_name) == OT_STRING && + ( sqstring_t(_SC("wakeup")).IsEqualTo( + _string(_nativeclosure(m_pCurVM->ci->_closure)->_name) ) || + sqstring_t(_SC("call")).IsEqualTo( + _string(_nativeclosure(m_pCurVM->ci->_closure)->_name) ) ) ) + { + m_nCalls += (int)( vm->ci - vm->_callsstack ) + 1; + } + } + } + + ThreadToID( vm ); + m_pCurVM = vm; + +#ifndef SQDBG_DISABLE_PROFILER + if ( IsProfilerEnabled() && + // Ignore repl + // NOTE: This isn't reliable, a thread could've been called from repl + // profiler is validated on step + ( !sourcename || + !sqstring_t(_SC("sqdbg")).IsEqualTo( SQStringFromSQChar( sourcename ) ) ) ) + { + ProfSwitchThread( vm ); + } +#endif + } + +#ifndef SQDBG_DISABLE_PROFILER + Assert( !IsProfilerEnabled() || + !sourcename || + sqstring_t(_SC("sqdbg")).IsEqualTo( SQStringFromSQChar( sourcename ) ) || + m_pProfiler == GetProfiler(vm) ); +#endif + + if ( m_pPausedThread == vm && + // Ignore repl + ( !sourcename || + !sqstring_t(_SC("sqdbg")).IsEqualTo( SQStringFromSQChar( sourcename ) ) )) + { + m_pPausedThread = NULL; + + if ( type == SQ_HOOK_LINE && (ci->_ip-1)->_arg1 == 0 ) + ci->_ip--; + + RestoreCachedInstructions(); + ClearCachedInstructions(); + +#ifndef SQDBG_DISABLE_PROFILER + if ( type == SQ_HOOK_CALL ) + { + if ( IsProfilerEnabled() && m_pProfiler && m_pProfiler->IsActive() ) + { + SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); + bool bGenerator = ( func->_bgenerator && ci->_ip == func->_instructions ); + + if ( !bGenerator || ( ci != vm->_callsstack && ((ci-1)->_ip-1)->op == _OP_RESUME ) ) + { + m_pProfiler->CallBegin( func ); + } + } + } +#endif + + Break( vm, breakreason_t::Pause ); + + if ( m_State == ThreadState_SuspendNow ) + Suspend(); + +#if SQUIRREL_VERSION_NUMBER < 300 + m_bInDebugHook = false; +#endif + return; + } + + breakreason_t breakReason; + + switch ( m_State ) + { + case ThreadState_Running: + break; + + case ThreadState_StepOver: + { + if ( m_nCalls == m_nStateCalls ) + { + switch ( type ) + { + case SQ_HOOK_LINE: + { + breakReason.reason = breakreason_t::Step; + break; + } + case SQ_HOOK_RETURN: + { + m_nStateCalls--; + break; + } + } + } + + break; + } + case ThreadState_StepIn: + { + if ( type == SQ_HOOK_LINE ) + { + breakReason.reason = breakreason_t::Step; + } + + break; + } + case ThreadState_StepOut: + { + if ( type == SQ_HOOK_RETURN && + m_nCalls == m_nStateCalls && + vm == m_pStateVM ) + { + m_State = ThreadState_NextStatement; + } + + break; + } + case ThreadState_NextStatement: + { + breakReason.reason = breakreason_t::Step; + break; + } + case ThreadState_StepOverInstruction: + { + if ( m_nCalls == m_nStateCalls ) + { + switch ( type ) + { + case SQ_HOOK_LINE: + { + if ( (ci->_ip-1)->_arg1 != 0 ) + { +#if SQUIRREL_VERSION_NUMBER < 300 + // Exiting trap + if ( ci->_ip - 3 >= _fp(_closure(ci->_closure)->_function)->_instructions && + (ci->_ip-2)->op == _OP_JMP && (ci->_ip-3)->op == _OP_POPTRAP ) + { + if ( InstructionStep( vm, ci, 1 ) ) + { + m_State = ThreadState_StepInInstruction; + } + else + { + m_State = ThreadState_NextStatement; + } + } +#endif + + break; + } + + breakReason.reason = breakreason_t::Step; + + RestoreCachedInstructions(); + ClearCachedInstructions(); + ci->_ip--; + + break; + } + case SQ_HOOK_CALL: + { + break; + } + case SQ_HOOK_RETURN: + { + RestoreCachedInstructions(); + ClearCachedInstructions(); + + if ( ci != vm->_callsstack ) + { + if ( InstructionStep( vm, ci - 1, 1 ) ) + { + m_State = ThreadState_StepInInstruction; + } + else + { + m_State = ThreadState_NextStatement; + } + } + + break; + } + default: UNREACHABLE(); + } + } + + break; + } + case ThreadState_StepInInstruction: + { + switch ( type ) + { + case SQ_HOOK_LINE: + { + if ( (ci->_ip-1)->_arg1 != 0 ) + { +#if SQUIRREL_VERSION_NUMBER < 300 + // Exiting trap + if ( ci->_ip - 3 >= _fp(_closure(ci->_closure)->_function)->_instructions && + (ci->_ip-2)->op == _OP_JMP && (ci->_ip-3)->op == _OP_POPTRAP ) + { + if ( InstructionStep( vm, ci, 1 ) ) + { + m_State = ThreadState_StepInInstruction; + } + else + { + m_State = ThreadState_NextStatement; + } + } +#endif + + break; + } + + breakReason.reason = breakreason_t::Step; + + RestoreCachedInstructions(); + ClearCachedInstructions(); + ci->_ip--; + + break; + } + case SQ_HOOK_CALL: + { + RestoreCachedInstructions(); + ClearCachedInstructions(); + + if ( ci->_ip->op == _OP_LINE ) + { + if ( InstructionStep( vm, ci ) ) + { + m_State = ThreadState_StepInInstruction; + } + else + { + m_State = ThreadState_NextStatement; + } + } + else + { + breakReason.reason = breakreason_t::Step; + } + + break; + } + case SQ_HOOK_RETURN: + { + RestoreCachedInstructions(); + ClearCachedInstructions(); + + if ( ci != vm->_callsstack ) + { + if ( InstructionStep( vm, ci - 1, 1 ) ) + { + m_State = ThreadState_StepInInstruction; + } + else + { + m_State = ThreadState_NextStatement; + } + } + + break; + } + default: UNREACHABLE(); + } + + break; + } + case ThreadState_StepOutInstruction: + { + if ( type == SQ_HOOK_LINE && + m_nCalls < m_nStateCalls && + vm == m_pStateVM ) + { + if ( (ci->_ip-1)->_arg1 != 0 ) + break; + + breakReason.reason = breakreason_t::Step; + + RestoreCachedInstructions(); + ClearCachedInstructions(); + ci->_ip--; + } + + break; + } + default: break; + } + + switch ( type ) + { + case SQ_HOOK_LINE: + { + if ( !sourcename ) + break; + + unsigned int srclen = SQStringFromSQChar( sourcename )->_len; + Assert( scstrlen(sourcename) == srclen ); +#ifdef SQDBG_SOURCENAME_HAS_PATH + StripFileName( &sourcename, &srclen ); +#endif + sqstring_t src; + src.Assign( sourcename, srclen ); + + breakpoint_t *bp = GetBreakpoint( line, src ); + + if ( bp ) + { + if ( HasCondition( bp ) ) + { + // Breakpoint condition function call can reallocate call stack + int frame = ci - vm->_callsstack; + + if ( !CheckBreakpointCondition( bp, vm, ci ) ) + { + ci = vm->_callsstack + frame; + break; + } + + ci = vm->_callsstack + frame; + } + + ++bp->hits; + + if ( bp->hitsTarget ) + { + if ( bp->hits < bp->hitsTarget ) + break; + + bp->hits = 0; + } + + if ( bp->logMessage.IsEmpty() ) + { + stringbuf_t< 64 > buf; + buf.Puts( "(sqdbg) Breakpoint hit " ); + buf.Puts( src ); + buf.Put(':'); + buf.PutInt( line ); + buf.Put('\n'); + buf.Term(); + + _OutputDebugStringA( buf.ptr ); + SendEvent_OutputStdOut( string_t( buf ), ci ); + breakReason.reason = breakreason_t::Breakpoint; + breakReason.id = bp->id; + } + else + { + // Use frame index instead of ci, variable evaluation can reallocate call stack + TracePoint( bp, vm, ci - vm->_callsstack ); + } + } + + break; + } + case SQ_HOOK_CALL: + { + m_nCalls++; + + sqstring_t func, src; + + if ( funcname ) + { + int funclen = SQStringFromSQChar( funcname )->_len; + Assert( (int)scstrlen(funcname) == funclen ); + func.Assign( funcname, funclen ); + } + else + { + func.Assign( _SC("") ); + } + + if ( sourcename ) + { + unsigned int srclen = SQStringFromSQChar( sourcename )->_len; + Assert( scstrlen(sourcename) == srclen ); +#ifdef SQDBG_SOURCENAME_HAS_PATH + StripFileName( &sourcename, &srclen ); +#endif + src.Assign( sourcename, srclen ); + } + else + { + src.Assign( _SC("") ); + } + +#ifdef _DEBUG + { + SQFunctionProto *pFunc = _fp(_closure(ci->_closure)->_function); + bool bGenerator = ( pFunc->_bgenerator && + ci->_ip != pFunc->_instructions && (ci->_ip-1)->op == _OP_YIELD ); + int decline = GetFunctionDeclarationLine( pFunc ); + AssertMsg2( line == decline || bGenerator, "unexpected func line %d != %d", (int)line, decline ); + } +#endif + + breakpoint_t *bp = GetFunctionBreakpoint( func, src, line ); + + if ( bp ) + { + if ( HasCondition( bp ) ) + { + // Breakpoint condition function call can reallocate call stack + int frame = ci - vm->_callsstack; + + if ( !CheckBreakpointCondition( bp, vm, ci ) ) + { + ci = vm->_callsstack + frame; + break; + } + + ci = vm->_callsstack + frame; + } + + if ( bp->hitsTarget ) + { + if ( ++bp->hits < bp->hitsTarget ) + break; + + bp->hits = 0; + } + + stringbuf_t< 128 > buf; + + if ( funcname ) + { + if ( !src.IsEmpty() ) + { + buf.Puts( "(sqdbg) Breakpoint hit " ); + buf.Puts( func ); + buf.Puts( "() @ " ); + buf.Puts( src ); + + if ( line ) + { + buf.Put(':'); + buf.PutInt( line ); + } + + buf.Put('\n'); + buf.Term(); + } + else + { + buf.Puts( "(sqdbg) Breakpoint hit " ); + buf.Puts( func ); + buf.Puts( "()\n" ); + buf.Term(); + } + } + else + { + if ( !src.IsEmpty() ) + { + buf.Puts( "(sqdbg) Breakpoint hit 'anonymous function' @ " ); + buf.Puts( src ); + + if ( line ) + { + buf.Put(':'); + buf.PutInt( line ); + } + + buf.Put('\n'); + buf.Term(); + } + else + { + buf.Puts( "(sqdbg) Breakpoint hit 'anonymous function'\n" ); + buf.Term(); + } + } + + _OutputDebugStringA( buf.ptr ); + SendEvent_OutputStdOut( string_t( buf ), ci ); + breakReason.reason = breakreason_t::FunctionBreakpoint; + breakReason.id = bp->id; + } + +#ifndef SQDBG_DISABLE_PROFILER + if ( IsProfilerEnabled() && m_pProfiler && m_pProfiler->IsActive() && + // Ignore repl + !src.IsEqualTo(_SC("sqdbg")) ) + { + SQFunctionProto *pFunc = _fp(_closure(ci->_closure)->_function); + bool bGenerator = ( pFunc->_bgenerator && ci->_ip == pFunc->_instructions ); + + if ( !bGenerator || ( ci != vm->_callsstack && ((ci-1)->_ip-1)->op == _OP_RESUME ) ) + { + m_pProfiler->CallBegin( pFunc ); + } + } +#endif + break; + } + case SQ_HOOK_RETURN: + { +#ifndef SQDBG_DISABLE_PROFILER + if ( IsProfilerEnabled() && m_pProfiler && m_pProfiler->IsActive() && + // Ignore repl + ( !sourcename || + !sqstring_t(_SC("sqdbg")).IsEqualTo( SQStringFromSQChar( sourcename ) ) ) ) + { + SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); + bool bGenerator = ( func->_bgenerator && ci->_ip == func->_instructions ); + + if ( !bGenerator ) + { + m_pProfiler->CallEnd(); + } + } +#endif + + m_nCalls--; + + if ( m_State != ThreadState_Running && + ci == vm->_callsstack && + vm == m_pRootVM ) // if exiting thread, step into root + { + Continue( vm ); + } + else if ( ( m_State == ThreadState_StepOver && m_nCalls <= m_nStateCalls ) || + ( m_State == ThreadState_StepOverInstruction && m_nCalls <= m_nStateCalls ) || + ( m_State == ThreadState_NextStatement && m_nCalls+1 <= m_nStateCalls ) || // StepOut + ( m_State == ThreadState_StepOutInstruction && m_nCalls+1 <= m_nStateCalls ) || + ( m_State == ThreadState_StepIn || m_State == ThreadState_StepInInstruction ) ) + { + SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); + bool bGenerator = ( func->_bgenerator && ci->_ip == func->_instructions ); + + if ( !bGenerator && + ( (ci->_ip-1)->op == _OP_RETURN || (ci->_ip-1)->op == _OP_YIELD ) && + (ci->_ip-1)->_arg0 != 0xFF ) + { +#ifdef NATIVE_DEBUG_HOOK + int index = vm->_stackbase + (ci->_ip-1)->_arg1; +#else + int index = vm->_stackbase - vm->ci->_prevstkbase + (ci->_ip-1)->_arg1; +#endif + const SQObjectPtr &val = vm->_stack._vals[ index ]; + + if ( !m_ReturnValues.size() || + !IsEqual( m_ReturnValues.top().value, val ) ) + { + returnvalue_t &rv = m_ReturnValues.append(); + rv.value = val; + + if ( funcname ) + { + rv.funcname = SQStringFromSQChar( funcname ); + __ObjAddRef( rv.funcname ); + } + else + { + rv.funcname = NULL; + rv.funcptr = (uintptr_t)func; + } + + if ( (ci->_ip-1)->op == _OP_YIELD && + // Keep track of yields up to 32 times at once + m_ReturnValues.size() < ( sizeof(m_iYieldValues) << 3 ) ) + { + m_iYieldValues |= 1 << m_ReturnValues.size(); + } + } + } + } + + break; + } + default: UNREACHABLE(); + } + + // NOTE: CMP metamethod function call can reallocate call stack + if ( !( m_DataWatches.size() && CheckDataBreakpoints( vm ) ) ) + { + if ( breakReason.reason ) + Break( vm, breakReason ); + } + + if ( m_State == ThreadState_SuspendNow ) + Suspend(); + +#if SQUIRREL_VERSION_NUMBER < 300 + m_bInDebugHook = false; +#endif +} + +#ifndef SQDBG_DISABLE_PROFILER +void SQDebugServer::ProfHook( HSQUIRRELVM vm, SQInteger type ) +{ + Assert( !IsClientConnected() ); + + if ( m_pCurVM != vm ) + { + m_pCurVM = vm; + ProfSwitchThread( vm ); + } + + if ( m_pProfiler && m_pProfiler->IsActive() ) + { +#ifdef NATIVE_DEBUG_HOOK + const SQVM::CallInfo *ci = vm->ci; +#else + const SQVM::CallInfo *ci = vm->ci - 1; +#endif + + SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); + bool bGenerator = ( func->_bgenerator && ci->_ip == func->_instructions ); + + switch ( type ) + { + case SQ_HOOK_CALL: + { + if ( !bGenerator || ( ci != vm->_callsstack && ((ci-1)->_ip-1)->op == _OP_RESUME ) ) + { + m_pProfiler->CallBegin( func ); + } + + break; + } + case SQ_HOOK_RETURN: + { + if ( !bGenerator ) + { + m_pProfiler->CallEnd(); + } + + break; + } + default: UNREACHABLE(); + } + } +} +#endif + +template < typename T > +void SQDebugServer::SendEvent_OutputStdOut( const T &strOutput, const SQVM::CallInfo *ci ) +{ + Assert( !ci || sq_type(ci->_closure) == OT_CLOSURE ); + + if ( IsClientConnected() ) + { + DAP_START_EVENT( ++m_Sequence, "output" ); + DAP_SET_TABLE( body ); + body.SetString( "category", "stdout" ); + body.SetString( "output", strOutput ); + if ( ci ) + { + SQFunctionProto *func = _fp(_closure(ci->_closure)->_function); + if ( !sqstring_t(_SC("sqdbg")).IsEqualTo( _string(func->_sourcename) ) ) + { + body.SetInt( "line", (int)func->GetLine( ci->_ip ) ); + wjson_table_t source = body.SetTable( "source" ); + SetSource( source, _string(func->_sourcename) ); + } + } + DAP_SEND(); + } +} + +void SQDebugServer::OnSQPrint( HSQUIRRELVM vm, const SQChar *buf, int len ) +{ + m_Print( vm, buf ); + + const SQVM::CallInfo *ci = NULL; + + // Assume the latest script function is the output source + for ( int i = vm->_callsstacksize; i-- > 0; ) + { + if ( sq_type(vm->_callsstack[i]._closure) == OT_CLOSURE ) + { + ci = vm->_callsstack + i; + break; + } + } + + SendEvent_OutputStdOut( sqstring_t( buf, len ), ci ); +} + +void SQDebugServer::OnSQError( HSQUIRRELVM vm, const SQChar *buf, int len ) +{ + m_PrintError( vm, buf ); + + const SQVM::CallInfo *ci = NULL; + + // Assume the latest script function is the output source + for ( int i = vm->_callsstacksize; i-- > 0; ) + { + if ( sq_type(vm->_callsstack[i]._closure) == OT_CLOSURE ) + { + ci = vm->_callsstack + i; + break; + } + } + + SendEvent_OutputStdOut( sqstring_t( buf, len ), ci ); +} + + +static inline HSQDEBUGSERVER sqdbg_get( HSQUIRRELVM vm ); +static inline HSQDEBUGSERVER sqdbg_get_debugger( HSQUIRRELVM vm ); +#ifdef NATIVE_DEBUG_HOOK +#ifdef DEBUG_HOOK_CACHED_SQDBG +static inline HSQDEBUGSERVER sqdbg_get_debugger_cached_debughook( HSQUIRRELVM vm ); +#else +#define sqdbg_get_debugger_cached_debughook sqdbg_get_debugger +#endif +#endif + +SQInteger SQDebugServer::SQDefineClass( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg ) + { + HSQOBJECT target; + HSQOBJECT params; + + sq_getstackobj( vm, -2, &target ); + sq_getstackobj( vm, -1, ¶ms ); + + Assert( sq_type(target) == OT_CLASS && sq_type(params) == OT_TABLE ); + + dbg->DefineClass( _class(target), _table(params) ); + } + + return 0; +} + +SQInteger SQDebugServer::SQPrintDisassembly( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg ) + { + HSQOBJECT target; + sq_getstackobj( vm, -1, &target ); + + if ( sq_type(target) != OT_CLOSURE ) + return sq_throwerror( vm, _SC("expected closure") ); + + int buflen = dbg->DisassemblyBufLen( _closure(target) ); + // NOTE: Both sqdbg and sq scratch pads are reused within this function call + SQChar *scratch = (SQChar*)sqdbg_malloc( sq_rsl(buflen) ); + AssertOOM( scratch, sq_rsl(buflen) ); + + if ( !scratch ) + { + sq_pushstring( vm, _SC(STR_NOMEM), STRLEN(STR_NOMEM) ); + return 1; + } + + sqstring_t str = dbg->PrintDisassembly( _closure(target), scratch, sq_rsl(buflen) ); + sq_pushstring( vm, str.ptr, str.len ); + + sqdbg_free( scratch, sq_rsl(buflen) ); + return 1; + } + + return 0; +} + +#ifndef SQDBG_DISABLE_PROFILER +SQInteger SQDebugServer::SQProfStart( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg && !dbg->IsProfilerEnabled() ) + { + dbg->ProfStart(); + } + + return 0; +} + +SQInteger SQDebugServer::SQProfStop( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg && dbg->IsProfilerEnabled() ) + { + dbg->ProfStop(); + } + + return 0; +} + +SQInteger SQDebugServer::SQProfPause( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg && dbg->IsProfilerEnabled() ) + { + dbg->ProfPause( vm ); + } + + return 0; +} + +SQInteger SQDebugServer::SQProfResume( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg && dbg->IsProfilerEnabled() ) + { + dbg->ProfResume( vm ); + } + + return 0; +} + +SQInteger SQDebugServer::SQProfReset( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg && dbg->IsProfilerEnabled() ) + { + HSQUIRRELVM thread = vm; + SQString *tag = NULL; + + HSQOBJECT arg1; + sq_resetobject( &arg1 ); + + SQInteger top = sq_gettop( vm ); + + if ( top > 3 ) + return sq_throwerror( vm, _SC("wrong number of parameters") ); + + if ( top > 2 ) + { + sq_getstackobj( vm, -2, &arg1 ); + + if ( sq_type(arg1) != OT_THREAD ) + return sq_throwerror( vm, _SC("expected thread") ); + + thread = _thread(arg1); + sq_resetobject( &arg1 ); + } + + sq_getstackobj( vm, -1, &arg1 ); + + switch ( sq_type(arg1) ) + { + case OT_THREAD: + thread = _thread(arg1); + break; + case OT_STRING: + tag = _string(arg1); + break; + default: + Assert(!"UNREACHABLE"); + break; + } + + dbg->ProfReset( thread, tag ); + } + + return 0; +} + +SQInteger SQDebugServer::SQProfGroupBegin( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg && dbg->IsProfilerEnabled() ) + { + HSQOBJECT tag; + sq_getstackobj( vm, -1, &tag ); + Assert( sq_type(tag) == OT_STRING ); + dbg->ProfGroupBegin( vm, _string(tag) ); + } + + return 0; +} + +SQInteger SQDebugServer::SQProfGroupEnd( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg && dbg->IsProfilerEnabled() ) + { + dbg->ProfGroupEnd( vm ); + } + + return 0; +} + +SQInteger SQDebugServer::SQProfGets( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg && dbg->IsProfilerEnabled() ) + { + HSQUIRRELVM thread = vm; + + HSQOBJECT arg1; + sq_resetobject( &arg1 ); + + SQInteger top = sq_gettop( vm ); + + if ( top > 3 ) + return sq_throwerror( vm, _SC("wrong number of parameters") ); + + if ( top > 2 ) + { + sq_getstackobj( vm, -2, &arg1 ); + + if ( sq_type(arg1) != OT_THREAD ) + return sq_throwerror( vm, _SC("expected thread") ); + + thread = _thread(arg1); + sq_resetobject( &arg1 ); + } + + sq_getstackobj( vm, -1, &arg1 ); + + sqstring_t str; + + switch ( sq_type(arg1) ) + { + case OT_STRING: + { + str = dbg->ProfGets( thread, _string(arg1), 0 ); + break; + } + case OT_INTEGER: + { + str = dbg->ProfGets( thread, NULL, _integer(arg1) ); + break; + } + default: + { + return sq_throwerror( vm, _SC("expected report type (integer) or group name (string)") ); + } + } + + if ( str.len ) + { + sq_pushstring( vm, str.ptr, str.len ); + return 1; + } + } + + return 0; +} + +SQInteger SQDebugServer::SQProfPrint( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg && dbg->IsProfilerEnabled() ) + { + HSQUIRRELVM thread = vm; + + HSQOBJECT arg1; + sq_resetobject( &arg1 ); + + SQInteger top = sq_gettop( vm ); + + if ( top > 3 ) + return sq_throwerror( vm, _SC("wrong number of parameters") ); + + if ( top > 2 ) + { + sq_getstackobj( vm, -2, &arg1 ); + + if ( sq_type(arg1) != OT_THREAD ) + return sq_throwerror( vm, _SC("expected thread") ); + + thread = _thread(arg1); + sq_resetobject( &arg1 ); + } + + sq_getstackobj( vm, -1, &arg1 ); + + switch ( sq_type(arg1) ) + { + case OT_STRING: + { + dbg->ProfPrint( thread, _string(arg1), 0 ); + break; + } + case OT_INTEGER: + { + dbg->ProfPrint( thread, NULL, _integer(arg1) ); + break; + } + default: + { + return sq_throwerror( vm, _SC("expected report type (integer) or group name (string)") ); + } + } + } + + return 0; +} +#endif + +SQInteger SQDebugServer::SQBreak( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg && dbg->IsClientConnected() ) + { + if ( dbg->m_State != ThreadState_Suspended && + ( !dbg->m_bDebugHookGuard || !dbg->m_bInREPL ) ) + { + dbg->m_pPausedThread = vm; + dbg->InstructionStep( vm, vm->ci - 1, 1 ); + } + } + + return 0; +} + +#ifndef SQDBG_DISABLE_COMPILER +SQInteger SQDebugServer::SQAddDataBreakpoint( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg && dbg->IsClientConnected() ) + { + HSQOBJECT expression, condition, hits; + sq_resetobject( &condition ); + sq_resetobject( &hits ); + sq_getstackobj( vm, 2, &expression ); + Assert( sq_type(expression) == OT_STRING ); + + SQInteger top = sq_gettop( vm ); + + if ( top > 4 ) + return sq_throwerror( vm, _SC("wrong number of parameters") ); + + if ( top > 2 ) + { + sq_getstackobj( vm, 3, &condition ); + Assert( sq_type(condition) == OT_STRING ); + + if ( top > 3 ) + { + sq_getstackobj( vm, 4, &hits ); + Assert( sq_type(hits) == OT_INTEGER ); + } + else + { + hits._type = OT_INTEGER; + hits._unVal.nInteger = 0; + } + } + + if ( _string(expression)->_len > (SQInteger)MAX_DATA_WATCH_NAME_LENGTH ) + return sq_throwerror( vm, _SC("name is too long") ); + + stringbuf_t< MAX_DATA_WATCH_BUF_SIZE > bufId; + bufId.Put('0'); + bufId.Put(':'); + bufId.Puts( _string(expression) ); + + stringbuf_t< MAX_DATA_WATCH_BUF_SIZE > cond; + + if ( sq_type(condition) == OT_STRING ) + { + if ( _string(condition)->_len > (SQInteger)MAX_DATA_WATCH_BUF_SIZE ) + return sq_throwerror( vm, _SC("condition is too long") ); + + cond.Puts( _string(condition) ); + } + + Assert( vm->ci > vm->_callsstack && + sq_type(vm->ci->_closure) == OT_NATIVECLOSURE && + _nativeclosure(vm->ci->_closure)->_function == &SQDebugServer::SQAddDataBreakpoint ); + + int repl = dbg->m_bInREPL && + vm->ci - 1 > vm->_callsstack && + sq_type((vm->ci-1)->_closure) == OT_CLOSURE && + sqstring_t(_SC("sqdbg")).IsEqualTo( + _string(_fp(_closure((vm->ci-1)->_closure)->_function)->_sourcename) ); + + int id = dbg->AddDataBreakpoint( vm, vm->ci - 1 - repl, bufId, cond, _integer(hits) ); + + if ( ISVALID_ID(id) ) + { + // TODO: Send new breakpoint event when the protocol supports it + } + else if ( id != DUPLICATE_ID ) + { + return -1; + } + } + + return 0; +} +#endif + +#ifndef SQDBG_PRINTBUF_SIZE +#define SQDBG_PRINTBUF_SIZE 2048 +#endif + +void SQDebugServer::SQPrint( HSQUIRRELVM vm, const SQChar *fmt, ... ) +{ + SQDebugServer *dbg = sqdbg_get_debugger( vm ); + Assert( dbg && dbg->IsClientConnected() ); + if ( dbg ) + { + SQChar buf[ SQDBG_PRINTBUF_SIZE ]; + va_list va; + va_start( va, fmt ); + int len = scvsprintf( buf, SQDBG_PRINTBUF_SIZE, fmt, va ); + va_end( va ); + + if ( len < 0 || len > SQDBG_PRINTBUF_SIZE-1 ) + len = SQDBG_PRINTBUF_SIZE-1; + + _OutputDebugString( buf ); + dbg->OnSQPrint( vm, buf, len ); + } +} + +void SQDebugServer::SQError( HSQUIRRELVM vm, const SQChar *fmt, ... ) +{ + SQDebugServer *dbg = sqdbg_get_debugger( vm ); + Assert( dbg && dbg->IsClientConnected() ); + if ( dbg ) + { + SQChar buf[ SQDBG_PRINTBUF_SIZE ]; + va_list va; + va_start( va, fmt ); + int len = scvsprintf( buf, SQDBG_PRINTBUF_SIZE, fmt, va ); + va_end( va ); + + if ( len < 0 || len > SQDBG_PRINTBUF_SIZE-1 ) + len = SQDBG_PRINTBUF_SIZE-1; + + _OutputDebugString( buf ); + dbg->OnSQError( vm, buf, len ); + } +} + +#ifndef SQDBG_CALL_DEFAULT_ERROR_HANDLER +void SQDebugServer::SQErrorAtFrame( HSQUIRRELVM vm, const SQVM::CallInfo *ci, const SQChar *fmt, ... ) +{ + SQDebugServer *dbg = sqdbg_get_debugger( vm ); + Assert( dbg && dbg->IsClientConnected() ); + if ( dbg ) + { + SQChar buf[ SQDBG_PRINTBUF_SIZE ]; + va_list va; + va_start( va, fmt ); + int len = scvsprintf( buf, SQDBG_PRINTBUF_SIZE, fmt, va ); + va_end( va ); + + if ( len < 0 || len > SQDBG_PRINTBUF_SIZE-1 ) + len = SQDBG_PRINTBUF_SIZE-1; + + _OutputDebugString( buf ); + dbg->m_PrintError( vm, buf ); + dbg->SendEvent_OutputStdOut( sqstring_t( buf, len ), ci ); + } +} +#endif + +SQInteger SQDebugServer::SQErrorHandler( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get_debugger( vm ); + if ( dbg && dbg->IsClientConnected() ) + { + dbg->ErrorHandler( vm ); + } + + return 0; +} + +#ifdef NATIVE_DEBUG_HOOK +void SQDebugServer::SQDebugHook( HSQUIRRELVM vm, SQInteger type, + const SQChar *sourcename, SQInteger line, const SQChar *funcname ) +{ + SQDebugServer *dbg = sqdbg_get_debugger_cached_debughook( vm ); + if ( dbg ) + { + // NOTE: SetDebugHook does not set threads unknown to the debugger, + // i.e. yet uncalled threads. This causes threads that were created while + // a client was connected to call the debug hook after the client disconnects. + // Check IsClientConnected here to catch those threads. + if ( dbg->IsClientConnected() ) + { + dbg->DebugHook( vm, type, sourcename, line, funcname ); + } + else + { + dbg->DoSetDebugHook( vm, NULL ); + } + } +} +#else +SQInteger SQDebugServer::SQDebugHook( HSQUIRRELVM vm ) +{ + SQDebugServer *dbg = sqdbg_get( vm ); + if ( dbg ) + { + if ( dbg->IsClientConnected() ) + { + HSQOBJECT type; + HSQOBJECT sourcename; + HSQOBJECT line; + HSQOBJECT funcname; + + sq_getstackobj( vm, -4, &type ); + sq_getstackobj( vm, -3, &sourcename ); + sq_getstackobj( vm, -2, &line ); + sq_getstackobj( vm, -1, &funcname ); + + Assert( sq_type(type) == OT_INTEGER && + ( sq_type(sourcename) == OT_STRING || sq_type(sourcename) == OT_NULL ) && + sq_type(line) == OT_INTEGER && + ( sq_type(funcname) == OT_STRING || sq_type(funcname) == OT_NULL ) ); + + const SQChar *src = sq_type(sourcename) == OT_STRING ? _string(sourcename)->_val : NULL; + const SQChar *fun = sq_type(funcname) == OT_STRING ? _string(funcname)->_val : NULL; + + dbg->DebugHook( vm, _integer(type), src, _integer(line), fun ); + } + else + { + dbg->DoSetDebugHook( vm, NULL ); + } + } + + return 0; +} +#endif + +#ifndef SQDBG_DISABLE_PROFILER +#ifdef NATIVE_DEBUG_HOOK +void SQDebugServer::SQProfHook( HSQUIRRELVM vm, SQInteger type, + const SQChar *sourcename, SQInteger, const SQChar * ) +{ + if ( type == SQ_HOOK_CALL || type == SQ_HOOK_RETURN ) + { + SQDebugServer *dbg = sqdbg_get_debugger_cached_debughook( vm ); + Assert( dbg && !dbg->IsClientConnected() ); + if ( dbg && dbg->IsProfilerEnabled() ) + { + // Rare case, client disconnected while waiting for repl response + if ( !sourcename || + !sqstring_t(_SC("sqdbg")).IsEqualTo( SQStringFromSQChar( sourcename ) ) ) + { + dbg->ProfHook( vm, type ); + } + } + } +} +#else +SQInteger SQDebugServer::SQProfHook( HSQUIRRELVM vm ) +{ + HSQOBJECT type; + sq_getstackobj( vm, -4 - 1, &type ); // -1 for debugger (sqdbg_get) + Assert( sq_type(type) == OT_INTEGER ); + + if ( _integer(type) == SQ_HOOK_CALL || _integer(type) == SQ_HOOK_RETURN ) + { + SQDebugServer *dbg = sqdbg_get( vm ); + Assert( dbg && !dbg->IsClientConnected() ); + if ( dbg && dbg->IsProfilerEnabled() ) + { + HSQOBJECT src; + sq_getstackobj( vm, -3 - 1, &src ); + // Rare case, client disconnected while waiting for repl response + if ( sq_type(src) != OT_STRING || !sqstring_t(_SC("sqdbg")).IsEqualTo( _string(src) ) ) + { + dbg->ProfHook( vm, _integer(type) ); + } + } + } + + return 0; +} +#endif +#endif + +#define SQDBG_SV_TAG "__sqdbg__" + +class CDebuggerScriptRef +{ +public: + SQDebugServer *dbg; +}; + +static SQInteger OnSQVMShutdown( SQUserPointer ppRef, SQInteger ) +{ + SQDebugServer *dbg = ((CDebuggerScriptRef*)_userdataval(*(SQObjectPtr*)ppRef))->dbg; + + if ( dbg ) + { + dbg->Shutdown(); + dbg->~SQDebugServer(); + sqdbg_free( dbg, sizeof(SQDebugServer) ); + } + + ((SQObjectPtr*)ppRef)->Null(); + + return 0; +} + +// For use within native calls from scripts +// Gets native closure outer variable +HSQDEBUGSERVER sqdbg_get( HSQUIRRELVM vm ) +{ + HSQOBJECT ref; + sq_getstackobj( vm, -1, &ref ); + Assert( sq_type(ref) == OT_USERDATA ); + sq_poptop( vm ); + return ((CDebuggerScriptRef*)_userdataval(ref))->dbg; +} + +HSQDEBUGSERVER sqdbg_get_debugger( HSQUIRRELVM vm ) +{ + // Use SQTable_Get to reduce hot path with stack operations + SQObjectPtr ref; + if ( SQTable_Get( _table(_ss(vm)->_registry), _SC(SQDBG_SV_TAG), ref ) ) + { + Assert( sq_type(ref) == OT_USERDATA ); + return ((CDebuggerScriptRef*)_userdataval(ref))->dbg; + } + + return NULL; +} + +void sqdbg_get_debugger_ref( HSQUIRRELVM vm, SQObjectPtr &ref ) +{ + SQTable_Get( _table(_ss(vm)->_registry), _SC(SQDBG_SV_TAG), ref ); + Assert( sq_type(ref) == OT_NULL || sq_type(ref) == OT_USERDATA ); +} + +#ifdef DEBUG_HOOK_CACHED_SQDBG +// Cache the debugger in an unused variable in the VM +// for at least 20% faster access on debug hook +// compared to registry table access +void sqdbg_set_debugger_cached_debughook( HSQUIRRELVM vm, bool state ) +{ + if ( state ) + { + SQObjectPtr ref; + sqdbg_get_debugger_ref( vm, ref ); + + vm->_debughook_closure = ref; + } + else Assert( sq_type(vm->_debughook_closure) == OT_NULL ); +} + +HSQDEBUGSERVER sqdbg_get_debugger_cached_debughook( HSQUIRRELVM vm ) +{ + Assert( sq_type(vm->_debughook_closure) == OT_USERDATA ); + return ((CDebuggerScriptRef*)_userdataval(vm->_debughook_closure))->dbg; +} +#endif + +HSQDEBUGSERVER sqdbg_attach_debugger( HSQUIRRELVM vm ) +{ + CDebuggerScriptRef *ref = NULL; + + CStackCheck stackcheck( vm ); + + sq_pushregistrytable( vm ); + sq_pushstring( vm, _SC(SQDBG_SV_TAG), -1 ); + + if ( SQ_SUCCEEDED( sq_get( vm, -2 ) ) ) + { + HSQOBJECT o; + sq_getstackobj( vm, -1, &o ); + Assert( sq_type(o) == OT_USERDATA ); + sq_pop( vm, 2 ); + + ref = (CDebuggerScriptRef*)_userdataval(o); + + if ( ref->dbg ) + return ref->dbg; + } + + if ( !ref ) + { + // Referenced by script functions and the registry + sq_pushstring( vm, _SC(SQDBG_SV_TAG), -1 ); + ref = (CDebuggerScriptRef*)sq_newuserdata( vm, sizeof(CDebuggerScriptRef) ); + sq_newslot( vm, -3, SQFalse ); + + // Only referenced by the registry + sq_pushstring( vm, _SC(SQDBG_SV_TAG "*"), -1 ); + // + // NOTE: ref can be freed while shutdown is in progress + // through the release of references to the root table + // in the debugger releasing the final references to the + // debugger reference in script functions. + // Using SQObjectPtr here to delay the release of the debugger ref, + // otherwise this could be a pointer to CDebuggerScriptRef. + // This issue practically only happens on SQ2 + // + // The release hook isn't set on the debugger ref itself + // because of its references on script functions causing + // its release hook to be called after the VM was released, + // which debugger shutdown requires for sq_release API calls + // although it doesn't require the VM but the SS. + // An alternative would be to guard sq api calls in debugger shutdown. + // + SQObjectPtr *ppRef = (SQObjectPtr*)sq_newuserdata( vm, sizeof(SQObjectPtr) ); + memzero( ppRef ); + sq_setreleasehook( vm, -1, &OnSQVMShutdown ); + sq_newslot( vm, -3, SQFalse ); + + SQObjectPtr o; + sqdbg_get_debugger_ref( vm, o ); + *ppRef = o; + + sq_poptop( vm ); + } + + ref->dbg = (SQDebugServer*)sqdbg_malloc( sizeof(SQDebugServer) ); + Assert( ref->dbg ); + new ( ref->dbg ) SQDebugServer(); + + ref->dbg->Attach( vm ); + + return ref->dbg; +} + +void sqdbg_destroy_debugger( HSQUIRRELVM vm ) +{ + CStackCheck stackcheck( vm ); + + sq_pushregistrytable( vm ); + sq_pushstring( vm, _SC(SQDBG_SV_TAG), -1 ); + + if ( SQ_SUCCEEDED( sq_get( vm, -2 ) ) ) + { + HSQOBJECT o; + sq_getstackobj( vm, -1, &o ); + Assert( sq_type(o) == OT_USERDATA ); + + CDebuggerScriptRef *ref = (CDebuggerScriptRef*)_userdataval(o); + + if ( ref->dbg ) + { + ref->dbg->Shutdown(); + ref->dbg->~SQDebugServer(); + sqdbg_free( ref->dbg, sizeof(SQDebugServer) ); + ref->dbg = NULL; + } + + sq_poptop( vm ); + } + + sq_poptop( vm ); +} + +int sqdbg_listen_socket( HSQDEBUGSERVER dbg, unsigned short port ) +{ + return ( dbg->ListenSocket( port ) == false ); +} + +void sqdbg_frame( HSQDEBUGSERVER dbg ) +{ + dbg->Frame(); +} + +void sqdbg_on_script_compile( HSQDEBUGSERVER dbg, const SQChar *script, SQInteger size, + const SQChar *sourcename, SQInteger sourcenamelen ) +{ + dbg->OnScriptCompile( script, size, sourcename, sourcenamelen ); +} diff --git a/sp/src/vscript/sqdbg/sqdbg/str.h b/sp/src/vscript/sqdbg/sqdbg/str.h new file mode 100644 index 00000000..0274159c --- /dev/null +++ b/sp/src/vscript/sqdbg/sqdbg/str.h @@ -0,0 +1,1481 @@ +//----------------------------------------------------------------------- +// github.com/samisalreadytaken/sqdbg +//----------------------------------------------------------------------- +// + +#ifndef SQDBG_STRING_H +#define SQDBG_STRING_H + +#define STRLEN(s) (sizeof(s) - 1) + +#define FMT_UINT32_LEN 10 // 4294967295 +#define FMT_PTR_LEN ( (int)sizeof(void*) * 2 + 2 ) + +#ifdef _SQ64 + #define FMT_INT_LEN 20 // -9223372036854775808 + #define FMT_OCT_LEN 23 // 01777777777777777777777 + + #if defined(_WIN32) || SQUIRREL_VERSION_NUMBER > 223 + #define FMT_INT "%lld" + #else + #define FMT_INT "%ld" + #endif +#else + #define FMT_INT_LEN 11 // -2147483648 + #define FMT_OCT_LEN 12 // 017777777777 + + #define FMT_INT "%d" +#endif + +#ifdef SQUNICODE + #define FMT_STR "%ls" + #define FMT_CSTR "%hs" + #define FMT_VCSTR "%.*hs" + #define FMT_VSTR "%.*ls" +#else + #define FMT_STR "%s" + #define FMT_CSTR "%s" + #define FMT_VCSTR "%.*s" + #define FMT_VSTR "%.*s" +#endif + +#ifdef SQUSEDOUBLE + #define FMT_FLT "%lf" + #define FMT_FLT_LEN ( 1 + DBL_MAX_10_EXP + 1 + 1 + FLT_DIG ) +#else + #define FMT_FLT "%f" + #define FMT_FLT_LEN ( 1 + FLT_MAX_10_EXP + 1 + 1 + FLT_DIG ) +#endif + +struct string_t; +#ifndef SQUNICODE +typedef string_t sqstring_t; +#else +struct sqstring_t; +#endif +struct stringbufbase_t; +template < int BUFSIZE > struct stringbuf_t; +typedef stringbufbase_t stringbufext_t; + +template < typename C, typename I > +int printint( C *buf, int size, I value ); + +template < bool padding = true, bool prefix = true, bool uppercase = true, typename C, typename I > +int printhex( C *buf, int size, I value ); + +template < typename C, typename I > +inline int printoct( C *buf, int size, I value ); + +template < typename I > +bool atoi( string_t str, I *out ); + +template < typename I > +bool atox( string_t str, I *out ); + +template < typename I > +bool atoo( string_t str, I *out ); + + +#ifdef SQUNICODE +void CopyString( const string_t &src, sqstring_t *dst ); +void CopyString( const sqstring_t &src, string_t *dst ); +#endif +template < typename T > void CopyString( const T &src, T *dst ); +template < typename T > void FreeString( T *dst ); + +#define IN_RANGE(c, min, max) \ + ((uint32_t)((uint32_t)(c) - (uint32_t)(min)) <= (uint32_t)((max)-(min))) + +#define IN_RANGE_CHAR(c, min, max) \ + ((unsigned char)((unsigned char)(c) - (unsigned char)(min)) <= (unsigned char)((max)-(min))) + +#define UTF16_NONCHAR(cp) IN_RANGE(cp, 0xFDD0, 0xFDEF) +#define UTF_NONCHAR(cp) ( ( (cp) & 0xFFFE ) == 0xFFFE ) + +// [0xD800, 0xDFFF] +#define UTF_SURROGATE(cp) ( ( (cp) & 0xFFFFF800 ) == 0x0000D800 ) + +// [0xD800, 0xDBFF] +#define UTF_SURROGATE_LEAD(cp) ( ( (cp) & 0xFFFFFC00 ) == 0x0000D800 ) + +// [0xDC00, 0xDFFF] +#define UTF_SURROGATE_TRAIL(cp) ( ( (cp) & 0xFFFFFC00 ) == 0x0000DC00 ) + +// 10xxxxxx +#define UTF8_TRAIL(c) ( ( (c) & 0xC0 ) == 0x80 ) + +// 110xxxxx 10xxxxxx +#define UTF8_2_LEAD(c) ( ( (c) & 0xE0 ) == 0xC0 ) + +// 1110xxxx 10xxxxxx 10xxxxxx +#define UTF8_3_LEAD(c) ( ( (c) & 0xF0 ) == 0xE0 ) + +// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx +#define UTF8_4_LEAD(c) ( ( (c) & 0xF8 ) == 0xF0 ) + +#define UTF8_2(len, c0, src) \ + ( (len) > 1 && UTF8_TRAIL((src)[1]) ) + +#define UTF8_3(len, c0, src) \ + ( (len) > 2 && UTF8_3_ISVALID(c0, (src)[1]) && UTF8_TRAIL((src)[2]) ) + +#define UTF8_4(len, c0, src) \ + ( (len) > 3 && UTF8_4_ISVALID(c0, (src)[1]) && UTF8_TRAIL((src)[2]) && UTF8_TRAIL((src)[3]) ) + +#define UTF8_3_ISVALID(c0, c1) \ + ( ( c0 == 0xE0 && IN_RANGE(c1, 0xA0, 0xBF) ) || \ + ( c0 == 0xED && IN_RANGE(c1, 0x80, 0x9F) ) || \ + ( UTF8_TRAIL(c1) && (IN_RANGE(c0, 0xE1, 0xEC) || IN_RANGE(c0, 0xEE, 0xEF)) ) ) + +#define UTF8_4_ISVALID(c0, c1) \ + ( ( c0 == 0xF0 && IN_RANGE(c1, 0x90, 0xBF) ) || \ + ( c0 == 0xF4 && IN_RANGE(c1, 0x80, 0x8F) ) || \ + ( UTF8_TRAIL(c1) && IN_RANGE(c0, 0xF1, 0xF3) ) ) + +#define UTF32_FROM_UTF8_2(c0, c1) \ + ( ( ( (c0) & 0x1F ) << 6 ) | \ + ( (c1) & 0x3F ) ) + +#define UTF32_FROM_UTF8_3(c0, c1, c2) \ + ( ( ( (c0) & 0x0F ) << 12 ) | \ + ( ( (c1) & 0x3F ) << 6 ) | \ + ( (c2) & 0x3F ) ) + +#define UTF32_FROM_UTF8_4(c0, c1, c2, c3) \ + ( ( ( (c0) & 0x07 ) << 18 ) | \ + ( ( (c1) & 0x3F ) << 12 ) | \ + ( ( (c2) & 0x3F ) << 6 ) | \ + ( (c3) & 0x3F ) ) + +#define UTF32_FROM_UTF16_SURROGATE(lead, trail) \ + ( ( ( ( (lead) & 0x3FF ) << 10 ) | ( (trail) & 0x3FF ) ) + 0x10000 ) + +#define UTF16_SURROGATE_FROM_UTF32(dst, cp) \ + (dst)[0] = 0xD800 | ( (cp - 0x10000) >> 10 ); \ + (dst)[1] = 0xDC00 | ( (cp - 0x10000) & 0x3FF ); + +#define UTF8_2_FROM_UTF32(mbc, cp) \ + (mbc)[0] = 0xC0 | ( (cp) >> 6 ); \ + (mbc)[1] = 0x80 | ( (cp) & 0x3F ); + +#define UTF8_3_FROM_UTF32(mbc, cp) \ + (mbc)[0] = 0xE0 | ( (cp) >> 12 ); \ + (mbc)[1] = 0x80 | ( ( (cp) >> 6 ) & 0x3F ); \ + (mbc)[2] = 0x80 | ( (cp) & 0x3F ); \ + +#define UTF8_4_FROM_UTF32(mbc, cp) \ + (mbc)[0] = 0xF0 | ( (cp) >> 18 ); \ + (mbc)[1] = 0x80 | ( ( (cp) >> 12 ) & 0x3F ); \ + (mbc)[2] = 0x80 | ( ( (cp) >> 6 ) & 0x3F ); \ + (mbc)[3] = 0x80 | ( (cp) & 0x3F ); + +typedef enum +{ + kUTFNoEscape = 0, + // Use 'x' as hex escape, escape invalid unicode + kUTFEscape = 1, + // Same as kUTFEscape, escape all backslashes as well, quote the whole input + kUTFEscapeQuoted, + // Use 'u' as hex escape + kUTFEscapeJSON, +} EUTFEscape; + +inline int IsValidUTF8( unsigned char *src, unsigned int srclen ); +#ifdef SQUNICODE +inline int IsValidUnicode( const SQChar *src, unsigned int srclen ); +template < bool undoEscape = false > +inline unsigned int UTF8ToSQUnicode( SQChar *dst, unsigned int destSize, const char *src, unsigned int srclen ); +template < EUTFEscape escape = kUTFNoEscape > +inline unsigned int SQUnicodeToUTF8( char *dst, unsigned int destSize, const SQChar *src, unsigned int srclen ); + +// Returns code unit count +template < bool undoEscape = false > +inline unsigned int SQUnicodeLength( const char *src, unsigned int srclen ) +{ + return UTF8ToSQUnicode< undoEscape >( NULL, 0, src, srclen ); +} + +// Returns byte length +template < EUTFEscape escape = kUTFNoEscape > +inline unsigned int UTF8Length( const SQChar *src, unsigned int srclen ) +{ + return SQUnicodeToUTF8< escape >( NULL, 0, src, srclen ); +} +#endif + +inline unsigned int scstombslen( const SQChar *src, unsigned int srclen ) +{ +#ifdef SQUNICODE + return UTF8Length( src, srclen ); +#else + (void)src; + return srclen; +#endif +} + +inline unsigned int scstombs( char *dst, unsigned int destSize, const SQChar *src, unsigned int srclen ) +{ +#ifdef SQUNICODE + return SQUnicodeToUTF8( dst, destSize, src, srclen ); +#else + unsigned int len = min( srclen, destSize ); + memcpy( dst, src, len ); + return len; +#endif +} + +#define STR_EXPAND(s) (s).len, (s).ptr + +struct string_t +{ + char *ptr; + unsigned int len; + + string_t() {} + + string_t( const char *src, unsigned int size ) : + ptr((char*)src), + len(size) + { + } + + string_t( const stringbufbase_t &src ); + +#ifndef SQUNICODE + string_t( SQString *src ) : + ptr(src->_val), + len(src->_len) + { + } +#endif + + template < int size > + string_t( const char (&src)[size] ) : + ptr((char*)src), + len(size-1) + { + // input wasn't a string literal, + // call ( src, size ) constructor instead + Assert( strlen(src) == len ); + } + + void Strip() + { + char *end = ptr + len; + + for ( char *c = ptr; c < end; c++ ) + { + if ( *c == ' ' || *c == '\t' || *c == '\n' ) + { + ptr++; + len--; + } + else + { + break; + } + } + + for ( char *c = end - 1; c >= ptr; c-- ) + { + if ( *c == ' ' || *c == '\t' || *c == '\n' ) + { + len--; + } + else + { + break; + } + } + } + + template < int size > + bool StartsWith( const char (&other)[size] ) const + { + if ( size-1 <= len && *ptr == *other ) + return !memcmp( ptr, other, size-1 ); + + return false; + } + + bool StartsWith( const string_t &other ) const + { + if ( other.len <= len && *ptr == *other.ptr ) + return !memcmp( ptr, other.ptr, other.len ); + + return false; + } + + template < int size > + bool IsEqualTo( const char (&other)[size] ) const + { + if ( size-1 == len && *ptr == *other ) + return !memcmp( ptr, other, size-1 ); + + return false; + } + + bool IsEqualTo( const char *other, unsigned int size ) const + { + if ( len == size && *ptr == *other ) + return !memcmp( ptr, other, size ); + + return false; + } + + bool IsEqualTo( const string_t &other ) const + { + if ( len == other.len && *ptr == *other.ptr ) + return !memcmp( ptr, other.ptr, len ); + + return false; + } + +#ifdef SQUNICODE + bool IsEqualTo( const sqstring_t &other ) const; +#endif + + bool IsEmpty() const + { + return !len; + } + + bool Contains( char ch ) const + { + return ( memchr( ptr, ch, len ) != NULL ); + } + + template < int size > + void Assign( const char (&src)[size] ) + { + ptr = (char*)src; + len = size - 1; + Assert( strlen(src) == len ); + } + + void Assign( const char *src, unsigned int size ) + { + ptr = (char*)src; + len = size; + } + +#ifndef SQUNICODE + void Assign( const SQString *src ) + { + ptr = (SQChar*)src->_val; + len = src->_len; + } +#endif + +private: + operator const char*(); + operator char*(); + string_t &operator=( const char *src ); +}; + +#ifdef SQUNICODE +struct sqstring_t +{ + SQChar *ptr; + unsigned int len; + + sqstring_t() {} + + sqstring_t( const SQString *src ) : + ptr((SQChar*)src->_val), + len(src->_len) + { + } + + sqstring_t( const SQChar *src, unsigned int size ) : + ptr((SQChar*)src), + len(size) + { + } + + template < int size > + sqstring_t( const SQChar (&src)[size] ) : + ptr((SQChar*)src), + len(size-1) + { + Assert( scstrlen(src) == len ); + } + + bool StartsWith( const string_t &other ) const + { + if ( other.len <= len ) + { + Assert( other.len ); + + unsigned int i = 0; + do + { + if ( ptr[i] > 0x7E || other.ptr[i] != (char)ptr[i] ) + { + AssertMsg( ptr[i] <= 0x7E, "not implemented" ); + return false; + } + } + while ( ++i < other.len ); + + return true; + } + + return false; + } + + bool IsEqualTo( const sqstring_t &other ) const + { + if ( len == other.len && *ptr == *other.ptr ) + return !memcmp( ptr, other.ptr, sq_rsl(len) ); + + return false; + } + + bool IsEqualTo( const SQString *other ) const + { + if ( len == other->_len && *ptr == *other->_val ) + return !memcmp( ptr, other->_val, sq_rsl(len) ); + + return false; + } + + bool IsEmpty() const + { + return !len; + } + + template < int size > + void Assign( const SQChar (&src)[size] ) + { + ptr = (SQChar*)src; + len = size - 1; + Assert( scstrlen(src) == len ); + } + + void Assign( const SQChar *src, unsigned int size ) + { + ptr = (SQChar*)src; + len = size; + } + + void Assign( const SQString *src ) + { + ptr = (SQChar*)src->_val; + len = src->_len; + } +}; +#endif + +struct stringbufbase_t +{ + char *ptr; + unsigned int len; + const int size; + + stringbufbase_t( char *src, unsigned int size ) : + ptr(src), + len(0), + size(size) + { + } + + stringbufbase_t( stringbufbase_t &src ) : + ptr(src.ptr), + len(src.len), + size(src.size) + { + } + + stringbufbase_t( const stringbufbase_t &src ) : + ptr(src.ptr), + len(src.len), + size(src.size) + { + } + + stringbufbase_t &operator=( const stringbufbase_t & ); + + int BufSize() + { + return size; + } + + int BytesLeft() + { + return BufSize() - len; + } + + template < int SIZE > + void Puts( const char (&psz)[SIZE] ) + { + int amt = min( BytesLeft(), (SIZE-1) ); + + memcpy( ptr + len, psz, amt ); + len += amt; + } + + void Puts( const string_t &str ) + { + Assert( str.len < INT_MAX ); + int amt = min( BytesLeft(), (int)str.len ); + + memcpy( ptr + len, str.ptr, amt ); + len += amt; + } + +#ifdef SQUNICODE + void Puts( const sqstring_t &str ) + { + len += SQUnicodeToUTF8( ptr + len, BytesLeft(), str.ptr, str.len ); + } +#endif + + void Put( char ch ) + { + Assert( len < INT_MAX ); + if ( BufSize() >= (int)( len + 1 ) ) + { + ptr[len++] = ch; + } + } + + void Term() + { + if ( (int)len > BufSize()-1 ) + len = BufSize()-1; + + ptr[len] = 0; + Assert( strlen(ptr) == len ); + } + + template < typename I > + void PutInt( I value ) + { + int space = BytesLeft(); + + if ( space < 1 ) + return; + + len += printint( ptr + len, space, value ); + } + + template < typename I > + void PutHex( I value, bool padding = true ) + { + int space = BytesLeft(); + + if ( space < 3 ) + return; + + if ( padding ) + { + len += printhex< true >( ptr + len, space, value ); + } + else + { + len += printhex< false >( ptr + len, space, value ); + } + } +}; + +string_t::string_t( const stringbufbase_t &src ) : + ptr(src.ptr), + len(src.len) +{ +} + +#ifdef SQUNICODE +bool string_t::IsEqualTo( const sqstring_t &other ) const +{ + if ( len == other.len ) + { + Assert( len ); + + unsigned int i = 0; + do + { + // Used for comparing against locals and outers, + // implement unicode conversion if locals can have unicode characters + if ( other.ptr[i] > 0x7E || (char)other.ptr[i] != ptr[i] ) + { + AssertMsg( other.ptr[i] <= 0x7E, "not implemented" ); + return false; + } + } + while ( ++i < len ); + + return true; + } + + return false; +} +#endif + +template < int BUFSIZE > +struct stringbuf_t : stringbufbase_t +{ + char ptr[BUFSIZE]; + + stringbuf_t() : stringbufbase_t( ptr, BUFSIZE ) + { + } +}; + +#define _isdigit( c ) \ + IN_RANGE_CHAR(c, '0', '9') + +#define _isxdigit( c ) \ + ( IN_RANGE_CHAR(c, '0', '9') || \ + IN_RANGE_CHAR(c, 'A', 'F') || \ + IN_RANGE_CHAR(c, 'a', 'f') ) + +#define _isalpha( c ) \ + ( IN_RANGE_CHAR(c, 'A', 'Z') || IN_RANGE_CHAR(c, 'a', 'z') ) + +#define _isalnum( c ) \ + ( _isalpha(c) || _isdigit(c) ) + +template < int BASE = 10, typename I > +inline int countdigits( I input ) +{ + int i = 0; + + do + { + input /= BASE; + i++; + } + while ( input ); + + return i; +} + +template < typename C, typename I > +inline int printint( C *buf, int size, I value ) +{ + Assert( buf ); + Assert( size > 0 ); + + if ( !value ) + { + if ( size >= 1 ) + { + buf[0] = '0'; + return 1; + } + + return 0; + } + + bool neg; + int len; + + if ( value >= 0 ) + { + len = countdigits( value ); + neg = false; + } + else + { + value = -value; + len = countdigits( value ) + 1; + buf[0] = '-'; + + neg = ( value < 0 ); // value == INT_MIN + } + + if ( len > size ) + len = size; + + int i = len - 1; + + do + { + C c = value % 10; + value /= 10; + buf[i--] = !neg ? ( '0' + c ) : ( '0' - c ); + } + while ( value && i >= 0 ); + + return len; +} + +template < bool padding, bool prefix, bool uppercase, typename C, typename I > +inline int printhex( C *buf, int size, I value ) +{ + Assert( buf ); + Assert( size > 0 ); + + int len = ( prefix ? 2 : 0 ) + ( padding ? sizeof(I) * 2 : countdigits<16>( value ) ); + + if ( len > size ) + len = size; + + int i = len - 1; + + do + { + C c = value & 0xf; + value >>= 4; + buf[i--] = c + ( ( c < 10 ) ? '0' : ( ( uppercase ? 'A' : 'a' ) - 10 ) ); + } + while ( value && i >= 0 ); + + if ( padding ) + { + while ( i >= ( prefix ? 2 : 0 ) ) + buf[i--] = '0'; + } + + if ( prefix ) + { + if ( i >= 0 ) + { + buf[i--] = 'x'; + + if ( i == 0 ) + buf[i--] = '0'; + } + } + + Assert( i == -1 ); + return len; +} + +template < typename C, typename I > +inline int printoct( C *buf, int size, I value ) +{ + Assert( buf ); + Assert( size > 0 ); + + int len = countdigits<8>( value ) + 1; + + if ( len > size ) + len = size; + + int i = len - 1; + + do + { + C c = value & 0x7; + value >>= 3; + buf[i--] = '0' + c; + } + while ( value && i >= 0 ); + + if ( i >= 0 ) + buf[i--] = '0'; + + Assert( i == -1 ); + return len; +} + +template < typename I > +inline bool atoi( string_t str, I *out ) +{ + Assert( str.ptr && str.len > 0 ); + + I val = 0; + bool neg = ( *str.ptr == '-' ); + + if ( neg ) + { + str.ptr++; + str.len--; + } + + for ( ; str.len--; str.ptr++ ) + { + unsigned char ch = *str.ptr; + + if ( IN_RANGE_CHAR(ch, '0', '9') ) + { + val *= 10; + val += ch - '0'; + } + else + { + *out = 0; + return false; + } + } + + *out = !neg ? val : -val; + return true; +} + +template < typename I > +inline bool atox( string_t str, I *out ) +{ + if ( str.StartsWith("0x") ) + { + str.ptr += 2; + str.len -= 2; + } + + I val = 0; + + for ( ; str.len--; str.ptr++ ) + { + unsigned char ch = *str.ptr; + + if ( IN_RANGE_CHAR(ch, '0', '9') ) + { + val <<= 4; + val += ch - '0'; + } + else if ( IN_RANGE_CHAR(ch, 'A', 'F') ) + { + val <<= 4; + val += ch - 'A' + 10; + } + else if ( IN_RANGE_CHAR(ch, 'a', 'f') ) + { + val <<= 4; + val += ch - 'a' + 10; + } + else + { + *out = 0; + return false; + } + } + + *out = val; + return true; +} + +template < typename I > +inline bool atoo( string_t str, I *out ) +{ + I val = 0; + + for ( ; str.len--; str.ptr++ ) + { + unsigned char ch = *str.ptr; + + if ( IN_RANGE_CHAR(ch, '0', '7') ) + { + val <<= 3; + val += ch - '0'; + } + else + { + *out = 0; + return false; + } + } + + *out = val; + return true; +} + +template < typename I > +inline bool strtoint( string_t str, I *out ) +{ + if ( !str.StartsWith("0x") ) + { + return atoi( str, out ); + } + else + { + return atox( str, out ); + } +} + +// Returns byte count of valid UTF8 sequences +// Returns 0 for control characters +// Returns 0 for noncharacters +inline int IsValidUTF8( unsigned char *src, unsigned int srclen ) +{ + unsigned char cp = src[0]; + + if ( cp <= 0x7E ) + { + if ( cp >= 0x20 ) + return 1; + + return 0; + } + else if ( IN_RANGE_CHAR(cp, 0xC2, 0xF4) ) + { + if ( UTF8_2_LEAD(cp) ) + { + if ( UTF8_2( srclen, cp, src ) ) + { + return 2; + } + } + else if ( UTF8_3_LEAD(cp) ) + { + if ( UTF8_3( srclen, cp, src ) ) + { + return 3; + } + } + else if ( UTF8_4_LEAD(cp) ) + { + if ( UTF8_4( srclen, cp, src ) ) + { + return 4; + } + } + } + // Look behind + // Unused, there is no condition where strings aren't processed linearly from the start +#if 0 + else if ( UTF8_TRAIL(cp) ) + { + int lim = srcindex - 3; + if ( lim < 0 ) + lim = 0; + + while ( srcindex-- > lim ) + { + cp = *(--src); + srclen++; + + if ( !UTF8_TRAIL(cp) ) + { + if ( IN_RANGE_CHAR(cp, 0xC2, 0xF4) ) + goto check; + + return 0; + } + } + } +#endif + // else [0x7F, 0xC2) & (0xF4, 0xFF] + + return 0; +} + +#ifdef SQUNICODE +// Returns code unit count for valid unicode +// Returns 0 for control characters +// Returns -1 if the invalid code unit is larger than 1 byte +// Noncharacters and private use areas are valid +inline int IsValidUnicode( const SQChar *src, unsigned int srclen ) +{ + uint32_t cp = (uint32_t)src[0]; + + if ( cp <= 0x7E ) + { + if ( cp >= 0x20 ) + return 1; + + return 0; + } + else if ( cp <= 0xFF ) + { + if ( IN_RANGE(cp, 0xC2, 0xF4) ) + { + if ( UTF8_2_LEAD(cp) ) + { + if ( UTF8_2( srclen, cp, src ) ) + { + return 2; + } + } + else if ( UTF8_3_LEAD(cp) ) + { + if ( UTF8_3( srclen, cp, src ) ) + { + return 3; + } + } + else if ( UTF8_4_LEAD(cp) ) + { + if ( UTF8_4( srclen, cp, src ) ) + { + return 4; + } + } + } + // else [0x7F, 0xC2) & (0xF4, 0xFF] + + return 0; + } + + if ( cp <= 0xFFFF ) + { + if ( UTF_SURROGATE(cp) ) + { + if ( srclen > 1 && UTF_SURROGATE_LEAD(cp) && UTF_SURROGATE_TRAIL(src[1]) ) + { + return 2; + } + + return -1; + } + + return 1; + } + else if ( cp <= 0x10FFFF ) + { + return 2; + } + else + { + return -1; + } +} + +template < bool undoEscape > +inline unsigned int UTF8ToSQUnicode( SQChar *dst, unsigned int destSize, const char *src, unsigned int srclen ) +{ + uint32_t cp; + const char *end = src + srclen; + unsigned int count = 0; + + for ( ; src < end; src++ ) + { + cp = (uint32_t)((unsigned char*)src)[0]; + + if ( cp <= 0x7E ) + { + if ( undoEscape ) + { + if ( cp == '\\' && src + 1 < end ) + { + switch ( ((unsigned char*)src)[1] ) + { + case '\"': cp = '\"'; src++; break; + case '\\': src++; break; + case 'b': cp = '\b'; src++; break; + case 'f': cp = '\f'; src++; break; + case 'n': cp = '\n'; src++; break; + case 'r': cp = '\r'; src++; break; + case 't': cp = '\t'; src++; break; + case 'x': + { + if ( src + sizeof(SQChar) * 2 + 1 < end ) + { + Verify( atox( { src + 2, sizeof(SQChar) * 2 }, &cp ) ); + src += sizeof(SQChar) * 2 + 1; + } + + break; + } + case 'u': + { + if ( src + sizeof(uint16_t) * 2 + 1 < end ) + { + Verify( atox( { src + 2, sizeof(uint16_t) * 2 }, &cp ) ); + src += sizeof(uint16_t) * 2 + 1; + } + + break; + } + } + } + } + + goto xffff; + } + else if ( IN_RANGE(cp, 0xC2, 0xF4) ) + { + if ( UTF8_2_LEAD(cp) ) + { + if ( UTF8_2( end - src, cp, (unsigned char*)src ) ) + { + cp = UTF32_FROM_UTF8_2( cp, src[1] ); + src += 1; + goto xffff; + } + } + else if ( UTF8_3_LEAD(cp) ) + { + if ( UTF8_3( end - src, cp, (unsigned char*)src ) ) + { + cp = UTF32_FROM_UTF8_3( cp, src[1], src[2] ); + src += 2; + goto xffff; + } + } + else if ( UTF8_4_LEAD(cp) ) + { + if ( UTF8_4( end - src, cp, (unsigned char*)src ) ) + { + cp = UTF32_FROM_UTF8_4( cp, src[1], src[2], src[3] ); + src += 3; + goto supplementary; + } + } + + goto xffff; + } + else // [0x7F, 0xC2) & (0xF4, 0xFF] + { + goto xffff; + } + +#if WCHAR_SIZE == 4 +xffff: +supplementary: + if ( dst ) + { + if ( destSize >= sizeof(SQChar) ) + { + *dst++ = cp; + destSize -= sizeof(SQChar); + count += 1; + } + else + { + // out of space + break; + } + } + else + { + count += 1; + } + + continue; +#else // WCHAR_SIZE == 2 +xffff: + if ( dst ) + { + if ( destSize >= sizeof(SQChar) ) + { + *dst++ = (SQChar)cp; + destSize -= sizeof(SQChar); + count += 1; + } + else + { + // out of space + break; + } + } + else + { + count += 1; + } + + continue; + +supplementary: + if ( dst ) + { + if ( destSize > sizeof(SQChar) ) + { + UTF16_SURROGATE_FROM_UTF32( dst, cp ); + dst += 2; + destSize -= 2 * sizeof(SQChar); + count += 2; + } + else + { + cp = 0xFFFD; + goto xffff; + } + } + else + { + count += 2; + } + + continue; +#endif + } + + return count; +} + +// SQUnicode can be UTF16 or UTF32 +template < EUTFEscape escape > +inline unsigned int SQUnicodeToUTF8( char *dst, unsigned int destSize, const SQChar *src, unsigned int srclen ) +{ + uint32_t cp; + const SQChar *end = src + srclen; + unsigned char mbc[ escape != 0 ? + ( escape == kUTFEscapeJSON ? + 6 : // "\u0000" + ( escape == kUTFEscapeQuoted ? + 14 : // "\\uD800\\uDC00" + // kUTFEscape + 12 ) ) : // "\uD800\uDC00" + 4 ]; + unsigned int count = 0; + unsigned int bytes; + + if ( escape == kUTFEscapeQuoted ) + { + mbc[0] = '\\'; + mbc[1] = '\"'; + bytes = 2; + + if ( dst ) + { + if ( bytes <= destSize ) + { + memcpy( dst, mbc, bytes ); + dst += bytes; + destSize -= bytes; + count += bytes; + } + else + { + // out of space + return count; + } + } + else + { + count += bytes; + } + } + + for ( ; src < end; src++ ) + { + cp = (uint32_t)src[0]; + + if ( cp <= 0xFF ) + { + if ( escape ) + { + bytes = 0; + + switch ( cp ) + { + case '\"': + if ( escape == kUTFEscapeQuoted ) + { + mbc[bytes++] = '\\'; + mbc[bytes++] = '\\'; + } + mbc[bytes++] = '\\'; + mbc[bytes++] = '\"'; + goto write; + case '\\': + if ( escape == kUTFEscapeQuoted ) + { + mbc[bytes++] = '\\'; + mbc[bytes++] = '\\'; + } + mbc[bytes++] = '\\'; + mbc[bytes++] = '\\'; + goto write; + case '\b': + if ( escape == kUTFEscapeQuoted ) + mbc[bytes++] = '\\'; + mbc[bytes++] = '\\'; + mbc[bytes++] = 'b'; + goto write; + case '\f': + if ( escape == kUTFEscapeQuoted ) + mbc[bytes++] = '\\'; + mbc[bytes++] = '\\'; + mbc[bytes++] = 'f'; + goto write; + case '\n': + if ( escape == kUTFEscapeQuoted ) + mbc[bytes++] = '\\'; + mbc[bytes++] = '\\'; + mbc[bytes++] = 'n'; + goto write; + case '\r': + if ( escape == kUTFEscapeQuoted ) + mbc[bytes++] = '\\'; + mbc[bytes++] = '\\'; + mbc[bytes++] = 'r'; + goto write; + case '\t': + if ( escape == kUTFEscapeQuoted ) + mbc[bytes++] = '\\'; + mbc[bytes++] = '\\'; + mbc[bytes++] = 't'; + goto write; + + default: + + if ( !IN_RANGE_CHAR(cp, 0x20, 0x7E) ) + { + // While UTF8 bytes are valid UTF16, converting them will + // make distinct SQ strings indistinguishable to the client +#ifndef SQDBG_ESCAPE_UTF8_BYTES_IN_UTF16 + if ( IN_RANGE(cp, 0xC2, 0xF4) ) + { + if ( UTF8_2_LEAD(cp) ) + { + if ( UTF8_2( end - src, cp, src ) ) + { + mbc[0] = (unsigned char)cp; + mbc[1] = (unsigned char)src[1]; + bytes = 2; + src += 1; + goto write; + } + } + else if ( UTF8_3_LEAD(cp) ) + { + if ( UTF8_3( end - src, cp, src ) ) + { + mbc[0] = (unsigned char)cp; + mbc[1] = (unsigned char)src[1]; + mbc[2] = (unsigned char)src[2]; + bytes = 3; + src += 2; + goto write; + } + } + else if ( UTF8_4_LEAD(cp) ) + { + if ( UTF8_4( end - src, cp, src ) ) + { + mbc[0] = (unsigned char)cp; + mbc[1] = (unsigned char)src[1]; + mbc[2] = (unsigned char)src[2]; + mbc[3] = (unsigned char)src[3]; + bytes = 4; + src += 3; + goto write; + } + } + } +#endif + // [0x7F, 0xC2) & (0xF4, 0xFF] + if ( escape == kUTFEscapeQuoted ) + mbc[bytes++] = '\\'; + + mbc[bytes++] = '\\'; + + if ( escape == kUTFEscapeJSON ) + { + mbc[bytes++] = 'u'; + bytes += printhex< true, false >( mbc + bytes, sizeof(mbc) - bytes, (uint16_t)cp ); + } + else + { + mbc[bytes++] = 'x'; + bytes += printhex< true, false >( mbc + bytes, sizeof(mbc) - bytes, (SQChar)cp ); + } + + goto write; + } + } + } + + mbc[0] = (unsigned char)cp; + bytes = 1; + } + else if ( cp <= 0x7FF ) + { + UTF8_2_FROM_UTF32( mbc, cp ); + bytes = 2; + } + else if ( cp <= 0xFFFF ) + { + if ( UTF_SURROGATE(cp) ) + { + if ( src + 1 < end && UTF_SURROGATE_LEAD(cp) && UTF_SURROGATE_TRAIL(src[1]) ) + { + cp = UTF32_FROM_UTF16_SURROGATE( cp, (uint32_t)src[1] ); + src++; + goto supplementary; + } + + if ( escape && escape != kUTFEscapeJSON ) + { + bytes = 0; + + if ( escape == kUTFEscapeQuoted ) + mbc[bytes++] = '\\'; + + mbc[bytes++] = '\\'; + mbc[bytes++] = 'x'; + bytes = bytes + printhex< true, false >( mbc + bytes, sizeof(mbc) - bytes, (SQChar)cp ); + goto write; + } + } + + UTF8_3_FROM_UTF32( mbc, cp ); + bytes = 3; + } + else + { + if ( cp > 0x10FFFF && escape && escape != kUTFEscapeJSON ) + { + // "\\uD800\\uDC00" + uint16_t s[2]; + UTF16_SURROGATE_FROM_UTF32( s, cp ); + + bytes = 0; + + if ( escape == kUTFEscapeQuoted ) + mbc[bytes++] = '\\'; + + mbc[bytes++] = '\\'; + mbc[bytes++] = 'u'; + bytes += printhex< true, false >( mbc + bytes, sizeof(mbc) - bytes, s[0] ); + + if ( escape == kUTFEscapeQuoted ) + mbc[bytes++] = '\\'; + + mbc[bytes++] = '\\'; + mbc[bytes++] = 'u'; + bytes += printhex< true, false >( mbc + bytes, sizeof(mbc) - bytes, s[1] ); + goto write; + } + +supplementary: + UTF8_4_FROM_UTF32( mbc, cp ); + bytes = 4; + } +write: + if ( dst ) + { + if ( bytes <= destSize ) + { + memcpy( dst, mbc, bytes ); + dst += bytes; + destSize -= bytes; + count += bytes; + } + else + { + // out of space + break; + } + } + else + { + count += bytes; + } + } + + if ( escape == kUTFEscapeQuoted ) + { + mbc[0] = '\\'; + mbc[1] = '\"'; + bytes = 2; + + if ( dst ) + { + if ( bytes <= destSize ) + { + memcpy( dst, mbc, bytes ); + dst += bytes; + destSize -= bytes; + count += bytes; + } + else + { + // out of space + return count; + } + } + else + { + count += bytes; + } + } + + return count; +} +#endif + +#endif // SQDBG_STRING_H diff --git a/sp/src/vscript/sqdbg/sqdbg/vec.h b/sp/src/vscript/sqdbg/sqdbg/vec.h new file mode 100644 index 00000000..22c8fe50 --- /dev/null +++ b/sp/src/vscript/sqdbg/sqdbg/vec.h @@ -0,0 +1,624 @@ +//----------------------------------------------------------------------- +// github.com/samisalreadytaken/sqdbg +//----------------------------------------------------------------------- +// + +#ifndef SQDBG_VEC_H +#define SQDBG_VEC_H + +void *sqdbg_malloc( unsigned int size ); +void *sqdbg_realloc( void *p, unsigned int oldsize, unsigned int size ); +void sqdbg_free( void *p, unsigned int size ); + +class CMemory +{ +public: +#pragma pack(push, 4) + struct memory_t + { + char *ptr; + unsigned int size; + }; +#pragma pack(pop) + + memory_t memory; + + char &operator[]( unsigned int i ) const + { + Assert( memory.size > 0 ); + return *( memory.ptr + i ); + } + + char *Base() + { + return memory.ptr; + } + + unsigned int Size() const + { + return memory.size; + } + + void Free() + { + if ( memory.ptr ) + { + sqdbg_free( memory.ptr, memory.size ); + memory.ptr = 0; + memory.size = 0; + } + } + + void Alloc( unsigned int count ) + { + Assert( count > 0 || memory.size == 0 ); + + if ( count == memory.size ) + return; + + const int size = ( count + sizeof(void*) - 1 ) & ~( sizeof(void*) - 1 ); + + if ( memory.ptr ) + { + memory.ptr = (char*)sqdbg_realloc( memory.ptr, memory.size, size ); + } + else + { + memory.ptr = (char*)sqdbg_malloc( size ); + } + + AssertOOM( memory.ptr, size ); + + if ( memory.ptr ) + { + memory.size = size; + } + else + { + memory.size = 0; + } + } + + void Ensure( unsigned int newcount ) + { + unsigned int oldcount = memory.size; + + if ( newcount <= oldcount ) + return; + + if ( oldcount != 0 ) + { + unsigned int i = (unsigned int)0x7fffffffu; + + while ( ( i >> 1 ) > newcount ) + { + i >>= 1; + } + + Assert( i > 0 ); + Assert( i > oldcount ); + Assert( i >= newcount ); + + newcount = i; + } + else + { + if ( newcount < 4 ) + newcount = 4; + } + + Alloc( newcount ); + } +}; + +template< int MEM_CACHE_CHUNKS_ALIGN = 2048 > +class CScratch +{ +public: + static const int MEM_CACHE_CHUNKSIZE = 4; + static const int INVALID_INDEX = 0x80000000; + + struct chunk_t + { + char *ptr; + int count; + }; + + chunk_t *m_Memory; + int m_MemChunkCount; + int m_LastFreeChunk; + int m_LastFreeIndex; + + char *Get( int index ) + { + Assert( index != INVALID_INDEX ); + + int msgIdx = index & 0x0000ffff; + int chunkIdx = index >> 16; + + Assert( m_Memory ); + Assert( chunkIdx < m_MemChunkCount ); + + chunk_t *chunk = &m_Memory[ chunkIdx ]; + Assert( msgIdx < chunk->count ); + + return &chunk->ptr[ msgIdx * MEM_CACHE_CHUNKSIZE ]; + } + + char *Alloc( int size, int *index = NULL, bool sequential = true ) + { + if ( !m_Memory ) + { + m_MemChunkCount = 4; + m_Memory = (chunk_t*)sqdbg_malloc( m_MemChunkCount * sizeof(chunk_t) ); + AssertOOM( m_Memory, m_MemChunkCount * sizeof(chunk_t) ); + memset( (char*)m_Memory, 0, m_MemChunkCount * sizeof(chunk_t) ); + + chunk_t *chunk = &m_Memory[0]; + chunk->count = MEM_CACHE_CHUNKS_ALIGN; + chunk->ptr = (char*)sqdbg_malloc( chunk->count * MEM_CACHE_CHUNKSIZE ); + AssertOOM( chunk->ptr, chunk->count * MEM_CACHE_CHUNKSIZE ); + memset( chunk->ptr, 0, chunk->count * MEM_CACHE_CHUNKSIZE ); + } + + int requiredChunks; + int msgIdx; + int chunkIdx; + int matchedChunks = 0; + + if ( sequential ) + { + requiredChunks = ( size - 1 ) / MEM_CACHE_CHUNKSIZE + 1; + msgIdx = m_LastFreeIndex; + chunkIdx = m_LastFreeChunk; + } + else + { + requiredChunks = ( size + sizeof(int) - 1 ) / MEM_CACHE_CHUNKSIZE + 1; + msgIdx = 0; + chunkIdx = 0; + } + + for (;;) + { + chunk_t *chunk = &m_Memory[ chunkIdx ]; + Assert( chunk->count && chunk->ptr ); + + if ( sequential ) + { + int remainingChunks = chunk->count - msgIdx; + + if ( remainingChunks >= requiredChunks ) + { + m_LastFreeIndex = msgIdx + requiredChunks; + m_LastFreeChunk = chunkIdx; + + if ( index ) + { + Assert( msgIdx < 0x0000ffff ); + Assert( chunkIdx < 0x00007fff ); + *index = ( chunkIdx << 16 ) | msgIdx; + } + + return &chunk->ptr[ msgIdx * MEM_CACHE_CHUNKSIZE ]; + } + } + else + { + char *ptr = &chunk->ptr[ msgIdx * MEM_CACHE_CHUNKSIZE ]; + Assert( *(int*)ptr >= 0 && *(int*)ptr < ( chunk->count - msgIdx ) * MEM_CACHE_CHUNKSIZE ); + + if ( *(int*)ptr == 0 ) + { + if ( ++matchedChunks == requiredChunks ) + { + msgIdx = msgIdx - matchedChunks + 1; + Assert( !index ); + ptr = &chunk->ptr[ msgIdx * MEM_CACHE_CHUNKSIZE ]; + *(int*)ptr = size; + return ptr + sizeof(int); + } + } + else + { + matchedChunks = 0; + } + + msgIdx += ( *(int*)ptr + sizeof(int) - 1 ) / MEM_CACHE_CHUNKSIZE + 1; + + Assert( msgIdx < 0x0000ffff ); + + if ( msgIdx < chunk->count ) + continue; + + matchedChunks = 0; + } + + msgIdx = 0; + + if ( ++chunkIdx >= m_MemChunkCount ) + { + int oldcount = m_MemChunkCount; + m_MemChunkCount <<= 1; + m_Memory = (chunk_t*)sqdbg_realloc( m_Memory, + oldcount * sizeof(chunk_t), + m_MemChunkCount * sizeof(chunk_t) ); + AssertOOM( m_Memory, m_MemChunkCount * sizeof(chunk_t) ); + memset( (char*)m_Memory + oldcount * sizeof(chunk_t), + 0, + (m_MemChunkCount - oldcount) * sizeof(chunk_t) ); + } + + chunk = &m_Memory[ chunkIdx ]; + + if ( chunk->count == 0 ) + { + Assert( chunk->ptr == NULL ); + + chunk->count = ( requiredChunks + ( MEM_CACHE_CHUNKS_ALIGN - 1 ) ) & ~( MEM_CACHE_CHUNKS_ALIGN - 1 ); + chunk->ptr = (char*)sqdbg_malloc( chunk->count * MEM_CACHE_CHUNKSIZE ); + AssertOOM( chunk->ptr, chunk->count * MEM_CACHE_CHUNKSIZE ); + memset( chunk->ptr, 0, chunk->count * MEM_CACHE_CHUNKSIZE ); + } + + Assert( chunkIdx < 0x00007fff ); + } + } + + void Free( void *ptr ) + { + Assert( m_Memory ); + Assert( ptr ); + + *(char**)&ptr -= sizeof(int); + +#ifdef _DEBUG + bool found = false; + + for ( int chunkIdx = 0; chunkIdx < m_MemChunkCount; chunkIdx++ ) + { + chunk_t *chunk = &m_Memory[ chunkIdx ]; + + if ( chunk->count && ptr >= chunk->ptr && ptr < chunk->ptr + chunk->count * MEM_CACHE_CHUNKSIZE ) + { + Assert( *(int*)ptr >= 0 ); + Assert( (char*)ptr + sizeof(int) + *(int*)ptr <= chunk->ptr + chunk->count * MEM_CACHE_CHUNKSIZE ); + found = true; + break; + } + } + + Assert( found ); + + (*(unsigned char**)&ptr)[ *(int*)ptr + sizeof(int) - 1 ] = 0xdd; +#endif + + memset( (char*)ptr, 0, *(int*)ptr + sizeof(int) ); + } + + void Free() + { + if ( !m_Memory ) + return; + + for ( int chunkIdx = 0; chunkIdx < m_MemChunkCount; chunkIdx++ ) + { + chunk_t *chunk = &m_Memory[ chunkIdx ]; + + if ( chunk->count ) + { + sqdbg_free( chunk->ptr, chunk->count * MEM_CACHE_CHUNKSIZE ); + } + } + + sqdbg_free( m_Memory, m_MemChunkCount * sizeof(chunk_t) ); + + m_Memory = NULL; + m_MemChunkCount = 4; + m_LastFreeChunk = 0; + m_LastFreeIndex = 0; + } + + void ReleaseShrink() + { + if ( !m_Memory ) + return; + + for ( int chunkIdx = 4; chunkIdx < m_MemChunkCount; chunkIdx++ ) + { + chunk_t *chunk = &m_Memory[ chunkIdx ]; + + if ( chunk->count ) + { + sqdbg_free( chunk->ptr, chunk->count * MEM_CACHE_CHUNKSIZE ); + + chunk->count = 0; + chunk->ptr = NULL; + } + } + +#ifdef _DEBUG + for ( int chunkIdx = 0; chunkIdx < 4; chunkIdx++ ) + { + chunk_t *chunk = &m_Memory[ chunkIdx ]; + + if ( chunk->count ) + { + memset( chunk->ptr, 0xdd, chunk->count * MEM_CACHE_CHUNKSIZE ); + } + } +#endif + + if ( m_MemChunkCount > 8 ) + { + int oldcount = m_MemChunkCount; + m_MemChunkCount = 8; + m_Memory = (chunk_t*)sqdbg_realloc( m_Memory, + oldcount * sizeof(chunk_t), + m_MemChunkCount * sizeof(chunk_t) ); + AssertOOM( m_Memory, m_MemChunkCount * sizeof(chunk_t) ); + } + + m_LastFreeChunk = 0; + m_LastFreeIndex = 0; + } + + void Release() + { + if ( !m_Memory || ( !m_LastFreeChunk && !m_LastFreeIndex ) ) + return; + +#ifdef _DEBUG + for ( int chunkIdx = 0; chunkIdx < m_MemChunkCount; chunkIdx++ ) + { + chunk_t *chunk = &m_Memory[ chunkIdx ]; + + if ( chunk->count ) + { + memset( chunk->ptr, 0xdd, chunk->count * MEM_CACHE_CHUNKSIZE ); + } + } +#endif + + m_LastFreeChunk = 0; + m_LastFreeIndex = 0; + } +}; + +template < typename T, bool bExternalMem = false, class CAllocator = CMemory > +class vector +{ +public: + typedef unsigned int I; + + CAllocator _base; + I _size; + + vector() : _base(), _size(0) + { + Assert( !bExternalMem ); + } + + vector( CAllocator &a ) : _base(a), _size(0) + { + Assert( bExternalMem ); + } + + vector( I count ) : _base(), _size(0) + { + Assert( !bExternalMem ); + _base.Alloc( count * sizeof(T) ); + } + + vector( const vector< T > &src ) : _base() + { + Assert( !bExternalMem ); + _base.Alloc( src._base.Size() ); + _size = src._size; + + for ( I i = 0; i < _size; i++ ) + new( &_base[ i * sizeof(T) ] ) T( (T&)src._base[ i * sizeof(T) ] ); + } + + ~vector() + { + Assert( (unsigned int)_size <= _base.Size() ); + + for ( I i = 0; i < _size; i++ ) + ((T&)(_base[ i * sizeof(T) ])).~T(); + + if ( !bExternalMem ) + _base.Free(); + } + + T &operator[]( I i ) const + { + Assert( _size > 0 ); + Assert( i >= 0 && i < _size ); + Assert( _size * sizeof(T) <= _base.Size() ); + return (T&)_base[ i * sizeof(T) ]; + } + + T *base() + { + return _base.Base(); + } + + I size() const + { + return _size; + } + + I capacity() const + { + return _base.Size() / sizeof(T); + } + + T &top() const + { + Assert( _size > 0 ); + return (T&)_base[ ( _size - 1 ) * sizeof(T) ]; + } + + void pop() + { + Assert( _size > 0 ); + ((T&)_base[ --_size * sizeof(T) ]).~T(); + } + + T &append() + { + _base.Ensure( ++_size * sizeof(T) ); + Assert( _size * sizeof(T) <= _base.Size() ); + return *( new( &_base[ ( _size - 1 ) * sizeof(T) ] ) T() ); + } + + void append( const T &src ) + { + _base.Ensure( ++_size * sizeof(T) ); + Assert( _size * sizeof(T) <= _base.Size() ); + new( &_base[ ( _size - 1 ) * sizeof(T) ] ) T( src ); + } + + T &insert( I i ) + { + Assert( i >= 0 && i <= _size ); + + _base.Ensure( ++_size * sizeof(T) ); + Assert( _size * sizeof(T) <= _base.Size() ); + + if ( i != _size - 1 ) + { + memmove( &_base[ ( i + 1 ) * sizeof(T) ], + &_base[ i * sizeof(T) ], + ( _size - ( i + 1 ) ) * sizeof(T) ); + } + + return *( new( &_base[ i * sizeof(T) ] ) T() ); + } + + void remove( I i ) + { + Assert( _size > 0 ); + Assert( i >= 0 && i < _size ); + + ((T&)_base[ i * sizeof(T) ]).~T(); + + if ( i != _size - 1 ) + { + memmove( &_base[ i * sizeof(T) ], + &_base[ ( i + 1 ) * sizeof(T) ], + ( _size - ( i + 1 ) ) * sizeof(T) ); + } + + _size--; + } + + void clear() + { + for ( I i = 0; i < _size; i++ ) + ((T&)_base[ i * sizeof(T) ]).~T(); + + _size = 0; + } + + void sort( int (*fn)(const T *, const T *) ) + { + Assert( _size * sizeof(T) <= _base.Size() ); + + if ( _size > 1 ) + { + qsort( _base.Base(), _size, sizeof(T), (int (*)(const void *, const void *))fn ); + } + } + + void reserve( I count ) + { + Assert( (unsigned int)_size <= _base.Size() ); + + if ( count == 0 ) + count = 4; + + if ( (unsigned int)count == _base.Size() ) + return; + + for ( I i = count; i < _size; i++ ) + ((T&)_base[ i * sizeof(T) ]).~T(); + + _base.Alloc( count * sizeof(T) ); + } + + void purge() + { + Assert( _size * sizeof(T) <= _base.Size() ); + + for ( I i = 0; i < _size; i++ ) + ((T&)_base[ i * sizeof(T) ]).~T(); + + _base.Free(); + _size = 0; + } +}; + +class CBuffer +{ +public: + CMemory _base; + int _size; + int _offset; + + char *base() + { + return _base.Base() + _offset; + } + + int size() const + { + return _size; + } + + int capacity() const + { + return _base.Size(); + } + + void reserve( int count ) + { + Assert( (unsigned int)_size <= _base.Size() ); + + if ( (unsigned int)count == _base.Size() ) + return; + + _base.Alloc( count ); + } + + void purge() + { + _base.Free(); + _size = 0; + _offset = 0; + } +}; + +class CBufTmpCache +{ +public: + CBuffer *buffer; + int size; + + CBufTmpCache( CBuffer *b ) : + buffer(b), + size(buffer->_size) + { + buffer->_offset += buffer->_size; + buffer->_size = 0; + } + + ~CBufTmpCache() + { + buffer->_offset -= size; + buffer->_size = size; + } +}; + +#endif // SQDBG_VEC_H diff --git a/sp/src/vscript/vscript.cpp b/sp/src/vscript/vscript.cpp index af1570ad..d7af40f4 100644 --- a/sp/src/vscript/vscript.cpp +++ b/sp/src/vscript/vscript.cpp @@ -17,6 +17,7 @@ IScriptVM* makeSquirrelVM(); int vscript_token = 0; +int vscript_debugger_port = 0; class CScriptManager : public CTier1AppSystem { @@ -60,10 +61,14 @@ public: } // Mapbase moves CScriptKeyValues into the library so it could be used elsewhere - virtual HSCRIPT CreateScriptKeyValues( IScriptVM *pVM, KeyValues *pKV, bool bAllowDestruct ) override + + // if bBorrow is false, CScriptKeyValues owns pKV memory + // Functions returning the result need to return HSCRIPT_RC + // see comment on IScriptVM::RegisterInstance() + virtual HSCRIPT CreateScriptKeyValues( IScriptVM *pVM, KeyValues *pKV, bool bBorrow ) override { - CScriptKeyValues *pSKV = new CScriptKeyValues( pKV ); - HSCRIPT hSKV = pVM->RegisterInstance( pSKV, bAllowDestruct ); + CScriptKeyValues *pSKV = new CScriptKeyValues( pKV, bBorrow ); + HSCRIPT hSKV = pVM->RegisterInstance( pSKV, true ); return hSKV; } @@ -72,11 +77,11 @@ public: CScriptKeyValues *pSKV = (hSKV ? (CScriptKeyValues*)pVM->GetInstanceValue( hSKV, GetScriptDesc( (CScriptKeyValues*)NULL ) ) : nullptr); if (pSKV) { - return pSKV->m_pKeyValues; + return pSKV->GetKeyValues(); } return nullptr; } }; -EXPOSE_SINGLE_INTERFACE(CScriptManager, IScriptManager, VSCRIPT_INTERFACE_VERSION); \ No newline at end of file +EXPOSE_SINGLE_INTERFACE(CScriptManager, IScriptManager, VSCRIPT_INTERFACE_VERSION); diff --git a/sp/src/vscript/vscript.vpc b/sp/src/vscript/vscript.vpc index 425afb8a..d95cfef9 100644 --- a/sp/src/vscript/vscript.vpc +++ b/sp/src/vscript/vscript.vpc @@ -12,7 +12,7 @@ $Configuration { $Compiler { - $AdditionalIncludeDirectories "$BASE;.\squirrel\include" + $AdditionalIncludeDirectories "$BASE;.\squirrel\include;.\squirrel\squirrel;.\squirrel\sqstdlib;.\sqdbg\include" $PreprocessorDefinitions "$BASE;MAPBASE_VSCRIPT" [$MAPBASE_VSCRIPT] } } @@ -65,5 +65,21 @@ $Project "VScript" } } } + + $Folder "sqdbg" + { + $File ".\sqdbg\sqdbg\server.cpp" + { + $Configuration + { + $Compiler + { + $PreprocessorDefinitions "$BASE;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS" + $AdditionalOptions "$BASE /wd4127 /wd4146 /wd4201 /wd4244 /wd4267 /wd4456 /wd4706" + $TreatWarningsAsErrors "No" + } + } + } + } } } diff --git a/sp/src/vscript/vscript_bindings_base.cpp b/sp/src/vscript/vscript_bindings_base.cpp index 99b6194a..498c3c02 100644 --- a/sp/src/vscript/vscript_bindings_base.cpp +++ b/sp/src/vscript/vscript_bindings_base.cpp @@ -106,7 +106,7 @@ BEGIN_SCRIPTDESC_ROOT( CScriptKeyValues, "Wrapper class over KeyValues instance" DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValueBool, "GetKeyBool", "Given a KeyValues object and a key name, return associated bool value" ); DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValueString, "GetKeyString", "Given a KeyValues object and a key name, return associated string value" ); DEFINE_SCRIPTFUNC_NAMED( ScriptIsKeyValueEmpty, "IsKeyEmpty", "Given a KeyValues object and a key name, return true if key name has no value" ); - DEFINE_SCRIPTFUNC_NAMED( ScriptReleaseKeyValues, "ReleaseKeyValues", "Given a root KeyValues object, release its contents" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptReleaseKeyValues, "ReleaseKeyValues", SCRIPT_HIDE ); DEFINE_SCRIPTFUNC( TableToSubKeys, "Converts a script table to KeyValues." ); DEFINE_SCRIPTFUNC( SubKeysToTable, "Converts to script table." ); @@ -131,79 +131,84 @@ BEGIN_SCRIPTDESC_ROOT( CScriptKeyValues, "Wrapper class over KeyValues instance" DEFINE_SCRIPTFUNC_NAMED( ScriptSetString, "SetString", "Given a KeyValues object, set its own associated string value" ); END_SCRIPTDESC(); -HSCRIPT CScriptKeyValues::ScriptFindKey( const char *pszName ) +HSCRIPT_RC CScriptKeyValues::ScriptFindKey( const char *pszName ) { - KeyValues *pKeyValues = m_pKeyValues->FindKey(pszName); + KeyValues *pKeyValues = GetKeyValues()->FindKey(pszName); if ( pKeyValues == NULL ) return NULL; - CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues ); + CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues, true ); + HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey, true ); + + pScriptKey->m_pBase = m_pSelf; + pScriptKey->m_pBase->AddRef(); - // UNDONE: who calls ReleaseInstance on this?? - HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey ); return hScriptInstance; } -HSCRIPT CScriptKeyValues::ScriptGetFirstSubKey( void ) +HSCRIPT_RC CScriptKeyValues::ScriptGetFirstSubKey( void ) { - KeyValues *pKeyValues = m_pKeyValues->GetFirstSubKey(); + KeyValues *pKeyValues = GetKeyValues()->GetFirstSubKey(); if ( pKeyValues == NULL ) return NULL; - CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues ); + CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues, true ); + HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey, true ); + + pScriptKey->m_pBase = m_pSelf; + pScriptKey->m_pBase->AddRef(); - // UNDONE: who calls ReleaseInstance on this?? - HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey ); return hScriptInstance; } -HSCRIPT CScriptKeyValues::ScriptGetNextKey( void ) +HSCRIPT_RC CScriptKeyValues::ScriptGetNextKey( void ) { - KeyValues *pKeyValues = m_pKeyValues->GetNextKey(); + KeyValues *pKeyValues = GetKeyValues()->GetNextKey(); if ( pKeyValues == NULL ) return NULL; - CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues ); + CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues, true ); + HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey, true ); + + // if I don't have a parent, then I own my siblings + pScriptKey->m_pBase = m_pBase ? m_pBase : m_pSelf; + pScriptKey->m_pBase->AddRef(); - // UNDONE: who calls ReleaseInstance on this?? - HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey ); return hScriptInstance; } int CScriptKeyValues::ScriptGetKeyValueInt( const char *pszName ) { - int i = m_pKeyValues->GetInt( pszName ); + int i = GetKeyValues()->GetInt( pszName ); return i; } float CScriptKeyValues::ScriptGetKeyValueFloat( const char *pszName ) { - float f = m_pKeyValues->GetFloat( pszName ); + float f = GetKeyValues()->GetFloat( pszName ); return f; } const char *CScriptKeyValues::ScriptGetKeyValueString( const char *pszName ) { - const char *psz = m_pKeyValues->GetString( pszName ); + const char *psz = GetKeyValues()->GetString( pszName ); return psz; } bool CScriptKeyValues::ScriptIsKeyValueEmpty( const char *pszName ) { - bool b = m_pKeyValues->IsEmpty( pszName ); + bool b = GetKeyValues()->IsEmpty( pszName ); return b; } bool CScriptKeyValues::ScriptGetKeyValueBool( const char *pszName ) { - bool b = m_pKeyValues->GetBool( pszName ); + bool b = GetKeyValues()->GetBool( pszName ); return b; } void CScriptKeyValues::ScriptReleaseKeyValues( ) { - m_pKeyValues->deleteThis(); - m_pKeyValues = NULL; } void KeyValues_TableToSubKeys( HSCRIPT hTable, KeyValues *pKV ) @@ -259,125 +264,134 @@ void KeyValues_SubKeysToTable( KeyValues *pKV, HSCRIPT hTable ) void CScriptKeyValues::TableToSubKeys( HSCRIPT hTable ) { - KeyValues_TableToSubKeys( hTable, m_pKeyValues ); + KeyValues_TableToSubKeys( hTable, GetKeyValues() ); } void CScriptKeyValues::SubKeysToTable( HSCRIPT hTable ) { - KeyValues_SubKeysToTable( m_pKeyValues, hTable ); + KeyValues_SubKeysToTable( GetKeyValues(), hTable ); } -HSCRIPT CScriptKeyValues::ScriptFindOrCreateKey( const char *pszName ) +HSCRIPT_RC CScriptKeyValues::ScriptFindOrCreateKey( const char *pszName ) { - KeyValues *pKeyValues = m_pKeyValues->FindKey(pszName, true); + KeyValues *pKeyValues = GetKeyValues()->FindKey(pszName, true); if ( pKeyValues == NULL ) return NULL; - CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues ); + CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues, true ); + HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey, true ); + + pScriptKey->m_pBase = m_pSelf; + pScriptKey->m_pBase->AddRef(); - // UNDONE: who calls ReleaseInstance on this?? - HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey ); return hScriptInstance; } const char *CScriptKeyValues::ScriptGetName() { - const char *psz = m_pKeyValues->GetName(); + const char *psz = GetKeyValues()->GetName(); return psz; } int CScriptKeyValues::ScriptGetInt() { - int i = m_pKeyValues->GetInt(); + int i = GetKeyValues()->GetInt(); return i; } float CScriptKeyValues::ScriptGetFloat() { - float f = m_pKeyValues->GetFloat(); + float f = GetKeyValues()->GetFloat(); return f; } const char *CScriptKeyValues::ScriptGetString() { - const char *psz = m_pKeyValues->GetString(); + const char *psz = GetKeyValues()->GetString(); return psz; } bool CScriptKeyValues::ScriptGetBool() { - bool b = m_pKeyValues->GetBool(); + bool b = GetKeyValues()->GetBool(); return b; } void CScriptKeyValues::ScriptSetKeyValueInt( const char *pszName, int iValue ) { - m_pKeyValues->SetInt( pszName, iValue ); + GetKeyValues()->SetInt( pszName, iValue ); } void CScriptKeyValues::ScriptSetKeyValueFloat( const char *pszName, float flValue ) { - m_pKeyValues->SetFloat( pszName, flValue ); + GetKeyValues()->SetFloat( pszName, flValue ); } void CScriptKeyValues::ScriptSetKeyValueString( const char *pszName, const char *pszValue ) { - m_pKeyValues->SetString( pszName, pszValue ); + GetKeyValues()->SetString( pszName, pszValue ); } void CScriptKeyValues::ScriptSetKeyValueBool( const char *pszName, bool bValue ) { - m_pKeyValues->SetBool( pszName, bValue ); + GetKeyValues()->SetBool( pszName, bValue ); } void CScriptKeyValues::ScriptSetName( const char *pszValue ) { - m_pKeyValues->SetName( pszValue ); + GetKeyValues()->SetName( pszValue ); } void CScriptKeyValues::ScriptSetInt( int iValue ) { - m_pKeyValues->SetInt( NULL, iValue ); + GetKeyValues()->SetInt( NULL, iValue ); } void CScriptKeyValues::ScriptSetFloat( float flValue ) { - m_pKeyValues->SetFloat( NULL, flValue ); + GetKeyValues()->SetFloat( NULL, flValue ); } void CScriptKeyValues::ScriptSetString( const char *pszValue ) { - m_pKeyValues->SetString( NULL, pszValue ); + GetKeyValues()->SetString( NULL, pszValue ); } void CScriptKeyValues::ScriptSetBool( bool bValue ) { - m_pKeyValues->SetBool( NULL, bValue ); + GetKeyValues()->SetBool( NULL, bValue ); } // constructors -CScriptKeyValues::CScriptKeyValues( KeyValues *pKeyValues = NULL ) +CScriptKeyValues::CScriptKeyValues( KeyValues *pKeyValues = NULL, bool bBorrow = false ) : + m_pBase( NULL ) { if (pKeyValues == NULL) { - m_pKeyValues = new KeyValues("CScriptKeyValues"); - } - else - { - m_pKeyValues = pKeyValues; + pKeyValues = new KeyValues("CScriptKeyValues"); + // Borrowed new memory doesn't make sense, are you trying to leak? + Assert( !bBorrow ); } + + m_pSelf = new KeyValues_RC( pKeyValues, bBorrow ); } // destructor CScriptKeyValues::~CScriptKeyValues( ) { - if (m_pKeyValues) + Assert( m_pSelf != m_pBase ); + + // Children are always borrowed + Assert( !m_pBase || m_pSelf->borrow ); + + m_pSelf->Release(); + + if ( m_pBase ) { - m_pKeyValues->deleteThis(); + m_pBase->Release(); } - m_pKeyValues = NULL; } //============================================================================= @@ -387,10 +401,9 @@ CScriptKeyValues::~CScriptKeyValues( ) //============================================================================= CScriptColorInstanceHelper g_ColorScriptInstanceHelper; -BEGIN_SCRIPTDESC_ROOT( Color, "" ) +BEGIN_SCRIPTDESC_ROOT_WITH_HELPER( Color, "", &g_ColorScriptInstanceHelper ) DEFINE_SCRIPT_CONSTRUCTOR() - DEFINE_SCRIPT_INSTANCE_HELPER( &g_ColorScriptInstanceHelper ) DEFINE_SCRIPTFUNC( SetColor, "Sets the color." ) @@ -440,13 +453,11 @@ bool CScriptColorInstanceHelper::Get( void *p, const char *pszKey, ScriptVariant bool CScriptColorInstanceHelper::Set( void *p, const char *pszKey, ScriptVariant_t &variant ) { Color *pClr = ((Color *)p); - if ( strlen(pszKey) == 1 ) + int iVal; + if ( strlen(pszKey) == 1 && variant.AssignTo( &iVal ) ) { - int iVal; - variant.AssignTo( &iVal ); switch (pszKey[0]) { - // variant.AssignTo( &(*pClr)[0] ); case 'r': (*pClr)[0] = iVal; return true; diff --git a/sp/src/vscript/vscript_bindings_base.h b/sp/src/vscript/vscript_bindings_base.h index 2629aada..2e0e8bf5 100644 --- a/sp/src/vscript/vscript_bindings_base.h +++ b/sp/src/vscript/vscript_bindings_base.h @@ -20,12 +20,12 @@ class CScriptKeyValues { public: - CScriptKeyValues( KeyValues *pKeyValues ); + CScriptKeyValues( KeyValues *pKeyValues, bool bBorrow ); ~CScriptKeyValues( ); - HSCRIPT ScriptFindKey( const char *pszName ); - HSCRIPT ScriptGetFirstSubKey( void ); - HSCRIPT ScriptGetNextKey( void ); + HSCRIPT_RC ScriptFindKey( const char *pszName ); + HSCRIPT_RC ScriptGetFirstSubKey( void ); + HSCRIPT_RC ScriptGetNextKey( void ); int ScriptGetKeyValueInt( const char *pszName ); float ScriptGetKeyValueFloat( const char *pszName ); const char *ScriptGetKeyValueString( const char *pszName ); @@ -37,7 +37,7 @@ public: void TableToSubKeys( HSCRIPT hTable ); void SubKeysToTable( HSCRIPT hTable ); - HSCRIPT ScriptFindOrCreateKey( const char *pszName ); + HSCRIPT_RC ScriptFindOrCreateKey( const char *pszName ); const char *ScriptGetName(); int ScriptGetInt(); @@ -55,9 +55,54 @@ public: void ScriptSetString( const char *pszValue ); void ScriptSetBool( bool bValue ); - KeyValues *GetKeyValues() { return m_pKeyValues; } + KeyValues *GetKeyValues() { return m_pSelf->ptr; } - KeyValues *m_pKeyValues; // actual KeyValue entity + // The lifetime of the KeyValues pointer needs to be decoupled from refcounted script objects + // because base kv script objects can be released while their children live: kv = kv.GetFirstSubKey() + // Refcounting externally allows children to extend the lifetime of KeyValues while + // being able to automatically dispose of CScriptKeyValues and HSCRIPT objects with + // script refcounted HSCRIPT_RC + + struct KeyValues_RC + { + KeyValues *ptr; + unsigned int refs; + // Wheter KeyValues memory is borrowed or owned by CScriptKeyValues + // if not borrowed, it is deleted on release + bool borrow; + + KeyValues_RC( KeyValues *pKeyValues, bool bBorrow ) : + ptr( pKeyValues ), + refs( 1 ), + borrow( bBorrow ) + { + } + + void AddRef() + { + Assert( refs < (unsigned int)-1 ); + refs++; + } + + void Release() + { + Assert( refs > 0 ); + refs--; + + if ( refs == 0 ) + { + if ( !borrow ) + { + ptr->deleteThis(); + } + + delete this; + } + } + }; + + KeyValues_RC *m_pSelf; + KeyValues_RC *m_pBase; }; //----------------------------------------------------------------------------- diff --git a/sp/src/vscript/vscript_bindings_math.cpp b/sp/src/vscript/vscript_bindings_math.cpp index cb1567d5..1d290ef7 100644 --- a/sp/src/vscript/vscript_bindings_math.cpp +++ b/sp/src/vscript/vscript_bindings_math.cpp @@ -205,10 +205,9 @@ void ScriptMatrixSetTranslation( const Vector& vecset, HSCRIPT hMat1 ) //============================================================================= CScriptQuaternionInstanceHelper g_QuaternionScriptInstanceHelper; -BEGIN_SCRIPTDESC_ROOT_NAMED( Quaternion, "Quaternion", "A quaternion." ) +BEGIN_SCRIPTDESC_ROOT_NAMED_WITH_HELPER( Quaternion, "Quaternion", "A quaternion.", &g_QuaternionScriptInstanceHelper ) DEFINE_SCRIPT_CONSTRUCTOR() - DEFINE_SCRIPT_INSTANCE_HELPER( &g_QuaternionScriptInstanceHelper ) DEFINE_SCRIPTFUNC_NAMED( ScriptInit, "Init", "Creates a quaternion with the given values." ) DEFINE_MEMBERVAR( "x", FIELD_FLOAT, "The quaternion's i axis component." ) @@ -259,39 +258,18 @@ bool CScriptQuaternionInstanceHelper::Set( void *p, const char *pszKey, ScriptVa switch (pszKey[0]) { case 'x': - variant.AssignTo( &pQuat->x ); - return true; + return variant.AssignTo( &pQuat->x ); case 'y': - variant.AssignTo( &pQuat->y ); - return true; + return variant.AssignTo( &pQuat->y ); case 'z': - variant.AssignTo( &pQuat->z ); - return true; + return variant.AssignTo( &pQuat->z ); case 'w': - variant.AssignTo( &pQuat->w ); - return true; + return variant.AssignTo( &pQuat->w ); } } return false; } -ScriptVariant_t *CScriptQuaternionInstanceHelper::Add( void *p, ScriptVariant_t &variant ) -{ - Quaternion *pQuat = ((Quaternion *)p); - - float flAdd; - variant.AssignTo( &flAdd ); - - (*pQuat)[0] += flAdd; - (*pQuat)[1] += flAdd; - (*pQuat)[2] += flAdd; - (*pQuat)[3] += flAdd; - - static ScriptVariant_t result; - result = (HSCRIPT)p; - return &result; -} - //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- diff --git a/sp/src/vscript/vscript_bindings_math.h b/sp/src/vscript/vscript_bindings_math.h index c2d960b5..20039b61 100644 --- a/sp/src/vscript/vscript_bindings_math.h +++ b/sp/src/vscript/vscript_bindings_math.h @@ -40,11 +40,6 @@ class CScriptQuaternionInstanceHelper : public IScriptInstanceHelper bool Get( void *p, const char *pszKey, ScriptVariant_t &variant ); bool Set( void *p, const char *pszKey, ScriptVariant_t &variant ); - - ScriptVariant_t *Add( void *p, ScriptVariant_t &variant ); - //ScriptVariant_t *Subtract( void *p, ScriptVariant_t &variant ); - //ScriptVariant_t *Multiply( void *p, ScriptVariant_t &variant ); - //ScriptVariant_t *Divide( void *p, ScriptVariant_t &variant ); }; inline Quaternion *ToQuaternion( HSCRIPT hQuat ) { return HScriptToClass( hQuat ); } diff --git a/sp/src/vscript/vscript_squirrel.cpp b/sp/src/vscript/vscript_squirrel.cpp index 9f46d68c..8dbabac1 100644 --- a/sp/src/vscript/vscript_squirrel.cpp +++ b/sp/src/vscript/vscript_squirrel.cpp @@ -23,14 +23,18 @@ #include "sqstdstring.h" // HACK: Include internal parts of squirrel for serialization +#include "squirrel/squirrel/sqvm.h" #include "squirrel/squirrel/sqobject.h" #include "squirrel/squirrel/sqstate.h" #include "squirrel/squirrel/sqtable.h" +#include "squirrel/squirrel/sqarray.h" #include "squirrel/squirrel/sqclass.h" #include "squirrel/squirrel/sqfuncproto.h" -#include "squirrel/squirrel/sqvm.h" +#include "squirrel/squirrel/squserdata.h" #include "squirrel/squirrel/sqclosure.h" +#include "sqdbg.h" + #include "tier1/utlbuffer.h" #include "tier1/mapbase_con_groups.h" #include "tier1/convar.h" @@ -41,25 +45,26 @@ extern ConVar developer; + struct WriteStateMap { - CUtlMap cache; - WriteStateMap() : cache(DefLessFunc(void*)) + CUtlRBTree< void* > cache; + + WriteStateMap() : cache( DefLessFunc(void*) ) {} - bool CheckCache(CUtlBuffer* pBuffer, void* ptr) + bool CheckCache( void* ptr, CUtlBuffer* pBuffer ) { - auto idx = cache.Find(ptr); - if (idx != cache.InvalidIndex()) + int idx = cache.Find( ptr ); + if ( idx != cache.InvalidIndex() ) { - pBuffer->PutInt(cache[idx]); + pBuffer->PutInt( idx ); return true; } else { - int newIdx = cache.Count(); - cache.Insert(ptr, newIdx); - pBuffer->PutInt(newIdx); + int newIdx = cache.Insert( ptr ); + pBuffer->PutInt( newIdx ); return false; } } @@ -67,79 +72,50 @@ struct WriteStateMap struct ReadStateMap { - CUtlMap cache; -#ifdef _DEBUG - CUtlMap allocated; -#endif - HSQUIRRELVM vm_; - ReadStateMap(HSQUIRRELVM vm) : - cache(DefLessFunc(int)), -#ifdef _DEBUG - allocated(DefLessFunc(int)), -#endif - vm_(vm) + CUtlMap< int, SQObject > cache; + + ReadStateMap() : cache( DefLessFunc(int) ) {} - ~ReadStateMap() - { - FOR_EACH_MAP_FAST(cache, i) - { - HSQOBJECT& obj = cache[i]; - sq_release(vm_, &obj); - } - } - - bool CheckCache(CUtlBuffer* pBuffer, HSQUIRRELVM vm, int * outmarker) + bool CheckCache( SQObject* ptr, CUtlBuffer* pBuffer, int* outmarker ) { int marker = pBuffer->GetInt(); - - auto idx = cache.Find(marker); - -#ifdef _DEBUG - auto allocatedIdx = allocated.Find(marker); - bool hasSeen = allocatedIdx != allocated.InvalidIndex(); - if (!hasSeen) + int idx = cache.Find( marker ); + if ( idx != cache.InvalidIndex() ) { - allocated.Insert(marker, true); - } -#endif + const SQObject &o = cache[idx]; + + Assert( o._type == ptr->_type ); + + ptr->_type = o._type; + ptr->_unVal.raw = o._unVal.raw; - if (idx != cache.InvalidIndex()) - { - sq_pushobject(vm, cache[idx]); return true; } else { -#ifdef _DEBUG - Assert(!hasSeen); -#endif *outmarker = marker; return false; } } - void StoreInCache(int marker, HSQOBJECT& obj) + void StoreInCache( int marker, const SQObject &obj ) { - cache.Insert(marker, obj); - } - - void StoreTopInCache(int marker) - { - HSQOBJECT obj; - sq_getstackobj(vm_, -1, &obj); - sq_addref(vm_, &obj); - cache.Insert(marker, obj); + int idx = cache.Insert( marker ); + SQObject &o = cache[idx]; + o._type = obj._type; + o._unVal.raw = obj._unVal.raw; } }; + class SquirrelVM : public IScriptVM { public: virtual bool Init() override; virtual void Shutdown() override; - virtual bool ConnectDebugger() override; + virtual bool ConnectDebugger( int port = 0 ) override; virtual void DisconnectDebugger() override; virtual ScriptLanguage_t GetLanguage() override; @@ -220,7 +196,7 @@ public: // External instances. Note class will be auto-registered. //-------------------------------------------------------- - virtual HSCRIPT RegisterInstance(ScriptClassDesc_t* pDesc, void* pInstance, bool bAllowDestruct = false) override; + virtual HSCRIPT RegisterInstance(ScriptClassDesc_t* pDesc, void* pInstance, bool bRefCounted = false) override; virtual void SetInstanceUniqeId(HSCRIPT hInstance, const char* pszId) override; virtual void RemoveInstance(HSCRIPT hInstance) override; @@ -251,6 +227,7 @@ public: virtual void CreateArray(ScriptVariant_t &arr, int size = 0) override; virtual bool ArrayAppend(HSCRIPT hArray, const ScriptVariant_t &val) override; + virtual HSCRIPT CopyObject(HSCRIPT obj) override; //---------------------------------------------------------------------------- @@ -267,13 +244,64 @@ public: virtual bool RaiseException(const char* pszExceptionText) override; + void WriteObject( const SQObjectPtr &obj, CUtlBuffer* pBuffer, WriteStateMap& writeState ); + + // Do not implicitly add/remove ref + void WriteObject( const SQObject &obj, CUtlBuffer* pBuffer, WriteStateMap& writeState ) + { + WriteObject( (const SQObjectPtr&)obj, pBuffer, writeState ); + } + + void WriteObject( SQTable *pObj, CUtlBuffer* pBuffer, WriteStateMap& writeState ) + { + SQObject obj; + obj._type = OT_TABLE; + obj._unVal.pUserPointer = pObj; + WriteObject( (const SQObjectPtr&)obj, pBuffer, writeState ); + } + + void WriteObject( SQClass *pObj, CUtlBuffer* pBuffer, WriteStateMap& writeState ) + { + SQObject obj; + obj._type = OT_CLASS; + obj._unVal.pUserPointer = pObj; + WriteObject( (const SQObjectPtr&)obj, pBuffer, writeState ); + } + + void WriteObject( SQWeakRef *pObj, CUtlBuffer* pBuffer, WriteStateMap& writeState ) + { + SQObject obj; + obj._type = OT_WEAKREF; + obj._unVal.pUserPointer = pObj; + WriteObject( (const SQObjectPtr&)obj, pBuffer, writeState ); + } + + void WriteObject( SQFunctionProto *pObj, CUtlBuffer* pBuffer, WriteStateMap& writeState ) + { + SQObject obj; + obj._type = OT_FUNCPROTO; + obj._unVal.pUserPointer = pObj; + WriteObject( (const SQObjectPtr&)obj, pBuffer, writeState ); + } + + void ReadObject( SQObjectPtr &obj, CUtlBuffer* pBuffer, ReadStateMap& readState ); + + // Do not implicity add/remove ref + void ReadObject( SQObject &obj, CUtlBuffer* pBuffer, ReadStateMap& readState ) + { + obj._type = OT_NULL; + obj._unVal.pUserPointer = 0; + ReadObject( (SQObjectPtr&)obj, pBuffer, readState ); + } + + void WriteVM( SQVM *pThis, CUtlBuffer *pBuffer, WriteStateMap &writeState ); + void ReadVM( SQVM *pThis, CUtlBuffer *pBuffer, ReadStateMap &readState ); - void WriteObject(CUtlBuffer* pBuffer, WriteStateMap& writeState, SQInteger idx); - void ReadObject(CUtlBuffer* pBuffer, ReadStateMap& readState); HSQUIRRELVM vm_ = nullptr; HSQOBJECT lastError_; HSQOBJECT vectorClass_; HSQOBJECT regexpClass_; + HSQDEBUGSERVER debugger_ = nullptr; }; static char TYPETAG_VECTOR[] = "VectorTypeTag"; @@ -1067,19 +1095,19 @@ namespace SQVector struct ClassInstanceData { - ClassInstanceData(void* instance, ScriptClassDesc_t* desc, const char* instanceId = nullptr, bool allowDestruct = false) : + ClassInstanceData(void* instance, ScriptClassDesc_t* desc, const char* instanceId = nullptr, bool refCounted = false) : instance(instance), desc(desc), instanceId(instanceId), - allowDestruct(allowDestruct) + refCounted(refCounted) {} void* instance; ScriptClassDesc_t* desc; - CUtlString instanceId; + CUtlConstString instanceId; - // Indicates this game-created instance is a weak reference and can be destructed (Blixibon) - bool allowDestruct; + // keep for setting instance release hook in save/restore + bool refCounted; }; bool CreateParamCheck(const ScriptFunctionBinding_t& func, char* output) @@ -1135,8 +1163,8 @@ void PushVariant(HSQUIRRELVM vm, const ScriptVariant_t& value) break; case FIELD_VECTOR: { - SquirrelVM* pSquirrelVM = (SquirrelVM*)sq_getforeignptr(vm); - assert(pSquirrelVM); + SquirrelVM* pSquirrelVM = (SquirrelVM*)sq_getsharedforeignptr(vm); + Assert(pSquirrelVM); sq_pushobject(vm, pSquirrelVM->vectorClass_); sq_createinstance(vm, -1); SQUserPointer p; @@ -1205,6 +1233,8 @@ bool getVariant(HSQUIRRELVM vm, SQInteger idx, ScriptVariant_t& variant) { case OT_NULL: { + variant.Free(); + variant.m_flags = 0; // TODO: Should this be (HSCRIPT)nullptr variant.m_type = FIELD_VOID; return true; @@ -1216,6 +1246,7 @@ bool getVariant(HSQUIRRELVM vm, SQInteger idx, ScriptVariant_t& variant) { return false; } + variant.Free(); variant = (int)val; return true; } @@ -1226,6 +1257,7 @@ bool getVariant(HSQUIRRELVM vm, SQInteger idx, ScriptVariant_t& variant) { return false; } + variant.Free(); variant = (float)val; return true; } @@ -1236,6 +1268,7 @@ bool getVariant(HSQUIRRELVM vm, SQInteger idx, ScriptVariant_t& variant) { return false; } + variant.Free(); variant = val ? true : false; return true; } @@ -1247,7 +1280,8 @@ bool getVariant(HSQUIRRELVM vm, SQInteger idx, ScriptVariant_t& variant) { return false; } - char* buffer = new char[size + 1]; + variant.Free(); + char* buffer = (char*)malloc(size + 1); V_memcpy(buffer, val, size); buffer[size] = 0; variant = buffer; @@ -1262,7 +1296,9 @@ bool getVariant(HSQUIRRELVM vm, SQInteger idx, ScriptVariant_t& variant) tag == TYPETAG_VECTOR && SQ_SUCCEEDED(sq_getinstanceup(vm, idx, (SQUserPointer*)&v, TYPETAG_VECTOR))) { - variant = new Vector(*v); + variant.Free(); + variant = (Vector*)malloc(sizeof(Vector)); + variant.EmplaceAllocedVector(*v); variant.m_flags |= SV_FREE; return true; } @@ -1270,6 +1306,7 @@ bool getVariant(HSQUIRRELVM vm, SQInteger idx, ScriptVariant_t& variant) } default: { + variant.Free(); HSQOBJECT* obj = new HSQOBJECT; sq_resetobject(obj); sq_getstackobj(vm, idx, obj); @@ -1293,7 +1330,8 @@ SQInteger function_stub(HSQUIRRELVM vm) ScriptFunctionBinding_t* pFunc = (ScriptFunctionBinding_t*)userptr; - auto nargs = pFunc->m_desc.m_Parameters.Count(); + int nargs = pFunc->m_desc.m_Parameters.Count(); + int nLastHScriptIdx = -1; if (nargs > top) { @@ -1370,9 +1408,10 @@ SQInteger function_stub(HSQUIRRELVM vm) { HSQOBJECT* pObject = new HSQOBJECT; *pObject = val; - sq_addref(vm, pObject); params[i] = (HSCRIPT)pObject; } + + nLastHScriptIdx = i; break; } default: @@ -1396,29 +1435,63 @@ SQInteger function_stub(HSQUIRRELVM vm) instance = ((ClassInstanceData*)self)->instance; } - ScriptVariant_t retval; + ScriptVariant_t script_retval; + ScriptVariantTemporaryStorage_t script_retval_storage; - SquirrelVM* pSquirrelVM = (SquirrelVM*)sq_getforeignptr(vm); - assert(pSquirrelVM); + SquirrelVM* pSquirrelVM = (SquirrelVM*)sq_getsharedforeignptr(vm); + Assert(pSquirrelVM); sq_resetobject(&pSquirrelVM->lastError_); - (*pFunc->m_pfnBinding)(pFunc->m_pFunction, instance, params.Base(), nargs, - pFunc->m_desc.m_ReturnType == FIELD_VOID ? nullptr : &retval); + bool call_success = (*pFunc->m_pfnBinding)(pFunc->m_pFunction, instance, params.Base(), nargs, + pFunc->m_desc.m_ReturnType == FIELD_VOID ? nullptr : &script_retval, script_retval_storage); + Assert(call_success); + (void)call_success; + SQInteger sq_retval; if (!sq_isnull(pSquirrelVM->lastError_)) { sq_pushobject(vm, pSquirrelVM->lastError_); sq_resetobject(&pSquirrelVM->lastError_); - return sq_throwobject(vm); + sq_retval = sq_throwobject(vm); + } + else + { + Assert(script_retval.m_type == pFunc->m_desc.m_ReturnType); + Assert( ( pFunc->m_desc.m_ReturnType != FIELD_VOID ) || !( pFunc->m_flags & SF_REFCOUNTED_RET ) ); + + if (pFunc->m_desc.m_ReturnType != FIELD_VOID) + { + PushVariant(vm, script_retval); + + if ( ( pFunc->m_flags & SF_REFCOUNTED_RET ) && script_retval.m_hScript ) + { + Assert( script_retval.m_type == FIELD_HSCRIPT ); + + // Release the intermediary ref held from RegisterInstance + sq_release(vm, (HSQOBJECT*)script_retval.m_hScript); + delete (HSQOBJECT*)script_retval.m_hScript; + } + + sq_retval = 1; + } + else + { + sq_retval = 0; + } } - PushVariant(vm, retval); + // strings never get copied here, Vector and QAngle are stored in script_retval_storage + // everything else is stored inline, so there should be no memory to free + Assert(!(script_retval.m_flags & SV_FREE)); - if (retval.m_type == FIELD_VECTOR) - delete retval.m_pVector; + for ( int i = 0; i <= nLastHScriptIdx; ++i ) + { + if ( pFunc->m_desc.m_Parameters[i] == FIELD_HSCRIPT ) + delete (HSQOBJECT*)params[i].m_hScript; + } - return pFunc->m_desc.m_ReturnType != FIELD_VOID; + return sq_retval; } @@ -1426,6 +1499,10 @@ SQInteger destructor_stub(SQUserPointer p, SQInteger size) { auto classInstanceData = (ClassInstanceData*)p; + // if instance is not deleted, then it's leaking + // this should never happen + Assert( classInstanceData->desc->m_pfnDestruct ); + if (classInstanceData->desc->m_pfnDestruct) classInstanceData->desc->m_pfnDestruct(classInstanceData->instance); @@ -1436,7 +1513,7 @@ SQInteger destructor_stub(SQUserPointer p, SQInteger size) SQInteger destructor_stub_instance(SQUserPointer p, SQInteger size) { auto classInstanceData = (ClassInstanceData*)p; - // We don't call destructor here because this is owned by the game + // This instance is owned by the game, don't delete it classInstanceData->~ClassInstanceData(); return 0; } @@ -1451,19 +1528,15 @@ SQInteger constructor_stub(HSQUIRRELVM vm) return sqstd_throwerrorf(vm, "Unable to construct instances of %s", pClassDesc->m_pszScriptName); } - SquirrelVM* pSquirrelVM = (SquirrelVM*)sq_getforeignptr(vm); - assert(pSquirrelVM); + SquirrelVM* pSquirrelVM = (SquirrelVM*)sq_getsharedforeignptr(vm); + Assert(pSquirrelVM); sq_resetobject(&pSquirrelVM->lastError_); void* instance = pClassDesc->m_pfnConstruct(); - if (!sq_isnull(pSquirrelVM->lastError_)) - { - sq_pushobject(vm, pSquirrelVM->lastError_); - sq_resetobject(&pSquirrelVM->lastError_); - return sq_throwobject(vm); - } + // expect construction to always succeed + Assert(sq_isnull(pSquirrelVM->lastError_)); { SQUserPointer p; @@ -1520,19 +1593,22 @@ SQInteger get_stub(HSQUIRRELVM vm) } ScriptVariant_t var; + SQInteger sq_retval = 0; if (classInstanceData && classInstanceData->instance && classInstanceData->desc->pHelper && classInstanceData->desc->pHelper->Get(classInstanceData->instance, key, var)) { PushVariant(vm, var); + sq_retval = 1; } else { - return sqstd_throwerrorf(vm, "the index '%.50s' does not exist", key); + sq_retval = sqstd_throwerrorf(vm, "the index '%.50s' does not exist", key); } - return 1; + var.Free(); + return sq_retval; } SQInteger set_stub(HSQUIRRELVM vm) @@ -1549,122 +1625,22 @@ SQInteger set_stub(HSQUIRRELVM vm) } ScriptVariant_t var; + SQInteger sq_retval = 0; getVariant( vm, -1, var ); - if (classInstanceData && + if (!( + classInstanceData && classInstanceData->instance && classInstanceData->desc->pHelper && - classInstanceData->desc->pHelper->Set(classInstanceData->instance, key, var)) + classInstanceData->desc->pHelper->Set(classInstanceData->instance, key, var) + )) { - sq_pop(vm, 1); - } - else - { - sq_pop(vm, 1); - return sqstd_throwerrorf(vm, "the index '%.50s' does not exist", key); - } - - return 0; -} - -SQInteger add_stub(HSQUIRRELVM vm) -{ - ClassInstanceData* classInstanceData = nullptr; - sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); - - ScriptVariant_t var; - getVariant( vm, 1, var ); - - if (classInstanceData && - classInstanceData->instance && - classInstanceData->desc->pHelper) - { - ScriptVariant_t *result = classInstanceData->desc->pHelper->Add( classInstanceData->instance, var ); - if (result != nullptr) - { - PushVariant( vm, *result ); - sq_pop(vm, 1); - return 1; - } + sq_retval = sqstd_throwerrorf(vm, "the index '%.50s' does not exist", key); } + var.Free(); sq_pop(vm, 1); - return sqstd_throwerrorf(vm, "invalid arith op +"); -} - -SQInteger sub_stub(HSQUIRRELVM vm) -{ - ClassInstanceData* classInstanceData = nullptr; - sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); - - ScriptVariant_t var; - getVariant( vm, 1, var ); - - if (classInstanceData && - classInstanceData->instance && - classInstanceData->desc->pHelper) - { - ScriptVariant_t *result = classInstanceData->desc->pHelper->Subtract( classInstanceData->instance, var ); - if (result != nullptr) - { - PushVariant( vm, *result ); - sq_pop(vm, 1); - return 1; - } - } - - sq_pop(vm, 1); - return sqstd_throwerrorf(vm, "invalid arith op -"); -} - -SQInteger mul_stub(HSQUIRRELVM vm) -{ - ClassInstanceData* classInstanceData = nullptr; - sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); - - ScriptVariant_t var; - getVariant( vm, 1, var ); - - if (classInstanceData && - classInstanceData->instance && - classInstanceData->desc->pHelper ) - { - ScriptVariant_t *result = classInstanceData->desc->pHelper->Add( classInstanceData->instance, var ); - if (result != nullptr) - { - PushVariant( vm, *result ); - sq_pop(vm, 1); - return 1; - } - } - - sq_pop(vm, 1); - return sqstd_throwerrorf(vm, "invalid arith op *"); -} - -SQInteger div_stub(HSQUIRRELVM vm) -{ - ClassInstanceData* classInstanceData = nullptr; - sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); - - ScriptVariant_t var; - getVariant( vm, 1, var ); - - if (classInstanceData && - classInstanceData->instance && - classInstanceData->desc->pHelper ) - { - ScriptVariant_t *result = classInstanceData->desc->pHelper->Add( classInstanceData->instance, var ); - if (result != nullptr) - { - PushVariant( vm, *result ); - sq_pop(vm, 1); - return 1; - } - } - - sq_pop(vm, 1); - return sqstd_throwerrorf(vm, "invalid arith op /"); + return sq_retval; } SQInteger IsValid_stub(HSQUIRRELVM vm) @@ -1984,7 +1960,7 @@ bool SquirrelVM::Init() if (vm_ == nullptr) return false; - sq_setforeignptr(vm_, this); + sq_setsharedforeignptr(vm_, this); sq_resetobject(&lastError_); sq_setprintfunc(vm_, printfunc, errorfunc); @@ -2072,15 +2048,37 @@ void SquirrelVM::Shutdown() } } -bool SquirrelVM::ConnectDebugger() +bool VScriptRunScript( const char *pszScriptName, HSCRIPT hScope, bool bWarnMissing ); + +bool SquirrelVM::ConnectDebugger( int port ) { - // TODO: Debugger support - return false; + if ( !debugger_ ) + { + debugger_ = sqdbg_attach_debugger( vm_ ); + + if ( sqdbg_listen_socket( debugger_, port ) != 0 ) + { + sqdbg_destroy_debugger( vm_ ); + debugger_ = nullptr; + return false; + } + } + else + { + sqdbg_frame( debugger_ ); + } + + VScriptRunScript( "sqdbg_definitions.nut", NULL, false ); + return true; } void SquirrelVM::DisconnectDebugger() { - // TODO: Debugger support + if ( debugger_ ) + { + sqdbg_destroy_debugger( vm_ ); + debugger_ = nullptr; + } } ScriptLanguage_t SquirrelVM::GetLanguage() @@ -2100,7 +2098,10 @@ void SquirrelVM::AddSearchPath(const char* pszSearchPath) bool SquirrelVM::Frame(float simTime) { - // TODO: Frame support + if ( debugger_ ) + { + sqdbg_frame( debugger_ ); + } return false; } @@ -2127,12 +2128,24 @@ HSCRIPT SquirrelVM::CompileScript(const char* pszScript, const char* pszId) { SquirrelSafeCheck safeCheck(vm_); - Assert(vm_); - if (pszId == nullptr) pszId = ""; - if (SQ_FAILED(sq_compilebuffer(vm_, pszScript, strlen(pszScript), pszId, SQTrue))) + bool bUnnamed = ( pszId == nullptr ); + if ( bUnnamed ) + { + pszId = ""; + } + + int nScriptLen = strlen(pszScript); + + if (SQ_FAILED(sq_compilebuffer(vm_, pszScript, nScriptLen, pszId, SQTrue))) { return nullptr; } + + if ( debugger_ && !bUnnamed ) + { + sqdbg_on_script_compile( debugger_, pszScript, nScriptLen, pszId, strlen(pszId) ); + } + HSQOBJECT* obj = new HSQOBJECT; sq_resetobject(obj); sq_getstackobj(vm_, -1, obj); @@ -2415,6 +2428,7 @@ void SquirrelVM::RegisterFunction(ScriptFunctionBinding_t* pScriptFunction) return; char typemask[64]; + Assert(pScriptFunction->m_desc.m_Parameters.Count() < sizeof(typemask)); if (!CreateParamCheck(*pScriptFunction, typemask)) { return; @@ -2500,22 +2514,6 @@ bool SquirrelVM::RegisterClass(ScriptClassDesc_t* pClassDesc) sq_newclosure(vm_, set_stub, 0); sq_newslot(vm_, -3, SQFalse); - sq_pushstring(vm_, "_add", -1); - sq_newclosure(vm_, add_stub, 0); - sq_newslot(vm_, -3, SQFalse); - - sq_pushstring(vm_, "_sub", -1); - sq_newclosure(vm_, sub_stub, 0); - sq_newslot(vm_, -3, SQFalse); - - sq_pushstring(vm_, "_mul", -1); - sq_newclosure(vm_, mul_stub, 0); - sq_newslot(vm_, -3, SQFalse); - - sq_pushstring(vm_, "_div", -1); - sq_newclosure(vm_, div_stub, 0); - sq_newslot(vm_, -3, SQFalse); - sq_pushstring(vm_, "IsValid", -1); sq_newclosure(vm_, IsValid_stub, 0); sq_newslot(vm_, -3, SQFalse); @@ -2534,6 +2532,7 @@ bool SquirrelVM::RegisterClass(ScriptClassDesc_t* pClassDesc) auto& scriptFunction = pClassDesc->m_FunctionBindings[i]; char typemask[64]; + Assert(scriptFunction.m_desc.m_Parameters.Count() < sizeof(typemask)); if (!CreateParamCheck(scriptFunction, typemask)) { Warning("Unable to create param check for %s.%s\n", @@ -2652,7 +2651,7 @@ void SquirrelVM::RegisterHook(ScriptHook_t* pHookDesc) RegisterHookDocumentation(vm_, pHookDesc, pHookDesc->m_desc, nullptr); } -HSCRIPT SquirrelVM::RegisterInstance(ScriptClassDesc_t* pDesc, void* pInstance, bool bAllowDestruct) +HSCRIPT SquirrelVM::RegisterInstance(ScriptClassDesc_t* pDesc, void* pInstance, bool bRefCounted) { SquirrelSafeCheck safeCheck(vm_); @@ -2674,15 +2673,19 @@ HSCRIPT SquirrelVM::RegisterInstance(ScriptClassDesc_t* pDesc, void* pInstance, } { - SQUserPointer p; - sq_getinstanceup(vm_, -1, &p, 0); - new(p) ClassInstanceData(pInstance, pDesc, nullptr, bAllowDestruct); + ClassInstanceData *self; + sq_getinstanceup(vm_, -1, (SQUserPointer*)&self, 0); + new(self) ClassInstanceData(pInstance, pDesc, nullptr, bRefCounted); + + // can't delete the instance if it doesn't have a destructor + // if the instance doesn't have a constructor, + // the class needs to register the destructor with DEFINE_SCRIPT_REFCOUNTED_INSTANCE() + Assert( !bRefCounted || self->desc->m_pfnDestruct ); } - sq_setreleasehook(vm_, -1, bAllowDestruct ? &destructor_stub : &destructor_stub_instance); + sq_setreleasehook(vm_, -1, bRefCounted ? &destructor_stub : &destructor_stub_instance); HSQOBJECT* obj = new HSQOBJECT; - sq_resetobject(obj); sq_getstackobj(vm_, -1, obj); sq_addref(vm_, obj); sq_pop(vm_, 3); @@ -2710,22 +2713,22 @@ void SquirrelVM::SetInstanceUniqeId(HSCRIPT hInstance, const char* pszId) void SquirrelVM::RemoveInstance(HSCRIPT hInstance) { + if (!hInstance) + return; + SquirrelSafeCheck safeCheck(vm_); - if (!hInstance) return; HSQOBJECT* obj = (HSQOBJECT*)hInstance; + ClassInstanceData *self; + sq_pushobject(vm_, *obj); - - SQUserPointer self; - sq_getinstanceup(vm_, -1, &self, nullptr); - - ((ClassInstanceData*)self)->~ClassInstanceData(); - + sq_getinstanceup(vm_, -1, (SQUserPointer*)&self, nullptr); sq_setinstanceup(vm_, -1, nullptr); sq_setreleasehook(vm_, -1, nullptr); sq_pop(vm_, 1); - sq_release(vm_, obj); + + self->~ClassInstanceData(); delete obj; } @@ -3074,6 +3077,7 @@ void SquirrelVM::ReleaseValue(ScriptVariant_t& value) // Let's prevent this being called again and giving some UB value.m_type = FIELD_VOID; + value.m_flags = 0; } bool SquirrelVM::ClearValue(HSCRIPT hScope, const char* pszKey) @@ -3158,6 +3162,20 @@ bool SquirrelVM::ArrayAppend(HSCRIPT hArray, const ScriptVariant_t &val) return ret; } +HSCRIPT SquirrelVM::CopyObject(HSCRIPT obj) +{ + if ( !obj ) + return NULL; + + HSQOBJECT *ret = new HSQOBJECT; + *ret = *(HSQOBJECT*)obj; + sq_addref( vm_, ret ); + return (HSCRIPT)ret; +} + +//------------------------------------------------------------- +//------------------------------------------------------------- + enum ClassType { VectorClassType = 0, @@ -3165,876 +3183,1342 @@ enum ClassType ScriptClassType = 2 }; -SQInteger closure_write(SQUserPointer file, SQUserPointer p, SQInteger size) +// Use iterator as SQTable::_nodes is private +#define FOREACH_SQTABLE( pTable, key, val )\ + SQInteger i = 0;\ + SQObjectPtr pi = i;\ + for ( ; (i = pTable->Next( false, pi, key, val )) != -1; pi._unVal.nInteger = i ) + + +#define NATIVE_NAME_READBUF_SIZE 128 + +void SquirrelVM::WriteObject( const SQObjectPtr &obj, CUtlBuffer* pBuffer, WriteStateMap& writeState ) { - ((CUtlBuffer*)file)->Put(p, size); - return size; -} + pBuffer->PutInt( obj._type ); -void SquirrelVM::WriteObject(CUtlBuffer* pBuffer, WriteStateMap& writeState, SQInteger idx) -{ - SquirrelSafeCheck safeCheck(vm_); - - HSQOBJECT obj; - sq_resetobject(&obj); - sq_getstackobj(vm_, idx, &obj); - - switch (obj._type) + switch ( obj._type ) { case OT_NULL: - { - pBuffer->PutInt(OT_NULL); break; - } + case OT_INTEGER: - { - pBuffer->PutInt(OT_INTEGER); - pBuffer->PutInt64(sq_objtointeger(&obj)); +#ifdef _SQ64 + pBuffer->PutInt64( obj._unVal.nInteger ); +#else + pBuffer->PutInt( obj._unVal.nInteger ); +#endif break; - } + case OT_FLOAT: - { - pBuffer->PutInt(OT_FLOAT); - pBuffer->PutFloat(sq_objtofloat(&obj)); +#ifdef SQUSEDOUBLE + pBuffer->PutDouble( obj._unVal.fFloat ); +#else + pBuffer->PutFloat( obj._unVal.fFloat ); +#endif break; - } + case OT_BOOL: - { - pBuffer->PutInt(OT_BOOL); - pBuffer->PutChar(sq_objtobool(&obj)); + Assert( ( obj._unVal.nInteger & -2 ) == 0 ); + pBuffer->PutChar( obj._unVal.nInteger ); break; - } + case OT_STRING: - { - pBuffer->PutInt(OT_STRING); - const char* val = nullptr; - SQInteger size = 0; - sq_getstringandsize(vm_, idx, &val, &size); - pBuffer->PutInt(size); - pBuffer->Put(val, size); + pBuffer->PutInt( obj._unVal.pString->_len ); + pBuffer->Put( obj._unVal.pString->_val, obj._unVal.pString->_len ); break; - } + case OT_TABLE: { - pBuffer->PutInt(OT_TABLE); - if (writeState.CheckCache(pBuffer, obj._unVal.pTable)) - { + SQTable *pThis = obj._unVal.pTable; + + if ( writeState.CheckCache( pThis, pBuffer ) ) break; - } - sq_getdelegate(vm_, idx); - WriteObject(pBuffer, writeState, -1); - sq_poptop(vm_); - int count = sq_getsize(vm_, idx); - sq_push(vm_, idx); - sq_pushnull(vm_); - pBuffer->PutInt(count); - while (SQ_SUCCEEDED(sq_next(vm_, -2))) + + int count = pThis->CountUsed(); + DBG_CODE_NOSCOPE( int nCountPut = pBuffer->TellPut(); ) + pBuffer->PutInt( count ); + + pBuffer->PutChar( pThis->_delegate != NULL ); + if ( pThis->_delegate ) { - WriteObject(pBuffer, writeState, -2); - WriteObject(pBuffer, writeState, -1); - sq_pop(vm_, 2); - --count; + WriteObject( pThis->_delegate, pBuffer, writeState ); } - sq_pop(vm_, 2); - Assert(count == 0); + + { + SQObjectPtr key, val; + FOREACH_SQTABLE( pThis, key, val ) + { + WriteObject( key, pBuffer, writeState ); + WriteObject( val, pBuffer, writeState ); + DBG_CODE( count--; ); + } + } + +#ifdef _DEBUG + if ( count ) + { + int prev = *(int*)( (char*)pBuffer->Base() + nCountPut ); + *(int*)( (char*)pBuffer->Base() + nCountPut ) = prev - count; + Warning( "Table serialisation error, changed count %d->%d\n", prev, prev - count ); + Assert(0); + } +#endif break; } case OT_ARRAY: { - pBuffer->PutInt(OT_ARRAY); - if (writeState.CheckCache(pBuffer, obj._unVal.pArray)) - { + SQArray *pThis = obj._unVal.pArray; + + if ( writeState.CheckCache( pThis, pBuffer ) ) break; - } - int count = sq_getsize(vm_, idx); - pBuffer->PutInt(count); - sq_push(vm_, idx); - sq_pushnull(vm_); - while (SQ_SUCCEEDED(sq_next(vm_, -2))) + + int count = pThis->_values.size(); + DBG_CODE_NOSCOPE( + int counter = count; + int nCountPut = pBuffer->TellPut(); + ) + pBuffer->PutInt( count ); + + for ( int i = 0; i < count; ++i ) { - WriteObject(pBuffer, writeState, -1); - sq_pop(vm_, 2); - --count; + WriteObject( pThis->_values[i], pBuffer, writeState ); + DBG_CODE( counter--; ); } - sq_pop(vm_, 2); - Assert(count == 0); + +#ifdef _DEBUG + if ( counter ) + { + int prev = *(int*)( (char*)pBuffer->Base() + nCountPut ); + *(int*)( (char*)pBuffer->Base() + nCountPut ) = prev - counter; + Warning( "Array serialisation error, changed count %d->%d\n", prev, prev - count ); + Assert(0); + } +#endif break; } case OT_CLOSURE: { - pBuffer->PutInt(OT_CLOSURE); - if (writeState.CheckCache(pBuffer, obj._unVal.pClosure)) - { + SQClosure *pThis = obj._unVal.pClosure; + + if ( writeState.CheckCache( pThis, pBuffer ) ) break; - } - SQInteger nparams = 0, nfreevars = 0; - sq_getclosureinfo(vm_, idx, &nparams, &nfreevars); - if (nfreevars == 0 && _closure(obj)->_function->_defaultparams == 0) + Assert( pThis->_function ); + + WriteObject( pThis->_function, pBuffer, writeState ); + + pBuffer->PutChar( pThis->_root != NULL ); + if ( pThis->_root ) { - pBuffer->PutChar(0); - - sq_push(vm_, idx); - if (SQ_FAILED(sq_writeclosure(vm_, closure_write, pBuffer))) - { - Error("Failed to write closure"); - } - sq_pop(vm_, 1); + WriteObject( pThis->_root, pBuffer, writeState ); } - else + + pBuffer->PutChar( pThis->_env != NULL ); + if ( pThis->_env ) { - // Unfortunately we can't use sq_writeclosure because it doesn't work well with - // outer variables - - pBuffer->PutChar(1); - - if (!_closure(obj)->Save(vm_, pBuffer, closure_write)) - { - Error("Failed to write closure\n"); - } - - int noutervalues = _closure(obj)->_function->_noutervalues; - for (int i = 0; i < noutervalues; ++i) - { - sq_pushobject(vm_, _closure(obj)->_outervalues[i]); - WriteObject(pBuffer, writeState, -1); - sq_poptop(vm_); - } - - int ndefaultparams = _closure(obj)->_function->_ndefaultparams; - for (int i = 0; i < ndefaultparams; ++i) - { - sq_pushobject(vm_, _closure(obj)->_defaultparams[i]); - WriteObject(pBuffer, writeState, -1); - sq_poptop(vm_); - } + WriteObject( pThis->_env, pBuffer, writeState ); } - if (_closure(obj)->_env) + // NOTE: Used for class functions to access the base class via 'base' keyword. + // This is assigned in SQClass::NewSlot(). + // The alternative to writing SQClosure::_base would be always using SQClass::NewSlot() on class restore. + pBuffer->PutChar( pThis->_base != NULL ); + if ( pThis->_base ) { - sq_pushobject(vm_, _closure(obj)->_env->_obj); + WriteObject( pThis->_base, pBuffer, writeState ); } - else + + int count = pThis->_function->_noutervalues; + pBuffer->PutInt( count ); + for ( int i = 0; i < count; ++i ) { - sq_pushnull(vm_); + WriteObject( pThis->_outervalues[i], pBuffer, writeState ); + } + + count = pThis->_function->_ndefaultparams; + pBuffer->PutInt( count ); + for ( int i = 0; i < count; ++i ) + { + WriteObject( pThis->_defaultparams[i], pBuffer, writeState ); } - WriteObject(pBuffer, writeState, -1); - sq_poptop(vm_); break; } + // TODO: Nameless and non-global native closures + // if _name == NULL and _noutervalues == 0 then it is member IsValid etc. + // if _name != NULL and _outervalues[0] == USERPOINTER then global or member functions + // if this is a non-bound (!_env) member native closure, it will be written but won't be read, + // or wrong closure will be read if a closure with the same name exists in the root table. + // + // An identifier can be added to script descriptions to determine the type of this native closure to read/write case OT_NATIVECLOSURE: { - pBuffer->PutInt(OT_NATIVECLOSURE); - sq_getclosurename(vm_, idx); + SQNativeClosure *pThis = obj._unVal.pNativeClosure; - const char* name = nullptr; - sq_getstring(vm_, -1, &name); - pBuffer->PutString(name); +#ifdef _DEBUG + bool bAsserted = false; - sq_pop(vm_, 1); - break; - } - case OT_CLASS: - { - pBuffer->PutInt(OT_CLASS); - if (writeState.CheckCache(pBuffer, obj._unVal.pClass)) + if ( pThis->_noutervalues && pThis->_name._type == OT_STRING && pThis->_name._unVal.pString ) { - break; - } - SQUserPointer typetag = nullptr; - sq_gettypetag(vm_, idx, &typetag); - if (typetag == TYPETAG_VECTOR) - { - pBuffer->PutInt(VectorClassType); - } - else if (typetag != nullptr) - { - // Seems so dangerous to treat typetag as ScriptClassDesc_t* - // however we don't really have an option without some sort of tagged - // pointer. - pBuffer->PutInt(NativeClassType); - pBuffer->PutString(((ScriptClassDesc_t*)typetag)->m_pszScriptName); - } - else - { - // HACK: We can't easily identify when the type is a builtin to exclude - // so we just check against the only class we need to deal with at the moment - // which is "regexp" - const char* builtinName = nullptr; - if (_class(obj) == _class(regexpClass_)) + Assert( pThis->_noutervalues == 1 ); + Assert( pThis->_outervalues[0]._type == OT_USERPOINTER ); + + const SQUserPointer userpointer = pThis->_outervalues[0]._unVal.pUserPointer; + const CUtlVector< ScriptClassDesc_t* > &classes = ScriptClassDesc_t::AllClassesDesc(); + FOR_EACH_VEC( classes, i ) { - builtinName = "regexp"; - } - - if (builtinName) - { - pBuffer->PutInt(NativeClassType); - pBuffer->PutString(builtinName); - break; - } - - pBuffer->PutInt(ScriptClassType); - - sq_getbase(vm_, idx); - WriteObject(pBuffer, writeState, -1); - sq_pop(vm_, 1); - - sq_push(vm_, idx); - sq_pushnull(vm_); - sq_getattributes(vm_, -2); - WriteObject(pBuffer, writeState, -1); - sq_pop(vm_, 1); - - sq_pushnull(vm_); - while (SQ_SUCCEEDED(sq_next(vm_, -2))) - { - pBuffer->PutChar(1); - // TODO: Member Attributes - WriteObject(pBuffer, writeState, -2); - WriteObject(pBuffer, writeState, -1); - sq_pop(vm_, 2); - } - sq_pop(vm_, 2); - - { - // HACK: Meta-methods are not included in an iterator of OT_CLASS - SQObjectPtrVec& metamethods = *(_ss(vm_)->_metamethods); - for (int i = 0; i < MT_LAST; ++i) + const CUtlVector< ScriptFunctionBinding_t > &funcs = classes[i]->m_FunctionBindings; + FOR_EACH_VEC( funcs, j ) { - if (sq_type(_class(obj)->_metamethods[i]) != OT_NULL) + if ( &funcs[j] == userpointer ) { - pBuffer->PutChar(1); - sq_pushobject(vm_, metamethods[i]); - sq_pushobject(vm_, _class(obj)->_metamethods[i]); - WriteObject(pBuffer, writeState, -2); - WriteObject(pBuffer, writeState, -1); - sq_pop(vm_, 2); + AssertMsg( 0, "SquirrelVM: Native closure is not saved! '%s' -> %s::%s", + pThis->_name._unVal.pString->_val, + classes[i]->m_pszScriptName, + funcs[j].m_desc.m_pszScriptName ); + bAsserted = true; + goto done; } } } - - pBuffer->PutChar(0); + done:; } +#endif + + if ( pThis->_name._type == OT_STRING && pThis->_name._unVal.pString && !pThis->_env ) + { + Assert( pThis->_name._unVal.pString->_len < NATIVE_NAME_READBUF_SIZE ); + pBuffer->Put( pThis->_name._unVal.pString->_val, pThis->_name._unVal.pString->_len + 1 ); + break; + } + +#ifdef _DEBUG + if ( pThis->_name._type == OT_STRING && pThis->_name._unVal.pString ) + { + if ( !bAsserted ) + AssertMsg( 0, "SquirrelVM: Native closure is not saved! '%s'", pThis->_name._unVal.pString->_val ); + } + else + { + AssertMsg( 0, "SquirrelVM: Native closure is not saved!" ); + } +#endif + + Assert( *(int*)pBuffer->PeekPut( -(int)sizeof(int) ) == OT_NATIVECLOSURE ); + *(int*)pBuffer->PeekPut( -(int)sizeof(int) ) = OT_NULL; + + break; + } + case OT_FUNCPROTO: + { + SQFunctionProto *pThis = obj._unVal.pFunctionProto; + + if ( writeState.CheckCache( pThis, pBuffer ) ) + break; + + // NOTE: SQFunctionProto::Save cannot save non-primitive literals. Save everything manually! + pBuffer->PutInt( pThis->_nliterals ); + pBuffer->PutInt( pThis->_nparameters ); + pBuffer->PutInt( pThis->_noutervalues ); + pBuffer->PutInt( pThis->_nlocalvarinfos ); + pBuffer->PutInt( pThis->_nlineinfos ); + pBuffer->PutInt( pThis->_ndefaultparams ); + pBuffer->PutInt( pThis->_ninstructions ); + pBuffer->PutInt( pThis->_nfunctions ); + + WriteObject( pThis->_sourcename, pBuffer, writeState ); + WriteObject( pThis->_name, pBuffer, writeState ); + + for ( int i = 0; i < pThis->_nliterals; ++i ) + { + WriteObject( pThis->_literals[i], pBuffer, writeState ); + } + + for ( int i = 0; i < pThis->_nparameters; ++i ) + { + WriteObject( pThis->_parameters[i], pBuffer, writeState ); + } + + for ( int i = 0; i < pThis->_noutervalues; ++i ) + { + SQOuterVar &p = pThis->_outervalues[i]; + pBuffer->PutUnsignedInt( p._type ); + WriteObject( p._src, pBuffer, writeState ); + WriteObject( p._name, pBuffer, writeState ); + } + + for ( int i = 0; i < pThis->_nlocalvarinfos; ++i ) + { + SQLocalVarInfo &p = pThis->_localvarinfos[i]; + WriteObject( p._name, pBuffer, writeState ); + pBuffer->PutUnsignedInt( p._pos ); + pBuffer->PutUnsignedInt( p._start_op ); + pBuffer->PutUnsignedInt( p._end_op ); + } + + pBuffer->Put( pThis->_lineinfos, sizeof(SQLineInfo) * pThis->_nlineinfos ); + pBuffer->Put( pThis->_defaultparams, sizeof(SQInteger) * pThis->_ndefaultparams ); + pBuffer->Put( pThis->_instructions, sizeof(SQInstruction) * pThis->_ninstructions ); + + for ( int i = 0; i < pThis->_nfunctions; ++i ) + { + WriteObject( pThis->_functions[i], pBuffer, writeState ); + } + + pBuffer->PutInt( pThis->_stacksize ); + pBuffer->PutChar( pThis->_bgenerator ); + pBuffer->PutInt( pThis->_varparams ); + + break; + } + case OT_CLASS: + { + SQClass *pThis = obj._unVal.pClass; + + if ( writeState.CheckCache( pThis, pBuffer ) ) + break; + + SQUserPointer typetag = pThis->_typetag; + + if ( typetag != NULL ) + { + if ( typetag == TYPETAG_VECTOR ) + { + pBuffer->PutChar( VectorClassType ); + } + else + { + ScriptClassDesc_t *pDesc = (ScriptClassDesc_t*)typetag; + pBuffer->PutChar( NativeClassType ); + pBuffer->PutString( pDesc->m_pszScriptName ); + Assert( strlen(pDesc->m_pszScriptName) < NATIVE_NAME_READBUF_SIZE ); + } + + break; + } + + if ( pThis == _class(regexpClass_) ) + { + pBuffer->PutChar( NativeClassType ); + pBuffer->PutString( "regexp" ); + + break; + } + + pBuffer->PutChar( ScriptClassType ); + + pBuffer->PutChar( pThis->_base != NULL ); + if ( pThis->_base ) + { + WriteObject( pThis->_base, pBuffer, writeState ); + } + + // FIXME: This is inefficient for inheritance, broken for native class inheritance + + WriteObject( pThis->_members, pBuffer, writeState ); + + int count = pThis->_defaultvalues.size(); + pBuffer->PutInt( count ); + + for ( int i = 0; i < count; ++i ) + { + WriteObject( pThis->_defaultvalues[i].val, pBuffer, writeState ); + } + + count = pThis->_methods.size(); + pBuffer->PutInt( count ); + + for ( int i = 0; i < count; ++i ) + { + WriteObject( pThis->_methods[i].val, pBuffer, writeState ); + } + + // only write valid metmathods instead of writing 18 nulls every time + int mask = 0; + int nMaskPut = pBuffer->TellPut(); + pBuffer->PutInt( mask ); + + // all metamethods can fit in an int32 mask + Assert( SQMetaMethod::MT_LAST <= (sizeof(int) << 3) ); + + for ( unsigned i = 0; i < SQMetaMethod::MT_LAST; ++i ) + { + if ( pThis->_metamethods[i]._type != OT_NULL ) + { + mask |= 1 << i; + WriteObject( pThis->_metamethods[i], pBuffer, writeState ); + } + } + + if ( mask ) + { + *(int*)( (char*)pBuffer->Base() + nMaskPut ) = mask; + } + + pBuffer->PutInt( pThis->_constructoridx ); + break; } case OT_INSTANCE: { - pBuffer->PutInt(OT_INSTANCE); - if (writeState.CheckCache(pBuffer, obj._unVal.pInstance)) - { - break; - } - sq_getclass(vm_, idx); - WriteObject(pBuffer, writeState, -1); - sq_pop(vm_, 1); + SQInstance *pThis = obj._unVal.pInstance; - if (_instance(obj)->_class == _class(regexpClass_)) + if ( writeState.CheckCache( pThis, pBuffer ) ) + break; + + WriteObject( pThis->_class, pBuffer, writeState ); + + if ( pThis->_class == _class(regexpClass_) ) { - sq_push(vm_, idx); - sq_pushstring(vm_, "pattern_", -1); - sq_rawget(vm_, -2); - WriteObject(pBuffer, writeState, -1); - sq_pop(vm_, 2); + SQObjectPtr key = SQString::Create( _ss(vm_), "pattern_" ); + SQObjectPtr val; + pThis->Get( key, val ); + WriteObject( val, pBuffer, writeState ); + break; } + SQUserPointer typetag = pThis->_class->_typetag; + if ( typetag ) { - // HACK: No way to get the default values part from accessing the class directly - SQUnsignedInteger nvalues = _instance(obj)->_class->_defaultvalues.size(); - for (SQUnsignedInteger n = 0; n < nvalues; n++) { - sq_pushobject(vm_, _instance(obj)->_values[n]); - WriteObject(pBuffer, writeState, -1); - sq_pop(vm_, 1); - } - } - - SQUserPointer typetag; - sq_gettypetag(vm_, idx, &typetag); - - if (typetag == TYPETAG_VECTOR) - { - Vector* v = nullptr; - sq_getinstanceup(vm_, idx, (SQUserPointer*)&v, TYPETAG_VECTOR); - Assert(v); - pBuffer->PutFloat(v->x); - pBuffer->PutFloat(v->y); - pBuffer->PutFloat(v->z); - } - else if (typetag) - { - ClassInstanceData* pClassInstanceData; - sq_getinstanceup(vm_, idx, (SQUserPointer*)&pClassInstanceData, typetag); - - if (pClassInstanceData) + if ( typetag == TYPETAG_VECTOR ) { - if (pClassInstanceData->desc->m_pszDescription[0] == SCRIPT_SINGLETON[0]) - { - // Do nothing, singleton should be created from just the class - } - else if (!pClassInstanceData->instanceId.IsEmpty()) - { - pBuffer->PutString(pClassInstanceData->instanceId); - - pBuffer->PutChar(pClassInstanceData->allowDestruct ? 1 : 0); - } - else - { - DevWarning("SquirrelVM::WriteObject: Unable to find instanceID for object of type %s, unable to serialize\n", - pClassInstanceData->desc->m_pszClassname); - pBuffer->PutString(""); - } + Vector *v = (Vector *)pThis->_userpointer; + Assert(v); + pBuffer->PutFloat( v->x ); + pBuffer->PutFloat( v->y ); + pBuffer->PutFloat( v->z ); } else { - pBuffer->PutString(""); + ClassInstanceData *pData = (ClassInstanceData *)pThis->_userpointer; + if ( pData ) + { + if ( pData->desc->m_pszDescription[0] == SCRIPT_SINGLETON[0] ) + { + // Do nothing, singleton should be created from just the class + } + else if ( !pData->instanceId.IsEmpty() ) + { + Assert( strlen(pData->instanceId.Get()) < NATIVE_NAME_READBUF_SIZE ); + pBuffer->PutString( pData->instanceId ); + pBuffer->PutChar( pData->refCounted ? 1 : 0 ); + } + else + { + DevWarning( "SquirrelVM::WriteObject: no instanceID for '%s', unable to serialize\n", + pData->desc->m_pszClassname ); + + pBuffer->PutString( "" ); + } + } + else + { + pBuffer->PutString( "" ); + } } + + break; + } + + // NOTE: move this up if native classes can have default values + int count = pThis->_class->_defaultvalues.size(); + pBuffer->PutInt( count ); + for ( int i = 0; i < count; ++i ) + { + WriteObject( pThis->_values[i], pBuffer, writeState ); } break; } case OT_WEAKREF: { - pBuffer->PutInt(OT_WEAKREF); - sq_getweakrefval(vm_, idx); - WriteObject(pBuffer, writeState, -1); - sq_pop(vm_, 1); + SQWeakRef *pThis = obj._unVal.pWeakRef; + WriteObject( pThis->_obj, pBuffer, writeState ); break; } - case OT_FUNCPROTO: //internal usage only + case OT_OUTER: { - pBuffer->PutInt(OT_FUNCPROTO); + SQOuter *pThis = obj._unVal.pOuter; - if (writeState.CheckCache(pBuffer, obj._unVal.pFunctionProto)) - { + if ( writeState.CheckCache( pThis, pBuffer ) ) break; - } - _funcproto(obj)->Save(vm_, pBuffer, closure_write); + Assert( pThis->_valptr == &pThis->_value ); // otherwise untested + + WriteObject( pThis->_value, pBuffer, writeState ); + break; } - case OT_OUTER: //internal usage only + case OT_THREAD: { - pBuffer->PutInt(OT_OUTER); + SQVM *pThis = obj._unVal.pThread; - if (writeState.CheckCache(pBuffer, obj._unVal.pOuter)) - { + if ( writeState.CheckCache( pThis, pBuffer ) ) break; + + pBuffer->PutInt( pThis->_stack.size() ); + pBuffer->PutInt( pThis->_callsstacksize ); + + if ( pThis->_callsstacksize ) + { + int stackidx = -1; + + for ( int i = pThis->_callsstacksize; i--; ) + { + const SQVM::CallInfo *ci = &pThis->_callsstack[i]; + + if ( pThis->ci == ci ) + stackidx = i; + + Assert( !ci->_generator ); + Assert( ci->_ip && ci->_ip >= ci->_closure._unVal.pClosure->_function->_instructions ); + Assert( pThis->_etraps.size() >= (SQUnsignedInteger)ci->_etraps ); + Assert( ci->_closure._type == OT_CLOSURE && ci->_closure._unVal.pClosure ); + + WriteObject( ci->_closure, pBuffer, writeState ); + + int offset = (int)ci->_ip - (int)ci->_closure._unVal.pClosure->_function->_instructions; + pBuffer->PutInt( offset ); + pBuffer->PutInt( ci->_etraps ); + pBuffer->PutInt( ci->_prevstkbase ); + pBuffer->PutInt( ci->_prevtop ); + pBuffer->PutInt( ci->_target ); + pBuffer->PutInt( ci->_ncalls ); + pBuffer->PutChar( ci->_root ); + + for ( int j = ci->_etraps; j--; ) + { + const SQExceptionTrap &et = pThis->_etraps[j]; + pBuffer->PutInt( et._extarget ); + pBuffer->PutInt( et._stackbase ); + pBuffer->PutInt( et._stacksize ); + Assert( et._ip == ci->_ip ); + } + } + + Assert( stackidx >= 0 && stackidx < pThis->_callsstacksize ); + pBuffer->PutInt( stackidx ); } - sq_pushobject(vm_, *_outer(obj)->_valptr); - WriteObject(pBuffer, writeState, -1); - sq_poptop(vm_); + pBuffer->PutInt( pThis->_nnativecalls ); + pBuffer->PutInt( pThis->_nmetamethodscall ); + + pBuffer->PutChar( pThis->_suspended ); + pBuffer->PutChar( pThis->_suspended_root ); + pBuffer->PutInt( pThis->_suspended_target ); + pBuffer->PutInt( pThis->_suspended_traps ); + + WriteVM( pThis, pBuffer, writeState ); break; } - // case OT_USERDATA: - // case OT_GENERATOR: - // case OT_USERPOINTER: - // case OT_THREAD: - // + case OT_GENERATOR: + { + SQGenerator *pThis = obj._unVal.pGenerator; + + if ( writeState.CheckCache( pThis, pBuffer ) ) + break; + + pBuffer->PutChar( pThis->_state ); + + if ( pThis->_state == SQGenerator::SQGeneratorState::eDead ) + break; + + WriteObject( pThis->_closure, pBuffer, writeState ); + + const SQVM::CallInfo &ci = pThis->_ci; + + Assert( !ci._generator ); + Assert( pThis->_closure._unVal.pClosure == ci._closure._unVal.pClosure ); + Assert( ci._ip && ci._ip >= ci._closure._unVal.pClosure->_function->_instructions ); + Assert( pThis->_etraps.size() >= (SQUnsignedInteger)ci._etraps ); + + int offset = (int)ci._ip - (int)ci._closure._unVal.pClosure->_function->_instructions; + pBuffer->PutInt( offset ); + pBuffer->PutInt( ci._etraps ); + pBuffer->PutInt( ci._prevstkbase ); + pBuffer->PutInt( ci._prevtop ); + pBuffer->PutInt( ci._target ); + pBuffer->PutInt( ci._ncalls ); + pBuffer->PutChar( ci._root ); + + for ( int j = ci._etraps; j--; ) + { + const SQExceptionTrap &et = pThis->_etraps[j]; + pBuffer->PutInt( et._extarget ); + pBuffer->PutInt( et._stackbase ); + pBuffer->PutInt( et._stacksize ); + Assert( et._ip == ci._ip ); + } + + int stacksize = pThis->_stack.size(); + pBuffer->PutInt( stacksize ); + for ( int i = 0; i < stacksize; ++i ) + { + WriteObject( pThis->_stack[i], pBuffer, writeState ); + } + + break; + } + case OT_USERDATA: + case OT_USERPOINTER: + Assert(0); + break; default: - Warning("SquirrelVM::WriteObject: Unexpected type %d", sq_gettype(vm_, idx)); - // Save a null instead - pBuffer->PutInt(OT_NULL); + AssertMsgAlways( 0, "SquirrelVM::WriteObject: unknown type" ); } } -void SquirrelVM::WriteState(CUtlBuffer* pBuffer) + +void SquirrelVM::ReadObject( SQObjectPtr &pObj, CUtlBuffer* pBuffer, ReadStateMap& readState ) { - SquirrelSafeCheck safeCheck(vm_); + SQObject obj; + obj._type = (SQObjectType)pBuffer->GetInt(); + DBG_CODE( obj._unVal.raw = 0xCC; ); - WriteStateMap writeState; - - sq_pushroottable(vm_); - - // Not really a check cache, but adds the root - HSQOBJECT obj; - sq_resetobject(&obj); - sq_getstackobj(vm_, -1, &obj); - writeState.CheckCache(pBuffer, _table(obj)); - - int count = sq_getsize(vm_, 1); - sq_pushnull(vm_); - pBuffer->PutInt(count); - - while (SQ_SUCCEEDED(sq_next(vm_, -2))) - { - WriteObject(pBuffer, writeState, -2); - WriteObject(pBuffer, writeState, -1); - sq_pop(vm_, 2); - --count; - } - sq_pop(vm_, 2); - Assert(count == 0); -} - -SQInteger closure_read(SQUserPointer file, SQUserPointer buf, SQInteger size) -{ - CUtlBuffer* pBuffer = (CUtlBuffer*)file; - pBuffer->Get(buf, size); - return pBuffer->IsValid() ? size : -1; -} - -void SquirrelVM::ReadObject(CUtlBuffer* pBuffer, ReadStateMap& readState) -{ - SquirrelSafeCheck safeCheck(vm_, 1); - - int thisType = pBuffer->GetInt(); - - switch (thisType) + switch ( obj._type ) { case OT_NULL: - { - sq_pushnull(vm_); + obj._unVal.raw = 0; break; - } + case OT_INTEGER: - { - sq_pushinteger(vm_, pBuffer->GetInt64()); +#ifdef _SQ64 + obj._unVal.nInteger = (SQInteger)pBuffer->GetInt64(); +#else + obj._unVal.nInteger = (SQInteger)pBuffer->GetInt(); +#endif break; - } + case OT_FLOAT: - { - sq_pushfloat(vm_, pBuffer->GetFloat()); +#ifdef SQUSEDOUBLE + obj._unVal.fFloat = (SQFloat)pBuffer->GetDouble(); +#else + obj._unVal.fFloat = (SQFloat)pBuffer->GetFloat(); +#endif break; - } + case OT_BOOL: - { - sq_pushbool(vm_, pBuffer->GetChar()); + obj._unVal.nInteger = pBuffer->GetChar(); + Assert( ( obj._unVal.nInteger & -2 ) == 0 ); break; - } + case OT_STRING: { - int size = pBuffer->GetInt(); - char* buffer = new char[size + 1]; - pBuffer->Get(buffer, size); - buffer[size] = 0; - sq_pushstring(vm_, buffer, size); - delete[] buffer; + int len = pBuffer->GetInt(); + char *psz; + + if ( len < 1024 ) + { + psz = (char*)stackalloc( len ); + } + else + { + psz = (char*)malloc( len ); + } + + pBuffer->Get( psz, len ); + + obj._unVal.pString = SQString::Create( _ss(vm_), psz, len ); + + if ( len >= 1024 ) + { + free( psz ); + } + break; } case OT_TABLE: { - int marker = 0; - if (readState.CheckCache(pBuffer, vm_, &marker)) - { + int marker; + if ( readState.CheckCache( &obj, pBuffer, &marker ) ) break; - } - ReadObject(pBuffer, readState); + SQTable *pThis; int count = pBuffer->GetInt(); - sq_newtableex(vm_, count); - readState.StoreTopInCache(marker); - sq_push(vm_, -2); - sq_setdelegate(vm_, -2); - - sq_remove(vm_, -2); - - for (int i = 0; i < count; ++i) + // NOTE: Hack for VM load - do not release the current roottable + // which would remove native funcs which are looked up from the root + if ( pObj._unVal.pTable != vm_->_roottable._unVal.pTable ) { - ReadObject(pBuffer, readState); - ReadObject(pBuffer, readState); - sq_rawset(vm_, -3); + pThis = SQTable::Create( _ss(vm_), count ); + } + else + { + pThis = pObj._unVal.pTable; + } + + obj._unVal.pTable = pThis; + readState.StoreInCache( marker, obj ); + + if ( pBuffer->GetChar() ) + { + SQObject delegate; + ReadObject( delegate, pBuffer, readState ); + pThis->_delegate = delegate._unVal.pTable; + } + + SQObjectPtr key, val; + while ( count-- ) + { + ReadObject( key, pBuffer, readState ); + ReadObject( val, pBuffer, readState ); + Assert( key._type != OT_NULL ); + pThis->NewSlot( key, val ); } break; } case OT_ARRAY: { - int marker = 0; - if (readState.CheckCache(pBuffer, vm_, &marker)) - { + int marker; + if ( readState.CheckCache( &obj, pBuffer, &marker ) ) break; - } int count = pBuffer->GetInt(); - sq_newarray(vm_, count); - readState.StoreTopInCache(marker); - for (int i = 0; i < count; ++i) + SQArray *pThis = SQArray::Create( _ss(vm_), count ); + obj._unVal.pArray = pThis; + readState.StoreInCache( marker, obj ); + + for ( int i = 0; i < count; ++i ) { - sq_pushinteger(vm_, i); - ReadObject(pBuffer, readState); - sq_rawset(vm_, -3); + ReadObject( pThis->_values[i], pBuffer, readState ); } + break; } case OT_CLOSURE: { - int marker = 0; - if (readState.CheckCache(pBuffer, vm_, &marker)) - { + int marker; + if ( readState.CheckCache( &obj, pBuffer, &marker ) ) break; - } - if (pBuffer->GetChar() == 0) + SQObjectPtr func, root; + ReadObject( func, pBuffer, readState ); + Assert( func._type == OT_FUNCPROTO && func._unVal.pFunctionProto ); + + if ( pBuffer->GetChar() ) { - if (SQ_FAILED(sq_readclosure(vm_, closure_read, pBuffer))) - { - Error("Failed to read closure\n"); - sq_pushnull(vm_); - break; - } - - readState.StoreTopInCache(marker); + ReadObject( root, pBuffer, readState ); + Assert( root._type != OT_NULL && root._unVal.pWeakRef ); } else { - SQObjectPtr ret; - if (!SQClosure::Load(vm_, pBuffer, closure_read, ret)) - { - Error("Failed to read closure\n"); - sq_pushnull(vm_); - break; - } - - vm_->Push(ret); - readState.StoreTopInCache(marker); - - int noutervalues = _closure(ret)->_function->_noutervalues; - for (int i = 0; i < noutervalues; ++i) - { - ReadObject(pBuffer, readState); - HSQOBJECT obj; - sq_resetobject(&obj); - sq_getstackobj(vm_, -1, &obj); - _closure(ret)->_outervalues[i] = obj; - sq_poptop(vm_); - } - - int ndefaultparams = _closure(ret)->_function->_ndefaultparams; - for (int i = 0; i < ndefaultparams; ++i) - { - ReadObject(pBuffer, readState); - HSQOBJECT obj; - sq_resetobject(&obj); - sq_getstackobj(vm_, -1, &obj); - _closure(ret)->_defaultparams[i] = obj; - sq_poptop(vm_); - } + root = vm_->_roottable._unVal.pTable->GetWeakRef( OT_TABLE ); } - ReadObject(pBuffer, readState); - HSQOBJECT env; - sq_resetobject(&env); - sq_getstackobj(vm_, -1, &env); - if (!sq_isnull(env)) + SQClosure *pThis = SQClosure::Create( _ss(vm_), func._unVal.pFunctionProto, root._unVal.pWeakRef ); + obj._unVal.pClosure = pThis; + readState.StoreInCache( marker, obj ); + + if ( pBuffer->GetChar() ) { - HSQOBJECT obj; - sq_getstackobj(vm_, -2, &obj); - if (_closure(obj) == nullptr) - Warning("Closure is null\n"); - else - _closure(obj)->_env = _refcounted(env)->GetWeakRef(sq_type(env)); + SQObject env; + ReadObject( env, pBuffer, readState ); + pThis->_env = env._unVal.pWeakRef; + } + + if ( pBuffer->GetChar() ) + { + SQObjectPtr base; + ReadObject( base, pBuffer, readState ); + pThis->_base = base._unVal.pClass; + } + + int count = pBuffer->GetInt(); + for ( int i = 0; i < count; ++i ) + { + ReadObject( pThis->_outervalues[i], pBuffer, readState ); + } + + count = pBuffer->GetInt(); + for ( int i = 0; i < count; ++i ) + { + ReadObject( pThis->_defaultparams[i], pBuffer, readState ); } - sq_poptop(vm_); break; } case OT_NATIVECLOSURE: { - char closureName[128] = ""; - pBuffer->GetString(closureName, sizeof(closureName)); + char psz[NATIVE_NAME_READBUF_SIZE] = ""; + pBuffer->GetString( psz, sizeof(psz) ); - sq_pushroottable(vm_); - sq_pushstring(vm_, closureName, -1); - if (SQ_FAILED(sq_get(vm_, -2))) + SQObjectPtr key = SQString::Create( _ss(vm_), psz ); + SQObjectPtr val; + + if ( !vm_->_roottable._unVal.pTable->Get( key, val ) ) { - Warning("SquirrelVM::ReadObject: Failed to find native closure\n"); - sq_pop(vm_, 1); - sq_pushnull(vm_); + Warning( "SquirrelVM::ReadObject: failed to find native closure '%s'\n", psz ); + obj._type = OT_NULL; + obj._unVal.raw = 0; + break; } - sq_remove(vm_, -2); + +#ifdef _DEBUG + // If it's a script closure which references this native closure, + // these references will stack up on each load-save + if ( val._type != OT_NATIVECLOSURE ) + Warning( "SquirrelVM::ReadObject: '%s' is not nativeclosure\n", psz ); +#endif + + AssertMsg( val._type == OT_NATIVECLOSURE || val._type == OT_CLOSURE, "'%s' is not a closure", psz ); + + obj._type = val._type; + obj._unVal.pNativeClosure = val._unVal.pNativeClosure; + + break; + } + case OT_FUNCPROTO: + { + int marker; + if ( readState.CheckCache( &obj, pBuffer, &marker ) ) + break; + + int nliterals = pBuffer->GetInt(); + int nparameters = pBuffer->GetInt(); + int noutervalues = pBuffer->GetInt(); + int nlocalvarinfos = pBuffer->GetInt(); + int nlineinfos = pBuffer->GetInt(); + int ndefaultparams = pBuffer->GetInt(); + int ninstructions = pBuffer->GetInt(); + int nfunctions = pBuffer->GetInt(); + + SQFunctionProto *pThis = SQFunctionProto::Create( _ss(vm_), ninstructions, nliterals, nparameters, + nfunctions, noutervalues, nlineinfos, nlocalvarinfos, ndefaultparams ); + obj._unVal.pFunctionProto = pThis; + readState.StoreInCache( marker, obj ); + + ReadObject( pThis->_sourcename, pBuffer, readState ); + ReadObject( pThis->_name, pBuffer, readState ); + + for ( int i = 0; i < pThis->_nliterals; ++i ) + { + ReadObject( pThis->_literals[i], pBuffer, readState ); + } + + for ( int i = 0; i < pThis->_nparameters; ++i ) + { + ReadObject( pThis->_parameters[i], pBuffer, readState ); + } + + for ( int i = 0; i < pThis->_noutervalues; ++i ) + { + SQOuterVar &p = pThis->_outervalues[i]; + p._type = (SQOuterType)pBuffer->GetUnsignedInt(); + ReadObject( p._src, pBuffer, readState ); + ReadObject( p._name, pBuffer, readState ); + } + + for ( int i = 0; i < pThis->_nlocalvarinfos; ++i ) + { + SQLocalVarInfo &p = pThis->_localvarinfos[i]; + ReadObject( p._name, pBuffer, readState ); + p._pos = pBuffer->GetUnsignedInt(); + p._start_op = pBuffer->GetUnsignedInt(); + p._end_op = pBuffer->GetUnsignedInt(); + } + + pBuffer->Get( pThis->_lineinfos, sizeof(SQLineInfo) * pThis->_nlineinfos ); + pBuffer->Get( pThis->_defaultparams, sizeof(SQInteger) * pThis->_ndefaultparams ); + pBuffer->Get( pThis->_instructions, sizeof(SQInstruction) * pThis->_ninstructions ); + + for ( int i = 0; i < pThis->_nfunctions; ++i ) + { + ReadObject( pThis->_functions[i], pBuffer, readState ); + } + + pThis->_stacksize = pBuffer->GetInt(); + pThis->_bgenerator = ( pBuffer->GetChar() != 0 ); + pThis->_varparams = pBuffer->GetInt(); break; } case OT_CLASS: { - int marker = 0; - if (readState.CheckCache(pBuffer, vm_, &marker)) - { + int marker; + if ( readState.CheckCache( &obj, pBuffer, &marker ) ) break; - } - ClassType classType = (ClassType)pBuffer->GetInt(); + ClassType type = (ClassType)pBuffer->GetChar(); - if (classType == VectorClassType) + if ( type == VectorClassType ) { - sq_pushobject(vm_, vectorClass_); - readState.StoreTopInCache(marker); + obj._unVal.pClass = vectorClass_._unVal.pClass; + readState.StoreInCache( marker, obj ); } - else if (classType == NativeClassType) + else if ( type == NativeClassType ) { - char className[128] = ""; - pBuffer->GetString(className, sizeof(className)); + char psz[NATIVE_NAME_READBUF_SIZE] = ""; + pBuffer->GetString( psz, sizeof(psz) ); - sq_pushroottable(vm_); - sq_pushstring(vm_, className, -1); - if (SQ_FAILED(sq_get(vm_, -2))) + SQObjectPtr key = SQString::Create( _ss(vm_), psz ); + SQObjectPtr val; + + if ( !vm_->_roottable._unVal.pTable->Get( key, val ) ) { - Warning("SquirrelVM::ReadObject: Failed to find native class: %s\n", className); - sq_pushnull(vm_); - } - sq_remove(vm_, -2); - readState.StoreTopInCache(marker); - } - else if (classType == ScriptClassType) - { - ReadObject(pBuffer, readState); - bool hasBase = sq_gettype(vm_, -1) != OT_NULL; - if (!hasBase) - { - sq_poptop(vm_); + Warning( "SquirrelVM::ReadObject: failed to find native class '%s'\n", psz ); + obj._type = OT_NULL; + obj._unVal.raw = 0; + break; } - sq_newclass(vm_, hasBase); - readState.StoreTopInCache(marker); + Assert( val._type == OT_CLASS ); - sq_pushnull(vm_); - ReadObject(pBuffer, readState); - sq_setattributes(vm_, -3); - sq_poptop(vm_); // Returns the old attributes + obj._unVal.pClass = val._unVal.pClass; + readState.StoreInCache( marker, obj ); + } + else if ( type == ScriptClassType ) + { + SQObjectPtr base, members; - while (pBuffer->GetChar()) + if ( pBuffer->GetChar() ) { - // TODO: Member Attributes - ReadObject(pBuffer, readState); - ReadObject(pBuffer, readState); - sq_newslot(vm_, -3, false); + ReadObject( base, pBuffer, readState ); + Assert( base._type == OT_CLASS ); } + + SQClass *pThis = SQClass::Create( _ss(vm_), base._unVal.pClass ); + obj._unVal.pClass = pThis; + readState.StoreInCache( marker, obj ); + + ReadObject( members, pBuffer, readState ); + + Assert( members._type == OT_TABLE ); + + // Replace with restored members + pThis->_members->Release(); + pThis->_members = members._unVal.pTable; + __ObjAddRef( pThis->_members ); + + int count = pBuffer->GetInt(); + pThis->_defaultvalues.resize( count ); + + for ( int i = 0; i < count; ++i ) + { + ReadObject( pThis->_defaultvalues[i].val, pBuffer, readState ); + } + + count = pBuffer->GetInt(); + pThis->_methods.resize( count ); + + for ( int i = 0; i < count; ++i ) + { + ReadObject( pThis->_methods[i].val, pBuffer, readState ); + } + + int mask = pBuffer->GetInt(); + if ( mask ) + { + for ( unsigned i = 0; i < SQMetaMethod::MT_LAST; ++i ) + if ( mask & (1 << i) ) + ReadObject( pThis->_metamethods[i], pBuffer, readState ); + } + + pThis->_constructoridx = pBuffer->GetInt(); + + Assert( pThis->_constructoridx == -1 || (SQUnsignedInteger)pThis->_constructoridx < pThis->_methods.size() ); } else { - Error("SquirrelVM::ReadObject: Unknown class type\n"); - sq_pushnull(vm_); + Assert( !"SquirrelVM::ReadObject: unknown class type" ); } + break; } case OT_INSTANCE: { - int marker = 0; - if (readState.CheckCache(pBuffer, vm_, &marker)) - { + int marker; + if ( readState.CheckCache( &obj, pBuffer, &marker ) ) break; - } - ReadObject(pBuffer, readState); + SQInstance *pThis; - HSQOBJECT klass; - sq_resetobject(&klass); - sq_getstackobj(vm_, -1, &klass); - if (_class(klass) == _class(regexpClass_)) + SQObjectPtr pClass; + ReadObject( pClass, pBuffer, readState ); + + Assert( pClass._type == OT_CLASS && pClass._unVal.pClass ); + + if ( _class(pClass) == _class(regexpClass_) ) { - sq_pushnull(vm_); - ReadObject(pBuffer, readState); - sq_call(vm_, 2, SQTrue, SQFalse); + SQObjectPtr key = SQString::Create( _ss(vm_), "pattern_" ); + SQObjectPtr val; + ReadObject( val, pBuffer, readState ); - readState.StoreTopInCache(marker); - - sq_remove(vm_, -2); + pThis = _class(pClass)->CreateInstance(); + pThis->Set( key, val ); + obj._unVal.pInstance = pThis; + readState.StoreInCache( marker, obj ); break; } - SQUserPointer typetag; - sq_gettypetag(vm_, -1, &typetag); + SQUserPointer typetag = _class(pClass)->_typetag; - if (typetag && typetag != TYPETAG_VECTOR && - ((ScriptClassDesc_t*)typetag)->m_pszDescription[0] == SCRIPT_SINGLETON[0]) + // singleton + if ( typetag && typetag != TYPETAG_VECTOR ) { - sq_poptop(vm_); - - Assert(sq_isclass(klass)); - - // singleton, lets find an equivlent in the root - bool foundSingleton = false; - sq_pushroottable(vm_); - sq_pushnull(vm_); - HSQOBJECT singleton; - sq_resetobject(&singleton); - while (SQ_SUCCEEDED(sq_next(vm_, -2))) + ScriptClassDesc_t *pDesc = (ScriptClassDesc_t *)typetag; + if ( pDesc->m_pszDescription[0] == SCRIPT_SINGLETON[0] ) { - sq_getstackobj(vm_, -1, &singleton); - if (sq_isinstance(singleton) && _instance(singleton)->_class == _class(klass)) + bool bFoundSingleton = false; + + SQObjectPtr key, val; + FOREACH_SQTABLE( vm_->_roottable._unVal.pTable, key, val ) { - foundSingleton = true; + if ( sq_isinstance(val) && _instance(val)->_class == _class(pClass) ) + { + pThis = val._unVal.pInstance; + obj._unVal.pInstance = pThis; + readState.StoreInCache( marker, obj ); - readState.StoreInCache(marker, singleton); - sq_addref(vm_, &singleton); - sq_pop(vm_, 2); - break; + bFoundSingleton = true; + break; + } } - sq_pop(vm_, 2); - } - sq_pop(vm_, 2); - if (!foundSingleton) - { - Warning("SquirrelVM::ReadObject: Failed to find singleton for %s\n", - ((ScriptClassDesc_t*)typetag)->m_pszScriptName); - } + if ( !bFoundSingleton ) + { + Warning( "SquirrelVM::ReadObject: failed to find native singleton of '%s'\n", pDesc->m_pszClassname ); + Assert(0); + obj._type = OT_NULL; + obj._unVal.raw = 0; + } - sq_pushobject(vm_, singleton); - break; - } - - - HSQOBJECT obj; - sq_createinstance(vm_, -1); - sq_getstackobj(vm_, -1, &obj); - sq_addref(vm_, &obj); - readState.StoreInCache(marker, obj); - - sq_remove(vm_, -2); - - { - // HACK: No way to get the default values part from accessing the class directly - SQUnsignedInteger nvalues = _instance(obj)->_class->_defaultvalues.size(); - for (SQUnsignedInteger n = 0; n < nvalues; n++) { - ReadObject(pBuffer, readState); - HSQOBJECT val; - sq_resetobject(&val); - sq_getstackobj(vm_, -1, &val); - _instance(obj)->_values[n] = val; - sq_pop(vm_, 1); + break; } } - if (typetag == TYPETAG_VECTOR) + pThis = SQInstance::Create( _ss(vm_), _class(pClass) ); + obj._unVal.pInstance = pThis; + readState.StoreInCache( marker, obj ); + + if ( typetag ) { - float x = pBuffer->GetFloat(); - float y = pBuffer->GetFloat(); - float z = pBuffer->GetFloat(); - SQUserPointer p; - sq_getinstanceup(vm_, -1, &p, 0); - new(p) Vector(x, y, z); - } - else if (typetag) - { - ScriptClassDesc_t* pClassDesc = (ScriptClassDesc_t*)typetag; - - char instanceName[128] = ""; - pBuffer->GetString(instanceName, sizeof(instanceName)); - - HSQOBJECT* hinstance = new HSQOBJECT; - sq_resetobject(hinstance); - sq_getstackobj(vm_, -1, hinstance); - sq_addref(vm_, hinstance); - - if (*instanceName) + if ( typetag == TYPETAG_VECTOR ) { - bool allowDestruct = (pBuffer->GetChar() == 1); - - auto instance = pClassDesc->pHelper->BindOnRead((HSCRIPT)hinstance, nullptr, instanceName); - if (instance == nullptr) - { - sq_release(vm_, hinstance); - delete hinstance; - sq_poptop(vm_); - sq_pushnull(vm_); - break; - } - - { - SQUserPointer p; - sq_getinstanceup(vm_, -1, &p, 0); - new(p) ClassInstanceData(instance, pClassDesc, instanceName, allowDestruct); - } - sq_setreleasehook(vm_, -1, allowDestruct ? &destructor_stub : &destructor_stub_instance); + float x = pBuffer->GetFloat(); + float y = pBuffer->GetFloat(); + float z = pBuffer->GetFloat(); + new ( pThis->_userpointer ) Vector( x, y, z ); } else { - sq_setinstanceup(vm_, -1, nullptr); + ScriptClassDesc_t *pDesc = (ScriptClassDesc_t *)typetag; + + char pszInstanceName[NATIVE_NAME_READBUF_SIZE] = ""; + pBuffer->GetString( pszInstanceName, sizeof(pszInstanceName) ); + + if ( pszInstanceName[0] ) + { + bool refCounted = ( pBuffer->GetChar() != 0 ); + + HSQOBJECT *hInstance = new HSQOBJECT; + hInstance->_type = OT_INSTANCE; + hInstance->_unVal.pInstance = pThis; + + Assert( pDesc->pHelper ); + + void *pInstance = pDesc->pHelper->BindOnRead( (HSCRIPT)hInstance, NULL, pszInstanceName ); + if ( pInstance ) + { + sq_addref( vm_, hInstance ); + new( pThis->_userpointer ) ClassInstanceData( pInstance, pDesc, pszInstanceName, refCounted ); + pThis->_hook = refCounted ? &destructor_stub : &destructor_stub_instance; + } + else + { + delete hInstance; + pThis->_userpointer = NULL; + } + } + else + { + pThis->_userpointer = NULL; + } } + + break; + } + + int count = pBuffer->GetInt(); +#ifdef _DEBUG + int size = pThis->_class->_defaultvalues.size(); + if ( count != size ) + { + Warning("SquirrelVM::ReadObject: Non-matching class default value count!\n"); + Assert(0); + } +#endif + for ( int i = 0; i < count; ++i ) + { + ReadObject( pThis->_values[i], pBuffer, readState ); } break; } case OT_WEAKREF: { - ReadObject(pBuffer, readState); - sq_weakref(vm_, -1); - break; - } - case OT_FUNCPROTO: //internal usage only - { - int marker = 0; - if (readState.CheckCache(pBuffer, vm_, &marker)) + SQObject ref; + ReadObject( ref, pBuffer, readState ); + if ( !ref._unVal.pRefCounted ) { + obj._type = OT_NULL; + obj._unVal.raw = 0; break; } - SQObjectPtr ret; - if (!SQFunctionProto::Load(vm_, pBuffer, closure_read, ret)) - { - Error("Failed to deserialize OT_FUNCPROTO\n"); - sq_pushnull(vm_); - break; - } + Assert( ISREFCOUNTED( ref._type ) ); + Assert( ref._unVal.pRefCounted->_uiRef > 0 ); + ref._unVal.pRefCounted->_uiRef--; - vm_->Push(ret); - readState.StoreTopInCache(marker); - } - case OT_OUTER: //internal usage only - { - int marker = 0; - if (readState.CheckCache(pBuffer, vm_, &marker)) - { - break; - } - - SQOuter* outer = SQOuter::Create(_ss(vm_), nullptr); - vm_->Push(outer); - readState.StoreTopInCache(marker); - - ReadObject(pBuffer, readState); - HSQOBJECT inner; - sq_resetobject(&inner); - sq_getstackobj(vm_, -1, &inner); - outer->_value = inner; - outer->_valptr = &(outer->_value); - sq_poptop(vm_); + SQWeakRef *pThis = ref._unVal.pRefCounted->GetWeakRef( ref._type ); + obj._unVal.pWeakRef = pThis; break; } - // case OT_USERDATA: - // case OT_GENERATOR: - // case OT_USERPOINTER: - // case OT_THREAD: - // + case OT_OUTER: + { + int marker; + if ( readState.CheckCache( &obj, pBuffer, &marker ) ) + break; + + SQOuter *pThis = SQOuter::Create( _ss(vm_), NULL ); + obj._unVal.pOuter = pThis; + readState.StoreInCache( marker, obj ); + + ReadObject( pThis->_value, pBuffer, readState ); + pThis->_valptr = &(pThis->_value); + + break; + } + case OT_THREAD: + { + int marker; + if ( readState.CheckCache( &obj, pBuffer, &marker ) ) + break; + + SQVM *pThis = sq_newthread( vm_, pBuffer->GetInt() ); + pThis->_uiRef++; + vm_->Pop(); + pThis->_uiRef--; + obj._unVal.pThread = pThis; + readState.StoreInCache( marker, obj ); + + pThis->_callsstacksize = pBuffer->GetInt(); + + if ( pThis->_callsstacksize ) + { + if ( pThis->_callsstacksize >= pThis->_alloccallsstacksize ) + pThis->GrowCallStack(); + + for ( int i = pThis->_callsstacksize; i--; ) + { + SQVM::CallInfo *ci = &pThis->_callsstack[i]; + + SQObjectPtr closure; + ReadObject( closure, pBuffer, readState ); + Assert( closure._type == OT_CLOSURE && closure._unVal.pClosure ); + + int offset = pBuffer->GetInt(); + int funcsize = sizeof(SQInstruction) * closure._unVal.pClosure->_function->_ninstructions; + int start = (int)(closure._unVal.pClosure->_function->_instructions); + int pos = start + offset; + ci->_ip = (SQInstruction*)pos; + + Assert( pos < (start + funcsize) ); + + // don't read past boundary + if ( pos >= (start + funcsize) ) + { + ci->_ip = (SQInstruction*)start; + } + + ci->_literals = closure._unVal.pClosure->_function->_literals; + ci->_closure = closure; + ci->_generator = NULL; + ci->_etraps = pBuffer->GetInt(); + ci->_prevstkbase = pBuffer->GetInt(); + ci->_prevtop = pBuffer->GetInt(); + ci->_target = pBuffer->GetInt(); + ci->_ncalls = pBuffer->GetInt(); + ci->_root = pBuffer->GetChar(); + + pThis->_etraps.resize( ci->_etraps ); + + for ( int j = ci->_etraps; j--; ) + { + SQExceptionTrap &et = pThis->_etraps[j]; + et._extarget = pBuffer->GetInt(); + et._stackbase = pBuffer->GetInt(); + et._stacksize = pBuffer->GetInt(); + et._ip = ci->_ip; + } + } + + int stackidx = pBuffer->GetInt(); + Assert( stackidx >= 0 && stackidx < pThis->_callsstacksize ); + pThis->ci = &pThis->_callsstack[ stackidx ]; + } + + pThis->_nnativecalls = pBuffer->GetInt(); + pThis->_nmetamethodscall = pBuffer->GetInt(); + + pThis->_suspended = pBuffer->GetChar(); + pThis->_suspended_root = pBuffer->GetChar(); + pThis->_suspended_target = pBuffer->GetInt(); + pThis->_suspended_traps = pBuffer->GetInt(); + + ReadVM( pThis, pBuffer, readState ); + + break; + } + case OT_GENERATOR: + { + int marker; + if ( readState.CheckCache( &obj, pBuffer, &marker ) ) + break; + + int state = pBuffer->GetChar(); + if ( state == SQGenerator::SQGeneratorState::eDead ) + { + // SQGenerator doesn't allow creating dead generators, write null. + // This is much simpler than creating a dummy closure only to tell the user it's dead. + obj._type = OT_NULL; + obj._unVal.raw = 0; + break; + } + + SQObjectPtr closure; + ReadObject( closure, pBuffer, readState ); + Assert( closure._type == OT_CLOSURE && closure._unVal.pClosure ); + + SQGenerator *pThis = SQGenerator::Create( _ss(vm_), closure._unVal.pClosure ); + obj._unVal.pGenerator = pThis; + readState.StoreInCache( marker, obj ); + + pThis->_state = (SQGenerator::SQGeneratorState)state; + + SQVM::CallInfo &ci = pThis->_ci; + + int offset = pBuffer->GetInt(); + int funcsize = sizeof(SQInstruction) * closure._unVal.pClosure->_function->_ninstructions; + int start = (int)(closure._unVal.pClosure->_function->_instructions); + int pos = start + offset; + ci._ip = (SQInstruction*)pos; + + Assert( pos < (start + funcsize) ); + + // don't read past boundary + if ( pos >= (start + funcsize) ) + { + ci._ip = (SQInstruction*)start; + } + + ci._literals = closure._unVal.pClosure->_function->_literals; + ci._closure = closure; + ci._generator = NULL; + ci._etraps = pBuffer->GetInt(); + ci._prevstkbase = pBuffer->GetInt(); + ci._prevtop = pBuffer->GetInt(); + ci._target = pBuffer->GetInt(); + ci._ncalls = pBuffer->GetInt(); + ci._root = pBuffer->GetChar(); + + pThis->_etraps.resize( ci._etraps ); + + for ( int j = ci._etraps; j--; ) + { + SQExceptionTrap &et = pThis->_etraps[j]; + et._extarget = pBuffer->GetInt(); + et._stackbase = pBuffer->GetInt(); + et._stacksize = pBuffer->GetInt(); + et._ip = ci._ip; + } + + int stacksize = pBuffer->GetInt(); + pThis->_stack.resize( stacksize ); + for ( int i = 0; i < stacksize; ++i ) + { + ReadObject( pThis->_stack[i], pBuffer, readState ); + } + + break; + } + case OT_USERDATA: + case OT_USERPOINTER: + { + Assert(0); + break; + } default: - Error("SquirrelVM::ReadObject: Unexpected type %d", thisType); + AssertMsgAlways( 0, "SquirrelVM::ReadObject: serialisation error" ); } + + Assert( !ISREFCOUNTED(obj._type) || obj._unVal.raw != 0xCC ); + + pObj = obj; } -void SquirrelVM::ReadState(CUtlBuffer* pBuffer) +void SquirrelVM::WriteVM( SQVM *pThis, CUtlBuffer *pBuffer, WriteStateMap &writeState ) { - SquirrelSafeCheck safeCheck(vm_); + // Let others access the current VM inside ReadObject + SQVM *pPrevVM = vm_; + vm_ = pThis; - ReadStateMap readState(vm_); + Assert( pThis->_roottable._unVal.pTable->_uiRef > 0 ); - sq_pushroottable(vm_); + WriteObject( pThis->_roottable, pBuffer, writeState ); - HSQOBJECT obj; - int marker = 0; - readState.CheckCache(pBuffer, vm_, &marker); - sq_getstackobj(vm_, -1, &obj); - sq_addref(vm_, &obj); - readState.StoreInCache(marker, obj); + Assert( pThis->_roottable._unVal.pTable->_uiRef > 0 ); - int count = pBuffer->GetInt(); - - for (int i = 0; i < count; ++i) + pBuffer->PutInt( pThis->_top ); + pBuffer->PutInt( pThis->_stackbase ); + pBuffer->PutInt( pThis->_stack.size() ); + for ( int i = 0; i < pThis->_top; ++i ) // write up to top { - ReadObject(pBuffer, readState); - ReadObject(pBuffer, readState); - - sq_rawset(vm_, -3); + WriteObject( pThis->_stack[i], pBuffer, writeState ); } - sq_pop(vm_, 1); + vm_ = pPrevVM; // restore +} + +void SquirrelVM::ReadVM( SQVM *pThis, CUtlBuffer *pBuffer, ReadStateMap &readState ) +{ + // Let others access the current VM inside ReadObject + SQVM *pPrevVM = vm_; + vm_ = pThis; + + ReadObject( pThis->_roottable, pBuffer, readState ); + + Assert( pThis->_roottable._unVal.pTable->_uiRef > 0 ); + + pThis->_top = pBuffer->GetInt(); + pThis->_stackbase = pBuffer->GetInt(); + pThis->_stack.resize( pBuffer->GetInt() ); + for ( int i = 0; i < pThis->_top; ++i ) + { + ReadObject( pThis->_stack[i], pBuffer, readState ); + } + + vm_ = pPrevVM; // restore +} + +void SquirrelVM::WriteState( CUtlBuffer* pBuffer ) +{ + // If the main VM can be suspended, WriteVM/ReadVM would need to include the code inside OT_THREAD r/w + Assert( !vm_->ci ); + + WriteStateMap writeState; + WriteVM( vm_, pBuffer, writeState ); +} + +void SquirrelVM::ReadState( CUtlBuffer* pBuffer ) +{ + ReadStateMap readState; + ReadVM( vm_, pBuffer, readState ); } void SquirrelVM::RemoveOrphanInstances()