forked from HotPocketRemix/DarkSoulsItemRandomizer
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathsettings_string_io.py
More file actions
113 lines (82 loc) · 3.38 KB
/
Copy pathsettings_string_io.py
File metadata and controls
113 lines (82 loc) · 3.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import base64
import binascii
import logging
import tkinter as tk
from ast import literal_eval
from json import dumps, loads
from typing import List, Tuple
__author__ = 'eagle_vis'
logger = logging.getLogger(__name__)
class IncompatibleVersionError(Exception):
pass
class InvalidValueError(Exception):
pass
class SettingsVariable:
def __init__(self, name: str, variable: tk.Variable, options: (List, Tuple)=None, on_update=None):
self.name = name
self.variable = variable
self.options = options
self.on_update = on_update
def get(self) -> str:
value = self.variable.get()
if self.options is not None and len(self.options) and value not in self.options:
raise InvalidValueError('Invalid value {} for {}'.format(value, self.name))
return str(value)
def set(self, value: str):
try:
new_val = None
if isinstance(self.variable, tk.BooleanVar):
new_val = bool(literal_eval(value))
elif isinstance(self.variable, tk.IntVar):
new_val = int(value)
elif isinstance(self.variable, tk.DoubleVar):
new_val = float(value)
elif isinstance(self.variable, tk.StringVar):
new_val = str(value)
if self.options is not None and len(self.options):
if new_val not in self.options:
raise ValueError
self.variable.set(new_val)
if callable(self.on_update):
self.on_update()
except Exception as e:
logger.exception('Invalid value supplied for variable {}: "{}" - '.format(self.name, value, e))
raise ValueError
class SettingsStringIO:
def __init__(self, version: str, compatible_versions: List[str], variables: List[SettingsVariable], call_after_update=None):
self.version = version
self.compatible_version = compatible_versions
self.variables = variables
self.call_after_update = call_after_update
self.required_keys = [v.name for v in variables]
self.required_keys.append('rv')
self.required_keys.sort()
def construct(self) -> str:
data_dict = {
'rv': self.version,
}
for var in self.variables:
data_dict[var.name] = var.get()
dict_string = bytes(dumps(data_dict, indent=None), 'ascii')
crc_string = '{:08x}'.format(binascii.crc32(dict_string) & 0xffffffff)
return base64.b64encode(dict_string + bytes(crc_string, 'ascii'))
def parse(self, string: str) -> bool:
data_string = base64.b64decode(string, validate=True)
given_crc = data_string[-8:].decode('ascii')
data_string = data_string[:-8]
computed_crc_string = '{:08x}'.format(binascii.crc32(data_string) & 0xffffffff)
if computed_crc_string != given_crc:
raise InvalidValueError
data_dict = loads(data_string)
if self.version != data_dict['rv']:
raise IncompatibleVersionError
if len(data_dict.keys()) != len(self.required_keys):
return False
for k in self.required_keys:
if k not in data_dict:
return False
for var in self.variables:
var.set(data_dict[var.name])
if callable(self.call_after_update):
self.call_after_update()
return True