Skip to content

Commit c48f46a

Browse files
authored
Merge pull request #5 from Schippi/bsorV2
Add new Fields for Replays with Beatleader >=0.9.6
2 parents 8d8015e + e0ffc2d commit c48f46a

File tree

5 files changed

+99
-51
lines changed

5 files changed

+99
-51
lines changed

README.md

+9-11
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
## Usage
66
```sh
7-
pip install py_bsor
7+
pip install py-bsor
88
```
99

10+
example - read bsor file and print some info:
1011
```python
1112
from bsor.Bsor import make_bsor
1213
from bsor.Scoring import calc_stats
@@ -15,16 +16,13 @@ import os
1516
if __name__ == '__main__':
1617
filename = 'D:/something/easy.bsor'
1718
print('File name : ', os.path.basename(filename))
18-
try:
19-
with open(filename, 'rb') as f:
20-
m = make_bsor(f)
21-
print('BSOR Version: %d' % m.file_version)
22-
print('BSOR notes: %d' % len(m.notes))
23-
print(m.info)
24-
stats = calc_stats(m)
25-
print(stats)
26-
except BSException as e:
27-
raise
19+
with open(filename, 'rb') as f:
20+
m = make_bsor(f)
21+
print(f'BSOR Version: {m.file_version}')
22+
print(f'BSOR notes: {len(m.notes)}')
23+
print(m.info)
24+
stats = calc_stats(m)
25+
print(stats)
2826
```
2927

3028
build:

pyproject.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
requires = ["setuptools>=61.0"]
33
build-backend = "setuptools.build_meta"
44
[project]
5-
name = "py_bsor"
6-
version = "0.9.14"
5+
name = "py-bsor"
6+
version = "1.1.2"
77
authors = [
88
{ name="Carsten Schipmann", email="[email protected]" },
99
]
10-
description = "BS Open Replay Parser"
10+
description = "BS Open Replay Parser, with added support for controlleroffsets and userdata"
1111
readme = "README.md"
1212
license = { file="LICENSE" }
1313
requires-python = ">=3.7"

src/bsor/Bsor.py

+71-36
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from bsor.Decoder import *
1+
from .Decoder import *
22
from typing import *
3-
import json
3+
import logging
4+
45
from abc import ABC, abstractmethod
56

67
from json import JSONEncoder
@@ -29,24 +30,25 @@ def default(self, o):
2930
SABER_LEFT = 1
3031
SABER_RIGHT = 0
3132

33+
MAX_SUPPORTED_VERSION = 1
3234
MAGIC_HEX = '0x442d3d69'
3335

3436
lookup_dict_scoring_type = {
35-
0: 'Normal',
36-
1: 'Ignore',
37-
2: 'NoScore',
38-
3: 'Normal',
39-
4: 'SliderHead',
40-
5: 'SliderTail',
41-
6: 'BurstSliderHead',
42-
7: 'BurstSliderElement'
37+
NOTE_SCORE_TYPE_NORMAL_1: 'Normal',
38+
NOTE_SCORE_TYPE_IGNORE: 'Ignore',
39+
NOTE_SCORE_TYPE_NOSCORE: 'NoScore',
40+
NOTE_SCORE_TYPE_NORMAL_2: 'Normal',
41+
NOTE_SCORE_TYPE_SLIDERHEAD: 'SliderHead',
42+
NOTE_SCORE_TYPE_SLIDERTAIL: 'SliderTail',
43+
NOTE_SCORE_TYPE_BURSTSLIDERHEAD: 'BurstSliderHead',
44+
NOTE_SCORE_TYPE_BURSTSLIDERELEMENT: 'BurstSliderElement'
4345
}
4446

4547
lookup_dict_event_type = {
46-
0: 'cut',
47-
1: 'badcut',
48-
2: 'miss',
49-
3: 'bomb'
48+
NOTE_EVENT_GOOD: 'cut',
49+
NOTE_EVENT_BAD: 'badcut',
50+
NOTE_EVENT_MISS: 'miss',
51+
NOTE_EVENT_BOMB: 'bomb'
5052
}
5153

5254

@@ -101,12 +103,11 @@ class Info(JSONable):
101103
def json_dict(self):
102104
return self.__dict__
103105

104-
105106
def make_info(f) -> Info:
106107
info_start = decode_byte(f)
107108

108109
if info_start != 0:
109-
raise BSException(f'Info must start with 0, got "{info_start}" instead')
110+
raise BSException(f'Info magic number must be 0, got "{info_start}" instead')
110111
info = Info()
111112
info.version = decode_string(f)
112113
info.gameVersion = decode_string(f)
@@ -190,7 +191,7 @@ def json_dict(self):
190191
def make_frames(f) -> List[Frame]:
191192
frames_start = decode_byte(f)
192193
if frames_start != 1:
193-
raise BSException(f'Frames must start with 1, got "{frames_start}" instead')
194+
raise BSException(f'Frames magic number must be 1, got "{frames_start}" instead')
194195
result = make_things(f, make_frame)
195196
return result
196197

@@ -267,7 +268,7 @@ def json_dict(self):
267268
def make_notes(f) -> List[Note]:
268269
notes_starter = decode_byte(f)
269270
if notes_starter != 2:
270-
raise BSException(f'Notes must start with 2, got "{notes_starter}" instead')
271+
raise BSException(f'Notes magic number must be 2, got "{notes_starter}" instead')
271272

272273
result = make_things(f, make_note)
273274
return result
@@ -440,6 +441,46 @@ def make_pause(f) -> Pause:
440441
return p
441442

442443

444+
class ControllerOffsets(JSONable):
445+
left: VRObject
446+
right: VRObject
447+
448+
def json_dict(self):
449+
return self.__dict__
450+
451+
def make_controller_offsets(f) -> ControllerOffsets:
452+
controller_offset_magic = decode_byte(f)
453+
if controller_offset_magic != 6:
454+
raise BSException(f'ControllerOffsets magic number must be 6, got "{controller_offset_magic}" instead')
455+
c = ControllerOffsets()
456+
c.left = make_vr_object(f)
457+
c.right = make_vr_object(f)
458+
return c
459+
460+
461+
class UserData(JSONable):
462+
key: str
463+
bytes: List[bytes]
464+
465+
def json_dict(self):
466+
return self.__dict__
467+
468+
469+
def make_user_datas(f) -> List[UserData]:
470+
user_data_magic = decode_byte(f)
471+
if user_data_magic != 7:
472+
raise BSException(f'UserData magic number must be 7, got "{user_data_magic}" instead')
473+
return make_things(f, make_user_data)
474+
475+
476+
def make_user_data(f) -> UserData:
477+
u = UserData()
478+
u.key = decode_string(f)
479+
byte_count = decode_int(f)
480+
u.bytes = [f.read(decode_byte(f)) for _ in range(byte_count)]
481+
return u
482+
483+
443484
class Bsor(JSONable):
444485
magic_numer: int
445486
file_version: int
@@ -449,6 +490,8 @@ class Bsor(JSONable):
449490
walls: List[Wall]
450491
heights: List[Height]
451492
pauses: List[Pause]
493+
controller_offsets: ControllerOffsets
494+
user_data: List[UserData]
452495

453496
def json_dict(self):
454497
return self.__dict__
@@ -461,29 +504,21 @@ def make_bsor(f: typing.BinaryIO) -> Bsor:
461504
if hex(m.magic_numer) != MAGIC_HEX:
462505
raise BSException(f'File magic number must be {MAGIC_HEX}, got "{hex(m.magic_numer)}" instead.')
463506
m.file_version = decode_byte(f)
464-
if m.file_version != 1:
465-
raise BSException(f'version {m.file_version} not supported')
507+
508+
if m.file_version > MAX_SUPPORTED_VERSION:
509+
logging.warning(f'File is version {m.file_version} and might not be compatible or not use all features'
510+
f', highest supported version is {MAX_SUPPORTED_VERSION}')
466511
m.info = make_info(f)
467512
m.frames = make_frames(f)
468513
m.notes = make_notes(f)
469514
m.walls = make_walls(f)
470515
m.heights = make_heights(f)
471516
m.pauses = make_pauses(f)
472-
517+
if f.peek(1):
518+
m.controller_offsets = make_controller_offsets(f)
519+
m.user_data = make_user_datas(f)
520+
else:
521+
m.controller_offsets = []
522+
m.user_data = []
473523
return m
474524

475-
476-
if __name__ == '__main__':
477-
import os
478-
479-
filename = 'D:/_TMP/Burst.bsor'
480-
print(f'File name : {os.path.basename(filename)}')
481-
try:
482-
with open(filename, "rb") as f:
483-
m = make_bsor(f)
484-
print(f'BSOR Version: {m.file_version}')
485-
print(f'BSOR notes: {len(m.notes)}')
486-
except BSException as e:
487-
# TODO please improve on this except-raise.
488-
# I've modified it to raise e for now because I don't know what you want to do with this, but please have something better.
489-
raise e

src/bsor/Scoring.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from bsor.Bsor import *
1+
from .Bsor import *
22
import json
33

44

src/main.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from bsor.Bsor import make_bsor
2+
from bsor.Scoring import calc_stats
3+
4+
if __name__ == '__main__':
5+
import os
6+
7+
# example, read basic info from bsor file
8+
filename = 'D:/_TMP/easy.bsor'
9+
print(f'File name : {os.path.basename(filename)}')
10+
with open(filename, "rb") as f:
11+
m = make_bsor(f)
12+
stats = calc_stats(m)
13+
print(f'BSOR Version: {m.file_version}')
14+
print(f'BSOR notes: {len(m.notes)}')
15+
print(f'BSOR stats: {stats}')

0 commit comments

Comments
 (0)