Add files via upload
This commit is contained in:
4
__init__.py
Normal file
4
__init__.py
Normal 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
92
ibunroku_binner2.py
Normal 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()
|
||||
133
ibunroku_event_strings_injecter.py
Normal file
133
ibunroku_event_strings_injecter.py
Normal 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
|
||||
108
ibunroku_event_strings_parser.py
Normal file
108
ibunroku_event_strings_parser.py
Normal 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
38
ibunroku_settings.py
Normal 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
17
setup.py
Normal 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',
|
||||
],
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user