Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 77 additions & 6 deletions src/net/data-parser/client-login-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ function longToName(nameLong: BigInt): string {
return ac.split('').reverse().join('');
}

/**
* Codes for user login attempts that are sent back to the game client
* to inform the user of the status of their login attempt.
*/
enum LoginResponseCode {
SUCCESS = 2,
INVALID_CREDENTIALS = 3,
ACCOUNT_DISABLED = 4,
ALREADY_LOGGED_IN = 5,
GAME_UPDATED = 6,
WORLD_FULL = 7,
LOGIN_SERVER_OFFLINE = 8,
LOGIN_LIMIT_EXCEEDED = 9,
BAD_SESSION_ID = 10,
// @TODO the rest
}

/**
* Parses the login packet from the game client.
*/
Expand Down Expand Up @@ -83,15 +100,33 @@ export class ClientLoginParser extends DataParser {
throw new Error(`Server key mismatch - ${this.clientConnection.serverKey} != ${incomingServerKey}`);
}

const clientUuid = decrypted.get('INT');
const gameClientId = decrypted.get('INT');
const usernameLong = BigInt(decrypted.get('LONG'));
const username = longToName(usernameLong);
const password = decrypted.getString();

logger.info(`Login request: ${username}/${password}`);

const credentialsResponseCode = this.checkCredentials(username, password);
if(credentialsResponseCode === -1) {
this.sendLogin([ clientKey1, clientKey2 ], gameClientId, username, password, isLowDetail);
} else {
logger.warn(`${username} attempted to login but received error code ${ credentialsResponseCode }.`);
this.sendLoginResponse(credentialsResponseCode);
}
}

/**
* Logs a user in and notifies their game client of a successful login.
* @param clientKeys The user's client keys (sent by the client).
* @param gameClientId The user's game client ID (sent by the client).
* @param username The user's username.
* @param password The user's password.
* @param isLowDetail Whether or not the user selected the "Low Detail" option.
*/
private sendLogin(clientKeys: [ number, number ], gameClientId: number, username: string, password: string, isLowDetail: boolean): void {
const sessionKey: number[] = [
Number(clientKey1), Number(clientKey2), Number(this.clientConnection.serverKey >> BigInt(32)), Number(this.clientConnection.serverKey)
Number(clientKeys[0]), Number(clientKeys[1]), Number(this.clientConnection.serverKey >> BigInt(32)), Number(this.clientConnection.serverKey)
];

const inCipher = new Isaac(sessionKey);
Expand All @@ -102,12 +137,12 @@ export class ClientLoginParser extends DataParser {

const outCipher = new Isaac(sessionKey);

const player = new Player(this.clientConnection.socket, inCipher, outCipher, clientUuid, username, password, isLowDetail);
const player = new Player(this.clientConnection.socket, inCipher, outCipher, gameClientId, username, password, isLowDetail);

world.registerPlayer(player);

const outputBuffer = new ByteBuffer(6);
outputBuffer.put(2, 'BYTE'); // login response code
outputBuffer.put(LoginResponseCode.SUCCESS, 'BYTE');
outputBuffer.put(player.rights.valueOf(), 'BYTE');
outputBuffer.put(0, 'BYTE'); // ???
outputBuffer.put(player.worldIndex + 1, 'SHORT');
Expand All @@ -116,8 +151,44 @@ export class ClientLoginParser extends DataParser {

player.init();

this.clientConnection.clientKey1 = BigInt(clientKey1);
this.clientConnection.clientKey2 = BigInt(clientKey2);
this.clientConnection.clientKey1 = BigInt(clientKeys[0]);
this.clientConnection.clientKey2 = BigInt(clientKeys[1]);
this.clientConnection.player = player;
}

/**
* Validates an incoming user's credentials and returns an error code if a problem occurs.
* This also checks if the user is already online.
* @param username The incoming user's username input.
* @param password The incoming user's password input.
*/
private checkCredentials(username: string, password: string): number {
if(!username || !password) {
return LoginResponseCode.INVALID_CREDENTIALS;
}

username = username.trim().toLowerCase();
password = password.trim();

if(username === '' || password === '') {
return LoginResponseCode.INVALID_CREDENTIALS;
}

if(world.playerOnline(username)) {
return LoginResponseCode.ALREADY_LOGGED_IN;
}

return -1;
}

/**
* Sends a login response code (by itself) to the game client.
* Used for error responses.
* @param responseCode The response code to send to the game client.
*/
private sendLoginResponse(responseCode: number): void {
const outputBuffer = new ByteBuffer(1);
outputBuffer.put(responseCode, 'BYTE');
this.clientConnection.socket.write(outputBuffer);
}
}
4 changes: 2 additions & 2 deletions src/world/actor/player/updating/actor-updating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function registerNewActors(packet: Packet, player: Player, trackedActors:
continue;
}

if(!world.playerExists(nearbyActor)) {
if(!world.playerOnline(nearbyActor)) {
// Other player is no longer in the game world
continue;
}
Expand Down Expand Up @@ -85,7 +85,7 @@ export function updateTrackedActors(packet: Packet, playerPosition: Position, ap
let exists = true;

if(trackedActor instanceof Player) {
if(!world.playerExists(trackedActor as Player)) {
if(!world.playerOnline(trackedActor as Player)) {
exists = false;
}
} else {
Expand Down
17 changes: 11 additions & 6 deletions src/world/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,13 +475,18 @@ export class World {
return Promise.resolve();
}

public playerExists(player: Player): boolean {
const foundPlayer = this.playerList[player.worldIndex];
if(!foundPlayer) {
return false;
}
public playerOnline(player: Player | string): boolean {
if(typeof player === 'string') {
player = player.toLowerCase();
return this.playerList.findIndex(p => p !== null && p.username.toLowerCase() === player) !== -1;
} else {
const foundPlayer = this.playerList[player.worldIndex];
if(!foundPlayer) {
return false;
}

return foundPlayer.equals(player);
return foundPlayer.equals(player);
}
}

public registerPlayer(player: Player): boolean {
Expand Down