Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

# coding=utf-8 

import tempfile 

import os 

import logging 

from filecmp import dircmp 

from os.path import exists, join 

from zipfile import BadZipFile, ZipFile, ZipInfo 

import shutil 

 

from pathlib import Path 

 

from emiz.sltp import SLTP 

from emiz.mission import Mission 

from emiz.dummy_miz import dummy_miz 

 

LOGGER = logging.getLogger('EMIZ').getChild(__name__) 

ENCODING = 'iso8859_15' 

 

 

class Miz: 

def __init__(self, path_to_miz_file, temp_dir=None, keep_temp_dir: bool = False, overwrite: bool = False): 

 

self.miz_path = Path(path_to_miz_file) 

 

if not self.miz_path.exists(): 

raise FileNotFoundError(path_to_miz_file) 

 

if not self.miz_path.is_file(): 

raise TypeError(path_to_miz_file) 

 

if not self.miz_path.suffix == '.miz': 

raise ValueError(path_to_miz_file) 

 

if temp_dir is not None: 

raise PendingDeprecationWarning() 

 

self.keep_temp_dir = keep_temp_dir 

 

self.overwrite = overwrite 

 

self.tmpdir = Path(tempfile.mkdtemp('EMFT_')) 

LOGGER.debug('temporary directory: {}'.format(self.tmpdir.absolute())) 

 

self.zip_content = None 

self._mission = None 

self._mission_qual = None 

self._l10n = None 

self._l10n_qual = None 

self._map_res = None 

self._map_res_qual = None 

 

def __enter__(self): 

LOGGER.debug('instantiating new Mission object as a context') 

self.unzip(self.overwrite) 

self._decode() 

return self 

 

def __exit__(self, exc_type, exc_val, exc_tb): 

if exc_type: 

LOGGER.error('there were error with this mission, keeping temp dir at "{}" and re-raising'.format( 

self.tmpdir.absolute())) 

LOGGER.error('{}\n{}'.format(exc_type, exc_val)) 

return False 

else: 

LOGGER.debug('closing Mission object context') 

if not self.keep_temp_dir: 

LOGGER.debug('removing temp dir: {}'.format(self.tmpdir.absolute())) 

shutil.rmtree(self.tmpdir.absolute()) 

 

@property 

def mission_file(self): 

return self.tmpdir.joinpath('mission') 

 

@property 

def dictionary_file(self): 

return self.tmpdir.joinpath('l10n', 'DEFAULT', 'dictionary') 

 

@property 

def map_res_file(self): 

return self.tmpdir.joinpath('l10n', 'DEFAULT', 'mapResource') 

 

@property 

def mission(self) -> Mission: 

if self._mission is None: 

raise RuntimeError() 

return self._mission 

 

@property 

def l10n(self) -> dict: 

if self._l10n is None: 

raise RuntimeError() 

return self._l10n 

 

@property 

def map_res(self) -> dict: 

if self._map_res is None: 

raise RuntimeError() 

return self._map_res 

 

@staticmethod 

def reorder(miz_file_path, target_dir, skip_options_file): 

 

LOGGER.debug('re-ordering miz file: {}'.format(miz_file_path)) 

LOGGER.debug('destination folder: {}'.format(target_dir)) 

LOGGER.debug('{}option file'.format('skipping' if skip_options_file else 'including')) 

 

if not Path(target_dir).exists(): 

LOGGER.debug(f'creating directory {target_dir}') 

os.makedirs(target_dir) 

 

with Miz(miz_file_path, overwrite=True) as m: 

 

def mirror_dir(src, dst): 

LOGGER.debug('{} -> {}'.format(src, dst)) 

diff_ = dircmp(src, dst, ignore) 

diff_list = diff_.left_only + diff_.diff_files 

LOGGER.debug('differences: {}'.format(diff_list)) 

for x in diff_list: 

source = Path(diff_.left).joinpath(x) 

target = Path(diff_.right).joinpath(x) 

LOGGER.debug('looking at: {}'.format(x)) 

if source.is_dir(): 

LOGGER.debug('isdir: {}'.format(x)) 

if not target.exists(): 

LOGGER.debug('creating: {}'.format(x)) 

target.mkdir() 

mirror_dir(source, target) 

else: 

LOGGER.debug('copying: {}'.format(x)) 

shutil.copy2(source.absolute(), diff_.right) 

for sub in diff_.subdirs.values(): 

assert isinstance(sub, dircmp) 

mirror_dir(sub.left, sub.right) 

 

m._encode() 

 

if skip_options_file: 

ignore = ['options'] 

else: 

ignore = [] 

 

mirror_dir(m.tmpdir, target_dir) 

 

def _decode(self): 

 

LOGGER.debug('decoding lua tables') 

 

if not self.zip_content: 

self.unzip(overwrite=False) 

 

LOGGER.debug('reading map resource file') 

with open(self.map_res_file, encoding=ENCODING) as f: 

self._map_res, self._map_res_qual = SLTP().decode(f.read()) 

 

LOGGER.debug('reading l10n file') 

with open(self.dictionary_file, encoding=ENCODING) as f: 

self._l10n, self._l10n_qual = SLTP().decode(f.read()) 

 

LOGGER.debug('reading mission file') 

with open(self.mission_file, encoding=ENCODING) as f: 

mission_data, self._mission_qual = SLTP().decode(f.read()) 

self._mission = Mission(mission_data, self._l10n) 

 

LOGGER.debug('decoding done') 

 

def _encode(self): 

 

LOGGER.debug('encoding lua tables') 

 

LOGGER.debug('encoding map resource') 

with open(self.map_res_file, mode='w', encoding=ENCODING) as f: 

f.write(SLTP().encode(self._map_res, self._map_res_qual)) 

 

LOGGER.debug('encoding l10n dictionary') 

with open(self.dictionary_file, mode='w', encoding=ENCODING) as f: 

f.write(SLTP().encode(self.l10n, self._l10n_qual)) 

LOGGER.debug('encoding mission dictionary') 

with open(self.mission_file, mode='w', encoding=ENCODING) as f: 

f.write(SLTP().encode(self.mission.d, self._mission_qual)) 

 

LOGGER.debug('encoding done') 

 

def _check_extracted_content(self): 

 

for filename in self.zip_content: 

p = self.tmpdir.joinpath(filename) 

if not p.exists(): 

raise FileNotFoundError(p.absolute()) 

 

def _extract_files_from_zip(self, zip_file): 

 

for item in zip_file.infolist(): # not using ZipFile.extractall() for security reasons 

assert isinstance(item, ZipInfo) 

 

LOGGER.debug('unzipping item: {}'.format(item.filename)) 

 

try: 

zip_file.extract(item, self.tmpdir.absolute()) 

except: 

LOGGER.error('failed to extract archive member: {}'.format(item.filename)) 

raise 

 

def unzip(self, overwrite: bool = False): 

 

if self.zip_content and not overwrite: 

raise FileExistsError(self.tmpdir.absolute()) 

 

LOGGER.debug('unzipping miz to temp dir') 

 

try: 

 

with ZipFile(self.miz_path.absolute()) as zip_file: 

 

LOGGER.debug('reading infolist') 

 

self.zip_content = [f.filename for f in zip_file.infolist()] 

 

self._extract_files_from_zip(zip_file) 

 

except BadZipFile: 

raise BadZipFile(self.miz_path.absolute()) 

 

except: 

LOGGER.exception('error while unzipping miz file: {}'.format(self.miz_path.absolute())) 

raise 

 

LOGGER.debug('checking miz content') 

 

# noinspection PyTypeChecker 

for miz_item in map( 

join, 

[self.tmpdir.absolute()], 

[ 

'mission', 

'options', 

'warehouses', 

'l10n/DEFAULT/dictionary', 

'l10n/DEFAULT/mapResource' 

] 

): 

 

if not exists(miz_item): 

LOGGER.error('missing file in miz: {}'.format(miz_item)) 

raise FileNotFoundError(miz_item) 

 

self._check_extracted_content() 

 

LOGGER.debug('all files have been found, miz successfully unzipped') 

 

def zip(self, destination=None): 

 

self._encode() 

 

if destination is None: 

destination = Path(self.miz_path.parent).joinpath('{}_EMFT.miz'.format(self.miz_path.name)) 

 

destination = Path(destination).absolute() 

 

LOGGER.debug('zipping mission to: {}'.format(destination)) 

 

with open(destination, mode='wb') as f: 

f.write(dummy_miz) 

 

with ZipFile(destination, mode='w', compression=8) as _z: 

 

for f in self.zip_content: 

abs_path = self.tmpdir.joinpath(f).absolute() 

LOGGER.debug('injecting in zip file: {}'.format(abs_path)) 

_z.write(abs_path, arcname=f) 

 

return destination