冒险解谜游戏中文网 ChinaAVG

标题: 【汉化资料】从花瓣区位看破解CreateFontA区位 [打印本页]

作者: shane007    时间: 2008-12-23 13:45
标题: 【汉化资料】从花瓣区位看破解CreateFontA区位
首先,分析游戏的文字显示方式。玩游戏的时候最怕打开游戏看到一片乱码,现在汉化游戏却是最希望打开游戏看到一片乱码,因为一般在非日文环境下能正常显示日文的游戏都使用的字符集限定,为汉化增加了不少的难度……
; D3 V6 V' G  w% C# P# @一起采取Textout方式的年代,日文、繁体中文十个有九个是乱码,于是有了Apploc和NL,后来比尔大叔推出了CreateFontA函数……CreateFontA能够直接定义程序使用和操作系统不同的字符集,于是乱码没有了,而汉化者噩梦开始了,因为汉化后的中文内容在经过日文字符集的编译后变为日语状态下的乱码。 2 T. R( K! E' [& y% M
以下是比尔大叔的MSDN Library对CreateFontA给出的函数原形 ' w! f& p1 ?: r8 F5 F

2 b3 Y, F- j8 k$ G! K: rHFONT CreateFont( ; E6 h- \/ w7 J5 v* C% i1 Q
int nHeight, // height of font
0 O- k6 N) Q$ `' a* c* Tint nWidth, // average character width 9 `7 e' R. h; _! i5 S4 k( @
int nEscapement, // angle of escapement ' n5 q, j8 f: G8 E
int nOrientation, // base-line orientation angle
; E# a2 M' g8 G' D6 L0 A7 fint fnWeight, // font weight
7 U- J% H: V! q0 S" @: LDWORD fdwItalic, // italic attribute option # |" O* X; f& t9 r  o! W
DWORD fdwUnderline, // underline attribute option
& b/ g7 a  ?+ u8 _/ b6 HDWORD fdwStrikeOut, // strikeout attribute option : p" M* f/ S/ K$ U
DWORD fdwCharSet, // character set identifier 7 f) ~2 C" u% u7 _
DWORD fdwOutputPrecision, // output precision
" m0 s/ w& {% [DWORD fdwClipPrecision, // clipping precision 9 u7 ?& R% k% e5 J3 R5 C
DWORD fdwQuality, // output quality
8 V/ o8 N1 ?- {) L% p8 YDWORD fdwPitchAndFamily, // pitch and family ) w$ j8 \! ]9 u5 x2 P
LPCTSTR lpszFace // typeface name
. ?# T8 F7 V+ U2 c# W) _); , C" D$ A  t4 \# ]4 d

- Q# \' Z* x7 F5 I. F. Y/ Y! THFONT CreateFontIndirect( 6 Z0 N+ t6 K: q% J7 c% u( q
CONST LOGFONT* lplf // characteristics 2 x7 U4 u" r7 t- B( h* W* L
);
. g3 O% ~$ J4 p$ S其中 LOGFONT的声明如下:
& T, y: D4 |" I. F' M
4 }0 f& t  w5 T4 Stypedef struct tagLOGFONT { 1 u4 ?, ]1 i8 e, Y& @5 C( {/ V6 X5 I
LONG lfHeight; . Y5 o+ l' X( b/ w9 S6 U) e
LONG lfWidth;
! L: O$ c! H! c9 iLONG lfEscapement; ) Q( l! i) r3 L; u' q" g' s+ t& A: o
LONG lfOrientation; 1 T! z& W) \9 c$ C
LONG lfWeight;
& m3 s9 w! T9 \, ?2 l, uBYTE lfItalic;
+ a' ^. U0 f5 @  ^* g+ R& zBYTE lfUnderline; 9 y8 M1 X5 X* i/ k
BYTE lfStrikeOut;
; V0 }9 h& F9 e) t; IBYTE lfCharSet; 0 ]; [& J* [. Z2 `8 t+ \/ G
BYTE lfOutPrecision; ( Q) T$ Q: u1 B2 z' r
BYTE lfClipPrecision;
' k" O$ {/ }$ V, o8 H% q, QBYTE lfQuality; $ x: B( P2 {$ l$ ~/ p# ~. O$ s
BYTE lfPitchAndFamily;
) s9 m$ w& j6 b; P" X! D5 x8 j3 ?" STCHAR lfFaceName[LF_FACESIZE]; - W- r; |1 l' S' h/ t  P, n
} LOGFONT, *PLOGFONT;
# N. v) T9 ]* r6 R1 K6 n$ O==================分割线================= * P, n: l* ^6 G9 r+ z8 y
要改变程序支持的字符集,就要改变程序调用上面两个函数时的fdwCharSet或lfCharSet的值
0 _: N5 C2 m" N3 w; o: V& [; R其中各字符集所对应的值如下: * Z8 l, T1 `/ Q. b* K
+ x+ K' {  ~" z: L3 [- V( b
字符集 值(十进制)
% S) q) P3 U2 `9 wANSI_CHARSET 0
! p  {" V4 P* C2 h- S9 M  j9 e% vDEFAULT_CHARSET 1
1 \2 i0 P% @" ?1 w4 X/ b) E# D7 |7 K8 wSYMBOL_CHARSET 2
2 W0 e; m4 k9 C  fMAC_CHARSET 77
/ G# m% a1 o0 g' l7 N3 rSHIFTJI_CHARSET 128 - G9 G$ E1 h$ W; ]' _9 m
HANGEUL_CHARSET 129
( J3 ^4 H4 S' l  |# ~% w& ?HANGUL_CHARSET 129 0 j) X9 V0 A2 ?
JOHAB_CHARSET 130
) c0 m2 w2 C  nGB2312_CHARSET 134
/ ?% S0 X. K) S( g, y* DCHINESEBIG5_CHARSET 136 6 y8 s0 R, [; |& N, Y- L5 R9 [6 s
GREEK_CHARSET 161 & z: b- N- A0 \- a
TURKISH_CHARSET 162
. c0 R1 `! V  U7 t% {VIETNAMESE_CHARSET 163 : k$ d; }* B8 H+ m7 y
HEBREW_CHARSET 177 , K! q/ \3 J1 u/ h0 j/ U) c8 ?
ARABIC_CHARSET 178
  N6 v) P0 M1 n1 e2 B8 nBALTIC_CHARSET 186
' C  P. [6 u! V: O7 tRUSSIAN_CHARSET 204 ! y  g. O# c2 U# r6 l
THAI_CHARSET 222 4 N3 u5 y! c; }% x
EASTEUROPE_CHARSET 238
2 x# j. f3 N( o: ~: v0 Z7 x1 fOEM_CHARSET 255
' {/ \0 b) |( ]: x可以看到简体中文是86(Hex),日语是80(Hex),繁体中文是88(Hex) * {# S& A) f# D0 h: H9 ]$ B
我们要做的就是找到游戏中定义调用字符集的部位,将80改为86,这样就能让程序正确的显示中文。
' A( P- k2 c3 e2 J2 A! X& |用pexplorer打开HANABIRA.exe,进入反编汇模式,查找Font字符串,我们发现调用CreateFontA函数的位置是唯一的:
( H% B7 d. Y# N5 N& B8 G+ W( U% \于是在以下代码我们停下来 : ^- g, E: [7 w* Y8 m
L0041E365:
( E9 X3 |3 S: b* p, Z# Cmov edi,[esp+14h]
- Z* L% }- A4 n  H- Z) D" o) Lmov ebx,[esp+28h]
# Y. i0 A. p9 H4 p3 @mov ebp,[esp+24h] 2 d) m. J" F) F4 H1 q
mov eax,[esp+20h] ; v' p7 m! P+ V  b+ ^7 z
mov ecx,[esp+1Ch] 7 ?7 e8 M, W9 m7 D, k5 F7 c( O
push edi - @. p1 O( A) S5 ]4 `
mov edx,[esp+30h] " H2 d$ A4 O' ?7 u# F$ H1 h, x
push 00000031h , z- l1 f' L  {. I+ Y" D" n* Y
push 00000002h
9 F8 t: C# D. A. ~0 j  L# Vpush 00000000h
9 p) A1 G# F& D: M8 x# spush 00000000h
$ w+ V# e+ d; T: cpush 00000080h   M, D  V% S# c9 b" i: {
push ebx
  b8 y/ m& z5 [push ebp
5 @* ?( a& s" y9 N, G$ q+ Z6 cpush eax ! e2 l/ n3 U* z$ x' x: y
mov eax,[esp+3Ch] " X! O7 ?* v, `# e. D: k7 o" E
push ecx
5 N' ~: [1 s, N# k4 d* R+ v; Apush 00000000h
& N" z9 v6 ]0 @1 [' ?push edx
: z2 C6 k7 Q' [$ lpush 00000000h 4 R8 n' k" H0 K1 ?& i) _: ^
push eax $ t* {5 y; x+ G* m5 g* H8 I. S  z4 d
call [GDI32.dll!CreateFontA]
9 U5 v- n2 v: @2 i% ?7 plea ecx,[esi+28h] : _4 q1 `) j9 |4 N5 h
mov [esi+00000138h],eax * t' F6 u9 _9 t8 z
mov eax,edi 6 r$ N/ [% s+ D2 E% T4 j! g. F! R
sub ecx,edi
, y- y% V6 h* o/ ]  j5 T) Wlea esp,[esp+00h] ) ^" A( K' Q( D8 c
注意这就是调用GDI32.dll中的CreateFontA函数了,我们在这个堆栈中寻找将80这个值传递给CreateFontA的部位。
# `. O3 m0 r! w7 Mpush 00000080h , T3 Z% z, R' X  Q; Z8 L& A
就是这里将80值压入传递中 ! P' a; X/ H) Q, U: a" B
PE中标记了这段赋值的Hex数值,用UE打开文件,找到
% K7 k( r: f: a68800005355   H3 I) B! p. o' F% C$ @+ `
将其改为 8 d2 J5 H( F7 h
68860005355
- K2 q# J& E  u4 p1 s% L保存之。
! B5 ~, n4 {7 M5 W这样PE中看到这段压入就成了
5 T" N# m1 Z3 {2 ?- L0 \push 00000086h % O% e# v/ u% x3 @' d
初战告破,运行游戏,你会看到日文全变成了乱码,说明程序已经在使用中文的字符集了
/ l$ f3 C. M% [4 S修改游戏脚本,加入几个中文看看……
  P4 y3 M9 `6 H/ g* }, ?为什么我添加的中文全部是“□”?
' `) K& ]6 \/ O/ |( g  R: a7 _这就是需要解决的第二关卡,字符集边界检查。
, [& L7 J6 X5 m% z" i
* d5 g2 L- V% ?; N既然已经设定了字符集,为什么还要边界检查呢?这是为了防止当游戏文本中含有某些非法的字符串时产生缓存溢出。于是在字形传递到GDI32.dll描绘字体准备显示在屏幕之前对其进行检查,发现超出了设定的缓存大小就将其拦截下来,于是屏幕上就显示出一个“□”。我们知道,由于日文的字符比中文少得多,所以这个缓存也小的多,换言之就是边界太窄。 1 Z: ^: K1 G+ P! C! U
边界检查的例子: ( `; @7 V- C4 Q9 w' g" V
cmp al,80 . O: s1 A# U' m
jbe xxxxxxxx + x; b1 N+ F# ^# k3 j
cmp al,09F
, [/ a+ \9 {$ `0 z- T7 Ljb xxxxxxxx ' @0 y6 t* u# _' r  |5 \  a8 \4 R
cmp al,0E0 , y! B3 V" w7 w* l7 j2 G3 G8 ?
jb xxxxxxxx
" ^, p/ _# G8 dcmp al,0FC
0 ]) l7 a+ e5 q6 F8 i& \ja xxxxxxxx
+ ?$ z2 y; n4 `; P1 O
6 K2 u; ^% c& m& [& P即看字符是否在80-9F(前两位)和e0-fc(后两位)之间 * x) y& l- u6 \" I/ m
===================================
  F/ X4 Q1 r) l3 S如何具体查找程序的字符集边界呢?常用的方法是下断,用OllyDbg载入游戏主程序运行,一步一步断下去,在出现一堆“□”的时候停住,然后转到ASM模式查看停在哪里。
8 \# c  y( |$ ~L0043BA00: - O& J! M: [. A5 M& l
cmp al,80h # |9 d- V" z$ V4 @# ~
jc L0043BA08
: ?1 G  L) I1 U' B9 K4 r3 ]% pcmp al,9Fh ! P/ \% G$ H$ t4 m* v: u
jbe L0043BA10
: ^$ Y! L7 E6 M2 N, O" AL0043BA08:
+ n3 q6 O: e% x- ecmp al,E0h
; R* ^& I5 c4 o( T4 [3 L$ R. i7 Cjc L0043BA30 % f$ v5 g5 h: {# s
cmp al,FFh
/ [" G" S! h+ d3 R' hja L0043BA30 * X5 R8 ~! u4 g0 D/ x
花瓣的上边界为80~FF,而下边界为E0~FF,好,开始动手 / F6 m8 L8 a% |9 b% p# `/ S8 E, \  n
这里我们将上边界改为80~FF,而下边界范围足够宽广,就不用改了。 ! |( w& y: K  G% |
这里为什么使用80~FF而不改为20~FF呢?因为我们需要让游戏文本中原本的日语空格(8140)不显示为乱码,于是8140刚好在边界外,就不会被送去CreateFontA进行显示,就会显示为日语的缺字码,一个空白——而它刚好就是空白,在显示上两者没有任何区别。 0 s& x# W& v$ w  D+ @2 P& p& y
如法炮制,打开EU修改之,保存测试,OK,正常显示了
7 F+ m% k$ P8 @. F7 D: a4 _, _
/ s( C4 D: b  _原文
8 z- |# i" h4 y( rhttp://blog.potatoneko.cn/
作者: zhang336970    时间: 2008-12-24 16:59
学习!!' U* I! |* ?1 S! I* ]+ X: P; x

& C, m" S& X: o& k/ u$ xSyberia2的汉化原理估计和这段代码类似吧。
作者: shane007    时间: 2008-12-24 19:59
引用第1楼zhang336970于2008-12-24 16:59发表的  :/ {# g- L7 b2 G: T1 C$ G
学习!!
/ O( C/ @. j: W% Y; |$ l2 A# L# w0 U1 Q$ N
/ n6 m& m0 Y, iSyberia2的汉化原理估计和这段代码类似吧。
5 f, e3 r8 c" D0 P1 L
不错,的确从中受到不少启发。
作者: zhang336970    时间: 2008-12-26 01:07
'' 我尝试制作一个整合版,就是在英文版的基础上加入日文版的exe,dll。结果发现无法进入游戏画面,看来还是要找到完整的日文正式版才行。''. t" L* C6 z0 g8 V, u( k
2 U$ h# g5 h1 L) D' W* Y
你没有试下用E版的改代码行不行?把E版的字符集和边界限制都改下,这样做后续问题会少。, s; ?8 P1 t' p+ J3 x5 z
总感觉缝缝补补的会出问题。
作者: shane007    时间: 2008-12-26 06:34
引用第3楼zhang336970于2008-12-26 01:07发表的 :
2 o4 D, Y& i3 C; G'' 我尝试制作一个整合版,就是在英文版的基础上加入日文版的exe,dll。结果发现无法进入游戏画面,看来还是要找到完整的日文正式版才行。'' 4 X+ O% j6 U/ {/ G. ~
- Z5 h% H; ^6 R+ B
你没有试下用E版的改代码行不行?把E版的字符集和边界限制都改下,这样做后续问题会少。 6 z9 @; J! v7 q0 A# X6 l
总感觉缝缝补补的会出问题。

$ G' i/ ^" F6 K9 K$ Z& b% n
% R5 v4 ?% p% a我也是这样想的。但是难度极大,寻找和修改都非常困难,E版的边界检查没有日文的边界检查那么明显。还要加代码修改双字节的问题。以我现在的功力,短期内估计不会有进展。




欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/) Powered by Discuz! X3.2