328 lines
		
	
	
		
			No EOL
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			328 lines
		
	
	
		
			No EOL
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const protobuf = require("@citizenfx/protobufjs");
 | 
						|
 | 
						|
const playerDatas = {};
 | 
						|
let slotsUsed = 0;
 | 
						|
 | 
						|
function assignSlotId() {
 | 
						|
	for (let i = 0; i < 32; i++) {
 | 
						|
		if (!(slotsUsed & (1 << i))) {
 | 
						|
			slotsUsed |= (1 << i);
 | 
						|
			return i;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
let hostIndex = -1;
 | 
						|
const isOneSync = GetConvar("onesync", "off") !== "off";
 | 
						|
 | 
						|
protobuf.load(GetResourcePath(GetCurrentResourceName()) + "/rline.proto", function(err, root) {
 | 
						|
	if (err) {
 | 
						|
		console.log(err);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	const RpcMessage = root.lookupType("rline.RpcMessage");
 | 
						|
	const RpcResponseMessage = root.lookupType("rline.RpcResponseMessage");
 | 
						|
	const InitSessionResponse = root.lookupType("rline.InitSessionResponse");
 | 
						|
	const InitPlayer2_Parameters = root.lookupType("rline.InitPlayer2_Parameters");
 | 
						|
	const InitPlayerResult = root.lookupType("rline.InitPlayerResult");
 | 
						|
	const GetRestrictionsResult = root.lookupType("rline.GetRestrictionsResult");
 | 
						|
	const QueueForSession_Seamless_Parameters = root.lookupType("rline.QueueForSession_Seamless_Parameters");
 | 
						|
	const QueueForSessionResult = root.lookupType("rline.QueueForSessionResult");
 | 
						|
	const QueueEntered_Parameters = root.lookupType("rline.QueueEntered_Parameters");
 | 
						|
	const TransitionReady_PlayerQueue_Parameters = root.lookupType("rline.TransitionReady_PlayerQueue_Parameters");
 | 
						|
	const TransitionToSession_Parameters = root.lookupType("rline.TransitionToSession_Parameters");
 | 
						|
	const TransitionToSessionResult = root.lookupType("rline.TransitionToSessionResult");
 | 
						|
	const scmds_Parameters = root.lookupType("rline.scmds_Parameters");
 | 
						|
 | 
						|
	function toArrayBuffer(buf) {
 | 
						|
		var ab = new ArrayBuffer(buf.length);
 | 
						|
		var view = new Uint8Array(ab);
 | 
						|
		for (var i = 0; i < buf.length; ++i) {
 | 
						|
			view[i] = buf[i];
 | 
						|
		}
 | 
						|
		return ab;
 | 
						|
	}
 | 
						|
 | 
						|
	function emitMsg(target, data) {
 | 
						|
		emitNet('__cfx_internal:pbRlScSession', target, toArrayBuffer(data));
 | 
						|
	}
 | 
						|
 | 
						|
	function emitSessionCmds(target, cmd, cmdname, msg) {
 | 
						|
		const stuff = {};
 | 
						|
		stuff[cmdname] = msg;
 | 
						|
 | 
						|
		emitMsg(target, RpcMessage.encode({
 | 
						|
			Header: {
 | 
						|
				MethodName: 'scmds'
 | 
						|
			},
 | 
						|
			Content: scmds_Parameters.encode({
 | 
						|
				sid: {
 | 
						|
					value: {
 | 
						|
						a: 2,
 | 
						|
						b: 2
 | 
						|
					}
 | 
						|
				},
 | 
						|
				ncmds: 1,
 | 
						|
				cmds: [
 | 
						|
					{
 | 
						|
						cmd,
 | 
						|
						cmdname,
 | 
						|
						...stuff
 | 
						|
					}
 | 
						|
				]
 | 
						|
			}).finish()
 | 
						|
		}).finish());
 | 
						|
	}
 | 
						|
 | 
						|
	function emitAddPlayer(target, msg) {
 | 
						|
		emitSessionCmds(target, 2, 'AddPlayer', msg);
 | 
						|
	}
 | 
						|
 | 
						|
	function emitRemovePlayer(target, msg) {
 | 
						|
		emitSessionCmds(target, 3, 'RemovePlayer', msg);
 | 
						|
	}
 | 
						|
 | 
						|
	function emitHostChanged(target, msg) {
 | 
						|
		emitSessionCmds(target, 5, 'HostChanged', msg);
 | 
						|
	}
 | 
						|
 | 
						|
	onNet('playerDropped', () => {
 | 
						|
		if (isOneSync) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		try {
 | 
						|
			const oData = playerDatas[source];
 | 
						|
			delete playerDatas[source];
 | 
						|
 | 
						|
			if (oData && hostIndex === oData.slot) {
 | 
						|
				const pda = Object.entries(playerDatas);
 | 
						|
 | 
						|
				if (pda.length > 0) {
 | 
						|
					hostIndex = pda[0][1].slot | 0; // TODO: actually use <=31 slot index *and* check for id
 | 
						|
 | 
						|
					for (const [ id, data ] of Object.entries(playerDatas)) {
 | 
						|
						emitHostChanged(id, {
 | 
						|
							index: hostIndex
 | 
						|
						});
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					hostIndex = -1;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if (!oData) {
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			if (oData.slot > -1) {
 | 
						|
				slotsUsed &= ~(1 << oData.slot);
 | 
						|
			}
 | 
						|
 | 
						|
			for (const [ id, data ] of Object.entries(playerDatas)) {
 | 
						|
				emitRemovePlayer(id, {
 | 
						|
					id: oData.id
 | 
						|
				});
 | 
						|
			}
 | 
						|
		} catch (e) {
 | 
						|
			console.log(e);
 | 
						|
			console.log(e.stack);
 | 
						|
		}
 | 
						|
	});
 | 
						|
 | 
						|
	function makeResponse(type, data) {
 | 
						|
		return {
 | 
						|
			Header: {
 | 
						|
			},
 | 
						|
			Container: {
 | 
						|
				Content: type.encode(data).finish()
 | 
						|
			}
 | 
						|
		};
 | 
						|
	}
 | 
						|
 | 
						|
	const handlers = {
 | 
						|
		async InitSession(source, data) {
 | 
						|
			return makeResponse(InitSessionResponse, {
 | 
						|
				sesid: Buffer.alloc(16),
 | 
						|
				/*token: {
 | 
						|
					tkn: 'ACSTOKEN token="meow",signature="meow"'
 | 
						|
				}*/
 | 
						|
			});
 | 
						|
		},
 | 
						|
 | 
						|
		async InitPlayer2(source, data) {
 | 
						|
			const req = InitPlayer2_Parameters.decode(data);
 | 
						|
 | 
						|
			if (!isOneSync) {
 | 
						|
				playerDatas[source] = {
 | 
						|
					gh: req.gh,
 | 
						|
					peerAddress: req.peerAddress,
 | 
						|
					discriminator: req.discriminator,
 | 
						|
					slot: -1
 | 
						|
				};
 | 
						|
			}
 | 
						|
 | 
						|
			return makeResponse(InitPlayerResult, {
 | 
						|
				code: 0
 | 
						|
			});
 | 
						|
		},
 | 
						|
 | 
						|
		async GetRestrictions(source, data) {
 | 
						|
			return makeResponse(GetRestrictionsResult, {
 | 
						|
				data: {
 | 
						|
 | 
						|
				}
 | 
						|
			});
 | 
						|
		},
 | 
						|
 | 
						|
		async ConfirmSessionEntered(source, data) {
 | 
						|
			return {};
 | 
						|
		},
 | 
						|
 | 
						|
		async TransitionToSession(source, data) {
 | 
						|
			const req = TransitionToSession_Parameters.decode(data);
 | 
						|
 | 
						|
			return makeResponse(TransitionToSessionResult, {
 | 
						|
				code: 1 // in this message, 1 is success
 | 
						|
			});
 | 
						|
		},
 | 
						|
 | 
						|
		async QueueForSession_Seamless(source, data) {
 | 
						|
			const req = QueueForSession_Seamless_Parameters.decode(data);
 | 
						|
 | 
						|
			if (!isOneSync) {
 | 
						|
				playerDatas[source].req = req.requestId;
 | 
						|
				playerDatas[source].id = req.requestId.requestor;
 | 
						|
				playerDatas[source].slot = assignSlotId();
 | 
						|
			}
 | 
						|
 | 
						|
			setTimeout(() => {
 | 
						|
				emitMsg(source, RpcMessage.encode({
 | 
						|
					Header: {
 | 
						|
						MethodName: 'QueueEntered'
 | 
						|
					},
 | 
						|
					Content: QueueEntered_Parameters.encode({
 | 
						|
						queueGroup: 69,
 | 
						|
						requestId: req.requestId,
 | 
						|
						optionFlags: req.optionFlags
 | 
						|
					}).finish()
 | 
						|
				}).finish());
 | 
						|
 | 
						|
				if (isOneSync) {
 | 
						|
					hostIndex = 16
 | 
						|
				} else if (hostIndex === -1) {
 | 
						|
					hostIndex = playerDatas[source].slot | 0;
 | 
						|
				}
 | 
						|
 | 
						|
				emitMsg(source, RpcMessage.encode({
 | 
						|
					Header: {
 | 
						|
						MethodName: 'TransitionReady_PlayerQueue'
 | 
						|
					},
 | 
						|
					Content: TransitionReady_PlayerQueue_Parameters.encode({
 | 
						|
						serverUri: {
 | 
						|
							url: ''
 | 
						|
						},
 | 
						|
						requestId: req.requestId,
 | 
						|
						id: {
 | 
						|
							value: {
 | 
						|
								a: 2,
 | 
						|
								b: 0
 | 
						|
							}
 | 
						|
						},
 | 
						|
						serverSandbox: 0xD656C677,
 | 
						|
						sessionType: 3,
 | 
						|
						transferId: {
 | 
						|
							value: {
 | 
						|
								a: 2,
 | 
						|
								b: 2
 | 
						|
							}
 | 
						|
						},
 | 
						|
					}).finish()
 | 
						|
				}).finish());
 | 
						|
 | 
						|
				setTimeout(() => {
 | 
						|
					emitSessionCmds(source, 0, 'EnterSession', {
 | 
						|
						index: (isOneSync) ? 16 : playerDatas[source].slot | 0,
 | 
						|
						hindex: hostIndex,
 | 
						|
						sessionFlags: 0,
 | 
						|
						mode: 0,
 | 
						|
						size: (isOneSync) ? 0 : Object.entries(playerDatas).filter(a => a[1].id).length,
 | 
						|
						//size: 2,
 | 
						|
						//size: Object.entries(playerDatas).length,
 | 
						|
						teamIndex: 0,
 | 
						|
						transitionId: {
 | 
						|
							value: {
 | 
						|
								a: 2,
 | 
						|
								b: 0
 | 
						|
							}
 | 
						|
						},
 | 
						|
						sessionManagerType: 0,
 | 
						|
						slotCount: 32
 | 
						|
					});
 | 
						|
				}, 50);
 | 
						|
 | 
						|
				if (!isOneSync) {
 | 
						|
					setTimeout(() => {
 | 
						|
						// tell player about everyone, and everyone about player
 | 
						|
						const meData = playerDatas[source];
 | 
						|
 | 
						|
						const aboutMe = {
 | 
						|
							id: meData.id,
 | 
						|
							gh: meData.gh,
 | 
						|
							addr: meData.peerAddress,
 | 
						|
							index: playerDatas[source].slot | 0
 | 
						|
						};
 | 
						|
 | 
						|
						for (const [ id, data ] of Object.entries(playerDatas)) {
 | 
						|
							if (id == source || !data.id) continue;
 | 
						|
 | 
						|
							emitAddPlayer(source, {
 | 
						|
								id: data.id,
 | 
						|
								gh: data.gh,
 | 
						|
								addr: data.peerAddress,
 | 
						|
								index: data.slot | 0
 | 
						|
							});
 | 
						|
 | 
						|
							emitAddPlayer(id, aboutMe);
 | 
						|
						}
 | 
						|
					}, 150);
 | 
						|
				}
 | 
						|
			}, 250);
 | 
						|
 | 
						|
			return makeResponse(QueueForSessionResult, {
 | 
						|
				code: 1
 | 
						|
			});
 | 
						|
		},
 | 
						|
	};
 | 
						|
 | 
						|
	async function handleMessage(source, method, data) {
 | 
						|
		if (handlers[method]) {
 | 
						|
			return await handlers[method](source, data);
 | 
						|
		}
 | 
						|
 | 
						|
		return {};
 | 
						|
	}
 | 
						|
 | 
						|
	onNet('__cfx_internal:pbRlScSession', async (data) => {
 | 
						|
		const s = source;
 | 
						|
 | 
						|
		try {
 | 
						|
			const message = RpcMessage.decode(new Uint8Array(data));
 | 
						|
			const response = await handleMessage(s, message.Header.MethodName, message.Content);
 | 
						|
 | 
						|
			if (!response || !response.Header) {
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			response.Header.RequestId = message.Header.RequestId;
 | 
						|
 | 
						|
			emitMsg(s, RpcResponseMessage.encode(response).finish());
 | 
						|
		} catch (e) {
 | 
						|
			console.log(e);
 | 
						|
			console.log(e.stack);
 | 
						|
		}
 | 
						|
	});
 | 
						|
}); |