object oriented file system skeleton
so there at least some preparation for java nio filesystem
This commit is contained in:
parent
6cc7ae51a0
commit
e8eed40a73
@ -2,7 +2,11 @@ package ru.dbotthepony.kstarbound
|
|||||||
|
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
|
import java.util.Arrays
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
inline fun <reified T> GsonBuilder.registerTypeAdapter(adapter: TypeAdapter<T>): GsonBuilder {
|
inline fun <reified T> GsonBuilder.registerTypeAdapter(adapter: TypeAdapter<T>): GsonBuilder {
|
||||||
return registerTypeAdapter(T::class.java, adapter)
|
return registerTypeAdapter(T::class.java, adapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> Array<T>.stream(): Stream<T> = Arrays.stream(this)
|
||||||
|
@ -3,10 +3,10 @@ package ru.dbotthepony.kstarbound
|
|||||||
import com.google.gson.*
|
import com.google.gson.*
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.api.IVFS
|
import ru.dbotthepony.kstarbound.api.IStarboundFile
|
||||||
import ru.dbotthepony.kstarbound.api.PhysicalFS
|
import ru.dbotthepony.kstarbound.api.NonExistingFile
|
||||||
import ru.dbotthepony.kstarbound.api.getPathFilename
|
import ru.dbotthepony.kstarbound.api.PhysicalFile
|
||||||
import ru.dbotthepony.kstarbound.api.getPathFolder
|
import ru.dbotthepony.kstarbound.api.explore
|
||||||
import ru.dbotthepony.kstarbound.defs.*
|
import ru.dbotthepony.kstarbound.defs.*
|
||||||
import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition
|
import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.projectile.*
|
import ru.dbotthepony.kstarbound.defs.projectile.*
|
||||||
@ -25,7 +25,6 @@ import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
|||||||
import ru.dbotthepony.kvector.vector.nfloat.Vector2f
|
import ru.dbotthepony.kvector.vector.nfloat.Vector2f
|
||||||
import ru.dbotthepony.kvector.vector.nint.Vector2i
|
import ru.dbotthepony.kvector.vector.nint.Vector2i
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
@ -40,7 +39,7 @@ const val PIXELS_IN_STARBOUND_UNITf = 8.0f
|
|||||||
// class TileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause)
|
// class TileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause)
|
||||||
// class ProjectileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause)
|
// class ProjectileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause)
|
||||||
|
|
||||||
object Starbound : IVFS {
|
object Starbound {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
private val _readingFolder = ThreadLocal<String>()
|
private val _readingFolder = ThreadLocal<String>()
|
||||||
@ -110,20 +109,67 @@ object Starbound : IVFS {
|
|||||||
private set
|
private set
|
||||||
var initialized = false
|
var initialized = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
@Volatile
|
||||||
var terminateLoading = false
|
var terminateLoading = false
|
||||||
|
|
||||||
private val archivePaths = ArrayList<File>()
|
private val archivePaths = ArrayList<File>()
|
||||||
private val fileSystems = ArrayList<IVFS>()
|
private val fileSystems = ArrayList<IStarboundFile>()
|
||||||
|
|
||||||
fun addFilePath(path: File) {
|
fun addFilePath(path: File) {
|
||||||
fileSystems.add(PhysicalFS(path))
|
fileSystems.add(PhysicalFile(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Добавляет уже прочитанный pak
|
|
||||||
*/
|
|
||||||
fun addPak(pak: StarboundPak) {
|
fun addPak(pak: StarboundPak) {
|
||||||
fileSystems.add(pak)
|
fileSystems.add(pak.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exists(path: String): Boolean {
|
||||||
|
@Suppress("name_shadowing")
|
||||||
|
var path = path
|
||||||
|
|
||||||
|
if (path[0] == '/') {
|
||||||
|
path = path.substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (fs in fileSystems) {
|
||||||
|
if (fs.locate(path).exists) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun locate(path: String): IStarboundFile {
|
||||||
|
@Suppress("name_shadowing")
|
||||||
|
var path = path
|
||||||
|
|
||||||
|
if (path[0] == '/') {
|
||||||
|
path = path.substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (fs in fileSystems) {
|
||||||
|
val file = fs.locate(path)
|
||||||
|
|
||||||
|
if (file.exists) {
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NonExistingFile(path.split("/").last(), fullPath = path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun locate(vararg path: String): IStarboundFile {
|
||||||
|
for (p in path) {
|
||||||
|
val get = locate(p)
|
||||||
|
|
||||||
|
if (get.exists) {
|
||||||
|
return get
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NonExistingFile(path[0].split("/").last(), fullPath = path[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,9 +180,11 @@ object Starbound : IVFS {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadJson(path: String): JsonElement {
|
fun loadJson(path: String): JsonElement {
|
||||||
return JsonParser.parseReader(getReader(path))
|
return JsonParser.parseReader(locate(path).reader())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun readDirect(path: String) = locate(path).readDirect()
|
||||||
|
|
||||||
fun getTileDefinition(name: String) = tiles[name]
|
fun getTileDefinition(name: String) = tiles[name]
|
||||||
private val initCallbacks = ArrayList<() -> Unit>()
|
private val initCallbacks = ArrayList<() -> Unit>()
|
||||||
|
|
||||||
@ -198,46 +246,6 @@ object Starbound : IVFS {
|
|||||||
}, "Asset Loader").start()
|
}, "Asset Loader").start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pathExists(path: String): Boolean {
|
|
||||||
for (fs in fileSystems) {
|
|
||||||
if (fs.pathExists(path)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun readOrNull(path: String): ByteBuffer? {
|
|
||||||
for (fs in fileSystems) {
|
|
||||||
if (fs.pathExists(path)) {
|
|
||||||
return fs.read(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun listFiles(path: String): List<String> {
|
|
||||||
val listing = mutableListOf<String>()
|
|
||||||
|
|
||||||
for (fs in fileSystems) {
|
|
||||||
listing.addAll(fs.listFiles(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
return listing
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun listDirectories(path: String): Collection<String> {
|
|
||||||
val listing = mutableListOf<String>()
|
|
||||||
|
|
||||||
for (fs in fileSystems) {
|
|
||||||
listing.addAll(fs.listDirectories(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
return listing
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onInitialize(callback: () -> Unit) {
|
fun onInitialize(callback: () -> Unit) {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
callback()
|
callback()
|
||||||
@ -260,12 +268,12 @@ object Starbound : IVFS {
|
|||||||
readingFolder = "/tiles/materials"
|
readingFolder = "/tiles/materials"
|
||||||
|
|
||||||
for (fs in fileSystems) {
|
for (fs in fileSystems) {
|
||||||
for (listedFile in fs.listAllFilesWithExtension("material")) {
|
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".material") }) {
|
||||||
try {
|
try {
|
||||||
callback("Loading $listedFile")
|
callback("Loading $listedFile")
|
||||||
|
|
||||||
readingFolder = getPathFolder(listedFile)
|
readingFolder = listedFile.computeDirectory()
|
||||||
val tileDef = gson.fromJson(getReader(listedFile), TileDefinition::class.java)
|
val tileDef = gson.fromJson(listedFile.reader(), TileDefinition::class.java)
|
||||||
|
|
||||||
check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" }
|
check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" }
|
||||||
check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" }
|
check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" }
|
||||||
@ -275,6 +283,10 @@ object Starbound : IVFS {
|
|||||||
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
||||||
LOGGER.error("Loading tile file $listedFile", err)
|
LOGGER.error("Loading tile file $listedFile", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (terminateLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,28 +295,32 @@ object Starbound : IVFS {
|
|||||||
|
|
||||||
private fun loadProjectiles(callback: (String) -> Unit) {
|
private fun loadProjectiles(callback: (String) -> Unit) {
|
||||||
for (fs in fileSystems) {
|
for (fs in fileSystems) {
|
||||||
for (listedFile in fs.listAllFilesWithExtension("projectile")) {
|
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".projectile") }) {
|
||||||
try {
|
try {
|
||||||
callback("Loading $listedFile")
|
callback("Loading $listedFile")
|
||||||
|
|
||||||
val def = gson.fromJson(getReader(listedFile), ConfigurableProjectile::class.java).assemble(getPathFolder(listedFile))
|
val def = gson.fromJson(listedFile.reader(), ConfigurableProjectile::class.java).assemble(listedFile.computeDirectory())
|
||||||
check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" }
|
check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" }
|
||||||
projectiles[def.projectileName] = def
|
projectiles[def.projectileName] = def
|
||||||
} catch(err: Throwable) {
|
} catch(err: Throwable) {
|
||||||
//throw ProjectileDefLoadingException("Loading projectile file $listedFile", err)
|
//throw ProjectileDefLoadingException("Loading projectile file $listedFile", err)
|
||||||
LOGGER.error("Loading projectile file $listedFile", err)
|
LOGGER.error("Loading projectile file $listedFile", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (terminateLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadFunctions(callback: (String) -> Unit) {
|
private fun loadFunctions(callback: (String) -> Unit) {
|
||||||
for (fs in fileSystems) {
|
for (fs in fileSystems) {
|
||||||
for (listedFile in fs.listAllFilesWithExtension("functions")) {
|
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".functions") }) {
|
||||||
try {
|
try {
|
||||||
callback("Loading $listedFile")
|
callback("Loading $listedFile")
|
||||||
|
|
||||||
val readObject = loadJson(listedFile) as JsonObject
|
val readObject = JsonParser.parseReader(listedFile.reader()) as JsonObject
|
||||||
|
|
||||||
for (key in readObject.keySet()) {
|
for (key in readObject.keySet()) {
|
||||||
val def = gson.fromJson(readObject[key], JsonFunction::class.java)
|
val def = gson.fromJson(readObject[key], JsonFunction::class.java)
|
||||||
@ -313,22 +329,28 @@ object Starbound : IVFS {
|
|||||||
} catch(err: Throwable) {
|
} catch(err: Throwable) {
|
||||||
LOGGER.error("Loading function file $listedFile", err)
|
LOGGER.error("Loading function file $listedFile", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (terminateLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadParallax(callback: (String) -> Unit) {
|
private fun loadParallax(callback: (String) -> Unit) {
|
||||||
for (fs in fileSystems) {
|
for (fs in fileSystems) {
|
||||||
for (listedFile in fs.listAllFiles("parallax")) {
|
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".parallax") }) {
|
||||||
if (listedFile.endsWith(".parallax")) {
|
try {
|
||||||
try {
|
callback("Loading $listedFile")
|
||||||
callback("Loading $listedFile")
|
|
||||||
|
|
||||||
val def = gson.fromJson(getReader(listedFile), ParallaxPrototype::class.java)
|
val def = gson.fromJson(listedFile.reader(), ParallaxPrototype::class.java)
|
||||||
parallax[getPathFilename(listedFile).substringBefore('.')] = def
|
parallax[listedFile.name.substringBefore('.')] = def
|
||||||
} catch(err: Throwable) {
|
} catch(err: Throwable) {
|
||||||
LOGGER.error("Loading parallax file $listedFile", err)
|
LOGGER.error("Loading parallax file $listedFile", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (terminateLoading) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,12 +360,12 @@ object Starbound : IVFS {
|
|||||||
readingFolder = "/tiles/materials"
|
readingFolder = "/tiles/materials"
|
||||||
|
|
||||||
for (fs in fileSystems) {
|
for (fs in fileSystems) {
|
||||||
for (listedFile in fs.listAllFilesWithExtension("matmod")) {
|
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".matmod") }) {
|
||||||
try {
|
try {
|
||||||
callback("Loading $listedFile")
|
callback("Loading $listedFile")
|
||||||
|
|
||||||
readingFolder = getPathFolder(listedFile)
|
readingFolder = listedFile.computeDirectory()
|
||||||
val tileDef = gson.fromJson(getReader(listedFile), MaterialModifier::class.java)
|
val tileDef = gson.fromJson(listedFile.reader(), MaterialModifier::class.java)
|
||||||
|
|
||||||
check(tileModifiers[tileDef.modName] == null) { "Already has material with name ${tileDef.modName} loaded!" }
|
check(tileModifiers[tileDef.modName] == null) { "Already has material with name ${tileDef.modName} loaded!" }
|
||||||
check(tileModifiersByID[tileDef.modId] == null) { "Already has material with ID ${tileDef.modId} loaded!" }
|
check(tileModifiersByID[tileDef.modId] == null) { "Already has material with ID ${tileDef.modId} loaded!" }
|
||||||
@ -353,6 +375,10 @@ object Starbound : IVFS {
|
|||||||
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
||||||
LOGGER.error("Loading tile modifier file $listedFile", err)
|
LOGGER.error("Loading tile modifier file $listedFile", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (terminateLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,12 +387,12 @@ object Starbound : IVFS {
|
|||||||
|
|
||||||
private fun loadLiquidDefinitions(callback: (String) -> Unit) {
|
private fun loadLiquidDefinitions(callback: (String) -> Unit) {
|
||||||
for (fs in fileSystems) {
|
for (fs in fileSystems) {
|
||||||
for (listedFile in fs.listAllFilesWithExtension("liquid")) {
|
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".liquid") }) {
|
||||||
try {
|
try {
|
||||||
callback("Loading $listedFile")
|
callback("Loading $listedFile")
|
||||||
|
|
||||||
readingFolder = getPathFolder(listedFile)
|
readingFolder = listedFile.computeDirectory()
|
||||||
val liquidDef = gson.fromJson(getReader(listedFile), LiquidDefinition::class.java)
|
val liquidDef = gson.fromJson(listedFile.reader(), LiquidDefinition::class.java)
|
||||||
|
|
||||||
check(liquid.put(liquidDef.name, liquidDef) == null) { "Already has liquid with name ${liquidDef.name} loaded!" }
|
check(liquid.put(liquidDef.name, liquidDef) == null) { "Already has liquid with name ${liquidDef.name} loaded!" }
|
||||||
check(liquidByID.put(liquidDef.liquidId, liquidDef) == null) { "Already has liquid with ID ${liquidDef.liquidId} loaded!" }
|
check(liquidByID.put(liquidDef.liquidId, liquidDef) == null) { "Already has liquid with ID ${liquidDef.liquidId} loaded!" }
|
||||||
@ -374,6 +400,10 @@ object Starbound : IVFS {
|
|||||||
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
||||||
LOGGER.error("Loading liquid definition file $listedFile", err)
|
LOGGER.error("Loading liquid definition file $listedFile", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (terminateLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,228 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.api
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.io.StarboundPak
|
||||||
|
import ru.dbotthepony.kstarbound.stream
|
||||||
|
import java.io.*
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
interface IStarboundFile {
|
||||||
|
val exists: Boolean
|
||||||
|
val isDirectory: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* null if root
|
||||||
|
*/
|
||||||
|
val parent: IStarboundFile?
|
||||||
|
val isFile: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* null if not a directory
|
||||||
|
*/
|
||||||
|
val children: Map<String, IStarboundFile>?
|
||||||
|
val name: String
|
||||||
|
|
||||||
|
fun orNull(): IStarboundFile? = if (exists) this else null
|
||||||
|
|
||||||
|
fun computeFullPath(): String {
|
||||||
|
var path = name
|
||||||
|
var parent = parent
|
||||||
|
|
||||||
|
while (parent != null) {
|
||||||
|
path = parent.name + "/" + path
|
||||||
|
parent = parent.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
fun computeDirectory(): String {
|
||||||
|
var path = ""
|
||||||
|
var parent = parent
|
||||||
|
|
||||||
|
while (parent != null) {
|
||||||
|
if (path == "")
|
||||||
|
path = parent.name
|
||||||
|
else
|
||||||
|
path = parent.name + "/" + path
|
||||||
|
|
||||||
|
parent = parent.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
fun locate(path: String): IStarboundFile {
|
||||||
|
@Suppress("name_shadowing")
|
||||||
|
val path = path.trim()
|
||||||
|
|
||||||
|
if (path == "" || path == ".") {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path == "..") {
|
||||||
|
return parent ?: NonExistingFile(computeFullPath() + "/..")
|
||||||
|
}
|
||||||
|
|
||||||
|
val split = path.lowercase().split("/")
|
||||||
|
var file = this
|
||||||
|
|
||||||
|
for (splitIndex in split.indices) {
|
||||||
|
if (split[splitIndex].isEmpty()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val children = file.children ?: return NonExistingFile(name = split.last(), fullPath = computeFullPath() + "/" + path)
|
||||||
|
val find = children[split[splitIndex]]
|
||||||
|
|
||||||
|
if (find is StarboundPak.SBDirectory) {
|
||||||
|
file = find
|
||||||
|
} else if (find is StarboundPak.SBFile) {
|
||||||
|
if (splitIndex + 1 != split.size) {
|
||||||
|
return NonExistingFile(name = split.last(), fullPath = computeFullPath() + "/" + path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return find
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NonExistingFile(name = split.last(), fullPath = computeFullPath() + "/" + path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalStateException if file is a directory
|
||||||
|
* @throws FileNotFoundException if file does not exist
|
||||||
|
*/
|
||||||
|
fun open(): InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalStateException if file is a directory
|
||||||
|
* @throws FileNotFoundException if file does not exist
|
||||||
|
*/
|
||||||
|
fun reader(): Reader = InputStreamReader(open())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalStateException if file is a directory
|
||||||
|
* @throws FileNotFoundException if file does not exist
|
||||||
|
*/
|
||||||
|
fun readJson(): JsonElement = JsonParser.parseReader(reader())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalStateException if file is a directory
|
||||||
|
* @throws FileNotFoundException if file does not exist
|
||||||
|
*/
|
||||||
|
fun read(): ByteBuffer {
|
||||||
|
val stream = open()
|
||||||
|
val read = stream.readAllBytes()
|
||||||
|
stream.close()
|
||||||
|
return ByteBuffer.wrap(read)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalStateException if file is a directory
|
||||||
|
* @throws FileNotFoundException if file does not exist
|
||||||
|
*/
|
||||||
|
fun readDirect(): ByteBuffer {
|
||||||
|
val read = read()
|
||||||
|
val buf = ByteBuffer.allocateDirect(read.capacity())
|
||||||
|
|
||||||
|
read.position(0)
|
||||||
|
|
||||||
|
for (i in 0 until read.capacity()) {
|
||||||
|
buf.put(read[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.position(0)
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* non existent file, without any context
|
||||||
|
*/
|
||||||
|
companion object : IStarboundFile {
|
||||||
|
override val exists: Boolean
|
||||||
|
get() = false
|
||||||
|
override val isDirectory: Boolean
|
||||||
|
get() = false
|
||||||
|
override val parent: IStarboundFile?
|
||||||
|
get() = null
|
||||||
|
override val isFile: Boolean
|
||||||
|
get() = false
|
||||||
|
override val children: Map<String, IStarboundFile>?
|
||||||
|
get() = null
|
||||||
|
override val name: String
|
||||||
|
get() = ""
|
||||||
|
|
||||||
|
override fun open(): InputStream {
|
||||||
|
throw FileNotFoundException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonExistingFile(
|
||||||
|
override val name: String,
|
||||||
|
override val parent: IStarboundFile? = null,
|
||||||
|
val fullPath: String? = null
|
||||||
|
) : IStarboundFile {
|
||||||
|
override val isDirectory: Boolean
|
||||||
|
get() = false
|
||||||
|
override val isFile: Boolean
|
||||||
|
get() = false
|
||||||
|
override val children: Map<String, IStarboundFile>?
|
||||||
|
get() = null
|
||||||
|
|
||||||
|
override val exists: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
override fun open(): InputStream {
|
||||||
|
throw FileNotFoundException("File ${fullPath ?: computeFullPath()} does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun IStarboundFile.explore(): Stream<IStarboundFile> {
|
||||||
|
val children = children ?: return Stream.of(this)
|
||||||
|
return Stream.concat(Stream.of(this), children.values.stream().flatMap { it.explore() })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPathFolder(path: String): String {
|
||||||
|
return path.substringBeforeLast('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPathFilename(path: String): String {
|
||||||
|
return path.substringAfterLast('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
class PhysicalFile(val real: File) : IStarboundFile {
|
||||||
|
override val exists: Boolean
|
||||||
|
get() = real.exists()
|
||||||
|
override val isDirectory: Boolean
|
||||||
|
get() = real.isDirectory
|
||||||
|
override val parent: PhysicalFile?
|
||||||
|
get() {
|
||||||
|
return PhysicalFile(real.parentFile ?: return null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isFile: Boolean
|
||||||
|
get() = real.isFile
|
||||||
|
override val children: Map<String, PhysicalFile>?
|
||||||
|
get() {
|
||||||
|
return real.list()?.stream()?.map { it to PhysicalFile(File(it)) }?.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
|
||||||
|
}
|
||||||
|
override val name: String
|
||||||
|
get() = real.name
|
||||||
|
|
||||||
|
override fun open(): InputStream {
|
||||||
|
return BufferedInputStream(real.inputStream())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "PhysicalFile[$real]"
|
||||||
|
}
|
||||||
|
}
|
@ -1,164 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.api
|
|
||||||
|
|
||||||
import java.io.*
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
interface IVFS {
|
|
||||||
fun pathExists(path: String): Boolean
|
|
||||||
|
|
||||||
fun pathExistsOrElse(path: String, orElse: String): String {
|
|
||||||
if (pathExists(path))
|
|
||||||
return path
|
|
||||||
|
|
||||||
return orElse
|
|
||||||
}
|
|
||||||
|
|
||||||
fun firstExisting(vararg pathList: String): String {
|
|
||||||
for (path in pathList)
|
|
||||||
if (pathExists(path))
|
|
||||||
return path
|
|
||||||
|
|
||||||
throw FileNotFoundException("Unable to find any of files specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun firstExistingOrNull(vararg pathList: String): String? {
|
|
||||||
for (path in pathList)
|
|
||||||
if (pathExists(path))
|
|
||||||
return path
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun read(path: String): ByteBuffer {
|
|
||||||
return readOrNull(path) ?: throw FileNotFoundException("No such file $path")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readOrNull(path: String): ByteBuffer?
|
|
||||||
|
|
||||||
fun readString(path: String): String {
|
|
||||||
return read(path).array().toString(Charsets.UTF_8)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getReader(path: String): Reader {
|
|
||||||
return InputStreamReader(ByteArrayInputStream(read(path).array()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun listFiles(path: String): Collection<String>
|
|
||||||
fun listDirectories(path: String): Collection<String>
|
|
||||||
|
|
||||||
fun listFilesAndDirectories(path: String): Collection<String> {
|
|
||||||
val a = listFiles(path)
|
|
||||||
val b = listDirectories(path)
|
|
||||||
|
|
||||||
return ArrayList<String>(a.size + b.size).also { it.addAll(a); it.addAll(b) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun listAllFilesWithExtension(extension: String): Collection<String> {
|
|
||||||
val listing = ArrayList<String>()
|
|
||||||
val ext = ".$extension"
|
|
||||||
|
|
||||||
for (listedFile in listAllFiles("")) {
|
|
||||||
if (listedFile.endsWith(ext)) {
|
|
||||||
listing.add(listedFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return listing
|
|
||||||
}
|
|
||||||
|
|
||||||
fun listAllFiles(path: String): Collection<String> {
|
|
||||||
val lists = mutableListOf<Collection<String>>()
|
|
||||||
|
|
||||||
lists.add(listFiles(path))
|
|
||||||
|
|
||||||
for (dir in listDirectories(path)) {
|
|
||||||
lists.add(listAllFiles(dir))
|
|
||||||
}
|
|
||||||
|
|
||||||
// flatten медленный
|
|
||||||
// return lists.flatten()
|
|
||||||
|
|
||||||
var size = 0
|
|
||||||
|
|
||||||
for (list in lists) {
|
|
||||||
size += list.size
|
|
||||||
}
|
|
||||||
|
|
||||||
return ArrayList<String>(size).also { lists.forEach(it::addAll) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readDirect(path: String): ByteBuffer {
|
|
||||||
val read = read(path)
|
|
||||||
|
|
||||||
val buf = ByteBuffer.allocateDirect(read.capacity())
|
|
||||||
|
|
||||||
read.position(0)
|
|
||||||
|
|
||||||
for (i in 0 until read.capacity()) {
|
|
||||||
buf.put(read[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.position(0)
|
|
||||||
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPathFolder(path: String): String {
|
|
||||||
return path.substringBeforeLast('/')
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPathFilename(path: String): String {
|
|
||||||
return path.substringAfterLast('/')
|
|
||||||
}
|
|
||||||
|
|
||||||
class PhysicalFS(root: File) : IVFS {
|
|
||||||
val root: File = root.absoluteFile
|
|
||||||
|
|
||||||
override fun pathExists(path: String): Boolean {
|
|
||||||
if (path.contains("..")) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return File(root.absolutePath, path).exists()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun readOrNull(path: String): ByteBuffer? {
|
|
||||||
if (path.contains("..")) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val fpath = File(root.path, path)
|
|
||||||
|
|
||||||
if (!fpath.exists()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val reader = fpath.inputStream()
|
|
||||||
val buf = ByteBuffer.allocate(reader.channel.size().toInt())
|
|
||||||
reader.read(buf.array())
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun listFiles(path: String): List<String> {
|
|
||||||
if (path.contains("..")) {
|
|
||||||
return listOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
val fpath = File(root.absolutePath, path)
|
|
||||||
return fpath.listFiles()?.filter { it.isFile }?.map {
|
|
||||||
it.path.replace('\\', '/').substring(root.path.length)
|
|
||||||
} ?: return listOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun listDirectories(path: String): Collection<String> {
|
|
||||||
if (path.contains("..")) {
|
|
||||||
return listOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
val fpath = File(root.absolutePath, path)
|
|
||||||
return fpath.listFiles()?.filter { it.isDirectory }?.map {
|
|
||||||
it.path.replace('\\', '/').substring(root.path.length)
|
|
||||||
} ?: return listOf()
|
|
||||||
}
|
|
||||||
}
|
|
@ -445,7 +445,7 @@ class GLStateTracker {
|
|||||||
|
|
||||||
fun loadNamedTexture(path: String, memoryFormat: Int, fileFormat: Int): GLTexture2D {
|
fun loadNamedTexture(path: String, memoryFormat: Int, fileFormat: Int): GLTexture2D {
|
||||||
return named2DTextures.computeIfAbsent(path) {
|
return named2DTextures.computeIfAbsent(path) {
|
||||||
if (!Starbound.pathExists(path)) {
|
if (!Starbound.exists(path)) {
|
||||||
throw FileNotFoundException("Unable to locate $path")
|
throw FileNotFoundException("Unable to locate $path")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +455,7 @@ class GLStateTracker {
|
|||||||
|
|
||||||
fun loadNamedTexture(path: String): GLTexture2D {
|
fun loadNamedTexture(path: String): GLTexture2D {
|
||||||
return named2DTextures.computeIfAbsent(path) {
|
return named2DTextures.computeIfAbsent(path) {
|
||||||
if (!Starbound.pathExists(path)) {
|
if (!Starbound.exists(path)) {
|
||||||
throw FileNotFoundException("Unable to locate $path")
|
throw FileNotFoundException("Unable to locate $path")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,7 +473,7 @@ class GLStateTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return named2DTextures.computeIfAbsent(path) {
|
return named2DTextures.computeIfAbsent(path) {
|
||||||
if (!Starbound.pathExists(path)) {
|
if (!Starbound.exists(path)) {
|
||||||
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
||||||
return@computeIfAbsent named2DTextures[missingTexturePath]!!
|
return@computeIfAbsent named2DTextures[missingTexturePath]!!
|
||||||
}
|
}
|
||||||
@ -489,7 +489,7 @@ class GLStateTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return named2DTextures.computeIfAbsent(path) {
|
return named2DTextures.computeIfAbsent(path) {
|
||||||
if (!Starbound.pathExists(path)) {
|
if (!Starbound.exists(path)) {
|
||||||
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
||||||
return@computeIfAbsent named2DTextures[missingTexturePath]!!
|
return@computeIfAbsent named2DTextures[missingTexturePath]!!
|
||||||
}
|
}
|
||||||
|
@ -266,7 +266,7 @@ interface IFrameGrid {
|
|||||||
if (splitLast.size == 1) {
|
if (splitLast.size == 1) {
|
||||||
// имя уже абсолютное
|
// имя уже абсолютное
|
||||||
return cache.computeIfAbsent(path) {
|
return cache.computeIfAbsent(path) {
|
||||||
val frames = Starbound.firstExistingOrNull("$path.frames", "${splitPath.joinToString("/")}/default.frames")
|
val frames = Starbound.locate("$path.frames", "${splitPath.joinToString("/")}/default.frames").orNull()
|
||||||
|
|
||||||
if (weak && frames == null) {
|
if (weak && frames == null) {
|
||||||
LOGGER.warn("Expected animated texture at {}, but .frames metafile is missing.", path)
|
LOGGER.warn("Expected animated texture at {}, but .frames metafile is missing.", path)
|
||||||
@ -274,14 +274,14 @@ interface IFrameGrid {
|
|||||||
return@computeIfAbsent singleFrame("$path.png", weakSize)
|
return@computeIfAbsent singleFrame("$path.png", weakSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
return@computeIfAbsent fromJson(Starbound.loadJson(frames ?: throw FileNotFoundException("Unable to find .frames meta for $path")) as JsonObject, path)
|
return@computeIfAbsent fromJson((frames?.readJson() ?: throw FileNotFoundException("Unable to find .frames meta for $path")) as JsonObject, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val newPath = "${splitPath.joinToString("/")}/${splitLast[0]}"
|
val newPath = "${splitPath.joinToString("/")}/${splitLast[0]}"
|
||||||
|
|
||||||
return cache.computeIfAbsent(newPath) {
|
return cache.computeIfAbsent(newPath) {
|
||||||
val frames = Starbound.firstExistingOrNull("$newPath.frames", "${splitPath.joinToString("/")}/default.frames")
|
val frames = Starbound.locate("$newPath.frames", "${splitPath.joinToString("/")}/default.frames").orNull()
|
||||||
|
|
||||||
if (weak && frames == null) {
|
if (weak && frames == null) {
|
||||||
LOGGER.warn("Expected animated texture at {}, but .frames metafile is missing.", newPath)
|
LOGGER.warn("Expected animated texture at {}, but .frames metafile is missing.", newPath)
|
||||||
@ -289,7 +289,7 @@ interface IFrameGrid {
|
|||||||
return@computeIfAbsent singleFrame("$newPath.png", weakSize)
|
return@computeIfAbsent singleFrame("$newPath.png", weakSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
return@computeIfAbsent fromJson(Starbound.loadJson(frames ?: throw FileNotFoundException("Unable to find .frames meta for $path")) as JsonObject, newPath)
|
return@computeIfAbsent fromJson((frames?.readJson() ?: throw FileNotFoundException("Unable to find .frames meta for $path")) as JsonObject, newPath)
|
||||||
}
|
}
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
throw MalformedFrameGridException("Reading animated texture definition $path", err)
|
throw MalformedFrameGridException("Reading animated texture definition $path", err)
|
||||||
|
@ -172,7 +172,7 @@ class ActionConfig : IConfigurableAction {
|
|||||||
|
|
||||||
override fun configure(directory: String): IActionOnReap {
|
override fun configure(directory: String): IActionOnReap {
|
||||||
return cache.computeIfAbsent(ensureAbsolutePath(file, directory)) {
|
return cache.computeIfAbsent(ensureAbsolutePath(file, directory)) {
|
||||||
if (!Starbound.pathExists(it)) {
|
if (!Starbound.exists(it)) {
|
||||||
LOGGER.error("Config $it does not exist")
|
LOGGER.error("Config $it does not exist")
|
||||||
return@computeIfAbsent CActionConfig(file, null)
|
return@computeIfAbsent CActionConfig(file, null)
|
||||||
}
|
}
|
||||||
|
@ -300,7 +300,7 @@ data class RenderTemplate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return cache.computeIfAbsent(path) {
|
return cache.computeIfAbsent(path) {
|
||||||
return@computeIfAbsent Starbound.gson.fromJson(Starbound.getReader(it), RenderTemplate::class.java)
|
return@computeIfAbsent Starbound.gson.fromJson(Starbound.locate(it).reader(), RenderTemplate::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,134 +1,137 @@
|
|||||||
package ru.dbotthepony.kstarbound.io
|
package ru.dbotthepony.kstarbound.io
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
||||||
import ru.dbotthepony.kstarbound.api.IVFS
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||||
import java.io.*
|
import ru.dbotthepony.kstarbound.api.IStarboundFile
|
||||||
|
import java.io.BufferedInputStream
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.RandomAccessFile
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.channels.Channels
|
import java.nio.channels.Channels
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.collections.HashMap
|
|
||||||
|
|
||||||
private fun readHeader(reader: RandomAccessFile, required: Int) {
|
private fun readHeader(reader: RandomAccessFile, required: Int) {
|
||||||
val read = reader.read()
|
val read = reader.read()
|
||||||
require(read == required) { "Bad Starbound Pak header, expected $required, got $read" }
|
require(read == required) { "Bad Starbound Pak header, expected $required, got $read" }
|
||||||
}
|
}
|
||||||
|
|
||||||
class StarboundPakFile(
|
class StarboundPak(val path: File, callback: (finished: Boolean, status: String) -> Unit = { _, _ -> }) : Closeable {
|
||||||
val storage: StarboundPak,
|
internal inner class SBDirectory(
|
||||||
val name: String,
|
override val name: String,
|
||||||
val offset: Long,
|
override val parent: IStarboundFile?,
|
||||||
val length: Long
|
) : IStarboundFile {
|
||||||
) {
|
override val exists: Boolean
|
||||||
val directoryName: String
|
get() = true
|
||||||
val directoryHiearchy: Array<String>
|
|
||||||
val fileName: String
|
|
||||||
|
|
||||||
init {
|
override val isDirectory: Boolean
|
||||||
val split = name.substring(1).split('/').toMutableList()
|
get() = true
|
||||||
fileName = split.last()
|
|
||||||
split.removeAt(split.size - 1)
|
|
||||||
directoryHiearchy = split.toTypedArray()
|
|
||||||
directoryName = split.joinToString("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun read(): ByteBuffer {
|
override val isFile: Boolean
|
||||||
val buf = ByteBuffer.allocate(length.toInt())
|
get() = false
|
||||||
storage.reader.seek(offset)
|
|
||||||
storage.reader.readFully(buf.array())
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
private var frozen = false
|
||||||
return name
|
private val innerChildren = Object2ObjectOpenHashMap<String, IStarboundFile>()
|
||||||
}
|
|
||||||
|
|
||||||
fun readString(): String {
|
fun put(value: IStarboundFile) {
|
||||||
return read().array().toString(Charsets.UTF_8)
|
check(!frozen) { "Can't put, already frozen!" }
|
||||||
}
|
innerChildren[value.name] = value
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun read(storage: StarboundPak, reader: RandomAccessFile): StarboundPakFile {
|
|
||||||
val readLength = reader.read()
|
|
||||||
//val name = reader.readASCIIString(readLength).lowercase()
|
|
||||||
val name = reader.readASCIIString(readLength)
|
|
||||||
require(name[0] == '/') { "$name appears to be not an absolute filename" }
|
|
||||||
val offset = reader.readLong()
|
|
||||||
val length = reader.readLong()
|
|
||||||
|
|
||||||
return StarboundPakFile(storage, name.intern(), offset, length)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun read(storage: StarboundPak, reader: DataInputStream): StarboundPakFile {
|
fun subdir(name: String): SBDirectory {
|
||||||
val readLength = reader.readVarInt()
|
check(!frozen) { "Can't subdir, already frozen!" }
|
||||||
val name = reader.readASCIIString(readLength)
|
require(name != "") { "Empty directory name provided" }
|
||||||
require(name[0] == '/') { "$name appears to be not an absolute filename" }
|
return innerChildren.computeIfAbsent(name, Object2ObjectFunction { SBDirectory(it as String, this) }) as? SBDirectory ?: throw IllegalStateException("$name already exists (in ${computeFullPath()})")
|
||||||
val offset = reader.readLong()
|
|
||||||
val length = reader.readLong()
|
|
||||||
|
|
||||||
return StarboundPakFile(storage, name.intern(), offset, length)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StarboundPakDirectory(val name: String, val parent: StarboundPakDirectory? = null) {
|
override val children: Map<String, IStarboundFile> = Collections.unmodifiableMap(innerChildren)
|
||||||
val files = HashMap<String, StarboundPakFile>()
|
|
||||||
val directories = HashMap<String, StarboundPakDirectory>()
|
|
||||||
|
|
||||||
fun resolve(path: Array<String>, level: Int = 0): StarboundPakDirectory {
|
fun freeze() {
|
||||||
if (path.size == level)
|
check(!frozen) { "Already frozen" }
|
||||||
return this
|
frozen = true
|
||||||
|
|
||||||
if (level == 0 && path[0] == "" && name == "/")
|
for (children in innerChildren.values) {
|
||||||
return resolve(path, 1)
|
if (children is SBDirectory) {
|
||||||
|
children.freeze()
|
||||||
if (directories.containsKey(path[level])) {
|
}
|
||||||
return directories[path[level]]!!.resolve(path, level + 1)
|
|
||||||
} else {
|
|
||||||
val dir = StarboundPakDirectory(path[level], this)
|
|
||||||
directories[path[level]] = dir
|
|
||||||
return dir.resolve(path, level + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getFile(name: String) = files[name]
|
|
||||||
fun getDirectory(name: String) = directories[name]
|
|
||||||
|
|
||||||
fun listFiles(): Collection<StarboundPakFile> = Collections.unmodifiableCollection(files.values)
|
|
||||||
fun listDirectories(): Collection<StarboundPakDirectory> = Collections.unmodifiableCollection(directories.values)
|
|
||||||
|
|
||||||
fun writeFile(file: StarboundPakFile) {
|
|
||||||
files[file.name.split('/').last()] = file
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fullName(): String {
|
|
||||||
var build = name
|
|
||||||
var getParent = parent
|
|
||||||
|
|
||||||
while (getParent != null) {
|
|
||||||
if (getParent.parent != null) {
|
|
||||||
build = "${getParent.name}/$build"
|
|
||||||
} else {
|
|
||||||
build = "/$build"
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getParent = getParent.parent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return build
|
override fun open(): InputStream {
|
||||||
|
throw IllegalStateException("${computeFullPath()} is a directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "SBDirectory[${computeFullPath()} @ $path]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
internal inner class SBFile(
|
||||||
return fullName()
|
override val name: String,
|
||||||
}
|
override val parent: IStarboundFile?,
|
||||||
}
|
val offset: Long,
|
||||||
|
val length: Long
|
||||||
|
) : IStarboundFile {
|
||||||
|
override val exists: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val isDirectory: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
override val isFile: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val children: Map<String, IStarboundFile>?
|
||||||
|
get() = null
|
||||||
|
|
||||||
|
override fun open(): InputStream {
|
||||||
|
return object : InputStream() {
|
||||||
|
private var innerOffset = 0L
|
||||||
|
|
||||||
|
override fun read(): Int {
|
||||||
|
if (innerOffset >= length) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.seek(innerOffset + offset)
|
||||||
|
innerOffset++
|
||||||
|
return reader.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(b: ByteArray, off: Int, len: Int): Int {
|
||||||
|
Objects.checkFromIndexSize(off, len, b.size)
|
||||||
|
|
||||||
|
// ok
|
||||||
|
if (len == 0)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
val readMax = len.coerceAtMost((length - innerOffset).toInt())
|
||||||
|
|
||||||
|
if (readMax <= 0)
|
||||||
|
return -1
|
||||||
|
|
||||||
|
reader.seek(innerOffset + offset)
|
||||||
|
val readBytes = reader.read(b, off, readMax)
|
||||||
|
|
||||||
|
if (readBytes == -1) {
|
||||||
|
throw RuntimeException("Unexpected EOF, want to read $readMax bytes from starting $offset in $path")
|
||||||
|
}
|
||||||
|
|
||||||
|
innerOffset += readBytes
|
||||||
|
return readBytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "SBFile[${computeFullPath()} @ $path]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class StarboundPak(val path: File, callback: (finished: Boolean, status: String) -> Unit = { _, _ -> }) : Closeable, IVFS {
|
|
||||||
val reader = RandomAccessFile(path, "r")
|
val reader = RandomAccessFile(path, "r")
|
||||||
private val filesByExtension = Object2ObjectArrayMap<String, ArrayList<StarboundPakFile>>()
|
|
||||||
private val filesByExtensionPath = Object2ObjectArrayMap<String, ArrayList<String>>()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
readHeader(reader, 0x53) // S
|
readHeader(reader, 0x53) // S
|
||||||
@ -163,75 +166,60 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String)
|
|||||||
// сразу за метаданными идёт количество файлов внутри данного pak в формате Big Endian variable int
|
// сразу за метаданными идёт количество файлов внутри данного pak в формате Big Endian variable int
|
||||||
val indexNodeCount = reader.readVarLong()
|
val indexNodeCount = reader.readVarLong()
|
||||||
|
|
||||||
private val _indexNodes = HashMap<String, StarboundPakFile>()
|
val root: IStarboundFile = SBDirectory("", null)
|
||||||
val indexNodes: Map<String, StarboundPakFile> = Collections.unmodifiableMap(_indexNodes)
|
|
||||||
private val _indexNodesLowercase = HashMap<String, StarboundPakFile>()
|
|
||||||
val indexNodesLowercase: Map<String, StarboundPakFile> = Collections.unmodifiableMap(_indexNodesLowercase)
|
|
||||||
val root = StarboundPakDirectory("/")
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Сразу же за количеством файлов идут сами файлы в формате
|
// Сразу же за количеством файлов идут сами файлы в формате
|
||||||
// byte (длинна имени файла)
|
// VarInt (длинна имени файла)
|
||||||
// byte[] (utf-8 имя файла)
|
// byte[] (utf-8 имя файла)
|
||||||
// long (offset от начала файла)
|
// long (offset от начала файла)
|
||||||
// long (длинна файла)
|
// long (длина файла)
|
||||||
val stream = DataInputStream(BufferedInputStream(Channels.newInputStream(reader.channel)))
|
val stream = DataInputStream(BufferedInputStream(Channels.newInputStream(reader.channel)))
|
||||||
|
|
||||||
for (i in 0 until indexNodeCount) {
|
for (i in 0 until indexNodeCount) {
|
||||||
|
var name: String? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
callback(false, "Reading index node $i")
|
callback(false, "Reading index node $i")
|
||||||
val read = StarboundPakFile.read(this, stream)
|
|
||||||
|
|
||||||
if (read.offset > reader.length()) {
|
val readLength = stream.readVarInt()
|
||||||
throw IndexOutOfBoundsException("Garbage offset at index $i: ${read.offset}")
|
name = stream.readASCIIString(readLength)
|
||||||
|
require(name[0] == '/') { "index node at $i with '$name' appears to be not an absolute filename" }
|
||||||
|
val offset = stream.readLong()
|
||||||
|
val length = stream.readLong()
|
||||||
|
|
||||||
|
if (offset > reader.length()) {
|
||||||
|
throw IndexOutOfBoundsException("Garbage offset at index $i: ${offset}")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (read.length > reader.length()) {
|
if (length + offset > reader.length()) {
|
||||||
throw IndexOutOfBoundsException("Garbage length at index $i: ${read.length}")
|
throw IndexOutOfBoundsException("Garbage offset + length at index $i: ${length + offset}")
|
||||||
}
|
}
|
||||||
|
|
||||||
_indexNodes[read.name] = read
|
|
||||||
// Starbound игнорирует регистр букв когда ищет пути, даже внутри pak архивов
|
// Starbound игнорирует регистр букв когда ищет пути, даже внутри pak архивов
|
||||||
_indexNodesLowercase[read.name.lowercase()] = read
|
val split = name.lowercase().split("/")
|
||||||
|
var parent = root as SBDirectory
|
||||||
|
|
||||||
root.resolve(read.directoryHiearchy).writeFile(read)
|
for (splitIndex in 1 until split.size - 1) {
|
||||||
|
parent = parent.subdir(split[splitIndex])
|
||||||
val last = read.name.substringAfterLast('/').substringAfterLast('.', "")
|
|
||||||
|
|
||||||
if (last != "") {
|
|
||||||
filesByExtension.computeIfAbsent(last, Object2ObjectFunction { ArrayList() }).add(read)
|
|
||||||
filesByExtensionPath.computeIfAbsent(last, Object2ObjectFunction { ArrayList() }).add(read.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parent.put(SBFile(split.last(), parent, offset, length))
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
throw IOException("Reading index node at $i", err)
|
if (name == null) {
|
||||||
|
throw IOException("Reading index node at $i", err)
|
||||||
|
} else {
|
||||||
|
throw IOException("Reading index node at $i ($name)", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callback(false, "Freezing virtual file system")
|
||||||
|
(root as SBDirectory).freeze()
|
||||||
callback(true, "Reading indexes finished")
|
callback(true, "Reading indexes finished")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun listFiles(path: String): Collection<String> {
|
|
||||||
return root.resolve(path.split("/").toTypedArray()).listFiles().map { it.name }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun listDirectories(path: String): Collection<String> {
|
|
||||||
return root.resolve(path.split("/").toTypedArray()).listDirectories().map { it.fullName() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pathExists(path: String): Boolean {
|
|
||||||
return _indexNodesLowercase.containsKey(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun readOrNull(path: String): ByteBuffer? {
|
|
||||||
val node = _indexNodesLowercase[path] ?: return null
|
|
||||||
return node.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
reader.close()
|
reader.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun listAllFilesWithExtension(extension: String): Collection<String> {
|
|
||||||
return filesByExtensionPath[extension]?.let(Collections::unmodifiableList) ?: listOf()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user