a tily updates to the savegame format handling -- read the SAVE_FORMAT.md

This commit is contained in:
minjaesong
2023-04-09 12:43:00 +09:00
parent 11a319788a
commit 6302f0402f
3 changed files with 39 additions and 26 deletions

View File

@@ -94,7 +94,7 @@ class QuickSingleplayerWorldSavingThread(
} }
val worldMeta = EntryFile(WriteWorld.encodeToByteArray64(ingame, time_t, actorsList, playersList)) val worldMeta = EntryFile(WriteWorld.encodeToByteArray64(ingame, time_t, actorsList, playersList))
val world = DiskEntry(SAVEGAMEINFO, ROOT, creation_t, time_t, worldMeta) 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 WriteSavegame.saveProgress += 1f
@@ -117,7 +117,7 @@ class QuickSingleplayerWorldSavingThread(
val entryContent = EntryFile(chunkBytes) val entryContent = EntryFile(chunkBytes)
val entry = DiskEntry(entryID, ROOT, creation_t, time_t, entryContent) val entry = DiskEntry(entryID, ROOT, creation_t, time_t, entryContent)
// "W1L0-92,15" // "W1L0-92,15"
addFile(disk, entry); skimmer.appendEntryOnly(entry) addFile(disk, entry); skimmer.appendEntry(entry)
WriteSavegame.saveProgress += chunkProgressMultiplier WriteSavegame.saveProgress += chunkProgressMultiplier
chunksWrote += 1 chunksWrote += 1
@@ -134,7 +134,7 @@ class QuickSingleplayerWorldSavingThread(
val actorContent = EntryFile(WriteActor.encodeToByteArray64(it)) val actorContent = EntryFile(WriteActor.encodeToByteArray64(it))
val actor = DiskEntry(it.referenceID.toLong(), ROOT, creation_t, time_t, actorContent) 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 WriteSavegame.saveProgress += actorProgressMultiplier
} }
@@ -142,7 +142,7 @@ class QuickSingleplayerWorldSavingThread(
disk.saveKind = VDSaveKind.WORLD_DATA disk.saveKind = VDSaveKind.WORLD_DATA
skimmer.rewriteDirectories() // skimmer.rewriteDirectories()
skimmer.injectDiskCRC(disk.hashCode()) skimmer.injectDiskCRC(disk.hashCode())
skimmer.setSaveMode(1 + 2 * isAuto.toInt()) skimmer.setSaveMode(1 + 2 * isAuto.toInt())
skimmer.setSaveKind(VDSaveKind.WORLD_DATA) skimmer.setSaveKind(VDSaveKind.WORLD_DATA)

View File

@@ -168,11 +168,15 @@ removefile:
skipRead(entrySize) // skips rest of the entry's actual contents skipRead(entrySize) // skips rest of the entry's actual contents
if (typeFlag > 0) { 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 entryToOffsetTable[entryID] = offset
debugPrintln("[DiskSkimmer] ... successfully read the entry $entryID at offset $offset (name: ${diskIDtoReadableFilename(entryID, getSaveKind())})")
} }
else { 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. * @return DiskEntry if the entry exists on the disk, `null` otherwise.
*/ */
fun requestFile(entryID: EntryID): DiskEntry? { 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()") if (!initialised) throw IllegalStateException("File entries not built! Initialise the Skimmer by executing rebuild()")
entryToOffsetTable[entryID].let { offset -> entryToOffsetTable[entryID].let { offset ->
@@ -323,9 +329,9 @@ removefile:
fa.write(crc.toBigEndian()) fa.write(crc.toBigEndian())
} }
private val modifiedDirectories = TreeSet<DiskEntry>() //private val modifiedDirectories = TreeSet<DiskEntry>()
fun rewriteDirectories() { /*fun rewriteDirectories() {
modifiedDirectories.forEach { modifiedDirectories.forEach {
invalidateEntry(it.entryID) invalidateEntry(it.entryID)
@@ -336,7 +342,7 @@ removefile:
entryToOffsetTable[it.entryID] = appendAt + 8 entryToOffsetTable[it.entryID] = appendAt + 8
it.serialize().forEach { fa.writeByte(it.toInt()) } it.serialize().forEach { fa.writeByte(it.toInt()) }
} }
} }*/
fun setSaveMode(bits: Int) { fun setSaveMode(bits: Int) {
fa.seek(49L) fa.seek(49L)
@@ -409,7 +415,7 @@ removefile:
// THESE ARE METHODS TO SUPPORT ON-LINE MODIFICATION // // THESE ARE METHODS TO SUPPORT ON-LINE MODIFICATION //
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
fun appendEntryOnly(entry: DiskEntry) { /*fun appendEntryOnly(entry: DiskEntry) {
val parentDir = requestFile(entry.parentEntryID)!! val parentDir = requestFile(entry.parentEntryID)!!
val id = entry.entryID val id = entry.entryID
@@ -426,19 +432,19 @@ removefile:
// append new file // append new file
entryToOffsetTable[id] = appendAt + 8 entryToOffsetTable[id] = appendAt + 8
entry.serialize().forEach { fa.writeByte(it.toInt()) } entry.serialize().forEach { fa.writeByte(it.toInt()) }
} }*/
fun appendEntry(entry: DiskEntry) { fun appendEntry(entry: DiskEntry) {
val parentDir = requestFile(entry.parentEntryID)!! // val parentDir = requestFile(entry.parentEntryID)!!
val id = entry.entryID val id = entry.entryID
val parent = entry.parentEntryID // val parent = entry.parentEntryID
// add the entry to its parent directory if there was none // add the entry to its parent directory if there was none
val dirContent = (parentDir.contents as EntryDirectory) // val dirContent = (parentDir.contents as EntryDirectory)
if (!dirContent.contains(id)) dirContent.add(id) // if (!dirContent.contains(id)) dirContent.add(id)
invalidateEntry(parent) // invalidateEntry(parent)
invalidateEntry(id) // invalidateEntry(id)
val appendAt = fa.length() val appendAt = fa.length()
fa.seek(appendAt) fa.seek(appendAt)
@@ -447,27 +453,28 @@ removefile:
entryToOffsetTable[id] = appendAt + 8 entryToOffsetTable[id] = appendAt + 8
entry.serialize().forEach { fa.writeByte(it.toInt()) } entry.serialize().forEach { fa.writeByte(it.toInt()) }
// append modified directory // append modified directory
entryToOffsetTable[parent] = fa.filePointer + 8 // entryToOffsetTable[parent] = fa.filePointer + 8
parentDir.serialize().forEach { fa.writeByte(it.toInt()) } // parentDir.serialize().forEach { fa.writeByte(it.toInt()) }
} }
fun deleteEntry(id: EntryID) { fun deleteEntry(id: EntryID) {
val entry = requestFile(id)!! val entry = requestFile(id)!!
val parentDir = requestFile(entry.parentEntryID)!! // val parentDir = requestFile(entry.parentEntryID)!!
val parent = entry.parentEntryID // val parent = entry.parentEntryID
invalidateEntry(parent) // invalidateEntry(parent)
invalidateEntry(id)
// remove the entry // remove the entry
val dirContent = (parentDir.contents as EntryDirectory) // val dirContent = (parentDir.contents as EntryDirectory)
dirContent.remove(id) // dirContent.remove(id)
val appendAt = fa.length() val appendAt = fa.length()
fa.seek(appendAt) fa.seek(appendAt)
// append modified directory // append modified directory
entryToOffsetTable[id] = appendAt + 8 // entryToOffsetTable[id] = appendAt + 8
parentDir.serialize().forEach { fa.writeByte(it.toInt()) } // parentDir.serialize().forEach { fa.writeByte(it.toInt()) }
} }
fun appendEntries(entries: List<DiskEntry>) = entries.forEach { appendEntry(it) } fun appendEntries(entries: List<DiskEntry>) = entries.forEach { appendEntry(it) }

View File

@@ -80,3 +80,9 @@ Making `inventory` transient is impossible as it would render Storage Chests unu
1. Allow multiple players share the same world 1. Allow multiple players share the same world
2. Make multiplayer possible 2. Make multiplayer possible
3. Make Players distributable (like VRChat avatars) 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