59 Commits

Author SHA1 Message Date
099278cfd2 changed version string 2024-07-01 14:53:13 -05:00
c372ff315e style changes 2024-07-01 14:53:13 -05:00
5f0de49399 update oobe, remove unimplemented back button 2024-07-01 14:53:13 -05:00
96aa9ca54e Update README.md 2024-07-01 05:36:42 -04:00
420308ad42 Update README.md 2024-07-01 05:35:36 -04:00
b38707afe5 Update README.md 2024-06-30 19:41:35 -04:00
e52a6f8b79 Updated version string 2024-06-30 18:38:05 -05:00
3666485dd7 Implemented mod generator UI 2024-06-30 18:36:37 -05:00
13878c3c74 Don't remember blacklisted files after deleting them 2024-06-30 17:56:30 -05:00
213b8227e1 Implemented meta editor 2024-06-30 17:52:25 -05:00
bfb37d74d8 Prevent incompatible mods from being loaded when features are added in the future 2024-06-30 17:16:28 -05:00
0a9d8d1bfd Output major version int to console for mod developers 2024-06-30 15:26:35 -05:00
c29448a521 Add link to documentation in Help 2024-06-30 15:23:46 -05:00
81350dd06b Implement settings menu 2024-06-30 09:51:03 -05:00
d839687040 remove unused file extensions 2024-06-30 09:11:02 -05:00
be3c42d7b4 prevent importing bogus files 2024-06-30 09:09:52 -05:00
59b070214c ignore run scripts 2024-06-30 08:59:01 -05:00
2c0e05865f Update README.md 2024-06-30 09:36:19 -04:00
3f286b5c91 update version string 2024-06-30 07:16:33 -05:00
2735c311ad more reliable Remove All 2024-06-30 07:15:48 -05:00
a9f0fc3f4c better handling of files post-deploy 2024-06-30 07:12:27 -05:00
f8146fd493 move .trim() for less redundancy 2024-06-30 06:58:15 -05:00
d57f183785 windows compatibility fixes v3 2024-06-30 06:55:29 -05:00
9a267e4f8d backslashes are fine 2024-06-30 06:49:49 -05:00
f6b8eeb29e fix ghost files on windows 2024-06-30 06:49:00 -05:00
679b4bc68e exclude build files 2024-06-30 03:07:08 -05:00
b7d6ddd701 fix initial setup soft lock 2024-06-29 17:10:26 -05:00
29dec51c2e readme 2024-06-29 14:24:20 -05:00
d79b360553 implement toggles 2024-06-29 14:23:21 -05:00
bf0421552c un-shit (menu options) 2024-06-29 13:41:56 -05:00
c7f2a84c3a shit 2024-06-29 13:34:30 -05:00
addd23b760 confirm dialogs, fix index wipe bug 2024-06-29 13:30:01 -05:00
8cc7fca35c fault tolerance for delete 2024-06-29 13:20:23 -05:00
6078cb3bec i'm retarded too apparently. we all are. it's so over 2024-06-29 13:02:01 -05:00
a7d261597e Okay so jpackage is insanely retarded and intellij is too so we're just not going to touch that with a 100 ft pole 2024-06-29 12:54:23 -05:00
5d683bb893 going to push out a release 2024-06-29 12:14:26 -05:00
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
433bc685dc DUH - don't err when asset folder already exists, it should! 2024-06-28 16:11:38 -05:00
5ab96366a6 FAQ was outdated 2024-06-28 16:52:05 -04:00
e4497afaa3 Update README.md 2024-06-28 16:50:52 -04:00
5a004b6744 to avoid more dumdums 2024-05-08 14:15:00 -05:00
7f5b0b85ae i forgot my own linux username :/ 2024-05-08 14:10:00 -05:00
f582cdb052 Update readme 2024-05-08 07:26:50 -05:00
72b075bf7b cleanup residual file ahead of time 2024-05-08 07:21:53 -05:00
020ec4b5d9 Windows compatibility 2024-05-08 07:17:26 -05:00
14 changed files with 1097 additions and 162 deletions

5
.gitignore vendored
View File

@@ -26,4 +26,7 @@ bin/
.vscode/
### Mac OS ###
.DS_Store
.DS_Store
/firestar.zip
/run-linux.sh
/run-win32.bat

19
.idea/artifacts/firestar.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<component name="ArtifactManager">
<artifact type="jpackage" name="firestar">
<output-path>$PROJECT_DIR$/out/artifacts/firestar</output-path>
<properties id="jpackage-properties">
<options>
<option name="version" value="natasha" />
<option name="copyright" value="(C) 2024 bonkmaykr" />
<option name="description" value="" />
<option name="vendor" value="Anti-Gravity Racing Federation" />
<option name="mainClass" value="Main" />
</options>
</properties>
<root id="root">
<element id="archive" name="firestar.jar">
<element id="module-output" name="firestar" />
</element>
</root>
</artifact>
</component>

View File

@@ -1,37 +1,26 @@
![](https://files.worlio.com/users/bonkmaykr/http/git/embed/firestar.png)
# 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.
## Video Tutorial: [>> CLICK HERE <<](https://www.youtube.com/watch?v=Bi9fpqR2Ulw)
![](https://files.worlio.com/users/bonkmaykr/http/git/embed/firestar_window.png)
# Installation
- **Windows & Mac:** Grab the Portable ZIP from the [Releases](https://git.worlio.com/bonkmaykr/firestar/releases) page and run the JAR file.
- **Linux:** Install one of the packages from the [Releases](https://git.worlio.com/bonkmaykr/firestar/releases) page:
- Ubuntu, Mint, PopOS: `sudo dpkg -i firestar_ver_arch.deb` or `gdebi firestar_ver_arch.deb`
- Fedora, Mageia, OpenSUSE, AlmaLinux: `sudo rpm -i firestar-ver-arch.rpm`
- Arch, EndeavourOS, CachyOS, Steam Deck: `sudo pacman -U firestar-ver-arch.pkg.tar.zst`
# System Requirements
- You will need a computer running Microsoft Windows, or any POSIX-compliant desktop operating system such as Mac OS X, Linux, BSD, or MINIX (Firestar does not run directly on the PSVita). ***Wayland support has not been tested.***
- You will also need the Java Runtime Environment installed, at least version 17 or newer. Non-Windows systems will require WINE to be installed for some functionality.
- Please run Firestar on an EXT or NTFS partition as some of the larger file operations it performs may cause glitches in certain filesystems used on memory cards. Do NOT install Firestar directly to your PSVita's storage.
# Mod File Structure
Mod files are contained inside of ZIP archives with the metadata being stored inside of the archive's embedded comments. The "data" folder inside contains the actual game assets for repacking.
Next to it at root level a pack.png will be supplied for displaying your mod's icon in later versions of Firestar. This feature is currently unimplemented.
Later versions of Firestar will have a built-in tool to handle these for you, since some archive programs don't make it easy enough to do by hand.
Typical mod metadata will look something like this:
```json
{
"version": 1,
"friendlyName": "Custom soundtrack",
"loaderversion": 0,
"author": "bonkmaykr",
"description": "I need sugar"
}
```
# 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?
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 (also like super proprietary... sorry). 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?
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 +29,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.
## 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.

BIN
resources/exo2.ttf Normal file

Binary file not shown.

93
src/Clifford.form Normal file
View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="Clifford">
<grid id="27dc6" binding="frameContainer" layout-manager="GridLayoutManager" row-count="5" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="3294c" class="javax.swing.JLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Name"/>
</properties>
</component>
<component id="2e9e" class="javax.swing.JLabel">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Author"/>
</properties>
</component>
<component id="e22ba" class="javax.swing.JTextField" binding="fName">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="9a381" class="javax.swing.JTextField" binding="fAuthor">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="c443e" class="javax.swing.JLabel">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Version"/>
</properties>
</component>
<component id="d7be8" class="javax.swing.JTextField" binding="fVersion">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="e6d9e" class="javax.swing.JLabel">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Description"/>
</properties>
</component>
<component id="4c6b" class="javax.swing.JTextPane" binding="fDescription">
<constraints>
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false">
<preferred-size width="150" height="50"/>
</grid>
</constraints>
<properties/>
</component>
<component id="9d8aa" class="javax.swing.JButton" binding="savebtn">
<constraints>
<grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Save"/>
</properties>
</component>
<component id="8ded9" class="javax.swing.JButton" binding="cancelbtn">
<constraints>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Cancel"/>
</properties>
</component>
</children>
</grid>
</form>

194
src/Clifford.java Normal file
View File

@@ -0,0 +1,194 @@
/*
* Firestar Mod Manager
* Copyright (C) 2024 bonkmaykr
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import org.json.JSONObject;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import static javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE;
public class Clifford implements ActionListener {
private JFrame frame = new JFrame();
private JPanel frameContainer;
private JTextField fName;
private JTextField fAuthor;
private JTextField fVersion;
private JTextPane fDescription;
private JButton savebtn;
private JButton cancelbtn;
MissPiggy invoker;
Main.Mod mod;
int index;
File directory;
boolean creating;
public void Action(MissPiggy inv, int modindex) { // Editor
invoker = inv;
mod = Main.Mods.get(modindex);
index = modindex;
creating = false;
frame.add(frameContainer);
frame.setSize(600, 200); // 1280 800
frame.setMinimumSize(new Dimension(200,100));
frame.setTitle("Options");
frame.setResizable(false);
frame.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
frame.setLayout(new GridLayout());
frame.setLocationRelativeTo(null);
frame.setAlwaysOnTop(true);
fName.setText(mod.friendlyName);
fAuthor.setText(mod.author);
fVersion.setText(String.valueOf(mod.version));
fDescription.setText(mod.description);
cancelbtn.addActionListener(this);
savebtn.addActionListener(this);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e)
{
invoker.frame.setEnabled(true);
e.getWindow().dispose();
}
});
}
public void Action(MissPiggy inv, File dir) { // Generator
invoker = inv;
directory = dir;
creating = true;
frame.add(frameContainer);
frame.setSize(600, 200); // 1280 800
frame.setMinimumSize(new Dimension(200,100));
frame.setTitle("Options");
frame.setResizable(false);
frame.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
frame.setLayout(new GridLayout());
frame.setLocationRelativeTo(null);
frame.setAlwaysOnTop(true);
cancelbtn.addActionListener(this);
savebtn.addActionListener(this);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e)
{
invoker.frame.setEnabled(true);
e.getWindow().dispose();
}
});
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (actionEvent.getSource() == cancelbtn) {
invoker.frame.setEnabled(true);
frame.dispose();
} else if (actionEvent.getSource() == savebtn && !creating) {
try {
mod.version = Integer.parseInt(fVersion.getText());
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(frame, "Mod version must be a valid integer.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
mod.friendlyName = fName.getText();
mod.author = fAuthor.getText();
mod.description = fDescription.getText();
JSONObject container = new JSONObject();
container.put("version", mod.version);
container.put("friendlyName", mod.friendlyName);
container.put("author", mod.author);
container.put("description", mod.description);
container.put("loaderversion", mod.loaderversion);
container.put("game", mod.game);
try {
new ZipFile(System.getProperty("user.home") + "/.firestar/mods/" + mod.path.trim()).setComment(container.toString());
} catch (ZipException e) {
System.out.println(e.getMessage());
JOptionPane.showMessageDialog(frame, "An error has occured.\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
Main.Mods.set(index, mod);
invoker.frame.setEnabled(true);
invoker.InitializeModListInGUI();
frame.dispose();
} else if (actionEvent.getSource() == savebtn && creating) {
try {
Integer.parseInt(fVersion.getText());
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(frame, "Mod version must be a valid integer.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileFilter(new FileNameExtensionFilter("Firestar Mod Package", "fstar"));
if (fileChooser.showSaveDialog(frame) == JFileChooser.APPROVE_OPTION) {
ZipFile zip = new ZipFile(fileChooser.getSelectedFile());
try {
zip.addFolder(new File(directory.getAbsolutePath() + "/data"));
if (new File(directory.getAbsolutePath() + "/delete.txt").exists()) {
zip.addFile(new File(directory.getAbsolutePath() + "/delete.txt"));
}
if (new File(directory.getAbsolutePath() + "/pack.png").exists()) {
zip.addFile(new File(directory.getAbsolutePath() + "/pack.png"));
}
JSONObject container = new JSONObject();
container.put("version", Integer.parseInt(fVersion.getText()));
container.put("friendlyName", fName.getText());
container.put("author", fAuthor.getText());
container.put("description", fDescription.getText());
container.put("loaderversion", 0); // TODO for later versions: Change depending on features inside of mod folder
container.put("game", "2048");
zip.setComment(container.toString());
zip.close();
} catch (Exception e) {
fileChooser.getSelectedFile().delete(); //cleanup
System.out.println(e.getMessage());
JOptionPane.showMessageDialog(frame, "An error has occured.\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
return;
}
JOptionPane.showMessageDialog(frame, "Mod file created", "Success", JOptionPane.INFORMATION_MESSAGE);
invoker.frame.setEnabled(true);
frame.dispose();
}
}
}
}

View File

@@ -87,7 +87,7 @@ public class Gonzo {
final Thread managerThread = new Thread() {
@Override
public void run() {
if (!Main.repatch) {
if (/*Main.repatch*/true) { //todo implement fast mode correctly or remove
CompatibilityRoutine();
} else {
FastRoutine();
@@ -116,8 +116,8 @@ public class Gonzo {
consoleDisplay.append("Firestar is extracting " + s + "\n");
//Process p = Runtime.getRuntime().exec(new String[]{"bash","-c","aplay /home/bonkyboo/kittens_loop.wav"}); // DEBUG
Process p;
if (!Main.wine) {p = Runtime.getRuntime().exec(new String[]{"bash","-c","cd " + System.getProperty("user.home") + "/.firestar/temp/" + ";wine ../psp2psarc.exe extract -y ../" + s});}
else {p = Runtime.getRuntime().exec(new String[]{"cd " + System.getProperty("user.home") + "/.firestar/temp/" + " && ../psp2psarc.exe extract -y ../" + s});}
if (!Main.windows) {p = Runtime.getRuntime().exec(new String[]{"bash","-c","cd " + System.getProperty("user.home") + "/.firestar/temp/" + ";wine ../psp2psarc.exe extract -y ../" + s});}
else {p = Runtime.getRuntime().exec(new String[]{new String(System.getProperty("user.home") + "\\.firestar\\psp2psarc.exe"), "extract", "-y", "..\\" + s}, null, new File(new String(System.getProperty("user.home") + "/.firestar/temp/").replace("/", "\\")));}
final Thread ioThread = new Thread() {
@Override
public void run() {
@@ -129,7 +129,7 @@ public class Gonzo {
System.out.println(line);
consoleDisplay.append(line + "\n");
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();
} catch (final Exception e) {
@@ -141,45 +141,62 @@ public class Gonzo {
p.waitFor();
} catch (IOException | InterruptedException e) {
System.out.println(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);
AllowExit();
break;
return;
}
}
// overwrite assets with custom ones from each mod and/or perform operations as specified in mod's delete list
// todo: implement RegEx functions after delete.txt
for (Main.Mod m : Main.Mods) {
try {
System.out.println("Firestar is extracting " + m.friendlyName + " by " + m.author);
consoleDisplay.append("Firestar is extracting " + m.friendlyName + " by " + m.author + "\n");
new ZipFile(System.getProperty("user.home") + "/.firestar/mods/" + m.path).extractAll(System.getProperty("user.home") + "/.firestar/temp/");
if (m.enabled) {
try {
System.out.println("Firestar is extracting " + m.friendlyName + " by " + m.author);
consoleDisplay.append("Firestar is extracting " + m.friendlyName + " by " + m.author + "\n");
new ZipFile(System.getProperty("user.home") + "/.firestar/mods/" + m.path).extractAll(System.getProperty("user.home") + "/.firestar/temp/");
if (new File(System.getProperty("user.home") + "/.firestar/temp/delete.txt").isFile()) {
System.out.println("Firestar is deleting files that conflict with " + m.friendlyName + " by " + m.author);
consoleDisplay.append("Firestar is deleting files that conflict with " + m.friendlyName + " by " + m.author + "\n");
if (new File(System.getProperty("user.home") + "/.firestar/temp/delete.txt").isFile()) {
System.out.println("Firestar is deleting files that conflict with " + m.friendlyName + " by " + m.author);
consoleDisplay.append("Firestar is deleting files that conflict with " + m.friendlyName + " by " + m.author + "\n");
String deleteQueue = new String(Files.readAllBytes(Paths.get(System.getProperty("user.home") + "/.firestar/temp/delete.txt")));
String[] dQarray = deleteQueue.split("\n");
Arrays.sort(dQarray);
System.out.println("The deletion queue is " + dQarray.length + " files long!"); //debug
String deleteQueue = new String(Files.readAllBytes(Paths.get(System.getProperty("user.home") + "/.firestar/temp/delete.txt")));
if (Main.windows) {deleteQueue = new String(Files.readAllBytes(Paths.get(System.getProperty("user.home") + "\\.firestar\\temp\\delete.txt")));} // might be unnecessary
String[] dQarray = deleteQueue.split("\n");
Arrays.sort(dQarray);
System.out.println("The deletion queue is " + dQarray.length + " files long!"); //debug
for (String file : dQarray) {
if(file.contains("..")) { //todo: find all possible hazardous paths and blacklist them with regex
System.out.println("WARNING: Firestar skipped a potentially dangerous delete command. Please ensure the mod you're installing is from someone you trust!");
consoleDisplay.append("WARNING: Firestar skipped a potentially dangerous delete command. Please ensure the mod you're installing is from someone you trust!\n");
} else {
System.out.println("Deleting " + System.getProperty("user.home") + "/.firestar/temp/data/" + file);
consoleDisplay.append("Deleting " + System.getProperty("user.home") + "/.firestar/temp/data/" + file + "\n");
new File(System.getProperty("user.home") + "/.firestar/temp/data/" + file).delete();
for (String file : dQarray) {
if(file.contains("..")) { //todo: find all possible hazardous paths and blacklist them with regex
System.out.println("WARNING: Firestar skipped a potentially dangerous delete command. Please ensure the mod you're installing is from someone you trust!");
consoleDisplay.append("WARNING: Firestar skipped a potentially dangerous delete command. Please ensure the mod you're installing is from someone you trust!\n");
} else {
if (!Main.windows) {
System.out.println("Deleting " + System.getProperty("user.home") + "/.firestar/temp/data/" + file);
consoleDisplay.append("Deleting " + System.getProperty("user.home") + "/.firestar/temp/data/" + file + "\n");
new File(System.getProperty("user.home") + "/.firestar/temp/data" + file).delete();}
else {
System.out.println("Deleting " + new String(System.getProperty("user.home") + "\\.firestar\\temp\\data" + file).replace("/", "\\"));
consoleDisplay.append("Deleting " + new String(System.getProperty("user.home") + "\\.firestar\\temp\\data" + file).replace("/", "\\") + "\n");
new File(new String(System.getProperty("user.home") + "\\.firestar\\temp\\data" + file).replace("/", "\\")).delete();
}
}
}
// cleanup so we don't run it again for the next mod unless needed
// this is not necessary but good practice
new File(System.getProperty("user.home") + "/.firestar/temp/delete.txt").delete();
}
} catch (Exception e) {
System.out.println(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);
AllowExit();
return;
}
} catch (IOException e) {
System.out.println(e.getMessage());
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
AllowExit();
break;
}
}
@@ -192,7 +209,8 @@ public class Gonzo {
// We need to clean up the path here on Linux to avoid psp2psarc getting confused about where the hell "/" is.
// In WINE it should see it as Z: by default, but if it's somewhere else then I don't have an elegant way of knowing what drive letter it's on, so
// relative paths are kind of the only choice here. This can be extended to Windows too as it works there, though completely unnecessary.
oFilesList2.add(p.replace("\\", "/").split(System.getProperty("user.home") + "/.firestar/temp/")[1]);
if (!Main.windows) {oFilesList2.add(p.replace("\\", "/").split(new String(System.getProperty("user.home") + "/.firestar/temp/"))[1]);}
else {oFilesList2.add(p.split(new String(System.getProperty("user.home") + "\\.firestar\\temp\\").replace("\\", "\\\\"))[1]);} // path wont match regex unless adjusted for windows here
}
//oFilesList2.forEach(System.out::println); //debug
File oFilesListO = new File(System.getProperty("user.home") + "/.firestar/temp/list.txt");
@@ -207,10 +225,13 @@ public class Gonzo {
i++;
}
oFilesListWr.close();
} catch (IOException e) {
} catch (Exception e) {
System.out.println(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);
AllowExit();
return;
}
// invoke psp2psarc.exe one final time to reconstruct the assets
@@ -218,8 +239,8 @@ public class Gonzo {
System.out.println("Firestar is compiling the final build");
consoleDisplay.append("Firestar is compiling the final build" + "\n");
Process p;
if (!Main.wine) {p = Runtime.getRuntime().exec(new String[]{"bash","-c","cd " + System.getProperty("user.home") + "/.firestar/temp" + ";wine ../psp2psarc.exe create --skip-missing-files -j12 -a -i --input-file=list.txt -o " + oArcTarget});}
else {p = Runtime.getRuntime().exec(new String[]{"cd " + System.getProperty("user.home") + "/.firestar/temp" + " && ../psp2psarc.exe create --skip-missing-files -j12 -a -i --input-file=list.txt -o " + oArcTarget});}
if (!Main.windows) {p = Runtime.getRuntime().exec(new String[]{"bash","-c","cd " + System.getProperty("user.home") + "/.firestar/temp" + ";wine ../psp2psarc.exe create --skip-missing-files -j12 -a -i --input-file=list.txt -o " + oArcTarget});}
else {p = Runtime.getRuntime().exec(new String[]{new String(System.getProperty("user.home") + "\\.firestar\\psp2psarc.exe"), "create", "--skip-missing-files", "-j12", "-a", "-i", "--input-file=list.txt", "-o" + oArcTarget}, null, new File(new String(System.getProperty("user.home") + "/.firestar/temp/").replace("/", "\\")));}
final Thread ioThread = new Thread() {
@Override
public void run() {
@@ -243,19 +264,33 @@ public class Gonzo {
p.waitFor();
} catch (IOException | InterruptedException e) {
System.out.println(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);
AllowExit();
return;
}
// cleanup
new File(Main.outpath).mkdirs();
new File(System.getProperty("user.home") + "/.firestar/temp/" + oArcTarget).renameTo(new File(Main.outpath + oArcTarget));
boolean one = new File(Main.outpath).mkdirs();
boolean two;
System.out.println("created export folder: " + one);
if (new File(Main.outpath + oArcTarget).exists()) {System.out.println("deleting existing file: " + Main.outpath + oArcTarget); new File(Main.outpath + oArcTarget).delete();} //hackjob
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 {
Process p;
if (!Main.wine) {p = Runtime.getRuntime().exec(new String[]{"bash","-c","rm -rf " + System.getProperty("user.home") + "/.firestar/temp/"});} // Scary!
else {p = Runtime.getRuntime().exec(new String[]{"rmdir " + System.getProperty("user.home") + "\\.firestar\\temp\\ /s /q"});}
//new File(System.getProperty("user.home") + "/.firestar/temp/").delete();
} catch (IOException e) {
File tmp = new File(System.getProperty("user.home") + "/.firestar/temp/");
Main.deleteDir(tmp);
} catch (Exception e) {
System.out.println("WARNING: Temporary files may not have been properly cleared.\n" + e.getMessage());
consoleDisplay.append("WARNING: Temporary files may not have been properly cleared.\n" + e.getMessage());
}
@@ -284,6 +319,7 @@ public class Gonzo {
public void AllowExit() {
System.out.println("\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());
frame.addWindowListener(new WindowAdapter() {
@Override

View File

@@ -26,10 +26,6 @@ import java.math.BigInteger;
import java.net.URL;
import java.nio.file.*;
import java.security.*;
import java.util.concurrent.TimeUnit;
import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;
import static javax.swing.WindowConstants.EXIT_ON_CLOSE;
// handles setup window
public class Kermit implements ActionListener {
@@ -99,7 +95,7 @@ public class Kermit implements ActionListener {
button.setVisible(false);frame.remove(button);
button2.setVisible(false);frame.remove(button2);
dialogText.setVisible(false);frame.remove(dialogText);
changePage(Pages.EXPORT_MODE);
changePage(Pages.SDK_INSTALL); // EXPORT_MODE
break;
case EXPORT_MODE:
button.setVisible(false);frame.remove(button);
@@ -176,10 +172,10 @@ public class Kermit implements ActionListener {
Main.repatch = false;
button.setEnabled(true);
} else if (actionEvent.getSource() == buttonHaveWine) {
Main.wine = true;
Main.windows = true;
button.setEnabled(true);
} else if (actionEvent.getSource() == buttonNoWine) {
Main.wine = false;
Main.windows = false;
button.setEnabled(true);
}else if (actionEvent.getSource() == openconfigfolderbutton) {
try {
@@ -214,9 +210,8 @@ public class Kermit implements ActionListener {
document.setParagraphAttributes(0, document.getLength(), align, false);
//dialogText.setText("Aww Fiddlesticks, what now?");
dialogText.setText("WELCOME TO FIRESTAR\n\n" +
"This initial setup guide will help you prepare your Playstation Vita, Playstation TV, or Vita3K emulator to play WipEout mods. You will be asked a series of questions to help Firestar decide the most optimal installation method for you.\n\n" +
"If you encounter any issues while using Firestar you may contact the author at bonkmaykr@screwgravity.net\n\n" +
"DISCLAIMER: This program is free software licensed under the GNU General Public License version 3.0. You may share this program and it's source code so long as you extend the same rights to others. The developers of this software are never responsible for any damage to your game console and Firestar is provided to you without any warranty or legal guarantee. By using Firestar, you agree to these terms. For more information, visit https://www.gnu.org/licenses/gpl-3.0.en.html");
"This initial setup guide will help you prepare your Playstation Vita, Playstation TV, or Vita3K emulator to play WipEout mods.\n\n Before continuing, please read the decryption guide at:\nhttps://git.worlio.com/bonkmaykr/firestar/wiki/Decrypting-Original-PSARC-Files\nsince you will need these files in order to use Firestar.\n\n" +
"If you encounter any issues while using Firestar you may contact the author at bonkmaykr@screwgravity.net");
frame.add(dialogText);
frame.setSize(600, 400);
@@ -235,7 +230,7 @@ public class Kermit implements ActionListener {
button.setBounds(292, 343, 300, 30);
frame.add(button);
button3.setVisible(true);
button3.setVisible(false);
button3.setBounds(0, 343, 292, 30);
frame.add(button3);
@@ -345,8 +340,8 @@ public class Kermit implements ActionListener {
dialogText.setVisible(false);frame.remove(dialogText);
//check if this is windows or not
if(System.getProperty("os.name").contains("Windows")) {Main.wine = false;System.out.println("Assuming we should NOT use WINE based on known system variables.");changePage(Pages.EXPORT_LOCATION);}
else {Main.wine = true;System.out.println("Assuming we should 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 = false;System.out.println("Assuming we should use WINE based on known system variables.");changePage(Pages.EXPORT_LOCATION);}
case EXPORT_LOCATION:
page = Pages.EXPORT_LOCATION;
@@ -355,7 +350,7 @@ public class Kermit implements ActionListener {
button.setBounds(292, 343, 300, 30);
frame.add(button);
button3.setVisible(true);
button3.setVisible(false);
button3.setBounds(0, 343, 292, 30);
frame.add(button3);
@@ -395,7 +390,7 @@ public class Kermit implements ActionListener {
button.setBounds(292, 343, 300, 30);
frame.add(button);
button3.setVisible(true);
button3.setVisible(false);
button3.setBounds(0, 343, 292, 30);
frame.add(button3);
@@ -409,7 +404,7 @@ public class Kermit implements ActionListener {
dialogText.setText("You're almost done!\n\n" +
"Please move all of your original PSARC files for WipEout (base game, patches, and HD Fury DLC) to the config folder and press Next when you are done.\n" +
"Firestar will use these to generate new PSARCs in place of the old ones.\n\n" +
"If you play on a real console, you will need to use VitaShell (remember to \"Open Decrypted\"). Check /app, /patch, and /addcont. Getting all of them is important since WipEout always loads them in a specific order and which ones you have determines where Firestar can compress the modified files to.");
"If you do not already have these files, please read:\nhttps://git.worlio.com/bonkmaykr/firestar/wiki/Decrypting-Original-PSARC-Files\nto acquire them.");
frame.add(dialogText);
//pathInput.setVisible(true);
@@ -453,7 +448,7 @@ public class Kermit implements ActionListener {
StyleConstants.setAlignment(align, StyleConstants.ALIGN_CENTER);
document.setParagraphAttributes(0, document.getLength(), align, false);
dialogText.setText("Firestar is ready!\n\n" +
"For technical support, email\nbonkmaykr@screwgravity.net.");
"For technical support, see Help > Manual.");
frame.add(dialogText);
button.setVisible(true);

View File

@@ -18,17 +18,17 @@
import org.json.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import javax.swing.*;
import java.util.List;
import javax.swing.JOptionPane;
public class Main {
// Build Information
public static final String vstr = "PREALPHA";
public static final String vcode = "Natasha";
public static final String vstr = "Release 1.1";
public static final String vcode = "Dekka";
public static final int vint = 0;
// User Settings
@@ -37,7 +37,7 @@ public class Main {
public static String outpath = System.getProperty("user.home") + "/.firestar/out/"; //game assets location
public static String inpath = System.getProperty("user.home") + "/.firestar/"; //game assets location
public static boolean repatch; //are we in compat mode?
public static boolean wine; //are we on Linux, MINIX, BSD?
public static boolean windows; //True = windows
//public static String psarc; //sdk location
public class Mod {
@@ -50,14 +50,18 @@ public class Main {
public String game; //TODO for multi game support
public int loaderversion = 0; //minimum required vint or feature level from Firestar
public String author; // if null, "Author is unknown."
public boolean enabled = true;
}
// Mods
public static List<Mod> Mods = new ArrayList<Mod>();
// UI Global Assets
public static Font fExo2;
public static void main(String[] args) {
// license string
System.out.printf("FIRESTAR MOD MANAGER for WipEout 2048\nversion " + vstr + " (codename " + vcode + ")\n" +
System.out.printf("FIRESTAR MOD MANAGER for WipEout 2048\nversion " + vstr + " (codename " + vcode + ") major " + vint + "\n" +
"JVM host appears to be " + System.getProperty("os.name") +
"\nRunning from " + System.getProperty("user.dir") +
"\nCopyright (C) 2024 bonkmaykr\n\nThis program is free software: you can redistribute it and/or modify\n" +
@@ -74,6 +78,14 @@ public class Main {
"along with this program. If not, see https://www.gnu.org/licenses/.\n\n\n\n");
//begin
// load global assets
try {
fExo2 = Font.createFont(Font.TRUETYPE_FONT, new File(System.getProperty("user.dir") + "/resources/exo2.ttf"));
} catch (Exception e) {
System.out.println("Font \"Exo 2\" is missing!");
fExo2 = new Font("Arial", Font.PLAIN, 12);
}
// check and load configs
File fConf = new File(System.getProperty("user.home") + "/.firestar/firestar.conf");
if (!fConf.isFile()) {
@@ -90,7 +102,7 @@ public class Main {
container.put("2048path", outpath);
container.put("HDpath", "TODO"); // proposed hd/fury support for ps3, will use very simplified Fast Mode due to less difficulty installing
container.put("safemode", repatch);
container.put("isUnix", wine);
container.put("isWin32", windows);
container.put("currentPlaylist", "TODO"); // proposed feature: store separate mod lists in lists/ to load/save later?
try {
@@ -101,6 +113,44 @@ public class Main {
}
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());
}
}
public static void deleteDir(File file) { // https://stackoverflow.com/a/29175213/9259829
File[] contents = file.listFiles();
if (contents != null) {
for (File f : contents) {
if (! Files.isSymbolicLink(f.toPath())) {
deleteDir(f);
}
}
}
file.delete();
}
}

View File

@@ -5,7 +5,10 @@
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties/>
<properties>
<background color="-1"/>
<foreground color="-592129"/>
</properties>
<border type="none"/>
<children>
<grid id="6cdb1" binding="descriptionContainer" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
@@ -17,7 +20,10 @@
<maximum-size width="300" height="-1"/>
</grid>
</constraints>
<properties/>
<properties>
<background color="-526337"/>
<foreground color="-592129"/>
</properties>
<border type="line" title="Description"/>
<children>
<component id="a9cee" class="javax.swing.JTextPane" binding="descriptionField">
@@ -38,7 +44,10 @@
<constraints>
<grid row="0" column="1" row-span="2" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<properties>
<background color="-526337"/>
<foreground color="-526337"/>
</properties>
<border type="line" title="Mods List" title-justification="1" title-position="2">
<font/>
</border>
@@ -64,7 +73,10 @@
<maximum-size width="300" height="200"/>
</grid>
</constraints>
<properties/>
<properties>
<background color="-526337"/>
<foreground color="-657921"/>
</properties>
<border type="line" title="Actions"/>
<children>
<component id="3cd60" class="javax.swing.JButton" binding="toggleButton" default-binding="true">
@@ -75,6 +87,10 @@
</grid>
</constraints>
<properties>
<background color="-15128227"/>
<borderPainted value="false"/>
<font name="Exo 2" style="1"/>
<foreground color="-657921"/>
<text value="Toggle"/>
</properties>
</component>
@@ -86,6 +102,10 @@
</grid>
</constraints>
<properties>
<background color="-15128227"/>
<borderPainted value="false"/>
<font name="Exo 2" style="1"/>
<foreground color="-657921"/>
<text value="Move Up"/>
</properties>
</component>
@@ -98,6 +118,11 @@
</grid>
</constraints>
<properties>
<alignmentX value="0.0"/>
<background color="-15128227"/>
<borderPainted value="false"/>
<font name="Exo 2" style="1"/>
<foreground color="-657921"/>
<text value="Delete"/>
</properties>
</component>
@@ -110,6 +135,10 @@
</grid>
</constraints>
<properties>
<background color="-15128227"/>
<borderPainted value="false"/>
<font name="Exo 2" style="1"/>
<foreground color="-592129"/>
<text value="Move Down"/>
</properties>
</component>
@@ -120,6 +149,10 @@
</grid>
</constraints>
<properties>
<background color="-15128227"/>
<borderPainted value="false"/>
<font name="Exo 2" style="1"/>
<foreground color="-592129"/>
<text value="Options"/>
</properties>
</component>
@@ -130,6 +163,10 @@
</grid>
</constraints>
<properties>
<background color="-15128227"/>
<borderPainted value="false"/>
<font name="Exo 2" style="1"/>
<foreground color="-526337"/>
<text value="Import"/>
</properties>
</component>
@@ -141,6 +178,10 @@
</grid>
</constraints>
<properties>
<background color="-15128227"/>
<borderPainted value="false"/>
<font name="Exo 2" style="1"/>
<foreground color="-657921"/>
<text value="Deploy"/>
</properties>
</component>

View File

@@ -19,21 +19,31 @@
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.math.RoundingMode;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.regex.Pattern;
import net.lingala.zip4j.*;
import net.lingala.zip4j.exception.ZipException;
import org.json.JSONException;
import org.json.JSONObject;
import static java.nio.file.StandardCopyOption.*;
import static javax.swing.WindowConstants.EXIT_ON_CLOSE;
@@ -62,13 +72,18 @@ public class MissPiggy implements ActionListener {
private JButton deployButton;
private JTextPane descriptionField;
private int selectedItem;
//private int selectedItem;
public String priorityList;
public String blackList;
public boolean listenersAlreadySet = false; // was written to troubleshoot a bug but this wasn't actually the cause
// Initialize the main window
public void Action(/*Main entryPoint*/) {
System.out.println("Main window created");
System.out.println("Loading program configuration");
Main.loadConf(this);
// populate menu bar
menuBar = new JMenuBar();
@@ -76,9 +91,10 @@ public class MissPiggy implements ActionListener {
toolsMenu = new JMenu("Tools");
helpMenu = new JMenu("Help");
fileMenu.add(new JMenuItem("Deploy All Mods"));
fileMenu.add(new JMenuItem("Import Mod..."));
fileMenu.add(new JMenuItem("Remove All"));
fileMenu.add(new JMenuItem("Deploy Mods"));
fileMenu.add(new JMenuItem("Import Mod from File"));
//fileMenu.add(new JSeparator());
fileMenu.add(new JMenuItem("Delete All"));
fileMenu.add(new JSeparator());
fileMenu.add(new JMenuItem("Options"));
fileMenu.add(new JMenuItem("Quit"));
@@ -88,6 +104,11 @@ public class MissPiggy implements ActionListener {
toolsMenu.add(new JMenuItem("Create Soundtrack Mod..."));
//toolsMenu.add(new JMenuItem("Download Mod from URL")); // TODO: implement. move option to File menu. should be ez
helpMenu.add(new JMenuItem("Manual"));
helpMenu.add(new JSeparator());
helpMenu.add(new JMenuItem("Source Code")); //replace with Website 'screwgravity.net' and 'Issue Tracker' gitea later
helpMenu.add(new JMenuItem("License"));
helpMenu.add(new JSeparator());
helpMenu.add(new JMenuItem("About Firestar"));
menuBar.add(fileMenu);
@@ -108,41 +129,21 @@ public class MissPiggy implements ActionListener {
fileMenu.getItem(5).addActionListener(this);
toolsMenu.getItem(0).addActionListener(this);
toolsMenu.getItem(1).addActionListener(this);
toolsMenu.getItem(2).addActionListener(this);
helpMenu.getItem(0).addActionListener(this);
helpMenu.getItem(2).addActionListener(this);
helpMenu.getItem(3).addActionListener(this);
helpMenu.getItem(5).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);
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
try {
@@ -151,6 +152,23 @@ public class MissPiggy implements ActionListener {
} catch (IOException e) {
System.out.println("ERROR: Failed to find /resources/titleIcon.png. Window will not have an icon.");
}
menuBar.setBackground(new Color(25, 41, 93));
fileMenu.setForeground(new Color(255, 255, 255));
fileMenu.setFont(Main.fExo2.deriveFont(Font.BOLD).deriveFont(12f));
toolsMenu.setForeground(new Color(255, 255, 255));
toolsMenu.setFont(Main.fExo2.deriveFont(Font.BOLD).deriveFont(12f));
helpMenu.setForeground(new Color(255, 255, 255));
helpMenu.setFont(Main.fExo2.deriveFont(Font.BOLD).deriveFont(12f));
toggleButton.setFont(Main.fExo2.deriveFont(Font.BOLD).deriveFont(12f));
deleteButton1.setFont(Main.fExo2.deriveFont(Font.BOLD).deriveFont(12f));
moveDownButton.setFont(Main.fExo2.deriveFont(Font.BOLD).deriveFont(12f));
moveUpButton.setFont(Main.fExo2.deriveFont(Font.BOLD).deriveFont(12f));
optionsButton.setFont(Main.fExo2.deriveFont(Font.BOLD).deriveFont(12f));
importButton.setFont(Main.fExo2.deriveFont(Font.BOLD).deriveFont(12f));
deployButton.setFont(Main.fExo2.deriveFont(Font.BOLD).deriveFont(12f));
frame.setSize(800, 600); // 1280 800
frame.setMinimumSize(new Dimension(640,480));
frame.setTitle("Firestar Mod Manager");
@@ -191,35 +209,72 @@ public class MissPiggy implements ActionListener {
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.
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]+=*")) {
File mod = new File(System.getProperty("user.home") + "/.firestar/mods/" + s.substring(s.indexOf("=") + 1).trim());
if (s.split("=")[0].matches("[0-9]+=*") &&
mod.exists()) {
//append mod to list from substring
Main.Mod m = new Main().new Mod();
m.path = s.substring(s.indexOf("=") + 1);
m.path = s.substring(s.indexOf("=") + 1).trim();
System.out.println("found file " + m.path);
//get json metadata from zip comment
JSONObject metadata;
try {
metadata = new JSONObject(new ZipFile(System.getProperty("user.home") + "/.firestar/mods/" + m.path).getComment());
} catch (ZipException e) {
throw new RuntimeException(e); //todo: fault tolerance
}
if (metadata.has("friendlyName")) {m.friendlyName = metadata.get("friendlyName").toString();} else {m.friendlyName = m.path;}
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("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();}
if (metadata.has("friendlyName")) {m.friendlyName = metadata.get("friendlyName").toString();} else {m.friendlyName = m.path;}
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("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
Main.Mods.add(m);
//send to list
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");
System.out.println(e.getMessage());
}
} else {
if (!s.isEmpty()) {System.out.println("WARNING: mod entry for " + s + " doesn't actually exist. skipping");}
}
}
try {
blackList = new String(Files.readAllBytes(Paths.get(System.getProperty("user.home") + "/.firestar/mods/blacklist")));
} catch (IOException e) {
File blackListFileHandle = new File(System.getProperty("user.home") + "/.firestar/mods/blacklist");
new File(System.getProperty("user.home") + "/.firestar/mods/").mkdirs();
if(!blackListFileHandle.isFile()){
try {
blackListFileHandle.createNewFile();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
blackList = "";
}
// initialize data structures from each list entry
String[] bListArray = blackList.split("\n");
//Arrays.sort(bListArray);
System.out.println("Initializing blacklist from file with length of " + bListArray.length + " units"); //debug
for (String s : bListArray) {
for (Main.Mod m : Main.Mods) {
if (s.trim().equals(m.path)) {
m.enabled = false;
}
}
}
}
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
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.");
modList.clearSelection();
modList.removeAll();
@@ -231,7 +286,8 @@ public class MissPiggy implements ActionListener {
/*JLabel[]*/String[] contents = new String[Main.Mods.size()];
System.out.println("Initializing modList to GUI with length of " + Main.Mods.size() + " units"); //debug
while (i < Main.Mods.size()) {
contents[i] = Main.Mods.get(i).friendlyName;
if (Main.Mods.get(i).enabled) {contents[i] = Main.Mods.get(i).friendlyName;}
else {contents[i] = Main.Mods.get(i).friendlyName + " (Disabled)";}
//debug
String authorDisplay;
@@ -241,6 +297,7 @@ public class MissPiggy implements ActionListener {
i++;
}
modList.setListData(contents);
createSelectionEventListener();
}
private ListSelectionListener whenItemSelected() {
@@ -251,21 +308,71 @@ public class MissPiggy implements ActionListener {
public void actionPerformed(ActionEvent actionEvent) {
if (actionEvent.getSource() == fileMenu.getItem(5)) {System.exit(0);} 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(2)) {removeAllGUI();} else
if (actionEvent.getSource() == optionsButton) {optionsGUI();} else
if (actionEvent.getSource() == fileMenu.getItem(4)) {optionsGUI();} else
if (actionEvent.getSource() == helpMenu.getItem(0)) {new Rowlf().displayAboutScreen();}
if (actionEvent.getSource() == moveUpButton) {moveUp(modList.getSelectedIndex());} else
if (actionEvent.getSource() == moveDownButton) {moveDown(modList.getSelectedIndex());} else
if (actionEvent.getSource() == toggleButton) {toggleSelected(modList.getSelectedIndex());} else
if (actionEvent.getSource() == deleteButton1) {deleteSelected();} else
if (actionEvent.getSource() == toolsMenu.getItem(0)) {metaEditorGUI(modList.getSelectedIndex());} else
if (actionEvent.getSource() == toolsMenu.getItem(1)) {generatorGUI();} else
if (actionEvent.getSource() == toolsMenu.getItem(2)) {throwUnimplemented();} else
if (actionEvent.getSource() == helpMenu.getItem(0)) {
try {
Desktop.getDesktop().browse(new URI("https://git.worlio.com/bonkmaykr/firestar/wiki/"));
} catch (Exception e) {
System.out.println(e.getMessage());
JOptionPane.showMessageDialog(frame, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
} else
if (actionEvent.getSource() == helpMenu.getItem(2)) {
try {
Desktop.getDesktop().browse(new URI("https://git.worlio.com/bonkmaykr/firestar"));
} catch (Exception e) {
System.out.println(e.getMessage());
JOptionPane.showMessageDialog(frame, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
} else
if (actionEvent.getSource() == helpMenu.getItem(3)) {
try {
Desktop.getDesktop().browse(new URI("https://www.gnu.org/licenses/gpl-3.0.en.html"));
} catch (Exception e) {
System.out.println(e.getMessage());
JOptionPane.showMessageDialog(frame, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
} else
if (actionEvent.getSource() == helpMenu.getItem(5)) {new Rowlf().displayAboutScreen();}
}
// Will likely split the below functions into separate classes to work with intellij GUI designer.
public void deployModGUI() {
// prevent interruptions
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
frame.setEnabled(false);
int i = 0;
for (Main.Mod m : Main.Mods) {
if (m.enabled) {i++;}
}
// start
new Gonzo().DeployMods(this);
if (i > 0) {
int result = JOptionPane.showConfirmDialog(frame, "A new PSARC will be generated. This can take several minutes.\nDuring this time, your computer may be very busy or slow.\n\nAre you sure you want to continue?", "Deploy Mods", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
if (result == JOptionPane.YES_OPTION) {
// prevent interruptions
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
frame.setEnabled(false);
// start
new Gonzo().DeployMods(this);
}
} else {
JOptionPane.showMessageDialog(frame, "Please add at least one mod file to continue.", "Error", JOptionPane.ERROR_MESSAGE);
}
}
public void wrapUpDeployment() {
@@ -275,26 +382,252 @@ public class MissPiggy implements ActionListener {
}
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", "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.getAbsolutePath());
if (zipImporterHandler.isValidZipFile()) {
try {
JSONObject json = new JSONObject(new ZipFile(selectedFile.getAbsolutePath()).getComment()); // intentionally trigger exception if file is random BS
if ((int)json.get("loaderversion") <= Main.vint) {
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();
} else {
System.out.println("ERROR: This mod requires feature level " + json.get("loaderversion").toString() + ", but you have level " + Main.vint + ".");
JOptionPane.showMessageDialog(frame, "This mod requires feature level " + json.get("loaderversion").toString() + ", but you have level " + Main.vint + ".\nPlease update Firestar to the latest version.", "Error", JOptionPane.ERROR_MESSAGE);
}
} catch (JSONException e) {
System.out.println("ERROR: File is not a valid ZIP archive with mod data. Aborting.");
JOptionPane.showMessageDialog(frame, "Whoops, that's not a valid mod file.", "Error", JOptionPane.ERROR_MESSAGE);
} 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 with mod data. Aborting.");
JOptionPane.showMessageDialog(frame, "Whoops, that's not a valid mod file.", "Error", JOptionPane.ERROR_MESSAGE);
}
}
}
public void removeAllGUI() {
// 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) {
Main.deleteDir(new File(System.getProperty("user.home") + "/.firestar/mods/"));
Main.Mods.clear();
InitializeModListStructure();
InitializeModListInGUI();
}
}
public void optionsGUI() {
// todo settings page w/ reset switch
new Waldorf().Action(this);
frame.setEnabled(false);
}
public void deleteSelected() {
if (modList.getSelectedIndex() >= 0) {
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());
regenerateModBlacklist(false);
regenerateModIndex(true);
} else {
JOptionPane.showMessageDialog(frame, "Please select a mod to delete first.", "Error", JOptionPane.ERROR_MESSAGE);
}
}
public void generatorGUI() {
// todo mod packer
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int result = fileChooser.showOpenDialog(frame);
if (result == JFileChooser.APPROVE_OPTION) {
if (fileChooser.getSelectedFile().isDirectory()
&& new File(fileChooser.getSelectedFile().getAbsolutePath() + "/data").isDirectory()) {
File file = fileChooser.getSelectedFile();
new Clifford().Action(this, file);
frame.setEnabled(false);
} else {
JOptionPane.showMessageDialog(frame, "You must select a folder containing a \"data\" directory with game assets.", "Error", JOptionPane.ERROR_MESSAGE);
}
}
}
public void metaEditorGUI() {
// todo tag editor
public void metaEditorGUI(int index) {
if (index >= 0) {
new Clifford().Action(this, index);
frame.setEnabled(false);
} else {
JOptionPane.showMessageDialog(frame, "Please select a mod to edit first.", "Error", JOptionPane.ERROR_MESSAGE);
}
}
public void aboutGUI() {
// todo about page
private void moveUp(int index) {
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);
}
}
private void toggleSelected(int index) {
if (index >= 0) {
Main.Mods.get(index).enabled = !Main.Mods.get(index).enabled;
regenerateModBlacklist(true);
} else {
JOptionPane.showMessageDialog(frame, "Please select a mod to toggle first.", "Error", JOptionPane.ERROR_MESSAGE);
}
}
public void throwUnimplemented() {
JOptionPane.showMessageDialog(frame, "This feature is unimplemented and will be coming soon.\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();
System.out.println("Mod index file regenerated.");
if(reload) {
Main.Mods.clear(); //cleanup
priorityList = "";
InitializeModListStructure();
InitializeModListInGUI();
}
} catch (Exception e) {
System.out.println(e.getMessage());
JOptionPane.showMessageDialog(frame, "An error has occured.\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
public void regenerateModBlacklist(boolean reload) {
try {
System.out.println("Regenerating blacklist..."); //debug
new File(System.getProperty("user.home") + "/.firestar/mods/blacklist").delete();
File blackListFileHandle = new File(System.getProperty("user.home") + "/.firestar/mods/blacklist");
blackListFileHandle.createNewFile();
BufferedWriter bw2 = new BufferedWriter(new FileWriter(System.getProperty("user.home") + "/.firestar/mods/blacklist", true));
int i2 = 0;
for (Main.Mod m : Main.Mods) {
if (!m.enabled) {
bw2.write(m.path);
bw2.newLine();
i2++;
}
}
bw2.close();
System.out.println("Mod blacklist file regenerated.");
if(reload) {
Main.Mods.clear(); //cleanup
blackList = "";
InitializeModListStructure();
InitializeModListInGUI();
}
} 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("");
informationText.getDocument().putProperty("filterNewlines", Boolean.FALSE);
informationText.setText("Created by bonkmaykr\n" +
"a.k.a. \"Downforce Agent\"\n" +
informationText.setHorizontalAlignment(JTextField.CENTER);
informationText.setText("Created by bonkmaykr, a.k.a. \"Downforce Agent\"\n" +
"\n" +
"Special thanks to:\n" +
"ThatOneBonk, for almost beating me to it\n" +
"Wirlaburla, for web hosting and code help\n" +
"ThatOneBonk, for Thallium and for modding 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");
informationText.setHighlighter(null);
informationText.getCaret().setVisible(false);

74
src/Waldorf.form Normal file
View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="Waldorf">
<grid id="27dc6" binding="frameContainer" layout-manager="GridLayoutManager" row-count="5" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties>
<maximumSize width="50" height="100"/>
<minimumSize width="50" height="100"/>
<preferredSize width="50" height="100"/>
</properties>
<border type="none"/>
<children>
<component id="3d88f" class="javax.swing.JButton" binding="okbtn">
<constraints>
<grid row="4" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Save"/>
</properties>
</component>
<component id="86775" class="javax.swing.JTextField" binding="fOutpath">
<constraints>
<grid row="3" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="db731" class="javax.swing.JLabel">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Mod Export Path"/>
</properties>
</component>
<component id="50d13" class="javax.swing.JButton" binding="cancelbtn">
<constraints>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Cancel"/>
</properties>
</component>
<component id="252b3" class="javax.swing.JButton" binding="resetbtn">
<constraints>
<grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Restore Defaults"/>
</properties>
</component>
<component id="e2cd7" class="javax.swing.JLabel">
<constraints>
<grid row="0" column="0" row-span="3" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value=".firestar Folder"/>
</properties>
</component>
<component id="c68f2" class="javax.swing.JButton" binding="bOpenFolder">
<constraints>
<grid row="0" column="1" row-span="3" col-span="2" vsize-policy="0" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<label value="Browse"/>
<text value="Browse"/>
</properties>
</component>
</children>
</grid>
</form>

104
src/Waldorf.java Normal file
View File

@@ -0,0 +1,104 @@
/*
* Firestar Mod Manager
* Copyright (C) 2024 bonkmaykr
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import org.json.JSONObject;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import static javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE;
public class Waldorf implements ActionListener {
private JFrame frame = new JFrame();
private JPanel frameContainer;
private JButton okbtn;
private JButton cancelbtn;
private JTextField fOutpath;
private JButton resetbtn;
private JButton bOpenFolder;
MissPiggy invoker;
public void Action(MissPiggy inv) {
invoker = inv;
frame.add(frameContainer);
frame.setSize(600, 200); // 1280 800
frame.setMinimumSize(new Dimension(200,100));
frame.setTitle("Options");
frame.setResizable(false);
frame.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
frame.setLayout(new GridLayout());
frame.setLocationRelativeTo(null);
frame.setAlwaysOnTop(true);
cancelbtn.addActionListener(this);
okbtn.addActionListener(this);
resetbtn.addActionListener(this);
bOpenFolder.addActionListener(this);
fOutpath.setText(Main.outpath);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e)
{
invoker.frame.setEnabled(true);
e.getWindow().dispose();
}
});
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (actionEvent.getSource() == cancelbtn) {
invoker.frame.setEnabled(true);
frame.dispose();
} else
if (actionEvent.getSource() == okbtn) {
Main.outpath = fOutpath.getText();
Main.writeConf();
invoker.frame.setEnabled(true);
frame.dispose();
} else
if (actionEvent.getSource() == resetbtn) {
int result = JOptionPane.showConfirmDialog(frame,"Are you sure you want to redo the initial setup?", "Restore Default Settings", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
if (result == JOptionPane.YES_OPTION) {
new File(System.getProperty("user.home") + "/.firestar/firestar.conf").delete();
int result2 = JOptionPane.showConfirmDialog(frame,"Firestar will now close.", "Restore Default Settings", JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE);
if (result2 == JOptionPane.OK_OPTION) {
System.exit(0);
}
}
} else
if (actionEvent.getSource() == bOpenFolder) {
try {
Desktop.getDesktop().open(new File(Main.inpath));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}