Initial commit: RE tooling and docs for The I of the Dragon localization/HD
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import os, re, struct
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
GAME = r"F:\steam\steamapps\common\The I of the Dragon"
|
||||
OUT = r"E:\DragonHD\patch"
|
||||
TBL = os.path.join(OUT, "tables")
|
||||
KEY = b"GBDFYTNE"
|
||||
os.makedirs(TBL, exist_ok=True)
|
||||
|
||||
CODE = 0xC1FA # GB2312 "龙"
|
||||
GLYPH = "龙"
|
||||
GW = GH = 16
|
||||
AX, AY = 122, 0 # free spot in Shrift_gb_Germany.TGA (256x128)
|
||||
ATLAS = "Shrift_gb_Germany.TGA"
|
||||
ADVANCE = 17
|
||||
HEIGHT = 16
|
||||
|
||||
# ---- 1. render glyph ----
|
||||
font = ImageFont.truetype(r"C:\Windows\Fonts\simhei.ttf", GH)
|
||||
g = Image.new("RGBA", (GW, GH), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(g)
|
||||
bb = d.textbbox((0, 0), GLYPH, font=font)
|
||||
d.text(((GW-(bb[2]-bb[0]))/2 - bb[0], (GH-(bb[3]-bb[1]))/2 - bb[1]), GLYPH, fill=(255, 255, 255, 255), font=font)
|
||||
gpx = g.load()
|
||||
|
||||
def tga_geom(raw):
|
||||
w = struct.unpack_from("<H", raw, 12)[0]
|
||||
h = struct.unpack_from("<H", raw, 14)[0]
|
||||
return w, h, raw[17]
|
||||
|
||||
def add_glyph(raw):
|
||||
w, h, desc = tga_geom(raw)
|
||||
off = 18 + raw[0]
|
||||
def row(y): return y if (desc & 0x20) else (h - 1 - y)
|
||||
for dy in range(GH):
|
||||
for dx in range(GW):
|
||||
a = gpx[dx, dy][3]
|
||||
if a == 0:
|
||||
continue
|
||||
i = off + (row(AY+dy) * w + (AX+dx)) * 4
|
||||
raw[i] = 255; raw[i+1] = 255; raw[i+2] = 255; raw[i+3] = a
|
||||
|
||||
# inject into loose + res
|
||||
loose = os.path.join(GAME, "Data", "Textures", "Ui", "Overhead", ATLAS)
|
||||
for p in [loose, os.path.join(GAME, "Data", "Textures", "Ui", "Overhead", "Shrift_gb.tga")]:
|
||||
if os.path.exists(p):
|
||||
raw = bytearray(open(p, "rb").read())
|
||||
add_glyph(raw)
|
||||
open(p, "wb").write(raw)
|
||||
print("injected loose", p)
|
||||
res = os.path.join(GAME, "Data", "Textures.res")
|
||||
data = bytearray(open(res, "rb").read())
|
||||
cnt = struct.unpack_from("<I", data, 0)[0]; p = 4; ent = {}
|
||||
for i in range(cnt):
|
||||
ln = struct.unpack_from("<I", data, p)[0]; p += 4
|
||||
nm = data[p:p+ln].decode("latin1"); p += ln
|
||||
o, s = struct.unpack_from("<II", data, p); p += 8
|
||||
ent[nm] = (o, s)
|
||||
for nm in ["UI\\Overhead\\Shrift_gb_Germany.TGA", "UI\\Overhead\\Shrift_gb.tga"]:
|
||||
if nm in ent:
|
||||
o, s = ent[nm]
|
||||
raw = bytearray(data[o:o+s])
|
||||
add_glyph(raw)
|
||||
data[o:o+s] = raw
|
||||
print("injected res", nm)
|
||||
open(res, "wb").write(data)
|
||||
|
||||
# ---- 2. cjk tables ----
|
||||
glyph_tbl = bytearray(65536 * 16)
|
||||
metric_tbl = bytearray(65536 * 8)
|
||||
u1 = AX / 256.0; v1 = AY / 128.0; u2 = (AX + GW) / 256.0; v2 = (AY + GH) / 128.0
|
||||
struct.pack_into("<4f", glyph_tbl, CODE * 16, u1, v1, u2, v2)
|
||||
struct.pack_into("<2i", metric_tbl, CODE * 8, ADVANCE, HEIGHT)
|
||||
open(os.path.join(TBL, "cjk_glyph.bin"), "wb").write(glyph_tbl)
|
||||
open(os.path.join(TBL, "cjk_metric.bin"), "wb").write(metric_tbl)
|
||||
print("tables written, code 0x%04X -> uv(%.4f,%.4f,%.4f,%.4f) advance=%d" % (CODE, u1, v1, u2, v2, ADVANCE))
|
||||
|
||||
# ---- 3. set test strings ----
|
||||
strp = os.path.join(GAME, "Data", "Misc", "Strings.dat")
|
||||
enc = open(strp, "rb").read()
|
||||
test = bytes([CODE >> 8, CODE & 0xFF])
|
||||
ids = ["MainMenu_StartNewGame", "MainMenu_LoadGame", "MainMenu_Options", "MainMenu_Credits",
|
||||
"MainMenu_Exit", "MainMenu_ResumeGame", "MainMenu_SaveLoadGame", "MainMenu_Multi",
|
||||
"WindowCaption"]
|
||||
done = []
|
||||
for sid in ids:
|
||||
pat = re.compile((r'(Id\s*=\s*"%s"\s*[\r\n]+\s*English\s*=\s*")[^"]*(")' % re.escape(sid)).encode())
|
||||
enc2 = pat.sub(lambda m: m.group(1) + test + m.group(2), enc, count=1)
|
||||
if enc2 != enc:
|
||||
done.append(sid)
|
||||
enc = enc2
|
||||
open(strp, "wb").write(enc)
|
||||
print("test strings set:", done)
|
||||
Reference in New Issue
Block a user