Skip to content

Instantly share code, notes, and snippets.

@scturtle
Last active December 12, 2025 08:09
Show Gist options
  • Select an option

  • Save scturtle/3ac8798853566c4954c7350d49fb0359 to your computer and use it in GitHub Desktop.

Select an option

Save scturtle/3ac8798853566c4954c7350d49fb0359 to your computer and use it in GitHub Desktop.
pal
from struct import pack, unpack_from
class MKFDecoder(object):
def __init__(self, path=None):
assert path or data
with open(path, "rb") as f:
self._content = memoryview(f.read())
self.count = unpack_from("<I", self._content, 0)[0] // 4
self.indexes = tuple(
unpack_from("<I", self._content, i << 2)[0] for i in range(self.count)
)
self.count -= 1
def __len__(self):
return self.count
def read(self, index):
data = self._content[self.indexes[index] : self.indexes[index + 1]]
return bytearray(data.tobytes())
class Palette(MKFDecoder):
def __init__(self):
super(Palette, self).__init__("Pat.mkf")
def get_palette(self, index=0, is_night=False):
data = self.read(index)
assert len(data) == 768 or len(data) == 1536 # with night
palette_data = bytearray()
for i in range(0, 768, 3):
r = data[(256 * 3 if is_night else 0) + i] << 2
g = data[(256 * 3 if is_night else 0) + i + 1] << 2
b = data[(256 * 3 if is_night else 0) + i + 2] << 2
palette_data.extend(pack("BBB", b, g, r))
return palette_data
pat = Palette()
class RLEDecoder(object):
def __init__(self, data):
self.data = data
@property
def width(self):
return unpack_from("H", self.data, 0)[0]
@property
def height(self):
return unpack_from("H", self.data, 2)[0]
def dump_tga(self, fn):
width = self.width
height = self.height
idx = 4
to_idx = 0
to_data = bytearray(width * height * 4) # BGRA
palette = pat.get_palette(0, is_night=False)
while idx < len(self.data) and to_idx < width * height:
code = unpack_from("B", self.data, idx)[0]
idx += 1
if code > 0x80: # transparent
count = code - 0x80
to_idx += count # skip
else:
count = code
for i in range(count):
color = self.data[idx]
dest_pos = to_idx * 4
to_data[to_idx * 4 + 0] = palette[color * 3]
to_data[to_idx * 4 + 1] = palette[color * 3 + 1]
to_data[to_idx * 4 + 2] = palette[color * 3 + 2]
to_data[to_idx * 4 + 3] = 255
idx += 1
to_idx += 1
header = pack("<BBBHHBHHHHBB", 0, 0, 2, 0, 0, 0, 0, 0, width, height, 32, 0x20)
with open(fn, "wb") as f:
f.write(header)
f.write(to_data)
class SubPlace(MKFDecoder):
def __init__(self, data):
(self.count,) = unpack_from("H", data, 0)
self._content = memoryview(data)
self.indexes = [
(x << 1 if x != 0x18444 >> 1 else 0x18444 & 0xFFFF)
if i < self.count
else len(data)
for i, x in enumerate(unpack_from("H" * (self.count + 1), self._content, 0))
]
if self.indexes[-2] <= self.indexes[-3]:
self.indexes = self.indexes[:-2] + self.indexes[-1:]
self.count -= 1
def __getitem__(self, index):
return RLEDecoder(self.read(index))
class Data(MKFDecoder):
def __init__(self):
super(Data, self).__init__("Data.mkf")
if __name__ == "__main__":
sub = SubPlace(Data().read(9))
for i in range(len(sub)):
sub[i].dump_tga(f"{i}.tga")
import os
import struct
class PalMessages:
def __init__(self, pal_path):
self.pal_path = pal_path
mkf_path = os.path.join(self.pal_path, 'Sss.mkf')
self._index_data = self._read_mkf_subfile(mkf_path, 3)
msg_path = os.path.join(self.pal_path, 'M.msg')
self._message_data = open(msg_path, 'rb').read()
word_path = os.path.join(self.pal_path, 'Word.dat')
self._words_data = open(word_path, 'rb').read()
def _read_mkf_subfile(self, mkf_file, sub_id):
with open(mkf_file, 'rb') as f:
f.seek(0)
f.seek(sub_id * 4)
offset_bytes = f.read(8)
start_offset = struct.unpack('<I', offset_bytes[0:4])[0]
end_offset = struct.unpack('<I', offset_bytes[4:8])[0]
length = end_offset - start_offset
f.seek(start_offset)
return f.read(length)
def get_text(self, index):
pos = index * 4
start_pos = struct.unpack('<I', self._index_data[pos:pos+4])[0]
end_pos = struct.unpack('<I', self._index_data[pos+4:pos+8])[0]
length = end_pos - start_pos
raw_msg = self._message_data[start_pos:end_pos]
return raw_msg.decode('gbk', errors='replace')
def get_word(self, index):
record_size = 10
pos = index * record_size
raw_word = self._words_data[pos : pos + record_size]
return raw_word.decode('gbk')
def __getitem__(self, index):
return self.get_text(index)
if __name__ == "__main__":
pal_msg = PalMessages(pal_path=".")
for i in range(13513):
print(f"Text[{i}]: {repr(pal_msg[i])}")
for i in range(565):
print(f"Word[{i}]: {repr(pal_msg.get_word(i))}")
sudo apt install libpulse-dev libegl-dev libxkbcommon-dev libwayland-dev
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment