15 Commits

Author SHA1 Message Date
fcb71d4600 fault tolerance 2024-06-29 12:10:01 -05:00
8ebec68caf Doy! 2024-06-29 12:04:21 -05:00
002d995c05 okay i have no fucking idea what is wrong 2024-06-29 11:56:35 -05:00
9780c3cc4f refactoring. regen routine to be reused 2024-06-29 09:46:02 -05:00
8107aaa5d5 shit 2024-06-29 05:56:46 -05:00
6d772f0949 delete selection implemented. fixed OOB bug 2024-06-29 05:54:04 -05:00
d8902af148 going to throw this out because nuking index is easier 2024-06-29 05:29:38 -05:00
2e1fe95670 implement Remove All 2024-06-29 04:47:55 -05:00
45fbe80f9f remove mention of fast mode 2024-06-29 04:47:47 -05:00
886ef3089d stuff 2024-06-29 01:09:38 -05:00
28d710a815 update version string 2024-06-29 01:09:38 -05:00
a1f41584a4 Update README.md 2024-06-29 01:58:10 -04:00
e2229afe70 Update README.md 2024-06-29 01:57:43 -04:00
b9743ee456 less redundancy 2024-06-29 00:55:16 -05:00
fcda7b8cf4 added importing 2024-06-29 00:54:00 -05:00
5 changed files with 208 additions and 61 deletions

View File

@@ -2,14 +2,16 @@
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 75% complete (config I/O in progress, basic systems functional) # about 85% complete
**What works:** **What works:**
- Repacking mods - Importing & deleting mods
- Repacking (installing) mods
- config loading
- Cross-platform support - Cross-platform support
**What doesn't work yet:** **What doesn't work yet:**
- config loading - change load order, toggle
- managing mods within the program - settings screen
- misc. tools (pack creator, soundtrack mod generator...) - misc. tools (pack creator, soundtrack mod generator...)
![](https://files.worlio.com/users/bonkmaykr/http/git/embed/firestar_window.png) ![](https://files.worlio.com/users/bonkmaykr/http/git/embed/firestar_window.png)

View File

@@ -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);

View File

@@ -25,7 +25,7 @@ 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;

View File

@@ -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,10 +69,12 @@ 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");
@@ -111,41 +120,16 @@ public class MissPiggy implements ActionListener {
toolsMenu.getItem(0).addActionListener(this); toolsMenu.getItem(0).addActionListener(this);
toolsMenu.getItem(1).addActionListener(this); toolsMenu.getItem(1).addActionListener(this);
helpMenu.getItem(0).addActionListener(this); helpMenu.getItem(0).addActionListener(this);
deployButton.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 {
@@ -194,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);
@@ -205,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();
@@ -244,6 +234,7 @@ public class MissPiggy implements ActionListener {
i++; i++;
} }
modList.setListData(contents); modList.setListData(contents);
createSelectionEventListener();
} }
private ListSelectionListener whenItemSelected() { private ListSelectionListener whenItemSelected() {
@@ -255,9 +246,18 @@ public class MissPiggy implements ActionListener {
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() == 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();}
} }
@@ -279,9 +279,12 @@ public class MissPiggy implements ActionListener {
} }
public void importModGUI() { public void importModGUI() {
// todo call system shell to request file picker
JFileChooser fileChooser = new JFileChooser(); JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home"))); 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); int result = fileChooser.showOpenDialog(frame);
if (result == JFileChooser.APPROVE_OPTION) { if (result == JFileChooser.APPROVE_OPTION) {
@@ -290,31 +293,172 @@ public class MissPiggy implements ActionListener {
ZipFile zipImporterHandler = new ZipFile(selectedFile.getPath()); ZipFile zipImporterHandler = new ZipFile(selectedFile.getPath());
if (zipImporterHandler.isValidZipFile()) { 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 { } else {
System.out.println("ERROR: File is not a valid ZIP archive. Aborting."); System.out.println("ERROR: File is not a valid ZIP archive. Aborting.");
JOptionPane.showMessageDialog(frame, "Whoops, that's not a valid ZIP archive.\nMod files should be in .zip, .agr, .agrc or .agrf.", "Error", JOptionPane.ERROR_MESSAGE); 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);
}
} }
} }

View File

@@ -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);