ankigammon 1.0.0__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.
Potentially problematic release.
This version of ankigammon might be problematic. Click here for more details.
- ankigammon/__init__.py +7 -0
- ankigammon/__main__.py +6 -0
- ankigammon/analysis/__init__.py +13 -0
- ankigammon/analysis/score_matrix.py +373 -0
- ankigammon/anki/__init__.py +6 -0
- ankigammon/anki/ankiconnect.py +224 -0
- ankigammon/anki/apkg_exporter.py +123 -0
- ankigammon/anki/card_generator.py +1307 -0
- ankigammon/anki/card_styles.py +1034 -0
- ankigammon/gui/__init__.py +8 -0
- ankigammon/gui/app.py +209 -0
- ankigammon/gui/dialogs/__init__.py +10 -0
- ankigammon/gui/dialogs/export_dialog.py +597 -0
- ankigammon/gui/dialogs/import_options_dialog.py +163 -0
- ankigammon/gui/dialogs/input_dialog.py +776 -0
- ankigammon/gui/dialogs/note_dialog.py +93 -0
- ankigammon/gui/dialogs/settings_dialog.py +384 -0
- ankigammon/gui/format_detector.py +292 -0
- ankigammon/gui/main_window.py +1071 -0
- ankigammon/gui/resources/icon.icns +0 -0
- ankigammon/gui/resources/icon.ico +0 -0
- ankigammon/gui/resources/icon.png +0 -0
- ankigammon/gui/resources/style.qss +394 -0
- ankigammon/gui/resources.py +26 -0
- ankigammon/gui/widgets/__init__.py +8 -0
- ankigammon/gui/widgets/position_list.py +193 -0
- ankigammon/gui/widgets/smart_input.py +268 -0
- ankigammon/models.py +322 -0
- ankigammon/parsers/__init__.py +7 -0
- ankigammon/parsers/gnubg_parser.py +454 -0
- ankigammon/parsers/xg_binary_parser.py +870 -0
- ankigammon/parsers/xg_text_parser.py +729 -0
- ankigammon/renderer/__init__.py +5 -0
- ankigammon/renderer/animation_controller.py +406 -0
- ankigammon/renderer/animation_helper.py +221 -0
- ankigammon/renderer/color_schemes.py +145 -0
- ankigammon/renderer/svg_board_renderer.py +824 -0
- ankigammon/settings.py +239 -0
- ankigammon/thirdparty/__init__.py +7 -0
- ankigammon/thirdparty/xgdatatools/__init__.py +17 -0
- ankigammon/thirdparty/xgdatatools/xgimport.py +160 -0
- ankigammon/thirdparty/xgdatatools/xgstruct.py +1032 -0
- ankigammon/thirdparty/xgdatatools/xgutils.py +118 -0
- ankigammon/thirdparty/xgdatatools/xgzarc.py +260 -0
- ankigammon/utils/__init__.py +13 -0
- ankigammon/utils/gnubg_analyzer.py +431 -0
- ankigammon/utils/gnuid.py +622 -0
- ankigammon/utils/move_parser.py +239 -0
- ankigammon/utils/ogid.py +335 -0
- ankigammon/utils/xgid.py +419 -0
- ankigammon-1.0.0.dist-info/METADATA +370 -0
- ankigammon-1.0.0.dist-info/RECORD +56 -0
- ankigammon-1.0.0.dist-info/WHEEL +5 -0
- ankigammon-1.0.0.dist-info/entry_points.txt +2 -0
- ankigammon-1.0.0.dist-info/licenses/LICENSE +21 -0
- ankigammon-1.0.0.dist-info/top_level.txt +1 -0
ankigammon/settings.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Settings and configuration management for AnkiGammon.
|
|
3
|
+
|
|
4
|
+
Handles loading and saving user preferences such as color scheme selection.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Settings:
|
|
14
|
+
"""Manages application settings with persistence."""
|
|
15
|
+
|
|
16
|
+
DEFAULT_SETTINGS = {
|
|
17
|
+
"default_color_scheme": "classic",
|
|
18
|
+
"deck_name": "My AnkiGammon Deck",
|
|
19
|
+
"show_options": True,
|
|
20
|
+
"interactive_moves": True,
|
|
21
|
+
"export_method": "ankiconnect",
|
|
22
|
+
"gnubg_path": None,
|
|
23
|
+
"gnubg_analysis_ply": 3,
|
|
24
|
+
"generate_score_matrix": False,
|
|
25
|
+
"board_orientation": "counter-clockwise",
|
|
26
|
+
"last_apkg_directory": None,
|
|
27
|
+
"import_error_threshold": 0.080,
|
|
28
|
+
"import_include_player_x": True,
|
|
29
|
+
"import_include_player_o": True,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def __init__(self, config_path: Optional[Path] = None):
|
|
33
|
+
"""
|
|
34
|
+
Initialize settings manager.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
config_path: Path to config file. If None, uses default location.
|
|
38
|
+
"""
|
|
39
|
+
if config_path is None:
|
|
40
|
+
config_dir = Path.home() / ".ankigammon"
|
|
41
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
config_path = config_dir / "config.json"
|
|
43
|
+
|
|
44
|
+
self.config_path = config_path
|
|
45
|
+
self._settings = self._load()
|
|
46
|
+
|
|
47
|
+
def _load(self) -> dict:
|
|
48
|
+
"""Load settings from config file."""
|
|
49
|
+
if not self.config_path.exists():
|
|
50
|
+
return self.DEFAULT_SETTINGS.copy()
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
54
|
+
loaded = json.load(f)
|
|
55
|
+
# Merge with defaults to handle new settings
|
|
56
|
+
settings = self.DEFAULT_SETTINGS.copy()
|
|
57
|
+
settings.update(loaded)
|
|
58
|
+
return settings
|
|
59
|
+
except (json.JSONDecodeError, IOError):
|
|
60
|
+
# If file is corrupted or unreadable, use defaults
|
|
61
|
+
return self.DEFAULT_SETTINGS.copy()
|
|
62
|
+
|
|
63
|
+
def _save(self) -> None:
|
|
64
|
+
"""Save settings to config file."""
|
|
65
|
+
try:
|
|
66
|
+
with open(self.config_path, 'w', encoding='utf-8') as f:
|
|
67
|
+
json.dump(self._settings, f, indent=2)
|
|
68
|
+
except IOError:
|
|
69
|
+
# Silently fail if unable to save
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
def get(self, key: str, default=None):
|
|
73
|
+
"""Get a setting value."""
|
|
74
|
+
return self._settings.get(key, default)
|
|
75
|
+
|
|
76
|
+
def set(self, key: str, value) -> None:
|
|
77
|
+
"""Set a setting value and save to disk."""
|
|
78
|
+
self._settings[key] = value
|
|
79
|
+
self._save()
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def color_scheme(self) -> str:
|
|
83
|
+
"""Get the default color scheme."""
|
|
84
|
+
return self._settings.get("default_color_scheme", "classic")
|
|
85
|
+
|
|
86
|
+
@color_scheme.setter
|
|
87
|
+
def color_scheme(self, value: str) -> None:
|
|
88
|
+
"""Set the default color scheme."""
|
|
89
|
+
self.set("default_color_scheme", value)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def deck_name(self) -> str:
|
|
93
|
+
"""Get the default deck name."""
|
|
94
|
+
return self._settings.get("deck_name", "My AnkiGammon Deck")
|
|
95
|
+
|
|
96
|
+
@deck_name.setter
|
|
97
|
+
def deck_name(self, value: str) -> None:
|
|
98
|
+
"""Set the default deck name."""
|
|
99
|
+
self.set("deck_name", value)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def show_options(self) -> bool:
|
|
103
|
+
"""Get whether to show options on cards."""
|
|
104
|
+
return self._settings.get("show_options", True)
|
|
105
|
+
|
|
106
|
+
@show_options.setter
|
|
107
|
+
def show_options(self, value: bool) -> None:
|
|
108
|
+
"""Set whether to show options on cards."""
|
|
109
|
+
self.set("show_options", value)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def interactive_moves(self) -> bool:
|
|
113
|
+
"""Get whether to enable interactive move visualization."""
|
|
114
|
+
return self._settings.get("interactive_moves", True)
|
|
115
|
+
|
|
116
|
+
@interactive_moves.setter
|
|
117
|
+
def interactive_moves(self, value: bool) -> None:
|
|
118
|
+
"""Set whether to enable interactive move visualization."""
|
|
119
|
+
self.set("interactive_moves", value)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def export_method(self) -> str:
|
|
123
|
+
"""Get the default export method."""
|
|
124
|
+
return self._settings.get("export_method", "ankiconnect")
|
|
125
|
+
|
|
126
|
+
@export_method.setter
|
|
127
|
+
def export_method(self, value: str) -> None:
|
|
128
|
+
"""Set the default export method."""
|
|
129
|
+
self.set("export_method", value)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def gnubg_path(self) -> Optional[str]:
|
|
133
|
+
"""Get the GnuBG executable path."""
|
|
134
|
+
return self._settings.get("gnubg_path", None)
|
|
135
|
+
|
|
136
|
+
@gnubg_path.setter
|
|
137
|
+
def gnubg_path(self, value: Optional[str]) -> None:
|
|
138
|
+
"""Set the GnuBG executable path."""
|
|
139
|
+
self.set("gnubg_path", value)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def gnubg_analysis_ply(self) -> int:
|
|
143
|
+
"""Get the GnuBG analysis depth (ply)."""
|
|
144
|
+
return self._settings.get("gnubg_analysis_ply", 3)
|
|
145
|
+
|
|
146
|
+
@gnubg_analysis_ply.setter
|
|
147
|
+
def gnubg_analysis_ply(self, value: int) -> None:
|
|
148
|
+
"""Set the GnuBG analysis depth (ply)."""
|
|
149
|
+
self.set("gnubg_analysis_ply", value)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def generate_score_matrix(self) -> bool:
|
|
153
|
+
"""Get whether to generate score matrix for cube decisions."""
|
|
154
|
+
return self._settings.get("generate_score_matrix", False)
|
|
155
|
+
|
|
156
|
+
@generate_score_matrix.setter
|
|
157
|
+
def generate_score_matrix(self, value: bool) -> None:
|
|
158
|
+
"""Set whether to generate score matrix for cube decisions."""
|
|
159
|
+
self.set("generate_score_matrix", value)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def board_orientation(self) -> str:
|
|
163
|
+
"""Get the board orientation (clockwise or counter-clockwise)."""
|
|
164
|
+
return self._settings.get("board_orientation", "counter-clockwise")
|
|
165
|
+
|
|
166
|
+
@board_orientation.setter
|
|
167
|
+
def board_orientation(self, value: str) -> None:
|
|
168
|
+
"""Set the board orientation (clockwise or counter-clockwise)."""
|
|
169
|
+
if value not in ["clockwise", "counter-clockwise"]:
|
|
170
|
+
raise ValueError("board_orientation must be 'clockwise' or 'counter-clockwise'")
|
|
171
|
+
self.set("board_orientation", value)
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def last_apkg_directory(self) -> Optional[str]:
|
|
175
|
+
"""Get the last directory used for APKG export."""
|
|
176
|
+
return self._settings.get("last_apkg_directory", None)
|
|
177
|
+
|
|
178
|
+
@last_apkg_directory.setter
|
|
179
|
+
def last_apkg_directory(self, value: Optional[str]) -> None:
|
|
180
|
+
"""Set the last directory used for APKG export."""
|
|
181
|
+
self.set("last_apkg_directory", value)
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def import_error_threshold(self) -> float:
|
|
185
|
+
"""Get the error threshold for XG file imports."""
|
|
186
|
+
return self._settings.get("import_error_threshold", 0.080)
|
|
187
|
+
|
|
188
|
+
@import_error_threshold.setter
|
|
189
|
+
def import_error_threshold(self, value: float) -> None:
|
|
190
|
+
"""Set the error threshold for XG file imports."""
|
|
191
|
+
self.set("import_error_threshold", value)
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def import_include_player_x(self) -> bool:
|
|
195
|
+
"""Get whether to include Player X mistakes in imports."""
|
|
196
|
+
return self._settings.get("import_include_player_x", True)
|
|
197
|
+
|
|
198
|
+
@import_include_player_x.setter
|
|
199
|
+
def import_include_player_x(self, value: bool) -> None:
|
|
200
|
+
"""Set whether to include Player X mistakes in imports."""
|
|
201
|
+
self.set("import_include_player_x", value)
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def import_include_player_o(self) -> bool:
|
|
205
|
+
"""Get whether to include Player O mistakes in imports."""
|
|
206
|
+
return self._settings.get("import_include_player_o", True)
|
|
207
|
+
|
|
208
|
+
@import_include_player_o.setter
|
|
209
|
+
def import_include_player_o(self, value: bool) -> None:
|
|
210
|
+
"""Set whether to include Player O mistakes in imports."""
|
|
211
|
+
self.set("import_include_player_o", value)
|
|
212
|
+
|
|
213
|
+
def is_gnubg_available(self) -> bool:
|
|
214
|
+
"""
|
|
215
|
+
Check if GnuBG is configured and accessible.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
True if gnubg_path is set and the file exists and is executable.
|
|
219
|
+
"""
|
|
220
|
+
path = self.gnubg_path
|
|
221
|
+
if path is None:
|
|
222
|
+
return False
|
|
223
|
+
try:
|
|
224
|
+
path_obj = Path(path)
|
|
225
|
+
return path_obj.exists() and os.access(path, os.X_OK)
|
|
226
|
+
except (OSError, ValueError):
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# Global settings instance
|
|
231
|
+
_settings = None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def get_settings() -> Settings:
|
|
235
|
+
"""Get the global settings instance."""
|
|
236
|
+
global _settings
|
|
237
|
+
if _settings is None:
|
|
238
|
+
_settings = Settings()
|
|
239
|
+
return _settings
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
xgdatatools - eXtreme Gammon binary file parser
|
|
3
|
+
|
|
4
|
+
Original author: Michael Petch <mpetch@gnubg.org>
|
|
5
|
+
License: LGPL-3.0 or later
|
|
6
|
+
Source: https://github.com/oysteijo/xgdatatools
|
|
7
|
+
|
|
8
|
+
This package contains the xgdatatools library for parsing eXtreme Gammon
|
|
9
|
+
binary (.xg) file formats.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from . import xgimport
|
|
13
|
+
from . import xgstruct
|
|
14
|
+
from . import xgutils
|
|
15
|
+
from . import xgzarc
|
|
16
|
+
|
|
17
|
+
__all__ = ['xgimport', 'xgstruct', 'xgutils', 'xgzarc']
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#
|
|
2
|
+
# xgimport.py - XG import module
|
|
3
|
+
# Copyright (C) 2013,2014 Michael Petch <mpetch@gnubg.org>
|
|
4
|
+
# <mpetch@capp-sysware.com>
|
|
5
|
+
#
|
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU Lesser General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18
|
+
#
|
|
19
|
+
#
|
|
20
|
+
|
|
21
|
+
from __future__ import with_statement as _with
|
|
22
|
+
import tempfile as _tempfile
|
|
23
|
+
import shutil as _shutil
|
|
24
|
+
import struct as _struct
|
|
25
|
+
import os as _os
|
|
26
|
+
from . import xgutils as _xgutils
|
|
27
|
+
from . import xgzarc as _xgzarc
|
|
28
|
+
from . import xgstruct as _xgstruct
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Import(object):
|
|
32
|
+
|
|
33
|
+
class Segment(object):
|
|
34
|
+
GDF_HDR, GDF_IMAGE, XG_GAMEHDR, XG_GAMEFILE, XG_ROLLOUTS, XG_COMMENT, \
|
|
35
|
+
ZLIBARC_IDX, XG_UNKNOWN = range(8)
|
|
36
|
+
EXTENSIONS = ['_gdh.bin', '.jpg', '_gamehdr.bin', '_gamefile.bin',
|
|
37
|
+
'_rollouts.bin', '_comments.bin', '_idx.bin', None]
|
|
38
|
+
GDF_HDR_EXT, GDF_IMAGE_EXT, XG_GAMEHDR_EXT, XG_GAMEFILE_EXT, \
|
|
39
|
+
XG_ROLLOUTS_EXT, XG_COMMENTS_EXT, \
|
|
40
|
+
XG_IDX_EXT, XG_UNKNOWN = EXTENSIONS
|
|
41
|
+
XG_FILEMAP = {'temp.xgi': XG_GAMEHDR, 'temp.xgr': XG_ROLLOUTS,
|
|
42
|
+
'temp.xgc': XG_COMMENT, 'temp.xg': XG_GAMEFILE}
|
|
43
|
+
|
|
44
|
+
XG_GAMEHDR_LEN = 556
|
|
45
|
+
|
|
46
|
+
__TMP_PREFIX = 'tmpXGI'
|
|
47
|
+
|
|
48
|
+
def __init__(self, type=GDF_HDR, delete=True, prefix=__TMP_PREFIX):
|
|
49
|
+
self.filename = None
|
|
50
|
+
self.fd = None
|
|
51
|
+
self.file = None
|
|
52
|
+
self.type = type
|
|
53
|
+
self.__prefix = prefix
|
|
54
|
+
self.__autodelete = delete
|
|
55
|
+
self.ext = self.EXTENSIONS[type]
|
|
56
|
+
|
|
57
|
+
def __enter__(self):
|
|
58
|
+
self.createtempfile()
|
|
59
|
+
return self
|
|
60
|
+
|
|
61
|
+
def __exit__(self, type, value, traceback):
|
|
62
|
+
self.closetempfile()
|
|
63
|
+
|
|
64
|
+
def closetempfile(self):
|
|
65
|
+
try:
|
|
66
|
+
if self.file is not None:
|
|
67
|
+
self.file.close()
|
|
68
|
+
finally:
|
|
69
|
+
self.fd = None
|
|
70
|
+
self.file = None
|
|
71
|
+
if self.__autodelete and self.filename is not None and \
|
|
72
|
+
_os.path.exists(self.filename):
|
|
73
|
+
try:
|
|
74
|
+
_os.unlink(self.filename)
|
|
75
|
+
finally:
|
|
76
|
+
self.filename = None
|
|
77
|
+
|
|
78
|
+
def copyto(self, fileto):
|
|
79
|
+
_shutil.copy(self.filename, fileto)
|
|
80
|
+
|
|
81
|
+
def createtempfile(self, mode="w+b"):
|
|
82
|
+
self.fd, self.filename = _tempfile.mkstemp(prefix=self.__prefix)
|
|
83
|
+
self.file = _os.fdopen(self.fd, mode)
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
def __init__(self, filename):
|
|
87
|
+
self.filename = filename
|
|
88
|
+
|
|
89
|
+
def getfilesegment(self):
|
|
90
|
+
with open(self.filename, "rb") as xginfile:
|
|
91
|
+
# Extract the uncompressed Game Data Header (GDH)
|
|
92
|
+
# Note: MS Windows Vista feature
|
|
93
|
+
gdfheader = \
|
|
94
|
+
_xgstruct.GameDataFormatHdrRecord().fromstream(xginfile)
|
|
95
|
+
if gdfheader is None:
|
|
96
|
+
raise Error("Not a game data format file", self.filename)
|
|
97
|
+
|
|
98
|
+
# Extract the Game Format Header to a temporary file
|
|
99
|
+
with Import.Segment(type=Import.Segment.GDF_HDR) as segment:
|
|
100
|
+
xginfile.seek(0)
|
|
101
|
+
block = xginfile.read(gdfheader.HeaderSize)
|
|
102
|
+
segment.file.write(block)
|
|
103
|
+
segment.file.flush()
|
|
104
|
+
yield segment
|
|
105
|
+
|
|
106
|
+
# Extract the uncompressed thumbnail JPEG from the GDF hdr
|
|
107
|
+
if (gdfheader.ThumbnailSize > 0):
|
|
108
|
+
with Import.Segment(type=Import.Segment.GDF_IMAGE) as segment:
|
|
109
|
+
xginfile.seek(gdfheader.ThumbnailOffset, _os.SEEK_CUR)
|
|
110
|
+
imgbuf = xginfile.read(gdfheader.ThumbnailSize)
|
|
111
|
+
segment.file.write(imgbuf)
|
|
112
|
+
segment.file.flush()
|
|
113
|
+
yield segment
|
|
114
|
+
|
|
115
|
+
# Retrieve an archive object from the stream
|
|
116
|
+
archiveobj = _xgzarc.ZlibArchive(xginfile)
|
|
117
|
+
|
|
118
|
+
# Process all the files in the archive
|
|
119
|
+
for filerec in archiveobj.arcregistry:
|
|
120
|
+
|
|
121
|
+
# Retrieve the archive file to a temporary file
|
|
122
|
+
segment_file, seg_filename = archiveobj.getarchivefile(filerec)
|
|
123
|
+
|
|
124
|
+
# Create a file segment object to passback to the caller
|
|
125
|
+
xg_filetype = Import.Segment.XG_FILEMAP[filerec.name]
|
|
126
|
+
xg_filesegment = Import.Segment(type=xg_filetype,
|
|
127
|
+
delete=False)
|
|
128
|
+
xg_filesegment.filename = seg_filename
|
|
129
|
+
xg_filesegment.fd = segment_file
|
|
130
|
+
|
|
131
|
+
# If we are looking at the game info file then check
|
|
132
|
+
# the magic number to ensure it is valid
|
|
133
|
+
if xg_filetype == Import.Segment.XG_GAMEFILE:
|
|
134
|
+
segment_file.seek(Import.Segment.XG_GAMEHDR_LEN)
|
|
135
|
+
magicStr = bytearray(segment_file.read(4)).decode('ascii')
|
|
136
|
+
if magicStr != 'DMLI':
|
|
137
|
+
raise Error("Not a valid XG gamefile", self.filename)
|
|
138
|
+
|
|
139
|
+
yield xg_filesegment
|
|
140
|
+
|
|
141
|
+
segment_file.close()
|
|
142
|
+
_os.unlink(seg_filename)
|
|
143
|
+
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class Error(Exception):
|
|
148
|
+
|
|
149
|
+
def __init__(self, error, filename):
|
|
150
|
+
self.value = "XG Import Error processing '%s': %s" % \
|
|
151
|
+
(filename, str(error))
|
|
152
|
+
self.error = error
|
|
153
|
+
self.filename = filename
|
|
154
|
+
|
|
155
|
+
def __str__(self):
|
|
156
|
+
return repr(self.value)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == '__main__':
|
|
160
|
+
pass
|