koro 2.0.0rc3__py3-none-any.whl → 2.0.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- koro/__init__.py +8 -8
- koro/slot/__init__.py +21 -21
- koro/slot/bin.py +158 -155
- koro/slot/file.py +70 -67
- koro/slot/save.py +137 -137
- koro/slot/xml.py +845 -845
- koro/stage/__init__.py +99 -98
- koro/stage/model.py +288 -288
- koro/stage/part.py +1754 -1754
- {koro-2.0.0rc3.dist-info → koro-2.0.2.dist-info}/LICENSE +24 -24
- koro-2.0.2.dist-info/METADATA +52 -0
- koro-2.0.2.dist-info/RECORD +14 -0
- {koro-2.0.0rc3.dist-info → koro-2.0.2.dist-info}/WHEEL +1 -1
- koro-2.0.0rc3.dist-info/METADATA +0 -21
- koro-2.0.0rc3.dist-info/RECORD +0 -14
- {koro-2.0.0rc3.dist-info → koro-2.0.2.dist-info}/top_level.txt +0 -0
koro/slot/save.py
CHANGED
@@ -1,137 +1,137 @@
|
|
1
|
-
from enum import Enum, unique
|
2
|
-
from io import BytesIO
|
3
|
-
from operator import index as ix
|
4
|
-
from os.path import basename, dirname, join
|
5
|
-
from typing import TYPE_CHECKING, Annotated, Any, Literal, SupportsIndex
|
6
|
-
from collections.abc import Mapping, Sequence
|
7
|
-
|
8
|
-
from ..stage import Stage
|
9
|
-
|
10
|
-
from . import Slot
|
11
|
-
from .xml import XmlSlot
|
12
|
-
|
13
|
-
if TYPE_CHECKING:
|
14
|
-
from _typeshed import StrOrBytesPath
|
15
|
-
else:
|
16
|
-
StrOrBytesPath = Any
|
17
|
-
|
18
|
-
|
19
|
-
__all__ = ["EditorPage", "get_slots", "SaveSlot"]
|
20
|
-
|
21
|
-
|
22
|
-
@unique
|
23
|
-
class EditorPage(Enum):
|
24
|
-
ORIGINAL = 0
|
25
|
-
FRIEND = 1
|
26
|
-
HUDSON = 2
|
27
|
-
|
28
|
-
|
29
|
-
class SaveSlot(Slot):
|
30
|
-
__match_args__ = ("path", "page", "index")
|
31
|
-
__slots__ = ("_offset", "_path")
|
32
|
-
|
33
|
-
_offset: Literal[8, 156392, 312776, 469160]
|
34
|
-
_path: str | bytes
|
35
|
-
|
36
|
-
def __init__(
|
37
|
-
self,
|
38
|
-
path: StrOrBytesPath,
|
39
|
-
page: EditorPage,
|
40
|
-
index: Annotated[
|
41
|
-
SupportsIndex,
|
42
|
-
Literal[
|
43
|
-
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
|
44
|
-
],
|
45
|
-
],
|
46
|
-
) -> None:
|
47
|
-
index = ix(index) - 1
|
48
|
-
if index in range(0, 20):
|
49
|
-
self._offset = 8 + 156864 * (index & 3) # type: ignore[assignment]
|
50
|
-
self._path = join(path, f"ed{(index >> 2) + 5 * page.value:02}.dat") # type: ignore[arg-type]
|
51
|
-
else:
|
52
|
-
raise ValueError("index must be between 1 and 20")
|
53
|
-
|
54
|
-
def __bool__(self) -> bool:
|
55
|
-
try:
|
56
|
-
with open(self._path, "rb") as f:
|
57
|
-
f.seek(self._offset)
|
58
|
-
return f.read(1) != b"\x00"
|
59
|
-
except FileNotFoundError:
|
60
|
-
return False
|
61
|
-
|
62
|
-
def __eq__(self, other: Any, /) -> bool:
|
63
|
-
if isinstance(other, SaveSlot):
|
64
|
-
return self._path == other._path and self._offset == other._offset
|
65
|
-
else:
|
66
|
-
return NotImplemented
|
67
|
-
|
68
|
-
def __hash__(self) -> int:
|
69
|
-
return hash((self._offset, self._path))
|
70
|
-
|
71
|
-
@property
|
72
|
-
def index(
|
73
|
-
self,
|
74
|
-
) -> Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]:
|
75
|
-
return (int(basename(self._path)[2:4]) % 5 >> 2 | self._offset // 156864) + 1 # type: ignore[return-value]
|
76
|
-
|
77
|
-
def load(self) -> Stage | None:
|
78
|
-
try:
|
79
|
-
with open(self._path, "rb") as f:
|
80
|
-
f.seek(self._offset)
|
81
|
-
with BytesIO() as b:
|
82
|
-
block: bytearray = bytearray()
|
83
|
-
while True:
|
84
|
-
block.clear()
|
85
|
-
f.
|
86
|
-
if len(b.getbuffer()) + len(block) > 156864:
|
87
|
-
del block[156864 - len(b.getbuffer()) :]
|
88
|
-
if block[-1]:
|
89
|
-
b.write(block)
|
90
|
-
else:
|
91
|
-
while block:
|
92
|
-
if block[len(block) >> 1]:
|
93
|
-
b.write(block[: (len(block) >> 1) + 1])
|
94
|
-
del block[: (len(block) >> 1) + 1]
|
95
|
-
else:
|
96
|
-
del block[len(block) >> 1 :]
|
97
|
-
data: bytes = b.getvalue()
|
98
|
-
if data:
|
99
|
-
return XmlSlot.deserialize(data)
|
100
|
-
else:
|
101
|
-
return None
|
102
|
-
except FileNotFoundError:
|
103
|
-
return None
|
104
|
-
|
105
|
-
@property
|
106
|
-
def page(self) -> EditorPage:
|
107
|
-
return EditorPage(int(basename(self._path)[2:4]) // 5)
|
108
|
-
|
109
|
-
@property
|
110
|
-
def path(self) -> StrOrBytesPath:
|
111
|
-
return dirname(self._path)
|
112
|
-
|
113
|
-
def __repr__(self) -> str:
|
114
|
-
return f"{type(self).__name__}({self.path!r}, {self.page!r}, {self.index!r})"
|
115
|
-
|
116
|
-
def save(self, data: Stage | None) -> None:
|
117
|
-
binary: bytes = b"" if data is None else XmlSlot.serialize(data)
|
118
|
-
if len(binary) > 156864:
|
119
|
-
raise ValueError("serialized
|
120
|
-
try:
|
121
|
-
with open(self._path, "xb") as f:
|
122
|
-
f.write(bytes(638976))
|
123
|
-
if data is None:
|
124
|
-
return
|
125
|
-
except FileExistsError:
|
126
|
-
pass
|
127
|
-
with open(self._path, "r+b") as f:
|
128
|
-
f.seek(self._offset)
|
129
|
-
f.write(binary)
|
130
|
-
f.write(bytes(156864 - len(binary)))
|
131
|
-
|
132
|
-
|
133
|
-
def get_slots(save: StrOrBytesPath, /) -> Mapping[EditorPage, Sequence[SaveSlot]]:
|
134
|
-
return {
|
135
|
-
page: tuple(SaveSlot(save, page, i) for i in range(1, 21))
|
136
|
-
for page in EditorPage
|
137
|
-
}
|
1
|
+
from enum import Enum, unique
|
2
|
+
from io import BytesIO
|
3
|
+
from operator import index as ix
|
4
|
+
from os.path import basename, dirname, join
|
5
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal, SupportsIndex
|
6
|
+
from collections.abc import Mapping, Sequence
|
7
|
+
|
8
|
+
from ..stage import Stage
|
9
|
+
|
10
|
+
from . import Slot
|
11
|
+
from .xml import XmlSlot
|
12
|
+
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from _typeshed import StrOrBytesPath
|
15
|
+
else:
|
16
|
+
StrOrBytesPath = Any
|
17
|
+
|
18
|
+
|
19
|
+
__all__ = ["EditorPage", "get_slots", "SaveSlot"]
|
20
|
+
|
21
|
+
|
22
|
+
@unique
|
23
|
+
class EditorPage(Enum):
|
24
|
+
ORIGINAL = 0
|
25
|
+
FRIEND = 1
|
26
|
+
HUDSON = 2
|
27
|
+
|
28
|
+
|
29
|
+
class SaveSlot(Slot):
|
30
|
+
__match_args__ = ("path", "page", "index")
|
31
|
+
__slots__ = ("_offset", "_path")
|
32
|
+
|
33
|
+
_offset: Literal[8, 156392, 312776, 469160]
|
34
|
+
_path: str | bytes
|
35
|
+
|
36
|
+
def __init__(
|
37
|
+
self,
|
38
|
+
path: StrOrBytesPath,
|
39
|
+
page: EditorPage,
|
40
|
+
index: Annotated[
|
41
|
+
SupportsIndex,
|
42
|
+
Literal[
|
43
|
+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
|
44
|
+
],
|
45
|
+
],
|
46
|
+
) -> None:
|
47
|
+
index = ix(index) - 1
|
48
|
+
if index in range(0, 20):
|
49
|
+
self._offset = 8 + 156864 * (index & 3) # type: ignore[assignment]
|
50
|
+
self._path = join(path, f"ed{(index >> 2) + 5 * page.value:02}.dat") # type: ignore[arg-type]
|
51
|
+
else:
|
52
|
+
raise ValueError("index must be between 1 and 20")
|
53
|
+
|
54
|
+
def __bool__(self) -> bool:
|
55
|
+
try:
|
56
|
+
with open(self._path, "rb") as f:
|
57
|
+
f.seek(self._offset)
|
58
|
+
return f.read(1) != b"\x00"
|
59
|
+
except FileNotFoundError:
|
60
|
+
return False
|
61
|
+
|
62
|
+
def __eq__(self, other: Any, /) -> bool:
|
63
|
+
if isinstance(other, SaveSlot):
|
64
|
+
return self._path == other._path and self._offset == other._offset
|
65
|
+
else:
|
66
|
+
return NotImplemented
|
67
|
+
|
68
|
+
def __hash__(self) -> int:
|
69
|
+
return hash((self._offset, self._path))
|
70
|
+
|
71
|
+
@property
|
72
|
+
def index(
|
73
|
+
self,
|
74
|
+
) -> Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]:
|
75
|
+
return (int(basename(self._path)[2:4]) % 5 >> 2 | self._offset // 156864) + 1 # type: ignore[return-value]
|
76
|
+
|
77
|
+
def load(self) -> Stage | None:
|
78
|
+
try:
|
79
|
+
with open(self._path, "rb") as f:
|
80
|
+
f.seek(self._offset)
|
81
|
+
with BytesIO() as b:
|
82
|
+
block: bytearray = bytearray()
|
83
|
+
while True:
|
84
|
+
block.clear()
|
85
|
+
block.extend(f.read1())
|
86
|
+
if len(b.getbuffer()) + len(block) > 156864:
|
87
|
+
del block[156864 - len(b.getbuffer()) :]
|
88
|
+
if block[-1]:
|
89
|
+
b.write(block)
|
90
|
+
else:
|
91
|
+
while block:
|
92
|
+
if block[len(block) >> 1]:
|
93
|
+
b.write(block[: (len(block) >> 1) + 1])
|
94
|
+
del block[: (len(block) >> 1) + 1]
|
95
|
+
else:
|
96
|
+
del block[len(block) >> 1 :]
|
97
|
+
data: bytes = b.getvalue()
|
98
|
+
if data:
|
99
|
+
return XmlSlot.deserialize(data)
|
100
|
+
else:
|
101
|
+
return None
|
102
|
+
except FileNotFoundError:
|
103
|
+
return None
|
104
|
+
|
105
|
+
@property
|
106
|
+
def page(self) -> EditorPage:
|
107
|
+
return EditorPage(int(basename(self._path)[2:4]) // 5)
|
108
|
+
|
109
|
+
@property
|
110
|
+
def path(self) -> StrOrBytesPath:
|
111
|
+
return dirname(self._path)
|
112
|
+
|
113
|
+
def __repr__(self) -> str:
|
114
|
+
return f"{type(self).__name__}({self.path!r}, {self.page!r}, {self.index!r})"
|
115
|
+
|
116
|
+
def save(self, data: Stage | None) -> None:
|
117
|
+
binary: bytes = b"" if data is None else XmlSlot.serialize(data)
|
118
|
+
if len(binary) > 156864:
|
119
|
+
raise ValueError("serialized stage data is too large to save")
|
120
|
+
try:
|
121
|
+
with open(self._path, "xb") as f:
|
122
|
+
f.write(bytes(638976))
|
123
|
+
if data is None:
|
124
|
+
return
|
125
|
+
except FileExistsError:
|
126
|
+
pass
|
127
|
+
with open(self._path, "r+b") as f:
|
128
|
+
f.seek(self._offset)
|
129
|
+
f.write(binary)
|
130
|
+
f.write(bytes(156864 - len(binary)))
|
131
|
+
|
132
|
+
|
133
|
+
def get_slots(save: StrOrBytesPath, /) -> Mapping[EditorPage, Sequence[SaveSlot]]:
|
134
|
+
return {
|
135
|
+
page: tuple(SaveSlot(save, page, i) for i in range(1, 21))
|
136
|
+
for page in EditorPage
|
137
|
+
}
|