rbstarbound 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +14 -5
- data/bin/rbstarbound +2 -9
- data/lib/rbstarbound/commands/dump.rb +35 -0
- data/lib/rbstarbound/commands/serialize.rb +39 -0
- data/lib/rbstarbound/exit_codes.rb +4 -0
- data/lib/rbstarbound/main_command.rb +22 -0
- data/lib/rbstarbound/player.rb +26 -0
- data/lib/rbstarbound/sbon.rb +133 -10
- data/lib/rbstarbound/sbvj01.rb +26 -18
- data/lib/rbstarbound/utils.rb +7 -0
- data/lib/rbstarbound/version.rb +1 -1
- data/lib/rbstarbound.rb +19 -5
- data/test/commands/dump_test.rb +19 -0
- data/test/commands/serialize_test.rb +19 -0
- data/test/data/1.3.3/test.yaml +3956 -0
- data/test/player_test.rb +17 -0
- data/test/rbstarbound_test.rb +0 -5
- data/test/sbvj01_test.rb +7 -3
- data/test/test_helper.rb +2 -0
- metadata +30 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d05f85c2aa5f0cfb19c28e8d45f9c378da343273
|
4
|
+
data.tar.gz: 2be0f43338decdda10062f93a5f0018e4db6f33e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9cc42be3a1e207957bac7e4252572e0b1a764f10c40959b1e0a6275291e3123ed0b3ca6b225fb54f629a6ffd43cb3e0fa4513f08320a053d0d5e85d05550e1dc
|
7
|
+
data.tar.gz: 181f76077841725a6f27e916b7b7c8f4892bd4b7dfab3ae6cf2080ce54052930cd6294d20e97007d45e3cec60a51ce9fe20ae5e0e0c7b51b6ed2a9d69335a0a6
|
data/README.md
CHANGED
@@ -9,7 +9,10 @@ Frankly, it's just a ruby version of
|
|
9
9
|
All thanks to
|
10
10
|
[blixt](https://github.com/blixt).
|
11
11
|
|
12
|
-
|
12
|
+
Also, see
|
13
|
+
[changelog
|
14
|
+
](https://github.com/JetPirate/rb-starbound/blob/master/changelog.yaml)
|
15
|
+
to know what's new.
|
13
16
|
## Installation
|
14
17
|
|
15
18
|
#### I. option
|
@@ -43,11 +46,16 @@ You just
|
|
43
46
|
|
44
47
|
To get YAML file with all parsed player data:
|
45
48
|
|
46
|
-
|
49
|
+
$ ./bin/rbstarbound dump -f yaml -i path/to/save/file -o path/to/output/file
|
50
|
+
|
51
|
+
For more see:
|
52
|
+
|
53
|
+
$ ./bin/rbstarbound --help
|
54
|
+
|
47
55
|
|
48
|
-
|
56
|
+
with installed as a gem you could use (from any folder):
|
49
57
|
|
50
|
-
|
58
|
+
$ rbstarbound --help
|
51
59
|
|
52
60
|
### with the gem/library
|
53
61
|
|
@@ -66,7 +74,8 @@ puts player['data']['identity']['name']
|
|
66
74
|
|
67
75
|
```
|
68
76
|
|
69
|
-
|
77
|
+
For more see:
|
78
|
+
https://github.com/JetPirate/rb-starbound/blob/master/examples
|
70
79
|
|
71
80
|
--------------------------------------------------------------------------------
|
72
81
|
|
data/bin/rbstarbound
CHANGED
@@ -1,12 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'rbstarbound'
|
4
|
-
|
5
|
-
|
6
|
-
puts 'You need to input file paths'
|
7
|
-
return
|
8
|
-
end
|
9
|
-
player = RBStarbound.parse_player_save_file(ARGV[0].to_s)
|
10
|
-
File.open(ARGV[1].to_s, 'w') do |file|
|
11
|
-
file.write(player['data'].to_yaml)
|
12
|
-
end
|
4
|
+
|
5
|
+
exit RBStarbound::MainCommand.run || RBStarbound::EX_OK
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RBStarbound
|
4
|
+
class DumpCommand < Clamp::Command
|
5
|
+
option ['-f', '--format'], 'FORMAT', 'file\'s output format; one of: yaml',
|
6
|
+
required: true
|
7
|
+
option ['-i', '--save-file'], 'FILE',
|
8
|
+
'file to fetch player data from',
|
9
|
+
required: true
|
10
|
+
option ['-o', '--formatted-file'], 'FILE', 'file to save player data to',
|
11
|
+
required: true
|
12
|
+
|
13
|
+
def execute
|
14
|
+
return unless format.casecmp('yaml').zero?
|
15
|
+
dump_to_yaml(save_file.to_s, formatted_file.to_s)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def dump_to_yaml(input, output)
|
21
|
+
player_data = RBStarbound.parse_player_save_file(input.to_s)
|
22
|
+
return RBStarbound::EX_ERR if player_data.nil?
|
23
|
+
begin
|
24
|
+
formatted_file = File.open(output, 'w')
|
25
|
+
formatted_file.write(player_data['data'].to_yaml)
|
26
|
+
return RBStarbound::EX_OK
|
27
|
+
rescue StandardError => e
|
28
|
+
RBStarbound.print_error(e)
|
29
|
+
return RBStarbound::EX_ERR
|
30
|
+
ensure
|
31
|
+
formatted_file.close unless formatted_file.nil?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module RBStarbound
|
6
|
+
class SerializeCommand < Clamp::Command
|
7
|
+
option ['-f', '--format'], 'FORMAT', 'file\'s input format; one of: yaml',
|
8
|
+
required: true
|
9
|
+
option ['-i', '--formatted-file'], 'FILE',
|
10
|
+
'file to fetch player data from',
|
11
|
+
required: true
|
12
|
+
option ['-o', '--save-file'], 'FILE', 'file to save player data to',
|
13
|
+
required: true
|
14
|
+
|
15
|
+
def execute
|
16
|
+
return unless format.casecmp('yaml').zero?
|
17
|
+
serialize_from_yaml(formatted_file.to_s, save_file.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def serialize_from_yaml(input, output)
|
23
|
+
formatted_file = File.open(input)
|
24
|
+
fetched_data = YAML.load(formatted_file)
|
25
|
+
player_data = RBStarbound::Player::Data.new(
|
26
|
+
RBStarbound::Player::DATA_NAME,
|
27
|
+
RBStarbound::Player::DATA_VERSION,
|
28
|
+
fetched_data
|
29
|
+
)
|
30
|
+
dumped = RBStarbound.dump_player_save_file(output, player_data)
|
31
|
+
return dumped ? RBStarbound::EX_OK : RBStarbound::EX_ERR
|
32
|
+
rescue StandardError => e
|
33
|
+
RBStarbound.print_error(e)
|
34
|
+
return RBStarbound::EX_ERR
|
35
|
+
ensure
|
36
|
+
formatted_file.close unless formatted_file.nil?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'clamp'
|
4
|
+
require 'rbstarbound/exit_codes'
|
5
|
+
require 'rbstarbound/commands/dump'
|
6
|
+
require 'rbstarbound/commands/serialize'
|
7
|
+
|
8
|
+
module RBStarbound
|
9
|
+
class MainCommand < Clamp::Command
|
10
|
+
option '--version', :flag, 'show version' do
|
11
|
+
puts RBStarbound::VERSION
|
12
|
+
exit RBStarbound::EX_OK
|
13
|
+
end
|
14
|
+
|
15
|
+
subcommand 'dump',
|
16
|
+
'dump a player save file as a formatted file',
|
17
|
+
RBStarbound::DumpCommand
|
18
|
+
subcommand 'serialize',
|
19
|
+
'serialize a player save file form a formatted file',
|
20
|
+
RBStarbound::SerializeCommand
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RBStarbound
|
4
|
+
module Player
|
5
|
+
DATA_NAME = 'PlayerEntity'
|
6
|
+
DATA_VERSION = 30
|
7
|
+
Data = Struct.new(:name, :version, :data) do
|
8
|
+
def player_name; data['identity']['name']; end
|
9
|
+
def player_name=(name); data['identity']['name'] = name; end
|
10
|
+
def gender; data['identity']['gender']; end
|
11
|
+
def gender=(value); data['identity']['gender'] = value; end
|
12
|
+
def hair_type; data['identity']['hairType']; end
|
13
|
+
def hair_type=(type); data['identity']['hairType'] = type; end
|
14
|
+
def personality; data['identity']['personalityIdle']; end
|
15
|
+
def personality=(value); data['identity']['personalityIdle'] = value; end
|
16
|
+
def death_count; data['identity']['deathCount']; end
|
17
|
+
def death_count=(count); data['identity']['deathCount'] = count; end
|
18
|
+
def play_time; data['identity']['playTime']; end
|
19
|
+
def uuid; data['identity']['uuid']; end
|
20
|
+
def intro_complete?; data['identity']['introComplete']; end
|
21
|
+
def intro_complete=(bool); data['identity']['introComplete'] = bool; end
|
22
|
+
def ship_level; data['identity']['shipLevel']; end
|
23
|
+
def ship_level=(level); data['identity']['shipLevel'] = level; end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/rbstarbound/sbon.rb
CHANGED
@@ -11,6 +11,16 @@ module RBStarbound
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
def self.write_varint(io, value)
|
15
|
+
buf = (value & 0b01_11_11_11).chr
|
16
|
+
value >>= 7
|
17
|
+
until value.zero?
|
18
|
+
buf = (value & 0b01_11_11_11 | 0b10_00_00_00).chr + buf
|
19
|
+
value >>= 7
|
20
|
+
end
|
21
|
+
io.write(buf)
|
22
|
+
end
|
23
|
+
|
14
24
|
def self.read_varint_signed(io)
|
15
25
|
value = read_varint(io)
|
16
26
|
# Least significant bit represents the sign.
|
@@ -18,15 +28,32 @@ module RBStarbound
|
|
18
28
|
value >> 1
|
19
29
|
end
|
20
30
|
|
31
|
+
def self.write_varint_signed(io, value)
|
32
|
+
if value < 0
|
33
|
+
write_varint(io, (-(value + 1) << 1 | 1))
|
34
|
+
else
|
35
|
+
write_varint(io, value << 1)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
21
39
|
def self.read_bytes(io)
|
22
40
|
length = read_varint(io)
|
23
41
|
io.read(length)
|
24
42
|
end
|
25
43
|
|
44
|
+
def self.write_bytes(io, data)
|
45
|
+
write_varint(io, data.length)
|
46
|
+
io.write(data)
|
47
|
+
end
|
48
|
+
|
26
49
|
def self.read_string(io)
|
27
50
|
read_bytes(io)
|
28
51
|
end
|
29
52
|
|
53
|
+
def self.write_string(io, data)
|
54
|
+
write_bytes(io, data)
|
55
|
+
end
|
56
|
+
|
30
57
|
def self.read_list(io)
|
31
58
|
list = []
|
32
59
|
read_varint(io).times do
|
@@ -35,6 +62,13 @@ module RBStarbound
|
|
35
62
|
list
|
36
63
|
end
|
37
64
|
|
65
|
+
def self.write_list(io, list)
|
66
|
+
write_varint(io, list.length)
|
67
|
+
list.each do |item|
|
68
|
+
write_dynamic(io, item)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
38
72
|
def self.read_map(io)
|
39
73
|
map = {}
|
40
74
|
read_varint(io).times do
|
@@ -44,25 +78,114 @@ module RBStarbound
|
|
44
78
|
map
|
45
79
|
end
|
46
80
|
|
81
|
+
def self.write_map(io, map)
|
82
|
+
write_varint(io, map.length)
|
83
|
+
map.each_pair do |key, val|
|
84
|
+
write_string(io, key)
|
85
|
+
write_dynamic(io, val)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
47
89
|
def self.read_dynamic(io)
|
48
|
-
case
|
49
|
-
when 1 then
|
50
|
-
when 2 then io
|
51
|
-
when 3 then
|
52
|
-
when 4 then
|
53
|
-
when 5 then
|
54
|
-
when 6 then
|
55
|
-
when 7 then
|
90
|
+
case read_data_type(io)
|
91
|
+
when 1 then read_nil(io)
|
92
|
+
when 2 then read_double(io) # big-endian
|
93
|
+
when 3 then read_bool(io)
|
94
|
+
when 4 then read_integer(io) # VLQ; signed
|
95
|
+
when 5 then read_str(io)
|
96
|
+
when 6 then read_array(io)
|
97
|
+
when 7 then read_hash(io)
|
56
98
|
else raise RBStarbound::ValueError, 'Unknown dynamic type'
|
57
99
|
end
|
58
100
|
end
|
59
101
|
|
60
|
-
def self.
|
102
|
+
def self.write_dynamic(io, data)
|
103
|
+
case data
|
104
|
+
when nil then write_nil(io)
|
105
|
+
when Float then write_double(io, data)
|
106
|
+
when TrueClass, FalseClass then write_bool(io, data)
|
107
|
+
when Integer then write_integer(io, data)
|
108
|
+
when String then write_str(io, data)
|
109
|
+
when Array then write_array(io, data)
|
110
|
+
when Hash then write_hash(io, data)
|
111
|
+
else raise ValueError, 'Cannot write value'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.read_data_type(io)
|
61
116
|
io.readchar.ord
|
62
117
|
end
|
63
118
|
|
119
|
+
def self.read_bool(io)
|
120
|
+
!read_data_type(io).zero?
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.write_bool(io, value)
|
124
|
+
if value.is_a? TrueClass
|
125
|
+
io.write("\x03\x01".b)
|
126
|
+
else
|
127
|
+
io.write("\x03\x00".b)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.read_double(io)
|
132
|
+
io.read(8).unpack('G').first
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.write_double(io, value)
|
136
|
+
io.write("\x02".b)
|
137
|
+
io.write([value].pack('G'))
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.read_integer(io)
|
141
|
+
read_varint_signed(io)
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.write_integer(io, data)
|
145
|
+
io.write("\x04".b)
|
146
|
+
write_varint_signed(io, data)
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.read_str(io)
|
150
|
+
read_string(io)
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.write_str(io, data)
|
154
|
+
io.write("\x05".b)
|
155
|
+
write_string(io, data)
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.read_array(io)
|
159
|
+
read_list(io)
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.write_array(io, data)
|
163
|
+
io.write("\x06".b)
|
164
|
+
write_list(io, data)
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.read_hash(io)
|
168
|
+
read_map(io)
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.write_hash(io, data)
|
172
|
+
io.write("\x07".b)
|
173
|
+
write_map(io, data)
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.read_nil(_)
|
177
|
+
nil
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.write_nil(io)
|
181
|
+
io.write("\x01".b)
|
182
|
+
end
|
183
|
+
|
64
184
|
class << self
|
65
|
-
private :
|
185
|
+
private :read_data_type, :read_nil, :read_bool, :read_double,
|
186
|
+
:read_integer, :read_str, :read_array, :read_hash, :write_nil,
|
187
|
+
:write_bool, :write_double, :write_integer, :write_str,
|
188
|
+
:write_array, :write_hash
|
66
189
|
end
|
67
190
|
end
|
68
191
|
end
|
data/lib/rbstarbound/sbvj01.rb
CHANGED
@@ -1,42 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'rbstarbound/player'
|
4
|
+
|
3
5
|
module RBStarbound
|
4
6
|
module SBVJ01
|
5
|
-
Player = Struct.new(:name, :version, :data) do
|
6
|
-
def player_name
|
7
|
-
data['identity']['name']
|
8
|
-
end
|
9
|
-
|
10
|
-
def uuid
|
11
|
-
data['identity']['uuid']
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
7
|
def self.parse(io)
|
16
8
|
unless sbvj01_header?(io)
|
17
9
|
raise RBStarbound::SBVJ01Error, 'Not a SBVJ01 file'
|
18
10
|
end
|
19
|
-
|
11
|
+
read_player_data(io)
|
20
12
|
end
|
21
13
|
|
22
|
-
def self.
|
23
|
-
|
14
|
+
def self.dump(io, player_data)
|
15
|
+
sbvj01_header(io)
|
16
|
+
write_player_data(io, player_data)
|
24
17
|
end
|
25
18
|
|
26
|
-
def self.
|
19
|
+
def self.read_player_data(io)
|
27
20
|
name = RBStarbound::SBON.read_string(io)
|
28
|
-
version =
|
29
|
-
version = io.read(4).unpack('i>')
|
21
|
+
version = nil
|
22
|
+
version = io.read(4).unpack('i>').first unless io.readchar.ord.zero?
|
30
23
|
data = RBStarbound::SBON.read_dynamic(io)
|
31
|
-
Player.new(name, version, data)
|
24
|
+
RBStarbound::Player::Data.new(name, version, data)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.write_player_data(io, player_data)
|
28
|
+
RBStarbound::SBON.write_string(io, player_data.name)
|
29
|
+
if player_data.version.nil?
|
30
|
+
io.write([0].pack('c'))
|
31
|
+
else
|
32
|
+
io.write([1, player_data.version].pack('ci>'))
|
33
|
+
end
|
34
|
+
RBStarbound::SBON.write_dynamic(io, player_data.data)
|
32
35
|
end
|
33
36
|
|
34
37
|
def self.sbvj01_header?(io)
|
35
38
|
io.read(6) == 'SBVJ01'
|
36
39
|
end
|
37
40
|
|
41
|
+
def self.sbvj01_header(io)
|
42
|
+
io.write('SBVJ01')
|
43
|
+
end
|
44
|
+
|
38
45
|
class << self
|
39
|
-
private :
|
46
|
+
private :sbvj01_header?, :sbvj01_header, :read_player_data,
|
47
|
+
:write_player_data
|
40
48
|
end
|
41
49
|
end
|
42
50
|
end
|
data/lib/rbstarbound/version.rb
CHANGED
data/lib/rbstarbound.rb
CHANGED
@@ -1,21 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rbstarbound/version'
|
2
4
|
require 'rbstarbound/exceptions'
|
5
|
+
require 'rbstarbound/utils'
|
3
6
|
require 'rbstarbound/sbvj01'
|
4
7
|
require 'rbstarbound/sbon'
|
8
|
+
require 'rbstarbound/exit_codes'
|
9
|
+
require 'rbstarbound/main_command'
|
5
10
|
|
6
11
|
module RBStarbound
|
7
|
-
def self.ping
|
8
|
-
puts 'Pong!'
|
9
|
-
end
|
10
|
-
|
11
12
|
def self.parse_player_save_file(path)
|
12
13
|
save_file = File.open(path, 'rb')
|
13
14
|
parsed_data = SBVJ01.parse(save_file)
|
14
15
|
rescue StandardError => e
|
15
|
-
|
16
|
+
print_error(e)
|
17
|
+
return nil
|
16
18
|
else
|
17
19
|
return parsed_data
|
18
20
|
ensure
|
19
21
|
save_file.close unless save_file.nil?
|
20
22
|
end
|
23
|
+
|
24
|
+
def self.dump_player_save_file(path, player_data)
|
25
|
+
save_file = File.open(path, 'w')
|
26
|
+
SBVJ01.dump(save_file, player_data)
|
27
|
+
rescue StandardError => e
|
28
|
+
print_error(e)
|
29
|
+
return false
|
30
|
+
else
|
31
|
+
return true
|
32
|
+
ensure
|
33
|
+
save_file.close unless save_file.nil?
|
34
|
+
end
|
21
35
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), '../test_helper')
|
4
|
+
|
5
|
+
class DumpCommandTest < Minitest::Test
|
6
|
+
def test_it_dumps_to_yaml
|
7
|
+
cmd = RBStarbound::MainCommand.new('', {})
|
8
|
+
file = Tempfile.new('dump_cmd_test_temp')
|
9
|
+
params = %w[dump -f yaml -i] << SAVE_FILE_PATH << '-o' << file.path
|
10
|
+
begin
|
11
|
+
cmd.run(params)
|
12
|
+
data = file.gets(DATA_COUNT)
|
13
|
+
refute_empty(data)
|
14
|
+
ensure
|
15
|
+
file.close
|
16
|
+
file.unlink
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), '../test_helper')
|
4
|
+
|
5
|
+
class SerializeCommandTest < Minitest::Test
|
6
|
+
def test_it_serializes_from_yaml
|
7
|
+
cmd = RBStarbound::MainCommand.new('', {})
|
8
|
+
file = Tempfile.new('serialize_cmd_test_temp')
|
9
|
+
params = %w[serialize -f yaml -i] << YAML_FILE_PATH << '-o' << file.path
|
10
|
+
begin
|
11
|
+
cmd.run(params)
|
12
|
+
data = file.gets(DATA_COUNT)
|
13
|
+
refute_empty(data)
|
14
|
+
ensure
|
15
|
+
file.close
|
16
|
+
file.unlink
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|