Skip to content

Instantly share code, notes, and snippets.

@dmitmel
Last active January 14, 2022 21:49
Show Gist options
  • Save dmitmel/c854669da3150b2e9c9398470ff89552 to your computer and use it in GitHub Desktop.
Save dmitmel/c854669da3150b2e9c9398470ff89552 to your computer and use it in GitHub Desktop.

This script converts a .world file into a .shipworld file, which will (mostly) be accepted by the game, meaning that you can now have a planet as your entire ship. You will still have to use cheats to add in the necessary ship equipement (SAIL console, fuel tank, captain's chair), but they will all work after placing them down in the world.

The script needs the library py-starbound and sbutils to be installed. Version 1.0.0 of py-starbound has been confirmed to work, but the deal with sbutils is a little bit complicated. I got it to work on the commit https://github.com/xhebox/sbutils/commit/c5d83b0247cacb0789d1e818af74f3c41dc4bbc2 (the latest commit on the master branch as of writing), but it needed the following patch:

diff --git a/lib/packet/connect_failure.go b/lib/packet/connect_failure.go
index 8253a3f..d5117d0 100644
--- a/lib/packet/connect_failure.go
+++ b/lib/packet/connect_failure.go
@@ -2,7 +2,7 @@ package packet

 import (
 	"github.com/xhebox/bstruct/byteorder"
-	. "github.com/xhebox/sbutils/lib/common"
+	// . "github.com/xhebox/sbutils/lib/common"
 )

 type ConnectFailurePacket struct {
diff --git a/makebtreedb/main.go b/makebtreedb/main.go
index 229444b..5fc6d78 100644
--- a/makebtreedb/main.go
+++ b/makebtreedb/main.go
@@ -86,6 +86,7 @@ func main() {

 		keys = append(keys, key)

+		fmt.Println("add:", key)
 		e = h.Insert(key, buf.Bytes())
 		if e != nil {
 			log.Fatalf("%+v\n", e)
@@ -94,14 +95,14 @@ func main() {
 		h.Commit()
 	}

-	le := len(keys) - 1
-	for k := range keys {
-		fmt.Println("remove:", keys[le-k])
-		e = h.Remove(keys[le-k])
-		if e != nil {
-			log.Fatalf("%+v\n", e)
-		}
-
-		h.Commit()
-	}
+	// le := len(keys) - 1
+	// for k := range keys {
+	// 	fmt.Println("remove:", keys[le-k])
+	// 	e = h.Remove(keys[le-k])
+	// 	if e != nil {
+	// 		log.Fatalf("%+v\n", e)
+	// 	}
+	//
+	// 	h.Commit()
+	// }
 }

Installing py-starbound is easy, pip install --user py-starbound is enough, with sbutils the process is as follows:

git clone https://github.com/xhebox/sbutils.git
cd sbutils
git checkout c5d83b0247cacb0789d1e818af74f3c41dc4bbc2  # The known working commit
git apply ../my.patch  # Save it to a file or do the changes by hand
go mod init github.com/xhebox/sbutils  # The repository uses old Go modules
go mod tidy
(cd dumpbtreedb && go build .)
(cd makebtreedb && go build .)

After that, you may now use the script. Before we start, the commands are for bash on a Linux system, Starbound is assumed to be installed in ~/starbound, this script is saved to ~/patch_map_metadata.py and sbutils has been cloned in ~/sbutils. The procedure is as follows:

  1. Create a new empty directory called dump somewhere, let's say in ~/starbound/dump.
  2. Next, locate the file of your chosen donor world. Planets are found in ~/starbound/storage/universe, their filenames star with the X and Y coordinates of the star system, then comes some ID which I assume to be the ID of the star system, then goes a number which specifies the planet's location in the system. I recommend to check the access and modification timestamps with stat after teleporting down to the planet to see if the file is what you were looking for.
  3. Now, cd into the dump directory and run ~/sbutils/dumpbtreedb/dumpbtreedb -i path/to/the/planet.world.
  4. This will unpack the world's database into nodes, which contain data in a format which can be described as simply "binary JSON". We only need to change a single node, whose file is named data_0000000000, it contains the metadata block of the world file (world properties, dungeon location, weather parameters etc).
  5. Now the Python script comes into play: run python ~/patch_map_metadata.py < data_0000000000 > data_0000000000.tmp and then mv data_0000000000.tmp data_0000000000.
  6. The patched metadata node can now be transplanted back into the world file with ~/sbutils/makebtreedb/makebtreedb -d . -i path/to/the/planet.world.
  7. And, finally, the world file can be set as the player's ship world by doing mv ~/starbound/storage/universe/x_y_id_n.world ~/starbound/storage/player/xxxxxxxxxxxxxxxx.shipworld. The operation is a success if the game doesn't segfault upon starting.
#!/usr/bin/env python3
import struct
import sys
from typing import IO, List
import starbound
# cd ~/SteamGames/Starbound
# mkdir dump
# cd dump
# ../sbutils/dumpbtreedb/dumpbtreedb -i ../storage/universe/x_y_id_n.world
# find . -maxdepth 1 -type f -not -name data_0000000000 -exec rm -v {} +
# python ../patch_map.py < data_0000000000 > data2_0000000000
# mv data2_0000000000 data_0000000000
# ../sbutils/makebtreedb/makebtreedb -d . -i ../storage/universe/x_y_id_n.world
# mv ../storage/universe/x_y_id_n.world ../storage/player/xxxxxxxxxxxxxxxx.shipworld
def main(argv: List[str]) -> int:
patch_metadata_sbvjson(sys.stdin.buffer, sys.stdout.buffer)
return 0
def patch_metadata_sbvjson(input_stream: IO[bytes], output_stream: IO[bytes]) -> None:
world_width, world_height = struct.unpack(">ii", input_stream.read(8))
metadata_wrapper = starbound.read_versioned_json(input_stream)
metadata = metadata_wrapper.data
def create_key(current_dict, path, dict_class=dict):
for key in path:
new_dict = dict_class()
next_dict = current_dict.setdefault(key, new_dict)
if next_dict is None:
next_dict = new_dict
current_dict[key] = next_dict
current_dict = next_dict
return current_dict
create_key(metadata, []).update({
"spawningEnabled": True,
})
create_key(metadata, ["worldProperties"]).update({
"ship.crewSize": 12,
"ship.maxFuel": 1000,
"ship.fuelEfficiency": 0.15,
"ship.level": 8,
"ship.fuel": 915.0,
"invinciblePlayers": True,
})
create_key(metadata, ["centralStructure", "config", "shipUpgrades"]).update({
"crewSize": 12,
"capabilities": ["teleport", "planetTravel", "systemTravel"]
})
output_stream.write(struct.pack(">ii", world_width, world_height))
starbound.write_versioned_json(output_stream, metadata_wrapper)
if __name__ == "__main__":
sys.exit(main(sys.argv))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment