Compare commits
21 Commits
72b075bf7b
...
beta-2
| Author | SHA1 | Date | |
|---|---|---|---|
| fcb71d4600 | |||
| 8ebec68caf | |||
| 002d995c05 | |||
| 9780c3cc4f | |||
| 8107aaa5d5 | |||
| 6d772f0949 | |||
| d8902af148 | |||
| 2e1fe95670 | |||
| 45fbe80f9f | |||
| 886ef3089d | |||
| 28d710a815 | |||
| a1f41584a4 | |||
| e2229afe70 | |||
| b9743ee456 | |||
| fcda7b8cf4 | |||
| 433bc685dc | |||
| 5ab96366a6 | |||
| e4497afaa3 | |||
| 5a004b6744 | |||
| 7f5b0b85ae | |||
| f582cdb052 |
26
README.md
26
README.md
@@ -1,8 +1,19 @@
|
|||||||

|

|
||||||
|
|
||||||
# about 60% complete (config I/O in progress, basic systems functional)
|
|
||||||
|
|
||||||
Firestar is a mod manager for WipEout 2048 which automatically handles sorting mods by priority and repacking game assets based on selected add-on packs. It runs on a desktop/laptop computer and aims to allow easy installation of mods for users who have only a surface level understanding of hacking the PSVita.
|
Firestar is a mod manager for WipEout 2048 which automatically handles sorting mods by priority and repacking game assets based on selected add-on packs. It runs on a desktop/laptop computer and aims to allow easy installation of mods for users who have only a surface level understanding of hacking the PSVita.
|
||||||
|
|
||||||
|
# about 85% complete
|
||||||
|
**What works:**
|
||||||
|
- Importing & deleting mods
|
||||||
|
- Repacking (installing) mods
|
||||||
|
- config loading
|
||||||
|
- Cross-platform support
|
||||||
|
|
||||||
|
**What doesn't work yet:**
|
||||||
|
- change load order, toggle
|
||||||
|
- settings screen
|
||||||
|
- misc. tools (pack creator, soundtrack mod generator...)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# System Requirements
|
# System Requirements
|
||||||
@@ -27,11 +38,9 @@ Typical mod metadata will look something like this:
|
|||||||
```
|
```
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
## What is the difference between Compatibility Mode and Fast Mode?
|
|
||||||
Fast Mode is the most intuitive solution. It simply unpacks all of the assets into a folder for the game to read. This makes it trivial to go back and modify it later without repacking anything and it means you don't need stolen Sony-confidential equipment. However, PSVita and PSTV memory cards use the exFAT filesystem which really does not play nice with WipEout assets and causes all sorts of glitches, so using a genuine PSARC is an absolute necessity when playing on a real console. Compatibility mode uses a real PSARC tool to try and create mod files as similar to the original game files as possible and create minimal friction with the game engine.
|
|
||||||
|
|
||||||
## (Non-Windows) Do I need WINE? And why?
|
## (Non-Windows) Do I need WINE? And why?
|
||||||
Only if you use Compatibility Mode. psp2psarc.exe is a Windows-only tool so we call WINE internally when needed, but Firestar itself will run on any desktop platform with a compliant Java runtime.
|
Yes. psp2psarc.exe is a Windows-only tool. Firestar can run natively on the system but it needs WINE in order to pack the game assets.
|
||||||
|
**If you are on Mac OS X you will need an older OS version that is still compatible with 32-bit applications.**
|
||||||
|
|
||||||
## Why do I need a leaked Playstation SDK?
|
## Why do I need a leaked Playstation SDK?
|
||||||
Because many third-party programs for opening PSARC files either have no ability to create new ones or are prone to corruption bugs. Using the one made by the developers of the file format is the best option as it's what the game developers would have used. Obviously this creates all kinds of legal grey areas so I can't distribute Sony's proprietary software next to my code. It's downloaded separately.
|
Because many third-party programs for opening PSARC files either have no ability to create new ones or are prone to corruption bugs. Using the one made by the developers of the file format is the best option as it's what the game developers would have used. Obviously this creates all kinds of legal grey areas so I can't distribute Sony's proprietary software next to my code. It's downloaded separately.
|
||||||
@@ -40,4 +49,7 @@ Because many third-party programs for opening PSARC files either have no ability
|
|||||||
Cross-platform support with no hassle, a built-in UI framework, and I don't need to sell my soul like every Electron or React 'Native' developer is doing these days.
|
Cross-platform support with no hassle, a built-in UI framework, and I don't need to sell my soul like every Electron or React 'Native' developer is doing these days.
|
||||||
|
|
||||||
## Why the name "Firestar"?
|
## Why the name "Firestar"?
|
||||||
Firestar was a secret track in the original WipEout which you unlocked after beating the game at least once, the mod manager is just named after that. Major versions are named after pilots or tracks. Internal class names are named after Muppets characters as this is the same naming convention that Sony used to codename parts inside the PSVita's SOC.
|
Firestar was a secret track in the original WipEout which you unlocked after beating the game at least once, the mod manager is just named after that. Major versions are named after pilots or tracks. Internal class names are named after Muppets characters as this is the same naming convention that Sony used to codename parts inside the PSVita's SOC.
|
||||||
|
|
||||||
|
## Fast Mode vs. Compatibility Mode
|
||||||
|
"Fast Mode" was a legacy option that was scrapped before it could be developed. It skipped the creation of a PSARC file and simply dumped the final game assets to their destination folder. This works great for emulators, and doesn't require proprietary stolen SDKs from SCE. However the filesystem on the Playstation Vita really hated this method and would glitch out when this was done, causing textures to be corrupted once placed on the memory card. The WipEout engine has no fault tolerance for malformed or missing assets, so it just crashes instantly when it encounters one. This is where Compatibility mode comes from, which is the default setting for 2048.
|
||||||
@@ -86,7 +86,7 @@ public class Gonzo {
|
|||||||
final Thread managerThread = new Thread() {
|
final Thread managerThread = new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!Main.repatch) {
|
if (Main.repatch) {
|
||||||
CompatibilityRoutine();
|
CompatibilityRoutine();
|
||||||
} else {
|
} else {
|
||||||
FastRoutine();
|
FastRoutine();
|
||||||
@@ -128,7 +128,7 @@ public class Gonzo {
|
|||||||
System.out.println(line);
|
System.out.println(line);
|
||||||
consoleDisplay.append(line + "\n");
|
consoleDisplay.append(line + "\n");
|
||||||
try {scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());}
|
try {scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());}
|
||||||
catch (Exception e) {System.out.println("WARNING: Swing failed to paint window due to race condition.\n" + e.getMessage());}
|
catch (Exception e) {System.out.println("WARNING: Swing failed to paint window due to race condition. You can safely ignore this.\n" + e.getMessage());}
|
||||||
}
|
}
|
||||||
reader.close();
|
reader.close();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
@@ -141,6 +141,7 @@ public class Gonzo {
|
|||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
System.out.println(e.getMessage());
|
System.out.println(e.getMessage());
|
||||||
consoleDisplay.append("CRITICAL FAILURE: " + e.getMessage());
|
consoleDisplay.append("CRITICAL FAILURE: " + e.getMessage());
|
||||||
|
JOptionPane.showMessageDialog(this.frame, "CRITICAL FAILURE: " + e.getMessage(), "Fatal Error", JOptionPane.ERROR_MESSAGE);
|
||||||
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||||
AllowExit();
|
AllowExit();
|
||||||
return;
|
return;
|
||||||
@@ -189,6 +190,7 @@ public class Gonzo {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(e.getMessage());
|
System.out.println(e.getMessage());
|
||||||
consoleDisplay.append("CRITICAL FAILURE: " + e.getMessage());
|
consoleDisplay.append("CRITICAL FAILURE: " + e.getMessage());
|
||||||
|
JOptionPane.showMessageDialog(this.frame, "CRITICAL FAILURE: " + e.getMessage(), "Fatal Error", JOptionPane.ERROR_MESSAGE);
|
||||||
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||||
AllowExit();
|
AllowExit();
|
||||||
return;
|
return;
|
||||||
@@ -223,6 +225,7 @@ public class Gonzo {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(e.getMessage());
|
System.out.println(e.getMessage());
|
||||||
consoleDisplay.append("CRITICAL FAILURE: " + e.getMessage());
|
consoleDisplay.append("CRITICAL FAILURE: " + e.getMessage());
|
||||||
|
JOptionPane.showMessageDialog(this.frame, "CRITICAL FAILURE: " + e.getMessage(), "Fatal Error", JOptionPane.ERROR_MESSAGE);
|
||||||
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||||
AllowExit();
|
AllowExit();
|
||||||
return;
|
return;
|
||||||
@@ -259,15 +262,27 @@ public class Gonzo {
|
|||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
System.out.println(e.getMessage());
|
System.out.println(e.getMessage());
|
||||||
consoleDisplay.append("CRITICAL FAILURE: " + e.getMessage());
|
consoleDisplay.append("CRITICAL FAILURE: " + e.getMessage());
|
||||||
|
JOptionPane.showMessageDialog(this.frame, "CRITICAL FAILURE: " + e.getMessage(), "Fatal Error", JOptionPane.ERROR_MESSAGE);
|
||||||
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||||
AllowExit();
|
AllowExit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
new File(Main.outpath).mkdirs();
|
boolean one = new File(Main.outpath).mkdirs();
|
||||||
if (!Main.windows) {new File(System.getProperty("user.home") + "/.firestar/temp/" + oArcTarget).renameTo(new File(Main.outpath + oArcTarget));}
|
boolean two;
|
||||||
else {new File(System.getProperty("user.home") + "\\.firestar\\temp\\" + oArcTarget).renameTo(new File(Main.outpath + oArcTarget));}
|
System.out.println("created export folder: " + one);
|
||||||
|
if (!Main.windows) {two = new File(System.getProperty("user.home") + "/.firestar/temp/" + oArcTarget).renameTo(new File(Main.outpath + oArcTarget));}
|
||||||
|
else {two = new File(System.getProperty("user.home") + "\\.firestar\\temp\\" + oArcTarget).renameTo(new File(Main.outpath + oArcTarget));}
|
||||||
|
System.out.println("moved file to destination: " + two);
|
||||||
|
if (two) {System.out.println("file should be located at " + Main.outpath + oArcTarget);} else {
|
||||||
|
System.out.println("CRITICAL FAILURE: Please check that your output path is correct and that you have write permissions!");
|
||||||
|
consoleDisplay.append("CRITICAL FAILURE: Please check that your output path is correct and that you have write permissions!");
|
||||||
|
JOptionPane.showMessageDialog(this.frame, "CRITICAL FAILURE: Please check that your output path is correct and that you have write permissions!", "Fatal Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||||
|
AllowExit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Process p;
|
Process p;
|
||||||
if (!Main.windows) {p = Runtime.getRuntime().exec(new String[]{"bash","-c","rm -rf " + System.getProperty("user.home") + "/.firestar/temp/"});} // Scary!
|
if (!Main.windows) {p = Runtime.getRuntime().exec(new String[]{"bash","-c","rm -rf " + System.getProperty("user.home") + "/.firestar/temp/"});} // Scary!
|
||||||
@@ -302,6 +317,7 @@ public class Gonzo {
|
|||||||
public void AllowExit() {
|
public void AllowExit() {
|
||||||
System.out.println("\n\nYou may now close the pop-up window.");
|
System.out.println("\n\nYou may now close the pop-up window.");
|
||||||
consoleDisplay.append("\n\n\nYou may now close the pop-up window.");
|
consoleDisplay.append("\n\n\nYou may now close the pop-up window.");
|
||||||
|
try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {} //ignore
|
||||||
scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
|
scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
|
||||||
frame.addWindowListener(new WindowAdapter() {
|
frame.addWindowListener(new WindowAdapter() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public class Kermit implements ActionListener {
|
|||||||
button.setVisible(false);frame.remove(button);
|
button.setVisible(false);frame.remove(button);
|
||||||
button2.setVisible(false);frame.remove(button2);
|
button2.setVisible(false);frame.remove(button2);
|
||||||
dialogText.setVisible(false);frame.remove(dialogText);
|
dialogText.setVisible(false);frame.remove(dialogText);
|
||||||
changePage(Pages.EXPORT_MODE);
|
changePage(Pages.SDK_INSTALL); // EXPORT_MODE
|
||||||
break;
|
break;
|
||||||
case EXPORT_MODE:
|
case EXPORT_MODE:
|
||||||
button.setVisible(false);frame.remove(button);
|
button.setVisible(false);frame.remove(button);
|
||||||
@@ -341,8 +341,8 @@ public class Kermit implements ActionListener {
|
|||||||
dialogText.setVisible(false);frame.remove(dialogText);
|
dialogText.setVisible(false);frame.remove(dialogText);
|
||||||
|
|
||||||
//check if this is windows or not
|
//check if this is windows or not
|
||||||
if(System.getProperty("os.name").contains("Windows")) {Main.windows = false;System.out.println("Assuming we should NOT use WINE based on known system variables.");changePage(Pages.EXPORT_LOCATION);}
|
if(System.getProperty("os.name").contains("Windows")) {Main.windows = true;System.out.println("Assuming we should NOT use WINE based on known system variables.");changePage(Pages.EXPORT_LOCATION);}
|
||||||
else {Main.windows = true;System.out.println("Assuming we should use WINE based on known system variables.");changePage(Pages.EXPORT_LOCATION);}
|
else {Main.windows = false;System.out.println("Assuming we should use WINE based on known system variables.");changePage(Pages.EXPORT_LOCATION);}
|
||||||
|
|
||||||
case EXPORT_LOCATION:
|
case EXPORT_LOCATION:
|
||||||
page = Pages.EXPORT_LOCATION;
|
page = Pages.EXPORT_LOCATION;
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ import org.json.*;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
// Build Information
|
// Build Information
|
||||||
public static final String vstr = "PREALPHA";
|
public static final String vstr = "Beta 1";
|
||||||
public static final String vcode = "Natasha";
|
public static final String vcode = "Natasha";
|
||||||
public static final int vint = 0;
|
public static final int vint = 0;
|
||||||
|
|
||||||
@@ -98,6 +99,32 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void loadConf(){
|
public static void loadConf(){
|
||||||
|
try {
|
||||||
|
JSONObject container = new JSONObject(new String(Files.readAllBytes(Paths.get(System.getProperty("user.home") + "/.firestar/firestar.conf"))));
|
||||||
|
System.out.println(container.toString()); // debug
|
||||||
|
int confvint = (int) container.get("version"); // used for converting configs between program versions later down the line
|
||||||
|
outpath = container.get("2048path").toString();
|
||||||
|
repatch = (boolean) container.get("safemode");
|
||||||
|
windows = (boolean) container.get("isWin32");
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("ERROR: Failed to load firestar.conf");
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadConf(MissPiggy w){
|
||||||
|
try {
|
||||||
|
JSONObject container = new JSONObject(new String(Files.readAllBytes(Paths.get(System.getProperty("user.home") + "/.firestar/firestar.conf"))));
|
||||||
|
System.out.println(container.toString()); // debug
|
||||||
|
int confvint = (int) container.get("version"); // used for converting configs between program versions later down the line
|
||||||
|
outpath = container.get("2048path").toString();
|
||||||
|
repatch = (boolean) container.get("safemode");
|
||||||
|
windows = (boolean) container.get("isWin32");
|
||||||
|
} catch (Exception e) {
|
||||||
|
JOptionPane.showMessageDialog(w.frame, "Firestar couldn't load your config file. Tread lightly.\n\n" + e.getMessage(), "Critical Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
System.out.println("ERROR: Failed to load firestar.conf");
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,21 +19,28 @@
|
|||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import net.lingala.zip4j.*;
|
import net.lingala.zip4j.*;
|
||||||
import net.lingala.zip4j.exception.ZipException;
|
import net.lingala.zip4j.exception.ZipException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import static java.nio.file.StandardCopyOption.*;
|
||||||
|
|
||||||
import static javax.swing.WindowConstants.EXIT_ON_CLOSE;
|
import static javax.swing.WindowConstants.EXIT_ON_CLOSE;
|
||||||
|
|
||||||
@@ -62,13 +69,17 @@ public class MissPiggy implements ActionListener {
|
|||||||
private JButton deployButton;
|
private JButton deployButton;
|
||||||
private JTextPane descriptionField;
|
private JTextPane descriptionField;
|
||||||
|
|
||||||
private int selectedItem;
|
//private int selectedItem;
|
||||||
|
|
||||||
public String priorityList;
|
public String priorityList;
|
||||||
|
|
||||||
|
public boolean listenersAlreadySet = false; // was written to troubleshoot a bug but this wasn't actually the cause
|
||||||
|
|
||||||
// Initialize the main window
|
// Initialize the main window
|
||||||
public void Action(/*Main entryPoint*/) {
|
public void Action(/*Main entryPoint*/) {
|
||||||
System.out.println("Main window created");
|
System.out.println("Main window created");
|
||||||
|
System.out.println("Loading program configuration");
|
||||||
|
Main.loadConf(this);
|
||||||
|
|
||||||
// populate menu bar
|
// populate menu bar
|
||||||
menuBar = new JMenuBar();
|
menuBar = new JMenuBar();
|
||||||
@@ -110,39 +121,15 @@ public class MissPiggy implements ActionListener {
|
|||||||
toolsMenu.getItem(1).addActionListener(this);
|
toolsMenu.getItem(1).addActionListener(this);
|
||||||
helpMenu.getItem(0).addActionListener(this);
|
helpMenu.getItem(0).addActionListener(this);
|
||||||
|
|
||||||
|
deployButton.addActionListener(this);
|
||||||
|
importButton.addActionListener(this);
|
||||||
|
deleteButton1.addActionListener(this);
|
||||||
|
optionsButton.addActionListener(this);
|
||||||
|
moveUpButton.addActionListener(this);
|
||||||
|
moveDownButton.addActionListener(this);
|
||||||
|
toggleButton.addActionListener(this);
|
||||||
|
|
||||||
descriptionField.getDocument().putProperty("filterNewlines", Boolean.FALSE);
|
descriptionField.getDocument().putProperty("filterNewlines", Boolean.FALSE);
|
||||||
modList.addListSelectionListener(e -> {
|
|
||||||
String authorDisplay;
|
|
||||||
File pathReference = new File(System.getProperty("user.home") + "/.firestar/mods/" + Main.Mods.get(modList.getSelectedIndex()).path);
|
|
||||||
DecimalFormat df = new DecimalFormat("##.##");
|
|
||||||
df.setRoundingMode(RoundingMode.UP);
|
|
||||||
float modFileSize = pathReference.length(); //precise units
|
|
||||||
String modFileSizeStr = String.valueOf(modFileSize);
|
|
||||||
String modFileSizeUnits = "bytes"; //todo: don't show decimals for bytes
|
|
||||||
if (pathReference.length() >= 1024) {
|
|
||||||
modFileSizeStr = String.valueOf(df.format(modFileSize / 1024));
|
|
||||||
modFileSizeUnits = "Kilobytes";
|
|
||||||
}
|
|
||||||
if (pathReference.length() >= 1024 * 1024) {
|
|
||||||
modFileSizeStr = String.valueOf(df.format(modFileSize / (1024 * 1024)));
|
|
||||||
modFileSizeUnits = "Megabytes";
|
|
||||||
}
|
|
||||||
if (pathReference.length() >= 1024 * 1024 * 1024) {
|
|
||||||
modFileSizeStr = String.valueOf(df.format(modFileSize / (1024 * 1024 * 1024)));
|
|
||||||
modFileSizeUnits = "Gigabytes";
|
|
||||||
}
|
|
||||||
if (Main.Mods.get(modList.getSelectedIndex()).author == null) {
|
|
||||||
authorDisplay = "an Unknown Author";
|
|
||||||
} else {
|
|
||||||
authorDisplay = Main.Mods.get(modList.getSelectedIndex()).author;
|
|
||||||
}
|
|
||||||
descriptionField.setText(
|
|
||||||
"\"" + Main.Mods.get(modList.getSelectedIndex()).friendlyName + "\"\n" +
|
|
||||||
"by " + authorDisplay + "\n\n" +
|
|
||||||
"Version " + Main.Mods.get(modList.getSelectedIndex()).version + "\n" +
|
|
||||||
modFileSizeStr + " " + modFileSizeUnits + " in size" +
|
|
||||||
"\n\n" + Main.Mods.get(modList.getSelectedIndex()).description
|
|
||||||
);});
|
|
||||||
|
|
||||||
// display window
|
// display window
|
||||||
try {
|
try {
|
||||||
@@ -191,8 +178,11 @@ public class MissPiggy implements ActionListener {
|
|||||||
Do nothing if the index number is not valid.
|
Do nothing if the index number is not valid.
|
||||||
there probably is not a practical reason to do this, but I want to eliminate any undefined behaviors while we're here.
|
there probably is not a practical reason to do this, but I want to eliminate any undefined behaviors while we're here.
|
||||||
we'll also eliminate any syntax errors caused by the lack of a = sign
|
we'll also eliminate any syntax errors caused by the lack of a = sign
|
||||||
|
|
||||||
|
06/29/24 - also skip files that were manually removed but remain in the list
|
||||||
*/
|
*/
|
||||||
if (s.split("=")[0].matches("[0-9]+=*")) {
|
if (s.split("=")[0].matches("[0-9]+=*") &&
|
||||||
|
new File(System.getProperty("user.home") + "/.firestar/mods/" + s.substring(s.indexOf("=") + 1)).exists()) {
|
||||||
//append mod to list from substring
|
//append mod to list from substring
|
||||||
Main.Mod m = new Main().new Mod();
|
Main.Mod m = new Main().new Mod();
|
||||||
m.path = s.substring(s.indexOf("=") + 1);
|
m.path = s.substring(s.indexOf("=") + 1);
|
||||||
@@ -202,24 +192,27 @@ public class MissPiggy implements ActionListener {
|
|||||||
JSONObject metadata;
|
JSONObject metadata;
|
||||||
try {
|
try {
|
||||||
metadata = new JSONObject(new ZipFile(System.getProperty("user.home") + "/.firestar/mods/" + m.path).getComment());
|
metadata = new JSONObject(new ZipFile(System.getProperty("user.home") + "/.firestar/mods/" + m.path).getComment());
|
||||||
} catch (ZipException e) {
|
if (metadata.has("friendlyName")) {m.friendlyName = metadata.get("friendlyName").toString();} else {m.friendlyName = m.path;}
|
||||||
throw new RuntimeException(e); //todo: fault tolerance
|
if (metadata.has("description")) {m.description = metadata.get("description").toString();}
|
||||||
}
|
if (metadata.has("version")) {m.version = Integer.parseInt(metadata.get("version").toString());}
|
||||||
if (metadata.has("friendlyName")) {m.friendlyName = metadata.get("friendlyName").toString();} else {m.friendlyName = m.path;}
|
if (metadata.has("author")) {m.author = metadata.get("author").toString();}
|
||||||
if (metadata.has("description")) {m.description = metadata.get("description").toString();}
|
if (metadata.has("loaderversion")) {m.loaderversion = Integer.parseInt(metadata.get("loaderversion").toString());}
|
||||||
if (metadata.has("version")) {m.version = Integer.parseInt(metadata.get("version").toString());}
|
if (metadata.has("game")) {m.game = metadata.get("game").toString();}
|
||||||
if (metadata.has("author")) {m.author = metadata.get("author").toString();}
|
|
||||||
if (metadata.has("loaderversion")) {m.loaderversion = Integer.parseInt(metadata.get("loaderversion").toString());}
|
|
||||||
if (metadata.has("game")) {m.game = metadata.get("game").toString();}
|
|
||||||
|
|
||||||
//send to list
|
//send to list
|
||||||
Main.Mods.add(m);
|
Main.Mods.add(m);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("WARNING: mod entry for " + s + " was found but does not contain valid JSON metadata. skipping");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!s.isEmpty()) {System.out.println("WARNING: mod entry for " + s + " doesn't actually exist. skipping");}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InitializeModListInGUI() { // i really wanted this to be "lights, camera, action" but the code organizing kept getting stupider and stupider so i gave up
|
public void InitializeModListInGUI() {
|
||||||
// cleanup
|
// cleanup
|
||||||
|
if (listenersAlreadySet) {modList.removeListSelectionListener(modList.getListSelectionListeners()[0]);} // was written to troubleshoot a bug but this wasn't actually the cause
|
||||||
descriptionField.setText("Select a mod from the list on the right to view more details, or to make changes to your installation.");
|
descriptionField.setText("Select a mod from the list on the right to view more details, or to make changes to your installation.");
|
||||||
modList.clearSelection();
|
modList.clearSelection();
|
||||||
modList.removeAll();
|
modList.removeAll();
|
||||||
@@ -241,6 +234,7 @@ public class MissPiggy implements ActionListener {
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
modList.setListData(contents);
|
modList.setListData(contents);
|
||||||
|
createSelectionEventListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListSelectionListener whenItemSelected() {
|
private ListSelectionListener whenItemSelected() {
|
||||||
@@ -251,9 +245,19 @@ public class MissPiggy implements ActionListener {
|
|||||||
public void actionPerformed(ActionEvent actionEvent) {
|
public void actionPerformed(ActionEvent actionEvent) {
|
||||||
if (actionEvent.getSource() == fileMenu.getItem(5)) {System.exit(0);} else
|
if (actionEvent.getSource() == fileMenu.getItem(5)) {System.exit(0);} else
|
||||||
if (actionEvent.getSource() == fileMenu.getItem(0)) {deployModGUI();} else
|
if (actionEvent.getSource() == fileMenu.getItem(0)) {deployModGUI();} else
|
||||||
|
if (actionEvent.getSource() == deployButton) {deployModGUI();} else
|
||||||
|
if (actionEvent.getSource() == importButton) {importModGUI();} else
|
||||||
if (actionEvent.getSource() == fileMenu.getItem(1)) {importModGUI();} else
|
if (actionEvent.getSource() == fileMenu.getItem(1)) {importModGUI();} else
|
||||||
if (actionEvent.getSource() == fileMenu.getItem(2)) {removeAllGUI();} else
|
if (actionEvent.getSource() == fileMenu.getItem(2)) {removeAllGUI();} else
|
||||||
|
if (actionEvent.getSource() == optionsButton) {optionsGUI();} else
|
||||||
if (actionEvent.getSource() == fileMenu.getItem(4)) {optionsGUI();} else
|
if (actionEvent.getSource() == fileMenu.getItem(4)) {optionsGUI();} else
|
||||||
|
|
||||||
|
if (actionEvent.getSource() == moveUpButton) {moveUp(modList.getSelectedIndex());} else
|
||||||
|
if (actionEvent.getSource() == moveDownButton) {moveDown(modList.getSelectedIndex());} else
|
||||||
|
|
||||||
|
if (actionEvent.getSource() == toggleButton) {throwUnimplemented();} else // todo
|
||||||
|
if (actionEvent.getSource() == deleteButton1) {deleteSelected();} else
|
||||||
|
|
||||||
if (actionEvent.getSource() == helpMenu.getItem(0)) {new Rowlf().displayAboutScreen();}
|
if (actionEvent.getSource() == helpMenu.getItem(0)) {new Rowlf().displayAboutScreen();}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,26 +279,186 @@ public class MissPiggy implements ActionListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void importModGUI() {
|
public void importModGUI() {
|
||||||
// todo call system shell to request file picker
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
|
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
|
||||||
|
//fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("All", "zip", "agr", "agrc", "agrf", "fstar"));
|
||||||
|
fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("ZIP Archive", "zip"));
|
||||||
|
fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("Firestar Mod Package", "agr", "agrc", "agrf", "fstar")); //what about fstar?
|
||||||
|
|
||||||
|
int result = fileChooser.showOpenDialog(frame);
|
||||||
|
|
||||||
|
if (result == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File selectedFile = fileChooser.getSelectedFile();
|
||||||
|
System.out.println("Importing selected mod file \"" + selectedFile.getName() + "\"");
|
||||||
|
|
||||||
|
ZipFile zipImporterHandler = new ZipFile(selectedFile.getPath());
|
||||||
|
if (zipImporterHandler.isValidZipFile()) {
|
||||||
|
try {
|
||||||
|
Path importDestination = Paths.get(System.getProperty("user.home") + "/.firestar/mods/"
|
||||||
|
+ selectedFile.getName() + "_" + Main.Mods.size() + ".zip");
|
||||||
|
Files.copy(Paths.get(selectedFile.getPath()), importDestination, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
String importDestinationName = importDestination.toFile().getName();
|
||||||
|
|
||||||
|
BufferedWriter bw = new BufferedWriter(new FileWriter(System.getProperty("user.home") + "/.firestar/mods/index", true));
|
||||||
|
bw.write(Main.Mods.size() + "=" + importDestinationName);
|
||||||
|
bw.newLine();
|
||||||
|
bw.close();
|
||||||
|
|
||||||
|
InitializeModListStructure();
|
||||||
|
InitializeModListInGUI();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
JOptionPane.showMessageDialog(frame, "An error has occured.\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("ERROR: File is not a valid ZIP archive. Aborting.");
|
||||||
|
JOptionPane.showMessageDialog(frame, "Whoops, that's not a valid ZIP archive.", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeAllGUI() {
|
public void removeAllGUI() {
|
||||||
// todo warning dialog that nukes list when Yes is clicked
|
// todo warning dialog that nukes list when Yes is clicked
|
||||||
|
int result = JOptionPane.showConfirmDialog(frame, "Do you really want to delete all mods?", "Remove All", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||||
|
|
||||||
|
if (result == JOptionPane.YES_OPTION) {
|
||||||
|
for (Main.Mod entry : Main.Mods) {
|
||||||
|
new File(System.getProperty("user.home") + "/.firestar/mods/" + entry.path).delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new File(System.getProperty("user.home") + "/.firestar/mods/index").delete();
|
||||||
|
Main.Mods.clear();
|
||||||
|
|
||||||
|
InitializeModListStructure();
|
||||||
|
InitializeModListInGUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void optionsGUI() {
|
public void optionsGUI() {
|
||||||
// todo settings page w/ reset switch
|
// todo settings page w/ reset switch
|
||||||
|
throwUnimplemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteSelected() {
|
||||||
|
File file = new File(System.getProperty("user.home") + "/.firestar/mods/" + Main.Mods.get(modList.getSelectedIndex()).path);
|
||||||
|
file.delete();
|
||||||
|
System.out.println("Deleted " + Main.Mods.get(modList.getSelectedIndex()).friendlyName); //debug
|
||||||
|
Main.Mods.remove(modList.getSelectedIndex());
|
||||||
|
regenerateModIndex(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generatorGUI() {
|
public void generatorGUI() {
|
||||||
// todo mod packer
|
// todo mod packer
|
||||||
|
throwUnimplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void metaEditorGUI() {
|
public void metaEditorGUI() {
|
||||||
// todo tag editor
|
// todo tag editor
|
||||||
|
throwUnimplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void aboutGUI() {
|
private void moveUp(int index) {
|
||||||
// todo about page
|
if (index > 0) {
|
||||||
|
Collections.swap(Main.Mods, index, index - 1);
|
||||||
|
System.out.println("Items moved, redeploying list");
|
||||||
|
InitializeModListInGUI();
|
||||||
|
regenerateModIndex(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveDown(int index) {
|
||||||
|
if (index < (Main.Mods.size() - 1)) {
|
||||||
|
Collections.swap(Main.Mods, index, index + 1);
|
||||||
|
System.out.println("Items moved, redeploying list");
|
||||||
|
InitializeModListInGUI();
|
||||||
|
regenerateModIndex(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void throwUnimplemented() {
|
||||||
|
JOptionPane.showMessageDialog(frame, "Unimplemented.\nSee README at https://git.worlio.com/bonkmaykr/firestar", "Unimplemented", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createSelectionEventListener() { // moved incase needs to be removed and re-added
|
||||||
|
listenersAlreadySet = true; // was written to troubleshoot a bug but this wasn't actually the cause
|
||||||
|
modList.addListSelectionListener(e -> {
|
||||||
|
if (modList.getSelectedIndex() >= 0 && modList.getModel().getSize() >= 1) { // avoid race OOB when reinitializing mod list
|
||||||
|
String authorDisplay;
|
||||||
|
|
||||||
|
try { //debug
|
||||||
|
|
||||||
|
File pathReference = new File(System.getProperty("user.home") + "/.firestar/mods/" + Main.Mods.get(modList.getSelectedIndex()).path);
|
||||||
|
DecimalFormat df = new DecimalFormat("##.##");
|
||||||
|
df.setRoundingMode(RoundingMode.UP);
|
||||||
|
float modFileSize = pathReference.length(); //precise units
|
||||||
|
String modFileSizeStr = String.valueOf(modFileSize);
|
||||||
|
String modFileSizeUnits = "bytes"; //todo: don't show decimals for bytes
|
||||||
|
if (pathReference.length() >= 1024) {
|
||||||
|
modFileSizeStr = String.valueOf(df.format(modFileSize / 1024));
|
||||||
|
modFileSizeUnits = "Kilobytes";
|
||||||
|
}
|
||||||
|
if (pathReference.length() >= 1024 * 1024) {
|
||||||
|
modFileSizeStr = String.valueOf(df.format(modFileSize / (1024 * 1024)));
|
||||||
|
modFileSizeUnits = "Megabytes";
|
||||||
|
}
|
||||||
|
if (pathReference.length() >= 1024 * 1024 * 1024) {
|
||||||
|
modFileSizeStr = String.valueOf(df.format(modFileSize / (1024 * 1024 * 1024)));
|
||||||
|
modFileSizeUnits = "Gigabytes";
|
||||||
|
}
|
||||||
|
if (Main.Mods.get(modList.getSelectedIndex()).author == null) {
|
||||||
|
authorDisplay = "an Unknown Author";
|
||||||
|
} else {
|
||||||
|
authorDisplay = Main.Mods.get(modList.getSelectedIndex()).author;
|
||||||
|
}
|
||||||
|
descriptionField.setText(
|
||||||
|
"\"" + Main.Mods.get(modList.getSelectedIndex()).friendlyName + "\"\n" +
|
||||||
|
"by " + authorDisplay + "\n\n" +
|
||||||
|
"Version " + Main.Mods.get(modList.getSelectedIndex()).version + "\n" +
|
||||||
|
modFileSizeStr + " " + modFileSizeUnits + " in size" +
|
||||||
|
"\n\n" + Main.Mods.get(modList.getSelectedIndex()).description
|
||||||
|
);}
|
||||||
|
|
||||||
|
catch (IndexOutOfBoundsException ex) {
|
||||||
|
System.out.println(ex.getMessage());
|
||||||
|
System.out.println("mods " + Main.Mods.size());
|
||||||
|
System.out.println("mod display " + modList.getModel().getSize());
|
||||||
|
System.out.println("selection index " + modList.getSelectedIndex());
|
||||||
|
|
||||||
|
int result = JOptionPane.showConfirmDialog(frame, "Firestar encountered an internal error.\n" + ex.getMessage(), "Fatal Error", JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
|
||||||
|
if (result == JOptionPane.OK_OPTION) {System.exit(1);} //user safety
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void regenerateModIndex(boolean reload) {
|
||||||
|
try {
|
||||||
|
System.out.println("Regenerating index..."); //debug
|
||||||
|
|
||||||
|
new File(System.getProperty("user.home") + "/.firestar/mods/index").delete();
|
||||||
|
File priorityListFileHandle = new File(System.getProperty("user.home") + "/.firestar/mods/index");
|
||||||
|
priorityListFileHandle.createNewFile();
|
||||||
|
|
||||||
|
|
||||||
|
BufferedWriter bw = new BufferedWriter(new FileWriter(System.getProperty("user.home") + "/.firestar/mods/index", true));
|
||||||
|
int i = 0;
|
||||||
|
for (Main.Mod m : Main.Mods) {
|
||||||
|
bw.write(i + "=" + m.path);
|
||||||
|
bw.newLine();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
bw.close();
|
||||||
|
|
||||||
|
if(reload) {
|
||||||
|
Main.Mods.clear(); //cleanup
|
||||||
|
priorityList = "";
|
||||||
|
InitializeModListStructure();
|
||||||
|
InitializeModListInGUI();
|
||||||
|
}
|
||||||
|
System.out.println("Mod index file regenerated.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
JOptionPane.showMessageDialog(frame, "An error has occured.\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,12 +47,13 @@ public class Rowlf {
|
|||||||
picLabel.setIcon(new ImageIcon(logo));picLabel.setText("");
|
picLabel.setIcon(new ImageIcon(logo));picLabel.setText("");
|
||||||
|
|
||||||
informationText.getDocument().putProperty("filterNewlines", Boolean.FALSE);
|
informationText.getDocument().putProperty("filterNewlines", Boolean.FALSE);
|
||||||
informationText.setText("Created by bonkmaykr\n" +
|
informationText.setHorizontalAlignment(JTextField.CENTER);
|
||||||
"a.k.a. \"Downforce Agent\"\n" +
|
informationText.setText("Created by bonkmaykr, a.k.a. \"Downforce Agent\"\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"Special thanks to:\n" +
|
"Special thanks to:\n" +
|
||||||
"ThatOneBonk, for almost beating me to it\n" +
|
"ThatOneBonk, for Thallium and for modding help\n" +
|
||||||
"Wirlaburla, for web hosting and code help\n" +
|
"Wirlaburla, for web hosting and being awesome\n" +
|
||||||
|
"Psygnosis, for making our favorite game ever\n" +
|
||||||
"and to all the PSVita hackers who made this possible");
|
"and to all the PSVita hackers who made this possible");
|
||||||
informationText.setHighlighter(null);
|
informationText.setHighlighter(null);
|
||||||
informationText.getCaret().setVisible(false);
|
informationText.getCaret().setVisible(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user