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

@@ -342,6 +342,14 @@ public class App implements ApplicationListener {
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() {
return currentScreen;
}
@@ -381,6 +389,17 @@ public class App implements ApplicationListener {
}
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();
updateBogoflops(100_000_000L);
@@ -441,7 +460,7 @@ public class App implements ApplicationListener {
// load configs
getDefaultDirectory();
createDirs();
AssetCache.INSTANCE.init();
AssetCache.INSTANCE.init(overrideAssetArchive);
initialiseConfig();
readConfigJson();

View File

@@ -19,20 +19,30 @@ import java.io.RandomAccessFile
*/
object AssetCache {
private val archivePath = File("./assets.tevd")
private val defaultArchivePath = File("./assets.tevd")
/** 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
/**
* 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() {
if (isDistribution) {
println("[AssetCache] Distribution mode: opening ${archivePath.path}")
dom = ClusteredFormatDOM(RandomAccessFile(archivePath, "r"))
@JvmOverloads
fun init(overridePath: String? = null) {
if (overridePath != null) {
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")
} else {
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.
*
* Note: nothing guarantees the file's actual existence!
*/
fun getClustfile(relativePath: String): Clustfile {
val path = if (relativePath.startsWith("/")) relativePath else "/$relativePath"
return Clustfile(dom!!, path).let {
if (!it.exists()) throw FileNotFoundException("Clustfile not exists: /$relativePath")
else it
}
return Clustfile(dom!!, path)
}
/**

View File

@@ -1,11 +1,14 @@
package net.torvald.terrarum
import com.badlogic.gdx.Files
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.ClustfileInputStream
import java.io.File
import java.io.InputStream
/**
* A GDX FileHandle backed by a Clustfile from TerranVirtualDisk.
* 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
init {
type = Files.FileType.Internal // just a dummy value
}
override fun extension(): String {
val n = name()
val dotIndex = n.lastIndexOf('.')
@@ -62,5 +69,12 @@ class ClustfileHandle(private val clustfile: Clustfile) : FileHandle() {
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
}

View File

@@ -340,9 +340,7 @@ object ModMgr {
}
}
catch (e: Throwable) {
printdbgerr(this, "Module failed to load, skipping: $moduleName")
printdbgerr(this, "\t$e")
print(App.csiR); e.printStackTrace(System.out); print(App.csi0)
printdbgerr(this, "Module failed to load, skipping: $moduleName\nstacktrace:\n${e.stackTraceToString()}")
logError(LoadErrorType.YOUR_FAULT, moduleName, e)
@@ -370,7 +368,7 @@ object ModMgr {
printmsg(this, "Module processed: $moduleName")
}
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)
@@ -397,9 +395,7 @@ object ModMgr {
// 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, "\t$e")
print(App.csiR); e.printStackTrace(System.out); print(App.csi0)
printmsgerr(this, "There was an error while loading module $moduleName\nstacktrace:\n${e.stackTraceToString()}")
logError(LoadErrorType.YOUR_FAULT, moduleName, e)

View File

@@ -17,9 +17,9 @@ import java.util.*;
*/
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;
/** %appdata%/Terrarum, without trailing slash */
@@ -27,27 +27,29 @@ public class Principii {
/** defaultDir + "/config.json" */
private static String configDir;
private static final String GAME_NAME = TerrarumAppConfiguration.INSTANCE.getGAME_NAME_FOR_FILESYSTEM();
public static void getDefaultDirRoot() {
String OS = OSName.toUpperCase();
if (OS.contains("WIN")) {
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
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")) {
operationSystem = "LINUX";
defaultDir = System.getProperty("user.home") + "/.Terrarum";
defaultDir = System.getProperty("user.home") + "/." + GAME_NAME;
}
else if (OS.contains("SUNOS")) {
operationSystem = "SOLARIS";
defaultDir = System.getProperty("user.home") + "/.Terrarum";
defaultDir = System.getProperty("user.home") + "/." + GAME_NAME;
}
else {
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("-Xmx"+xmx+"G");
cmd0.add("-cp");
cmd0.add("./out/TerrarumBuild.jar");
cmd0.add("./out/" + TerrarumAppConfiguration.JAR_NAME);
cmd0.add(cp);
cmd0.addAll(List.of(args));
var cmd = cmd0.stream().filter((it) -> !it.isBlank()).toList();
System.out.println(cmd);

View File

@@ -17,6 +17,8 @@ object TerrarumAppConfiguration {
// CONFIGURATION FOR THE APP ITSELF //
//////////////////////////////////////
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)"
val COPYRIGHT_LICENSE: String; get() = Lang["COPYRIGHT_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.
# Name of the module corresponds with the name of the directory the module is stored in,
# 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)
# where %APPDATA% is:
# Windows -- C:\Users\<username>\AppData\Roaming\Terrarum