Skip to content

Commit

Permalink
Now support variative name length
Browse files Browse the repository at this point in the history
  • Loading branch information
TesterTesterov authored Dec 26, 2021
1 parent 3fd6a2f commit 73f5f4e
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 39 deletions.
57 changes: 36 additions & 21 deletions ai5win_arc.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
class AI5WINArc(SilkyArc): # Previously released tool came to be handy.
# Some part of the class is from SilkyArcTool.
name_encoding = "cp932"
bytes_for_name = 32
possible_name_bytes = (20, 30, 32, 256)
header_int_structure = "I" # Just to be safe make this a parameter.

known_keys_triplets = (
(95, 1182992201, 391284862),
(3, 0x33656755, 0x68820811),
)

def __init__(self, arc: str, dir: str, verbose: bool = True, integrity_check: bool = False, **kwargs):
Expand All @@ -36,12 +37,14 @@ def __init__(self, arc: str, dir: str, verbose: bool = True, integrity_check: bo
first_key: int, key for text in header.
second_key: int, key for size in header.
third_key: int, key for offset in header.
name_bytes: int, number of bytes for a name.
"""
super().__init__(arc, dir, verbose, integrity_check)

self.first_key = kwargs.get("first_key", None)
self.second_key = kwargs.get("second_key", None)
self.third_key = kwargs.get("third_key", None)
self.name_bytes = kwargs.get("name_bytes", None)

# names
# 0 -- name, 1 -- compressed in lzss size, 2 -- offset from the beginning of the file.
Expand All @@ -53,14 +56,14 @@ def __init__(self, arc: str, dir: str, verbose: bool = True, integrity_check: bo
def _unpack_names(self) -> list:
"""Unpack archive names."""
input_file = open(self._arc_name, 'rb')
self.first_key, self.second_key, self.third_key = self.hack_crypto_keys(input_file)
self.first_key, self.second_key, self.third_key, self.name_bytes = self.hack_size_and_crypto_keys(input_file)
entry_count = self._read_header(input_file)

array_name = []
keyer = (self.second_key, self.third_key)
for entrer in range(entry_count):
prms = []
name = self.decrypt_name(input_file.read(self.bytes_for_name))
name = self.decrypt_name(input_file.read(self.name_bytes))
prms.append(name)
for key in keyer:
prms.append(struct.unpack(self.header_int_structure, input_file.read(4))[0] ^ key)
Expand Down Expand Up @@ -125,7 +128,7 @@ def _pack_names_and_files(self) -> tuple:

names.append(name_array)

sum += 40
sum += self.name_bytes + 8
# 1 байт за размер имени, далее имя, далее три >I параметра.

if self._verbose:
Expand Down Expand Up @@ -173,32 +176,44 @@ def _pack_files(self, head_len: int, temp_file: tempfile.TemporaryFile) -> None:
# Other technical methods.

@staticmethod
def hack_crypto_keys(input_file) -> tuple:
"""Hack all three keys used to encrypt/obfusificate the header.
def hack_size_and_crypto_keys(input_file) -> tuple:
"""Hack all three keys used to encrypt/obfusificate the header and length of names.
Works only if the archive has at least 2 entries.
First key is for text, second key is for size, third is for offset."""

current_offset = input_file.tell()

input_file.seek(0, 0)
entry_count = struct.unpack('I', input_file.read(4))[0]
start_offset = 4 + entry_count * 40
bytes_for_name = 0
first_key = 0
second_key = 0
third_key = 0

input_file.seek(4 + AI5WINArc.bytes_for_name-1, 0)
first_key = input_file.read(1)[0]
bad_size = struct.unpack(AI5WINArc.header_int_structure, input_file.read(4))[0]
third_key = struct.unpack(AI5WINArc.header_int_structure, input_file.read(4))[0] ^ start_offset
for bytes_for_name in AI5WINArc.possible_name_bytes:
input_file.seek(0, 0)
entry_count = struct.unpack('I', input_file.read(4))[0]
start_offset = 4 + entry_count * (bytes_for_name + 8)

input_file.seek(4+40+36, 0)
next_offset = struct.unpack(AI5WINArc.header_int_structure, input_file.read(4))[0] ^ third_key
good_size = next_offset - start_offset
second_key = bad_size ^ good_size
input_file.seek(4 + bytes_for_name - 1, 0)
first_key = input_file.read(1)[0]
bad_size = struct.unpack(AI5WINArc.header_int_structure, input_file.read(4))[0]
third_key = struct.unpack(AI5WINArc.header_int_structure, input_file.read(4))[0] ^ start_offset

input_file.seek(4 + (bytes_for_name + 8) + (bytes_for_name + 4), 0)
next_offset = struct.unpack(AI5WINArc.header_int_structure, input_file.read(4))[0] ^ third_key
good_size = next_offset - start_offset
second_key = bad_size ^ good_size

if good_size <= 0:
continue

break

input_file.seek(current_offset, 0)

print(">>< Hacked keys/Взломанные ключи:", hex(first_key), hex(second_key), hex(third_key))
print(">>< Hacked name size/Взломанный размер имён:", bytes_for_name)

return first_key, second_key, third_key
return first_key, second_key, third_key, bytes_for_name

def decrypt_name(self, test: bytes) -> str:
"""Decrypt AI5WIN-encrypted header entry name."""
Expand All @@ -213,10 +228,10 @@ def encrypt_name(self, test: str) -> bytes:
"""Encrypt AI5WIN-encrypted header entry name."""
test = test.encode(AI5WINArc.name_encoding)
check_len = len(test)
if check_len >= (AI5WINArc.bytes_for_name - 1):
test = test[:AI5WINArc.bytes_for_name - 1] + b'\x00'
if check_len >= (self.name_bytes - 1):
test = test[:self.name_bytes - 1] + b'\x00'
else:
test += b'\x00' * (AI5WINArc.bytes_for_name - check_len)
test += b'\x00' * (self.name_bytes - check_len)
tester = b''
for i in test:
tester += struct.pack('B', i ^ self.first_key)
Expand Down
55 changes: 37 additions & 18 deletions ai5win_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ class AI5WINArcToolGUI(SilkyArcToolGUI):
"AI5WIN Archives",
"*", # 10
"All files",
"Unpack archive",
"Pack archive",
"Unpack",
"Pack",
"Warning",
"Archive name not stated.", # 15
"Directory name not stated.",
"Error",
"Help",
"Choise/input of keys (0xNN...) for packing:"
"Choise/input of keys (0xNN...) for packing:",
"Choise/input of names length for packing:",
),
'rus': (
"AI5WINArcTool от Tester-а",
Expand All @@ -45,30 +46,31 @@ class AI5WINArcToolGUI(SilkyArcToolGUI):
"Архивы AI5WIN",
"*", # 10
"Все файлы",
"Распаковать архив",
"Запаковать архив",
"Распаковать",
"Запаковать",
"Предупреждение",
"Имя архива не указано.", # 15
"Имя директории не указано.",
"Ошибка",
"Справка",
"Выбор/ввод ключей (0xNN...) для запаковки:"
"Выбор/ввод ключей (0xNN...) для запаковки:",
"Выбор/ввод длины имён для запаковки:",
)
}

program_help = {
'eng': """
Dual languaged (rus+eng) GUI tool for packing and unpacking archives of AI5WIN engine. Very-very incomplete list of games of the engine thou can see on vndb. It is not the same arc as used in Silky Engine and AI6WIN. For Silky Engine .arc archives use SilkyArcTool instead, for AI5WIN's use AI6WINArcTool!
Dual languaged (rus+eng) GUI tool for packing and unpacking archives of AI5WIN engine. Very-very incomplete list of games of the engine thou can see on vndb. It is not the same arc as used in Silky Engine and AI6WIN. For Silky Engine .arc archives use SilkyArcTool instead, for AI6WIN's use AI6WINArcTool!
Important note: the tool is quite slow. It may take several minutes to extract and especially pack even rather small archives.
> Usage:
1. Run the tool (main.py or .exe).
2. Print filename (with extension!!!) or choose it by clicking on button "...".
3. Print directory or choose it by clicking on button "...".
4. If you want to pack, then choose the keys (or enter yours).
4. If you want to pack, then choose the keys and name size (or enter your data).
5. Push the button pack or "Unpack" to "Pack" or unpack.
6. Just wait until it done.
7. If you unpacked, then in the directory of archive will apeear new ".key" file. Open it with text editor and you will get keys of this archive (hacked authomatically). Enter them afterwards to repack the archive.
7. If you unpacked, then in the directory of archive will apeear new ".key" file. Open it with text editor and you will get keys and names size of this archive (hacked authomatically). Enter this data afterwards to repack the archive.
""",
'rus': """
Двуязычное средство (рус+англ) для распаковки и запаковки архивов движка AI5WIN. Очень-преочень неполный список игр на движке вы можете обозревать здесь. Не стоит путать его с разновидностями .arc, используемым в Silky Engine и AI6WIN. Для них используйте другие средства: SilkyArcTool и AI6WINArcTool соответственно!
Expand All @@ -78,10 +80,10 @@ class AI5WINArcToolGUI(SilkyArcToolGUI):
1. Запустите пакет средств (main.py иль .exe).
2. Введите имя архива (с расширением!!!) или выберите его, нажав на кнопку "...".
3. Введите имя директории файлов или выберите его, нажав на кнопку "...".
4. Если вы хотите запаковать архив, выберите ключи (или введите свои).
4. Если вы хотите запаковать архив, выберите ключи и размер имён (или введите свои данные).
5. Нажмите на кнопку, соответствующую желаемому действию ("Распаковать" и "Запаковать").
6. Ждите завершения.
7. Если вы выполняли распаковку, то в директории архива появится новый файл с расширением ".key". Откройте его с текстовым редактором. Вы сможете увидеть ключи данного архива (взломанные автоматически), которые вы можете в дальнейшем вводить для перезапаковки архива.
7. Если вы выполняли распаковку, то в директории архива появится новый файл с расширением ".key". Откройте его с текстовым редактором. Вы сможете увидеть ключи и размер имён данного архива (взломанные автоматически); сии данные вы можете в дальнейшем вводить для перезапаковки архива.
"""
}

Expand All @@ -107,6 +109,7 @@ def __init__(self, **kwargs):
self._first_key = tk.StringVar()
self._second_key = tk.StringVar()
self._third_key = tk.StringVar()
self._name_bytes = tk.StringVar()

self._root.geometry('{}x{}+{}+{}'.format(
self._width,
Expand Down Expand Up @@ -166,7 +169,7 @@ def __init__(self, **kwargs):
new_btn = tk.Button(
master=self._bottom_frame,
background="white",
font=("Helvetica", 14),
font=("Helvetica", 12),
command=actions[i],
)
new_btn.lang_index = 12 + i
Expand All @@ -175,7 +178,7 @@ def __init__(self, **kwargs):
self._help_btn = tk.Button(
master=self._bottom_frame,
background="white",
font=("Helvetica", 14),
font=("Helvetica", 12),
command=lambda: showinfo(self._strings_lib[self._language][18], self.programm_help[self._language]),
)
self._help_btn.lang_index = 18
Expand All @@ -197,6 +200,18 @@ def __init__(self, **kwargs):
values=good_key_values,
state="readonly")

self._name_lbl = tk.Label(master=self._bottom_frame,
background="white",
font=("Helvetica", 10))
self._name_lbl.lang_index = 20
good_name_values = [str(i) for i in AI5WINArc.possible_name_bytes]
self._name_cmb = ttk.Combobox(master=self._bottom_frame,
font=("Helvetica", 12),
textvariable=self._name_bytes,
values=good_name_values)
if good_name_values:
self._name_bytes.set(good_name_values[0])

keys_vars = (self._first_key, self._second_key, self._third_key)
self._keys_ent = []
for key in keys_vars:
Expand All @@ -221,12 +236,13 @@ def __init__(self, **kwargs):
self._keys_lbl.place(relx=0.0, rely=0.4, relwidth=1.0, relheight=0.1)
self._keys_cmb.place(relx=0.0, rely=0.5, relwidth=1.0, relheight=0.1)
for num, widget in enumerate(self._keys_ent):
widget.place(relx=0.33*num, rely=0.6, relwidth=0.33, relheight=0.1)
widget.place(relx=0.33 * num, rely=0.6, relwidth=0.33, relheight=0.1)
for num, widget in enumerate(self._action_btns):
widget.place(relx=0.0, rely=0.7 + 0.1 * num, relwidth=1.0, relheight=0.1)
self._help_btn.place(relx=0.0, rely=0.9, relwidth=1.0, relheight=0.1)
widget.place(relx=0.0 + 0.33 * num, rely=0.9, relwidth=0.33 + 0.01 * num, relheight=0.1)
self._name_lbl.place(relx=0.0, rely=0.7, relwidth=1.0, relheight=0.1)
self._name_cmb.place(relx=0.0, rely=0.8, relwidth=1.0, relheight=0.1)
self._help_btn.place(relx=0.67, rely=0.9, relwidth=0.33, relheight=0.1)
self._bottom_frame.place(relx=0.0, rely=0.1, relwidth=1.0, relheight=0.9)

self._root.mainloop()

# Technical methods for packing and unpacking.
Expand All @@ -242,9 +258,11 @@ def _unpack_this_archive(self, arc_name, dir_name) -> None:
key_file.write("Key 1/Ключ 1: {}\n".format(hex(arc_archive.first_key)))
key_file.write("Key 2/Ключ 2: {}\n".format(hex(arc_archive.second_key)))
key_file.write("Key 3/Ключ 3: {}\n".format(hex(arc_archive.third_key)))
key_file.write("Длина имён: {}\n".format(hex(arc_archive.name_bytes)))
self._first_key.set(hex(arc_archive.first_key))
self._second_key.set(hex(arc_archive.second_key))
self._third_key.set(hex(arc_archive.third_key))
self._name_bytes.set(str(arc_archive.name_bytes))
except Exception as e:
showerror(self._strings_lib[self._language][17], str(e))
finally:
Expand All @@ -257,7 +275,8 @@ def _pack_this_archive(self, arc_name, dir_name) -> None:
arc_archive = AI5WINArc(arc_name, dir_name, verbose=True, integrity_check=False,
first_key=int(self._first_key.get(), 16),
second_key=int(self._second_key.get(), 16),
third_key=int(self._third_key.get(), 16))
third_key=int(self._third_key.get(), 16),
name_bytes=int(self._name_bytes.get(), 10))
arc_archive.pack()
except Exception as e:
showerror(self._strings_lib[self._language][17], str(e))
Expand Down

0 comments on commit 73f5f4e

Please sign in to comment.