|
2 | 2 |
|
3 | 3 | import java.util.regex.Pattern; |
4 | 4 |
|
| 5 | +/** |
| 6 | + * Game Sync ID generation process: |
| 7 | + * |
| 8 | + * Let's take example PID "1231499195". |
| 9 | + * Start by storing both the PID and its checksum (35497) in working variable "ugsid". |
| 10 | + * We do this by simply shifting the checksum 32 bits to the left: ugsid = pid | (checksum << 32) = 0x8AA949672FBB |
| 11 | + * |
| 12 | + * Calculating each character is pretty straightforward. |
| 13 | + * We just use the 5 least significant bits of ugsid as the index for the character table. |
| 14 | + * Character 1: 0x8AA949672FBB & 0x1F = 27 = '5' |
| 15 | + * |
| 16 | + * After each character, we shift ugsid 5 bits to the right. Since ugsid contains 48 bits of data, |
| 17 | + * taking the 5 least significant bits each time gives us enough indexes for (if we round up) exactly 10 characters. |
| 18 | + * If we take a look at the full value of ugsid (0x8AA949672FBB) in binary and split it into sections of 5 bits, |
| 19 | + * we'll actually already be able to see the entire Game Sync ID in reverse: |
| 20 | + * |
| 21 | + * Character: 'E' 'L' 'X' 'F' 'E' 'Y' 'Q' 'M' '7' '5' |
| 22 | + * Chartable index: 4 10 21 5 4 22 14 11 29 27 |
| 23 | + * Binary: XX100 01010 10101 00101 00100 10110 01110 01011 11101 11011 |
| 24 | + * |
| 25 | + * Adding all of the characters together gets us the Game Sync ID "57MQYEFXLE". |
| 26 | + * |
| 27 | + * Reversing this process to retrieve the PID and checksum is very straightforward. |
| 28 | + * Simply go through each character, find the index of it in the character table & left shift the total value 5 bits each time. |
| 29 | + * If we do this with the Game Sync ID we just created, then it should give us back the value of ugsid: 0x8AA949672FBB |
| 30 | + * To then retrieve the PID, simply do: ugsid & 0xFFFFFFFF = 1231499195 |
| 31 | + * To retrieve the checksum, simply do: (ugsid >> 32) & 0xFFFF = 35497 |
| 32 | + * We can then validate the Game Sync ID if we want to by comparing the checksums. |
| 33 | + */ |
5 | 34 | public class GsidUtility { |
6 | 35 |
|
7 | 36 | public static final String GSID_CHARTABLE = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; |
8 | 37 | public static final Pattern GSID_PATTERN = Pattern.compile("[A-HJ-NP-Z2-9]{10}"); |
9 | 38 |
|
10 | 39 | /** |
11 | | - * Stringifies the specified numerical Game Sync ID |
| 40 | + * Stringifies the specified numerical Game Sync ID. |
12 | 41 | * |
13 | 42 | * Black 2 - {@code sub_21B480C} (overlay #199) |
14 | 43 | */ |
15 | 44 | public static String stringifyGameSyncId(int gsid) { |
16 | 45 | char[] output = new char[10]; |
17 | | - int index = 0; |
| 46 | + long checksum = Crc16.calc(gsid); |
| 47 | + long ugsid = gsid | (checksum << 32); |
18 | 48 |
|
19 | | - // v12 = gsid |
20 | | - // v5 = sub_204405C(gsid, 4u) |
21 | | - // v8 = v5 + __CFSHR__(v12, 31) + (v12 >> 31) |
22 | | - |
23 | | - // uses unsigned ints for bitshift operations |
24 | | - long ugsid = gsid; |
25 | | - long checksum = Crc16.calc(gsid); // + __CFSHR__(v12, 31) + (v12 >> 31); ?? |
26 | | - |
27 | | - // do while v4 < 10 |
28 | 49 | for(int i = 0; i < output.length; i++) { |
29 | | - index = (int)((ugsid & 0x1F) & 0x1FFFF); // chartable string is unicode, so normally multiplies by 2 |
30 | | - ugsid = (ugsid >> 5) | (checksum << 27); |
31 | | - checksum >>= 5; |
32 | | - output[i] = GSID_CHARTABLE.charAt(index); // sub_2048734(v4, chartable + index) |
| 50 | + int index = (int)((ugsid >> (5 * i)) & 0x1F); |
| 51 | + output[i] = GSID_CHARTABLE.charAt(index); |
33 | 52 | } |
34 | 53 |
|
35 | 54 | return new String(output); |
36 | 55 | } |
37 | 56 |
|
| 57 | + /** |
| 58 | + * Determines if a Game Sync ID is valid by checking its length, characters & checksum. |
| 59 | + * |
| 60 | + * @return {@code true} if the Game Sync ID is valid, otherwise {@code false}. |
| 61 | + */ |
38 | 62 | public static boolean isValidGameSyncId(String gsid) { |
39 | | - return GSID_PATTERN.matcher(gsid).matches(); |
| 63 | + if(gsid == null) { |
| 64 | + return false; |
| 65 | + } |
| 66 | + |
| 67 | + int length = gsid.length(); |
| 68 | + long ugsid = 0; |
| 69 | + |
| 70 | + if(length != 10) { |
| 71 | + return false; |
| 72 | + } |
| 73 | + |
| 74 | + for(int i = 0; i < length; i++) { |
| 75 | + int index = GSID_CHARTABLE.indexOf(gsid.charAt(i)); |
| 76 | + |
| 77 | + if(index == -1) { |
| 78 | + return false; |
| 79 | + } |
| 80 | + |
| 81 | + ugsid |= (long)index << (5 * i); |
| 82 | + } |
| 83 | + |
| 84 | + int output = (int)(ugsid & 0xFFFFFFFF); |
| 85 | + int checksum = (int)((ugsid >> 32) & 0xFFFF); |
| 86 | + return output >= 0 && Crc16.calc(output) == checksum; |
40 | 87 | } |
41 | 88 | } |
0 commit comments