Skip to content

Commit

Permalink
Add gui tool for fixing error 60000
Browse files Browse the repository at this point in the history
  • Loading branch information
kuroppoi committed Aug 10, 2023
1 parent c0cc952 commit 306b9e6
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 25 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,4 @@ Entralinked has a built-in DNS server.\
In order for your game to connect, you must configure the DNS settings of your DS.\
By default, Entralinked is configured to automatically use the local host of the system.\
This approach is not always accurate, however, and you may need to manually configure it in `config.json`.\
If you receive error code `60000` when trying to connect, erase the WFC Configuration of your DS and try again.\
After tucking in a Pokémon, navigate to http://localhost/dashboard/profile.html to configure Game Sync settings.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
implementation 'io.javalin:javalin:5.5.0'
implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0'
implementation 'com.formdev:flatlaf:3.1.1'
implementation 'com.formdev:flatlaf-extras:3.1.1'
implementation 'com.formdev:flatlaf-intellij-themes:3.1.1'
}

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/entralinked/Entralinked.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public static void main(String[] args) {
private final GameSpyServer gameSpyServer;
private final HttpServer httpServer;
private MainView mainView;
private boolean initialized;

public Entralinked(String[] args) {
long beginTime = System.currentTimeMillis();
Expand Down Expand Up @@ -131,6 +132,8 @@ public Entralinked(String[] args) {
"ERROR: Entralinked failed to start. Please check the logs for info."));
}
}

initialized = true;
}

public boolean startServers() {
Expand Down Expand Up @@ -192,4 +195,8 @@ public UserManager getUserManager() {
public PlayerManager getPlayerManager() {
return playerManager;
}

public boolean isInitialized() {
return initialized;
}
}
13 changes: 13 additions & 0 deletions src/main/java/entralinked/gui/MainView.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
Expand All @@ -35,6 +37,7 @@

import entralinked.Entralinked;
import entralinked.utility.ConsumerAppender;
import entralinked.utility.SwingUtility;

/**
* Simple Swing user interface.
Expand Down Expand Up @@ -110,6 +113,15 @@ public Dimension getPreferredSize() {

// Create window
JFrame frame = new JFrame("Entralinked");

// Create menu bar
JMenuBar menuBar = new JMenuBar();
JMenu helpMenu = new JMenu("Help");
helpMenu.add(SwingUtility.createAction("Update PID (Error 60000)", () -> new PidToolDialog(entralinked, frame)));
helpMenu.add(SwingUtility.createAction("GitHub", () -> openUrl("https://github.com/kuroppoi/entralinked")));
menuBar.add(helpMenu);

// Set window properties
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent event) {
Expand All @@ -131,6 +143,7 @@ public void windowClosing(WindowEvent event) {
new ImageIcon(getClass().getResource("/icon-16x.png")).getImage()));
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setMinimumSize(new Dimension(512, 288));
frame.setJMenuBar(menuBar);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
Expand Down
106 changes: 106 additions & 0 deletions src/main/java/entralinked/gui/PidToolDialog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package entralinked.gui;

import java.awt.GridBagLayout;
import java.util.regex.Pattern;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

import com.formdev.flatlaf.extras.components.FlatTextField;

import entralinked.Entralinked;
import entralinked.model.user.User;
import entralinked.model.user.UserManager;
import entralinked.utility.SwingUtility;

public class PidToolDialog {

public static final Pattern WFC_ID_PATTERN = Pattern.compile("[0-9]{16}");
public static final Pattern FRIEND_CODE_PATTERN = Pattern.compile("[0-9]{12}");

public PidToolDialog(Entralinked entralinked, JFrame frame) {
// Create dialog
JDialog dialog = new JDialog(frame, "PID Tool");

// Create input fields
FlatTextField wfcIdField = new FlatTextField();
wfcIdField.setPlaceholderText("XXXX-XXXX-XXXX-XXXX");
FlatTextField friendCodeField = new FlatTextField();
friendCodeField.setPlaceholderText("XXXX-XXXX-XXXX");

// Create logic
JButton updateButton = new JButton("Update");
updateButton.addActionListener(event -> {
if(!entralinked.isInitialized()) {
JOptionPane.showMessageDialog(dialog, "Please wait for Entralinked to finish starting.", "Attention", JOptionPane.WARNING_MESSAGE);
return;
}

String userId = wfcIdField.getText().replace("-", "");
String friendCode = friendCodeField.getText().replace("-", "");

// Make sure WFC ID is valid
if(!WFC_ID_PATTERN.matcher(userId).matches()) {
JOptionPane.showMessageDialog(dialog, "Please enter a valid Wi-Fi Connection ID.", "Attention", JOptionPane.WARNING_MESSAGE);
return;
}

// Make sure Friend Code is valid
if(!FRIEND_CODE_PATTERN.matcher(friendCode).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);

// Make sure user exists
if(user == null) {
JOptionPane.showMessageDialog(dialog, "This Wi-Fi Connection ID does not exist.", "Attention", JOptionPane.WARNING_MESSAGE);
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;
}
}

userManager.updateProfileIdForUser(user, profileId);
JOptionPane.showMessageDialog(dialog, "Profile has been updated. Please restart your game and use Game Sync.");
});

// Create content panel
JPanel panel = new JPanel(new GridBagLayout());
String infoLabel = """
<html>
Enter the Wi-Fi Connection ID found in the internet settings of your DS<br>
as well as your Friend Code which you can view in-game using the Pal Pad.<br>
Confirm that the input data is correct and press 'Update'<br>
</html>
""";
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
panel.add(new JLabel(infoLabel), SwingUtility.createConstraints(0, 0, 2, 1));
panel.add(new JLabel("Wi-Fi Connection ID"), SwingUtility.createConstraints(0, 1, 1, 1, 0, 1));
panel.add(wfcIdField, SwingUtility.createConstraints(1, 1));
panel.add(new JLabel("Friend Code"), SwingUtility.createConstraints(0, 2, 1, 1, 0, 1));
panel.add(friendCodeField, SwingUtility.createConstraints(1, 2));
panel.add(updateButton, SwingUtility.createConstraints(0, 3, 2, 1));

// Set dialog properties
dialog.setResizable(false);
dialog.add(panel);
dialog.pack();
dialog.setLocationRelativeTo(frame);
dialog.setVisible(true);
}
}
6 changes: 5 additions & 1 deletion src/main/java/entralinked/model/user/GameProfile.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public class GameProfile {

private final int id;
private int id;
private String firstName;
private String lastName;
private String aimName;
Expand All @@ -20,6 +20,10 @@ public GameProfile(int id, String firstName, String lastName, String aimName, St
this.zipCode = zipCode;
}

public void setId(int id) {
this.id = id;
}

public int getId() {
return id;
}
Expand Down
38 changes: 15 additions & 23 deletions src/main/java/entralinked/model/user/UserManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public class UserManager {
private static final Logger logger = LogManager.getLogger();
private final ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
private final Map<String, User> users = new ConcurrentHashMap<>();
private final Map<Integer, GameProfile> profiles = new ConcurrentHashMap<>();
private final Map<String, ServiceSession> serviceSessions = new ConcurrentHashMap<>();
private final File dataDirectory = new File("users");

Expand All @@ -49,7 +48,7 @@ public UserManager() {
}
}

logger.info("Loaded {} user(s) with a total of {} profile(s)", users.size(), profiles.size());
logger.info("Loaded {} user(s)", users.size());
}

/**
Expand All @@ -75,20 +74,8 @@ private void loadUser(File inputFile) {
throw new IOException("Duplicate user ID %s".formatted(id));
}

// Check for duplicate profile IDs before indexing anything
Collection<GameProfile> userProfiles = user.getProfiles();

if(userProfiles.stream().map(GameProfile::getId).anyMatch(profiles::containsKey)) {
throw new IOException("Duplicate profile ID in user %s".formatted(id));
}

// Index user
users.put(id, user);

// Index profiles
for(GameProfile profile : userProfiles) {
profiles.put(profile.getId(), profile);
}
} catch(IOException e) {
logger.error("Could not load user data at {}", inputFile.getAbsolutePath(), e);
}
Expand Down Expand Up @@ -233,7 +220,7 @@ public GameProfile createProfileForUser(User user, String branchCode) {
return null;
}

int profileId = nextProfileId();
int profileId = (int)(Math.random() * Integer.MAX_VALUE);
GameProfile profile = new GameProfile(profileId);
user.addProfile(branchCode, profile);

Expand All @@ -243,22 +230,27 @@ public GameProfile createProfileForUser(User user, String branchCode) {
return null;
}

profiles.put(profileId, profile);
return profile;
}

/**
* @return A unique random 32-bit profile ID.
* 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}.
*/
private int nextProfileId() {
int profileId = (int)(Math.random() * Integer.MAX_VALUE);
public boolean updateProfileIdForUser(User user, int profileId) {
// Set the id of all profiles
for(GameProfile profile : user.getProfiles()) {
profile.setId(profileId);
}

// I live for that microscopic chance of StackOverflowError
if(profiles.containsKey(profileId)) {
return nextProfileId();
// Try to save user
if(!saveUser(user)) {
return false;
}

return profileId;
return true;
}

/**
Expand Down
58 changes: 58 additions & 0 deletions src/main/java/entralinked/utility/SwingUtility.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package entralinked.utility;

import java.awt.GridBagConstraints;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;

public class SwingUtility {

@SuppressWarnings("serial")
public static Action createAction(String name, Icon icon, Runnable handler) {
AbstractAction action = new AbstractAction(name, icon) {
@Override
public void actionPerformed(ActionEvent event) {
handler.run();
}
};

if(icon != null) {
action.putValue(Action.SHORT_DESCRIPTION, name);
}

return action;
}

public static Action createAction(String name, Runnable handler) {
return createAction(name, null, handler);
}

public static GridBagConstraints createConstraints(int x, int y) {
return createConstraints(x, y, 1, 1);
}

public static GridBagConstraints createConstraints(int x, int y, int width, int height) {
return createConstraints(x, y, width, height, 1, 1);
}

public static GridBagConstraints createConstraints(int x, int y, int width, int height, double weightX, double weightY) {
return createConstraints(x, y, width, height, weightX, weightY, 8, 8);
}

public static GridBagConstraints createConstraints(int x, int y, int width, int height, double weightX, double weightY,
int paddingX, int paddingY) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.BOTH;
constraints.gridx = x;
constraints.gridy = y;
constraints.gridwidth = width;
constraints.gridheight = height;
constraints.weightx = weightX;
constraints.weighty = weightY;
constraints.ipadx = paddingX;
constraints.ipady = paddingY;
return constraints;
}
}

0 comments on commit 306b9e6

Please sign in to comment.