mirror of
https://github.com/kuroppoi/entralinked.git
synced 2026-04-25 15:47:00 -05:00
Add gui tool for fixing error 60000
This commit is contained in:
parent
c0cc952cc2
commit
306b9e6db6
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ public class Entralinked {
|
|||
private final GameSpyServer gameSpyServer;
|
||||
private final HttpServer httpServer;
|
||||
private MainView mainView;
|
||||
private boolean initialized;
|
||||
|
||||
public Entralinked(String[] args) {
|
||||
long beginTime = System.currentTimeMillis();
|
||||
|
|
@ -131,6 +132,8 @@ public class Entralinked {
|
|||
"ERROR: Entralinked failed to start. Please check the logs for info."));
|
||||
}
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public boolean startServers() {
|
||||
|
|
@ -192,4 +195,8 @@ public class Entralinked {
|
|||
public PlayerManager getPlayerManager() {
|
||||
return playerManager;
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import javax.swing.ImageIcon;
|
|||
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;
|
||||
|
|
@ -35,6 +37,7 @@ import com.formdev.flatlaf.intellijthemes.FlatOneDarkIJTheme;
|
|||
|
||||
import entralinked.Entralinked;
|
||||
import entralinked.utility.ConsumerAppender;
|
||||
import entralinked.utility.SwingUtility;
|
||||
|
||||
/**
|
||||
* Simple Swing user interface.
|
||||
|
|
@ -110,6 +113,15 @@ public class MainView {
|
|||
|
||||
// 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) {
|
||||
|
|
@ -131,6 +143,7 @@ public class MainView {
|
|||
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);
|
||||
|
|
|
|||
106
src/main/java/entralinked/gui/PidToolDialog.java
Normal file
106
src/main/java/entralinked/gui/PidToolDialog.java
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package entralinked.model.user;
|
|||
|
||||
public class GameProfile {
|
||||
|
||||
private final int id;
|
||||
private int id;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String aimName;
|
||||
|
|
@ -20,6 +20,10 @@ public class GameProfile {
|
|||
this.zipCode = zipCode;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
@ -49,7 +48,7 @@ public class UserManager {
|
|||
}
|
||||
}
|
||||
|
||||
logger.info("Loaded {} user(s) with a total of {} profile(s)", users.size(), profiles.size());
|
||||
logger.info("Loaded {} user(s)", users.size());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -75,20 +74,8 @@ public class UserManager {
|
|||
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);
|
||||
}
|
||||
|
|
@ -233,7 +220,7 @@ public class UserManager {
|
|||
return null;
|
||||
}
|
||||
|
||||
int profileId = nextProfileId();
|
||||
int profileId = (int)(Math.random() * Integer.MAX_VALUE);
|
||||
GameProfile profile = new GameProfile(profileId);
|
||||
user.addProfile(branchCode, profile);
|
||||
|
||||
|
|
@ -243,22 +230,27 @@ public class UserManager {
|
|||
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);
|
||||
|
||||
// I live for that microscopic chance of StackOverflowError
|
||||
if(profiles.containsKey(profileId)) {
|
||||
return nextProfileId();
|
||||
public boolean updateProfileIdForUser(User user, int profileId) {
|
||||
// Set the id of all profiles
|
||||
for(GameProfile profile : user.getProfiles()) {
|
||||
profile.setId(profileId);
|
||||
}
|
||||
|
||||
return profileId;
|
||||
// Try to save user
|
||||
if(!saveUser(user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
58
src/main/java/entralinked/utility/SwingUtility.java
Normal file
58
src/main/java/entralinked/utility/SwingUtility.java
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user