Skip to content

Commit 2371c50

Browse files
committed
feat: .tpl support
1 parent 8cac1d8 commit 2371c50

5 files changed

Lines changed: 225 additions & 137 deletions

File tree

ECO2/__main__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from ECO2.cli import cli
2+
3+
if __name__ == '__main__':
4+
cli()

ECO2/cli.py

Lines changed: 89 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,115 @@
1+
from itertools import chain
2+
from pathlib import Path
3+
import sys
4+
15
import click
6+
from loguru import logger
27

38
from ECO2.eco2 import Eco2
49

10+
_log_format = ('<green>{time:HH:mm:ss}</green> | '
11+
'<level>{level: <8}</level> | '
12+
'<cyan>{module}</cyan>:<cyan>{line}</cyan> '
13+
'<level>{message}</level>')
14+
515

616
@click.group()
7-
def cli():
8-
pass
17+
@click.option('-d', '--debug', is_flag=True, help='Show debug message')
18+
def cli(debug: bool):
19+
"""
20+
\b
21+
사용 방법 안내: 다음 명령어 입력
22+
`ECO2 --help`
23+
`ECO2 encrypt --help`
24+
`ECO2 decrypt --help`
25+
"""
26+
logger.remove()
27+
logger.add(sys.stdout,
28+
level=(10 if debug else 20),
29+
format=_log_format,
30+
backtrace=False)
931

1032

1133
@cli.command()
12-
@click.argument('eco')
13-
@click.option('-s',
14-
'--savedir',
15-
help=('해석한 header와 value 파일의 저장 경로. '
16-
'미설정 시 eco 파일과 동일 경로에 저장.'))
17-
@click.option('-h',
18-
'--header',
19-
default='header',
20-
help='header 파일 이름. 미설정 시 `header`로 지정.')
21-
@click.option('-v',
22-
'--value',
23-
help=('value 파일 이름. '
24-
'미설정 시 eco 파일과 동일 이름으로 저장.'))
25-
def decrypt(eco, savedir, header, value):
34+
@click.argument('inputs', nargs=-1, required=True)
35+
@click.option('-o',
36+
'--output',
37+
help=('저장할 파일 경로. 확장자를 `.header`, `.xml`로 바꾼 경로에 '
38+
'각각 header와 value를 저장함. INPUT이 여러 개일 경우 무시됨.'))
39+
def decrypt(inputs: tuple, output):
2640
"""
2741
\b
28-
eco 파일을 해석해서 header와 value 파일로 나눠 저장.
29-
header: eco의 버전, 생성 날짜 등 정보를 담은 바이너리 파일.
30-
value : 해석 설정 정보를 담은 xml 파일.
42+
ECO2 저장 파일 (`.eco`, `.tpl`)을 해석해서 header와 value 파일로 나눠 저장.
43+
header: ECO2 저장 파일의 버전, 생성 날짜 등 정보를 담은 바이너리 파일.
44+
`output.header` 형식.
45+
value : 해석 설정 정보를 담은 xml 파일. `output.xml` 형식.
3146
3247
\b
3348
Argument:
34-
ECO: 해석할 eco 파일 경로
49+
INPUTS: 해석할 ECO2 저장 파일의 경로.
50+
폴더를 지정하는 경우 해당 폴더 내 모든 저장 파일을 대상으로 함.
3551
"""
36-
Eco2.decrypt(path=eco,
37-
save_dir=savedir,
38-
header_name=header,
39-
value_name=value)
52+
paths = [Path(x) for x in inputs]
53+
54+
op = None
55+
if len(paths) == 1:
56+
if paths[0].is_dir():
57+
paths = chain(paths[0].glob('*.eco'), paths[0].glob('*.tpl'))
58+
elif output:
59+
op = Path(output)
60+
61+
for path in paths:
62+
header_path = op.with_suffix(Eco2.header_ext) if op else None
63+
value_path = op.with_suffix(Eco2.value_ext) if op else None
64+
65+
try:
66+
Eco2.decrypt(path=path,
67+
header_path=header_path,
68+
value_path=value_path)
69+
except (ValueError, OSError) as e:
70+
logger.exception(e)
4071

4172

4273
@cli.command()
43-
@click.argument('header')
44-
@click.argument('value')
45-
@click.option('-s',
46-
'--savedir',
47-
help=('해석한 header와 value 파일의 저장 경로. '
48-
'미설정 시 value 파일과 동일 경로에 저장.'))
49-
def encrypt(header, value, savedir):
74+
@click.argument('inputs', nargs=-1, required=True)
75+
@click.option('-h',
76+
'--header',
77+
help=('header 파일 경로. 미지정 시 value 파일과 동일한 경로·이름에 '
78+
'확장자가 `.header`인 파일로 추정. INPUT이 폴더일 경우 무시됨.'))
79+
@click.option('-o',
80+
'--output',
81+
help=('저장할 eco 파일의 경로. '
82+
'INPUT이 여러 개일 경우 무시됨.'))
83+
def encrypt(inputs: tuple, header, output):
5084
"""
5185
header와 value를 암호화해서 eco 파일로 변환
5286
5387
\b
54-
Arguments:
55-
HEADER: header 파일 경로.
56-
VALUE : value 파일 경로. 폴더 경로를 지정하면 해당 폴더에 존재하는 모든 .xml 파일을 변환.
88+
Argument:
89+
INPUTS: 해석할 value 파일의 경로.
90+
폴더를 지정하는 경우 해당 폴더 내 모든 xml 파일을 대상으로 함.
5791
"""
58-
Eco2.encrypt_dir(header_path=header, value_path=value, save_dir=savedir)
92+
paths = [Path(x) for x in inputs]
93+
94+
header_path = None
95+
output_path = None
96+
if len(paths) == 1:
97+
if paths[0].is_dir():
98+
paths = paths[0].glob(f'*{Eco2.value_ext}')
99+
else:
100+
if header:
101+
header_path = Path(header)
102+
if output:
103+
output_path = Path(output)
104+
105+
for path in paths:
106+
hp = header_path if header_path else path.with_suffix(Eco2.header_ext)
107+
op = output_path if output_path else path.with_suffix('.eco')
108+
109+
try:
110+
Eco2.encrypt(header_path=hp, value_path=path, save_path=op)
111+
except (ValueError, OSError) as e:
112+
logger.exception(e)
59113

60114

61115
if __name__ == '__main__':

ECO2/cx_setup.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@
44
if __name__ == '__main__':
55
options = {
66
'build_exe': {
7-
'optimize':
8-
1,
9-
'excludes': [
10-
'email', 'html', 'http', 'logging', 'tkinter', 'unittest'
11-
]
7+
'optimize': 1,
8+
'excludes': ['email', 'html', 'http', 'tkinter', 'unittest']
129
}
1310
}
1411

ECO2/eco2.py

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from itertools import cycle
22
from pathlib import Path
33

4+
from loguru import logger
5+
46

57
class Eco2:
68
header = (
@@ -11,12 +13,14 @@ class Eco2:
1113
(256, 'Desc'),
1214
(19, 'Make time'),
1315
(19, 'Edit time'),
14-
(8, 'unknown'),
16+
(8, 'password'),
1517
)
1618
key = (172, 41, 85, 66)
1719
header_encoding = 'EUC-KR'
1820
value_encoding = 'UTF-8'
21+
header_ext = '.header'
1922
value_ext = '.xml'
23+
ds = '</DS>'
2024

2125
@classmethod
2226
def decrypt_bytes(cls, data: bytes):
@@ -57,13 +61,11 @@ def _decode_header(cls, data: bytes):
5761
def _print_header_info(cls, header: bytes):
5862
header_dict = cls._decode_header(header)
5963

60-
print('Header info:')
61-
6264
for key, value in header_dict.items():
63-
if key == 'unknown':
65+
if key == 'password':
6466
continue
6567

66-
print(f' {key:10s}: {value}')
68+
logger.info('[HEADER] {:10s}: {}', key, value)
6769

6870
@classmethod
6971
def _write_value(cls, path: Path, value: str):
@@ -75,78 +77,82 @@ def _read_value(cls, path: Path):
7577
return path.read_text(encoding=cls.value_encoding).replace('\n', '\r\n')
7678

7779
@classmethod
78-
def _decrypt_eco2_data(cls, data: bytes):
79-
decrypted = cls.decrypt_bytes(data)
80+
def _decrypt(cls, data: bytes, decrypt: bool):
81+
if decrypt:
82+
data = cls.decrypt_bytes(data)
83+
8084
hl = cls.header_length()
85+
header_bytes = data[:hl]
86+
value_bytes = data[hl:]
8187

82-
header_bytes = decrypted[:hl]
83-
value_bytes = decrypted[hl:]
84-
value = value_bytes.decode()
88+
try:
89+
value = value_bytes.decode(cls.value_encoding)
90+
except ValueError:
91+
# 케이스 설정 부분 (<DS>...</DS>)만 추출하고
92+
# 결과부 (<DSR>...</DSR>)은 버림
93+
logger.debug('ECO2 파일의 결과부 (DSR)를 제외합니다.')
94+
value = value_bytes.decode(cls.value_encoding, 'replace')
95+
96+
if cls.ds not in value:
97+
raise ValueError('인코딩 에러')
98+
99+
value = value[:(value.find(cls.ds) + len(cls.ds))]
85100

86101
return header_bytes, value
87102

88103
@classmethod
89-
def decrypt(cls, path, save_dir=None, header_name=None, value_name=None):
104+
def decrypt(cls, path, header_path=None, value_path=None):
90105
path = Path(path)
91-
save_dir = path.parent if save_dir is None else Path(save_dir)
92-
if not save_dir.exists():
93-
raise FileNotFoundError(save_dir)
94106

95-
if header_name is None:
96-
header_name = 'header'
97-
if value_name is None:
98-
value_name = path.stem
107+
if header_path is None:
108+
header_path = path.with_suffix(cls.header_ext)
109+
else:
110+
header_path = Path(header_path)
111+
112+
if value_path is None:
113+
value_path = path.with_suffix(cls.value_ext)
114+
else:
115+
value_path = Path(value_path)
99116

100-
data = path.read_bytes()
101-
bheader, value = cls._decrypt_eco2_data(data)
117+
logger.info('Input: {}', path)
118+
logger.debug('Header: {}', header_path)
119+
logger.debug('Value: {}', value_path)
102120

103-
cls._print_header_info(bheader)
121+
data = path.read_bytes()
122+
decrypt = (path.suffix == '.eco')
123+
try:
124+
header, value = cls._decrypt(data=data, decrypt=decrypt)
125+
except ValueError:
126+
header, value = cls._decrypt(data=data, decrypt=(not decrypt))
104127

105-
header_path = save_dir.joinpath(header_name)
106-
header_path.write_bytes(bheader)
128+
cls._print_header_info(header)
107129

108-
value_path = save_dir.joinpath(value_name + cls.value_ext)
130+
header_path.write_bytes(header)
109131
cls._write_value(path=value_path, value=value)
110132

111133
@classmethod
112-
def _encrypt(cls, header: bytes, value_path: str, save_path: Path):
113-
value = cls._read_value(path=value_path)
114-
bvalue = value.encode(cls.value_encoding)
115-
encrypted = cls.encrypt_bytes(header + bvalue)
134+
def _encrypt(cls, header: bytes, value: bytes, save_path: Path):
135+
encrypted = cls.encrypt_bytes(header + value)
116136
save_path.write_bytes(encrypted)
117137

118138
@classmethod
119139
def encrypt(cls, header_path, value_path, save_path=None):
120-
if save_path is None:
121-
save_path = 'output.eco'
122-
123140
header_path = Path(header_path)
124141
value_path = Path(value_path)
125-
save_path = Path(save_path)
126142

127-
header = header_path.read_bytes()
128-
cls._print_header_info(header)
143+
if save_path:
144+
save_path = Path(save_path)
145+
else:
146+
save_path = value_path.with_suffix('.eco')
129147

130-
cls._encrypt(header=header, value_path=value_path, save_path=save_path)
131-
132-
@classmethod
133-
def encrypt_dir(cls, header_path, value_path, save_dir=None):
134-
header_path = Path(header_path)
135-
value_path = Path(value_path)
148+
logger.info('Value: {}', value_path)
149+
logger.info('Header: {}', header_path)
150+
logger.debug('Output: {}', save_path)
136151

137152
header = header_path.read_bytes()
138153
cls._print_header_info(header)
139154

140-
save_dir = value_path if save_dir is None else Path(save_dir)
141-
if not save_dir.is_dir():
142-
save_dir = save_dir.parent
143-
144-
if value_path.is_dir():
145-
vps = value_path.glob(f'*{cls.value_ext}')
146-
else:
147-
vps = [value_path]
155+
value = cls._read_value(path=value_path)
156+
value_bytes = value.encode(cls.value_encoding)
148157

149-
for vp in vps:
150-
cls._encrypt(header=header,
151-
value_path=vp,
152-
save_path=save_dir.joinpath(f'{vp.stem}.eco'))
158+
cls._encrypt(header=header, value=value_bytes, save_path=save_path)

0 commit comments

Comments
 (0)