176 lines
12 KiB
Python
176 lines
12 KiB
Python
"""Execute staged/production security methods; native held UI/replication pending."""
|
|
import os,re
|
|
from pathlib import Path
|
|
from policy_harness import ROOT,body,run
|
|
base=Path(os.environ.get('ERR_VEHICLE_SECURITY_ROOT',str(ROOT)))
|
|
component=base/'addons/core/Scripts/Game/ERR/ERR_VehicleSecurityComponent.c'
|
|
actions=base/'addons/core/Scripts/Game/ERR/ERR_VehicleSecurityActions.c'
|
|
|
|
def translate(value):
|
|
value=value.replace('out string reason','string& reason').replace('string.Empty','string{}').replace('override ','')
|
|
for t in ('ERR_GameMode','Physics','ERR_VehicleSecurityComponent'):
|
|
value=re.sub(r'\b'+t+r' (\w+)',t+r'* \1',value)
|
|
for t in ('ERR_GameMode','ERR_Actors','Replication','CompartmentAccessComponent','ERR_VehicleSecurityComponent','vector','Vehicle','ERR_Phase'):
|
|
value=value.replace(t+'.',t+'::')
|
|
for var in ('mode','physics','user','entity','security'):
|
|
value=value.replace(var+'.',var+'->')
|
|
value=value.replace('GetOwner().','GetOwner()->').replace('mode->Players().','mode->Players()->')
|
|
value=value.replace('super.','Base::').replace('null','nullptr')
|
|
value=re.sub(r'(?<![\w.])(\d+\.\d+)(?![\w.])',r'\1f',value)
|
|
return value
|
|
# Execute the actual preparation hook separately from breach eligibility.
|
|
configuration=translate(body(component,'bool Configure('))
|
|
run("""#include <cassert>
|
|
struct Entity {bool vehicle=true,alive=true;};using IEntity=Entity*;
|
|
struct Vehicle {static IEntity Cast(IEntity e){return e&&e->vehicle?e:nullptr;}};
|
|
struct ERR_Phase {static const int PREPARING=1,ACTIVE=2;};
|
|
struct ERR_GameMode {int phase=ERR_Phase::PREPARING;static ERR_GameMode* current;
|
|
static ERR_GameMode* Get(){return current;}int Phase(){return phase;}};
|
|
ERR_GameMode* ERR_GameMode::current=nullptr;
|
|
struct ERR_Actors {static IEntity observed;static bool Alive(IEntity e){observed=e;return e&&e->alive;}};
|
|
IEntity ERR_Actors::observed=nullptr;
|
|
struct Replication {static bool server;static int bumps;static bool IsServer(){return server;}static void BumpMe(){bumps++;}};
|
|
bool Replication::server=true;int Replication::bumps=0;
|
|
struct Security {IEntity owner=nullptr;bool m_Configured=false,m_Breached=false;IEntity GetOwner(){return owner;}
|
|
"""+configuration+"""
|
|
};
|
|
int main(){
|
|
// Reject every invalid preparation boundary without replication or state changes.
|
|
for(int variant=0;variant<6;variant++){
|
|
ERR_GameMode mode;ERR_GameMode::current=&mode;Entity vehicle;Security s;s.owner=&vehicle;
|
|
Replication::server=true;Replication::bumps=0;
|
|
if(variant==0)Replication::server=false;
|
|
if(variant==1)ERR_GameMode::current=nullptr;
|
|
if(variant==2)mode.phase=ERR_Phase::ACTIVE;
|
|
if(variant==3)s.owner=nullptr;
|
|
if(variant==4)vehicle.vehicle=false;
|
|
if(variant==5)vehicle.alive=false;
|
|
assert(!s.Configure()&&!s.m_Configured&&!s.m_Breached&&Replication::bumps==0);
|
|
}
|
|
ERR_GameMode mode;ERR_GameMode::current=&mode;Replication::server=true;
|
|
Entity vehicle,unrelated;unrelated.alive=false;Security s;s.owner=&vehicle;
|
|
assert(s.Configure()&&s.m_Configured&&!s.m_Breached&&Replication::bumps==1);
|
|
assert(ERR_Actors::observed==&vehicle); // Liveness is checked on this component's exact vehicle.
|
|
assert(s.Configure()&&Replication::bumps==1&&!s.m_Breached);
|
|
s.m_Breached=true;assert(s.Configure()&&s.m_Breached&&s.m_Configured&&Replication::bumps==1);
|
|
// Invalid repeat calls do not relock or reset configured state.
|
|
vehicle.alive=false;assert(!s.Configure()&&s.m_Breached&&s.m_Configured&&Replication::bumps==1);vehicle.alive=true;
|
|
mode.phase=ERR_Phase::ACTIVE;assert(!s.Configure()&&s.m_Breached&&s.m_Configured&&Replication::bumps==1);
|
|
mode.phase=ERR_Phase::PREPARING;Replication::server=false;
|
|
assert(!s.Configure()&&s.m_Breached&&s.m_Configured&&Replication::bumps==1);
|
|
}
|
|
""")
|
|
|
|
methods='\n'.join(translate(body(component,s)) for s in ('bool Locked(', 'static bool Motion(', 'static bool Reach(', 'bool CanBreach(', 'bool TryBreach('))
|
|
run('''#include <cassert>
|
|
#include <cmath>
|
|
#include <string>
|
|
using string=std::string;
|
|
struct vector {float x=0; float Length(){return x;} static float Distance(vector a,vector b){return std::abs(a.x-b.x);} };
|
|
struct Physics {vector speed,angular; vector GetVelocity(){return speed;} vector GetAngularVelocity(){return angular;} };
|
|
struct Entity {bool player=true,awake=true,alive=true,aboard=false; vector pos; Physics* physics=nullptr; vector GetOrigin(){return pos;} Physics* GetPhysics(){return physics;} vector CoordToParent(vector){return pos;} };
|
|
using IEntity=Entity*;
|
|
struct Roster {IEntity admitted=nullptr; bool CurrentActor(IEntity e){return e&&e==admitted;} };
|
|
struct ERR_GameMode {bool active=true;Roster roster;bool rosterExists=true; static ERR_GameMode* current;static ERR_GameMode* Get(){return current;}bool Active(){return active;} Roster* Players(){return rosterExists?&roster:nullptr;} };
|
|
ERR_GameMode* ERR_GameMode::current=nullptr;
|
|
struct ERR_Actors {static bool Player(IEntity e){return e&&e->player;}static bool CanFight(IEntity e){return e&&e->alive&&e->awake;}static bool Alive(IEntity e){return e&&e->alive;} };
|
|
struct Replication {static bool server;static int bumps; static bool IsServer(){return server;}static void BumpMe(){bumps++;} };bool Replication::server=true;int Replication::bumps=0;
|
|
struct CompartmentAccessComponent {static bool GetVehicleIn(IEntity e){return e&&e->aboard;} };
|
|
struct Security {bool m_Configured=true,m_Breached=false,empty=true,clear=true;IEntity owner;vector m_LockAnchor; IEntity GetOwner(){return owner;}bool EmptyVehicle(){return empty;}bool ClearReach(IEntity,vector){return clear;}
|
|
'''+methods+'''
|
|
};
|
|
int main(){
|
|
Physics physics;Entity vehicle,player,other;vehicle.physics=&physics;
|
|
ERR_GameMode mode; ERR_GameMode::current=&mode;mode.roster.admitted=&player;
|
|
Security s;s.owner=&vehicle;string reason;
|
|
assert(s.CanBreach(&player,reason)&&reason.empty());
|
|
for(float bad : {-1.f,0.251f}) assert(!Security::Motion(bad,0));
|
|
assert(Security::Motion(0.25,0.1)&&!Security::Motion(0,0.101));
|
|
assert(Security::Reach(2)&&!Security::Reach(-1)&&!Security::Reach(2.001));
|
|
s.m_Configured=false;assert(!s.TryBreach(&player));s.m_Configured=true;
|
|
mode.active=false;assert(!s.TryBreach(&player));mode.active=true;
|
|
player.player=false;assert(!s.TryBreach(&player));player.player=true;
|
|
player.awake=false;assert(!s.TryBreach(&player));player.awake=true;
|
|
player.alive=false;assert(!s.TryBreach(&player));player.alive=true;
|
|
player.aboard=true;assert(!s.TryBreach(&player));player.aboard=false;
|
|
vehicle.alive=false;assert(!s.TryBreach(&player));vehicle.alive=true;
|
|
vehicle.physics=nullptr;assert(!s.TryBreach(&player));vehicle.physics=&physics;
|
|
physics.speed.x=0.26;assert(!s.TryBreach(&player));physics.speed.x=0;
|
|
physics.angular.x=0.11;assert(!s.TryBreach(&player));physics.angular.x=0;
|
|
s.empty=false;assert(!s.TryBreach(&player));s.empty=true;
|
|
player.pos.x=2.01;assert(!s.TryBreach(&player));player.pos.x=2;
|
|
s.clear=false;assert(!s.CanBreach(&player,reason)&&reason.find("obstructed")!=string::npos);assert(!s.TryBreach(&player));s.clear=true;
|
|
// Body replacement, absent roster and direct replica execution cannot unlock.
|
|
assert(!s.TryBreach(&other));mode.roster.admitted=nullptr;assert(!s.TryBreach(&player));mode.roster.admitted=&player;
|
|
mode.rosterExists=false;assert(!s.TryBreach(&player));mode.rosterExists=true;
|
|
Replication::server=false;assert(!s.TryBreach(&player));Replication::server=true;
|
|
ERR_GameMode::current=nullptr;assert(!s.TryBreach(&player));ERR_GameMode::current=&mode;
|
|
assert(Replication::bumps==0&&s.Locked());
|
|
assert(s.TryBreach(&player)&&!s.Locked()&&Replication::bumps==1);
|
|
assert(!s.TryBreach(&player)&&!s.TryBreach(&other)&&Replication::bumps==1);
|
|
}
|
|
''')
|
|
|
|
class Section:
|
|
def __init__(self,name,next_name=None):
|
|
source=actions.read_text();self.source=source[source.index('class '+name):]
|
|
if next_name:self.source=self.source[:self.source.index('class '+next_name)]
|
|
def read_text(self):return self.source
|
|
for name,next_name in [('ERR_SecureGetInAction','ERR_SecureOpenVehicleDoorAction'),('ERR_SecureOpenVehicleDoorAction',None)]:
|
|
section=Section(name,next_name)
|
|
methods='\n'.join(translate(body(section,s)) for s in ('override bool CanBePerformedScript(', 'override void PerformAction('))
|
|
run('''#include <cassert>
|
|
#include <string>
|
|
using IEntity=int;
|
|
using string=std::string;
|
|
struct ERR_VehicleSecurityComponent {bool locked=true;static ERR_VehicleSecurityComponent* security;static ERR_VehicleSecurityComponent* Find(IEntity){return security;}bool Locked(){return locked;}};
|
|
ERR_VehicleSecurityComponent* ERR_VehicleSecurityComponent::security=nullptr;
|
|
struct Base {bool allowed=true;int calls=0;IEntity GetOwner(){return 42;}void SetCannotPerformReason(std::string){}bool CanBePerformedScript(IEntity){return allowed;}void PerformAction(IEntity,IEntity){calls++;}};
|
|
struct Action:Base {
|
|
'''+methods+'''
|
|
};
|
|
int main(){Action a;ERR_VehicleSecurityComponent s;ERR_VehicleSecurityComponent::security=&s;
|
|
a.PerformAction(42,7);assert(a.calls==0);s.locked=false;a.allowed=false;a.PerformAction(42,7);assert(a.calls==0);
|
|
a.allowed=true;a.PerformAction(43,7);assert(a.calls==0);a.PerformAction(42,7);assert(a.calls==1);
|
|
s.locked=true;a.PerformAction(42,7);assert(a.calls==1);
|
|
ERR_VehicleSecurityComponent::security=nullptr;a.PerformAction(42,7);assert(a.calls==2); // unrelated owner delegates native policy
|
|
}
|
|
''')
|
|
|
|
|
|
# The held action invokes only the owning security component on the server; its
|
|
# TryBreach path above performs fresh checks rather than trusting availability.
|
|
execution=translate(body(Section('ERR_ForceVehicleLockAction','ERR_SecureGetInAction'),'override void PerformAction('))
|
|
run("""#include <cassert>
|
|
using IEntity=int;
|
|
struct Replication {static bool server;static bool IsServer(){return server;}};bool Replication::server=false;
|
|
struct ERR_VehicleSecurityComponent {int calls=0,last=0;static ERR_VehicleSecurityComponent* security;static ERR_VehicleSecurityComponent* Find(IEntity){return security;}void TryBreach(IEntity user){calls++;last=user;}};
|
|
ERR_VehicleSecurityComponent* ERR_VehicleSecurityComponent::security=nullptr;
|
|
struct Action {IEntity GetOwner(){return 42;}
|
|
"""+execution+"""
|
|
};
|
|
int main(){Action a;ERR_VehicleSecurityComponent s;ERR_VehicleSecurityComponent::security=&s;
|
|
a.PerformAction(42,7);assert(s.calls==0);Replication::server=true;
|
|
a.PerformAction(43,7);assert(s.calls==0);a.PerformAction(42,7);assert(s.calls==1&&s.last==7);
|
|
ERR_VehicleSecurityComponent::security=nullptr;a.PerformAction(42,7);assert(s.calls==1);
|
|
}
|
|
""")
|
|
|
|
# Exact resource overrides cover both front seats, three separately attached rear
|
|
# seats and all four door actions without replacing physical vehicle resources.
|
|
prefabs=base/'addons/core/Prefabs/ERR/Vehicles'
|
|
uaz=(prefabs/'SupplyUAZ.et').read_text();rear=(prefabs/'SupplyUAZRearSeats.et').read_text()
|
|
assert '"{259EE7B78C51B624}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et"' in uaz
|
|
assert '"{780AAF0A51AEE61A}Prefabs/Vehicles/Wheeled/UAZ469/VehParts/Seats/UAZ469_seats_rear.et"' in rear
|
|
assert uaz.count('OpenDoorAction ERR_SecureOpenVehicleDoorAction')==4
|
|
assert uaz.count('CompartmentAction ERR_SecureGetInAction')==2
|
|
assert rear.count('CompartmentAction ERR_SecureGetInAction')==3
|
|
for slot in ['Passenger_l02','Passenger_r02','Passenger_m02']:assert 'CargoCompartmentSlot '+slot in rear
|
|
assert 'SupplyUAZRearSeats.et' in uaz and 'Duration 4' in uaz and 'VisibilityRange 2' in uaz
|
|
assert uaz.count('-0.95 1.05 0.3')==2 # reach anchor matches visible held-action context
|
|
for signature,result in [('override bool HasLocalEffectOnlyScript(',False),('override bool CheckOnServerFirstScript(',True),('override bool CanBroadcastScript(',False)]:
|
|
assert 'return '+str(result).lower()+';' in body(Section('ERR_ForceVehicleLockAction','ERR_SecureGetInAction'),signature)
|
|
assert '[RplProp()] protected bool m_Breached;' in component.read_text()
|
|
assert 'modded class' not in actions.read_text()+component.read_text()
|
|
print('PASS: actual preparation Configure guards/idempotence/breach preservation, security eligibility/final current-body server mutation and all scoped action guards; all five seat/four door resource overrides. Native hold, prefab merge, replication and boarding remain unverified.')
|