Add files via upload

This commit is contained in:
2025-02-08 21:20:29 +03:00
committed by GitHub
parent f4bf8faf18
commit 9652ec481c
6 changed files with 392 additions and 0 deletions

4
__init__.py Normal file
View File

@@ -0,0 +1,4 @@
# __init__.py
from .ibunroku_event_strings_parser import parse_event_strings as parse_event_strings
from .ibunroku_event_strings_injecter import inject_event_strings as inject_event_strings
from .ibunroku_binner2 import from_bin as from_bin, to_bin as to_bin

92
ibunroku_binner2.py Normal file
View File

@@ -0,0 +1,92 @@
import os
import struct
def from_bin(file_path):
"""
Extracts binary files from a BIN file.
Args:
file_path (str): Path to the BIN file.
"""
CHUNK = 2048
pointers = [0]
folder_name = file_path.rstrip(".BIN") + "/"
if not os.path.isdir(folder_name):
os.mkdir(folder_name)
with open(file_path, 'rb') as f:
data = f.read()
i = 0
while True:
pointer = struct.unpack('<H', data[i:i+2])[0] * CHUNK
if i > 0:
pointer += 154
pointers.append(pointer)
i += 2
if data[i:i+2] == b'\x00\x00':
break
for p in range(1, len(pointers) - 1):
file_start = pointers[p]
file_end = file_start + struct.unpack('<H', data[pointers[p]+4:pointers[p]+6])[0] + 8
with open(f'{folder_name}{p}.bin', 'wb') as f2:
f2.write(data[file_start:file_end])
print(f'{p}.bin extracted!')
def to_bin(folder_path):
"""
Assembles binary files into a BIN file.
Args:
folder_path (str): Path to the folder containing binary files.
"""
CHUNK = 2048
header = b'\x01\x00'
bytestream = b''
bin_name = folder_path.rstrip("/") + ".BIN"
with open(bin_name, 'wb') as f:
i = 1
size = CHUNK
while True:
file_path = f'{folder_path}/{i}.bin'
if os.path.isfile(file_path):
with open(file_path, 'rb') as f2:
data = f2.read()
while len(data) % CHUNK != 0:
data += b'\x00'
size += len(data)
if i == 1:
data += b'\x00' * 154
bytestream += data
header += struct.pack('<H', size // CHUNK)
i += 1
else:
break
header += b'\x00' * (CHUNK - len(header))
f.write(header)
f.write(bytestream)
print(f'{i-1} files imported!')
print(f'{bin_name} assembled!')
if __name__ == "__main__":
def main():
"""Main function to handle user input and execute bin operations."""
print("Ibunroku Binner")
while True:
print("Usage:")
print("To import into BIN file: [import] [folder path]")
print("To export from BIN to files: [export] [file path]")
user_input = input()
try:
command, path = user_input.split(" ")
if command == "import":
to_bin(path)
elif command == "export":
from_bin(path)
except ValueError:
print("Invalid input. Please try again.")
main()

View File

@@ -0,0 +1,133 @@
import os
import struct
import codecs
import json
from ibunroku_settings import load_encoding_table, commands, russian, hex_from_str
def from_hex_str(s):
"""Converts a hex string to an integer."""
bin_ = ''.join(hex_from_str[v] for v in s)
bin_ = bin_[8:] + bin_[:8]
return int(bin_, 2)
def inject_event_strings(bin_file_path, json_file_path, tbl_path):
"""
Injects event strings from a JSON file into a binary file.
Args:
bin_file_path (str): Path to the original binary file.
json_file_path (str): Path to the JSON file containing parsed strings.
tbl_path (str): Path to the encoding table file.
"""
encoding_table = load_encoding_table(tbl_path)
with open(bin_file_path, 'rb') as binfile:
binfile_data = binfile.read()
with codecs.open(json_file_path, 'r', encoding='utf_8') as jsonfile:
json_data = json.load(jsonfile)
# Extract header information
header_size = json_data["Header"]["Header Size"]
file_size = json_data["Header"]["File Size"]
music = json_data["Header"]["Music"]
end_text_table = json_data["Header"]["End of Text Table"]
unk_pointer1 = json_data["Header"]["Unknown Pointer 1"] - end_text_table
unk_pointer2 = json_data["Header"]["Unknown Pointer 2"] - end_text_table
unk_pointer3 = json_data["Header"]["Unknown Pointer 3"] - end_text_table
script_table_pointer = json_data["Header"]["Script Table Pointer"]
text_table_pointer = json_data["Header"]["Text Table Pointer"]
data_after = json_data["Other Data"]
# Prepare kanji dictionary
kanji = {v: k for k, v in encoding_table.items()}
# Convert strings and update pointers
textblock = b''
new_pointers = [text_table_pointer]
text_pointer = text_table_pointer
for s in json_data["Parsed strings"]:
converted_s = b''
i = 0
while i < len(json_data["Parsed strings"][s]):
char_s = json_data["Parsed strings"][s][i]
if char_s in russian:
converted_s += struct.pack('B', russian.index(char_s))
i += 1
elif char_s in encoding_table.values():
char_hex_code = [k for k, v in encoding_table.items() if v == char_s][0]
converted_s += struct.pack('B', int(char_hex_code, 16))
i += 1
elif char_s == "|":
kanji_hex_code = json_data["Parsed strings"][s][i+1:i+5]
converted_s += struct.pack('H', from_hex_str(kanji_hex_code))
i += 5
elif char_s == '[':
command = ''
j = 1
while ']' not in command:
command += json_data["Parsed strings"][s][i+j]
j += 1
if '=' in command:
command = command.split('=')
command_index = commands.index(f'[{command[0]}]')
if command[0] == "WAIT":
converted_s += b'\xFF' + struct.pack('B', command_index) + struct.pack('B', int(command[1].rstrip(']'))) + b'\x00'
else:
converted_s += b'\xFF' + struct.pack('B', command_index) + struct.pack('B', int(command[1].rstrip(']')))
else:
converted_s += b'\xFF' + struct.pack('B', commands.index(f'[{command}]'))
i += j
else:
kanji_hex_code = kanji[char_s]
converted_s += struct.pack('>H', from_hex_str(kanji_hex_code))
i += 1
text_pointer += len(converted_s)
new_pointers.append(text_pointer)
textblock += converted_s
# Update file size and pointers
file_size = text_table_pointer + len(textblock) + len(data_after)
end_text_table = text_table_pointer + len(textblock)
unk_pointer1 += end_text_table
unk_pointer2 += end_text_table
unk_pointer3 += end_text_table
# Update text pointers in the binary file
new_table = binfile_data[:text_table_pointer+8]
for i, s in enumerate(json_data["Parsed strings"]):
new_table = new_table[:int(s)+8] + struct.pack('<H', new_pointers[i]) + new_table[int(s)+10:]
new_table = new_table[script_table_pointer:]
# Inject data into the new binary file
with open(bin_file_path + "_", 'wb') as f2:
f2.write(struct.pack('<H', header_size) + b'\x10\x80')
f2.write(struct.pack('<H', file_size) + b'\x10\x80')
f2.write(binfile_data[8:10])
f2.write(struct.pack('<H', music))
f2.write(binfile_data[12:60])
f2.write(struct.pack('<H', end_text_table) + b'\x10\x80')
f2.write(binfile_data[64:68])
f2.write(struct.pack('<H', unk_pointer1) + b'\x10\x80')
f2.write(binfile_data[72:76])
f2.write(struct.pack('<H', unk_pointer2) + b'\x10\x80')
f2.write(binfile_data[80:84])
f2.write(struct.pack('<H', unk_pointer3) + b'\x10\x80')
f2.write(binfile_data[88:104])
f2.write(struct.pack('<H', script_table_pointer) + b'\x10\x80')
f2.write(binfile_data[108:script_table_pointer])
f2.write(new_table)
f2.write(textblock)
f2.write(bytes(data_after))
print(f'Exported: {bin_file_path}_')
if __name__ == '__main__':
while True:
bin_file = input("Enter the path to the original bin file: ")
json_file = input("Enter the path to the JSON file: ")
tbl_file = input("Enter the path to the encoding table file: ")
if os.path.isfile(bin_file) and os.path.isfile(json_file) and os.path.isfile(tbl_file):
inject_event_strings(bin_file, json_file, tbl_file)
break

View File

@@ -0,0 +1,108 @@
import os
import struct
import codecs
import json
from ibunroku_settings import load_encoding_table, commands
def parse_event_strings(file_path, tbl_path):
"""
Parses event strings from a binary file and saves the output to text and JSON files.
Args:
file_path (str): Path to the binary file.
tbl_path (str): Path to the encoding table file.
"""
encoding_table = load_encoding_table(tbl_path)
with open(file_path, 'rb') as f:
data = f.read()
# Unpack header information
header_size = struct.unpack('<H', data[:2])[0]
file_size = struct.unpack('<H', data[4:6])[0]
data = data[header_size:]
# Extract pointers and other data
music = struct.unpack('<H', data[2:4])[0]
end_text_table_pointer = struct.unpack('<H', data[52:54])[0]
other_data = data[end_text_table_pointer:file_size]
unk_pointer1 = struct.unpack('<H', data[60:62])[0]
unk_pointer2 = struct.unpack('<H', data[68:70])[0]
unk_pointer3 = struct.unpack('<H', data[76:78])[0]
table_pointer = struct.unpack('<H', data[96:98])[0]
# Extract raw strings
raw_strings = []
i = table_pointer
while data[i+2:i+4] != b'\x10\x80':
i += 2
text_table_pointer = struct.unpack('<H', data[i:i+2])[0]
while i < text_table_pointer:
if data[i+2:i+4] == b'\x10\x80' and data[i-4:i] == b'\xFF\x55\x00\x00':
reader = struct.unpack('<H', data[i:i+2])[0]
string = b''
while data[reader:reader+2] != b'\xFF\x01':
string += struct.pack('B', data[reader])
reader += 1
string += b'\xFF\x01\x00\x00'
raw_strings.append((i, string))
i += 4
else:
i += 4
# Prepare header JSON
header_json = {
'Header Size': header_size,
'File Size': file_size,
'Music': music,
'End of Text Table': end_text_table_pointer,
'Unknown Pointer 1': unk_pointer1,
'Unknown Pointer 2': unk_pointer2,
'Unknown Pointer 3': unk_pointer3,
'Script Table Pointer': table_pointer,
'Text Table Pointer': text_table_pointer
}
# Decode strings using character dictionary
strings = {}
with codecs.open('output.txt', 'w', encoding="utf_8") as txt:
for raw in raw_strings:
decoded_string = ''
i = 0
while i < len(raw[1]):
char = raw[1][i]
if char < 0x80:
decoded_string += encoding_table.get(f'{char:02X}', f'[kana={char}]')
i += 1
elif char >= 0x80 and char < 0xFF:
char = int.from_bytes(raw[1][i:i+2], 'big')
char_hex = f'{char:04X}'
decoded_string += encoding_table.get(char_hex, f'|{char_hex}')
i += 2
else:
command = raw[1][i+1]
if command == 5:
wait = int.from_bytes(raw[1][i+2:i+3], 'big')
decoded_string += f'[WAIT={wait}]'
i += 4
elif command == 6:
color = raw[1][i+2]
decoded_string += f'[COLOR={color}]'
i += 3
elif command == 14:
something = raw[1][i+2]
decoded_string += f'[SOMETHING={something}]'
i += 3
else:
try:
decoded_string += commands[command]
except IndexError:
print(f'Unknown command {command} in file {file_path}')
i += 2
strings[str(raw[0])] = decoded_string
txt.write(f'{str(raw[0])}:\t{decoded_string}\n')
# Save other data and strings to JSON
with codecs.open('output.json', 'w', encoding='utf_8') as jsonf:
json.dump({'Header': header_json, 'Parsed strings': strings, 'Other Data': list(other_data)}, jsonf, indent=4, ensure_ascii=False)

38
ibunroku_settings.py Normal file
View File

@@ -0,0 +1,38 @@
import codecs
def load_encoding_table(file_path):
"""
Loads the encoding table from a file and returns a dictionary mapping hex codes to characters or commands.
Args:
file_path (str): Path to the encoding table file.
Returns:
dict: Dictionary mapping hex codes to characters or commands.
"""
encoding_table = {}
with codecs.open(file_path, 'r', encoding='utf_8') as file:
for line in file:
line = line.strip()
if line and '=' in line:
hex_code, char = line.split('=')
encoding_table[hex_code.strip()] = char.strip()
return encoding_table
# Mapping of commands used in the game
commands = [
'[wait]', '[end]', '[nl]'
]
# Mapping of Russian characters to their byte values
russian = [
'<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>',
'<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>', '<EFBFBD>'
]
# Mapping of hex strings to binary strings
hex_from_str = {
'0': '0000', '1': '0001', '2': '0010', '3': '0011', '4': '0100', '5': '0101',
'6': '0110', '7': '0111', '8': '1000', '9': '1001', 'A': '1010', 'B': '1011',
'C': '1100', 'D': '1101', 'E': '1110', 'F': '1111'
}

17
setup.py Normal file
View File

@@ -0,0 +1,17 @@
from setuptools import setup, find_packages
setup(
name='Persona1-text-tools',
version='0.1',
packages=find_packages(),
install_requires=[
# <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD>
],
entry_points={
'console_scripts': [
'parse-event-strings=Persona1_text_tools.ibunroku_event_strings_parser:main',
'inject-event-strings=Persona1_text_tools.ibunroku_event_strings_injecter:main',
'bin-tool=Persona1_text_tools.ibunroku_binner2:main',
],
},
)