diff --git a/SAVE_FORMAT.md b/SAVE_FORMAT.md index 5e133e465..324b4259c 100644 --- a/SAVE_FORMAT.md +++ b/SAVE_FORMAT.md @@ -1,74 +1,31 @@ -## Format ## +## Introduction ## -Contain everything on [TEVD](github.com/minjaesong/TerranVirtualDisk) +On the main game, any player can access any generated worlds, and thus players data and worlds are saved separately. -* Save meta - - binary - - Filename : world (with no extension) +The main game directory is composed of following directories: - |Type |Mnemonic |Description | - |----------|-----------|-----------------------------| - |Byte[4] |TESV |Magic | - |Byte[n] |name |Savegame name, UTF-8 | - |Byte |NULL |String terminator | - |Byte[8] |terraseed |Terrain seed | - |Byte[8] |rogueseed |Randomiser seed | - |Byte[4] |crc1 |CRC-32 of worldinfo1 entry | - |Byte[4] |crc2 |CRC-32 of worldinfo2 entry | - |Byte[4] |crc3 |CRC-32 of worldinfo3 entry | - |Byte[4] |crc4 |CRC-32 of worldinfo4 entry | - |Byte[32] |hash4321 |SHA-256 of crc4..crc3..crc2..crc1| - |Int |refid |Reference ID of the player | - |Long |time_t |Current world's time_t | - |Byte[6] |t_create |Creation time of the savefile in time_t| - |Byte[6] |t_lastacc |Last play time in time_t | - |Int |t_wasted |Total playtime in time_t | - - Endianness: Big - - each entry on the disk contains CRC of its data, we can compare CRC saved in meta && CRC of entry header && CRC of actual content +``` +.Terrarum ++ Players + - , JSON.gz ++ Shared + - , TEVD { * } + - ++ Worlds + - , TVDA { WriteWorld, actors JSON, chunk data, screenshot.tga.gz taken by the last player } +``` -* Actor/Faction data - - GSON - - Filename : (refid) (with no extension) +(TEVD stands for Terrarum Virtual Disk spec version 3, TVDA stands for spec version 254; both have MAGIC header of `TEVd`) +## Prerequisites ## -* Prop data - - CSV - - Filename : (with no extension) - worldinfo2 -- tileprop - worldinfo3 -- itemprop - worldinfo4 -- materialprop - worldinfo5 -- modules loadorder +1. Player ID must not be strictly 9545698 (0x91A7E2) + 1. Use classname `net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer` to check +2. Each world has to be uniquely identifiable + 1. Use GUID, World Name, etc. +3. `ActorNowPlaying` must be drawn on top of other actors of same RenderOrder +## Goals ## -* Human-readable - - Tiles_list.txt -- list of tiles in csv - - Items_list.txt -- list of items in csv - - Materials_list.txt -- list of materials in csv - - load_order.txt -- module load order - - - -## How it works ## -* If hash discrepancy has detected, (hash of csv in save dir != stored hash || hash of TEMD != stored hash), printout "Save file corrupted. Continue?" with prompt "Yes/No" - -Directory: - - +--- - --- 2a93bc5f (item ID) Actor/DynamicItem/Faction/etc. data (JSON) - --- 423bdc83 (item ID) Actor/DynamicItem/Faction/etc. data (JSON) - --- Items_list.txt Human-readable - --- Materials_list.txt Human-readable - --- Tiles_list.txt Human-readable - --- world save meta (binary) - --- worldinfo1 TEMD (binary) - --- worldinfo2 tileprop (CSV) - --- worldinfo3 itemprop (CSV) - --- worldinfo4 materialprop (CSV) - +--- computers - --- (UUID) virtual disk - +--- tapestries - --- (random Int) tapestry - -Alongside with save1.tevd (extension should not exist in real game), keep save1.backup.tevd as a last-working save. \ No newline at end of file +1. Allows multiple players share same world +2. Makes multiplayer possible diff --git a/src/net/torvald/terrarum/App.java b/src/net/torvald/terrarum/App.java index 03abfca2b..134207072 100644 --- a/src/net/torvald/terrarum/App.java +++ b/src/net/torvald/terrarum/App.java @@ -929,8 +929,14 @@ public class App implements ApplicationListener { public static String operationSystem; /** %appdata%/Terrarum, without trailing slash */ public static String defaultDir; - /** defaultDir + "/Saves", without trailing slash */ - public static String defaultSaveDir; + /** For Demo version only. defaultDir + "/Saves", without trailing slash */ + public static String saveDir; + /** For shared materials (e.g. image of a computer disk). defaultDir + "/Shared", without trailing slash */ + public static String saveSharedDir; + /** For the main game where any players can access any world (unless flagged as private). defaultDir + "/Players", without trailing slash */ + public static String playersDir; + /** For the main game. defaultDir + "/Worlds", without trailing slash */ + public static String worldsDir; /** defaultDir + "/config.json" */ public static String configDir; public static RunningEnvironment environment; @@ -958,8 +964,10 @@ public class App implements ApplicationListener { defaultDir = System.getProperty("user.home") + "/.Terrarum"; } -// defaultSaveDir = defaultDir + "/Players"; // as per the save format in the game's planning - defaultSaveDir = defaultDir + "/Saves"; // for the demo release + saveDir = defaultDir + "/Saves"; // for the demo release + saveSharedDir = defaultDir + "/Shared"; + playersDir = defaultDir + "/Players"; + worldsDir = defaultDir + "/Worlds"; configDir = defaultDir + "/config.json"; System.out.println(String.format("os.name = %s (with identifier %s)", OSName, operationSystem)); @@ -969,7 +977,7 @@ public class App implements ApplicationListener { } private static void createDirs() { - File[] dirs = {new File(defaultSaveDir)}; + File[] dirs = {new File(saveDir)}; for (File it : dirs) { if (!it.exists()) diff --git a/src/net/torvald/terrarum/IngameInstance.kt b/src/net/torvald/terrarum/IngameInstance.kt index 8cff0cbed..6e68ed833 100644 --- a/src/net/torvald/terrarum/IngameInstance.kt +++ b/src/net/torvald/terrarum/IngameInstance.kt @@ -368,23 +368,23 @@ open class IngameInstance(val batch: SpriteBatch) : Screen { fun makeSavegameBackupCopy() { try { // do not overwrite clean .2 with dirty .1 - val file2 = File(App.defaultSaveDir, INGAME.savegameNickname+".3") - val file1 = File(App.defaultSaveDir, INGAME.savegameNickname+".2") + val file2 = File(App.saveDir, INGAME.savegameNickname + ".3") + val file1 = File(App.saveDir, INGAME.savegameNickname + ".2") val flags2 = FileInputStream(file2).let { it.skip(49L); val r = it.read(); it.close(); r } val flags1 = FileInputStream(file1).let { it.skip(49L); val r = it.read(); it.close(); r } if (!(flags2 == 0 && flags1 != 0) || !file2.exists()) file1.copyTo(file2, true) } catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {} try { // do not overwrite clean .2 with dirty .1 - val file2 = File(App.defaultSaveDir, INGAME.savegameNickname+".2") - val file1 = File(App.defaultSaveDir, INGAME.savegameNickname+".1") + val file2 = File(App.saveDir, INGAME.savegameNickname + ".2") + val file1 = File(App.saveDir, INGAME.savegameNickname + ".1") val flags2 = FileInputStream(file2).let { it.skip(49L); val r = it.read(); it.close(); r } val flags1 = FileInputStream(file1).let { it.skip(49L); val r = it.read(); it.close(); r } if (!(flags2 == 0 && flags1 != 0) || !file2.exists()) file1.copyTo(file2, true) } catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {} try { - File(App.defaultSaveDir, INGAME.savegameNickname).copyTo( - File(App.defaultSaveDir, INGAME.savegameNickname+".1"), // don't use .bak as it's used by the savecracker + File(App.saveDir, INGAME.savegameNickname).copyTo( + File(App.saveDir, INGAME.savegameNickname + ".1"), // don't use .bak as it's used by the savecracker true ) } catch (e: NoSuchFileException) {} @@ -392,7 +392,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen { - fun getSaveFileMain() = File(App.defaultSaveDir, savegameNickname) + fun getSaveFileMain() = File(App.saveDir, savegameNickname) diff --git a/src/net/torvald/terrarum/Terrarum.kt b/src/net/torvald/terrarum/Terrarum.kt index 0f31dd8bf..c9f6a0c47 100644 --- a/src/net/torvald/terrarum/Terrarum.kt +++ b/src/net/torvald/terrarum/Terrarum.kt @@ -36,7 +36,6 @@ import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import net.torvald.util.CircularArray import java.io.File import java.io.PrintStream -import java.util.logging.Level import kotlin.math.absoluteValue import kotlin.math.round @@ -244,7 +243,7 @@ object Terrarum : Disposable { val currentSaveDir: File get() { - val file = File(defaultSaveDir + "/test") + val file = File(saveDir + "/test") // failsafe? if (!file.exists()) file.mkdir() @@ -683,7 +682,7 @@ class Codex : KVHashMap() { fun AppUpdateListOfSavegames() { App.savegames.clear() - File(App.defaultSaveDir).listFiles().filter { !it.isDirectory && !it.name.contains('.') }.map { file -> + File(App.saveDir).listFiles().filter { !it.isDirectory && !it.name.contains('.') }.map { file -> try { DiskSkimmer(file, Common.CHARSET, true) } diff --git a/src/net/torvald/terrarum/modulebasegame/console/Save.kt b/src/net/torvald/terrarum/modulebasegame/console/Save.kt index 6ad2f8007..ffe0f0d17 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/Save.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/Save.kt @@ -22,7 +22,7 @@ object Save : ConsoleCommand { val ingame = Terrarum.ingame!! as TerrarumIngame val savename = args[1].trim() val disk = VDUtil.createNewDisk(1L shl 60, savename, Common.CHARSET) - val file = File(App.defaultSaveDir + "/${args[1]}") + val file = File(App.saveDir + "/${args[1]}") WriteSavegame(disk, file, ingame, false) diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryEscMenu.kt b/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryEscMenu.kt index 1645c504d..8bea2dae8 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryEscMenu.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryEscMenu.kt @@ -89,7 +89,7 @@ class UIInventoryEscMenu(val full: UIInventoryFull) : UICanvas() { INGAME.makeSavegameBackupCopy() // save the game - WriteSavegame(INGAME.savegameArchive, File(App.defaultSaveDir, INGAME.savegameNickname), Terrarum.ingame!! as TerrarumIngame, false) { + WriteSavegame(INGAME.savegameArchive, File(App.saveDir, INGAME.savegameNickname), Terrarum.ingame!! as TerrarumIngame, false) { // callback: System.gc() screen = 0