2 Commits

Author SHA1 Message Date
kuroppoi
91eb9971f3 Validate friend code checksum 2023-08-11 13:40:43 +02:00
kuroppoi
3224f6312a Only update id of the profile the user uses next when using the pid tool 2023-08-11 05:18:37 +02:00
5 changed files with 52 additions and 37 deletions

View File

@@ -15,7 +15,7 @@ import com.formdev.flatlaf.extras.components.FlatTextField;
import entralinked.Entralinked;
import entralinked.model.user.User;
import entralinked.model.user.UserManager;
import entralinked.utility.MD5;
import entralinked.utility.SwingUtility;
public class PidToolDialog {
@@ -41,8 +41,8 @@ public class PidToolDialog {
return;
}
String userId = wfcIdField.getText().replace("-", "");
String friendCode = friendCodeField.getText().replace("-", "");
String userId = wfcIdField.getText().replace("-", "").replaceAll("\\s+", "");
String friendCodeString = friendCodeField.getText().replace("-", "").replaceAll("\\s+", "");
// Make sure WFC ID is valid
if(!WFC_ID_PATTERN.matcher(userId).matches()) {
@@ -51,14 +51,12 @@ public class PidToolDialog {
}
// Make sure Friend Code is valid
if(!FRIEND_CODE_PATTERN.matcher(friendCode).matches()) {
if(!FRIEND_CODE_PATTERN.matcher(friendCodeString).matches()) {
JOptionPane.showMessageDialog(dialog, "Please enter a valid Friend Code.", "Attention", JOptionPane.WARNING_MESSAGE);
return;
}
UserManager userManager = entralinked.getUserManager();
User user = userManager.getUser(userId.substring(0, 13));
int profileId = (int)(Long.parseLong(friendCode) & 0x7FFFFFFF);
User user = entralinked.getUserManager().getUser(userId.substring(0, 13));
// Make sure user exists
if(user == null) {
@@ -66,17 +64,29 @@ public class PidToolDialog {
return;
}
// Show warning if this user has multiple profiles
if(user.getProfiles().size() > 1) {
if(JOptionPane.showConfirmDialog(dialog, "Multiple profiles detected. Do you want to update all of them?\n"
+ "This may cause error 60000 to occur on your other game cartridges.",
"Attention", JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
return;
}
long friendCode = Long.parseLong(friendCodeString);
int profileId = (int)(friendCode & 0x7FFFFFFF);
int checksum = (int)(friendCode >> 32);
// Compute friend code checksum
byte[] buffer = {0x00, 0x00, 0x00, 0x00, 0x4A, 0x41, 0x52, 0x49}; // Last 4 bytes is inverted game code (IRAJ)
buffer[0] = (byte)(profileId & 0xFF);
buffer[1] = (byte)(profileId >> 8 & 0xFF);
buffer[2] = (byte)(profileId >> 16 & 0xFF);
buffer[3] = (byte)(profileId >> 24 & 0xFF);
byte[] hash = MD5.digest(buffer);
int computedChecksum = hash[0] >> 1 & 0x7F;
// Compare checksums
if(computedChecksum != checksum) {
JOptionPane.showMessageDialog(dialog, "This is not a valid Friend Code. Please check for typos.", "Attention", JOptionPane.WARNING_MESSAGE);
return;
}
userManager.updateProfileIdForUser(user, profileId);
JOptionPane.showMessageDialog(dialog, "Profile has been updated. Please restart your game and use Game Sync.");
// Everything checks out -- update the profile id!
user.setProfileIdOverride(profileId);
JOptionPane.showMessageDialog(dialog,
"All done! Please restart your game and use Game Sync.\nGame profile data will be updated and saved once you do so.");
});
// Create content panel

View File

@@ -13,6 +13,7 @@ public class User {
private final String password; // I debated hashing it, but.. it's a 3-digit password...
private final Map<String, GameProfile> profiles = new HashMap<>();
private final Map<String, Dlc> dlcOverrides = new HashMap<>();
private int profileIdOverride; // For making it easier for the user to fix error 60000
public User(String id, String password) {
this.id = id;
@@ -70,4 +71,12 @@ public class User {
public Dlc getDlcOverride(String type) {
return dlcOverrides.get(type);
}
public void setProfileIdOverride(int profileIdOverride) {
this.profileIdOverride = profileIdOverride;
}
public int getProfileIdOverride() {
return profileIdOverride;
}
}

View File

@@ -233,26 +233,6 @@ public class UserManager {
return profile;
}
/**
* This will forcibly set the profile id of all profiles of this user to the specified one.
* Potentially a destructive operation; use with caution.
*
* @return {@code true} if the operation was successful, otherwise {@code false}.
*/
public boolean updateProfileIdForUser(User user, int profileId) {
// Set the id of all profiles
for(GameProfile profile : user.getProfiles()) {
profile.setId(profileId);
}
// Try to save user
if(!saveUser(user)) {
return false;
}
return true;
}
/**
* @return {@code true} if a user with the specified ID exists, otherwise {@code false}.
*/

View File

@@ -119,6 +119,15 @@ public class GameSpyHandler extends SimpleChannelInboundHandler<GameSpyRequest>
}
}
// Update profile id if an override is set
int profileIdOverride = user.getProfileIdOverride();
if(profileIdOverride > 0) {
profile.setId(profileIdOverride);
user.setProfileIdOverride(0);
userManager.saveUser(user); // It's not too big of a deal if this fails for some reason
}
// Prepare and send response
sessionKey = secureRandom.nextInt(Integer.MAX_VALUE);
String proof = createCredentialHash(partnerChallengeHash, authToken, serverChallenge, clientChallenge);

View File

@@ -21,6 +21,13 @@ public class MD5 {
* @return A hex-formatted MD5 hash of the specified input.
*/
public static String digest(String string) {
return StringUtil.toHexStringPadded(digest(string.getBytes(StandardCharsets.ISO_8859_1)));
}
/**
* @return An MD5 hash of the specified input.
*/
public static byte[] digest(byte[] bytes) {
if(digest == null) {
try {
digest = MessageDigest.getInstance("MD5");
@@ -29,6 +36,6 @@ public class MD5 {
}
}
return StringUtil.toHexStringPadded(digest.digest(string.getBytes(StandardCharsets.ISO_8859_1)));
return digest.digest(bytes);
}
}