20 Commits

Author SHA1 Message Date
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
81069fbb5d build changes + windows compatibility 2024-05-08 04:51:25 -05:00
7a0f224505 Why are the bools inverted? Both of them? kill me 2024-05-07 13:22:24 -05:00
b31275012a Update readme 2024-05-07 13:11:11 -05:00
21724bc04c Fix game crash caused by psarc not including root "data" folder and only it's contents. 2024-05-07 13:08:12 -05:00
cd1d9eab3c Deploy function completed - mostly 2024-05-07 12:21:22 -05:00
16337680a8 TIL there's a muppet named rizz rat... gen z has ruined me 2024-05-07 08:44:26 -05:00
8 changed files with 557 additions and 43 deletions

13
.idea/artifacts/firestar_jar.xml generated Normal file
View File

@@ -0,0 +1,13 @@
<component name="ArtifactManager">
<artifact type="jar" build-on-make="true" name="firestar:jar">
<output-path>$PROJECT_DIR$/out/artifacts/firestar_jar</output-path>
<root id="archive" name="firestar.jar">
<element id="directory" name="META-INF">
<element id="file-copy" path="$PROJECT_DIR$/bin/META-INF/MANIFEST.MF" />
</element>
<element id="module-output" name="firestar" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/net/lingala/zip4j/zip4j/2.11.5/zip4j-2.11.5.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/json/json/20240303/json-20240303.jar" path-in-jar="/" />
</root>
</artifact>
</component>

View File

@@ -1,8 +1,19 @@
![](https://files.worlio.com/users/bonkmaykr/http/git/embed/firestar.png)
# about 50% complete (I/O in progress)
Firestar is a mod manager for WipEout 2048 which automatically handles sorting mods by priority and repacking game assets based on selected add-on packs. It runs on a desktop/laptop computer and aims to allow easy installation of mods for users who have only a surface level understanding of hacking the PSVita.
# about 85% complete
**What works:**
- Importing mods
- Repacking (installing) mods
- config loading
- Cross-platform support
**What doesn't work yet:**
- managing mods within the program (delete, change priority, toggle)
- settings screen
- misc. tools (pack creator, soundtrack mod generator...)
![](https://files.worlio.com/users/bonkmaykr/http/git/embed/firestar_window.png)
# System Requirements
@@ -13,15 +24,23 @@ Firestar is a mod manager for WipEout 2048 which automatically handles sorting m
# 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.
TODO: More details will be provided once I/O routines are completed.
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. 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.
@@ -30,4 +49,7 @@ Because many third-party programs for opening PSARC files either have no ability
Cross-platform support with no hassle, a built-in UI framework, and I don't need to sell my soul like every Electron or React 'Native' developer is doing these days.
## 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.

28
src/Gonzo.form Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="Gonzo">
<grid id="27dc6" binding="frameContainer" layout-manager="GridLayoutManager" row-count="1" column-count="1" 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>
<scrollpane id="6830c" binding="scrollPane">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="line" title="Please wait a few minutes. Progress is shown above." title-justification="2" title-position="5"/>
<children>
<component id="491c1" class="javax.swing.JTextArea" binding="consoleDisplay">
<constraints/>
<properties>
<text value=""/>
</properties>
</component>
</children>
</scrollpane>
</children>
</grid>
</form>

346
src/Gonzo.java Normal file
View File

@@ -0,0 +1,346 @@
/*
* 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 javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class Gonzo {
JFrame frame = new JFrame();
private JPanel frameContainer;
private JTextArea consoleDisplay;
private JScrollPane scrollPane;
public boolean data0;
public boolean data1;
public boolean data2;
public boolean dlc1;
public boolean dlc2;
private MissPiggy invoker;
public String oArcTarget = "dlc2.psarc"; // which psarc to rebuild the assets in
public void DeployMods(MissPiggy inv) {
invoker = inv;
System.out.println("\n\nStarting mod deployment\n\n");
frame.add(frameContainer); // initialize window contents -- will be handled by IntelliJ IDEA
try {
BufferedImage windowIcon = ImageIO.read(new File(System.getProperty("user.dir") + "/resources/titleIcon.png"));
frame.setIconImage(windowIcon);
} catch (IOException e) {
System.out.println("ERROR: Failed to find /resources/titleIcon.png. Window will not have an icon.");
}
frame.setSize(800, 400);
frame.setMinimumSize(new Dimension(600,400));
frame.setTitle("Mod Installation");
frame.setResizable(false);
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
frame.setLayout(new GridLayout());
frame.setLocationRelativeTo(null);
frame.setAlwaysOnTop(true);
frame.setVisible(true);
File psarcHandle = new File(Main.inpath + "data.psarc");
data0 = psarcHandle.isFile();
psarcHandle = new File(Main.inpath + "data1.psarc");
data1 = psarcHandle.isFile();
psarcHandle = new File(Main.inpath + "data2.psarc");
data2 = psarcHandle.isFile();
psarcHandle = new File(Main.inpath + "dlc1.psarc");
dlc1 = psarcHandle.isFile();
psarcHandle = new File(Main.inpath + "dlc2.psarc");
dlc2 = psarcHandle.isFile();
System.out.println("Source files discovered: data " + data0 + ", data1 " + data1 + ", data2 " + data2 + ", dlc1 " + dlc1 + ", dlc2 " + dlc2);
final Thread managerThread = new Thread() {
@Override
public void run() {
if (Main.repatch) {
CompatibilityRoutine();
} else {
FastRoutine();
}
}
};
managerThread.start();
}
private void CompatibilityRoutine() {
// create temporary working area for asset dump
new File(System.getProperty("user.home") + "/.firestar/temp/").mkdirs();
// decide which files to dump
List<String> dumpThese = new ArrayList<String>();
if (data0) {dumpThese.add("data.psarc");oArcTarget = "data.psarc";}
if (data1) {dumpThese.add("data1.psarc");oArcTarget = "data1.psarc";}
if (data2) {dumpThese.add("data2.psarc");oArcTarget = "data2.psarc";}
if (dlc1) {dumpThese.add("dlc1.psarc");oArcTarget = "dlc1.psarc";}
if (dlc2) {dumpThese.add("dlc2.psarc");oArcTarget = "dlc2.psarc";}
// dump all assets to working area
for (String s : dumpThese) {
try {
System.out.println("Firestar is extracting " + s);
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.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() {
try {
final BufferedReader reader = new BufferedReader(
new InputStreamReader(p.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
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. You can safely ignore this.\n" + e.getMessage());}
}
reader.close();
} catch (final Exception e) {
e.printStackTrace(); // will probably definitely absolutely for sure hang firestar unless we do something. Too bad!
}
}
};
ioThread.start();
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;
}
}
// 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 (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")));
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 {
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;
}
}
// create a list of the contents of data/ for psp2psarc.exe to read from
List<String> oFilesList = new ArrayList<String>();
List<String> oFilesList2 = new ArrayList<String>();
try {
listAllFiles(Paths.get(System.getProperty("user.home") + "/.firestar/temp/data/"), oFilesList);
for (String p : oFilesList) {
// 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.
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");
if (oFilesListO.isFile()) {oFilesListO.delete();}
FileWriter oFilesListWr = new FileWriter(oFilesListO, true);
int i = 0;
for (String p : oFilesList2) {
oFilesListWr.append(p);
if (i != oFilesList2.size()) {
oFilesListWr.append("\n");
}
i++;
}
oFilesListWr.close();
} 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
try {
System.out.println("Firestar is compiling the final build");
consoleDisplay.append("Firestar is compiling the final build" + "\n");
Process p;
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() {
try {
final BufferedReader reader = new BufferedReader(
new InputStreamReader(p.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
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());}
}
reader.close();
} catch (final Exception e) {
e.printStackTrace(); // will probably definitely absolutely for sure hang firestar unless we do something. Too bad!
}
}
};
ioThread.start();
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
boolean one = new File(Main.outpath).mkdirs();
boolean two;
System.out.println("created export folder: " + one);
if (!Main.windows) {two = new File(System.getProperty("user.home") + "/.firestar/temp/" + oArcTarget).renameTo(new File(Main.outpath + oArcTarget));}
else {two = new File(System.getProperty("user.home") + "\\.firestar\\temp\\" + oArcTarget).renameTo(new File(Main.outpath + oArcTarget));}
System.out.println("moved file to destination: " + two);
if (two) {System.out.println("file should be located at " + Main.outpath + oArcTarget);} else {
System.out.println("CRITICAL FAILURE: Please check that your output path is correct and that you have write permissions!");
consoleDisplay.append("CRITICAL FAILURE: Please check that your output path is correct and that you have write permissions!");
JOptionPane.showMessageDialog(this.frame, "CRITICAL FAILURE: Please check that your output path is correct and that you have write permissions!", "Fatal Error", JOptionPane.ERROR_MESSAGE);
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
AllowExit();
return;
}
try {
Process p;
if (!Main.windows) {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", new String(System.getProperty("user.home") + "/.firestar/temp/").replace("/", "\\").replace("\\", "\\\\"), "/s", "/q"});}
//new File(System.getProperty("user.home") + "/.firestar/temp/").delete();
} catch (IOException 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());
}
// done!
try {
TimeUnit.SECONDS.sleep(1); // avoid race condition when logging
} catch (InterruptedException e) {
//ignore
}
TitledBorder titledBorder = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.BLACK), "DONE! Close this pop-up to continue.");
titledBorder.setTitlePosition(TitledBorder.BOTTOM);
titledBorder.setTitleJustification(TitledBorder.CENTER);
scrollPane.setBorder(titledBorder);
scrollPane.repaint();
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
AllowExit();
}
private void FastRoutine() {
}
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
public void windowClosing(WindowEvent e)
{
invoker.wrapUpDeployment();
e.getWindow().dispose();
}
});
}
private static void listAllFiles(Path currentPath, List<String> allFiles)
throws IOException
{
try (DirectoryStream<Path> stream = Files.newDirectoryStream(currentPath))
{
for (Path entry : stream) {
if (Files.isDirectory(entry)) {
listAllFiles(entry, allFiles);
} else {
allFiles.add(entry.toString());
}
}
}
}
}

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 {
@@ -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 {
@@ -345,8 +341,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;

View File

@@ -18,24 +18,24 @@
import org.json.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import javax.swing.*;
import javax.swing.JOptionPane;
public class Main {
// Build Information
public static final String vstr = "PREALPHA";
public static final String vstr = "Beta 1";
public static final String vcode = "Natasha";
public static final int vint = 0;
// User Settings
public static String outpath; //game assets location
// TODO: replace with user preference when config i/o is done
// also please double check that outpath is actually valid
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 {
@@ -88,7 +88,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 {
@@ -99,6 +99,32 @@ 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());
}
}
}

View File

@@ -19,21 +19,25 @@
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.nio.file.*;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Random;
import java.util.regex.Pattern;
import net.lingala.zip4j.*;
import net.lingala.zip4j.exception.ZipException;
import org.json.JSONObject;
import static java.nio.file.StandardCopyOption.*;
import static javax.swing.WindowConstants.EXIT_ON_CLOSE;
@@ -69,6 +73,8 @@ public class MissPiggy implements ActionListener {
// 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();
@@ -110,6 +116,14 @@ public class MissPiggy implements ActionListener {
toolsMenu.getItem(1).addActionListener(this);
helpMenu.getItem(0).addActionListener(this);
deployButton.addActionListener(this);
importButton.addActionListener(this);
deleteButton1.addActionListener(this);
optionsButton.addActionListener(this);
moveUpButton.addActionListener(this);
moveDownButton.addActionListener(this);
toggleButton.addActionListener(this);
descriptionField.getDocument().putProperty("filterNewlines", Boolean.FALSE);
modList.addListSelectionListener(e -> {
String authorDisplay;
@@ -191,8 +205,11 @@ 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]+=*")) {
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
Main.Mod m = new Main().new Mod();
m.path = s.substring(s.indexOf("=") + 1);
@@ -202,18 +219,20 @@ public class MissPiggy implements ActionListener {
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");
}
} else {
if (!s.isEmpty()) {System.out.println("WARNING: mod entry for " + s + " doesn't actually exist. skipping");}
}
}
}
@@ -251,40 +270,103 @@ 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() == moveUpButton) {throwUnimplemented();} else // todo
if (actionEvent.getSource() == moveDownButton) {throwUnimplemented();} else // todo
if (actionEvent.getSource() == toggleButton) {throwUnimplemented();} else // todo
if (actionEvent.getSource() == deleteButton1) {throwUnimplemented();} else // todo
if (actionEvent.getSource() == helpMenu.getItem(0)) {new Rowlf().displayAboutScreen();}
}
// Will likely split the below functions into separate classes to work with intellij GUI designer.
public void deployModGUI() {
// todo dialog that monitors psarc progress, handles file transfers, etc
// could prevent closing program during this process? (may also be unnecessary)
// prevent interruptions
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
frame.setEnabled(false);
// start
new Gonzo().DeployMods(this);
}
public void wrapUpDeployment() {
// restore functionality to main window
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.setEnabled(true);
}
public void importModGUI() {
// todo call system shell to request file picker
JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
//fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("All", "zip", "agr", "agrc", "agrf", "fstar"));
fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("ZIP Archive", "zip"));
fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("Firestar Mod Package", "agr", "agrc", "agrf", "fstar")); //what about fstar?
int result = fileChooser.showOpenDialog(frame);
if (result == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
System.out.println("Importing selected mod file \"" + selectedFile.getName() + "\"");
ZipFile zipImporterHandler = new ZipFile(selectedFile.getPath());
if (zipImporterHandler.isValidZipFile()) {
try {
Path importDestination = Paths.get(System.getProperty("user.home") + "/.firestar/mods/"
+ selectedFile.getName() + "_" + Main.Mods.size() + ".zip");
Files.copy(Paths.get(selectedFile.getPath()), importDestination, StandardCopyOption.REPLACE_EXISTING);
String importDestinationName = importDestination.toFile().getName();
BufferedWriter bw = new BufferedWriter(new FileWriter(System.getProperty("user.home") + "/.firestar/mods/index", true));
bw.write(Main.Mods.size() + "=" + importDestinationName);
bw.newLine();
bw.close();
InitializeModListStructure();
InitializeModListInGUI();
} catch (Exception e) {
System.out.println(e.getMessage());
JOptionPane.showMessageDialog(frame, "An error has occured.\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
} else {
System.out.println("ERROR: File is not a valid ZIP archive. Aborting.");
JOptionPane.showMessageDialog(frame, "Whoops, that's not a valid ZIP archive.", "Error", JOptionPane.ERROR_MESSAGE);
}
}
}
public void removeAllGUI() {
// todo warning dialog that nukes list when Yes is clicked
throwUnimplemented();
}
public void optionsGUI() {
// todo settings page w/ reset switch
throwUnimplemented();
}
public void generatorGUI() {
// todo mod packer
throwUnimplemented();
}
public void metaEditorGUI() {
// todo tag editor
throwUnimplemented();
}
public void aboutGUI() {
// todo about page
throwUnimplemented();
}
public void throwUnimplemented() {
JOptionPane.showMessageDialog(frame, "Unimplemented.\nSee README at https://git.worlio.com/bonkmaykr/firestar", "Unimplemented", JOptionPane.INFORMATION_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);