mirror of
https://github.com/kuroppoi/entralinked.git
synced 2026-04-25 15:47:00 -05:00
Improve Game Sync ID processing
Also fixes an exception that occurs when a negative PID is sent through Memory Link.
This commit is contained in:
parent
afc79c39ad
commit
d5c2a57c51
|
|
@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import entralinked.GameVersion;
|
||||
import entralinked.utility.GsidUtility;
|
||||
|
||||
/**
|
||||
* Manager class for managing {@link Player} information (Global Link users)
|
||||
|
|
@ -117,9 +118,14 @@ public class PlayerManager {
|
|||
Player player = mapper.readValue(inputFile, PlayerDto.class).toPlayer();
|
||||
String gameSyncId = player.getGameSyncId();
|
||||
|
||||
// Check if Game Sync ID is valid
|
||||
if(!GsidUtility.isValidGameSyncId(gameSyncId)) {
|
||||
throw new IOException("Invalid Game Sync ID: %s".formatted(gameSyncId));
|
||||
}
|
||||
|
||||
// Check for duplicate Game Sync ID
|
||||
if(doesPlayerExist(gameSyncId)) {
|
||||
throw new IOException("Duplicate Game Sync ID %s".formatted(gameSyncId));
|
||||
throw new IOException("Duplicate Game Sync ID: %s".formatted(gameSyncId));
|
||||
}
|
||||
|
||||
player.setDataDirectory(inputFile.getParentFile());
|
||||
|
|
|
|||
|
|
@ -321,6 +321,13 @@ public class PglHandler implements HttpHandler {
|
|||
*/
|
||||
private void handleMemoryLink(PglRequest request, Context ctx) throws IOException {
|
||||
LEOutputStream outputStream = new LEOutputStream(ctx.outputStream());
|
||||
|
||||
// Check if Game Sync ID is valid
|
||||
if(!GsidUtility.isValidGameSyncId(request.gameSyncId())) {
|
||||
writeStatusCode(outputStream, 8); // Invalid Game Sync ID
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = playerManager.getPlayer(request.gameSyncId());
|
||||
User user = ctx.attribute("user");
|
||||
|
||||
|
|
@ -472,9 +479,9 @@ public class PglHandler implements HttpHandler {
|
|||
// Prepare response
|
||||
LEOutputStream outputStream = new LEOutputStream(ctx.outputStream());
|
||||
|
||||
// Make sure Game Sync ID is present
|
||||
if(request.gameSyncId() == null) {
|
||||
writeStatusCode(outputStream, 1); // Unauthorized
|
||||
// Check if Game Sync ID is valid
|
||||
if(!GsidUtility.isValidGameSyncId(request.gameSyncId())) {
|
||||
writeStatusCode(outputStream, 8); // Invalid Game Sync ID
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -511,6 +518,12 @@ public class PglHandler implements HttpHandler {
|
|||
LEOutputStream outputStream = new LEOutputStream(ctx.outputStream());
|
||||
String gameSyncId = GsidUtility.stringifyGameSyncId(Integer.parseInt(ctx.body().replace("\u0000", ""))); // So quirky
|
||||
|
||||
// Check if Game Sync ID is valid
|
||||
if(!GsidUtility.isValidGameSyncId(request.gameSyncId())) {
|
||||
writeStatusCode(outputStream, 8); // Invalid Game Sync ID
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if player doesn't exist already
|
||||
if(playerManager.doesPlayerExist(gameSyncId)) {
|
||||
writeStatusCode(outputStream, 2); // Duplicate Game Sync ID
|
||||
|
|
|
|||
|
|
@ -25,12 +25,6 @@ public class GsidDeserializer extends StdDeserializer<String> {
|
|||
|
||||
@Override
|
||||
public String deserialize(JsonParser parser, DeserializationContext context) throws IOException {
|
||||
int gsid = parser.getIntValue();
|
||||
|
||||
if(gsid < 0) {
|
||||
throw new IOException("Game Sync ID cannot be a negative number.");
|
||||
}
|
||||
|
||||
return GsidUtility.stringifyGameSyncId(gsid);
|
||||
return GsidUtility.stringifyGameSyncId(parser.getValueAsInt(-1));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,40 +2,87 @@ package entralinked.utility;
|
|||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Game Sync ID generation process:
|
||||
*
|
||||
* Let's take example PID "1231499195".
|
||||
* Start by storing both the PID and its checksum (35497) in working variable "ugsid".
|
||||
* We do this by simply shifting the checksum 32 bits to the left: ugsid = pid | (checksum << 32) = 0x8AA949672FBB
|
||||
*
|
||||
* Calculating each character is pretty straightforward.
|
||||
* We just use the 5 least significant bits of ugsid as the index for the character table.
|
||||
* Character 1: 0x8AA949672FBB & 0x1F = 27 = '5'
|
||||
*
|
||||
* After each character, we shift ugsid 5 bits to the right. Since ugsid contains 48 bits of data,
|
||||
* taking the 5 least significant bits each time gives us enough indexes for (if we round up) exactly 10 characters.
|
||||
* If we take a look at the full value of ugsid (0x8AA949672FBB) in binary and split it into sections of 5 bits,
|
||||
* we'll actually already be able to see the entire Game Sync ID in reverse:
|
||||
*
|
||||
* Character: 'E' 'L' 'X' 'F' 'E' 'Y' 'Q' 'M' '7' '5'
|
||||
* Chartable index: 4 10 21 5 4 22 14 11 29 27
|
||||
* Binary: XX100 01010 10101 00101 00100 10110 01110 01011 11101 11011
|
||||
*
|
||||
* Adding all of the characters together gets us the Game Sync ID "57MQYEFXLE".
|
||||
*
|
||||
* Reversing this process to retrieve the PID and checksum is very straightforward.
|
||||
* Simply go through each character, find the index of it in the character table & left shift the total value 5 bits each time.
|
||||
* If we do this with the Game Sync ID we just created, then it should give us back the value of ugsid: 0x8AA949672FBB
|
||||
* To then retrieve the PID, simply do: ugsid & 0xFFFFFFFF = 1231499195
|
||||
* To retrieve the checksum, simply do: (ugsid >> 32) & 0xFFFF = 35497
|
||||
* We can then validate the Game Sync ID if we want to by comparing the checksums.
|
||||
*/
|
||||
public class GsidUtility {
|
||||
|
||||
public static final String GSID_CHARTABLE = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||
public static final Pattern GSID_PATTERN = Pattern.compile("[A-HJ-NP-Z2-9]{10}");
|
||||
|
||||
/**
|
||||
* Stringifies the specified numerical Game Sync ID
|
||||
* Stringifies the specified numerical Game Sync ID.
|
||||
*
|
||||
* Black 2 - {@code sub_21B480C} (overlay #199)
|
||||
*/
|
||||
public static String stringifyGameSyncId(int gsid) {
|
||||
char[] output = new char[10];
|
||||
int index = 0;
|
||||
long checksum = Crc16.calc(gsid);
|
||||
long ugsid = gsid | (checksum << 32);
|
||||
|
||||
// v12 = gsid
|
||||
// v5 = sub_204405C(gsid, 4u)
|
||||
// v8 = v5 + __CFSHR__(v12, 31) + (v12 >> 31)
|
||||
|
||||
// uses unsigned ints for bitshift operations
|
||||
long ugsid = gsid;
|
||||
long checksum = Crc16.calc(gsid); // + __CFSHR__(v12, 31) + (v12 >> 31); ??
|
||||
|
||||
// do while v4 < 10
|
||||
for(int i = 0; i < output.length; i++) {
|
||||
index = (int)((ugsid & 0x1F) & 0x1FFFF); // chartable string is unicode, so normally multiplies by 2
|
||||
ugsid = (ugsid >> 5) | (checksum << 27);
|
||||
checksum >>= 5;
|
||||
output[i] = GSID_CHARTABLE.charAt(index); // sub_2048734(v4, chartable + index)
|
||||
int index = (int)((ugsid >> (5 * i)) & 0x1F);
|
||||
output[i] = GSID_CHARTABLE.charAt(index);
|
||||
}
|
||||
|
||||
return new String(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a Game Sync ID is valid by checking its length, characters & checksum.
|
||||
*
|
||||
* @return {@code true} if the Game Sync ID is valid, otherwise {@code false}.
|
||||
*/
|
||||
public static boolean isValidGameSyncId(String gsid) {
|
||||
return GSID_PATTERN.matcher(gsid).matches();
|
||||
if(gsid == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int length = gsid.length();
|
||||
long ugsid = 0;
|
||||
|
||||
if(length != 10) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < length; i++) {
|
||||
int index = GSID_CHARTABLE.indexOf(gsid.charAt(i));
|
||||
|
||||
if(index == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ugsid |= (long)index << (5 * i);
|
||||
}
|
||||
|
||||
int output = (int)(ugsid & 0xFFFFFFFF);
|
||||
int checksum = (int)((ugsid >> 32) & 0xFFFF);
|
||||
return output >= 0 && Crc16.calc(output) == checksum;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,13 @@ public class GsidUtilityTest {
|
|||
// Illegal length (should be 10)
|
||||
assertFalse(GsidUtility.isValidGameSyncId("Y67UEN38K"));
|
||||
assertFalse(GsidUtility.isValidGameSyncId("3ER5K8MBN4C"));
|
||||
|
||||
// Invalid checksum
|
||||
assertFalse(GsidUtility.isValidGameSyncId("VFWM2Q2ADH"));
|
||||
assertFalse(GsidUtility.isValidGameSyncId("44DAWDA4SH"));
|
||||
assertFalse(GsidUtility.isValidGameSyncId("J6F55U7FUE"));
|
||||
assertFalse(GsidUtility.isValidGameSyncId("8FAB4ZF6JF"));
|
||||
assertFalse(GsidUtility.isValidGameSyncId("HWLNS77HWD"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -38,8 +45,8 @@ public class GsidUtilityTest {
|
|||
void testValidGameSyncIds() {
|
||||
assertTrue(GsidUtility.isValidGameSyncId("VFWM2QAXNF"));
|
||||
assertTrue(GsidUtility.isValidGameSyncId("44DAWDJKJ8"));
|
||||
assertTrue(GsidUtility.isValidGameSyncId("J6F55UB2X9"));
|
||||
assertTrue(GsidUtility.isValidGameSyncId("8FAB4Z3EN9"));
|
||||
assertTrue(GsidUtility.isValidGameSyncId("J6F55UB2XD"));
|
||||
assertTrue(GsidUtility.isValidGameSyncId("8FAB4Z3END"));
|
||||
assertTrue(GsidUtility.isValidGameSyncId("HWLNS7BTNB"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user