Skip to content

Commit e69a721

Browse files
committed
Make JavaScript responsible for blocking illegal vehicle modifications
1 parent e962c8e commit e69a721

File tree

6 files changed

+91
-39
lines changed

6 files changed

+91
-39
lines changed

data/server/callbacks.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ forward OnPlayerRequestSpawn(playerid);
4949
forward OnObjectMoved(objectid);
5050
forward OnPlayerObjectMoved(playerid, objectid);
5151
forward OnPlayerPickUpPickup(playerid, pickupid);
52-
forward OnVehicleMod(playerid, vehicleid, componentid);
52+
[Cancelable] forward OnVehicleMod(playerid, vehicleid, componentid);
5353
forward OnEnterExitModShop(playerid, enterexit, interiorid);
5454
forward OnVehiclePaintjob(playerid, vehicleid, paintjobid);
5555
forward OnVehicleRespray(playerid, vehicleid, color1, color2);

javascript/entities/vehicle_manager.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import { ScopedCallbacks } from 'base/scoped_callbacks.js';
66

7+
import { canVehicleModelHaveComponent } from 'entities/vehicle_components.js';
8+
79
// The vehicle manager is in control of all vehicles that have been created by the JavaScript code
810
// of Las Venturas Playground. It deliberately does not provide access to the Pawn vehicles.
911
export class VehicleManager {
@@ -116,16 +118,36 @@ export class VehicleManager {
116118

117119
// ---------------------------------------------------------------------------------------------
118120

121+
// Called when a vehicle is being modified by a particular player. This event is cancelable, and
122+
// should be canceled when an illegal modification is being made. This is a tactic that players
123+
// with malicious intent often use for crashing others.
119124
onVehicleMod(event) {
120125
const player = server.playerManager.getById(event.playerid);
121-
if (!player)
126+
const vehicle = this.vehicles_.get(event.vehicleid);
127+
128+
// If either the |player| or the |vehicle| is invalid, bail out and prevent the default
129+
// processing from happening. We don't have enough information to blame a cheater.
130+
if (!player || !vehicle) {
131+
console.log(`[exception] Vehicle modification found with invalid player/vehicle.`);
132+
133+
event.preventDefault();
122134
return;
135+
}
123136

124-
const vehicle = this.vehicles_.get(event.vehicleid);
125-
if (!vehicle)
137+
const componentId = event.componentid;
138+
139+
// If the |vehicle| is not allowed to have the |componentId|, we cancel the event, notify
140+
// observers of an illegal vehicle modification, and bail out.
141+
if (!canVehicleModelHaveComponent(vehicle.modelId, componentId)) {
142+
this.notifyObservers('onVehicleIllegalModification', player, vehicle, componentId);
143+
144+
event.preventDefault();
126145
return;
146+
}
147+
148+
// TODO: Track modifications for the vehicle.
127149

128-
this.notifyObservers('onVehicleMod', player, vehicle, event.componentid);
150+
this.notifyObservers('onVehicleMod', player, vehicle, componentId);
129151
}
130152

131153
onVehiclePaintjob(event) {

javascript/features/abuse/abuse_monitor.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import { AbuseDatabase } from 'features/abuse/abuse_database.js';
66
import { AbuseDetector } from 'features/abuse/abuse_detector.js';
77

8+
import { getComponentName } from 'entities/vehicle_components.js';
9+
810
// The abuse monitor keeps track of instances of abuse across the active players. A series of abuse
911
// detectors are responsible for detecting it, after which it ends up here for decision making and
1012
// distribution of the knowledge to in-game administrators.
@@ -15,6 +17,8 @@ export class AbuseMonitor {
1517
this.settings_ = settings;
1618

1719
provideNative('ReportAbuse', 'iss', AbuseMonitor.prototype.reportAbusePawn.bind(this));
20+
21+
server.vehicleManager.addObserver(this);
1822
}
1923

2024
// Reports abuse by the given |player|, indicating that they've been observed exercising the
@@ -61,7 +65,28 @@ export class AbuseMonitor {
6165
return 1;
6266
}
6367

68+
// ---------------------------------------------------------------------------------------------
69+
70+
// Called when the |player| has made an illegal vehicle modification. This could be used to
71+
// crash other players on the server, and thus is considered certain abuse.
72+
onVehicleIllegalModification(player, vehicle, componentId) {
73+
// (1) Report abuse for the |player|.
74+
this.reportAbuse(player, 'illegal vehicle modification', AbuseDetector.kDetected, {
75+
vehicleModelId: vehicle.modelId,
76+
vehicleModelName: vehicle.model.name,
77+
vehicleComponentId: componentId,
78+
vehicleComponentName: getComponentName(componentId),
79+
});
80+
81+
// (2) Immediately, without delay, remove the |player| from the server.
82+
player.kick();
83+
}
84+
85+
// ---------------------------------------------------------------------------------------------
86+
6487
dispose() {
88+
server.vehicleManager.removeObserver(this);
89+
6590
provideNative('ReportAbuse', 'iss', (playerid, detectorName, certainty) => 0);
6691
}
6792
}

javascript/features/abuse/abuse_monitor.test.js

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,58 @@
22
// Use of this source code is governed by the MIT license, a copy of which can
33
// be found in the LICENSE file.
44

5-
import { AbuseMonitor } from 'features/abuse/abuse_monitor.js';
6-
75
describe('AbuseMonitor', (it, beforeEach) => {
8-
return; // disabled for now
9-
6+
let gunther = null;
107
let monitor = null;
118
let settings = null;
129

1310
beforeEach(() => {
11+
gunther = server.playerManager.getById(/* Gunther= */ 0);
1412
monitor = server.featureManager.loadFeature('abuse').monitor_;
1513
settings = server.featureManager.loadFeature('settings');
1614
});
1715

18-
it('should be able to detect and kick fake non-player characters', assert => {
19-
const russell = server.playerManager.getById(1 /* Russell */);
16+
it('should kick players for illegal vehicle modifications', assert => {
17+
const vehicle = server.vehicleManager.createVehicle({
18+
modelId: 411, // Infernus
19+
20+
position: new Vector(0, 0, 0),
21+
rotation: 180,
22+
});
23+
24+
gunther.enterVehicle(vehicle);
25+
26+
assert.isTrue(gunther.isConnected());
27+
assert.isTrue(vehicle.isConnected());
2028

21-
russell.level = Player.LEVEL_ADMINISTRATOR;
29+
assert.strictEqual(gunther.vehicle, vehicle);
2230

23-
// Connect the evil bot to the server. They should be kicked immediately after.
24-
server.playerManager.onPlayerConnect({
25-
playerid: 42,
26-
name: 'EvilBot',
27-
ip: '42.42.42.42',
28-
npc: true
31+
let defaultPrevented = false;
32+
33+
// (1) Have |gunther| make a valid modification to the |vehicle|.
34+
dispatchEvent('vehiclemod', {
35+
preventDefault: () => defaultPrevented = true,
36+
37+
playerid: gunther.id,
38+
vehicleid: vehicle.id,
39+
componentid: 1075, // Rimshine Wheels
2940
});
3041

31-
assert.isNull(server.playerManager.getById(42 /* evilbot */));
42+
assert.isFalse(defaultPrevented);
43+
assert.isTrue(gunther.isConnected());
44+
// TODO: Verify that |vehicle| has the component.
3245

33-
assert.equal(russell.messages.length, 1);
34-
assert.isTrue(
35-
russell.messages[0].includes(
36-
Message.format(Message.ABUSE_ANNOUNCE_KICKED, 'EvilBot', 42,
37-
'illegal non-player character')));
46+
// (2) Have |gunther| make an illegal modification to the |vehicle|.
47+
dispatchEvent('vehiclemod', {
48+
preventDefault: () => defaultPrevented = true,
3849

39-
});
50+
playerid: gunther.id,
51+
vehicleid: vehicle.id,
52+
componentid: 1144, // Left Square Vents, not available for Infernus
53+
});
4054

55+
assert.isTrue(defaultPrevented);
56+
assert.isFalse(gunther.isConnected());
57+
// TODO: Verify that |vehicle| does not have the component.
58+
});
4159
});

javascript/features/finance/financial_disposition_monitor.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe('FinancialDispositionMonitor', (it, beforeEach, afterEach) => {
9595
dispatchEvent('vehiclemod', {
9696
playerid: gunther.id,
9797
vehicleid: vehicle.id,
98-
componentid: 1000 // spoiler
98+
componentid: 1008 // 5x Nitro
9999
});
100100

101101
await server.clock.advance(kDispositionMonitorSpinDelay);

pawn/Entities/Vehicles/VehicleEvents.pwn

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -141,19 +141,6 @@ public OnVehicleMod(playerid, vehicleid, componentid) {
141141
if (Player(playerid)->isConnected() == false || Player(playerid)->isNonPlayerCharacter() == true)
142142
return 0; // don't handle invalid players or NPCs
143143

144-
if (!IsLegalModification(vehicleid, componentid)) {
145-
new message[128];
146-
147-
format(message, sizeof(message), "%s (Id:%d) made an illegal vehicle modification with component %d.",
148-
Player(playerid)->nicknameString(), playerid, componentid);
149-
Admin(playerid, message);
150-
151-
// Kick them from Las Venturas Playground
152-
Player(playerid)->kick("Illegal vehicle modification");
153-
154-
return 0; // don't handle illegal modifications
155-
}
156-
157144
if (!VehicleModel(GetVehicleModel(vehicleid))->isValidComponent(componentid))
158145
return false;
159146

0 commit comments

Comments
 (0)