custom asset archive loading

This commit is contained in:
minjaesong
2026-02-20 13:23:32 +09:00
parent 2dea82bb9c
commit 97d22913bf
7 changed files with 72 additions and 28 deletions

View File

@@ -1,7 +1,8 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Terrarum (no prebuild)" type="JarApplication"> <configuration default="false" name="Terrarum (no prebuild, release-mode assets)" type="JarApplication">
<option name="JAR_PATH" value="$PROJECT_DIR$/out/TerrarumBuild.jar" /> <option name="JAR_PATH" value="$PROJECT_DIR$/out/TerrarumBuild.jar" />
<option name="VM_PARAMETERS" value="-ea -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd" /> <option name="VM_PARAMETERS" value="-ea -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd" />
<option name="PROGRAM_PARAMETERS" value="--assets buildapp/out/assets.tevd" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="ALTERNATIVE_JRE_PATH" value="17" /> <option name="ALTERNATIVE_JRE_PATH" value="17" />
<module name="TerrarumBuild" /> <module name="TerrarumBuild" />

View File

@@ -342,6 +342,14 @@ public class App implements ApplicationListener {
public static boolean hasUpdate = true; public static boolean hasUpdate = true;
/**
* Path to a specific .tevd assets archive supplied via the --assets command-line argument.
* When non-null, this archive is used exclusively; the default ./assets.tevd and the
* local ./assets/ directory are both ignored.
*/
@Nullable
public static String overrideAssetArchive = null;
public static Screen getCurrentScreen() { public static Screen getCurrentScreen() {
return currentScreen; return currentScreen;
} }
@@ -381,6 +389,17 @@ public class App implements ApplicationListener {
} }
public static void main(String[] args) { public static void main(String[] args) {
// System.out.println("Arguments: "+String.join(",", args));
// Parse command-line arguments before anything else
for (int i = 0; i < args.length; i++) {
if (args[i].equals("--assets") && i + 1 < args.length) {
overrideAssetArchive = args[i + 1];
System.out.println("Using custom assets: "+overrideAssetArchive);
i++;
}
}
loadedTime_t = getTIME_T(); loadedTime_t = getTIME_T();
updateBogoflops(100_000_000L); updateBogoflops(100_000_000L);
@@ -441,7 +460,7 @@ public class App implements ApplicationListener {
// load configs // load configs
getDefaultDirectory(); getDefaultDirectory();
createDirs(); createDirs();
AssetCache.INSTANCE.init(); AssetCache.INSTANCE.init(overrideAssetArchive);
initialiseConfig(); initialiseConfig();
readConfigJson(); readConfigJson();

View File

@@ -19,20 +19,30 @@ import java.io.RandomAccessFile
*/ */
object AssetCache { object AssetCache {
private val archivePath = File("./assets.tevd") private val defaultArchivePath = File("./assets.tevd")
/** Whether we're running from a distribution archive */ /** Whether we're running from a distribution archive */
val isDistribution: Boolean get() = archivePath.exists() val isDistribution: Boolean get() = dom != null
private var dom: ClusteredFormatDOM? = null private var dom: ClusteredFormatDOM? = null
/** /**
* Open the archive on startup. Call early, after defaultDir is set. * Open the archive on startup. Call early, after defaultDir is set.
*
* @param overridePath When non-null, load this specific .tevd archive exclusively,
* ignoring both the default ./assets.tevd and the ./assets/ directory.
*/ */
fun init() { @JvmOverloads
if (isDistribution) { fun init(overridePath: String? = null) {
println("[AssetCache] Distribution mode: opening ${archivePath.path}") if (overridePath != null) {
dom = ClusteredFormatDOM(RandomAccessFile(archivePath, "r")) val f = File(overridePath)
if (!f.exists()) throw FileNotFoundException("Specified assets archive not found: $overridePath")
println("[AssetCache] Override archive: opening ${f.path}")
dom = ClusteredFormatDOM(RandomAccessFile(f, "r"))
println("[AssetCache] Override archive opened successfully")
} else if (defaultArchivePath.exists()) {
println("[AssetCache] Distribution mode: opening ${defaultArchivePath.path}")
dom = ClusteredFormatDOM(RandomAccessFile(defaultArchivePath, "r"))
println("[AssetCache] Archive opened successfully") println("[AssetCache] Archive opened successfully")
} else { } else {
println("[AssetCache] No archive found, using loose assets (development mode)") println("[AssetCache] No archive found, using loose assets (development mode)")
@@ -41,13 +51,12 @@ object AssetCache {
/** /**
* Get a Clustfile for a path relative to the assets root. * Get a Clustfile for a path relative to the assets root.
*
* Note: nothing guarantees the file's actual existence!
*/ */
fun getClustfile(relativePath: String): Clustfile { fun getClustfile(relativePath: String): Clustfile {
val path = if (relativePath.startsWith("/")) relativePath else "/$relativePath" val path = if (relativePath.startsWith("/")) relativePath else "/$relativePath"
return Clustfile(dom!!, path).let { return Clustfile(dom!!, path)
if (!it.exists()) throw FileNotFoundException("Clustfile not exists: /$relativePath")
else it
}
} }
/** /**

View File

@@ -1,11 +1,14 @@
package net.torvald.terrarum package net.torvald.terrarum
import com.badlogic.gdx.Files
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.GdxRuntimeException
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.Clustfile import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.Clustfile
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.ClustfileInputStream import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.ClustfileInputStream
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
/** /**
* A GDX FileHandle backed by a Clustfile from TerranVirtualDisk. * A GDX FileHandle backed by a Clustfile from TerranVirtualDisk.
* Allows transparent asset loading from .tevd archives using all standard GDX APIs. * Allows transparent asset loading from .tevd archives using all standard GDX APIs.
@@ -30,6 +33,10 @@ class ClustfileHandle(private val clustfile: Clustfile) : FileHandle() {
override fun path(): String = clustfile.path override fun path(): String = clustfile.path
init {
type = Files.FileType.Internal // just a dummy value
}
override fun extension(): String { override fun extension(): String {
val n = name() val n = name()
val dotIndex = n.lastIndexOf('.') val dotIndex = n.lastIndexOf('.')
@@ -62,5 +69,12 @@ class ClustfileHandle(private val clustfile: Clustfile) : FileHandle() {
return File(clustfile.path) return File(clustfile.path)
} }
override fun sibling(name: String?): FileHandle {
if (name == null) throw GdxRuntimeException("Sibling name must not be null.")
val parentPath = clustfile.parent ?: throw GdxRuntimeException("Cannot get the sibling of the root.")
val siblingPath = if (parentPath.endsWith("/")) "$parentPath$name" else "$parentPath/$name"
return ClustfileHandle(Clustfile(clustfile.DOM, siblingPath))
}
override fun toString(): String = clustfile.path override fun toString(): String = clustfile.path
} }

View File

@@ -340,9 +340,7 @@ object ModMgr {
} }
} }
catch (e: Throwable) { catch (e: Throwable) {
printdbgerr(this, "Module failed to load, skipping: $moduleName") printdbgerr(this, "Module failed to load, skipping: $moduleName\nstacktrace:\n${e.stackTraceToString()}")
printdbgerr(this, "\t$e")
print(App.csiR); e.printStackTrace(System.out); print(App.csi0)
logError(LoadErrorType.YOUR_FAULT, moduleName, e) logError(LoadErrorType.YOUR_FAULT, moduleName, e)
@@ -370,7 +368,7 @@ object ModMgr {
printmsg(this, "Module processed: $moduleName") printmsg(this, "Module processed: $moduleName")
} }
catch (noSuchModule: FileNotFoundException) { catch (noSuchModule: FileNotFoundException) {
printmsgerr(this, "No such module, skipping: $moduleName") printmsgerr(this, "No such module, skipping: $moduleName\nstacktrace:\n${noSuchModule.stackTraceToString()}")
logError(LoadErrorType.NOT_EVEN_THERE, moduleName, noSuchModule) logError(LoadErrorType.NOT_EVEN_THERE, moduleName, noSuchModule)
@@ -397,9 +395,7 @@ object ModMgr {
// TODO: Instead of skipping module with error, just display the error message onto the face? // TODO: Instead of skipping module with error, just display the error message onto the face?
printmsgerr(this, "There was an error while loading module $moduleName") printmsgerr(this, "There was an error while loading module $moduleName\nstacktrace:\n${e.stackTraceToString()}")
printmsgerr(this, "\t$e")
print(App.csiR); e.printStackTrace(System.out); print(App.csi0)
logError(LoadErrorType.YOUR_FAULT, moduleName, e) logError(LoadErrorType.YOUR_FAULT, moduleName, e)

View File

@@ -17,9 +17,9 @@ import java.util.*;
*/ */
public class Principii { public class Principii {
private static KVHashMap gameConfig = new KVHashMap(); private static final KVHashMap gameConfig = new KVHashMap();
private static String OSName = System.getProperty("os.name"); private static final String OSName = System.getProperty("os.name");
private static String operationSystem; private static String operationSystem;
/** %appdata%/Terrarum, without trailing slash */ /** %appdata%/Terrarum, without trailing slash */
@@ -27,27 +27,29 @@ public class Principii {
/** defaultDir + "/config.json" */ /** defaultDir + "/config.json" */
private static String configDir; private static String configDir;
private static final String GAME_NAME = TerrarumAppConfiguration.INSTANCE.getGAME_NAME_FOR_FILESYSTEM();
public static void getDefaultDirRoot() { public static void getDefaultDirRoot() {
String OS = OSName.toUpperCase(); String OS = OSName.toUpperCase();
if (OS.contains("WIN")) { if (OS.contains("WIN")) {
operationSystem = "WINDOWS"; operationSystem = "WINDOWS";
defaultDir = System.getenv("APPDATA") + "/Terrarum"; defaultDir = System.getenv("APPDATA") + "/" + GAME_NAME;
} }
else if (OS.contains("OS X") || OS.contains("MACOS")) { // OpenJDK for mac will still report "Mac OS X" with version number "10.16", even on Big Sur and beyond else if (OS.contains("OS X") || OS.contains("MACOS")) { // OpenJDK for mac will still report "Mac OS X" with version number "10.16", even on Big Sur and beyond
operationSystem = "OSX"; operationSystem = "OSX";
defaultDir = System.getProperty("user.home") + "/Library/Application Support/Terrarum"; defaultDir = System.getProperty("user.home") + "/Library/Application Support/" + GAME_NAME;
} }
else if (OS.contains("NUX") || OS.contains("NIX") || OS.contains("BSD")) { else if (OS.contains("NUX") || OS.contains("NIX") || OS.contains("BSD")) {
operationSystem = "LINUX"; operationSystem = "LINUX";
defaultDir = System.getProperty("user.home") + "/.Terrarum"; defaultDir = System.getProperty("user.home") + "/." + GAME_NAME;
} }
else if (OS.contains("SUNOS")) { else if (OS.contains("SUNOS")) {
operationSystem = "SOLARIS"; operationSystem = "SOLARIS";
defaultDir = System.getProperty("user.home") + "/.Terrarum"; defaultDir = System.getProperty("user.home") + "/." + GAME_NAME;
} }
else { else {
operationSystem = "UNKNOWN"; operationSystem = "UNKNOWN";
defaultDir = System.getProperty("user.home") + "/.Terrarum"; defaultDir = System.getProperty("user.home") + "/." + GAME_NAME;
} }
} }
@@ -135,8 +137,9 @@ public class Principii {
cmd0.add("-Xms1G"); cmd0.add("-Xms1G");
cmd0.add("-Xmx"+xmx+"G"); cmd0.add("-Xmx"+xmx+"G");
cmd0.add("-cp"); cmd0.add("-cp");
cmd0.add("./out/TerrarumBuild.jar"); cmd0.add("./out/" + TerrarumAppConfiguration.JAR_NAME);
cmd0.add(cp); cmd0.add(cp);
cmd0.addAll(List.of(args));
var cmd = cmd0.stream().filter((it) -> !it.isBlank()).toList(); var cmd = cmd0.stream().filter((it) -> !it.isBlank()).toList();
System.out.println(cmd); System.out.println(cmd);

View File

@@ -17,6 +17,8 @@ object TerrarumAppConfiguration {
// CONFIGURATION FOR THE APP ITSELF // // CONFIGURATION FOR THE APP ITSELF //
////////////////////////////////////// //////////////////////////////////////
const val GAME_NAME = "Terrarum" const val GAME_NAME = "Terrarum"
const val JAR_NAME = "TerrarumBuild.jar" // must match what's on the buildapp scripts
val GAME_NAME_FOR_FILESYSTEM = GAME_NAME.replace(' ', '_')
const val COPYRIGHT_DATE_NAME = "© 2013-2026 CuriousToꝛvald (minjaesong)" const val COPYRIGHT_DATE_NAME = "© 2013-2026 CuriousToꝛvald (minjaesong)"
val COPYRIGHT_LICENSE: String; get() = Lang["COPYRIGHT_GNU_GPL_3"] val COPYRIGHT_LICENSE: String; get() = Lang["COPYRIGHT_GNU_GPL_3"]
const val COPYRIGHT_LICENSE_ENGLISH = "Distributed under GNU GPL 3" const val COPYRIGHT_LICENSE_ENGLISH = "Distributed under GNU GPL 3"
@@ -38,7 +40,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
# Modules are loaded from top to bottom. # Modules are loaded from top to bottom.
# Name of the module corresponds with the name of the directory the module is stored in, # Name of the module corresponds with the name of the directory the module is stored in,
# typically under: # typically under:
# 1. assets/mods of the installation path (the modules comes with the release of the game) # 1. assets.tevd/mods of the installation path (the modules comes with the release of the game)
# 2. %APPDATA%/Modules (the modules installed by the user) # 2. %APPDATA%/Modules (the modules installed by the user)
# where %APPDATA% is: # where %APPDATA% is:
# Windows -- C:\Users\<username>\AppData\Roaming\Terrarum # Windows -- C:\Users\<username>\AppData\Roaming\Terrarum