返回提交历史
Added
src/controllers/custom/retroactivelyApplyGuildCheatController.ts
+25
-0
Modified
src/controllers/custom/setGuildCheatController.ts
+9
-6
Modified
src/helpers/udp.ts
+15
-2
Modified
src/routes/custom.ts
+2
-0
Modified
src/services/arbiterService.ts
+40
-0
Added
src/services/clanCheatsService.ts
+70
-0
Modified
src/types/guildTypes.ts
+10
-2
Modified
static/webui/index.html
+4
-4
Modified
static/webui/script.js
+13
-0
XFEstudio/XFESpaceNinjaServer
feat: retroactive clan cheats (#3694)
Closes #3331 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3694 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
08af0c2f
代码差异
9 个文件
+188
-14
@@ -0,0 +1,25 @@
1
import type { RequestHandler } from "express";
2
import { getAccountIdForRequest } from "../../services/loginService.ts";
3
import { clanLockCheats } from "../../services/clanCheatsService.ts";
4
import type { IGuildCheats } from "../../types/guildTypes.ts";
5
import { getGuildForRequest } from "../../services/guildService.ts";
6
import { GuildMember } from "../../models/guildModel.ts";
7
import { broadcastControlMessages } from "../../services/arbiterService.ts";
8
9
export const retroactivelyApplyGuildCheatController: RequestHandler = async (req, res) => {
10
const accountId = await getAccountIdForRequest(req);
11
const guild = await getGuildForRequest(req, accountId);
12
const member = await GuildMember.findOne({ accountId: accountId, guildId: guild._id });
13
if (member && member.rank <= 1) {
14
const meta = clanLockCheats[req.query.cheat as string as keyof IGuildCheats]!;
15
const msgs = meta.cleanupGuild(guild);
16
if (!meta.isGuildInIdealState(guild)) {
17
throw new Error(`cleanupGuild for ${req.query.cheat as string} does not satsify its isGuildInIdealState`);
18
}
19
await guild.save();
20
if (msgs.length) {
21
broadcastControlMessages(`${guild._id.toString().toUpperCase()}_0`, msgs);
22
}
23
}
24
res.end();
25
};
@@ -1,4 +1,5 @@
1
1
import { GuildMember } from "../../models/guildModel.ts";
2
import { clanLockCheats } from "../../services/clanCheatsService.ts";
2
3
import { getGuildForRequest } from "../../services/guildService.ts";
3
4
import { getAccountIdForRequest } from "../../services/loginService.ts";
4
5
import type { IGuildCheats } from "../../types/guildTypes.ts";
@@ -6,17 +7,19 @@ import type { RequestHandler } from "express";
6
7
7
8
export const setGuildCheatController: RequestHandler = async (req, res) => {
8
9
const accountId = await getAccountIdForRequest(req);
9
const payload = req.body as ISetGuildCheatRequest;
10
10
const guild = await getGuildForRequest(req, accountId);
11
11
const member = await GuildMember.findOne({ accountId: accountId, guildId: guild._id });
12
12
13
if (member) {
14
if (member.rank > 1) {
15
res.end();
16
return;
17
}
13
if (member && member.rank <= 1) {
14
const payload = req.body as ISetGuildCheatRequest;
15
const meta = payload.value ? clanLockCheats[payload.key] : undefined;
16
18
17
guild[payload.key] = payload.value;
19
18
await guild.save();
19
20
if (meta && !meta.isGuildInIdealState(guild)) {
21
res.send("retroactivable");
22
}
20
23
}
21
24
res.end();
22
25
};
@@ -18,9 +18,22 @@ export const sendUdpMessage = (host: string, port: number, message: Buffer | str
18
18
19
19
export const sendCustomMessageToNrs = async (message: string): Promise<void> => {
20
20
const msgbuf = Buffer.from("\x00" + message, "utf8");
21
const crc = crc32(Buffer.from("b471e49539930dc9b5a131e6247c7387A", "utf8"), crc32(msgbuf));
22
const msg = Buffer.concat([Buffer.from([0x00, crc >> 24, crc >> 16, crc >> 8, crc]), msgbuf]);
21
const msg = addCompressionAndIntegrityToDatagram(msgbuf);
23
22
for (const [nrsAddr, nrsPort] of getNrsAddresses()) {
24
23
await sendUdpMessage(nrsAddr, nrsPort, msg);
25
24
}
26
25
};
26
27
const SALT_BUF = Buffer.from("b471e49539930dc9b5a131e6247c7387A", "utf8");
28
29
const addCompressionAndIntegrityToDatagram = (msg: Buffer): Buffer => {
30
const crc = crc32(SALT_BUF, crc32(msg));
31
return Buffer.concat([Buffer.from([0x00, crc >> 24, crc >> 16, crc >> 8, crc]), msg]);
32
};
33
34
export const packHubDatagram = (msg: Buffer): Buffer => {
35
const header = Buffer.alloc(6);
36
header.writeUInt32LE(0x80000000, 0);
37
header.writeUInt16LE(msg.length, 4);
38
return addCompressionAndIntegrityToDatagram(Buffer.concat([header, msg]));
39
};
@@ -29,6 +29,7 @@ import { removeCustomizationController } from "../controllers/custom/removeCusto
29
29
import { removeIsNewController } from "../controllers/custom/removeIsNewController.ts";
30
30
import { removeItemsController } from "../controllers/custom/removeItemsController.ts";
31
31
import { retroactivelyApplyCheatController } from "../controllers/api/retroactivelyApplyCheatController.ts";
32
import { retroactivelyApplyGuildCheatController } from "../controllers/custom/retroactivelyApplyGuildCheatController.ts";
32
33
33
34
import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts";
34
35
import { createAccountController } from "../controllers/custom/createAccountController.ts";
@@ -86,6 +87,7 @@ customRouter.get("/removeCustomization", removeCustomizationController);
86
87
customRouter.get("/removeIsNew", removeIsNewController);
87
88
customRouter.get("/removeItems", removeItemsController);
88
89
customRouter.get("/retroactivelyApplyCheat", retroactivelyApplyCheatController);
90
customRouter.get("/retroactivelyApplyGuildCheat", retroactivelyApplyGuildCheatController);
89
91
90
92
customRouter.post("/abilityOverride", abilityOverrideController);
91
93
customRouter.post("/createAccount", createAccountController);
@@ -1,4 +1,6 @@
1
import { packHubDatagram } from "../helpers/udp.ts";
1
2
import { config, type IHubServer } from "./configService.ts";
3
import dgram from "node:dgram";
2
4
3
5
export type TRegionId = "ASIA" | "OCEANIA" | "EUROPE" | "RUSSIA" | "NORTH_AMERICA" | "SOUTH_AMERICA";
4
6
@@ -38,3 +40,41 @@ export const pickHubServer = (regionId: TRegionId): IHubServer => {
38
40
}
39
41
return bestServer;
40
42
};
43
44
export const broadcastControlMessages = (level: string, msgs: string[]): void => {
45
const instance = hubInstances[level] as IHubInstance | undefined;
46
if (instance) {
47
instance.Players += 1; // to account for the hubDropped that we will cause here
48
49
const [host, port] = instance.Hub.split("%THIS_MACHINE%").join("127.0.0.1").split(":");
50
const socket = dgram.createSocket("udp4");
51
52
socket.send(
53
packHubDatagram(
54
Buffer.concat([
55
Buffer.from([0xb4, 0x03, 0x18, 0x00, 0x00, 0x00]),
56
Buffer.from("000000000000000000000000", "utf8"),
57
Buffer.alloc(8),
58
Buffer.from([0x03, 0x00, 0x00, 0x00]),
59
Buffer.from("SNS", "utf8"),
60
Buffer.alloc(4),
61
Buffer.from([level.length, 0x00, 0x00, 0x00]),
62
Buffer.from(level, "utf8")
63
])
64
),
65
parseInt(port),
66
host
67
);
68
socket.once("message", msg => {
69
const peerId = msg.readUInt16LE(13);
70
for (const msg of msgs) {
71
const header = Buffer.alloc(8);
72
header.writeUInt8(0xb4, 0);
73
header.writeUInt8(0x07, 1);
74
header.writeUInt16LE(peerId, 2);
75
header.writeUInt32LE(msg.length, 4);
76
socket.send(packHubDatagram(Buffer.concat([header, Buffer.from(msg, "utf8")])), parseInt(port), host);
77
}
78
});
79
}
80
};
@@ -0,0 +1,70 @@
1
import type { TGuildDatabaseDocument } from "../models/guildModel.ts";
2
import type { IGuildCheats } from "../types/guildTypes.ts";
3
4
interface IClanLockCheat {
5
isGuildInIdealState: (guild: TGuildDatabaseDocument) => boolean;
6
cleanupGuild: (guild: TGuildDatabaseDocument) => string[];
7
}
8
9
export const clanLockCheats: Partial<Record<keyof IGuildCheats, IClanLockCheat>> = {
10
noDojoRoomBuildStage: {
11
isGuildInIdealState: (guild: TGuildDatabaseDocument) =>
12
guild.DojoComponents.every(
13
component => component.CompletionTime && component.CompletionTime.getTime() <= Date.now()
14
),
15
cleanupGuild: (guild: TGuildDatabaseDocument) => {
16
for (const component of guild.DojoComponents) {
17
if (!component.CompletionTime || component.CompletionTime.getTime() > Date.now()) {
18
component.CompletionTime = new Date();
19
}
20
}
21
return [
22
`{"from":"000000000000000000000000","to":"dojo","msg":{"dojoMsgType":1,"n":1,"a":false,"f":false,"u":""}}`
23
];
24
}
25
},
26
fastDojoRoomDestruction: {
27
isGuildInIdealState: (guild: TGuildDatabaseDocument) =>
28
guild.DojoComponents.every(
29
component => !component.DestructionTime || component.DestructionTime.getTime() <= Date.now()
30
),
31
cleanupGuild: (guild: TGuildDatabaseDocument) => {
32
for (const component of guild.DojoComponents) {
33
if (component.DestructionTime && component.DestructionTime.getTime() > Date.now()) {
34
component.DestructionTime = new Date();
35
}
36
}
37
return [
38
`{"from":"000000000000000000000000","to":"dojo","msg":{"dojoMsgType":1,"n":1,"a":false,"f":false,"u":""}}`
39
];
40
}
41
},
42
noDojoDecoBuildStage: {
43
isGuildInIdealState: (guild: TGuildDatabaseDocument) =>
44
guild.DojoComponents.every(
45
component =>
46
!component.Decos ||
47
component.Decos.every(deco => deco.CompletionTime && deco.CompletionTime.getTime() <= Date.now())
48
),
49
cleanupGuild: (guild: TGuildDatabaseDocument) => {
50
const msgs: string[] = [];
51
for (const component of guild.DojoComponents) {
52
if (component.Decos) {
53
let anyChanges = false;
54
for (const deco of component.Decos) {
55
if (!deco.CompletionTime || deco.CompletionTime.getTime() > Date.now()) {
56
deco.CompletionTime = new Date();
57
anyChanges = true;
58
}
59
}
60
if (anyChanges) {
61
msgs.push(
62
`{"from":"000000000000000000000000","to":"dojo","msg":{"dojoMsgType":0,"id":"${component._id.toString()}","op":6,"u":""}}`
63
);
64
}
65
}
66
}
67
return msgs;
68
}
69
}
70
};
@@ -41,8 +41,8 @@ export interface IGuildClient {
41
41
// Fields specific to SNS
42
42
export interface IGuildCheats {
43
43
noDojoRoomBuildStage?: boolean;
44
noDojoDecoBuildStage?: boolean;
45
44
fastDojoRoomDestruction?: boolean;
45
noDojoDecoBuildStage?: boolean;
46
46
noDojoResearchCosts?: boolean;
47
47
noDojoResearchTime?: boolean;
48
48
fastClanAscension?: boolean;
@@ -217,7 +217,15 @@ export interface IDojoComponentSettings {
217
217
218
218
export interface IDojoComponentDatabase extends Omit<
219
219
IDojoComponentClient,
220
"id" | "SortId" | "pi" | "CompletionTime" | "DestructionTime" | "Decos" | "PaintBot"
220
| "id"
221
| "SortId"
222
| "pi"
223
| "CompletionTime"
224
| "TimeRemaining"
225
| "DestructionTime"
226
| "DestructionTimeRemaining"
227
| "Decos"
228
| "PaintBot"
221
229
> {
222
230
_id: Types.ObjectId;
223
231
SortId?: Types.ObjectId;
@@ -690,6 +690,10 @@
690
690
<input class="form-check-input" type="checkbox" id="noDojoRoomBuildStage" />
691
691
<label class="form-check-label" for="noDojoRoomBuildStage" data-loc="cheats_noDojoRoomBuildStage"></label>
692
692
</div>
693
<div class="form-check">
694
<input class="form-check-input" type="checkbox" id="fastDojoRoomDestruction" />
695
<label class="form-check-label" for="fastDojoRoomDestruction" data-loc="cheats_fastDojoRoomDestruction"></label>
696
</div>
693
697
<div class="form-check">
694
698
<input class="form-check-input" type="checkbox" id="noDojoDecoBuildStage" />
695
699
<label class="form-check-label" for="noDojoDecoBuildStage" data-loc="cheats_noDojoDecoBuildStage"></label>
@@ -702,10 +706,6 @@
702
706
<input class="form-check-input" type="checkbox" id="noDojoResearchTime" />
703
707
<label class="form-check-label" for="noDojoResearchTime" data-loc="cheats_noDojoResearchTime"></label>
704
708
</div>
705
<div class="form-check">
706
<input class="form-check-input" type="checkbox" id="fastDojoRoomDestruction" />
707
<label class="form-check-label" for="fastDojoRoomDestruction" data-loc="cheats_fastDojoRoomDestruction"></label>
708
</div>
709
709
<div class="form-check">
710
710
<input class="form-check-input" type="checkbox" id="fastClanAscension" />
711
711
<label class="form-check-label" for="fastClanAscension" data-loc="cheats_fastClanAscension"></label>
@@ -4094,6 +4094,19 @@ document.querySelectorAll("#guild-cheats input[type=checkbox]").forEach(elm => {
4094
4094
key: elm.id,
4095
4095
value: elm.checked
4096
4096
})
4097
}).done(res => {
4098
if (res == "retroactivable") {
4099
if (window.confirm(loc("cheats_retroactivePrompt"))) {
4100
$.get(
4101
"/custom/retroactivelyApplyGuildCheat?" +
4102
window.authz +
4103
"&guildId=" +
4104
window.guildId +
4105
"&cheat=" +
4106
elm.id
4107
);
4108
}
4109
}
4097
4110
});
4098
4111
});
4099
4112
};