diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/vwf_renderer_test.py b/tests/vwf_renderer_test.py
new file mode 100644
index 0000000..f1eb9fe
--- /dev/null
+++ b/tests/vwf_renderer_test.py
@@ -0,0 +1,32 @@
+import unittest
+from pathlib import Path
+from xml.etree import ElementTree
+
+from build import read_fixed_from_xml
+from utils.smallvwf import VwfAsset
+
+
+class VwfRendererTestCase(unittest.TestCase):
+ def test_render(self):
+ vwf = VwfAsset("./fonts/8x8vwf.png")
+ strings = []
+ # with open("./text/fr/monsters.xml", encoding='utf-8') as datasource:
+ # tree = ElementTree.parse(datasource)
+ # root = tree.getroot()
+ #
+ # for child in root:
+ # text = child.text
+ # if text:
+ # strings.append(text)
+
+ strings = [
+ "Objets", "Magie", "Equiper", "Statut", "Placer", "Changer", "Options", "Sauver"
+ ]
+
+ vwf.set_strings(strings)
+
+ vwf.render()
+ renderd = Path("./renderd.bin")
+ serialized = vwf.serialize()
+
+ renderd.write_bytes(serialized)
diff --git a/utils/dump.py b/utils/dump.py
index 9bede5b..683e59b 100755
--- a/utils/dump.py
+++ b/utils/dump.py
@@ -1,8 +1,12 @@
#!/usr/bin/env python3.4
+import struct
+
+import script
from a816.cpu.cpu_65c816 import snes_to_rom
+from a816.symbols import low_rom_bus
from script import Table
from script.formulas import base_relative_16bits_pointer_formula
-from script.pointers import Script, write_pointers_as_xml
+from script.pointers import Script, write_pointers_as_xml, Pointer
def write_fixed_text_as_xml(pointers, table, length, output_file):
@@ -16,9 +20,137 @@ def write_fixed_text_as_xml(pointers, table, length, output_file):
fd.write('\n')
fd.write('\n')
+def write_nul_terminated_strings_as_xml(strings, output_file):
+ with open(output_file, 'wt', encoding='utf-8') as fd:
+ fd.writelines('\n')
+ fd.write('\n')
+ for s in strings:
+ fd.write('')
+ fd.write(s) #.strip())
+ fd.write('\n')
+ fd.write('\n')
+
+def dump_en():
+ # jtable = Table('../text/ff4_jap.tbl')
+ jtable = Table('../text/ff4_menus.tbl')
+ item_description = low_rom_bus.get_address(0x2407ad)
+ item_desc = []
+
+ # with open('../ff4j.smc', 'rb') as rom:
+ with open('../Final Fantasy IV (Japan) (Rev 1) [En by J2e v3.21].sfc', 'rb') as rom:
+ rom.seek(item_description.physical)
+ for k in range(443):
+ s = bytes()
+ while (char := rom.read(1)) != b'\x00':
+ print(char)
+ s += char
+ item_desc.append(jtable.to_text(s))
+
+ write_nul_terminated_strings_as_xml(item_desc, "../text/en/item_descriptions.xml")
+ ...
+
+def dump_jp():
+ jtable = Table('../text/ff4_jap.tbl')
+ #jtable = Table('.text/ff4_menus.tbl')
+ item_description = low_rom_bus.get_address(0x0fae2a)
+ item_desc = []
+
+ with open('../ff4j.smc', 'rb') as rom:
+ #with open('../Final Fantasy IV (Japan) (Rev 1) [En by J2e v3.21].sfc', 'rb') as rom:
+ rom.seek(item_description.physical)
+ for k in range(47):
+ s = bytes()
+ while (char := rom.read(1)) != b'\x00':
+ print(char)
+ s += char
+ item_desc.append(jtable.to_text(s))
+
+ write_nul_terminated_strings_as_xml(item_desc, "../text/jp/item_descriptions.xml")
+ ...
+
+
+def dump_handedness():
+ jtable = Table('../text/ff4_jap.tbl')
+ addr = low_rom_bus.get_address(0x01e2d9)
+ with open('../ff4j.smc', 'rb') as rom:
+ rom.seek(addr.physical)
+ data = rom.read(8*4)
+ text = jtable.to_text(data)
+ ...
if __name__ == '__main__':
- table = Table('text/ff4_menus.tbl')
+ jtable = Table('../text/ff4_jap.tbl')
+# addr = low_rom_bus.get_address(0xef200)
+# with open('../ff4j.smc', 'rb') as rom:
+# rom.seek(addr.physical)
+# i = 0
+# battle_messages_pointers = []
+# while i < 0xba:
+# data = rom.read(2)
+# pointer = struct.unpack(' np.ndarray:
shape = image.shape
width = shape[1]
height = shape[0]
@@ -34,7 +35,7 @@ def char_as_1bbp(char):
return bytes(binary_data)
-def get_max_width(char):
+def get_max_width(char: np.ndarray) -> int:
max_width = 0
for byte in char:
trimmed = np.trim_zeros(byte, 'b')
@@ -43,23 +44,43 @@ def get_max_width(char):
return max_width
-def convert_font_to_1bpp(font_file, has_grid=True):
+def convert_font_to_1bpp(font_file, has_grid=True, char_height=16):
image = np.array(Image.open(font_file))
# image = ndimage.imread(font_file)
- char = get_char(image, 0x00, has_grid, 8, 16)
+ char = get_char(image, 0x00, has_grid, 8, char_height)
data = b''
char_index = 1
while len(char) > 0:
data += char_as_1bbp(char)
- char = get_char(image, char_index, has_grid, 8, 16)
+ char = get_char(image, char_index, has_grid, 8, char_height)
+ char_index += 1
+
+ len_table = {}
+ for i in range(char_index - 1):
+ len_table[i] = get_max_width(get_char(image, i, has_grid, 8, char_height))
+
+ return len_table, data
+
+def convert_font_to_2bpp(font_file, has_grid=True, char_height=16):
+ image = np.array(Image.open(font_file))
+
+ # image = ndimage.imread(font_file)
+
+ char = get_char(image, 0x00, has_grid, 8, char_height)
+
+ data = b''
+ char_index = 1
+ while len(char) > 0:
+ data += write_as_2bpp(char)
+ char = get_char(image, char_index, has_grid, 8, char_height)
char_index += 1
len_table = {}
for i in range(char_index - 1):
- len_table[i] = get_max_width(get_char(image, i, has_grid, 8, 16))
+ len_table[i] = get_max_width(get_char(image, i, has_grid, 8, char_height))
return len_table, data
@@ -89,4 +110,17 @@ def remove_grid(font_file):
if __name__ == '__main__':
- remove_grid('/Users/emmanuel/PycharmProjects/ff4/fonts/wicked_vwf.png')
\ No newline at end of file
+ remove_grid('/Users/emmanuel/PycharmProjects/ff4/fonts/wicked_vwf.png')
+
+
+def write_as_2bpp(data: np.ndarray) -> bytearray:
+ binary_data = bytearray()
+ for y_value in range(0, len(data[0]), 8):
+ char = data[0:8, y_value:y_value + 8]
+
+ for byte in char:
+ byte_value = int(''.join(byte.astype(str)).ljust(8, '0'), 2)
+ binary_data.append(0xFF)
+ binary_data.append(byte_value)
+
+ return binary_data
diff --git a/utils/scratchpad.py b/utils/scratchpad.py
new file mode 100644
index 0000000..83b98d9
--- /dev/null
+++ b/utils/scratchpad.py
@@ -0,0 +1,22 @@
+if __name__ == "__main__":
+ texts = [
+ "Requis",
+ "Garde",
+ "Rang"
+ ]
+ t = ' '.join(texts)
+
+ available = sorted({c for c in t})
+ print(''.join(sorted(available)))
+ print (len(available))
+ mp_needed = [available.index(c) + 0xdd for c in "Requis"]
+ # mp_needed[mp_needed.index(0xdd)]= 0xff
+
+ garde_text = [available.index(c) + 0xdd for c in "Garde "]
+ garde_text[garde_text.index(0xdd)]= 0xff
+
+ passer_text = [available.index(c) + 0xdd for c in "Rang"]
+
+ print(mp_needed)
+ print(garde_text)
+ print(passer_text)
diff --git a/utils/smallvwf.py b/utils/smallvwf.py
index 4896421..6b1253c 100644
--- a/utils/smallvwf.py
+++ b/utils/smallvwf.py
@@ -1,12 +1,15 @@
-from math import ceil
import binascii
import struct
-from PIL import Image
-from utils.font import get_char, get_max_width
+from math import ceil
+
import numpy as np
+from PIL import Image
+from script import Table
+
+from utils.font import get_char, get_max_width, write_as_2bpp
+
def text_to_char(text):
- ord('Z') - ord('A')
data = []
for char in text:
if 'A' <= char <= 'Z':
@@ -44,18 +47,6 @@ def build_text_image(font_file, text_data):
return buffer
-def write_as_2bpp(data):
- binary_data = bytearray()
- for y_value in range(0, len(data[0]), 8):
- char = data[0:8, y_value:y_value + 8]
-
- for byte in char:
- byte_value = int(''.join(byte.astype(str)).ljust(8, '0'), 2)
- binary_data.append(0xFF)
- binary_data.append(byte_value)
-
- return binary_data
-
classes = [
'Chevalier noir ',
'Chevalier dragon ',
@@ -117,6 +108,82 @@ def write_as_2bpp(data):
]
+# new file format:
+# ptr: 2 len:1 tile_count: 1
+#
+# ptr: data: len
+# on the programming side:
+# we need to know where we can write in the vram
+# vram_ptr
+# need to display item n: lookup the n-th pointer and the data size,
+# setup DMA transfer
+# know the tile_id and tile_count ?
+# ring buffer ?
+
+class VwfAsset:
+ def __init__(self, font_file: str, table: Table) -> None:
+ self.font = np.array(Image.open(font_file))
+ self.strings = []
+ self.rendered_strings: dict[str, np.ndarray] = {}
+ self.table = table
+
+ def set_strings(self, strings: list[str]) -> None:
+ self.strings = strings
+
+ def get_char(self, char: int) -> np.ndarray:
+ current_char = get_char(self.font, char, True, 8, 8)
+ if char == 0xFF:
+ width = 2
+ else:
+ width = get_max_width(current_char)
+
+ return current_char[0:8, 0:width + 1]
+
+ def render_string(self, string: str) -> np.ndarray | None:
+ buffer: np.ndarray | None = None
+ chars = self.table.to_bytes(string)
+ for char in chars:
+ culled_char = self.get_char(char)
+
+ if buffer is not None:
+ buffer = np.concatenate((buffer, culled_char), 1)
+ else:
+ buffer = culled_char
+
+ return buffer
+
+ def render(self) -> None:
+ buffer: np.ndarray | None = None
+
+ for string in self.strings:
+ string_buffer = self.render_string(string)
+ self.rendered_strings[string] = string_buffer
+
+ return buffer
+
+ def serialize(self) -> bytearray:
+ pointers: list[tuple[int, int, int]] = []
+ data = bytearray()
+ data_origin = len(self.rendered_strings.keys()) * 3
+
+ for string, rendered_string in self.rendered_strings.items():
+ serialized_string = write_as_2bpp(rendered_string)
+ pointers.append((len(data) + data_origin, len(serialized_string), len(serialized_string) // 16))
+ data += serialized_string
+
+ pointer_data = bytearray()
+
+ for pointer in pointers:
+ pointer_data += struct.pack(">HBB", pointer[0] & 0xffff, pointer[1], pointer[2])
+
+
+ return pointer_data + data
+
+
+
+
+
+
def generate_8x8_vwf_asset(string_list, prefix, table_start, max_tile_length=None):
k = 0
current_id = table_start
@@ -129,7 +196,7 @@ def generate_8x8_vwf_asset(string_list, prefix, table_start, max_tile_length=Non
for string in string_list:
if max_tile_length:
output.seek(k * line_length)
- data = build_text_image('fonts/8x8vwf.png', string.strip())
+ data = build_text_image('fonts/8x8vwf2.png', string.strip())
data_2bpp = write_as_2bpp(data)
output.write(data_2bpp)
length_table.write(struct.pack('