From 6302f0402f5d0ab392d88059660c9763804f5e57 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sun, 9 Apr 2023 12:43:00 +0900 Subject: [PATCH] a tily updates to the savegame format handling -- read the SAVE_FORMAT.md --- .../serialise/QuickSaveThread.kt | 8 +-- .../torvald/terrarum/savegame/DiskSkimmer.kt | 51 +++++++++++-------- work_files/DataFormats/SAVE_FORMAT.md | 6 +++ 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/net/torvald/terrarum/modulebasegame/serialise/QuickSaveThread.kt b/src/net/torvald/terrarum/modulebasegame/serialise/QuickSaveThread.kt index be6a8e7d0..a4bb9e8de 100644 --- a/src/net/torvald/terrarum/modulebasegame/serialise/QuickSaveThread.kt +++ b/src/net/torvald/terrarum/modulebasegame/serialise/QuickSaveThread.kt @@ -94,7 +94,7 @@ class QuickSingleplayerWorldSavingThread( } val worldMeta = EntryFile(WriteWorld.encodeToByteArray64(ingame, time_t, actorsList, playersList)) val world = DiskEntry(SAVEGAMEINFO, ROOT, creation_t, time_t, worldMeta) - addFile(disk, world); skimmer.appendEntryOnly(world) + addFile(disk, world); skimmer.appendEntry(world) WriteSavegame.saveProgress += 1f @@ -117,7 +117,7 @@ class QuickSingleplayerWorldSavingThread( val entryContent = EntryFile(chunkBytes) val entry = DiskEntry(entryID, ROOT, creation_t, time_t, entryContent) // "W1L0-92,15" - addFile(disk, entry); skimmer.appendEntryOnly(entry) + addFile(disk, entry); skimmer.appendEntry(entry) WriteSavegame.saveProgress += chunkProgressMultiplier chunksWrote += 1 @@ -134,7 +134,7 @@ class QuickSingleplayerWorldSavingThread( val actorContent = EntryFile(WriteActor.encodeToByteArray64(it)) val actor = DiskEntry(it.referenceID.toLong(), ROOT, creation_t, time_t, actorContent) - addFile(disk, actor); skimmer.appendEntryOnly(actor) + addFile(disk, actor); skimmer.appendEntry(actor) WriteSavegame.saveProgress += actorProgressMultiplier } @@ -142,7 +142,7 @@ class QuickSingleplayerWorldSavingThread( disk.saveKind = VDSaveKind.WORLD_DATA - skimmer.rewriteDirectories() +// skimmer.rewriteDirectories() skimmer.injectDiskCRC(disk.hashCode()) skimmer.setSaveMode(1 + 2 * isAuto.toInt()) skimmer.setSaveKind(VDSaveKind.WORLD_DATA) diff --git a/src/net/torvald/terrarum/savegame/DiskSkimmer.kt b/src/net/torvald/terrarum/savegame/DiskSkimmer.kt index 050d6bc76..f64f94f69 100644 --- a/src/net/torvald/terrarum/savegame/DiskSkimmer.kt +++ b/src/net/torvald/terrarum/savegame/DiskSkimmer.kt @@ -168,11 +168,15 @@ removefile: skipRead(entrySize) // skips rest of the entry's actual contents if (typeFlag > 0) { + if (entryToOffsetTable[entryID] != null) + debugPrintln("[DiskSkimmer] ... overwriting the entry $entryID previously found at offset:${entryToOffsetTable[entryID]} with offset:$offset (name: ${diskIDtoReadableFilename(entryID, getSaveKind())})") + else + debugPrintln("[DiskSkimmer] ... successfully read the entry $entryID at offset:$offset (name: ${diskIDtoReadableFilename(entryID, getSaveKind())})") + entryToOffsetTable[entryID] = offset - debugPrintln("[DiskSkimmer] ... successfully read the entry $entryID at offset $offset (name: ${diskIDtoReadableFilename(entryID, getSaveKind())})") } else { - debugPrintln("[DiskSkimmer] ... discarding entry $entryID at offset $offset (name: ${diskIDtoReadableFilename(entryID, getSaveKind())})") + debugPrintln("[DiskSkimmer] ... discarding entry $entryID at offset:$offset (name: ${diskIDtoReadableFilename(entryID, getSaveKind())})") } } @@ -192,6 +196,8 @@ removefile: * @return DiskEntry if the entry exists on the disk, `null` otherwise. */ fun requestFile(entryID: EntryID): DiskEntry? { + if (entryID == 0L) throw UnsupportedOperationException("Peeking at the root entry is an undefined behaviour for the Terrarum Savegame format.") + if (!initialised) throw IllegalStateException("File entries not built! Initialise the Skimmer by executing rebuild()") entryToOffsetTable[entryID].let { offset -> @@ -323,9 +329,9 @@ removefile: fa.write(crc.toBigEndian()) } - private val modifiedDirectories = TreeSet() + //private val modifiedDirectories = TreeSet() - fun rewriteDirectories() { + /*fun rewriteDirectories() { modifiedDirectories.forEach { invalidateEntry(it.entryID) @@ -336,7 +342,7 @@ removefile: entryToOffsetTable[it.entryID] = appendAt + 8 it.serialize().forEach { fa.writeByte(it.toInt()) } } - } + }*/ fun setSaveMode(bits: Int) { fa.seek(49L) @@ -409,7 +415,7 @@ removefile: // THESE ARE METHODS TO SUPPORT ON-LINE MODIFICATION // /////////////////////////////////////////////////////// - fun appendEntryOnly(entry: DiskEntry) { + /*fun appendEntryOnly(entry: DiskEntry) { val parentDir = requestFile(entry.parentEntryID)!! val id = entry.entryID @@ -426,19 +432,19 @@ removefile: // append new file entryToOffsetTable[id] = appendAt + 8 entry.serialize().forEach { fa.writeByte(it.toInt()) } - } + }*/ fun appendEntry(entry: DiskEntry) { - val parentDir = requestFile(entry.parentEntryID)!! +// val parentDir = requestFile(entry.parentEntryID)!! val id = entry.entryID - val parent = entry.parentEntryID +// val parent = entry.parentEntryID // add the entry to its parent directory if there was none - val dirContent = (parentDir.contents as EntryDirectory) - if (!dirContent.contains(id)) dirContent.add(id) +// val dirContent = (parentDir.contents as EntryDirectory) +// if (!dirContent.contains(id)) dirContent.add(id) - invalidateEntry(parent) - invalidateEntry(id) +// invalidateEntry(parent) +// invalidateEntry(id) val appendAt = fa.length() fa.seek(appendAt) @@ -447,27 +453,28 @@ removefile: entryToOffsetTable[id] = appendAt + 8 entry.serialize().forEach { fa.writeByte(it.toInt()) } // append modified directory - entryToOffsetTable[parent] = fa.filePointer + 8 - parentDir.serialize().forEach { fa.writeByte(it.toInt()) } +// entryToOffsetTable[parent] = fa.filePointer + 8 +// parentDir.serialize().forEach { fa.writeByte(it.toInt()) } } fun deleteEntry(id: EntryID) { val entry = requestFile(id)!! - val parentDir = requestFile(entry.parentEntryID)!! - val parent = entry.parentEntryID +// val parentDir = requestFile(entry.parentEntryID)!! +// val parent = entry.parentEntryID - invalidateEntry(parent) +// invalidateEntry(parent) + invalidateEntry(id) // remove the entry - val dirContent = (parentDir.contents as EntryDirectory) - dirContent.remove(id) +// val dirContent = (parentDir.contents as EntryDirectory) +// dirContent.remove(id) val appendAt = fa.length() fa.seek(appendAt) // append modified directory - entryToOffsetTable[id] = appendAt + 8 - parentDir.serialize().forEach { fa.writeByte(it.toInt()) } +// entryToOffsetTable[id] = appendAt + 8 +// parentDir.serialize().forEach { fa.writeByte(it.toInt()) } } fun appendEntries(entries: List) = entries.forEach { appendEntry(it) } diff --git a/work_files/DataFormats/SAVE_FORMAT.md b/work_files/DataFormats/SAVE_FORMAT.md index a89c854a7..f9c8d8b79 100644 --- a/work_files/DataFormats/SAVE_FORMAT.md +++ b/work_files/DataFormats/SAVE_FORMAT.md @@ -80,3 +80,9 @@ Making `inventory` transient is impossible as it would render Storage Chests unu 1. Allow multiple players share the same world 2. Make multiplayer possible 3. Make Players distributable (like VRChat avatars) + +## Quirks with Terrarum's custom TVDA implementation + +- Subdirectory is not allowed -- every file must be on the root directory +- The entry for the root directory is there (only to satisfy the format constraints), but it's basically meaningless -- contents of the entry are undefined +- Programmers are encouraged to scan the entire disk to get the file listings