mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-10 18:44:05 +09:00
GameWorld: adding "worldIndex"; more save/load stuffs
This commit is contained in:
@@ -11,6 +11,10 @@ import java.util.concurrent.locks.Lock
|
|||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import javax.swing.JOptionPane
|
import javax.swing.JOptionPane
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Although the game (as product) can have infinitely many stages/planets/etc., those stages must be manually managed by YOU;
|
||||||
|
* this instance only stores the stage that is currently being used.
|
||||||
|
*/
|
||||||
open class IngameInstance(val batch: SpriteBatch) : Screen {
|
open class IngameInstance(val batch: SpriteBatch) : Screen {
|
||||||
|
|
||||||
var screenZoom = 1.0f
|
var screenZoom = 1.0f
|
||||||
@@ -20,6 +24,8 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
|
|||||||
open lateinit var consoleHandler: ConsoleWindow
|
open lateinit var consoleHandler: ConsoleWindow
|
||||||
|
|
||||||
open lateinit var world: GameWorld
|
open lateinit var world: GameWorld
|
||||||
|
/** how many different planets/stages/etc. are thenre. Whole stages must be manually managed by YOU. */
|
||||||
|
var gameworldCount = 0
|
||||||
/** The actor the game is currently allowing you to control.
|
/** The actor the game is currently allowing you to control.
|
||||||
*
|
*
|
||||||
* Most of the time it'd be the "player", but think about the case where you have possessed
|
* Most of the time it'd be the "player", but think about the case where you have possessed
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import org.dyn4j.geometry.Vector2
|
|||||||
typealias BlockAddress = Long
|
typealias BlockAddress = Long
|
||||||
typealias BlockDamage = Float
|
typealias BlockDamage = Float
|
||||||
|
|
||||||
open class GameWorld(val width: Int, val height: Int) {
|
open class GameWorld(var worldIndex: Int, val width: Int, val height: Int) {
|
||||||
|
|
||||||
|
|
||||||
//layers
|
//layers
|
||||||
|
|||||||
@@ -231,7 +231,8 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
|||||||
|
|
||||||
|
|
||||||
// init map as chosen size
|
// init map as chosen size
|
||||||
gameworld = GameWorldExtension(worldParams.width, worldParams.height)
|
gameworld = GameWorldExtension(1, worldParams.width, worldParams.height)
|
||||||
|
gameworldCount++
|
||||||
world = gameworld as GameWorld
|
world = gameworld as GameWorld
|
||||||
|
|
||||||
// generate terrain for the map
|
// generate terrain for the map
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import kotlin.properties.Delegates
|
|||||||
/**
|
/**
|
||||||
* Created by minjaesong on 2018-07-03.
|
* Created by minjaesong on 2018-07-03.
|
||||||
*/
|
*/
|
||||||
class GameWorldExtension(width: Int, height: Int): GameWorld(width, height) {
|
class GameWorldExtension(worldIndex: Int, width: Int, height: Int): GameWorld(worldIndex, width, height) {
|
||||||
|
|
||||||
val time: WorldTime
|
val time: WorldTime
|
||||||
val economy = GameEconomy()
|
val economy = GameEconomy()
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
|
package net.torvald.terrarum.modulecomputers.virtualcomputer.tvd
|
||||||
|
|
||||||
|
import net.torvald.terrarum.serialise.toLittleInt
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates entry-to-offset tables to allow streaming from the disk, without storing whole VD file to the memory.
|
* Creates entry-to-offset tables to allow streaming from the disk, without storing whole VD file to the memory.
|
||||||
*
|
*
|
||||||
* Created by minjaesong on 2017-11-17.
|
* Created by minjaesong on 2017-11-17.
|
||||||
*/
|
*/
|
||||||
class DiskSkimmer(diskFile: File) {
|
class DiskSkimmer(private val diskFile: File) {
|
||||||
|
|
||||||
class EntryOffsetPair(val entryID: Int, val offset: Long)
|
|
||||||
|
|
||||||
val entryToOffsetTable = ArrayList<EntryOffsetPair>()
|
/**
|
||||||
|
* EntryID to Offset.
|
||||||
|
*
|
||||||
|
* Offset is where the header begins, so first 4 bytes are exactly the same as the EntryID.
|
||||||
|
*/
|
||||||
|
val entryToOffsetTable = HashMap<EntryID, Long>()
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -64,7 +70,7 @@ class DiskSkimmer(diskFile: File) {
|
|||||||
|
|
||||||
|
|
||||||
// fill up table
|
// fill up table
|
||||||
entryToOffsetTable.add(EntryOffsetPair(entryID, currentPosition))
|
entryToOffsetTable[entryID] = currentPosition
|
||||||
|
|
||||||
skipRead(4) // skip entryID of parent
|
skipRead(4) // skip entryID of parent
|
||||||
|
|
||||||
@@ -88,15 +94,109 @@ class DiskSkimmer(diskFile: File) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using entryToOffsetTable, composes DiskEntry on the fly upon request.
|
||||||
|
* @return DiskEntry if the entry exists on the disk, `null` otherwise.
|
||||||
|
*/
|
||||||
|
fun requestFile(entryID: EntryID): DiskEntry? {
|
||||||
|
// FIXME untested
|
||||||
|
entryToOffsetTable[entryID].let { offset ->
|
||||||
|
if (offset == null)
|
||||||
|
return null
|
||||||
|
else {
|
||||||
|
val fis = FileInputStream(diskFile)
|
||||||
|
fis.skip(offset + 4) // get to the EntryHeader's parent directory area
|
||||||
|
val parent = fis.read(4).toLittleInt()
|
||||||
|
val fileFlag = fis.read(1)[0]
|
||||||
|
val filename = fis.read(256)
|
||||||
|
val creationTime = fis.read(6).toInt48()
|
||||||
|
val modifyTime = fis.read(6).toInt48()
|
||||||
|
val skip_crc = fis.read(4)
|
||||||
|
|
||||||
|
// get entry size // TODO future me, does this kind of comment helpful or redundant?
|
||||||
|
val entrySize = when (fileFlag) {
|
||||||
|
DiskEntry.NORMAL_FILE -> {
|
||||||
|
fis.read(6).toInt48()
|
||||||
|
}
|
||||||
|
DiskEntry.DIRECTORY -> {
|
||||||
|
fis.read(2).toUint16().toLong()
|
||||||
|
}
|
||||||
|
DiskEntry.SYMLINK -> 4L
|
||||||
|
else -> throw UnsupportedOperationException("Unsupported entry type: $fileFlag") // FIXME no support for compressed file
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val entryContent = when (fileFlag) {
|
||||||
|
DiskEntry.NORMAL_FILE -> {
|
||||||
|
val byteArray = ByteArray64(entrySize)
|
||||||
|
// read one byte at a time
|
||||||
|
for (c in 0L until entrySize) {
|
||||||
|
byteArray[c] = fis.read().toByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryFile(byteArray)
|
||||||
|
}
|
||||||
|
DiskEntry.DIRECTORY -> {
|
||||||
|
val dirContents = ArrayList<EntryID>()
|
||||||
|
// read 4 bytes at a time
|
||||||
|
val bytesBuffer4 = ByteArray(4)
|
||||||
|
for (c in 0L until entrySize) {
|
||||||
|
fis.read(bytesBuffer4)
|
||||||
|
dirContents.add(bytesBuffer4.toIntBig())
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryDirectory(dirContents)
|
||||||
|
}
|
||||||
|
DiskEntry.SYMLINK -> {
|
||||||
|
val target = fis.read(4).toIntBig()
|
||||||
|
|
||||||
|
EntrySymlink(target)
|
||||||
|
}
|
||||||
|
else -> throw UnsupportedOperationException("Unsupported entry type: $fileFlag") // FIXME no support for compressed file
|
||||||
|
}
|
||||||
|
|
||||||
|
return DiskEntry(entryID, parent, filename, creationTime, modifyTime, entryContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun InputStream.read(size: Int): ByteArray {
|
||||||
|
val ba = ByteArray(size)
|
||||||
|
this.read(ba)
|
||||||
|
return ba
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.toUint16(): Int {
|
||||||
|
return this[0].toUint().shl(8) or
|
||||||
|
this[1].toUint()
|
||||||
|
}
|
||||||
|
|
||||||
private fun ByteArray.toIntBig(): Int {
|
private fun ByteArray.toIntBig(): Int {
|
||||||
return this[0].toUint().shl(24) or this[1].toUint().shl(16) or
|
return this[0].toUint().shl(24) or
|
||||||
this[2].toUint().shl(8) or this[2].toUint()
|
this[1].toUint().shl(16) or
|
||||||
|
this[2].toUint().shl(8) or
|
||||||
|
this[3].toUint()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ByteArray.toInt48(): Long {
|
private fun ByteArray.toInt48(): Long {
|
||||||
return this[0].toUlong().shl(56) or this[1].toUlong().shl(48) or
|
return this[0].toUlong().shl(40) or
|
||||||
this[2].toUlong().shl(40) or this[3].toUlong().shl(32) or
|
this[1].toUlong().shl(32) or
|
||||||
this[4].toUlong().shl(24) or this[5].toUlong().shl(16) or
|
this[2].toUlong().shl(24) or
|
||||||
this[6].toUlong().shl(8) or this[7].toUlong()
|
this[3].toUlong().shl(16) or
|
||||||
|
this[4].toUlong().shl(8) or
|
||||||
|
this[5].toUlong()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.toInt64(): Long {
|
||||||
|
return this[0].toUlong().shl(56) or
|
||||||
|
this[1].toUlong().shl(48) or
|
||||||
|
this[2].toUlong().shl(40) or
|
||||||
|
this[3].toUlong().shl(32) or
|
||||||
|
this[4].toUlong().shl(24) or
|
||||||
|
this[5].toUlong().shl(16) or
|
||||||
|
this[6].toUlong().shl(8) or
|
||||||
|
this[7].toUlong()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
# Terran Virtual Disk Image Format Specification
|
||||||
|
|
||||||
|
current specversion number: 0x03
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### 0x03
|
||||||
|
- Option to compress file entry
|
||||||
|
|
||||||
|
### 0x02
|
||||||
|
- 48-Bit filesize and timestamp (Max 256 TiB / 8.9 million years)
|
||||||
|
- 8 Reserved footer
|
||||||
|
|
||||||
|
### 0x01
|
||||||
|
**Note: this version were never released in public**
|
||||||
|
- Doubly Linked List instead of Singly
|
||||||
|
|
||||||
|
|
||||||
|
## Specs
|
||||||
|
|
||||||
|
* File structure
|
||||||
|
|
||||||
|
|
||||||
|
Header
|
||||||
|
|
||||||
|
IndexNumber
|
||||||
|
<entry>
|
||||||
|
|
||||||
|
IndexNumber
|
||||||
|
<entry>
|
||||||
|
|
||||||
|
IndexNumber
|
||||||
|
<entry>
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
Footer
|
||||||
|
|
||||||
|
|
||||||
|
* Order of the indices does not matter. Actual sorting is a job of the application.
|
||||||
|
* Endianness: Big
|
||||||
|
|
||||||
|
|
||||||
|
## Header
|
||||||
|
Uint8[4] Magic: TEVd
|
||||||
|
Int48 Disk size in bytes (max 256 TiB)
|
||||||
|
Uint8[32] Disk name
|
||||||
|
Int32 CRC-32
|
||||||
|
1. create list of arrays that contains CRC
|
||||||
|
2. put all the CRCs of entries
|
||||||
|
3. sort the list (here's the catch -- you will treat CRCs as SIGNED integer)
|
||||||
|
4. for elems on list: update crc with the elem (crc = calculateCRC(crc, elem))
|
||||||
|
Int8 Version
|
||||||
|
|
||||||
|
(Header size: 47 bytes)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## IndexNumber and Contents
|
||||||
|
<Entry Header>
|
||||||
|
<Actual Entry>
|
||||||
|
|
||||||
|
### Entry Header
|
||||||
|
Int32 EntryID (random Integer). This act as "jump" position for directory listing.
|
||||||
|
NOTE: Index 0 must be a root "Directory"; 0xFEFEFEFE is invalid (used as footer marker)
|
||||||
|
Int32 EntryID of parent directory
|
||||||
|
Int8 Flag for file or directory or symlink (cannot be negative)
|
||||||
|
0x01: Normal file, 0x02: Directory list, 0x03: Symlink
|
||||||
|
0x11: Compressed normal file
|
||||||
|
Uint8[256] File name in UTF-8
|
||||||
|
Int48 Creation date in real-life UNIX timestamp
|
||||||
|
Int48 Last modification date in real-life UNIX timestamp
|
||||||
|
Int32 CRC-32 of Actual Entry
|
||||||
|
|
||||||
|
(Header size: 281 bytes)
|
||||||
|
|
||||||
|
### Entry of File (Uncompressed)
|
||||||
|
Int48 File size in bytes (max 256 TiB)
|
||||||
|
<Bytes> Actual Contents
|
||||||
|
|
||||||
|
(Header size: 6 bytes)
|
||||||
|
|
||||||
|
### Entry of File (Compressed)
|
||||||
|
Int48 Size of compressed payload (max 256 TiB)
|
||||||
|
Int48 Size of uncompressed file (max 256 TiB)
|
||||||
|
<Bytes> Actual Contents, DEFLATEd payload
|
||||||
|
|
||||||
|
(Header size: 12 bytes)
|
||||||
|
|
||||||
|
### Entry of Directory
|
||||||
|
Uint16 Number of entries (normal files, other directories, symlinks)
|
||||||
|
<Int32s> Entry listing, contains IndexNumber
|
||||||
|
|
||||||
|
(Header size: 2 bytes)
|
||||||
|
|
||||||
|
### Entry of Symlink
|
||||||
|
Int32 Target IndexNumber
|
||||||
|
|
||||||
|
(Content size: 4 bytes)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Footer
|
||||||
|
Uint8[4] 0xFE 0xFE 0xFE 0xFE (footer marker)
|
||||||
|
Int8 Disk properties flag 1
|
||||||
|
0b 7 6 5 4 3 2 1 0
|
||||||
|
|
||||||
|
0th bit: Readonly
|
||||||
|
|
||||||
|
Int8[7] Reserved, should be filled with zero
|
||||||
|
<optional footer if present>
|
||||||
|
Uint8[2] 0xFF 0x19 (EOF mark)
|
||||||
@@ -792,18 +792,22 @@ object VDUtil {
|
|||||||
/**
|
/**
|
||||||
* Creates new zero-filled file with given name and size
|
* Creates new zero-filled file with given name and size
|
||||||
*/
|
*/
|
||||||
fun createNewBlankFile(disk: VirtualDisk, directoryID: EntryID, fileSize: Long, filename: String, charset: Charset) {
|
fun createNewBlankFile(disk: VirtualDisk, directoryID: EntryID, fileSize: Long, filename: String, charset: Charset): EntryID {
|
||||||
disk.checkReadOnly()
|
disk.checkReadOnly()
|
||||||
disk.checkCapacity(fileSize + DiskEntry.HEADER_SIZE + 4)
|
disk.checkCapacity(fileSize + DiskEntry.HEADER_SIZE + 4)
|
||||||
|
|
||||||
addFile(disk, directoryID, DiskEntry(
|
val newEntry = DiskEntry(
|
||||||
disk.generateUniqueID(),
|
disk.generateUniqueID(),
|
||||||
directoryID,
|
directoryID,
|
||||||
filename.toEntryName(DiskEntry.NAME_LENGTH, charset = charset),
|
filename.toEntryName(DiskEntry.NAME_LENGTH, charset = charset),
|
||||||
currentUnixtime,
|
currentUnixtime,
|
||||||
currentUnixtime,
|
currentUnixtime,
|
||||||
EntryFile(fileSize)
|
EntryFile(fileSize)
|
||||||
))
|
)
|
||||||
|
|
||||||
|
addFile(disk, directoryID, newEntry)
|
||||||
|
|
||||||
|
return newEntry.entryID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package net.torvald.terrarum.virtualcomputer.tvd.finder
|
||||||
|
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.GridLayout
|
||||||
|
import javax.swing.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by SKYHi14 on 2017-04-01.
|
||||||
|
*/
|
||||||
|
object Popups {
|
||||||
|
val okCancel = arrayOf("OK", "Cancel")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class OptionDiskNameAndCap {
|
||||||
|
val name = JTextField(11)
|
||||||
|
val capacity = JSpinner(SpinnerNumberModel(
|
||||||
|
368640L.toJavaLong(),
|
||||||
|
0L.toJavaLong(),
|
||||||
|
(1L shl 38).toJavaLong(),
|
||||||
|
1L.toJavaLong()
|
||||||
|
)) // default 360 KiB, MAX 256 GiB
|
||||||
|
val mainPanel = JPanel()
|
||||||
|
val settingPanel = JPanel()
|
||||||
|
|
||||||
|
init {
|
||||||
|
mainPanel.layout = BorderLayout()
|
||||||
|
settingPanel.layout = GridLayout(2, 2, 2, 0)
|
||||||
|
|
||||||
|
//name.text = "Unnamed"
|
||||||
|
|
||||||
|
settingPanel.add(JLabel("Name (max 32 bytes)"))
|
||||||
|
settingPanel.add(name)
|
||||||
|
settingPanel.add(JLabel("Capacity (bytes)"))
|
||||||
|
settingPanel.add(capacity)
|
||||||
|
|
||||||
|
mainPanel.add(settingPanel, BorderLayout.CENTER)
|
||||||
|
mainPanel.add(JLabel("Set capacity to 0 to make the disk read-only"), BorderLayout.SOUTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns either JOptionPane.OK_OPTION or JOptionPane.CANCEL_OPTION
|
||||||
|
*/
|
||||||
|
fun showDialog(title: String): Int {
|
||||||
|
return JOptionPane.showConfirmDialog(null, mainPanel,
|
||||||
|
title, JOptionPane.OK_CANCEL_OPTION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun kotlin.Long.toJavaLong() = java.lang.Long(this)
|
||||||
|
|
||||||
|
class OptionFileNameAndCap {
|
||||||
|
val name = JTextField(11)
|
||||||
|
val capacity = JSpinner(SpinnerNumberModel(
|
||||||
|
4096L.toJavaLong(),
|
||||||
|
0L.toJavaLong(),
|
||||||
|
((1L shl 48) - 1L).toJavaLong(),
|
||||||
|
1L.toJavaLong()
|
||||||
|
)) // default 360 KiB, MAX 256 TiB
|
||||||
|
val mainPanel = JPanel()
|
||||||
|
val settingPanel = JPanel()
|
||||||
|
|
||||||
|
init {
|
||||||
|
mainPanel.layout = BorderLayout()
|
||||||
|
settingPanel.layout = GridLayout(2, 2, 2, 0)
|
||||||
|
|
||||||
|
//name.text = "Unnamed"
|
||||||
|
|
||||||
|
settingPanel.add(JLabel("Name (max 32 bytes)"))
|
||||||
|
settingPanel.add(name)
|
||||||
|
settingPanel.add(JLabel("Capacity (bytes)"))
|
||||||
|
settingPanel.add(capacity)
|
||||||
|
|
||||||
|
mainPanel.add(settingPanel, BorderLayout.CENTER)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns either JOptionPane.OK_OPTION or JOptionPane.CANCEL_OPTION
|
||||||
|
*/
|
||||||
|
fun showDialog(title: String): Int {
|
||||||
|
return JOptionPane.showConfirmDialog(null, mainPanel,
|
||||||
|
title, JOptionPane.OK_CANCEL_OPTION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OptionSize {
|
||||||
|
val capacity = JSpinner(SpinnerNumberModel(
|
||||||
|
368640L.toJavaLong(),
|
||||||
|
0L.toJavaLong(),
|
||||||
|
(1L shl 38).toJavaLong(),
|
||||||
|
1L.toJavaLong()
|
||||||
|
)) // default 360 KiB, MAX 256 GiB
|
||||||
|
val settingPanel = JPanel()
|
||||||
|
|
||||||
|
init {
|
||||||
|
settingPanel.add(JLabel("Size (bytes)"))
|
||||||
|
settingPanel.add(capacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns either JOptionPane.OK_OPTION or JOptionPane.CANCEL_OPTION
|
||||||
|
*/
|
||||||
|
fun showDialog(title: String): Int {
|
||||||
|
return JOptionPane.showConfirmDialog(null, settingPanel,
|
||||||
|
title, JOptionPane.OK_CANCEL_OPTION)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,843 @@
|
|||||||
|
package net.torvald.terrarum.virtualcomputer.tvd.finder
|
||||||
|
|
||||||
|
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.*
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.Dimension
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
|
import java.awt.event.MouseAdapter
|
||||||
|
import java.awt.event.MouseEvent
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.*
|
||||||
|
import java.util.logging.Level
|
||||||
|
import javax.swing.*
|
||||||
|
import javax.swing.table.AbstractTableModel
|
||||||
|
import javax.swing.ListSelectionModel
|
||||||
|
import javax.swing.text.DefaultCaret
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by SKYHi14 on 2017-04-01.
|
||||||
|
*/
|
||||||
|
class VirtualDiskCracker(val sysCharset: Charset = Charsets.UTF_8) : JFrame() {
|
||||||
|
|
||||||
|
|
||||||
|
private val annoyHackers = true // Jar build settings. Intended for Terrarum proj.
|
||||||
|
|
||||||
|
|
||||||
|
private val PREVIEW_MAX_BYTES = 4L * 1024 // 4 kBytes
|
||||||
|
|
||||||
|
private val appName = "TerranVirtualDiskCracker"
|
||||||
|
private val copyright = "Copyright 2017 Torvald (minjaesong). Distributed under MIT license."
|
||||||
|
|
||||||
|
private val magicOpen = "I solemnly swear that I am up to no good."
|
||||||
|
private val magicSave = "Mischief managed."
|
||||||
|
private val annoyWhenLaunchMsg = "Type in following to get started:\n$magicOpen"
|
||||||
|
private val annoyWhenSaveMsg = "Type in following to save:\n$magicSave"
|
||||||
|
|
||||||
|
private val panelMain = JPanel()
|
||||||
|
private val menuBar = JMenuBar()
|
||||||
|
private val tableFiles: JTable
|
||||||
|
private val fileDesc = JTextArea()
|
||||||
|
private val diskInfo = JTextArea()
|
||||||
|
private val statBar = JLabel("Open a disk or create new to get started")
|
||||||
|
|
||||||
|
private var vdisk: VirtualDisk? = null
|
||||||
|
private var clipboard: DiskEntry? = null
|
||||||
|
|
||||||
|
private val labelPath = JLabel("(root)")
|
||||||
|
private var currentDirectoryEntries: Array<DiskEntry>? = null
|
||||||
|
private val directoryHierarchy = Stack<EntryID>(); init { directoryHierarchy.push(0) }
|
||||||
|
|
||||||
|
private fun gotoSubDirectory(id: EntryID) {
|
||||||
|
directoryHierarchy.push(id)
|
||||||
|
labelPath.text = vdisk!!.entries[id]!!.getFilenameString(sysCharset)
|
||||||
|
selectedFile = null
|
||||||
|
fileDesc.text = ""
|
||||||
|
updateDiskInfo()
|
||||||
|
}
|
||||||
|
val currentDirectory: EntryID
|
||||||
|
get() = directoryHierarchy.peek()
|
||||||
|
val upperDirectory: EntryID
|
||||||
|
get() = if (directoryHierarchy.lastIndex == 0) 0
|
||||||
|
else directoryHierarchy[directoryHierarchy.lastIndex - 1]
|
||||||
|
private fun gotoRoot() {
|
||||||
|
directoryHierarchy.removeAllElements()
|
||||||
|
directoryHierarchy.push(0)
|
||||||
|
selectedFile = null
|
||||||
|
fileDesc.text = ""
|
||||||
|
updateDiskInfo()
|
||||||
|
}
|
||||||
|
private fun gotoParent() {
|
||||||
|
if (directoryHierarchy.size > 1)
|
||||||
|
directoryHierarchy.pop()
|
||||||
|
selectedFile = null
|
||||||
|
fileDesc.text = ""
|
||||||
|
updateDiskInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private var selectedFile: EntryID? = null
|
||||||
|
|
||||||
|
val tableColumns = arrayOf("Name", "Date Modified", "Size")
|
||||||
|
val tableParentRecord = arrayOf(arrayOf("..", "", ""))
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
if (annoyHackers) {
|
||||||
|
val mantra = JOptionPane.showInputDialog(annoyWhenLaunchMsg)
|
||||||
|
if (mantra != magicOpen) {
|
||||||
|
System.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
panelMain.layout = BorderLayout()
|
||||||
|
this.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
|
||||||
|
|
||||||
|
|
||||||
|
tableFiles = JTable(tableParentRecord, tableColumns)
|
||||||
|
tableFiles.addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent) {
|
||||||
|
val table = e.source as JTable
|
||||||
|
val row = table.rowAtPoint(e.point)
|
||||||
|
|
||||||
|
|
||||||
|
selectedFile = if (row > 0)
|
||||||
|
currentDirectoryEntries!![row - 1].entryID
|
||||||
|
else
|
||||||
|
null // clicked ".."
|
||||||
|
|
||||||
|
|
||||||
|
fileDesc.text = if (selectedFile != null) {
|
||||||
|
getFileInfoText(vdisk!!.entries[selectedFile!!]!!)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
""
|
||||||
|
|
||||||
|
fileDesc.caretPosition = 0
|
||||||
|
|
||||||
|
// double click
|
||||||
|
if (e.clickCount == 2) {
|
||||||
|
if (row == 0) {
|
||||||
|
gotoParent()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val record = currentDirectoryEntries!![row - 1]
|
||||||
|
if (record.contents is EntryDirectory) {
|
||||||
|
gotoSubDirectory(record.entryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
tableFiles.selectionModel = object : DefaultListSelectionModel() {
|
||||||
|
init { selectionMode = ListSelectionModel.SINGLE_SELECTION }
|
||||||
|
override fun clearSelection() { } // required!
|
||||||
|
override fun removeSelectionInterval(index0: Int, index1: Int) { } // required!
|
||||||
|
override fun fireValueChanged(isAdjusting: Boolean) { } // required!
|
||||||
|
}
|
||||||
|
tableFiles.model = object : AbstractTableModel() {
|
||||||
|
override fun getRowCount(): Int {
|
||||||
|
return if (vdisk != null)
|
||||||
|
1 + (currentDirectoryEntries?.size ?: 0)
|
||||||
|
else 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getColumnCount() = tableColumns.size
|
||||||
|
|
||||||
|
override fun getColumnName(column: Int) = tableColumns[column]
|
||||||
|
|
||||||
|
override fun getValueAt(rowIndex: Int, columnIndex: Int): Any {
|
||||||
|
if (rowIndex == 0) {
|
||||||
|
return tableParentRecord[0][columnIndex]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (vdisk != null) {
|
||||||
|
val entry = currentDirectoryEntries!![rowIndex - 1]
|
||||||
|
return when(columnIndex) {
|
||||||
|
0 -> entry.getFilenameString(sysCharset)
|
||||||
|
1 -> Instant.ofEpochSecond(entry.modificationDate).
|
||||||
|
atZone(TimeZone.getDefault().toZoneId()).
|
||||||
|
format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||||
|
2 -> entry.getEffectiveSize()
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val menuFile = JMenu("File")
|
||||||
|
menuFile.mnemonic = KeyEvent.VK_F
|
||||||
|
menuFile.add("New Disk…").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
try {
|
||||||
|
val makeNewDisk: Boolean
|
||||||
|
if (vdisk != null) {
|
||||||
|
makeNewDisk = confirmedDiscard()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
makeNewDisk = true
|
||||||
|
}
|
||||||
|
if (makeNewDisk) {
|
||||||
|
// inquire new size
|
||||||
|
val dialogBox = OptionDiskNameAndCap()
|
||||||
|
val confirmNew = JOptionPane.OK_OPTION == dialogBox.showDialog("Set Property of New Disk")
|
||||||
|
|
||||||
|
if (confirmNew) {
|
||||||
|
vdisk = VDUtil.createNewDisk(
|
||||||
|
(dialogBox.capacity.value as Long).toLong(),
|
||||||
|
dialogBox.name.text,
|
||||||
|
sysCharset
|
||||||
|
)
|
||||||
|
gotoRoot()
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("Disk created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuFile.add("Open Disk…").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
val makeNewDisk: Boolean
|
||||||
|
if (vdisk != null) {
|
||||||
|
makeNewDisk = confirmedDiscard()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
makeNewDisk = true
|
||||||
|
}
|
||||||
|
if (makeNewDisk) {
|
||||||
|
val fileChooser = JFileChooser()
|
||||||
|
fileChooser.showOpenDialog(null)
|
||||||
|
if (fileChooser.selectedFile != null) {
|
||||||
|
try {
|
||||||
|
vdisk = VDUtil.readDiskArchive(fileChooser.selectedFile, Level.WARNING, { popupWarning(it) }, sysCharset)
|
||||||
|
if (vdisk != null) {
|
||||||
|
gotoRoot()
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("Disk loaded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuFile.addSeparator()
|
||||||
|
menuFile.add("Save Disk as…").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
|
||||||
|
|
||||||
|
if (annoyHackers) {
|
||||||
|
val mantra = JOptionPane.showInputDialog(annoyWhenSaveMsg)
|
||||||
|
if (mantra != magicSave) {
|
||||||
|
popupError("Nope!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val fileChooser = JFileChooser()
|
||||||
|
fileChooser.showSaveDialog(null)
|
||||||
|
if (fileChooser.selectedFile != null) {
|
||||||
|
try {
|
||||||
|
VDUtil.dumpToRealMachine(vdisk!!, fileChooser.selectedFile)
|
||||||
|
setStat("Disk saved")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuBar.add(menuFile)
|
||||||
|
|
||||||
|
val menuEdit = JMenu("Edit")
|
||||||
|
menuEdit.mnemonic = KeyEvent.VK_E
|
||||||
|
menuEdit.add("New File…").addMouseListener(object: MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
try {
|
||||||
|
val dialogBox = OptionFileNameAndCap()
|
||||||
|
val confirmNew = JOptionPane.OK_OPTION == dialogBox.showDialog("Set Property of New File")
|
||||||
|
|
||||||
|
if (confirmNew) {
|
||||||
|
if (VDUtil.nameExists(vdisk!!, dialogBox.name.text, currentDirectory, sysCharset)) {
|
||||||
|
popupError("The name already exists")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
VDUtil.createNewBlankFile(
|
||||||
|
vdisk!!,
|
||||||
|
currentDirectory,
|
||||||
|
(dialogBox.capacity.value as Long).toLong(),
|
||||||
|
dialogBox.name.text,
|
||||||
|
sysCharset
|
||||||
|
)
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("File created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.add("New Directory…").addMouseListener(object: MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
val newname = JOptionPane.showInputDialog("Enter a new directory name:")
|
||||||
|
if (newname != null) {
|
||||||
|
try {
|
||||||
|
if (VDUtil.nameExists(vdisk!!, newname, currentDirectory, sysCharset)) {
|
||||||
|
popupError("The name already exists")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
VDUtil.addDir(vdisk!!, currentDirectory, newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset))
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("Directory created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.addSeparator()
|
||||||
|
menuEdit.add("Cut").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
// copy
|
||||||
|
clipboard = vdisk!!.entries[selectedFile]
|
||||||
|
|
||||||
|
// delete
|
||||||
|
if (vdisk != null && selectedFile != null) {
|
||||||
|
try {
|
||||||
|
VDUtil.deleteFile(vdisk!!, selectedFile!!)
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("File deleted")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.add("Copy").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
clipboard = vdisk!!.entries[selectedFile]
|
||||||
|
setStat("File copied")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.add("Paste").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
fun paste1(newname: ByteArray) {
|
||||||
|
try {
|
||||||
|
VDUtil.addFile(vdisk!!, currentDirectory, DiskEntry(// clone
|
||||||
|
vdisk!!.generateUniqueID(),
|
||||||
|
currentDirectory,
|
||||||
|
newname,
|
||||||
|
clipboard!!.creationDate,
|
||||||
|
clipboard!!.modificationDate,
|
||||||
|
clipboard!!.contents
|
||||||
|
))
|
||||||
|
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("File pasted")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (clipboard != null && vdisk != null) {
|
||||||
|
// check name collision. If it is, ask for new one
|
||||||
|
if (VDUtil.nameExists(vdisk!!, clipboard!!.getFilenameString(sysCharset), currentDirectory, sysCharset)) {
|
||||||
|
val newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
|
||||||
|
if (newname != null) {
|
||||||
|
paste1(newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
paste1(clipboard!!.filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.add("Paste as Symbolic Link").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
fun pasteSymbolic(newname: ByteArray) {
|
||||||
|
try {
|
||||||
|
// check if the original file is there in the first place
|
||||||
|
if (vdisk!!.entries[clipboard!!.entryID] != null) {
|
||||||
|
val entrySymlink = EntrySymlink(clipboard!!.entryID)
|
||||||
|
VDUtil.addFile(vdisk!!, currentDirectory, DiskEntry(
|
||||||
|
vdisk!!.generateUniqueID(),
|
||||||
|
currentDirectory,
|
||||||
|
newname,
|
||||||
|
VDUtil.currentUnixtime,
|
||||||
|
VDUtil.currentUnixtime,
|
||||||
|
entrySymlink
|
||||||
|
))
|
||||||
|
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("Symbolic link created")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
popupError("The orignal file is gone")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (clipboard != null && vdisk != null) {
|
||||||
|
// check name collision. If it is, ask for new one
|
||||||
|
if (VDUtil.nameExists(vdisk!!, clipboard!!.getFilenameString(sysCharset), currentDirectory, sysCharset)) {
|
||||||
|
val newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
|
||||||
|
if (newname != null) {
|
||||||
|
pasteSymbolic(newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pasteSymbolic(clipboard!!.filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.add("Delete").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null && selectedFile != null) {
|
||||||
|
try {
|
||||||
|
VDUtil.deleteFile(vdisk!!, selectedFile!!)
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("File deleted")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.add("Rename…").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (selectedFile != null) {
|
||||||
|
try {
|
||||||
|
val newname = JOptionPane.showInputDialog("Enter a new name:")
|
||||||
|
if (newname != null) {
|
||||||
|
if (VDUtil.nameExists(vdisk!!, newname, currentDirectory, sysCharset)) {
|
||||||
|
popupError("The name already exists")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
VDUtil.renameFile(vdisk!!, selectedFile!!, newname, sysCharset)
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("File renamed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.add("Look Clipboard").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
popupMessage(if (clipboard != null)
|
||||||
|
"${clipboard ?: "(bug found)"}"
|
||||||
|
else "(nothing)", "Clipboard"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
menuEdit.addSeparator()
|
||||||
|
menuEdit.add("Import…").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
val fileChooser = JFileChooser()
|
||||||
|
fileChooser.fileSelectionMode = JFileChooser.FILES_AND_DIRECTORIES
|
||||||
|
fileChooser.isMultiSelectionEnabled = true
|
||||||
|
fileChooser.showOpenDialog(null)
|
||||||
|
if (fileChooser.selectedFiles.isNotEmpty()) {
|
||||||
|
try {
|
||||||
|
fileChooser.selectedFiles.forEach {
|
||||||
|
if (!it.isDirectory) {
|
||||||
|
val entry = VDUtil.importFile(it, vdisk!!.generateUniqueID(), sysCharset)
|
||||||
|
|
||||||
|
val newname: String?
|
||||||
|
if (VDUtil.nameExists(vdisk!!, entry.filename, currentDirectory)) {
|
||||||
|
newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newname = entry.getFilenameString(sysCharset)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newname != null) {
|
||||||
|
entry.filename = newname.toEntryName(DiskEntry.NAME_LENGTH, sysCharset)
|
||||||
|
VDUtil.addFile(vdisk!!, currentDirectory, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val newname: String?
|
||||||
|
if (VDUtil.nameExists(vdisk!!, it.name.toEntryName(DiskEntry.NAME_LENGTH, sysCharset), currentDirectory)) {
|
||||||
|
newname = JOptionPane.showInputDialog("The name already exists. Enter a new name:")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newname = it.name
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newname != null) {
|
||||||
|
VDUtil.importDirRecurse(vdisk!!, it, currentDirectory, sysCharset, newname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("File added")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileChooser.isMultiSelectionEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.add("Export…").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
val file = vdisk!!.entries[selectedFile ?: currentDirectory]!!
|
||||||
|
|
||||||
|
val fileChooser = JFileChooser()
|
||||||
|
fileChooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
|
||||||
|
fileChooser.isMultiSelectionEnabled = false
|
||||||
|
fileChooser.showSaveDialog(null)
|
||||||
|
if (fileChooser.selectedFile != null) {
|
||||||
|
try {
|
||||||
|
val file = VDUtil.resolveIfSymlink(vdisk!!, file.entryID)
|
||||||
|
if (file.contents is EntryFile) {
|
||||||
|
VDUtil.exportFile(file.contents, fileChooser.selectedFile)
|
||||||
|
setStat("File exported")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
VDUtil.exportDirRecurse(vdisk!!, file.entryID, fileChooser.selectedFile, sysCharset)
|
||||||
|
setStat("Files exported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.addSeparator()
|
||||||
|
menuEdit.add("Rename Disk…").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
try {
|
||||||
|
val newname = JOptionPane.showInputDialog("Enter a new disk name:")
|
||||||
|
if (newname != null) {
|
||||||
|
vdisk!!.diskName = newname.toEntryName(VirtualDisk.NAME_LENGTH, sysCharset)
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("Disk renamed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.add("Resize Disk…").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
try {
|
||||||
|
val dialog = OptionSize()
|
||||||
|
val confirmed = dialog.showDialog("Input") == JOptionPane.OK_OPTION
|
||||||
|
if (confirmed) {
|
||||||
|
vdisk!!.capacity = (dialog.capacity.value as Long).toLong()
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("Disk resized")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuEdit.addSeparator()
|
||||||
|
menuEdit.add("Set/Unset Write Protection").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
try {
|
||||||
|
vdisk!!.isReadOnly = vdisk!!.isReadOnly.not()
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("Disk write protection ${if (vdisk!!.isReadOnly) "" else "dis"}engaged")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuBar.add(menuEdit)
|
||||||
|
|
||||||
|
val menuManage = JMenu("Manage")
|
||||||
|
menuManage.add("Report Orphans…").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
try {
|
||||||
|
val reports = VDUtil.gcSearchOrphan(vdisk!!)
|
||||||
|
val orphansCount = reports.size
|
||||||
|
val orphansSize = reports.map { vdisk!!.entries[it]!!.contents.getSizeEntry() }.sum()
|
||||||
|
val message = "Orphans count: $orphansCount\n" +
|
||||||
|
"Size: ${orphansSize.bytes()}"
|
||||||
|
popupMessage(message, "Orphans Report")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuManage.add("Report Phantoms…").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
try {
|
||||||
|
val reports = VDUtil.gcSearchPhantomBaby(vdisk!!)
|
||||||
|
val phantomsSize = reports.size
|
||||||
|
val message = "Phantoms count: $phantomsSize"
|
||||||
|
popupMessage(message, "Phantoms Report")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuManage.addSeparator()
|
||||||
|
menuManage.add("Remove Orphans").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
try {
|
||||||
|
val oldSize = vdisk!!.usedBytes
|
||||||
|
VDUtil.gcDumpOrphans(vdisk!!)
|
||||||
|
val newSize = vdisk!!.usedBytes
|
||||||
|
popupMessage("Saved ${(oldSize - newSize).bytes()}", "GC Report")
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("Orphan nodes removed")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuManage.add("Full Garbage Collect").addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
if (vdisk != null) {
|
||||||
|
try {
|
||||||
|
val oldSize = vdisk!!.usedBytes
|
||||||
|
VDUtil.gcDumpAll(vdisk!!)
|
||||||
|
val newSize = vdisk!!.usedBytes
|
||||||
|
popupMessage("Saved ${(oldSize - newSize).bytes()}", "GC Report")
|
||||||
|
updateDiskInfo()
|
||||||
|
setStat("Orphan nodes and null directory pointers removed")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
popupError(e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuBar.add(menuManage)
|
||||||
|
|
||||||
|
val menuAbout = JMenu("About")
|
||||||
|
menuAbout.addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent?) {
|
||||||
|
popupMessage(copyright, "Copyright")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
menuBar.add(menuAbout)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
diskInfo.highlighter = null
|
||||||
|
diskInfo.text = "(Disk not loaded)"
|
||||||
|
diskInfo.preferredSize = Dimension(-1, 60)
|
||||||
|
|
||||||
|
fileDesc.highlighter = null
|
||||||
|
fileDesc.text = ""
|
||||||
|
fileDesc.caret.isVisible = false
|
||||||
|
(fileDesc.caret as DefaultCaret).updatePolicy = DefaultCaret.NEVER_UPDATE
|
||||||
|
|
||||||
|
val fileDescScroll = JScrollPane(fileDesc)
|
||||||
|
val tableFilesScroll = JScrollPane(tableFiles)
|
||||||
|
tableFilesScroll.size = Dimension(200, -1)
|
||||||
|
|
||||||
|
val panelFinder = JPanel(BorderLayout())
|
||||||
|
panelFinder.add(labelPath, BorderLayout.NORTH)
|
||||||
|
panelFinder.add(tableFilesScroll, BorderLayout.CENTER)
|
||||||
|
|
||||||
|
val panelFileDesc = JPanel(BorderLayout())
|
||||||
|
panelFileDesc.add(JLabel("Entry Information"), BorderLayout.NORTH)
|
||||||
|
panelFileDesc.add(fileDescScroll, BorderLayout.CENTER)
|
||||||
|
|
||||||
|
val filesSplit = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panelFinder, panelFileDesc)
|
||||||
|
filesSplit.resizeWeight = 0.714285
|
||||||
|
|
||||||
|
|
||||||
|
val panelDiskOp = JPanel(BorderLayout(2, 2))
|
||||||
|
panelDiskOp.add(filesSplit, BorderLayout.CENTER)
|
||||||
|
panelDiskOp.add(diskInfo, BorderLayout.SOUTH)
|
||||||
|
|
||||||
|
|
||||||
|
panelMain.add(menuBar, BorderLayout.NORTH)
|
||||||
|
panelMain.add(panelDiskOp, BorderLayout.CENTER)
|
||||||
|
panelMain.add(statBar, BorderLayout.SOUTH)
|
||||||
|
|
||||||
|
|
||||||
|
this.title = appName
|
||||||
|
this.add(panelMain)
|
||||||
|
this.setSize(700, 700)
|
||||||
|
this.isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun confirmedDiscard() = 0 == JOptionPane.showOptionDialog(
|
||||||
|
null, // parent
|
||||||
|
"Any changes to current disk will be discarded. Continue?",
|
||||||
|
"Confirm Discard", // window title
|
||||||
|
JOptionPane.DEFAULT_OPTION, // option type
|
||||||
|
JOptionPane.WARNING_MESSAGE, // message type
|
||||||
|
null, // icon
|
||||||
|
Popups.okCancel, // options (provided by JOptionPane.OK_CANCEL_OPTION in this case)
|
||||||
|
Popups.okCancel[1] // default selection
|
||||||
|
)
|
||||||
|
private fun popupMessage(message: String, title: String = "") {
|
||||||
|
JOptionPane.showOptionDialog(
|
||||||
|
null,
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
JOptionPane.DEFAULT_OPTION,
|
||||||
|
JOptionPane.INFORMATION_MESSAGE,
|
||||||
|
null, null, null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private fun popupError(message: String, title: String = "Uh oh…") {
|
||||||
|
JOptionPane.showOptionDialog(
|
||||||
|
null,
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
JOptionPane.DEFAULT_OPTION,
|
||||||
|
JOptionPane.ERROR_MESSAGE,
|
||||||
|
null, null, null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private fun popupWarning(message: String, title: String = "Careful…") {
|
||||||
|
JOptionPane.showOptionDialog(
|
||||||
|
null,
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
JOptionPane.DEFAULT_OPTION,
|
||||||
|
JOptionPane.WARNING_MESSAGE,
|
||||||
|
null, null, null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private fun updateCurrentDirectory() {
|
||||||
|
currentDirectoryEntries = VDUtil.getDirectoryEntries(vdisk!!, currentDirectory)
|
||||||
|
}
|
||||||
|
private fun updateDiskInfo() {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
directoryHierarchy.forEach {
|
||||||
|
sb.append(vdisk!!.entries[it]!!.getFilenameString(sysCharset))
|
||||||
|
sb.append('/')
|
||||||
|
}
|
||||||
|
sb.dropLast(1)
|
||||||
|
labelPath.text = sb.toString()
|
||||||
|
|
||||||
|
diskInfo.text = if (vdisk == null) "(Disk not loaded)" else getDiskInfoText(vdisk!!)
|
||||||
|
tableFiles.revalidate()
|
||||||
|
tableFiles.repaint()
|
||||||
|
|
||||||
|
|
||||||
|
updateCurrentDirectory()
|
||||||
|
}
|
||||||
|
private fun getDiskInfoText(disk: VirtualDisk): String {
|
||||||
|
return """Name: ${String(disk.diskName, sysCharset)}
|
||||||
|
Capacity: ${disk.capacity} bytes (${disk.usedBytes} bytes used, ${disk.capacity - disk.usedBytes} bytes free)
|
||||||
|
Write protected: ${disk.isReadOnly.toEnglish()}"""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Boolean.toEnglish() = if (this) "Yes" else "No"
|
||||||
|
|
||||||
|
|
||||||
|
private fun getFileInfoText(file: DiskEntry): String {
|
||||||
|
return """Name: ${file.getFilenameString(sysCharset)}
|
||||||
|
Size: ${file.getEffectiveSize()}
|
||||||
|
Type: ${DiskEntry.getTypeString(file.contents)}
|
||||||
|
CRC: ${file.hashCode().toHex()}
|
||||||
|
EntryID: ${file.entryID}
|
||||||
|
ParentID: ${file.parentEntryID}""" + if (file.contents is EntryFile) """
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
${String(file.contents.bytes.sliceArray64(0L..minOf(PREVIEW_MAX_BYTES, file.contents.bytes.size) - 1).toByteArray(), sysCharset)}""" else ""
|
||||||
|
}
|
||||||
|
private fun Long.bytes() = if (this == 1L) "1 byte" else "$this bytes"
|
||||||
|
private fun Int.entries() = if (this == 1) "1 entry" else "$this entries"
|
||||||
|
private fun DiskEntry.getEffectiveSize() = if (this.contents is EntryFile)
|
||||||
|
this.contents.getSizePure().bytes()
|
||||||
|
else if (this.contents is EntryDirectory)
|
||||||
|
this.contents.entryCount.entries()
|
||||||
|
else if (this.contents is EntrySymlink)
|
||||||
|
"(symlink)"
|
||||||
|
else
|
||||||
|
"n/a"
|
||||||
|
private fun setStat(message: String) {
|
||||||
|
statBar.text = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
VirtualDiskCracker(Charset.forName("CP437"))
|
||||||
|
}
|
||||||
@@ -4,13 +4,12 @@ import net.torvald.terrarum.gameworld.BlockAddress
|
|||||||
import net.torvald.terrarum.gameworld.BlockDamage
|
import net.torvald.terrarum.gameworld.BlockDamage
|
||||||
import net.torvald.terrarum.gameworld.MapLayer
|
import net.torvald.terrarum.gameworld.MapLayer
|
||||||
import net.torvald.terrarum.gameworld.PairedMapLayer
|
import net.torvald.terrarum.gameworld.PairedMapLayer
|
||||||
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
|
|
||||||
import net.torvald.terrarum.realestate.LandUtil
|
import net.torvald.terrarum.realestate.LandUtil
|
||||||
|
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskSkimmer.Companion.read
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.zip.Inflater
|
import java.util.zip.Inflater
|
||||||
import java.util.zip.InflaterOutputStream
|
|
||||||
import kotlin.IllegalArgumentException
|
import kotlin.IllegalArgumentException
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
@@ -27,13 +26,6 @@ internal object ReadLayerDataZip {
|
|||||||
|
|
||||||
|
|
||||||
val magicBytes = ByteArray(4)
|
val magicBytes = ByteArray(4)
|
||||||
val versionNumber = ByteArray(1)
|
|
||||||
val layerCount = ByteArray(1)
|
|
||||||
val payloadCountByte = ByteArray(1)
|
|
||||||
val compression = ByteArray(1)
|
|
||||||
val worldWidth = ByteArray(4)
|
|
||||||
val worldHeight = ByteArray(4)
|
|
||||||
val spawnAddress = ByteArray(6)
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////
|
//////////////////
|
||||||
@@ -47,18 +39,16 @@ internal object ReadLayerDataZip {
|
|||||||
throw IllegalArgumentException("File not a Layer Data")
|
throw IllegalArgumentException("File not a Layer Data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val versionNumber = inputStream.read(1)[0].toUint()
|
||||||
inputStream.read(versionNumber)
|
val layerCount = inputStream.read(1)[0].toUint()
|
||||||
inputStream.read(layerCount)
|
val payloadCount = inputStream.read(1)[0].toUint()
|
||||||
inputStream.read(payloadCountByte)
|
val compression = inputStream.read(1)[0].toUint()
|
||||||
inputStream.read(compression)
|
val width = inputStream.read(4).toLittleInt()
|
||||||
inputStream.read(worldWidth)
|
val height = inputStream.read(4).toLittleInt()
|
||||||
inputStream.read(worldHeight)
|
val spawnAddress = inputStream.read(6).toLittleInt48()
|
||||||
inputStream.read(spawnAddress)
|
|
||||||
|
|
||||||
// read payloads
|
// read payloads
|
||||||
|
|
||||||
val payloadCount = payloadCountByte[0].toUint()
|
|
||||||
val pldBuffer4 = ByteArray(4)
|
val pldBuffer4 = ByteArray(4)
|
||||||
val pldBuffer6 = ByteArray(6)
|
val pldBuffer6 = ByteArray(6)
|
||||||
val pldBuffer8 = ByteArray(8)
|
val pldBuffer8 = ByteArray(8)
|
||||||
@@ -114,8 +104,6 @@ internal object ReadLayerDataZip {
|
|||||||
// END OF FILE READ //
|
// END OF FILE READ //
|
||||||
//////////////////////
|
//////////////////////
|
||||||
|
|
||||||
val width = worldWidth.toLittleInt()
|
|
||||||
val height = worldHeight.toLittleInt()
|
|
||||||
val worldSize = width.toLong() * height
|
val worldSize = width.toLong() * height
|
||||||
|
|
||||||
val payloadBytes = HashMap<String, ByteArray>()
|
val payloadBytes = HashMap<String, ByteArray>()
|
||||||
@@ -140,7 +128,7 @@ internal object ReadLayerDataZip {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val spawnPoint = LandUtil.resolveBlockAddr(width, spawnAddress.toLittleInt48())
|
val spawnPoint = LandUtil.resolveBlockAddr(width, spawnAddress)
|
||||||
|
|
||||||
val terrainDamages = HashMap<BlockAddress, BlockDamage>()
|
val terrainDamages = HashMap<BlockAddress, BlockDamage>()
|
||||||
val wallDamages = HashMap<BlockAddress, BlockDamage>()
|
val wallDamages = HashMap<BlockAddress, BlockDamage>()
|
||||||
|
|||||||
10
src/net/torvald/terrarum/serialise/SavegameLoader.kt
Normal file
10
src/net/torvald/terrarum/serialise/SavegameLoader.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package net.torvald.terrarum.serialise
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by minjaesong on 2018-10-03.
|
||||||
|
*/
|
||||||
|
object SavegameLoader {
|
||||||
|
|
||||||
|
// TODO read TEVd, load necessary shits
|
||||||
|
|
||||||
|
}
|
||||||
60
src/net/torvald/terrarum/serialise/SavegameWriter.kt
Normal file
60
src/net/torvald/terrarum/serialise/SavegameWriter.kt
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package net.torvald.terrarum.serialise
|
||||||
|
|
||||||
|
import net.torvald.terrarum.Terrarum
|
||||||
|
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
|
||||||
|
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskEntry
|
||||||
|
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.DiskSkimmer
|
||||||
|
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil
|
||||||
|
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VirtualDisk
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by minjaesong on 2018-10-03.
|
||||||
|
*/
|
||||||
|
object SavegameWriter {
|
||||||
|
|
||||||
|
// TODO create temporary files (worldinfo), create JSON files on RAM, pack those into TEVd as per Savegame container.txt
|
||||||
|
|
||||||
|
private val charset = Charset.forName("UTF-8")
|
||||||
|
|
||||||
|
operator fun invoke(): Boolean {
|
||||||
|
val diskImage = generateDiskImage(null)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun generateDiskImage(oldDiskFile: File?): VirtualDisk {
|
||||||
|
val disk = VDUtil.createNewDisk(0x7FFFFFFFFFFFFFFFL, "TerrarumSave", charset)
|
||||||
|
val oldDiskSkimmer = oldDiskFile?.let { DiskSkimmer(oldDiskFile) }
|
||||||
|
|
||||||
|
val ROOT = disk.root.entryID
|
||||||
|
val ingame = Terrarum.ingame!!
|
||||||
|
val gameworld = ingame.world
|
||||||
|
|
||||||
|
// serialise current world (stage)
|
||||||
|
val world = WriteLayerDataZip() // filename can be anything that is "tmp_world[n]" where [n] is any number
|
||||||
|
val worldFile = VDUtil.importFile(world!!, gameworld.worldIndex, charset)
|
||||||
|
|
||||||
|
// add current world (stage) to the disk
|
||||||
|
VDUtil.addFile(disk, ROOT, worldFile)
|
||||||
|
|
||||||
|
// put other worlds (stages) to the disk (without loading whole oldDiskFile onto the disk)
|
||||||
|
oldDiskSkimmer?.let {
|
||||||
|
// skim-and-write other worlds
|
||||||
|
for (c in 1..ingame.gameworldCount) {
|
||||||
|
if (c != gameworld.worldIndex) {
|
||||||
|
val oldWorldFile = oldDiskSkimmer.requestFile(c)
|
||||||
|
VDUtil.addFile(disk, ROOT, oldWorldFile!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO world[n] is done, needs whole other things
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return disk
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,12 @@ import java.util.zip.DeflaterOutputStream
|
|||||||
import java.util.zip.GZIPOutputStream
|
import java.util.zip.GZIPOutputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO this one does not use TerranVirtualDisk
|
* This object only writes a file named 'worldinfo1'.
|
||||||
|
*
|
||||||
|
* The intended operation is as follows:
|
||||||
|
* 1. This and others write
|
||||||
|
*
|
||||||
|
* TODO temporarily dump on the disk THEN pack? Or put all the files (in ByteArray64) in the RAM THEN pack?
|
||||||
*
|
*
|
||||||
* Created by minjaesong on 2016-03-18.
|
* Created by minjaesong on 2016-03-18.
|
||||||
*/
|
*/
|
||||||
@@ -23,7 +28,7 @@ internal object WriteLayerDataZip {
|
|||||||
|
|
||||||
// FIXME UNTESTED !!
|
// FIXME UNTESTED !!
|
||||||
|
|
||||||
val LAYERS_FILENAME = "worldinfo1"
|
val LAYERS_FILENAME = "world"
|
||||||
|
|
||||||
val MAGIC = byteArrayOf(0x54, 0x45, 0x4D, 0x7A)
|
val MAGIC = byteArrayOf(0x54, 0x45, 0x4D, 0x7A)
|
||||||
val VERSION_NUMBER = 3.toByte()
|
val VERSION_NUMBER = 3.toByte()
|
||||||
@@ -34,29 +39,36 @@ internal object WriteLayerDataZip {
|
|||||||
val PAYLOAD_FOOTER = byteArrayOf(0x45, 0x6E, 0x64, 0x50, 0x59, 0x4C, 0x64, -1)
|
val PAYLOAD_FOOTER = byteArrayOf(0x45, 0x6E, 0x64, 0x50, 0x59, 0x4C, 0x64, -1)
|
||||||
val FILE_FOOTER = byteArrayOf(0x45, 0x6E, 0x64, 0x54, 0x45, 0x4D, -1, -2)
|
val FILE_FOOTER = byteArrayOf(0x45, 0x6E, 0x64, 0x54, 0x45, 0x4D, -1, -2)
|
||||||
|
|
||||||
val NULL: Byte = 0
|
//val NULL: Byte = 0
|
||||||
|
|
||||||
|
|
||||||
internal operator fun invoke(saveDirectoryName: String): Boolean {
|
/**
|
||||||
val path = "${Terrarum.defaultSaveDir}/$saveDirectoryName/${LAYERS_FILENAME}"
|
* TODO currently it'll dump the temporary file (tmp_worldinfo1) onto the disk and will return the temp file.
|
||||||
val tempPath = "${path}_bak"
|
*
|
||||||
|
* @return File on success; `null` on failure
|
||||||
|
*/
|
||||||
|
internal operator fun invoke(): File? {
|
||||||
val world = (Terrarum.ingame!!.world)
|
val world = (Terrarum.ingame!!.world)
|
||||||
|
|
||||||
val parentDir = File("${Terrarum.defaultSaveDir}/$saveDirectoryName")
|
val path = "${Terrarum.defaultSaveDir}/tmp_$LAYERS_FILENAME${world.worldIndex}"
|
||||||
|
|
||||||
|
// TODO let's try dump-on-the-disk-then-pack method...
|
||||||
|
|
||||||
|
/*val parentDir = File("${Terrarum.defaultSaveDir}/$saveDirectoryName")
|
||||||
if (!parentDir.exists()) {
|
if (!parentDir.exists()) {
|
||||||
parentDir.mkdir()
|
parentDir.mkdir()
|
||||||
}
|
}
|
||||||
else if (!parentDir.isDirectory) {
|
else if (!parentDir.isDirectory) {
|
||||||
EchoError("Savegame directory is not actually a directory, aborting...")
|
EchoError("Savegame directory is not actually a directory, aborting...")
|
||||||
return false
|
return false
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
|
||||||
val tempFile = File(tempPath)
|
|
||||||
val outFile = File(path)
|
val outFile = File(path)
|
||||||
tempFile.createNewFile()
|
if (outFile.exists()) outFile.delete()
|
||||||
|
outFile.createNewFile()
|
||||||
|
|
||||||
val outputStream = BufferedOutputStream(FileOutputStream(tempFile), 8192)
|
val outputStream = BufferedOutputStream(FileOutputStream(outFile), 8192)
|
||||||
val deflater = DeflaterOutputStream(outputStream, true)
|
val deflater = DeflaterOutputStream(outputStream, true)
|
||||||
|
|
||||||
fun wb(byteArray: ByteArray) { outputStream.write(byteArray) }
|
fun wb(byteArray: ByteArray) { outputStream.write(byteArray) }
|
||||||
@@ -152,12 +164,8 @@ internal object WriteLayerDataZip {
|
|||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
|
|
||||||
outFile.delete()
|
|
||||||
tempFile.copyTo(outFile, overwrite = true)
|
|
||||||
tempFile.delete()
|
|
||||||
println("Saved map data '$LAYERS_FILENAME' to $saveDirectoryName.")
|
|
||||||
|
|
||||||
return true
|
return outFile
|
||||||
}
|
}
|
||||||
catch (e: IOException) {
|
catch (e: IOException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -166,7 +174,7 @@ internal object WriteLayerDataZip {
|
|||||||
outputStream.close()
|
outputStream.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ Files contained the TerranVirtualDisk is as follows:
|
|||||||
|
|
||||||
(root)
|
(root)
|
||||||
worldinfo0 -- Savegame Metadata (TESV)
|
worldinfo0 -- Savegame Metadata (TESV)
|
||||||
worldinfo1 -- Layer Data (TEMD)
|
Has fixed Entry ID of 32767
|
||||||
worldinfo2 -- Copy of blocks.csv -- will use this from the next load
|
worldinfo1 -- Copy of blocks.csv -- will use this from the next load
|
||||||
worldinfo3 -- Copy of items.csv -- will use this from the next load
|
worldinfo2 -- Copy of items.csv -- will use this from the next load
|
||||||
worldinfo4 -- Copy of materials.csv -- will use this from the next load
|
worldinfo3 -- Copy of materials.csv -- will use this from the next load
|
||||||
|
world[n] -- Layer Data (TEMD); [n] is a serial number of the world (starts at 1)
|
||||||
|
Has fixed Entry ID of [n]
|
||||||
|
(any random number in Hex 32768..ACTORID_MIN - 1) -- Serialised Dynamic Item?
|
||||||
(any random number in Hex ACTORID_MIN..FFFFFFFF) -- Serialised Entity Information (including Player)
|
(any random number in Hex ACTORID_MIN..FFFFFFFF) -- Serialised Entity Information (including Player)
|
||||||
(PLAYER_REF_ID in Hex -- 91A7E2) -- Player Character Information (Serialised--JSON'd--Entity Information)
|
(PLAYER_REF_ID in Hex -- 91A7E2) -- Player Character Information (Serialised--JSON'd--Entity Information)
|
||||||
(51621D) -- The Debug Player (Serialised Entity Information)
|
(51621D) -- The Debug Player (Serialised Entity Information)
|
||||||
|
|||||||
Reference in New Issue
Block a user