pywargame 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pywargame/__init__.py +2 -0
- pywargame/common/__init__.py +3 -0
- pywargame/common/collector.py +87 -0
- pywargame/common/dicedraw.py +363 -0
- pywargame/common/drawdice.py +40 -0
- pywargame/common/singleton.py +22 -0
- pywargame/common/test.py +25 -0
- pywargame/common/verbose.py +59 -0
- pywargame/common/verboseguard.py +53 -0
- pywargame/cyberboard/__init__.py +18 -0
- pywargame/cyberboard/archive.py +283 -0
- pywargame/cyberboard/base.py +63 -0
- pywargame/cyberboard/board.py +462 -0
- pywargame/cyberboard/cell.py +200 -0
- pywargame/cyberboard/collect.py +49 -0
- pywargame/cyberboard/collectgbx0pwd.py +30 -0
- pywargame/cyberboard/collectgbxext.py +30 -0
- pywargame/cyberboard/collectgsnexp.py +32 -0
- pywargame/cyberboard/collectgsnext.py +30 -0
- pywargame/cyberboard/draw.py +396 -0
- pywargame/cyberboard/exporter.py +1132 -0
- pywargame/cyberboard/extractor.py +240 -0
- pywargame/cyberboard/features.py +17 -0
- pywargame/cyberboard/gamebox.py +81 -0
- pywargame/cyberboard/gbxexp.py +76 -0
- pywargame/cyberboard/gbxext.py +64 -0
- pywargame/cyberboard/gsnexp.py +147 -0
- pywargame/cyberboard/gsnext.py +59 -0
- pywargame/cyberboard/head.py +111 -0
- pywargame/cyberboard/image.py +76 -0
- pywargame/cyberboard/main.py +47 -0
- pywargame/cyberboard/mark.py +102 -0
- pywargame/cyberboard/palette.py +36 -0
- pywargame/cyberboard/piece.py +169 -0
- pywargame/cyberboard/player.py +36 -0
- pywargame/cyberboard/scenario.py +115 -0
- pywargame/cyberboard/testgrid.py +156 -0
- pywargame/cyberboard/tile.py +121 -0
- pywargame/cyberboard/tray.py +68 -0
- pywargame/cyberboard/windows.py +41 -0
- pywargame/cyberboard/zeropwd.py +45 -0
- pywargame/cyberboard.py +2728 -0
- pywargame/gbx0pwd.py +2776 -0
- pywargame/gbxextract.py +2795 -0
- pywargame/gsnexport.py +16499 -0
- pywargame/gsnextract.py +2790 -0
- pywargame/latex/__init__.py +2 -0
- pywargame/latex/collect.py +34 -0
- pywargame/latex/latexexporter.py +4010 -0
- pywargame/latex/main.py +184 -0
- pywargame/vassal/__init__.py +66 -0
- pywargame/vassal/base.py +139 -0
- pywargame/vassal/board.py +243 -0
- pywargame/vassal/buildfile.py +60 -0
- pywargame/vassal/chart.py +79 -0
- pywargame/vassal/chessclock.py +197 -0
- pywargame/vassal/collect.py +98 -0
- pywargame/vassal/collectpatch.py +28 -0
- pywargame/vassal/command.py +21 -0
- pywargame/vassal/documentation.py +322 -0
- pywargame/vassal/dumpcollect.py +28 -0
- pywargame/vassal/dumpvsav.py +28 -0
- pywargame/vassal/element.py +439 -0
- pywargame/vassal/exporter.py +89 -0
- pywargame/vassal/extension.py +101 -0
- pywargame/vassal/folder.py +103 -0
- pywargame/vassal/game.py +940 -0
- pywargame/vassal/gameelements.py +1091 -0
- pywargame/vassal/globalkey.py +127 -0
- pywargame/vassal/globalproperty.py +433 -0
- pywargame/vassal/grid.py +573 -0
- pywargame/vassal/map.py +1061 -0
- pywargame/vassal/mapelements.py +1020 -0
- pywargame/vassal/merge.py +57 -0
- pywargame/vassal/merger.py +460 -0
- pywargame/vassal/moduledata.py +275 -0
- pywargame/vassal/mrgcollect.py +31 -0
- pywargame/vassal/patch.py +44 -0
- pywargame/vassal/patchcollect.py +28 -0
- pywargame/vassal/player.py +83 -0
- pywargame/vassal/save.py +495 -0
- pywargame/vassal/skel.py +380 -0
- pywargame/vassal/trait.py +224 -0
- pywargame/vassal/traits/__init__.py +36 -0
- pywargame/vassal/traits/area.py +50 -0
- pywargame/vassal/traits/basic.py +35 -0
- pywargame/vassal/traits/calculatedproperty.py +22 -0
- pywargame/vassal/traits/cargo.py +29 -0
- pywargame/vassal/traits/click.py +41 -0
- pywargame/vassal/traits/clone.py +28 -0
- pywargame/vassal/traits/delete.py +24 -0
- pywargame/vassal/traits/deselect.py +32 -0
- pywargame/vassal/traits/dynamicproperty.py +112 -0
- pywargame/vassal/traits/globalcommand.py +55 -0
- pywargame/vassal/traits/globalhotkey.py +26 -0
- pywargame/vassal/traits/globalproperty.py +54 -0
- pywargame/vassal/traits/hide.py +67 -0
- pywargame/vassal/traits/label.py +76 -0
- pywargame/vassal/traits/layer.py +105 -0
- pywargame/vassal/traits/mark.py +20 -0
- pywargame/vassal/traits/mask.py +85 -0
- pywargame/vassal/traits/mat.py +26 -0
- pywargame/vassal/traits/moved.py +35 -0
- pywargame/vassal/traits/movefixed.py +51 -0
- pywargame/vassal/traits/nonrect.py +95 -0
- pywargame/vassal/traits/nostack.py +55 -0
- pywargame/vassal/traits/place.py +104 -0
- pywargame/vassal/traits/prototype.py +20 -0
- pywargame/vassal/traits/report.py +34 -0
- pywargame/vassal/traits/restrictaccess.py +28 -0
- pywargame/vassal/traits/restrictcommand.py +32 -0
- pywargame/vassal/traits/return.py +40 -0
- pywargame/vassal/traits/rotate.py +62 -0
- pywargame/vassal/traits/sendto.py +59 -0
- pywargame/vassal/traits/sheet.py +129 -0
- pywargame/vassal/traits/skel.py +9 -0
- pywargame/vassal/traits/stack.py +28 -0
- pywargame/vassal/traits/submenu.py +27 -0
- pywargame/vassal/traits/trail.py +61 -0
- pywargame/vassal/traits/trigger.py +72 -0
- pywargame/vassal/turn.py +272 -0
- pywargame/vassal/upgrade.py +191 -0
- pywargame/vassal/vmod.py +323 -0
- pywargame/vassal/vsav.py +100 -0
- pywargame/vassal/widget.py +358 -0
- pywargame/vassal/withtraits.py +634 -0
- pywargame/vassal/xml.py +4 -0
- pywargame/vassal/zone.py +399 -0
- pywargame/vassal.py +12500 -0
- pywargame/vmodpatch.py +12548 -0
- pywargame/vsavdump.py +12533 -0
- pywargame/vslmerge.py +13015 -0
- pywargame/wgexport.py +16689 -0
- pywargame/ztexport.py +14351 -0
- pywargame/zuntzu/__init__.py +5 -0
- pywargame/zuntzu/base.py +82 -0
- pywargame/zuntzu/collect.py +38 -0
- pywargame/zuntzu/countersheet.py +250 -0
- pywargame/zuntzu/dicehand.py +48 -0
- pywargame/zuntzu/exporter.py +936 -0
- pywargame/zuntzu/gamebox.py +154 -0
- pywargame/zuntzu/map.py +36 -0
- pywargame/zuntzu/piece.py +37 -0
- pywargame/zuntzu/scenario.py +208 -0
- pywargame/zuntzu/ztexp.py +115 -0
- pywargame-0.3.1.dist-info/METADATA +353 -0
- pywargame-0.3.1.dist-info/RECORD +150 -0
- pywargame-0.3.1.dist-info/WHEEL +5 -0
- pywargame-0.3.1.dist-info/licenses/LICENSE +5 -0
- pywargame-0.3.1.dist-info/top_level.txt +1 -0
pywargame/gbx0pwd.py
ADDED
@@ -0,0 +1,2776 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# Script collected from other scripts
|
3
|
+
#
|
4
|
+
# cyberboard.py
|
5
|
+
# zeropwd.py
|
6
|
+
#
|
7
|
+
# ====================================================================
|
8
|
+
# From cyberboard.py
|
9
|
+
# Script collected from other scripts
|
10
|
+
#
|
11
|
+
# ../common/singleton.py
|
12
|
+
# ../common/verbose.py
|
13
|
+
# ../common/verboseguard.py
|
14
|
+
# features.py
|
15
|
+
# archive.py
|
16
|
+
# base.py
|
17
|
+
# head.py
|
18
|
+
# image.py
|
19
|
+
# tile.py
|
20
|
+
# piece.py
|
21
|
+
# mark.py
|
22
|
+
# draw.py
|
23
|
+
# cell.py
|
24
|
+
# board.py
|
25
|
+
# gamebox.py
|
26
|
+
# scenario.py
|
27
|
+
# player.py
|
28
|
+
# windows.py
|
29
|
+
# palette.py
|
30
|
+
# tray.py
|
31
|
+
# extractor.py
|
32
|
+
#
|
33
|
+
# ====================================================================
|
34
|
+
# From ../common/singleton.py
|
35
|
+
# ====================================================================
|
36
|
+
class Singleton(type):
|
37
|
+
'''Meta base class for singletons'''
|
38
|
+
_instances = {}
|
39
|
+
def __call__(cls, *args, **kwargs):
|
40
|
+
'''Create the singleton object or returned existing
|
41
|
+
|
42
|
+
Parameters
|
43
|
+
----------
|
44
|
+
args : tuple
|
45
|
+
Arguments
|
46
|
+
kwargs : dict
|
47
|
+
Keyword arguments
|
48
|
+
'''
|
49
|
+
if cls not in cls._instances:
|
50
|
+
cls._instances[cls] = \
|
51
|
+
super(Singleton, cls).__call__(*args, **kwargs)
|
52
|
+
return cls._instances[cls]
|
53
|
+
|
54
|
+
#
|
55
|
+
# EOF
|
56
|
+
#
|
57
|
+
# ====================================================================
|
58
|
+
# From ../common/verbose.py
|
59
|
+
# --------------------------------------------------------------------
|
60
|
+
|
61
|
+
class Verbose(metaclass=Singleton):
|
62
|
+
def __init__(self,verbose=False):
|
63
|
+
'''Singleton for writing message to screen, contigent on setting
|
64
|
+
|
65
|
+
Parameters
|
66
|
+
----------
|
67
|
+
verbose : bool
|
68
|
+
Whether to show messages or not
|
69
|
+
'''
|
70
|
+
self._indent = ''
|
71
|
+
self._verbose = verbose
|
72
|
+
|
73
|
+
def setVerbose(self,verbose):
|
74
|
+
'''Set whether to print or not
|
75
|
+
|
76
|
+
Parameters
|
77
|
+
----------
|
78
|
+
verbose : bool
|
79
|
+
Whether to show messages or not
|
80
|
+
'''
|
81
|
+
self._verbose = verbose
|
82
|
+
|
83
|
+
@property
|
84
|
+
def verbose(self):
|
85
|
+
'''Test if this is verbose'''
|
86
|
+
return self._verbose
|
87
|
+
|
88
|
+
def message(self,*args,**kwargs):
|
89
|
+
'''Write messsage if verbose
|
90
|
+
|
91
|
+
Parameters
|
92
|
+
----------
|
93
|
+
args : tuple
|
94
|
+
Arguments
|
95
|
+
kwargs : dict
|
96
|
+
Keyword arguments
|
97
|
+
'''
|
98
|
+
if not self._verbose: return
|
99
|
+
if not kwargs.pop('noindent', False):
|
100
|
+
print(self._indent,end='')
|
101
|
+
print(*args,**kwargs)
|
102
|
+
|
103
|
+
def incr(self):
|
104
|
+
'''Increment indention'''
|
105
|
+
self._indent += ' '
|
106
|
+
|
107
|
+
def decr(self):
|
108
|
+
'''Decrement indention'''
|
109
|
+
if len(self._indent) > 0:
|
110
|
+
self._indent = self._indent[:-1]
|
111
|
+
|
112
|
+
#
|
113
|
+
# EOF
|
114
|
+
#
|
115
|
+
# ====================================================================
|
116
|
+
# From ../common/verboseguard.py
|
117
|
+
# --------------------------------------------------------------------
|
118
|
+
|
119
|
+
class VerboseGuard:
|
120
|
+
def __init__(self,*args,**kwargs):
|
121
|
+
'''A guard pattern that increases verbose indention
|
122
|
+
|
123
|
+
This is a context manager. The arguments passed are used for
|
124
|
+
an initial message, before increasinig indention.
|
125
|
+
|
126
|
+
Parameters
|
127
|
+
----------
|
128
|
+
args : tuple
|
129
|
+
Arguments
|
130
|
+
kwargs : dict
|
131
|
+
Keyword arguments
|
132
|
+
'''
|
133
|
+
Verbose().message(*args,**kwargs)
|
134
|
+
|
135
|
+
def __bool_(self):
|
136
|
+
'''Test if verbose'''
|
137
|
+
return Verbose().verbose
|
138
|
+
|
139
|
+
def __enter__(self):
|
140
|
+
'''Enter context'''
|
141
|
+
Verbose().incr()
|
142
|
+
return self
|
143
|
+
|
144
|
+
def __exit__(self,*args):
|
145
|
+
'''Exit context'''
|
146
|
+
Verbose().decr()
|
147
|
+
|
148
|
+
@property
|
149
|
+
def i(self):
|
150
|
+
return Verbose()._indent
|
151
|
+
|
152
|
+
def __call__(self,*args,**kwargs):
|
153
|
+
'''Write a message at current indention level
|
154
|
+
|
155
|
+
Parameters
|
156
|
+
----------
|
157
|
+
args : tuple
|
158
|
+
Arguments
|
159
|
+
kwargs : dict
|
160
|
+
Keyword arguments
|
161
|
+
'''
|
162
|
+
Verbose().message(*args,**kwargs)
|
163
|
+
|
164
|
+
#
|
165
|
+
# EOF
|
166
|
+
#
|
167
|
+
# ====================================================================
|
168
|
+
# From features.py
|
169
|
+
# --------------------------------------------------------------------
|
170
|
+
|
171
|
+
class Features(metaclass=Singleton):
|
172
|
+
def __init__(self):
|
173
|
+
self.bmp_zlib = False # wxBMPHandler + Zlib
|
174
|
+
self.id_size = 2 # Size of IDs in bytes (1, 2 or 4)
|
175
|
+
self.size_size = 4 # Size of sizes in bytes (4 or 8)
|
176
|
+
self.sub_size = 2 # Size of sub-sizes in bytes (4 or 8)
|
177
|
+
self.square_cells = False # Geomorphic boards, square cells
|
178
|
+
self.rotate_unit = False # Geomorphic boards, rotated unit board
|
179
|
+
self.piece_100 = False # Pieces w/<= 100 sides
|
180
|
+
self.private_board = False #
|
181
|
+
self.roll_state = False # serialize roll state
|
182
|
+
self.little_endian = True
|
183
|
+
# ====================================================================
|
184
|
+
# From archive.py
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
class BaseArchive:
|
189
|
+
WORD_SIZE = 2
|
190
|
+
|
191
|
+
def __init__(self,filename,mode='rb'):
|
192
|
+
'''Read data from a MFT CArchive stored on disk
|
193
|
+
|
194
|
+
Works as a context manager
|
195
|
+
'''
|
196
|
+
with VerboseGuard(f'Opening archive {filename}'):
|
197
|
+
self._filename = filename
|
198
|
+
self._file = open(filename,mode)
|
199
|
+
self._i = 0
|
200
|
+
self.vmsg = lambda *args : Verbose().message(*args)
|
201
|
+
#self.vmsg = lambda *args : None
|
202
|
+
|
203
|
+
def __enter__(self):
|
204
|
+
'''Enter context'''
|
205
|
+
return self
|
206
|
+
|
207
|
+
def __exit__(self,*args,**kwargs):
|
208
|
+
'''Exit context'''
|
209
|
+
self._file.close()
|
210
|
+
|
211
|
+
def tell(self):
|
212
|
+
pass
|
213
|
+
|
214
|
+
def read(self,n):
|
215
|
+
'''Read n bytes from archive'''
|
216
|
+
pass
|
217
|
+
|
218
|
+
def chr(self,n):
|
219
|
+
'''Read n characters from archive'''
|
220
|
+
b = self.read(n)
|
221
|
+
try:
|
222
|
+
c = b.decode()
|
223
|
+
self.vmsg(f'char->{c}')
|
224
|
+
return c
|
225
|
+
except:
|
226
|
+
print(f'Failed at {b} ({self._file.tell()})')
|
227
|
+
raise
|
228
|
+
|
229
|
+
def int(self,n):
|
230
|
+
'''Read an (unsigned) integer from archive'''
|
231
|
+
b = self.read(n)
|
232
|
+
i = int.from_bytes(b,'little' if Features().little_endian else 'big')
|
233
|
+
self.vmsg(f'int->{i}')
|
234
|
+
return i
|
235
|
+
|
236
|
+
def byte(self):
|
237
|
+
'''Read a byte from archive'''
|
238
|
+
return self.int(1)
|
239
|
+
|
240
|
+
def word(self):
|
241
|
+
'''Read a word (16bit integer) from archive'''
|
242
|
+
w = self.int(BaseArchive.WORD_SIZE)
|
243
|
+
self.vmsg(f'word->{w}')
|
244
|
+
return w;
|
245
|
+
|
246
|
+
|
247
|
+
def dword(self):
|
248
|
+
'''Read a double word (32bit integer) from archive'''
|
249
|
+
d = self.int(2*BaseArchive.WORD_SIZE)
|
250
|
+
self.vmsg(f'dword->{d}')
|
251
|
+
return d
|
252
|
+
|
253
|
+
def size(self):
|
254
|
+
'''Read a size'''
|
255
|
+
s = self.int(Features().size_size)
|
256
|
+
self.vmsg(f'size->{s}')
|
257
|
+
return s
|
258
|
+
|
259
|
+
def sub_size(self):
|
260
|
+
'''Read a size'''
|
261
|
+
s = self.int(Features().sub_size)
|
262
|
+
self.vmsg(f'sub->{s}')
|
263
|
+
return s
|
264
|
+
|
265
|
+
def count(self):
|
266
|
+
'''Read a count'''
|
267
|
+
if Features().size_size == 4:
|
268
|
+
return self.word()
|
269
|
+
|
270
|
+
c = self.word()
|
271
|
+
if c != 0xFFFF:
|
272
|
+
return c
|
273
|
+
|
274
|
+
c = self.dword()
|
275
|
+
if c != 0xFFFFFFFF:
|
276
|
+
return c
|
277
|
+
|
278
|
+
return int(8)
|
279
|
+
|
280
|
+
def iden(self):
|
281
|
+
'''Read an identifier'''
|
282
|
+
i = self.int(Features().id_size)
|
283
|
+
self.vmsg(f'id->{i}')
|
284
|
+
return i
|
285
|
+
|
286
|
+
def _strlen(self):
|
287
|
+
'''Read length of following string from archive'''
|
288
|
+
# See https://github.com/pixelspark/corespark/blob/master/Libraries/atlmfc/src/mfc/arccore.cpp
|
289
|
+
|
290
|
+
s = 1
|
291
|
+
l = self.byte()
|
292
|
+
|
293
|
+
if l < 0xFF: # Small ASCII string
|
294
|
+
self.vmsg(f'slen->{l},{s}')
|
295
|
+
return l, s
|
296
|
+
|
297
|
+
l = self.word()
|
298
|
+
if l == 0xFFFE: # Unicode - try again
|
299
|
+
s = 2
|
300
|
+
l = self.byte()
|
301
|
+
|
302
|
+
if l < 0xFF: # Small unicode
|
303
|
+
self.vmsg(f'slen->{l},{s}')
|
304
|
+
return l, s
|
305
|
+
|
306
|
+
|
307
|
+
l = self.word() # Large unicode string
|
308
|
+
|
309
|
+
if l < 0xFFFF: # Not super long
|
310
|
+
self.vmsg(f'slen->{l},{s}')
|
311
|
+
return l, s
|
312
|
+
|
313
|
+
l = self.dword()
|
314
|
+
|
315
|
+
if l < 0xFFFFFFFF: # Not hyper long
|
316
|
+
self.vmsg(f'slen->{l},{s}')
|
317
|
+
return l, s
|
318
|
+
|
319
|
+
|
320
|
+
self.vmsg(f'slen->{8},fixed')
|
321
|
+
return self.int(8)
|
322
|
+
|
323
|
+
def str(self):
|
324
|
+
'''Read a string from the archive'''
|
325
|
+
# See https://github.com/pixelspark/corespark/blob/master/Libraries/atlmfc/src/mfc/arccore.cpp
|
326
|
+
l, s = self._strlen()
|
327
|
+
# print(f'Read string of length {l}*{s} at {self.tell()}')
|
328
|
+
ret = [self.read(s) for i in range(l)]
|
329
|
+
try:
|
330
|
+
ret = [c.decode() for c in ret]
|
331
|
+
except:
|
332
|
+
ret = ['']
|
333
|
+
self.vmsg(f'str->"{"".join(ret)}"')
|
334
|
+
return ''.join(ret)
|
335
|
+
|
336
|
+
@property
|
337
|
+
def filename(self):
|
338
|
+
return self._filename
|
339
|
+
|
340
|
+
@property
|
341
|
+
def path(self):
|
342
|
+
from pathlib import Path
|
343
|
+
return Path(self._filename)
|
344
|
+
|
345
|
+
# ====================================================================
|
346
|
+
class UnbufferedArchive(BaseArchive):
|
347
|
+
def __init__(self,filename,mode='rb'):
|
348
|
+
'''Read data from a MFT CArchive stored on dist
|
349
|
+
|
350
|
+
Works as a context manager
|
351
|
+
'''
|
352
|
+
super(UnbufferedArchive,self).__init__(filename,mode='rb')
|
353
|
+
|
354
|
+
def read(self,n):
|
355
|
+
'''Read n bytes from archive - directly from file'''
|
356
|
+
b = self._file.read(n)
|
357
|
+
self.vmsg(f'read->{list(b)}')
|
358
|
+
# print(f'{self._i:6d} -> {b}')
|
359
|
+
self._i += n
|
360
|
+
return b
|
361
|
+
|
362
|
+
def tell(self):
|
363
|
+
return self._file.tell()
|
364
|
+
|
365
|
+
# ====================================================================
|
366
|
+
class BufferedArchive(BaseArchive):
|
367
|
+
def __init__(self,filename,mode='rb'):
|
368
|
+
'''Read data from a MFT CArchive stored on dist
|
369
|
+
|
370
|
+
Works as a context manager
|
371
|
+
'''
|
372
|
+
super(BufferedArchive,self).__init__(filename,mode='rb')
|
373
|
+
self._size = 4096
|
374
|
+
self._max = self._size
|
375
|
+
self._current = self._max
|
376
|
+
self._buffer = []
|
377
|
+
|
378
|
+
def read(self,n):
|
379
|
+
with VerboseGuard(f'Read {n} bytes') as g:
|
380
|
+
'''Read n bytes from archive - buffered
|
381
|
+
|
382
|
+
This emulates the behaviour of MFC CArchive::Read
|
383
|
+
'''
|
384
|
+
|
385
|
+
nmax = n
|
386
|
+
ntmp = min(nmax, self._max - self._current)
|
387
|
+
b = self._buffer[self._current:self._current+ntmp]
|
388
|
+
g(f'Take {ntmp} bytes from buffer -> {b}')
|
389
|
+
self._current += ntmp
|
390
|
+
nmax -= ntmp
|
391
|
+
|
392
|
+
if nmax != 0:
|
393
|
+
g(f'Need to read at least {nmax} from file')
|
394
|
+
assert self._current == self._max,\
|
395
|
+
f'Something is wrong! {self._current} != ' \
|
396
|
+
f'{self._max} (1)'
|
397
|
+
|
398
|
+
g(f'Missing {nmax} bytes -> ({nmax % self._size})')
|
399
|
+
ntmp = nmax - (nmax % self._size)
|
400
|
+
nread = 0
|
401
|
+
nleft = ntmp
|
402
|
+
nbytes = 0
|
403
|
+
while True:
|
404
|
+
tmpbuf = self._file.read(nleft)
|
405
|
+
nbytes = len(tmpbuf)
|
406
|
+
nread += nbytes
|
407
|
+
nleft -= nbytes
|
408
|
+
b += tmpbuf
|
409
|
+
g(f'Read {nleft} -> {tmpbuf}')
|
410
|
+
|
411
|
+
if nbytes <= 0 or nleft <= 0:
|
412
|
+
break
|
413
|
+
|
414
|
+
nmax -= nread
|
415
|
+
|
416
|
+
if nmax > 0 and nread == ntmp:
|
417
|
+
# Last read chunk into buffer and copy
|
418
|
+
assert self._current == self._max,\
|
419
|
+
f'Something is wrong! {self._current} != ' \
|
420
|
+
f'{self._max} (2)'
|
421
|
+
|
422
|
+
assert nmax < self._size, \
|
423
|
+
f'Something is wrong {nmax} >= {self._size}'
|
424
|
+
|
425
|
+
nlastleft = max(nmax,self._size)
|
426
|
+
nlastbytes = 0
|
427
|
+
nread = 0
|
428
|
+
self._buffer = []
|
429
|
+
while True:
|
430
|
+
tmpbuf = self._file.read(nlastleft)
|
431
|
+
nlastbytes = len(tmpbuf)
|
432
|
+
nread += nlastbytes
|
433
|
+
nlastleft -= nlastbytes
|
434
|
+
self._buffer += tmpbuf
|
435
|
+
|
436
|
+
if (nlastbytes <= 0) or \
|
437
|
+
(nlastleft <= 0) or \
|
438
|
+
nread >= ntmp:
|
439
|
+
break
|
440
|
+
|
441
|
+
self._current = 0
|
442
|
+
self._max = nread
|
443
|
+
|
444
|
+
ntmp = min(nmax, self._max - self._current)
|
445
|
+
b += self._buffer[self._current:
|
446
|
+
self._current+ntmp]
|
447
|
+
self._current += ntmp
|
448
|
+
nmax -= ntmp
|
449
|
+
|
450
|
+
g(b)
|
451
|
+
return b''.join(b)
|
452
|
+
|
453
|
+
def tell(self):
|
454
|
+
return self._file.tell()
|
455
|
+
|
456
|
+
|
457
|
+
Archive = UnbufferedArchive
|
458
|
+
# Archive = BufferedArchive
|
459
|
+
|
460
|
+
#
|
461
|
+
# EOF
|
462
|
+
#
|
463
|
+
# ====================================================================
|
464
|
+
# From base.py
|
465
|
+
|
466
|
+
# --------------------------------------------------------------------
|
467
|
+
class CbFont:
|
468
|
+
def __init__(self,ar):
|
469
|
+
'''Shared structure that holds font information'''
|
470
|
+
# Fonts
|
471
|
+
with VerboseGuard('Reading font definition'):
|
472
|
+
self._size = ar.word()
|
473
|
+
self._flags = ar.word()
|
474
|
+
self._family = ar.word()
|
475
|
+
self._name = ar.str()
|
476
|
+
|
477
|
+
def isBold(self):
|
478
|
+
return self._flags & 0x1
|
479
|
+
|
480
|
+
def isItalic(self):
|
481
|
+
return self._flags & 0x2
|
482
|
+
|
483
|
+
def isUnderline(self):
|
484
|
+
return self._flags & 0x4
|
485
|
+
|
486
|
+
def __str__(self):
|
487
|
+
return (f'Font:{self._name} ({self._family}) @ '
|
488
|
+
f'{self._size} ({self._flags:08x})')
|
489
|
+
|
490
|
+
# --------------------------------------------------------------------
|
491
|
+
class CbManager:
|
492
|
+
def __init__(self,ar):
|
493
|
+
'''Base class for some managers'''
|
494
|
+
with VerboseGuard('Reading general manager'):
|
495
|
+
self._foreground = ar.dword()
|
496
|
+
self._background = ar.dword()
|
497
|
+
self._linewidth = ar.word()
|
498
|
+
self._font = CbFont(ar)
|
499
|
+
self._reserved = [ar.word() for _ in range(4)]
|
500
|
+
|
501
|
+
def _readNsub(self,ar,sub_size):
|
502
|
+
return ar.int(sub_size)
|
503
|
+
|
504
|
+
def _readSub(self,ar,cls,sub_size=None):
|
505
|
+
if sub_size is None:
|
506
|
+
sub_size = Features().sub_size
|
507
|
+
with VerboseGuard(f'Reading sub {cls} of manager ({sub_size})'):
|
508
|
+
n = self._readNsub(ar,sub_size)
|
509
|
+
return [cls(ar) for _ in range(n)]
|
510
|
+
|
511
|
+
def _strSub(self,title,subs):
|
512
|
+
subl = '\n '.join([str(s) for s in subs])
|
513
|
+
return f' # {title}: {len(subs)}\n {subl}'
|
514
|
+
|
515
|
+
def __str__(self):
|
516
|
+
return (f' Foreground: {self._foreground:08x}\n'
|
517
|
+
f' Background: {self._background:08x}\n'
|
518
|
+
f' Linewidth: {self._linewidth}\n'
|
519
|
+
f' Font: {self._font}\n'
|
520
|
+
f' Reserved: {self._reserved}\n')
|
521
|
+
|
522
|
+
#
|
523
|
+
# EOF
|
524
|
+
#
|
525
|
+
# ====================================================================
|
526
|
+
# From head.py
|
527
|
+
|
528
|
+
def num_version(major,minor):
|
529
|
+
return major * 256 + minor
|
530
|
+
|
531
|
+
def readVector(ar,cls):
|
532
|
+
with VerboseGuard('Reading vector') as g:
|
533
|
+
if Features().size_size == 8:
|
534
|
+
n = ar.size()
|
535
|
+
else:
|
536
|
+
n = ar.word()
|
537
|
+
if n == 0xFFFF:
|
538
|
+
n = ar.dword()
|
539
|
+
if n == 0xFFFFFFFF:
|
540
|
+
n = ar.int(64)
|
541
|
+
g(f'{n} elements')
|
542
|
+
return [cls(ar) for _ in range(n)]
|
543
|
+
|
544
|
+
# ====================================================================
|
545
|
+
class GBXHeader:
|
546
|
+
BOX = 'GBOX'
|
547
|
+
SCENARIO = 'GSCN'
|
548
|
+
def __init__(self,ar,expect=BOX):
|
549
|
+
'''GBXHeader of file
|
550
|
+
|
551
|
+
4 bytes format ID
|
552
|
+
4x1 byte format and program version
|
553
|
+
|
554
|
+
8 bytes in total
|
555
|
+
'''
|
556
|
+
with VerboseGuard('Reading header') as g:
|
557
|
+
sig = ar.chr(len(expect))
|
558
|
+
assert sig == expect, f'Not a {expect} file: {sig}'
|
559
|
+
|
560
|
+
self._major = ar.byte()
|
561
|
+
self._minor = ar.byte()
|
562
|
+
self._programMajor = ar.byte()
|
563
|
+
self._programMinor = ar.byte()
|
564
|
+
self._vers = num_version(self._major,self._minor)
|
565
|
+
g(f'Version {self._major}.{self._minor}')
|
566
|
+
|
567
|
+
assert self._vers >= num_version(3,0),\
|
568
|
+
f'{self._major}.{self._minor} format not supported'
|
569
|
+
|
570
|
+
if self._vers >= num_version(4,0):
|
571
|
+
g(f'Detected version 4.0 or newer, setting some features')
|
572
|
+
Features().id_size = 4
|
573
|
+
Features().size_size = 8
|
574
|
+
Features().sub_size = 8
|
575
|
+
Features().square_cells = True
|
576
|
+
Features().rotate_unit = True
|
577
|
+
Features().piece_100 = True
|
578
|
+
Features().private_board = True
|
579
|
+
Features().roll_state = True
|
580
|
+
|
581
|
+
|
582
|
+
|
583
|
+
|
584
|
+
|
585
|
+
def __str__(self):
|
586
|
+
return ('Header:\n'
|
587
|
+
f' Format major version: {self._major}\n'
|
588
|
+
f' Format minor version: {self._minor}\n'
|
589
|
+
f' Program major version: {self._programMajor}\n'
|
590
|
+
f' Program minor version: {self._programMinor}\n')
|
591
|
+
|
592
|
+
|
593
|
+
# --------------------------------------------------------------------
|
594
|
+
class GBXStrings:
|
595
|
+
def __init__(self,ar):
|
596
|
+
'''Map IDs to strings'''
|
597
|
+
with VerboseGuard(f'Reading string mappings'):
|
598
|
+
strMapN = ar.size()
|
599
|
+
|
600
|
+
self._id2str = {}
|
601
|
+
for _ in range(strMapN):
|
602
|
+
key = ar.dword()
|
603
|
+
val = ar.str()
|
604
|
+
|
605
|
+
self._id2str[key] = val
|
606
|
+
|
607
|
+
def __str__(self):
|
608
|
+
return ('Strings:\n'+
|
609
|
+
'\n'.join([f' {key:8x}: {val}'
|
610
|
+
for key,val in self._id2str.items()]))
|
611
|
+
|
612
|
+
# --------------------------------------------------------------------
|
613
|
+
class GSNStrings:
|
614
|
+
def __init__(self,ar):
|
615
|
+
'''Map IDs to strings'''
|
616
|
+
with VerboseGuard(f'Reading string mappings'):
|
617
|
+
strMapN = ar.size()
|
618
|
+
|
619
|
+
self._id2str = {}
|
620
|
+
for _ in range(strMapN):
|
621
|
+
key = ar.size()
|
622
|
+
val = ar.str()
|
623
|
+
|
624
|
+
self._id2str[key] = val
|
625
|
+
|
626
|
+
def __str__(self):
|
627
|
+
return ('Strings:\n'+
|
628
|
+
'\n'.join([f' {key:8x}: {val}'
|
629
|
+
for key,val in self._id2str.items()]))
|
630
|
+
|
631
|
+
#
|
632
|
+
# EOF
|
633
|
+
#
|
634
|
+
# ====================================================================
|
635
|
+
# From image.py
|
636
|
+
|
637
|
+
# ====================================================================
|
638
|
+
class GBXImage:
|
639
|
+
def __init__(self,ar,transparent=None,save=None):
|
640
|
+
'''A DIB image stored in GameBox'''
|
641
|
+
with VerboseGuard('Reading an image') as g:
|
642
|
+
size = ar.dword()
|
643
|
+
|
644
|
+
if size & 0x80000000: # Compressed
|
645
|
+
from zlib import decompress
|
646
|
+
g(f'Read compressed image')
|
647
|
+
|
648
|
+
size &= 0x80000000 # Remove flag
|
649
|
+
compSize = ar.dword() # Compressed size
|
650
|
+
compressed = ar.read(compSize) # Compressed
|
651
|
+
buffer = decompress(compressed,bufsize=size)
|
652
|
+
#assert len(buffer) == size, \
|
653
|
+
# f'Failed to decompress to expected {size}, ' \
|
654
|
+
# f'got {len(buffer)}'
|
655
|
+
|
656
|
+
else:
|
657
|
+
buffer = ar.read(size)
|
658
|
+
|
659
|
+
from PIL import Image as PILImage
|
660
|
+
from io import BytesIO
|
661
|
+
from numpy import asarray, where, uint8
|
662
|
+
|
663
|
+
img = PILImage.open(BytesIO(buffer)).convert('RGBA')
|
664
|
+
|
665
|
+
# If transparancy is defined, clean up the image
|
666
|
+
if transparent is None:
|
667
|
+
self._img = img
|
668
|
+
else:
|
669
|
+
g(f'Making #{transparent:06x} transparent')
|
670
|
+
dat = asarray(img)
|
671
|
+
tr = (transparent >> 16) & 0xFF
|
672
|
+
tg = (transparent >> 8) & 0xFF
|
673
|
+
tb = (transparent >> 0) & 0xFF
|
674
|
+
dat2 = dat.copy()
|
675
|
+
dat2[:,:,3] = (255*(dat[:,:,:3]!=[tb,tg,tr]).any(axis=2))\
|
676
|
+
.astype(uint8)
|
677
|
+
|
678
|
+
#if (dat[:,:,3] == dat2[:,:,3]).all():
|
679
|
+
# print(f'Transparency operation seemed to have no effect '
|
680
|
+
# f'for image')
|
681
|
+
|
682
|
+
self._img = PILImage.fromarray(dat2)
|
683
|
+
|
684
|
+
if save is None:
|
685
|
+
return
|
686
|
+
|
687
|
+
self._img.save(save)
|
688
|
+
|
689
|
+
@classmethod
|
690
|
+
def b64encode(cls,img):
|
691
|
+
'''Encode image as a base64 data URL'''
|
692
|
+
from io import BytesIO
|
693
|
+
from base64 import b64encode
|
694
|
+
|
695
|
+
if img is None:
|
696
|
+
return None
|
697
|
+
|
698
|
+
buffered = BytesIO()
|
699
|
+
img.save(buffered,format='PNG')
|
700
|
+
data = b64encode(buffered.getvalue())
|
701
|
+
if not isinstance(data,str):
|
702
|
+
data = data.decode()
|
703
|
+
|
704
|
+
return 'data:image/png;base64,'+data
|
705
|
+
|
706
|
+
#
|
707
|
+
# EOF
|
708
|
+
#
|
709
|
+
# ====================================================================
|
710
|
+
# From tile.py
|
711
|
+
|
712
|
+
# ====================================================================
|
713
|
+
class GBXTileLocation:
|
714
|
+
def __init__(self,ar):
|
715
|
+
'''Where a tile can be found'''
|
716
|
+
with VerboseGuard('Reading tile location') as g:
|
717
|
+
self._sheet = (ar.word() if not Features().size_size == 8 else
|
718
|
+
ar.size())
|
719
|
+
self._offset = ar.word()
|
720
|
+
g(f'Tile location at sheet={self._sheet} offset={self._offset}')
|
721
|
+
if self._sheet == 65535: self._sheet = -1
|
722
|
+
|
723
|
+
def __str__(self):
|
724
|
+
return f'{self._sheet:3d} @ {self._offset:6d}'
|
725
|
+
|
726
|
+
|
727
|
+
# --------------------------------------------------------------------
|
728
|
+
class GBXTileDefinition:
|
729
|
+
def __init__(self,ar):
|
730
|
+
'''The definition of a tile'''
|
731
|
+
with VerboseGuard('Reading tile definition'):
|
732
|
+
self._full = GBXTileLocation(ar)
|
733
|
+
self._half = GBXTileLocation(ar)
|
734
|
+
self._fill = ar.dword()
|
735
|
+
|
736
|
+
def __str__(self):
|
737
|
+
return f'Full: {self._full}, Half: {self._half}, Fill: {self._fill:08x}'
|
738
|
+
|
739
|
+
|
740
|
+
# --------------------------------------------------------------------
|
741
|
+
class GBXTileSet:
|
742
|
+
def __init__(self,ar):
|
743
|
+
'''A set of tiles'''
|
744
|
+
with VerboseGuard('Reading tile set'):
|
745
|
+
self._name = ar.str()
|
746
|
+
n = ar.word() if Features().size_size != 8 else ar.size()
|
747
|
+
self._ids = [ar.iden()
|
748
|
+
for _ in range(n)]
|
749
|
+
|
750
|
+
def __str__(self):
|
751
|
+
return (self._name + ':' + ','.join([str(i) for i in self._ids]))
|
752
|
+
|
753
|
+
|
754
|
+
# --------------------------------------------------------------------
|
755
|
+
class GBXTileSheet:
|
756
|
+
def __init__(self,ar,transparent):
|
757
|
+
'''A single image that has multiple tile images in it
|
758
|
+
|
759
|
+
X,Y are the tile sizes
|
760
|
+
'''
|
761
|
+
with VerboseGuard('Reading tile sheet'):
|
762
|
+
self._x = ar.word()
|
763
|
+
self._y = ar.word()
|
764
|
+
hasBM = ar.word()
|
765
|
+
self._img = GBXImage(ar,transparent) if hasBM else None
|
766
|
+
|
767
|
+
def sub(self,off):
|
768
|
+
if self._img is None:
|
769
|
+
return None
|
770
|
+
|
771
|
+
return self._img._img.crop((0,off,self._x,off+self._y))
|
772
|
+
|
773
|
+
def __str__(self):
|
774
|
+
bm = str(self._img) if self._img is not None else 'None'
|
775
|
+
return (f'c=({self._x:4d},{self._y:4d}) bitmap={bm}')
|
776
|
+
|
777
|
+
|
778
|
+
# --------------------------------------------------------------------
|
779
|
+
class GBXTileManager(CbManager):
|
780
|
+
def __init__(self,ar):
|
781
|
+
'''Tile manager (including tiles)'''
|
782
|
+
with VerboseGuard('Reading tile manager'):
|
783
|
+
self._transparent = ar.dword()
|
784
|
+
super(GBXTileManager,self).__init__(ar)
|
785
|
+
|
786
|
+
ts = lambda ar : GBXTileSheet(ar, self._transparent)
|
787
|
+
self._tiledefs = self._readSub(ar,GBXTileDefinition,
|
788
|
+
Features().id_size)
|
789
|
+
self._tilesets = self._readSub(ar,GBXTileSet)
|
790
|
+
self._tilesheets = self._readSub(ar,ts)
|
791
|
+
self._toStore = {} # Used in boards, not elsewhere
|
792
|
+
|
793
|
+
def image(self,tileID):
|
794
|
+
if tileID is None:
|
795
|
+
return None
|
796
|
+
if tileID == 0xFFFF:
|
797
|
+
return None
|
798
|
+
|
799
|
+
tileDef = self._tiledefs[tileID]
|
800
|
+
tileSht = self._tilesheets[tileDef._full._sheet]
|
801
|
+
img = tileSht.sub(tileDef._full._offset)
|
802
|
+
return img
|
803
|
+
|
804
|
+
def store(self,tileID):
|
805
|
+
filename = self._toStore.get(tileID,{}).get('filename',None)
|
806
|
+
if filename is None:
|
807
|
+
filename = f'tile_{tileID:04d}.png'
|
808
|
+
self._toStore[tileID] = {
|
809
|
+
'filename': filename,
|
810
|
+
'image' : self.image(tileID)
|
811
|
+
}
|
812
|
+
|
813
|
+
return filename
|
814
|
+
|
815
|
+
|
816
|
+
def __str__(self):
|
817
|
+
return ('Tile manager:\n'
|
818
|
+
+ f' Transparent: {self._transparent:08x}\n'
|
819
|
+
+ super(GBXTileManager,self).__str__()
|
820
|
+
+ self._strSub('tiles',self._tiledefs) + '\n'
|
821
|
+
+ self._strSub('tile sets',self._tilesets) + '\n'
|
822
|
+
+ self._strSub('tile sheets',self._tilesheets))
|
823
|
+
|
824
|
+
#
|
825
|
+
# EOF
|
826
|
+
#
|
827
|
+
# ====================================================================
|
828
|
+
# From piece.py
|
829
|
+
# ====================================================================
|
830
|
+
class GBXPieceDef:
|
831
|
+
def __init__(self,ar):
|
832
|
+
'''Definition of a piece
|
833
|
+
|
834
|
+
FRONT and BACK are tile IDs
|
835
|
+
|
836
|
+
FLAGS is ...
|
837
|
+
'''
|
838
|
+
with VerboseGuard(f'Reading piece definition'):
|
839
|
+
if Features().piece_100:
|
840
|
+
n = ar.size()
|
841
|
+
self._ids = [ar.iden() for _ in range(n)]
|
842
|
+
else:
|
843
|
+
self._ids = [ar.word(),ar.word()]
|
844
|
+
self._flags = ar.word()
|
845
|
+
|
846
|
+
|
847
|
+
@property
|
848
|
+
def _front(self):
|
849
|
+
return self._ids[0]
|
850
|
+
|
851
|
+
@property
|
852
|
+
def _back(self):
|
853
|
+
return self._ids[1] if len(self._ids) > 1 else 0
|
854
|
+
|
855
|
+
def __str__(self):
|
856
|
+
return f'Piece: {self._front:04x},{self._back:04x},{self._flags:04x}'
|
857
|
+
|
858
|
+
# --------------------------------------------------------------------
|
859
|
+
class GBXPieceSet:
|
860
|
+
def __init__(self,ar):
|
861
|
+
'''Set of pieces'''
|
862
|
+
with VerboseGuard(f'Reading piece set'):
|
863
|
+
self._name = ar.str()
|
864
|
+
n = ar.sub_size()
|
865
|
+
self._pieces = [ar.iden() for _ in range(n)]
|
866
|
+
|
867
|
+
def __len__(self):
|
868
|
+
return len(self._pieces)
|
869
|
+
|
870
|
+
def __str__(self):
|
871
|
+
return (f'{self._name}: '+','.join([str(p) for p in self._pieces]))
|
872
|
+
|
873
|
+
# --------------------------------------------------------------------
|
874
|
+
class GBXPieceManager:
|
875
|
+
def __init__(self,ar):
|
876
|
+
'''Manager of pieces'''
|
877
|
+
with VerboseGuard(f'Reading piece manager') as g:
|
878
|
+
self._reserved = [ar.word() for _ in range(4)]
|
879
|
+
g(f'Reserved are {self._reserved}')
|
880
|
+
n = ar.iden();
|
881
|
+
g(f'Will read {n} pieces')
|
882
|
+
self._pieces = [GBXPieceDef(ar) for _ in range(n)]
|
883
|
+
n = ar.sub_size()
|
884
|
+
g(f'Will read {n} sets')
|
885
|
+
self._sets = [GBXPieceSet(ar) for _ in range(n)]
|
886
|
+
|
887
|
+
def __len__(self):
|
888
|
+
return len(self._sets)
|
889
|
+
|
890
|
+
def toDict(self,tileManager,strings):
|
891
|
+
from math import log10, ceil
|
892
|
+
with VerboseGuard(f'Creating dict from piece manager') as gg:
|
893
|
+
gg(f'Has {len(self._sets)} and {len(self._pieces)} pieces')
|
894
|
+
setDigits = int(ceil(log10(len(self)+.5)))
|
895
|
+
pieceDigits = 1
|
896
|
+
for pieceSet in self._sets:
|
897
|
+
pieceDigits = max(pieceDigits,
|
898
|
+
int(ceil(log10(len(pieceSet)+.5))))
|
899
|
+
|
900
|
+
cnt = 0
|
901
|
+
piecesMap = {}
|
902
|
+
setList = []
|
903
|
+
ret = {'map': piecesMap,
|
904
|
+
'sets': setList }
|
905
|
+
|
906
|
+
for ips, pieceSet in enumerate(self._sets):
|
907
|
+
with VerboseGuard(f'Creating dict from piece set '
|
908
|
+
f'{pieceSet._name}') as g:
|
909
|
+
setPrefix = f'piece_{ips:0{setDigits}d}'
|
910
|
+
idList = []
|
911
|
+
setDict = { 'description': pieceSet._name.strip(),
|
912
|
+
'pieces': idList }
|
913
|
+
setList.append(setDict)
|
914
|
+
|
915
|
+
for ipc, pieceID in enumerate(pieceSet._pieces):
|
916
|
+
|
917
|
+
piecePrefix = f'{setPrefix}_{ipc:0{pieceDigits}d}'
|
918
|
+
pieceDef = self._pieces[pieceID]
|
919
|
+
tmpStr = strings._id2str.get(pieceID,'')
|
920
|
+
pieceDesc = tmpStr.replace('\r','').replace('\n',', ').replace('/','\\/')
|
921
|
+
pieceDict = {}
|
922
|
+
if pieceDesc != '':
|
923
|
+
pieceDict['description'] = pieceDesc
|
924
|
+
cnt += 1
|
925
|
+
# pieceList.append(pieceDict)
|
926
|
+
idList .append(pieceID)
|
927
|
+
|
928
|
+
# print(f'{pd} => "{tr}"')
|
929
|
+
for tileId,which in zip([pieceDef._front,
|
930
|
+
pieceDef._back],
|
931
|
+
['front',
|
932
|
+
'back']):
|
933
|
+
img = tileManager.image(tileId)
|
934
|
+
|
935
|
+
if img is None:
|
936
|
+
continue
|
937
|
+
|
938
|
+
sav = f'{piecePrefix}_{which}.png'
|
939
|
+
setname = pieceSet._name.strip()\
|
940
|
+
.replace('\n',' ')\
|
941
|
+
.replace('\r',' ')\
|
942
|
+
.replace('/','\\/')
|
943
|
+
gg(f'Set name, escaped: "{setname}"')
|
944
|
+
pieceDict[which] = {
|
945
|
+
'image': img,
|
946
|
+
'filename': sav,
|
947
|
+
'set': setname }
|
948
|
+
piecesMap[pieceID] = pieceDict
|
949
|
+
g(f'{pieceID}: {pieceDict}')
|
950
|
+
|
951
|
+
gg(f'{list(piecesMap.keys())}')
|
952
|
+
return ret
|
953
|
+
|
954
|
+
|
955
|
+
def __str__(self):
|
956
|
+
return ('Piece manager:\n'
|
957
|
+
+f'Reserved: {self._reserved}\n'
|
958
|
+
+f'# pieces: {len(self._pieces)}\n '
|
959
|
+
+'\n '.join([str(p) for p in self._pieces])+'\n'
|
960
|
+
+f'# piece sets: {len(self._sets)}\n '
|
961
|
+
+'\n '.join([str(p) for p in self._sets])
|
962
|
+
)
|
963
|
+
|
964
|
+
# --------------------------------------------------------------------
|
965
|
+
class GSNPieceEntry:
|
966
|
+
def __init__(self,ar,vers,i):
|
967
|
+
'''Manager of pieces'''
|
968
|
+
with VerboseGuard(f'Reading piece # {i:3d} ({vers//256}.{vers%256})'):
|
969
|
+
self._side = ar.byte()
|
970
|
+
self._facing = ar.word()
|
971
|
+
self._owner = ar.word() if vers < num_version(3,10) else ar.dword()
|
972
|
+
|
973
|
+
def __str__(self):
|
974
|
+
return f'Piece: {self._side}, {self._facing:3d}, {self._owner:08x}'
|
975
|
+
|
976
|
+
# --------------------------------------------------------------------
|
977
|
+
class GSNPieceTable:
|
978
|
+
def __init__(self,ar,vers):
|
979
|
+
'''Manager of pieces'''
|
980
|
+
with VerboseGuard(f'Reading piece table'):
|
981
|
+
self._reserved = [ar.word() for _ in range(4)]
|
982
|
+
n = ar.word()#sub_size()
|
983
|
+
if Features().piece_100:
|
984
|
+
dummy = ar.word();
|
985
|
+
self._pieces = [GSNPieceEntry(ar,vers,i) for i in range(n)]
|
986
|
+
|
987
|
+
def __str__(self):
|
988
|
+
pl = '\n '.join([str(s) for s in self._pieces])
|
989
|
+
return (f'Piece table: {self._reserved} {len(self._pieces)}'
|
990
|
+
f'\n {pl}\n')
|
991
|
+
#
|
992
|
+
# EOF
|
993
|
+
#
|
994
|
+
# ====================================================================
|
995
|
+
# From mark.py
|
996
|
+
|
997
|
+
# ====================================================================
|
998
|
+
class GBXMarkDef:
|
999
|
+
def __init__(self,ar):
|
1000
|
+
'''Definition of a mark'''
|
1001
|
+
with VerboseGuard(f'Reading mark definition'):
|
1002
|
+
self._id = ar.iden()
|
1003
|
+
self._flags = ar.word()
|
1004
|
+
|
1005
|
+
def __str__(self):
|
1006
|
+
return f'Mark: {self._id:04x},{self._flags:04x}'
|
1007
|
+
|
1008
|
+
# --------------------------------------------------------------------
|
1009
|
+
class GBXMarkSet:
|
1010
|
+
def __init__(self,ar):
|
1011
|
+
'''Set of marks'''
|
1012
|
+
with VerboseGuard(f'Reading mark set'):
|
1013
|
+
self._name = ar.str()
|
1014
|
+
self._viz = ar.word()
|
1015
|
+
n = ar.sub_size()
|
1016
|
+
self._marks = [ar.iden() for _ in range(n)]
|
1017
|
+
|
1018
|
+
def __len__(self):
|
1019
|
+
return len(self._marks)
|
1020
|
+
|
1021
|
+
def __str__(self):
|
1022
|
+
return (f'{self._name}: '+','.join([str(p) for p in self._marks]))
|
1023
|
+
|
1024
|
+
# --------------------------------------------------------------------
|
1025
|
+
class GBXMarkManager:
|
1026
|
+
def __init__(self,ar):
|
1027
|
+
'''Manager of marks'''
|
1028
|
+
with VerboseGuard(f'Reading mark manager'):
|
1029
|
+
self._reserved = [ar.word() for _ in range(4)]
|
1030
|
+
n = ar.iden()
|
1031
|
+
self._marks = [GBXMarkDef(ar) for _ in range(n)]
|
1032
|
+
n = ar.sub_size()
|
1033
|
+
self._sets = [GBXMarkSet(ar) for _ in range(n)]
|
1034
|
+
|
1035
|
+
def __len__(self):
|
1036
|
+
return len(self._sets)
|
1037
|
+
|
1038
|
+
def toDict(self,tileManager,strings):
|
1039
|
+
from math import log10, ceil
|
1040
|
+
with VerboseGuard(f'Creating dict from mark manager'):
|
1041
|
+
setDigits = int(ceil(log10(len(self)+.5)))
|
1042
|
+
markDigits = 1
|
1043
|
+
for markSet in self._sets:
|
1044
|
+
markDigits = max(markDigits,
|
1045
|
+
int(ceil(log10(len(markSet)+.5))))
|
1046
|
+
|
1047
|
+
marksMap = {}
|
1048
|
+
setList = []
|
1049
|
+
ret = {'map': marksMap,
|
1050
|
+
'sets': setList }
|
1051
|
+
|
1052
|
+
for ips, markSet in enumerate(self._sets):
|
1053
|
+
with VerboseGuard(f'Creating dict mark set {markSet._name}'):
|
1054
|
+
setPrefix = f'mark_{ips:0{setDigits}d}'
|
1055
|
+
idList = []
|
1056
|
+
setDict = { 'description': markSet._name.strip(),
|
1057
|
+
'marks': idList }
|
1058
|
+
setList.append(setDict)
|
1059
|
+
|
1060
|
+
for ipc, markID in enumerate(markSet._marks):
|
1061
|
+
markPrefix = f'{setPrefix}_{ipc:0{markDigits}d}'
|
1062
|
+
markDef = self._marks[markID]
|
1063
|
+
tmpStr = strings._id2str.get(markID|0xF0000000,'')
|
1064
|
+
markDesc = tmpStr.replace('\r','').replace('\n',', ')
|
1065
|
+
markDict = {}
|
1066
|
+
if markDesc != '':
|
1067
|
+
markDict['description'] = markDesc
|
1068
|
+
marksMap[markID] = markDict
|
1069
|
+
idList .append(markID)
|
1070
|
+
|
1071
|
+
img = tileManager.image(markDef._id)
|
1072
|
+
|
1073
|
+
if img is None:
|
1074
|
+
continue
|
1075
|
+
|
1076
|
+
sav = f'{markPrefix}.png'
|
1077
|
+
markDict.update({'image': img,
|
1078
|
+
'filename': sav })
|
1079
|
+
|
1080
|
+
return ret
|
1081
|
+
|
1082
|
+
def __str__(self):
|
1083
|
+
return ('Mark manager:\n'
|
1084
|
+
+f'Reserved: {self._reserved}\n'
|
1085
|
+
+f'# marks: {len(self._marks)}\n '
|
1086
|
+
+'\n '.join([str(p) for p in self._marks])+'\n'
|
1087
|
+
+f'# mark sets: {len(self._sets)}\n '
|
1088
|
+
+'\n '.join([str(p) for p in self._sets])
|
1089
|
+
)
|
1090
|
+
|
1091
|
+
#
|
1092
|
+
# EOF
|
1093
|
+
#
|
1094
|
+
# ====================================================================
|
1095
|
+
# From draw.py
|
1096
|
+
|
1097
|
+
# ====================================================================
|
1098
|
+
class GBXDraw:
|
1099
|
+
def __init__(self,ar):
|
1100
|
+
'''Base class for drawing objects'''
|
1101
|
+
self._flags = ar.dword()
|
1102
|
+
self._left = ar.word()
|
1103
|
+
self._top = ar.word()
|
1104
|
+
self._right = ar.word()
|
1105
|
+
self._bottom = ar.word()
|
1106
|
+
|
1107
|
+
def isSecondPass(self):
|
1108
|
+
return self._flags & 0x00000008
|
1109
|
+
|
1110
|
+
def bbWidth(self):
|
1111
|
+
return self._right - self._left
|
1112
|
+
|
1113
|
+
def bbHeight(self):
|
1114
|
+
return self._bottom - self._top
|
1115
|
+
|
1116
|
+
def centerX(self):
|
1117
|
+
return (self._left + self._right)//2
|
1118
|
+
|
1119
|
+
def centerY(self):
|
1120
|
+
return (self._top + self._bottom)//2
|
1121
|
+
|
1122
|
+
def center(self):
|
1123
|
+
return (self.centerX(),self.centerY())
|
1124
|
+
|
1125
|
+
def upperLeft(self):
|
1126
|
+
return (self._left,self._top)
|
1127
|
+
|
1128
|
+
def lowerRight(self):
|
1129
|
+
return (self._right,self._bottom)
|
1130
|
+
|
1131
|
+
def bbSize(self):
|
1132
|
+
return (self.bbWidth(),self.bbHeight())
|
1133
|
+
|
1134
|
+
@classmethod
|
1135
|
+
def hex(cls,val):
|
1136
|
+
if val == 0xFF000000:
|
1137
|
+
h = 'none'
|
1138
|
+
else:
|
1139
|
+
b = (val >> 16) & 0xFF
|
1140
|
+
g = (val >> 8) & 0xFF
|
1141
|
+
r = (val >> 0) & 0xFF
|
1142
|
+
h = f'rgb({r},{g},{b})'
|
1143
|
+
return h
|
1144
|
+
|
1145
|
+
def toDict(self,calc):
|
1146
|
+
return None
|
1147
|
+
|
1148
|
+
def baseDict(self):
|
1149
|
+
return {
|
1150
|
+
'left': self._left,
|
1151
|
+
'top': self._top,
|
1152
|
+
'right': self._right,
|
1153
|
+
'bottom': self._bottom,
|
1154
|
+
'x': self.centerX(),
|
1155
|
+
'y': self.centerY()
|
1156
|
+
}
|
1157
|
+
|
1158
|
+
def svg(self,dwg,g,defmap):
|
1159
|
+
print(f'{self.__class__}.svg method not implemented')
|
1160
|
+
pass
|
1161
|
+
|
1162
|
+
def __str__(self):
|
1163
|
+
return (f'Flags:{self._flags:08x} '
|
1164
|
+
+ f'({self._left},{self._top})x({self._right},{self._bottom})')
|
1165
|
+
|
1166
|
+
# --------------------------------------------------------------------
|
1167
|
+
class GBXRectangle(GBXDraw):
|
1168
|
+
def __init__(self,ar):
|
1169
|
+
'''Draw a rectangle'''
|
1170
|
+
with VerboseGuard(f'Reading rectangle'):
|
1171
|
+
super(GBXRectangle,self).__init__(ar)
|
1172
|
+
self._fill = ar.dword()
|
1173
|
+
self._line = ar.dword()
|
1174
|
+
self._width = ar.word()
|
1175
|
+
|
1176
|
+
def svg(self,dwg,g,defmap):
|
1177
|
+
r = g.add(dwg.rect(insert=(self.upperLeft()),
|
1178
|
+
size=(self.bbSize()),
|
1179
|
+
fill=self.hex(self._fill),
|
1180
|
+
stroke=self.hex(self._line),
|
1181
|
+
stroke_width=self._width))
|
1182
|
+
|
1183
|
+
def __str__(self):
|
1184
|
+
return 'Rectangle: '+super(GBXRectangle,self).__str__()
|
1185
|
+
|
1186
|
+
# --------------------------------------------------------------------
|
1187
|
+
class GBXEllipse(GBXRectangle):
|
1188
|
+
def __init__(self,ar):
|
1189
|
+
'''Draw an ellipse'''
|
1190
|
+
with VerboseGuard(f'Reading ellipse'):
|
1191
|
+
super(GBXEllipse,self).__init__(ar)
|
1192
|
+
|
1193
|
+
def svg(self,dwg,g,defmap):
|
1194
|
+
'''Create SVG object'''
|
1195
|
+
g.add(dwg.ellipse(center=(self.centerX(),self.centerY()),
|
1196
|
+
r=(self.bbWidth(),self.bbHeight()),
|
1197
|
+
fill=self.hex(self._fill),
|
1198
|
+
stroke=self.hex(self._line),
|
1199
|
+
stroke_width=self._width))
|
1200
|
+
def __str__(self):
|
1201
|
+
return 'Ellipse: '+super(GBXRectangle,self).__str__()
|
1202
|
+
|
1203
|
+
# --------------------------------------------------------------------
|
1204
|
+
class GBXLine(GBXDraw):
|
1205
|
+
def __init__(self,ar):
|
1206
|
+
'''Draw a line'''
|
1207
|
+
with VerboseGuard(f'Reading line'):
|
1208
|
+
super(GBXLine,self).__init__(ar)
|
1209
|
+
self._x0 = ar.word()
|
1210
|
+
self._y0 = ar.word()
|
1211
|
+
self._x1 = ar.word()
|
1212
|
+
self._y1 = ar.word()
|
1213
|
+
self._line = ar.dword()
|
1214
|
+
self._width = ar.word()
|
1215
|
+
|
1216
|
+
def svg(self,dwg,g,defmap):
|
1217
|
+
'''Create SVG object'''
|
1218
|
+
g.add(dwg.line(start=(self._x0,self._y0),
|
1219
|
+
end=(self._x1,self._y1),
|
1220
|
+
stroke=self.hex(self._line),
|
1221
|
+
stroke_width=self._width))
|
1222
|
+
|
1223
|
+
|
1224
|
+
def __str__(self):
|
1225
|
+
return 'Line: ' + super(GBXLine,self).__str__()
|
1226
|
+
# f'({self._x0},{self._y0}) -> ({self._x1},{self._y1})')
|
1227
|
+
|
1228
|
+
|
1229
|
+
# --------------------------------------------------------------------
|
1230
|
+
class GBXTile(GBXDraw):
|
1231
|
+
def __init__(self,ar):
|
1232
|
+
'''Draw a tile'''
|
1233
|
+
with VerboseGuard(f'Reading tile'):
|
1234
|
+
super(GBXTile,self).__init__(ar)
|
1235
|
+
self._id = ar.word()
|
1236
|
+
|
1237
|
+
def svgDef(self,dwg,tileManager,markManager,defmap):
|
1238
|
+
'''Create SVG definition from image'''
|
1239
|
+
if self._id in defmap:
|
1240
|
+
return
|
1241
|
+
|
1242
|
+
img = tileManager.image(self._id)
|
1243
|
+
data = GBXImage.b64encode(img)
|
1244
|
+
if data is None:
|
1245
|
+
return
|
1246
|
+
|
1247
|
+
iden = f'tile_{self._id:04x}'
|
1248
|
+
img = dwg.defs.add(dwg.image(id=iden,href=(data),
|
1249
|
+
size=(img.width,img.height)))
|
1250
|
+
defmap[self._id] = img
|
1251
|
+
|
1252
|
+
def svg(self,dwg,g,defmap):
|
1253
|
+
'''Create SVG object'''
|
1254
|
+
if self._id not in defmap: return
|
1255
|
+
|
1256
|
+
g.add(dwg.use(defmap[self._id],
|
1257
|
+
insert=(self._left,self._top)))
|
1258
|
+
|
1259
|
+
def __str__(self):
|
1260
|
+
return f'Tile: {self._id} ' + super(GBXTile,self).__str__()
|
1261
|
+
|
1262
|
+
# --------------------------------------------------------------------
|
1263
|
+
class GBXText(GBXDraw):
|
1264
|
+
def __init__(self,ar):
|
1265
|
+
'''Draw text'''
|
1266
|
+
with VerboseGuard(f'Reading text'):
|
1267
|
+
super(GBXText,self).__init__(ar)
|
1268
|
+
self._angle = ar.word()
|
1269
|
+
self._color = ar.dword()
|
1270
|
+
self._text = ar.str()
|
1271
|
+
self._font = CbFont(ar)
|
1272
|
+
|
1273
|
+
def svg(self,dwg,g,defmap):
|
1274
|
+
'''Create SVG object'''
|
1275
|
+
g.add(dwg.text(self._text,
|
1276
|
+
insert=(self._left,self._bottom),
|
1277
|
+
rotate=[self._angle],
|
1278
|
+
fill=self.hex(self._color),
|
1279
|
+
font_family='monospace' if self._font._name == '' else self._font._name,
|
1280
|
+
font_size=self._font._size,
|
1281
|
+
font_weight='bold' if self._font.isBold() else 'normal',
|
1282
|
+
font_style='italic' if self._font.isItalic() else 'normal',
|
1283
|
+
text_decoration='underline' if self._font.isUnderline() else 'none'))
|
1284
|
+
|
1285
|
+
def __str__(self):
|
1286
|
+
return f'Text: "{self._text}" '+super(GBXText,self).__str__()
|
1287
|
+
|
1288
|
+
# --------------------------------------------------------------------
|
1289
|
+
class GBXPolyline(GBXDraw):
|
1290
|
+
def __init__(self,ar):
|
1291
|
+
'''Draw a polyline'''
|
1292
|
+
with VerboseGuard(f'Reading polyline'):
|
1293
|
+
super(GBXPolyline,self).__init__(ar)
|
1294
|
+
self._fill = ar.dword()
|
1295
|
+
self._line = ar.dword()
|
1296
|
+
self._width = ar.word()
|
1297
|
+
n = (ar.word() if Features().size_size != 8 else
|
1298
|
+
ar.size())
|
1299
|
+
self._points = [[ar.word(),ar.word()] for _ in range(n)]
|
1300
|
+
|
1301
|
+
def svg(self,dwg,g,defmap):
|
1302
|
+
'''Create SVG object'''
|
1303
|
+
g.add(dwg.polyline(self._points,
|
1304
|
+
fill=self.hex(self._fill),
|
1305
|
+
stroke=self.hex(self._line),
|
1306
|
+
stroke_width=self._width))
|
1307
|
+
|
1308
|
+
def __str__(self):
|
1309
|
+
return f'Polyline: {len(self._points)} '+super(GBXPolyline,self).__str__()
|
1310
|
+
|
1311
|
+
# --------------------------------------------------------------------
|
1312
|
+
class GBXBitmap(GBXDraw):
|
1313
|
+
CNT = 0
|
1314
|
+
|
1315
|
+
def __init__(self,ar):
|
1316
|
+
'''Draw a bitmap'''
|
1317
|
+
with VerboseGuard(f'Reading bitmap'):
|
1318
|
+
super(GBXBitmap,self).__init__(ar)
|
1319
|
+
sav = f'B{GBXBitmap.CNT:04d}.png'
|
1320
|
+
GBXBitmap.CNT += 1
|
1321
|
+
self._scale = ar.word()
|
1322
|
+
self._img = GBXImage(ar,save=None)
|
1323
|
+
|
1324
|
+
def svg(self,dwg,g,defmap):
|
1325
|
+
'''Create SVG object'''
|
1326
|
+
data = GBXImage.b64encode(self._img._img)
|
1327
|
+
size = self._img._img.width, self._img._img.height
|
1328
|
+
g.add(dwg.image(insert=(self._left,self._top),
|
1329
|
+
size=size,
|
1330
|
+
href=(data)))
|
1331
|
+
|
1332
|
+
def __str__(self):
|
1333
|
+
return f'Bitmap: {self._img} ' + super(GBXBitmap,self).__str__()
|
1334
|
+
|
1335
|
+
# --------------------------------------------------------------------
|
1336
|
+
class GBXPiece(GBXDraw):
|
1337
|
+
def __init__(self,ar):
|
1338
|
+
'''Draw a piece'''
|
1339
|
+
with VerboseGuard(f'Reading piece (draw)'):
|
1340
|
+
super(GBXPiece,self).__init__(ar)
|
1341
|
+
self._id = ar.iden()
|
1342
|
+
|
1343
|
+
def toDict(self,calc=None):
|
1344
|
+
d = {'type': 'Piece',
|
1345
|
+
'id': self._id,
|
1346
|
+
'pixel': self.baseDict()
|
1347
|
+
}
|
1348
|
+
if calc is not None:
|
1349
|
+
d['grid'] = calc(*self.center())
|
1350
|
+
return d
|
1351
|
+
|
1352
|
+
def __str__(self):
|
1353
|
+
return f'Piece: {self._id} ' + super(GBXPiece,self).__str__()
|
1354
|
+
|
1355
|
+
# --------------------------------------------------------------------
|
1356
|
+
class GBXMark(GBXDraw):
|
1357
|
+
def __init__(self,ar):
|
1358
|
+
'''Draw a mark tile'''
|
1359
|
+
with VerboseGuard(f'Reading mark (draw)'):
|
1360
|
+
super(GBXMark,self).__init__(ar)
|
1361
|
+
self._id = ar.size()
|
1362
|
+
self._mid = ar.iden()
|
1363
|
+
self._ang = ar.word()
|
1364
|
+
|
1365
|
+
def toDict(self,calc=None):
|
1366
|
+
d = {'type': 'Mark',
|
1367
|
+
'id': self._mid,
|
1368
|
+
'pixel': self.baseDict()
|
1369
|
+
}
|
1370
|
+
if calc is not None:
|
1371
|
+
d['grid'] = calc(*self.center())
|
1372
|
+
return d
|
1373
|
+
def svgDef(self,dwg,tileManager,markManager,defmap):
|
1374
|
+
'''Create SVG def from mark'''
|
1375
|
+
if self._id in defmap:
|
1376
|
+
return
|
1377
|
+
|
1378
|
+
data = GBXImage.b64encode(tileManager.image(self._id))
|
1379
|
+
if data is None:
|
1380
|
+
return
|
1381
|
+
|
1382
|
+
iden = f'mark_{self._id:04x}'
|
1383
|
+
img = dwg.defs.add(dwg.image(id=iden,href=(data)))
|
1384
|
+
defmap[self._id] = img
|
1385
|
+
|
1386
|
+
def svg(self,dwg,g,defmap):
|
1387
|
+
'''Create SVG object'''
|
1388
|
+
if self._id not in defmap: return
|
1389
|
+
|
1390
|
+
g.add(dwg.use(defmap[self._id],
|
1391
|
+
insert=(self._left,self._top)))
|
1392
|
+
|
1393
|
+
def __str__(self):
|
1394
|
+
return f'Mark: {self._id}/{self._mid} ' + super(GBXMark,self).__str__()
|
1395
|
+
|
1396
|
+
# --------------------------------------------------------------------
|
1397
|
+
class GBXLineObj(GBXLine):
|
1398
|
+
def __init__(self,ar):
|
1399
|
+
'''Line object via reference'''
|
1400
|
+
with VerboseGuard(f'Reading line object'):
|
1401
|
+
super(GBXLineObj,self).__init__(ar)
|
1402
|
+
|
1403
|
+
self._id = ar.iden()
|
1404
|
+
|
1405
|
+
def __str__(self):
|
1406
|
+
return f'Line: {self._id} ' + super(GBXLineObj,self).__str__()
|
1407
|
+
|
1408
|
+
|
1409
|
+
# --------------------------------------------------------------------
|
1410
|
+
class GBXDrawList:
|
1411
|
+
RECT = 0
|
1412
|
+
ELLIPSE = 1
|
1413
|
+
LINE = 2
|
1414
|
+
TILE = 3
|
1415
|
+
TEXT = 4
|
1416
|
+
POLYLINE = 5
|
1417
|
+
BITMAP = 6
|
1418
|
+
PIECE = 0x80
|
1419
|
+
MARK = 0x81
|
1420
|
+
LINEOBJ = 0x82
|
1421
|
+
|
1422
|
+
TMAP = { RECT: GBXRectangle,
|
1423
|
+
ELLIPSE: GBXEllipse,
|
1424
|
+
LINE: GBXLine,
|
1425
|
+
TILE: GBXTile,
|
1426
|
+
TEXT: GBXText,
|
1427
|
+
POLYLINE: GBXPolyline,
|
1428
|
+
BITMAP: GBXBitmap,
|
1429
|
+
PIECE: GBXPiece,
|
1430
|
+
MARK: GBXMark,
|
1431
|
+
LINEOBJ: GBXLineObj}
|
1432
|
+
|
1433
|
+
def __init__(self,ar):
|
1434
|
+
'''A list of drawn objects'''
|
1435
|
+
with VerboseGuard(f'Reading draw list'):
|
1436
|
+
n = ar.sub_size()
|
1437
|
+
|
1438
|
+
self._obj = [self._readObj(ar) for n in range(n)]
|
1439
|
+
|
1440
|
+
def toDict(self,calc=None):
|
1441
|
+
with VerboseGuard(f'Making dictionary from draw list at pass'):
|
1442
|
+
ret = []
|
1443
|
+
for i in self._obj:
|
1444
|
+
d = i.toDict(calc)
|
1445
|
+
if d is None:
|
1446
|
+
continue
|
1447
|
+
|
1448
|
+
ret.append(d)
|
1449
|
+
|
1450
|
+
return ret
|
1451
|
+
|
1452
|
+
def _readObj(self,ar):
|
1453
|
+
'''Read one object'''
|
1454
|
+
tpe = ar.word()
|
1455
|
+
cls = self.TMAP.get(tpe,None)
|
1456
|
+
if cls is None:
|
1457
|
+
raise RuntimeError(f'Unknown type of draw: {tpe}')
|
1458
|
+
|
1459
|
+
return cls(ar)
|
1460
|
+
|
1461
|
+
def svgDefs(self,dwg,tileManager,markManager,defmap):
|
1462
|
+
'''Create SVG defs'''
|
1463
|
+
with VerboseGuard(f'Create SVG defs from draw list'):
|
1464
|
+
for i in self._obj:
|
1465
|
+
if type(i) not in [GBXTile,GBXMark]: continue
|
1466
|
+
|
1467
|
+
i.svgDef(dwg,tileManager,markManager,defmap)
|
1468
|
+
|
1469
|
+
def svg(self,dwg,g,passNo,defmap):
|
1470
|
+
'''Create SVG objects'''
|
1471
|
+
with VerboseGuard(f'Drawing SVG from draw list at pass {passNo}'
|
1472
|
+
f' ({len(self._obj)} objects)') as gg:
|
1473
|
+
for i in self._obj:
|
1474
|
+
if passNo == 1 and i.isSecondPass():
|
1475
|
+
continue
|
1476
|
+
elif passNo == 2 and not i.isSecondPass():
|
1477
|
+
continue
|
1478
|
+
gg(f'Drawing {i}')
|
1479
|
+
i.svg(dwg,g,defmap)
|
1480
|
+
|
1481
|
+
def __str__(self):
|
1482
|
+
return '\n '.join([str(o) for o in self._obj])
|
1483
|
+
|
1484
|
+
#
|
1485
|
+
# EOF
|
1486
|
+
#
|
1487
|
+
# ====================================================================
|
1488
|
+
# From cell.py
|
1489
|
+
|
1490
|
+
# ====================================================================
|
1491
|
+
class GBXCellGeometry:
|
1492
|
+
RECTANGLE = 0
|
1493
|
+
HORIZONTAL_BRICK = 1
|
1494
|
+
VERTICAL_BRICK = 2
|
1495
|
+
HEXAGON = 3
|
1496
|
+
SIDEWAYS_HEXAGON = 4
|
1497
|
+
STAGGER_OUT = 0
|
1498
|
+
STAGGER_IN = 1
|
1499
|
+
TYPES = {
|
1500
|
+
RECTANGLE : 'rectangle',
|
1501
|
+
HORIZONTAL_BRICK: 'horizontal brick',
|
1502
|
+
VERTICAL_BRICK : 'vertical brick',
|
1503
|
+
HEXAGON : 'hexagon',
|
1504
|
+
SIDEWAYS_HEXAGON: 'sideways hexagon'
|
1505
|
+
}
|
1506
|
+
STAGGERS = {
|
1507
|
+
STAGGER_OUT: 'out',
|
1508
|
+
STAGGER_IN: 'in'
|
1509
|
+
}
|
1510
|
+
|
1511
|
+
def __init__(self,ar):
|
1512
|
+
'''The geometry of cells'''
|
1513
|
+
with VerboseGuard('Reading cell geometry'):
|
1514
|
+
from numpy import max
|
1515
|
+
|
1516
|
+
self._type = ar.word()
|
1517
|
+
self._stagger = ar.word()
|
1518
|
+
self._left = ar.word()
|
1519
|
+
self._top = ar.word()
|
1520
|
+
self._right = ar.word()
|
1521
|
+
self._bottom = ar.word()
|
1522
|
+
n = 7 if self._type > 2 else 5
|
1523
|
+
self._points = [[ar.word(),ar.word()] for _ in range(n)]
|
1524
|
+
size = max(self._points,axis=0)
|
1525
|
+
self._dx = int(size[0])
|
1526
|
+
self._dy = int(size[1])
|
1527
|
+
self._size = [self._dx,self._dy]
|
1528
|
+
|
1529
|
+
if self._type == self.HEXAGON:
|
1530
|
+
self._dx = int(0.75 * self._dx)
|
1531
|
+
elif self._type == self.SIDEWAYS_HEXAGON:
|
1532
|
+
self._dy = int(0.75 * self._dy)
|
1533
|
+
|
1534
|
+
def toDict(self):
|
1535
|
+
from numpy import max
|
1536
|
+
return {'shape': self.TYPES.get(self._type,''),
|
1537
|
+
'stagger': self.STAGGERS.get(self._stagger,''),
|
1538
|
+
'size': self._size,
|
1539
|
+
'bounding box (ltrb)':
|
1540
|
+
(self._left, self._top, self._right, self._bottom),
|
1541
|
+
'points': self._points }
|
1542
|
+
|
1543
|
+
def svgDef(self,dwg):
|
1544
|
+
with VerboseGuard('Defining SVG cell geometry'):
|
1545
|
+
if self._type in [0,1,2]:
|
1546
|
+
return dwg.defs.add(dwg.rect(id='cell',
|
1547
|
+
size=(self._right-self._left,
|
1548
|
+
self._bottom-self._top)))
|
1549
|
+
|
1550
|
+
return dwg.defs.add(dwg.polygon(self._points,id='cell'))
|
1551
|
+
|
1552
|
+
def translate(self,row,col,center=False):
|
1553
|
+
x = col * self._dx
|
1554
|
+
y = row * self._dy
|
1555
|
+
if self._type == self.RECTANGLE: # No offset for rectangles
|
1556
|
+
return x,y
|
1557
|
+
if self._type in [self.HORIZONTAL_BRICK,self.SIDEWAYS_HEXAGON]:
|
1558
|
+
x += self._dx//2 if (row % 2) != self._stagger else 0
|
1559
|
+
if self._type in [self.VERTICAL_BRICK,self.HEXAGON]:
|
1560
|
+
y += self._dy//2 if (col % 2) != self._stagger else 0
|
1561
|
+
if center:
|
1562
|
+
x += self._size[0]//2
|
1563
|
+
y += self._size[1]//2
|
1564
|
+
return x,y
|
1565
|
+
|
1566
|
+
def inverse(self,x,y):
|
1567
|
+
col = x / self._dx
|
1568
|
+
row = y / self._dy
|
1569
|
+
if self._type in [self.HORIZONTAL_BRICK,self.SIDEWAYS_HEXAGON]:
|
1570
|
+
col -= .5 if (int(row) % 2) != self._stagger else 0
|
1571
|
+
if self._type in [self.VERTICAL_BRICK,self.HEXAGON]:
|
1572
|
+
row -= .5 if (int(col) % 2) != self._stagger else 0
|
1573
|
+
|
1574
|
+
# CyberBoard start at 1
|
1575
|
+
return int(row)+1, int(col)+1
|
1576
|
+
|
1577
|
+
|
1578
|
+
def boardSize(self,nrows,ncols):
|
1579
|
+
w = ncols * self._dx
|
1580
|
+
h = nrows * self._dy
|
1581
|
+
|
1582
|
+
if self._type in [2,3]:
|
1583
|
+
h += self._dy // 2
|
1584
|
+
if self._type in [1,4]:
|
1585
|
+
w += self._dx // 2
|
1586
|
+
if self._type == 3:
|
1587
|
+
w += self._dx // 3
|
1588
|
+
if self._type == 4:
|
1589
|
+
h += self._dy // 3
|
1590
|
+
|
1591
|
+
return w+1,h+1
|
1592
|
+
|
1593
|
+
def __str__(self):
|
1594
|
+
return (f'type: {self.TYPES.get(self._type,"")} '
|
1595
|
+
+ f'stagger: {self.STAGGERS.get(self._stagger,"")} '
|
1596
|
+
+ f'({self._left},{self._top})x({self._right},{self._bottom}) '
|
1597
|
+
+ f': [{self._points}]')
|
1598
|
+
|
1599
|
+
|
1600
|
+
# --------------------------------------------------------------------
|
1601
|
+
class GBXCell:
|
1602
|
+
def __init__(self,ar,row,column):
|
1603
|
+
'''A single cell'''
|
1604
|
+
with VerboseGuard(f'Reading cell row={row} column={column}'):
|
1605
|
+
self._row = row
|
1606
|
+
self._column = column
|
1607
|
+
if Features().id_size == 4:
|
1608
|
+
self._is_tile = ar.byte();
|
1609
|
+
self._tile = ar.dword()
|
1610
|
+
if Features().id_size != 4:
|
1611
|
+
self._is_tile = (self._tile >> 16) == 0xFFFF;
|
1612
|
+
if self._is_tile:
|
1613
|
+
self._tile = self._tile & 0xFFFF
|
1614
|
+
|
1615
|
+
def tileID(self):
|
1616
|
+
if not self._is_tile:
|
1617
|
+
return None
|
1618
|
+
return self._tile
|
1619
|
+
|
1620
|
+
def color(self):
|
1621
|
+
if self._is_tile:
|
1622
|
+
return None
|
1623
|
+
return GBXDraw.hex(self._tile)
|
1624
|
+
|
1625
|
+
def toDict(self,tileManager,calc=None):
|
1626
|
+
d = {'row': self._row,
|
1627
|
+
'column': self._column}
|
1628
|
+
if not self._is_tile:
|
1629
|
+
d['color'] = GBXDraw.hex(self._tile)
|
1630
|
+
else:
|
1631
|
+
d['tile'] = tileManager.store(self._tile)
|
1632
|
+
if calc is not None:
|
1633
|
+
d['pixel'] = calc.translate(self._row,self._column,True)
|
1634
|
+
return d
|
1635
|
+
|
1636
|
+
|
1637
|
+
def svgDef(self,dwg,tileManager,ptm):
|
1638
|
+
tileID = self.tileID()
|
1639
|
+
if tileID is None:
|
1640
|
+
return
|
1641
|
+
|
1642
|
+
if tileID in ptm: # Have it
|
1643
|
+
return
|
1644
|
+
|
1645
|
+
with VerboseGuard(f'Defining SVG pattern'):
|
1646
|
+
img = tileManager.image(tileID)
|
1647
|
+
data = GBXImage.b64encode(img)
|
1648
|
+
if data is None:
|
1649
|
+
return
|
1650
|
+
|
1651
|
+
iden = f'terrain_{tileID:04x}'
|
1652
|
+
pat = dwg.defs.add(dwg.pattern(id=iden,
|
1653
|
+
size=(img.width,img.height)))
|
1654
|
+
pat.add(dwg.image(href=(data)))
|
1655
|
+
ptm[tileID] = pat
|
1656
|
+
|
1657
|
+
def svg(self,dwg,g,cell,geom,ptm):
|
1658
|
+
tileID = self.tileID()
|
1659
|
+
if tileID is not None:
|
1660
|
+
fill = ptm[tileID].get_paint_server()
|
1661
|
+
else:
|
1662
|
+
fill = self.color()
|
1663
|
+
|
1664
|
+
trans = geom.translate(self._row,self._column)
|
1665
|
+
iden = f'cell_bg_{self._column:03d}{self._row:03d}'
|
1666
|
+
g.add(dwg.use(cell,insert=trans,fill=fill,id=iden))
|
1667
|
+
|
1668
|
+
def svgFrame(self,dwg,g,cell,geom,color):
|
1669
|
+
trans = geom.translate(self._row,self._column)
|
1670
|
+
iden = f'cell_fg_{self._column:03d}{self._row:03d}'
|
1671
|
+
g.add(dwg.use(cell,
|
1672
|
+
insert=trans,
|
1673
|
+
stroke=GBXDraw.hex(color),
|
1674
|
+
fill='none',
|
1675
|
+
id=iden))
|
1676
|
+
|
1677
|
+
|
1678
|
+
def __str__(self):
|
1679
|
+
return f'({self._row:02d},{self._column:02d}): {self._tile:08x}'
|
1680
|
+
|
1681
|
+
#
|
1682
|
+
# EOF
|
1683
|
+
#
|
1684
|
+
# ====================================================================
|
1685
|
+
# From board.py
|
1686
|
+
|
1687
|
+
# --------------------------------------------------------------------
|
1688
|
+
class GBXBoardCalculator:
|
1689
|
+
def __init__(self,board):
|
1690
|
+
self._geometry = board._full
|
1691
|
+
self._nRows = board._nRows
|
1692
|
+
self._nCols = board._nCols
|
1693
|
+
self._rowOffset = board._rowOffset
|
1694
|
+
self._colOffset = board._colOffset
|
1695
|
+
self._rowInvert = board._rowInvert
|
1696
|
+
self._colInvert = board._colInvert
|
1697
|
+
|
1698
|
+
def __call__(self,x,y):
|
1699
|
+
# Shift depending on grid type and stagger
|
1700
|
+
row, col = self._geometry.inverse(x,y)
|
1701
|
+
if self._rowInvert:
|
1702
|
+
row = self._nRows - row - 1
|
1703
|
+
if self._colInvert:
|
1704
|
+
col = self._nCols - col - 1
|
1705
|
+
|
1706
|
+
return row+self._rowOffset, col+self._colOffset
|
1707
|
+
|
1708
|
+
# --------------------------------------------------------------------
|
1709
|
+
class GBXBoard:
|
1710
|
+
def __init__(self,ar):
|
1711
|
+
'''A board'''
|
1712
|
+
with VerboseGuard(f'Reading board') as g:
|
1713
|
+
self._serial = ar.iden()
|
1714
|
+
self._visible = ar.word()
|
1715
|
+
self._snap = ar.word()
|
1716
|
+
self._xSnap = ar.dword()
|
1717
|
+
self._ySnap = ar.dword()
|
1718
|
+
self._xSnapOffset = ar.dword()
|
1719
|
+
self._ySnapOffset = ar.dword()
|
1720
|
+
self._maxLayer = ar.word()
|
1721
|
+
self._background = ar.dword()
|
1722
|
+
self._name = ar.str()
|
1723
|
+
hasDraw = ar.word()
|
1724
|
+
self._baseDraw = GBXDrawList(ar) if hasDraw else None
|
1725
|
+
|
1726
|
+
self._showCellBorder = ar.word()
|
1727
|
+
self._topCellBorder = ar.word()
|
1728
|
+
self._reserved = [ar.word() for _ in range(4)]
|
1729
|
+
self._reserved2 = None
|
1730
|
+
self._rowOffset = 0
|
1731
|
+
self._colOffset = 0
|
1732
|
+
self._rowInvert = False
|
1733
|
+
self._colInvert = False
|
1734
|
+
self._nRows = 0
|
1735
|
+
self._nCols = 0
|
1736
|
+
self._transparent = False
|
1737
|
+
self._numbers = 0
|
1738
|
+
self._trackCell = False
|
1739
|
+
self._frameColor = 0xFF000000
|
1740
|
+
self._full = None
|
1741
|
+
self._half = None
|
1742
|
+
self._small = None
|
1743
|
+
self._map = []
|
1744
|
+
self._topDraw = None
|
1745
|
+
|
1746
|
+
hasArray = ar.word()
|
1747
|
+
if hasArray != 0:
|
1748
|
+
self._reserved2 = [ar.word() for _ in range(4)]
|
1749
|
+
self._rowOffset = ar.word()
|
1750
|
+
self._colOffset = ar.word()
|
1751
|
+
self._rowInvert = ar.word()
|
1752
|
+
self._colInvert = ar.word()
|
1753
|
+
self._nRows = ar.int(Features().sub_size)
|
1754
|
+
self._nCols = ar.int(Features().sub_size)
|
1755
|
+
self._transparent = ar.word()
|
1756
|
+
self._numbers = ar.word()
|
1757
|
+
self._trackCell = ar.word()
|
1758
|
+
self._frameColor = ar.dword()
|
1759
|
+
|
1760
|
+
self._full = GBXCellGeometry(ar)
|
1761
|
+
self._half = GBXCellGeometry(ar)
|
1762
|
+
self._small = GBXCellGeometry(ar)
|
1763
|
+
|
1764
|
+
self._map = [[GBXCell(ar,row,col) for col in range(self._nCols)]
|
1765
|
+
for row in range(self._nRows)]
|
1766
|
+
|
1767
|
+
hasDraw = ar.word()
|
1768
|
+
self._topDraw = GBXDrawList(ar) if hasDraw else None
|
1769
|
+
g(f'Board background read: {self._background:06x}, frame color: {self._frameColor:06x}')
|
1770
|
+
|
1771
|
+
def toDict(self,tileManager,markManager,strings,no,boardDigits,
|
1772
|
+
alsoMap=True):
|
1773
|
+
from io import StringIO
|
1774
|
+
|
1775
|
+
with VerboseGuard(f'Making dict of board {self._name}') as g:
|
1776
|
+
sav = f'board_{no:0{boardDigits}d}.svg'
|
1777
|
+
g(f'File to save in: {sav}')
|
1778
|
+
dct = {'name': self._name,
|
1779
|
+
'serial': self._serial,
|
1780
|
+
'visible': self._visible,
|
1781
|
+
'snap': {
|
1782
|
+
'enable': self._snap,
|
1783
|
+
'x': {
|
1784
|
+
'distance': self._xSnap,
|
1785
|
+
'offset': self._xSnapOffset
|
1786
|
+
},
|
1787
|
+
'y': {
|
1788
|
+
'distance': self._ySnap,
|
1789
|
+
'offset': self._ySnapOffset
|
1790
|
+
}
|
1791
|
+
},
|
1792
|
+
'max layer': self._maxLayer,
|
1793
|
+
'cell border': {
|
1794
|
+
'visible': self._showCellBorder,
|
1795
|
+
'on top layer': self._topCellBorder,
|
1796
|
+
},
|
1797
|
+
'rows': {
|
1798
|
+
'size': self._nRows,
|
1799
|
+
'offset': self._rowOffset,
|
1800
|
+
'inverted': self._rowInvert
|
1801
|
+
},
|
1802
|
+
'columns': {
|
1803
|
+
'size': self._nCols,
|
1804
|
+
'offset': self._colOffset,
|
1805
|
+
'inverted': self._colInvert
|
1806
|
+
},
|
1807
|
+
'cells': {
|
1808
|
+
'transparent': self._transparent,
|
1809
|
+
'foreground': self._frameColor,
|
1810
|
+
},
|
1811
|
+
'numbering': {
|
1812
|
+
'order': 'V' if self._numbers % 2 == 0 else 'H',
|
1813
|
+
'padding': self._numbers in [2,3],
|
1814
|
+
'first': 'A' if self._numbers in [4,5] else 'N'
|
1815
|
+
}
|
1816
|
+
}
|
1817
|
+
if self._full is not None:
|
1818
|
+
dct['cells']['geometry'] = self._full.toDict()
|
1819
|
+
if alsoMap and self._map is not None:
|
1820
|
+
dct['cells']['list'] = [[c.toDict(tileManager,self._full)
|
1821
|
+
for c in row]
|
1822
|
+
for row in self._map]
|
1823
|
+
|
1824
|
+
|
1825
|
+
sav = f'board_{no:0{boardDigits}d}.svg'
|
1826
|
+
img = self.drawing(sav,
|
1827
|
+
tileManager=tileManager,
|
1828
|
+
markManager=markManager)
|
1829
|
+
# img.save(pretty=True)
|
1830
|
+
|
1831
|
+
stream = StringIO()
|
1832
|
+
img.write(stream,pretty=True)
|
1833
|
+
|
1834
|
+
dct['filename'] = sav
|
1835
|
+
dct['image'] = stream.getvalue()#img.tostring()
|
1836
|
+
dct['size'] = self._full.boardSize(self._nRows,self._nCols)
|
1837
|
+
|
1838
|
+
return dct
|
1839
|
+
|
1840
|
+
def drawing(self,sav,tileManager,markManager,*args,**kwargs):
|
1841
|
+
from svgwrite import Drawing
|
1842
|
+
from svgwrite.base import Title
|
1843
|
+
with VerboseGuard(f'Making SVG of board {self._name}') as g:
|
1844
|
+
size = self._full.boardSize(self._nRows,self._nCols)
|
1845
|
+
dwg = Drawing(filename=sav,size=size)
|
1846
|
+
frame, defMap, patMap = self.defs(dwg,tileManager,markManager)
|
1847
|
+
|
1848
|
+
# Draw background
|
1849
|
+
g(f'Board background: {self._background:06x} {GBXDraw.hex(self._background)}')
|
1850
|
+
dwg.add(Title(self._name))
|
1851
|
+
dwg.add(dwg.rect(id='background',
|
1852
|
+
insert=(0,0),
|
1853
|
+
size=size,
|
1854
|
+
fill=GBXDraw.hex(self._background)
|
1855
|
+
#f'#{self._background:06x}')
|
1856
|
+
))
|
1857
|
+
# GBXDraw.hex(self._background)
|
1858
|
+
|
1859
|
+
g('Drawing base layer')
|
1860
|
+
bse = self.base (dwg, 0, defMap)
|
1861
|
+
g('Drawing cells')
|
1862
|
+
grd = self.cells(dwg, frame, patMap)
|
1863
|
+
if self._showCellBorder and not self._topCellBorder:
|
1864
|
+
self.borders(dwg,frame)
|
1865
|
+
g('Drawing top layer')
|
1866
|
+
top1 = self.top (dwg, 1, defMap)
|
1867
|
+
if self._showCellBorder and self._topCellBorder:
|
1868
|
+
self.borders(dwg,frame)
|
1869
|
+
top2 = self.top (dwg, 2, defMap)
|
1870
|
+
|
1871
|
+
return dwg
|
1872
|
+
|
1873
|
+
def defs(self,dwg,tileManager,markManager):
|
1874
|
+
defMap = {}
|
1875
|
+
patMap = {}
|
1876
|
+
frame = self._full.svgDef(dwg)
|
1877
|
+
defMap['cell'] = frame
|
1878
|
+
|
1879
|
+
# Get defininitions from base layer
|
1880
|
+
if self._baseDraw:
|
1881
|
+
self._baseDraw.svgDefs(dwg,
|
1882
|
+
tileManager,
|
1883
|
+
markManager,
|
1884
|
+
defMap)
|
1885
|
+
# Get definitions from cell layer
|
1886
|
+
for row in self._map:
|
1887
|
+
for cell in row:
|
1888
|
+
cell.svgDef(dwg,tileManager,patMap)
|
1889
|
+
|
1890
|
+
# Get definitions from top layer
|
1891
|
+
if self._topDraw:
|
1892
|
+
self._topDraw.svgDefs(dwg,
|
1893
|
+
tileManager,
|
1894
|
+
markManager,
|
1895
|
+
defMap)
|
1896
|
+
|
1897
|
+
return frame, defMap, patMap
|
1898
|
+
|
1899
|
+
def base(self,dwg,passNo,defMap):
|
1900
|
+
bse = dwg.add(dwg.g(id=f'base_{passNo:02d}'))
|
1901
|
+
if self._baseDraw:
|
1902
|
+
self._baseDraw.svg(dwg,bse,passNo,defMap)
|
1903
|
+
|
1904
|
+
return bse
|
1905
|
+
|
1906
|
+
def cells(self,dwg,frame,patMap):
|
1907
|
+
grd = dwg.add(dwg.g(id='grid'))
|
1908
|
+
for row in self._map:
|
1909
|
+
for cell in row:
|
1910
|
+
cell.svg(dwg,grd,frame,self._full,patMap)
|
1911
|
+
|
1912
|
+
return grd
|
1913
|
+
|
1914
|
+
def borders(self,dwg,frame):
|
1915
|
+
brd = dwg.add(dwg.g(id='borders'))
|
1916
|
+
for row in self._map:
|
1917
|
+
for cell in row:
|
1918
|
+
cell.svgFrame(dwg,brd,frame,self._full,self._frameColor)
|
1919
|
+
|
1920
|
+
return brd
|
1921
|
+
|
1922
|
+
def top(self,dwg,passNo,defMap):
|
1923
|
+
top = dwg.add(dwg.g(id=f'top_{passNo:02d}'))
|
1924
|
+
if self._topDraw:
|
1925
|
+
self._topDraw.svg (dwg,top,passNo,defMap)
|
1926
|
+
|
1927
|
+
return top
|
1928
|
+
|
1929
|
+
def __str__(self):
|
1930
|
+
return (f'GBXBoard: {self._name}\n'
|
1931
|
+
f' serial: {self._serial}\n'
|
1932
|
+
f' visible: {self._visible}\n'
|
1933
|
+
f' snap: {self._snap}\n'
|
1934
|
+
f' xSnap: {self._xSnap}\n'
|
1935
|
+
f' ySnap: {self._ySnap}\n'
|
1936
|
+
f' xSnapOffset: {self._xSnapOffset}\n'
|
1937
|
+
f' ySnapOffset: {self._ySnapOffset}\n'
|
1938
|
+
f' maxLayer: {self._maxLayer}\n'
|
1939
|
+
f' background: {self._background:08x}\n'
|
1940
|
+
f' Base draws: {self._baseDraw}\n'
|
1941
|
+
f' Show cell border: {self._showCellBorder}\n'
|
1942
|
+
f' Top cell border: {self._topCellBorder}\n'
|
1943
|
+
f' Reserved: {self._reserved}\n'
|
1944
|
+
f' Reserved2: {self._reserved}\n'
|
1945
|
+
f' Row offset: {self._rowOffset}\n'
|
1946
|
+
f' Column offset: {self._colOffset}\n'
|
1947
|
+
f' Row invert: {self._rowInvert}\n'
|
1948
|
+
f' Colunn invert: {self._colInvert}\n'
|
1949
|
+
f' # Rows: {self._nRows}\n'
|
1950
|
+
f' # Cols: {self._nCols}\n'
|
1951
|
+
f' Transparent: {self._transparent}\n'
|
1952
|
+
f' Numbers: {self._numbers}\n'
|
1953
|
+
f' Track cells: {self._trackCell}\n'
|
1954
|
+
f' Frame color: {self._frameColor:08x}\n'
|
1955
|
+
f' Full geometry: {self._full}\n'
|
1956
|
+
f' Half geometry: {self._half}\n'
|
1957
|
+
f' Small geometry: {self._small}\n'
|
1958
|
+
f' Top draws: {self._topDraw}'
|
1959
|
+
)
|
1960
|
+
|
1961
|
+
|
1962
|
+
|
1963
|
+
|
1964
|
+
# --------------------------------------------------------------------
|
1965
|
+
class GBXBoardManager(CbManager):
|
1966
|
+
def __init__(self,ar):
|
1967
|
+
'''Manager of boards'''
|
1968
|
+
with VerboseGuard(f'Reading board manager'):
|
1969
|
+
self._nextSerial = ar.iden()
|
1970
|
+
super(GBXBoardManager,self).__init__(ar)
|
1971
|
+
# print(Features().id_size)
|
1972
|
+
self._boards = self._readSub(ar,GBXBoard)
|
1973
|
+
|
1974
|
+
def __len__(self):
|
1975
|
+
return len(self._boards)
|
1976
|
+
|
1977
|
+
def bySerial(self,serial):
|
1978
|
+
for b in self._boards:
|
1979
|
+
if b._serial == serial:
|
1980
|
+
return b
|
1981
|
+
|
1982
|
+
return None
|
1983
|
+
|
1984
|
+
def toDict(self,tileManager,markManager,strings):
|
1985
|
+
from math import log10, ceil
|
1986
|
+
with VerboseGuard(f'Making dict board manager'):
|
1987
|
+
boardDigits = int(ceil(log10(len(self)+.5)))
|
1988
|
+
|
1989
|
+
return {b._serial:
|
1990
|
+
b.toDict(tileManager,markManager,strings,no,boardDigits)
|
1991
|
+
for no, b in enumerate(self._boards)}
|
1992
|
+
|
1993
|
+
def __str__(self):
|
1994
|
+
return ('GBXBoard manager:\n'
|
1995
|
+
+ f' Next serial: {self._nextSerial:08x}\n'
|
1996
|
+
+ super(GBXBoardManager,self).__str__()
|
1997
|
+
+ self._strSub('boards',self._boards))
|
1998
|
+
|
1999
|
+
# --------------------------------------------------------------------
|
2000
|
+
class GSNGeomorphicElement:
|
2001
|
+
def __init__(self,ar):
|
2002
|
+
with VerboseGuard('Reading geomorphic element'):
|
2003
|
+
self._serial = ar.word()
|
2004
|
+
|
2005
|
+
def __str__(self):
|
2006
|
+
return f'GSNGeomorphicElement: {self._serial}'
|
2007
|
+
|
2008
|
+
# --------------------------------------------------------------------
|
2009
|
+
class GSNGeomorphicBoard:
|
2010
|
+
def __init__(self,ar):
|
2011
|
+
with VerboseGuard('Reading geomorphic board'):
|
2012
|
+
self._name = ar.str()
|
2013
|
+
self._nRows = ar.word()
|
2014
|
+
self._nCols = ar.word()
|
2015
|
+
n = ar.word()
|
2016
|
+
self._elements = [GSNGeomorphicElement(ar) for _ in range(n)]
|
2017
|
+
|
2018
|
+
def __str__(self):
|
2019
|
+
pl = '\n '.join([str(s) for s in self._elements])
|
2020
|
+
return (f'GeomorphicBoard: {self._name}\n'
|
2021
|
+
f' Size: {self._nRows}x{self._nCols}\n'
|
2022
|
+
f' Elements:\n {pl}\n')
|
2023
|
+
|
2024
|
+
# --------------------------------------------------------------------
|
2025
|
+
class GSNBoard:
|
2026
|
+
def __init__(self,ar,vers):
|
2027
|
+
with VerboseGuard(f'Reading scenario board {vers//256}.{vers%256}'):
|
2028
|
+
hasGeo = ar.byte()
|
2029
|
+
self._geo = GSNGeomorphicBoard(ar) if hasGeo else None
|
2030
|
+
self._serial = ar.iden()
|
2031
|
+
self._snap = ar.word()
|
2032
|
+
self._xSnap = ar.dword()
|
2033
|
+
self._ySnap = ar.dword()
|
2034
|
+
self._xSnapOffset = ar.dword()
|
2035
|
+
self._ySnapOffset = ar.dword()
|
2036
|
+
self._xStagger = ar.word()
|
2037
|
+
self._yStagger = ar.word()
|
2038
|
+
self._piecesVisible = ar.word()
|
2039
|
+
self._blockBeneath = ar.word()
|
2040
|
+
self._rotate180 = ar.word()
|
2041
|
+
self._showTiny = ar.word()
|
2042
|
+
self._indicatorsVisible= ar.word()
|
2043
|
+
self._cellBorders = ar.word()
|
2044
|
+
self._smallCellBorders = ar.word()
|
2045
|
+
self._enforceLocks = ar.word()
|
2046
|
+
self._plotLineColor = ar.dword()
|
2047
|
+
self._plotLineWidth = ar.word()
|
2048
|
+
self._lineColor = ar.dword()
|
2049
|
+
self._lineWidth = ar.word()
|
2050
|
+
self._textColor = ar.dword()
|
2051
|
+
self._textBoxColor = ar.dword()
|
2052
|
+
self._font = CbFont(ar)
|
2053
|
+
self._gridCenters = ar.word()
|
2054
|
+
self._snapMove = ar.word()
|
2055
|
+
self._indactorsTop = ar.word()
|
2056
|
+
self._openOnLoad = ar.word()
|
2057
|
+
self._prevPlotMode = ar.word()
|
2058
|
+
self._prevPlotX = ar.word()
|
2059
|
+
self._prevPlotY = ar.word()
|
2060
|
+
self._ownerMask = (ar.word() if vers < num_version(3,10) else
|
2061
|
+
ar.dword())
|
2062
|
+
self._restrictToOwner = ar.word()
|
2063
|
+
self._pieces = GBXDrawList(ar)
|
2064
|
+
self._indicators = GBXDrawList(ar)
|
2065
|
+
|
2066
|
+
|
2067
|
+
def toDict(self,boardManager):
|
2068
|
+
board = (None if boardManager is None else
|
2069
|
+
boardManager.bySerial(self._serial))
|
2070
|
+
geom = None if board is None else board._full
|
2071
|
+
calc = None if geom is None else GBXBoardCalculator(board)
|
2072
|
+
|
2073
|
+
return {
|
2074
|
+
'onload': self._openOnLoad != 0,
|
2075
|
+
'snap': {
|
2076
|
+
'enable': self._snap,
|
2077
|
+
'onmove': self._snapMove != 0,
|
2078
|
+
'gridCenter': self._gridCenters != 0,
|
2079
|
+
'x': {
|
2080
|
+
'distance': self._xSnap,
|
2081
|
+
'offset': self._xSnapOffset
|
2082
|
+
},
|
2083
|
+
'y': {
|
2084
|
+
'distance': self._ySnap,
|
2085
|
+
'offset': self._ySnapOffset
|
2086
|
+
}
|
2087
|
+
},
|
2088
|
+
'moves': {
|
2089
|
+
'color': self._plotLineColor,
|
2090
|
+
'width': self._plotLineWidth
|
2091
|
+
},
|
2092
|
+
'stacking': [self._xStagger, self._yStagger],
|
2093
|
+
'owner': self._ownerMask,
|
2094
|
+
'restrict': self._restrictToOwner != 0,
|
2095
|
+
'grid': {
|
2096
|
+
'show': self._cellBorders != 0,
|
2097
|
+
'color': self._lineColor,
|
2098
|
+
'width': self._lineWidth
|
2099
|
+
},
|
2100
|
+
'pieces': self._pieces.toDict(calc),
|
2101
|
+
'indicators': self._indicators.toDict(calc)
|
2102
|
+
}
|
2103
|
+
|
2104
|
+
def __str__(self):
|
2105
|
+
return (f'ScenarioBoard: {self._serial}'
|
2106
|
+
f' Geomorphic: {"None" if self._geo is None else self._geo}\n'
|
2107
|
+
f' Font: {self._font}\n'
|
2108
|
+
f' Pieces:\n{str(self._pieces)}\n'
|
2109
|
+
f' Indicators:\n{str(self._indicators)}')
|
2110
|
+
|
2111
|
+
# --------------------------------------------------------------------
|
2112
|
+
class GSNBoardManager:
|
2113
|
+
def __init__(self,ar,vers):
|
2114
|
+
with VerboseGuard(f'Reading scenario board manager') as g:
|
2115
|
+
self._nextGeoSerial = ar.iden()
|
2116
|
+
self._reserved = [ar.word() for _ in range(3)]
|
2117
|
+
n = ar.sub_size()
|
2118
|
+
g(f'Got {n} boards to read')
|
2119
|
+
self._boards = [GSNBoard(ar,vers) for _ in range(n)]
|
2120
|
+
|
2121
|
+
def toDict(self,boardManager):
|
2122
|
+
hasStart = False
|
2123
|
+
for b in self._boards:
|
2124
|
+
if b._openOnLoad:
|
2125
|
+
hasStart = True
|
2126
|
+
|
2127
|
+
# Make sure at least one map is loaded
|
2128
|
+
if not hasStart and len(self._boards) > 0:
|
2129
|
+
self._boards[0]._openOnLoad = True
|
2130
|
+
|
2131
|
+
return {b._serial: b.toDict(boardManager) for b in self._boards }
|
2132
|
+
|
2133
|
+
def __str__(self):
|
2134
|
+
pl = '\n '.join([str(s) for s in self._boards])
|
2135
|
+
return f'GSNBoardManager: {self._nextGeoSerial}\n {pl}\n'
|
2136
|
+
|
2137
|
+
#
|
2138
|
+
# EOF
|
2139
|
+
#
|
2140
|
+
|
2141
|
+
# ====================================================================
|
2142
|
+
# From gamebox.py
|
2143
|
+
|
2144
|
+
# --------------------------------------------------------------------
|
2145
|
+
class GBXInfo:
|
2146
|
+
def __init__(self,ar):
|
2147
|
+
'''GameBox information'''
|
2148
|
+
with VerboseGuard('Reading information') as g:
|
2149
|
+
self._bitsPerPixel = ar.word() # 2 -> 2
|
2150
|
+
self._majorRevs = ar.dword() # 4 -> 6
|
2151
|
+
self._minorRevs = ar.dword() # 4 -> 10
|
2152
|
+
self._gameID = ar.dword() # 4 -> 14
|
2153
|
+
self._boxID = ar.read(16) # 16 -> 30
|
2154
|
+
self._author = ar.str() # X -> 30+X
|
2155
|
+
self._title = ar.str() # Y -> 30+X+Y
|
2156
|
+
self._description = ar.str() # Z -> 30+X+Y+Z
|
2157
|
+
self._password = ar.read(16) # 16 -> 46+X+Y+Z
|
2158
|
+
self._stickyDraw = ar.word() # 2 -> 48+X+Y+Z
|
2159
|
+
self._compression = ar.word() # 2 -> 50+X+Y+Z
|
2160
|
+
self._reserved = [ar.word() for _ in range(4)] # 4x2 -> 58+X+Y+Z
|
2161
|
+
g(f'GameBox is {self._title} by {self._author} (password: {self._password})')
|
2162
|
+
|
2163
|
+
def __str__(self):
|
2164
|
+
return ('Information:\n'
|
2165
|
+
f' Bits/pixel: {self._bitsPerPixel}\n'
|
2166
|
+
f' Major revision: {self._majorRevs}\n'
|
2167
|
+
f' Minor revision: {self._minorRevs}\n'
|
2168
|
+
f' Game ID: {self._gameID}\n'
|
2169
|
+
f' Box ID: {self._boxID}\n'
|
2170
|
+
f' Author: {self._author}\n'
|
2171
|
+
f' Title: {self._title}\n'
|
2172
|
+
f' Description: {self._description}\n'
|
2173
|
+
f' Password: {self._password}\n'
|
2174
|
+
f' Sticky Draw tools: {self._stickyDraw}\n'
|
2175
|
+
f' Compression level: {self._compression}\n'
|
2176
|
+
f' Reserved: {self._reserved}')
|
2177
|
+
|
2178
|
+
|
2179
|
+
# ====================================================================
|
2180
|
+
class GameBox:
|
2181
|
+
def __init__(self,ar):
|
2182
|
+
'''Container of game'''
|
2183
|
+
with VerboseGuard(f'Reading gamebox'):
|
2184
|
+
self._header = GBXHeader(ar,GBXHeader.BOX)
|
2185
|
+
self._info = GBXInfo(ar)
|
2186
|
+
self._strings = GBXStrings(ar)
|
2187
|
+
self._tileManager = GBXTileManager(ar)
|
2188
|
+
self._boardManager = GBXBoardManager(ar)
|
2189
|
+
self._pieceManager = GBXPieceManager(ar)
|
2190
|
+
self._markManager = GBXMarkManager(ar)
|
2191
|
+
|
2192
|
+
# print(self._strings)
|
2193
|
+
# print(self._markManager)
|
2194
|
+
|
2195
|
+
def __str__(self):
|
2196
|
+
return (str(self._header)+
|
2197
|
+
str(self._info)+
|
2198
|
+
str(self._strings)+
|
2199
|
+
str(self._tileManager)+
|
2200
|
+
str(self._boardManager)+
|
2201
|
+
str(self._pieceManager)+
|
2202
|
+
str(self._markManager))
|
2203
|
+
|
2204
|
+
|
2205
|
+
@classmethod
|
2206
|
+
def fromFile(cls,filename):
|
2207
|
+
with VerboseGuard(f'Read gamebox from {filename}'):
|
2208
|
+
with Archive(filename,'rb') as ar:
|
2209
|
+
return GameBox(ar)
|
2210
|
+
|
2211
|
+
#
|
2212
|
+
# EOF
|
2213
|
+
#
|
2214
|
+
# ====================================================================
|
2215
|
+
# From scenario.py
|
2216
|
+
|
2217
|
+
class GSNInfo:
|
2218
|
+
def __init__(self,ar):
|
2219
|
+
'''Scenario information'''
|
2220
|
+
self._disableOwnerTips = ar.word()
|
2221
|
+
self._reserved = [ar.word() for _ in range(3)]
|
2222
|
+
self._gameID = ar.dword() # 4 -> 14
|
2223
|
+
self._majorRevs = ar.dword() # 4 -> 6
|
2224
|
+
self._minorRevs = ar.dword() # 4 -> 10
|
2225
|
+
self._gbxFilename = ar.str()
|
2226
|
+
self._scenarioID = ar.dword()
|
2227
|
+
self._title = ar.str() # Y -> 30+X+Y
|
2228
|
+
self._author = ar.str() # X -> 30+X
|
2229
|
+
self._description = ar.str() # Z -> 30+X+Y+Z
|
2230
|
+
self._keepBackup = ar.word()
|
2231
|
+
self._keepHistory = ar.word()
|
2232
|
+
self._verifyState = ar.word()
|
2233
|
+
self._verifySave = ar.word()
|
2234
|
+
self._showObjectTip = ar.word()
|
2235
|
+
|
2236
|
+
def __str__(self):
|
2237
|
+
return ('Information:\n'
|
2238
|
+
f' Disable owner tips: {self._disableOwnerTips}\n'
|
2239
|
+
f' Reserved: {self._reserved}\n'
|
2240
|
+
f' Game ID: {self._gameID}\n'
|
2241
|
+
f' Major revision: {self._majorRevs}\n'
|
2242
|
+
f' Minor revision: {self._minorRevs}\n'
|
2243
|
+
f' Game box filename: {self._gbxFilename}\n'
|
2244
|
+
f' Scenario ID: {self._scenarioID}\n'
|
2245
|
+
f' Title: {self._title}\n'
|
2246
|
+
f' Author: {self._author}\n'
|
2247
|
+
f' Description: {self._description}\n'
|
2248
|
+
f' Keep backup: {self._keepBackup}\n'
|
2249
|
+
f' Keep history: {self._keepHistory}\n'
|
2250
|
+
f' Verify state: {self._verifyState}\n'
|
2251
|
+
f' Verify save: {self._verifySave}\n'
|
2252
|
+
f' Show object tips: {self._showObjectTip}\n')
|
2253
|
+
|
2254
|
+
# ====================================================================
|
2255
|
+
class Scenario:
|
2256
|
+
def __init__(self,ar,gbxfilename=None):
|
2257
|
+
'''Container of game'''
|
2258
|
+
with VerboseGuard(f'Reading scenario'):
|
2259
|
+
self._header = GBXHeader (ar,GBXHeader.SCENARIO)
|
2260
|
+
self._info = GSNInfo (ar)
|
2261
|
+
self._strings = GSNStrings (ar)
|
2262
|
+
self._playerManager = GSNPlayerManager(ar)
|
2263
|
+
self._windows = GSNWindows (ar)
|
2264
|
+
self._trayA = GSNTrayPalette (ar,self._header._vers,'A')
|
2265
|
+
self._trayB = GSNTrayPalette (ar,self._header._vers,'B')
|
2266
|
+
self._mark = GSNMarkPalette (ar,self._header._vers)
|
2267
|
+
self._boards = GSNBoardManager (ar,self._header._vers)
|
2268
|
+
self._trayManager = GSNTrayManager (ar,self._header._vers)
|
2269
|
+
self._pieceTable = GSNPieceTable (ar,self._header._vers)
|
2270
|
+
# Possibly override GBX file name
|
2271
|
+
if gbxfilename is not None and gbxfilename != '':
|
2272
|
+
self._info._gbxFilename = gbxfilename
|
2273
|
+
|
2274
|
+
self.readGameBox(ar)
|
2275
|
+
|
2276
|
+
def readGameBox(self,ar):
|
2277
|
+
from pathlib import Path
|
2278
|
+
with VerboseGuard(f'Read game box file {self._info._gbxFilename}') as v:
|
2279
|
+
gbxname = self._info._gbxFilename
|
2280
|
+
gbxpath = Path(gbxname)
|
2281
|
+
|
2282
|
+
if not gbxpath.exists():
|
2283
|
+
v(f'GameBox file {gbxpath} does not exist')
|
2284
|
+
if '\\' in gbxname:
|
2285
|
+
gbxname = gbxname.replace('\\','/')
|
2286
|
+
gbxpath = Path(gbxname)
|
2287
|
+
gbxpath = ar.path.parent / Path(gbxpath.name)
|
2288
|
+
if not gbxpath.exists():
|
2289
|
+
raise RuntimeError(f'GameBox file {gbxpath} cannot be found')
|
2290
|
+
|
2291
|
+
self._gbx = GameBox.fromFile(str(gbxpath))
|
2292
|
+
|
2293
|
+
if self._gbx._info._gameID != self._info._gameID:
|
2294
|
+
raise RuntimeError(f'Game IDs from GBX and GSN does not match: '
|
2295
|
+
f'{self._gbx._info._gameID} versus '
|
2296
|
+
f'{self._header._gameID}')
|
2297
|
+
|
2298
|
+
def __str__(self):
|
2299
|
+
return (str(self._header)+
|
2300
|
+
str(self._info)+
|
2301
|
+
str(self._strings)+
|
2302
|
+
str(self._playerManager)+
|
2303
|
+
str(self._windows)+
|
2304
|
+
str(self._trayA)+
|
2305
|
+
str(self._trayB)+
|
2306
|
+
str(self._mark)+
|
2307
|
+
str(self._boards)+
|
2308
|
+
str(self._trayManager)+
|
2309
|
+
str(self._pieceTable)+
|
2310
|
+
str(self._gbx)+
|
2311
|
+
''
|
2312
|
+
)
|
2313
|
+
|
2314
|
+
|
2315
|
+
@classmethod
|
2316
|
+
def fromFile(cls,filename,gbxfile=None):
|
2317
|
+
with Archive(filename,'rb') as ar:
|
2318
|
+
return Scenario(ar,gbxfile)
|
2319
|
+
# ====================================================================
|
2320
|
+
# From player.py
|
2321
|
+
|
2322
|
+
class GSNPlayerManager:
|
2323
|
+
def __init__(self,ar):
|
2324
|
+
with VerboseGuard(f'Reading players mappings'):
|
2325
|
+
self._enable = ar.byte()
|
2326
|
+
self._players = []
|
2327
|
+
if self._enable:
|
2328
|
+
n = ar.sub_size()
|
2329
|
+
self._players = [GSNPlayer(ar) for _ in range(n)]
|
2330
|
+
|
2331
|
+
def toDict(self):
|
2332
|
+
return [p._name for p in self._players]
|
2333
|
+
|
2334
|
+
def __str__(self):
|
2335
|
+
pl = '\n '.join([str(s) for s in self._players])
|
2336
|
+
return ('Players:\n'
|
2337
|
+
f' Enabled: {self._enable}\n'
|
2338
|
+
f' # players: {len(self._players)}\n {pl}\n')
|
2339
|
+
|
2340
|
+
class GSNPlayer:
|
2341
|
+
def __init__(self,ar):
|
2342
|
+
with VerboseGuard(f'Reading player'):
|
2343
|
+
self._key = ar.dword()
|
2344
|
+
self._name = ar.str()
|
2345
|
+
|
2346
|
+
|
2347
|
+
def __str__(self):
|
2348
|
+
return f'Player: {self._name} (0x{self._key:08x})'
|
2349
|
+
|
2350
|
+
#
|
2351
|
+
# EOF
|
2352
|
+
#
|
2353
|
+
|
2354
|
+
# ====================================================================
|
2355
|
+
# From windows.py
|
2356
|
+
|
2357
|
+
class GSNWindow:
|
2358
|
+
def __init__(self,ar):
|
2359
|
+
with VerboseGuard('Reading window state') as g:
|
2360
|
+
self._code = ar.word()
|
2361
|
+
self._user = ar.word()
|
2362
|
+
self._board = ar.iden()
|
2363
|
+
self._state = [ar.dword() for _ in range(10)]
|
2364
|
+
n = ar.size()
|
2365
|
+
g(f'Read {self._code} {self._user} {self._board} {self._state}')
|
2366
|
+
g(f'Reading {n} bytes at {ar.tell()}')
|
2367
|
+
self._buffer = ar.read(n)
|
2368
|
+
|
2369
|
+
def __str__(self):
|
2370
|
+
return (f'code={self._code:04x} '
|
2371
|
+
f'user={self._user:04x} '
|
2372
|
+
f'board={self._board:04x} '
|
2373
|
+
f'buffer={len(self._buffer)}')
|
2374
|
+
|
2375
|
+
class GSNWindows:
|
2376
|
+
def __init__(self,ar):
|
2377
|
+
with VerboseGuard(f'Reading window states') as g:
|
2378
|
+
self._savePositions = ar.word()
|
2379
|
+
self._enable = ar.byte()
|
2380
|
+
n = ar.size() if self._enable else 0
|
2381
|
+
g(f'Save position: {self._savePositions}, '
|
2382
|
+
f'enable {self._enable} '
|
2383
|
+
f'n={n}')
|
2384
|
+
self._states = [GSNWindow(ar) for _ in range(n)]
|
2385
|
+
|
2386
|
+
def __str__(self):
|
2387
|
+
pl = '\n '.join([str(s) for s in self._states])
|
2388
|
+
return f'Windows states ({len(self._states)})\n {pl}\n'
|
2389
|
+
|
2390
|
+
|
2391
|
+
#
|
2392
|
+
# EOF
|
2393
|
+
#
|
2394
|
+
# ====================================================================
|
2395
|
+
# From palette.py
|
2396
|
+
|
2397
|
+
class GSNPalette:
|
2398
|
+
def __init__(self,ar):
|
2399
|
+
with VerboseGuard('Reading palette'):
|
2400
|
+
self._visible = ar.word()
|
2401
|
+
self._comboIndex = ar.dword()
|
2402
|
+
self._topIndex = ar.dword()
|
2403
|
+
|
2404
|
+
class GSNTrayPalette(GSNPalette):
|
2405
|
+
def __init__(self,ar,vers,iden):
|
2406
|
+
with VerboseGuard(f'Reading scenario tray palette {iden}'):
|
2407
|
+
super(GSNTrayPalette,self).__init__(ar)
|
2408
|
+
self._iden = iden
|
2409
|
+
self._listSel = readVector(ar,lambda ar : ar.dword())
|
2410
|
+
|
2411
|
+
def __str__(self):
|
2412
|
+
return f'GSNTrayPalette: {self._comboIndex} '\
|
2413
|
+
f'{self._topIndex} {self._listSel}\n'
|
2414
|
+
|
2415
|
+
class GSNMarkPalette(GSNPalette):
|
2416
|
+
def __init__(self,ar,vers):
|
2417
|
+
with VerboseGuard(f'Reading scenario mark palette'):
|
2418
|
+
super(GSNMarkPalette,self).__init__(ar)
|
2419
|
+
self._listSel = ar.dword()
|
2420
|
+
def __str__(self):
|
2421
|
+
return f'GSNMarkPalette: {self._comboIndex} '\
|
2422
|
+
f'{self._topIndex} {self._listSel}\n'
|
2423
|
+
|
2424
|
+
#
|
2425
|
+
# EOF
|
2426
|
+
#
|
2427
|
+
|
2428
|
+
# ====================================================================
|
2429
|
+
# From tray.py
|
2430
|
+
|
2431
|
+
class GSNTraySet:
|
2432
|
+
def __init__(self,ar,vers):
|
2433
|
+
with VerboseGuard(f'Reading tray set') as g:
|
2434
|
+
self._name = ar.str()
|
2435
|
+
g(f'Tray set: {self._name}')
|
2436
|
+
self._random = (ar.word() if Features().piece_100 else 0)
|
2437
|
+
self._visibleFlags = ar.dword()
|
2438
|
+
self._ownerMask = (ar.word() if vers < num_version(3,10) else
|
2439
|
+
ar.dword())
|
2440
|
+
self._restrict = ar.word()
|
2441
|
+
self._pieces = readVector(ar,lambda ar: ar.iden())
|
2442
|
+
|
2443
|
+
def __len__(self):
|
2444
|
+
return len(self._pieces)
|
2445
|
+
|
2446
|
+
def toDict(self):
|
2447
|
+
viz = self._visibleFlags & ~0xFFFF8000
|
2448
|
+
# print(f'{self._visibleFlags:08x} -> {viz}')
|
2449
|
+
vizStr = {0: 'all',
|
2450
|
+
1: 'owner',
|
2451
|
+
2: 'generic',
|
2452
|
+
3: 'none'}.get(viz,'')
|
2453
|
+
return {'name': self._name,
|
2454
|
+
'visible': vizStr,
|
2455
|
+
'owner': self._ownerMask,
|
2456
|
+
'restrict': self._restrict,
|
2457
|
+
'pieces': self._pieces }
|
2458
|
+
|
2459
|
+
def __str__(self):
|
2460
|
+
return (f'Tray set: {self._name} '
|
2461
|
+
f'[visible={self._visibleFlags},'
|
2462
|
+
f'ownerMask={self._ownerMask},'
|
2463
|
+
f'resrict={self._restrict}] '
|
2464
|
+
f'({len(self)}): {self._pieces}')
|
2465
|
+
|
2466
|
+
|
2467
|
+
class GSNTrayManager:
|
2468
|
+
def __init__(self,ar,vers,iden=''):
|
2469
|
+
with VerboseGuard(f'Reading tray {iden} manager @ {ar.tell()}') as g:
|
2470
|
+
self._iden = iden
|
2471
|
+
self._reserved = [ar.word() for _ in range(4)]
|
2472
|
+
g(f'{self._reserved}')
|
2473
|
+
self._dummy = (ar.byte() if vers >= num_version(4,0) else 0)
|
2474
|
+
self._sets = readVector(ar, lambda ar : GSNTraySet(ar,vers))
|
2475
|
+
|
2476
|
+
def __len__(self):
|
2477
|
+
return len(self._sets)
|
2478
|
+
|
2479
|
+
def toDict(self):
|
2480
|
+
return [s.toDict() for s in self._sets]
|
2481
|
+
|
2482
|
+
def __str__(self):
|
2483
|
+
pl = '\n '.join([str(s) for s in self._sets])
|
2484
|
+
return f'TrayManager: {self._iden} ({len(self)})\n {pl}\n'
|
2485
|
+
|
2486
|
+
|
2487
|
+
#
|
2488
|
+
# EOF
|
2489
|
+
#
|
2490
|
+
|
2491
|
+
|
2492
|
+
|
2493
|
+
|
2494
|
+
# ====================================================================
|
2495
|
+
# From extractor.py
|
2496
|
+
|
2497
|
+
# ====================================================================
|
2498
|
+
class CbExtractor:
|
2499
|
+
def __init__(self):
|
2500
|
+
pass
|
2501
|
+
|
2502
|
+
def save(self,filename):
|
2503
|
+
from zipfile import ZipFile
|
2504
|
+
from copy import deepcopy
|
2505
|
+
|
2506
|
+
with VerboseGuard(f'Saving to {filename}') as g:
|
2507
|
+
with ZipFile(filename,'w') as zfile:
|
2508
|
+
self._save(zfile)
|
2509
|
+
|
2510
|
+
def saveImages(self,d,zfile):
|
2511
|
+
with VerboseGuard(f'Saving images') as g:
|
2512
|
+
for serial, board in d['boards'].items():
|
2513
|
+
g(f'Saving board: {board}')
|
2514
|
+
self.saveSVG(board,zfile)
|
2515
|
+
|
2516
|
+
for piece in d['pieces']['map'].values():
|
2517
|
+
g(f'Saving piece: {piece}')
|
2518
|
+
for which in ['front','back']:
|
2519
|
+
if which not in piece:
|
2520
|
+
continue
|
2521
|
+
|
2522
|
+
side = piece[which]
|
2523
|
+
self.savePNG(side,zfile)
|
2524
|
+
piece[which] = side['filename']
|
2525
|
+
|
2526
|
+
for mark in d['marks']['map'].values():
|
2527
|
+
g(f'Saving marks: {mark}')
|
2528
|
+
self.savePNG(mark,zfile)
|
2529
|
+
|
2530
|
+
for tile in d['tiles'].values():
|
2531
|
+
g(f'Saving tile: {tile}')
|
2532
|
+
self.savePNG(tile,zfile)
|
2533
|
+
|
2534
|
+
del d['tiles']
|
2535
|
+
|
2536
|
+
|
2537
|
+
def saveSVG(self,d,zfile,removeImage=True):
|
2538
|
+
from io import StringIO
|
2539
|
+
|
2540
|
+
with VerboseGuard(f'Saving SVG') as g:
|
2541
|
+
filename = d['filename']
|
2542
|
+
image = d['image']
|
2543
|
+
|
2544
|
+
# stream = StringIO()
|
2545
|
+
# image.write(stream,pretty=True)
|
2546
|
+
g(f'Saving SVG: {image}')
|
2547
|
+
zfile.writestr(filename,image)#stream.getvalue())
|
2548
|
+
|
2549
|
+
if removeImage:
|
2550
|
+
del d['image']
|
2551
|
+
|
2552
|
+
def savePNG(self,d,zfile,removeImage=True):
|
2553
|
+
with VerboseGuard(f'Saving PNG') as g:
|
2554
|
+
filename = d['filename']
|
2555
|
+
img = d['image']
|
2556
|
+
|
2557
|
+
with zfile.open(filename,'w') as file:
|
2558
|
+
g(f'Save {img}')
|
2559
|
+
img.save(file,format='PNG')
|
2560
|
+
|
2561
|
+
if removeImage:
|
2562
|
+
del d['image']
|
2563
|
+
|
2564
|
+
def _save(self,zfile):
|
2565
|
+
pass
|
2566
|
+
|
2567
|
+
|
2568
|
+
# ====================================================================
|
2569
|
+
class GBXExtractor(CbExtractor):
|
2570
|
+
def __init__(self,gbx):
|
2571
|
+
'''Turns gambox into a more sensible structure'''
|
2572
|
+
super(GBXExtractor,self).__init__()
|
2573
|
+
|
2574
|
+
with VerboseGuard(f'Extract gamebox {gbx._info._title}') as g:
|
2575
|
+
self._d = {
|
2576
|
+
'title': gbx._info._title,
|
2577
|
+
'author': gbx._info._author,
|
2578
|
+
'description': gbx._info._description.replace('\r',''),
|
2579
|
+
'major': gbx._info._majorRevs,
|
2580
|
+
'minor': gbx._info._minorRevs,
|
2581
|
+
'version': f'{gbx._info._majorRevs}.{gbx._info._minorRevs}'
|
2582
|
+
}
|
2583
|
+
|
2584
|
+
self._d['pieces'] = gbx._pieceManager.toDict(gbx._tileManager,
|
2585
|
+
gbx._strings)
|
2586
|
+
self._d['marks'] = gbx._markManager.toDict(gbx._tileManager,
|
2587
|
+
gbx._strings)
|
2588
|
+
self._d['boards'] = gbx._boardManager.toDict(gbx._tileManager,
|
2589
|
+
gbx._markManager,
|
2590
|
+
gbx._strings)
|
2591
|
+
self._d['tiles'] = gbx._tileManager._toStore
|
2592
|
+
|
2593
|
+
g(f'Done rationalizing {gbx._info._title}')
|
2594
|
+
|
2595
|
+
|
2596
|
+
def _save(self,zfile):
|
2597
|
+
from pprint import pprint
|
2598
|
+
from io import StringIO
|
2599
|
+
from json import dumps
|
2600
|
+
from copy import deepcopy
|
2601
|
+
|
2602
|
+
with VerboseGuard(f'Saving {self._d["title"]} to {zfile.filename}')as g:
|
2603
|
+
d = deepcopy(self._d)
|
2604
|
+
|
2605
|
+
self.saveImages(d,zfile)
|
2606
|
+
|
2607
|
+
zfile.writestr('info.json',dumps(d,indent=2))
|
2608
|
+
|
2609
|
+
g(f'Done saving')
|
2610
|
+
|
2611
|
+
def fromZipfile(self,zipfile,d):
|
2612
|
+
pass
|
2613
|
+
|
2614
|
+
def __str__(self):
|
2615
|
+
from pprint import pformat
|
2616
|
+
|
2617
|
+
return pformat(self._d,depth=2)
|
2618
|
+
|
2619
|
+
# ====================================================================
|
2620
|
+
class GSNExtractor(CbExtractor):
|
2621
|
+
def __init__(self,gsn,zipfile=None):
|
2622
|
+
'''Turns gambox into a more sensible structure'''
|
2623
|
+
super(GSNExtractor,self).__init__()
|
2624
|
+
|
2625
|
+
if zipfile is not None:
|
2626
|
+
self.fromZipfile(zipfile)
|
2627
|
+
return
|
2628
|
+
|
2629
|
+
with VerboseGuard(f'Extract scenario {gsn._info._title}') as g:
|
2630
|
+
gbxextractor = GBXExtractor(gsn._gbx)
|
2631
|
+
self._d = {
|
2632
|
+
'title': gsn._info._title,
|
2633
|
+
'author': gsn._info._author,
|
2634
|
+
'description': gsn._info._description.replace('\r',''),
|
2635
|
+
'major': gsn._info._majorRevs,
|
2636
|
+
'minor': gsn._info._minorRevs,
|
2637
|
+
'version': f'{gsn._info._majorRevs}.{gsn._info._minorRevs}',
|
2638
|
+
'gamebox': gbxextractor._d}
|
2639
|
+
self._d['players'] = gsn._playerManager.toDict()
|
2640
|
+
self._d['trays'] = gsn._trayManager.toDict()
|
2641
|
+
self._d['boards'] = gsn._boards.toDict(gsn._gbx._boardManager)
|
2642
|
+
|
2643
|
+
|
2644
|
+
def _save(self,zfile):
|
2645
|
+
from pprint import pprint
|
2646
|
+
from io import StringIO
|
2647
|
+
from json import dumps
|
2648
|
+
from copy import deepcopy
|
2649
|
+
|
2650
|
+
with VerboseGuard(f'Saving {self._d["title"]} to {zfile.filename}')as g:
|
2651
|
+
d = deepcopy(self._d)
|
2652
|
+
|
2653
|
+
self.saveImages(d['gamebox'],zfile)
|
2654
|
+
|
2655
|
+
zfile.writestr('info.json',dumps(d,indent=2))
|
2656
|
+
|
2657
|
+
g(f'Done saving')
|
2658
|
+
|
2659
|
+
def fromZipfile(self,zipfile):
|
2660
|
+
from json import loads
|
2661
|
+
from PIL import Image as PILImage
|
2662
|
+
from io import BytesIO
|
2663
|
+
from wand.image import Image as WandImage
|
2664
|
+
with VerboseGuard(f'Reading module from zip file') as v:
|
2665
|
+
self._d = loads(zipfile.read('info.json').decode())
|
2666
|
+
|
2667
|
+
newMap = {}
|
2668
|
+
for pieceSID,piece in self._d['gamebox']['pieces']['map'].items():
|
2669
|
+
pieceID = int(pieceSID)
|
2670
|
+
if pieceID in newMap:
|
2671
|
+
continue
|
2672
|
+
for which in ['front', 'back']:
|
2673
|
+
if which not in piece:
|
2674
|
+
continue
|
2675
|
+
|
2676
|
+
|
2677
|
+
fn = piece[which]
|
2678
|
+
v(f'Read image {fn}')
|
2679
|
+
bts = BytesIO(zipfile.read(fn))
|
2680
|
+
img = PILImage.open(bts)
|
2681
|
+
piece[which] = {'filename': fn,
|
2682
|
+
'image': img,
|
2683
|
+
'size': img.size}
|
2684
|
+
|
2685
|
+
newMap[pieceID] = piece
|
2686
|
+
|
2687
|
+
del self._d['gamebox']['pieces']['map']
|
2688
|
+
self._d['gamebox']['pieces']['map'] = newMap
|
2689
|
+
|
2690
|
+
newMap = {}
|
2691
|
+
for markSID,mark in self._d['gamebox']['marks']['map'].items():
|
2692
|
+
markID = int(markSID)
|
2693
|
+
if markID in newMap:
|
2694
|
+
continue
|
2695
|
+
fn = mark['filename']
|
2696
|
+
v(f'Read image {fn}')
|
2697
|
+
bts = BytesIO(zipfile.read(fn))
|
2698
|
+
img = PILImage.open(bts)
|
2699
|
+
dsc = mark.get('description',None)
|
2700
|
+
mark['image'] = img
|
2701
|
+
mark['size'] = img.size
|
2702
|
+
newMap[markID] = mark
|
2703
|
+
|
2704
|
+
del self._d['gamebox']['marks']['map']
|
2705
|
+
self._d['gamebox']['marks']['map'] = newMap
|
2706
|
+
|
2707
|
+
newMap = {}
|
2708
|
+
for boardSID,board in self._d['gamebox']['boards'].items():
|
2709
|
+
boardID = int(boardSID)
|
2710
|
+
if boardID in newMap:
|
2711
|
+
continue
|
2712
|
+
filename = board['filename']
|
2713
|
+
v(f'Read file {filename}')
|
2714
|
+
content = zipfile.read(filename)
|
2715
|
+
img = WandImage(blob=content)
|
2716
|
+
board['image'] = content.decode()
|
2717
|
+
board['size'] = img.size
|
2718
|
+
newMap[boardID] = board
|
2719
|
+
|
2720
|
+
# del self._d['gamebox']['boards']
|
2721
|
+
# self._d['gamebox']['boards'] = newMap
|
2722
|
+
|
2723
|
+
# print(self)
|
2724
|
+
|
2725
|
+
def __str__(self):
|
2726
|
+
from pprint import pformat
|
2727
|
+
|
2728
|
+
return pformat(self._d)#,depth=5)
|
2729
|
+
|
2730
|
+
#
|
2731
|
+
# EOF
|
2732
|
+
#
|
2733
|
+
# ====================================================================
|
2734
|
+
# From zeropwd.py
|
2735
|
+
|
2736
|
+
nullpwd = b'\xee\n\xcbg\xbc\xdb\x92\x1a\x0c\xd2\xf1y\x83*\x96\xc9'
|
2737
|
+
|
2738
|
+
def zeropwd(filename):
|
2739
|
+
from pathlib import Path
|
2740
|
+
|
2741
|
+
pos = None
|
2742
|
+
with Archive(filename,'rb') as ar:
|
2743
|
+
header = GBXHeader(ar,GBXHeader.BOX)
|
2744
|
+
box = GBXInfo(ar)
|
2745
|
+
|
2746
|
+
|
2747
|
+
pos = ar.tell() - 4*2 - 2 - 2 - 16
|
2748
|
+
|
2749
|
+
with open(filename,'rb') as file:
|
2750
|
+
cnt = file.read()
|
2751
|
+
old = cnt[pos:pos+16]
|
2752
|
+
|
2753
|
+
lcnt = list(cnt)
|
2754
|
+
lcnt[pos:pos+16] = list(nullpwd)
|
2755
|
+
ncnt = bytes(lcnt)
|
2756
|
+
|
2757
|
+
on = Path(filename)
|
2758
|
+
on = on.with_stem(on.stem + '-new')
|
2759
|
+
with open(on,'wb') as file:
|
2760
|
+
file.write(ncnt)
|
2761
|
+
|
2762
|
+
|
2763
|
+
if __name__ == '__main__':
|
2764
|
+
from argparse import ArgumentParser, FileType
|
2765
|
+
ap = ArgumentParser(description='Disable password in gamebox')
|
2766
|
+
ap.add_argument('input', type=str, help='The file')
|
2767
|
+
|
2768
|
+
args = ap.parse_args()
|
2769
|
+
|
2770
|
+
zeropwd(args.input)
|
2771
|
+
|
2772
|
+
|
2773
|
+
|
2774
|
+
##
|
2775
|
+
# End of generated script
|
2776
|
+
##
|