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