Created
January 8, 2025 17:26
-
-
Save jrobinson3k1/b4bcc6154402987dd46e9a9c3b30f30f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // parses a v3 PSG file | |
| object PassiveTreeParser { | |
| fun readData(inputStream: InputStream): SkillTreeGraph { | |
| val data = DataTypeReader(inputStream, order = ByteOrder.LITTLE_ENDIAN).readPassiveTreeData() | |
| val remainingBytes = inputStream.readAllBytes() | |
| require(remainingBytes.isEmpty()) { "$remainingBytes bytes were not processed" } | |
| return data | |
| } | |
| private fun DataTypeReader.readPassiveTreeData(): SkillTreeGraph = | |
| when (val version = readByte().toInt()) { | |
| 3 -> V3Decoder.decode(this) | |
| else -> throw UnsupportedOperationException("Missing decoder for version $version") | |
| } | |
| private object V3Decoder { | |
| fun decode(reader: DataTypeReader): SkillTreeGraph = with(reader) { | |
| val type = readByte() | |
| val orbitCount = readByte() | |
| val orbits = (0 until orbitCount).map { readByte().toUByte() } | |
| val rootNodeCount = readInt() | |
| val rootNodeIds = (0 until rootNodeCount).map { readInt().also { skip(4) } } | |
| val nodeGroupCount = readInt() | |
| val nodeGroups = (0 until nodeGroupCount).map { readNodeGroup() } | |
| return SkillTreeGraph( | |
| version = 3, | |
| type = type.toInt(), | |
| orbits = orbits.map { it.toInt() }, | |
| rootIds = rootNodeIds, | |
| groups = nodeGroups, | |
| ) | |
| } | |
| private fun DataTypeReader.readNodeGroup(): SkillTreeGraph.Group { | |
| val x = readFloat() | |
| val y = readFloat() | |
| val groupAssociationKey = readInt() | |
| val groupBackgroundOverride = readInt() | |
| val isJewelPositionReference = readByte().toInt() != 0 | |
| val nodeCount = readInt() | |
| val nodes = (0 until nodeCount).map { readNode() } | |
| return SkillTreeGraph.Group( | |
| location = x to y, | |
| groupAssociationKey = groupAssociationKey, | |
| groupBackgroundOverride = groupBackgroundOverride, | |
| isJewelPositionReference = isJewelPositionReference, | |
| nodes = nodes, | |
| ) | |
| } | |
| private fun DataTypeReader.readNode(): SkillTreeGraph.NodeRef { | |
| val id = readInt() | |
| val radius = readInt() | |
| val position = readInt() | |
| val connectionCount = readInt() | |
| val nodeConnections = (0 until connectionCount).map { readConnection() } | |
| return SkillTreeGraph.NodeRef( | |
| id = id, | |
| orbit = radius, | |
| orbitIndex = position, | |
| connectedNodeIds = nodeConnections, | |
| ) | |
| } | |
| private fun DataTypeReader.readConnection(): SkillTreeGraph.ConnectionRef { | |
| val nodeId = readInt() | |
| val orbit = readInt() | |
| return SkillTreeGraph.ConnectionRef( | |
| nodeId = nodeId, | |
| orbit = orbit, | |
| ) | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| data class SkillTreeGraph( | |
| val version: Int, | |
| val type: Int, | |
| val orbits: List<Int>, | |
| val rootIds: List<Int>, | |
| val groups: List<Group>, | |
| ) { | |
| data class Group( | |
| val location: Pair<Float, Float>, | |
| val groupAssociationKey: Int, | |
| val groupBackgroundOverride: Int, | |
| val isJewelPositionReference: Boolean, | |
| val nodes: List<NodeRef>, | |
| ) | |
| data class NodeRef( | |
| val id: Int, | |
| val orbit: Int, | |
| val orbitIndex: Int, | |
| val connectedNodeIds: List<ConnectionRef>, | |
| ) | |
| data class ConnectionRef( | |
| val nodeId: Int, | |
| val orbit: Int, | |
| ) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@jrobinson3k1 I started digging into these binaries because I wanted to build a dependency graph, for calculation purposes. But I did end up cobbling a quick visualization for the x,y coords, and it does look a bit wonky, but maybe it is just the code that chatgpt wrote that is wrong idk.
Now that I have all the attribute data, names, icons, dependencies/relationships of the skills ... I can see the whole picture of how to make a script that will find "optimal" builds given different constraints. My worry was that hard-coding all the data manually would quickly become out-of-sync with the actual game, which is why I wanted to read it directly from the game files instead.
And I already had the
datc64decoded, there are several places that have the "schema" for each of the files available. Noted the graph_id column in that database, which is why I wanted to read the psg.