KOEI/삼국지 시리즈

삼국지5PK 일판에 한글을 출력시키고 말겠다. (2부)

K66Google 2022. 2. 16. 13:29

(1부에 이어서 계속...)

 

6. 뒷 바이트가 0xFE인 문자들이 ?로 뜨는 문제

지난 1부에서, 뒷 바이트가 0xFD, 0xFE로 끝나는 글자들이 게임 내에서 ?로 출력되는 문제가 있다고 말한 적이 있었다.

어셈블리 수정을 통해 0xFD 글자들은 출력시키는 데 성공했으나 0xFE 글자들은 여전히 출력이 ?로 뜨는 상태다.

 

?로 뜨는 이유는, 뒷 바이트가 0xFE에서 0x7E로 바뀌었기 때문이다.

여기서 나는 또 다른 꼼수가 생각났다. 뒷 바이트가 0x7E면 +0x80해서 무조건 0xFE로 만들면 어떨까?

왜냐하면 한국어 인코딩(CP949)은 애당초 뒷 바이트에 0x7E를 사용하지 않기 때문에, 0xB07E... 0xB17E... 이런 문자 코드 자체가 존재하지 않는다. 따라서 무조건 0xFE로 바꿔도 다른 글자와 충돌할 일은 없다.

그럼 TextOutA 함수에 문자가 들어가기 전에 바꿔주는 루틴을 집어넣도록 하자.

 

바꿔야 할 위치는 맨 처음에 반각 필터링 제거때문에 갔던 ADD ECX,0xE1 의 아래쪽이다.

빈 자리에 교체 함수를 만들고 원래 코드에서 교체 함수 자리로 호출하게 만든다.

 

0041ED94 : SAR ECX,1 ->  CALL 0041EDC1 로 변경.

(바꾸면 아래쪽의 0041ED96 코드도 없어짐)

0041EDC1 :   
SAR ECX,1 (0041ED94에 있었던 함수.)
MOVZX ECX,CL (0041ED96에 있었던 함수.)
CMP AL, 0x7E
JNZ 0041EDCC
ADD AL,0x80
RETN

 

이렇게 하면 뒷 바이트가 0x7E로 들어온 글자들도 다시 0xFE로 복구된다. 그러면...

 

마침내 신무장 이름 입력창에서 '층', '숭' 같은 뒷 바이트가 0xFE인 글자들도 정상적으로 출력되는 모습을 볼 수 있게 된다.

이제 Crystaltile2를 열고 실행파일 내부의 일본어 텍스트를 모두 한국어로 바꾼다.

 

 

7. 반각 가타카나의 전각 히라가나 강제변환 미해결 문제 

실행파일 한글화를 완료한 후, 메시지 파일 쪽의 한글 출력을 테스트해보기로 했다.

MESSAGE.s5 파일을 LS11 압축기로 압축을 푼 다음, 메시지 파일 013번의 '새로운 게임을 시작한다' 대사를 '한글' 이라고 바꾼다. 그리고 저장하고 압축기로 재압축해서 나온 MESSAGE.s5 파일을 게임 폴더에 넣는다.

 

그러면 '한글' 이라고 정상적으로 출력이...

 

아니... 왜 쪽쭈혻쯤이라고 나오는거야!!!

분명히 1부 맨 처음에서 수정을 했을텐데... 설마 아니었던 건가?

그렇다. 이것은 돈에이의 함정이었다. 내가 수정했던 코드(CMP AL, 0xA5)는 이와 무관했던 코드인 것이다.

 

참고로 '한글'이 '쪽쭈혻쯤' 으로 바뀌어서 출력되는 이유는 이렇다.

'한글'이 바이트가 쪼개지고, 쪼개진 바이트가 반각 가타카나 취급을 받고, 그래서 전각 히라가나로 강제변환이 되고, 강제변환 된 전각 히라가나에 임의의 값이 더해져서 최종적으로 '쪽쭈혻쯤'이 되는 이런 괴변환 현상이 벌어지는 것이다.

 

결국 나는 Ollydbg에서 또 삽질을 하기 시작했다.

이번에는 CMP ***, 0xA6 으로 검색하지 않고 CMP ***, 0xDF로 검색해본다.

왜 0xDF로 검색햐나면... 일본어 반각 가타카나는 0xA0~0xDF 영역을 쓰기 때문이다. 0xDF가 마지막으로 쓰는 영역이니 거기를 조사해보면 뭔가 실마리를 찾을 수 있을지도 모른다는 생각이 들어서였다.

 

검색해서 나온 코드가 속한 함수의 맨 꼭대기 부분(004761C0)에 브레이크 포인트(F2)를 걸고 게임을 실행해서 브레이크가 걸리는 지 확인하니까 브레이크가 걸렸다. 이 함수에 무언가가 있다.

F8을 눌러 한 단계 씩 내리다가 004761EA를 통과하니까 EAX가 82A0으로 바뀌었다. 82A0은 전각 히라가나 'あ' 자다. 바로 여기서 전각 히라가나로 강제변환하는 함수를 호출하고 있는 것이다.

그렇다면 이 함수를 호출하지 못하도록 조치할 필요가 있다. 그러나 이 함수 안에서는 여기를 뛰어넘을 수 있는 점프 구문(J...로 시작하는 어셈블리)이 없다.

 

하는 수 없이, 나는 함수의 맨 꼭대기(004761C0)에서 Find reference를 해보았다. 이걸 하면 이 함수를 누가 호출하고 있는지 확인할 수 있다.

결과값을 보니 00477B2C에서 호출하고 있었다. 위쪽에서 이 호출 부분을 뛰어넘을 수 있는 점프 구문이 있나 찾아보았다... 아! 위에 JA 구문을 JMP (무조건 점프) 로 바꾸면 여기를 무조건 뛰어넘을 수 있을 것이다. 이 호출을 뛰어넘으면 전각 히라가나 강제변환도 발생하지 않을테니까 '한글' 이라고 정상적으로 출력될 것이다... 이론적으로는.

 

* 결론

00477B19 : JA... 를 JMP... 로 변경.

 

어셈블리를 수정 후 저장하고 실행해본다. 결과는...?

 

마침내 메시지 파일 내의 '한글'도 전각 히라가나 강제변환 없이 정상적으로 출력되었다.

 

 

8. 영상 한글화

스팀판 삼국지5PK의 오프닝은 기존 윈도우판의 오프닝을 쓰지 않고 PC98 버전의 영상으로 바뀌었다. 동영상 해상도도 640x480으로 개선되었다. 기존 정발판 오프닝 영상으로 때우기에는 이 영상이 아깝기 때문에, 동영상 편집을 해야 할 필요가 있었다. (엔딩은 안 바뀌었으니까 그냥 정발판 엔딩으로 때워도 된다.)

 

* 편집 지점

처음에 뜨는 '歴史シミュレーションシゲーム' 로고.

- 'from its HISTORICAL SIMULATION SERIES' 으로 바꿔야 한다.

 

마지막에 뜨는 삼국지5 로고.

- 정발판은 밑에 '파워업키트' 로고까지 떴기 때문에 추가로 삽입해야 한다.

 

포토샵은 많이 써봐서 로고 따오는 건 어렵지 않았지만, 프리미어 프로는 많이 써 본적이 없어서 약간 시간이 좀 걸렸다.

(그냥 여니까 오디오가 무음으로 나와서 샤나인코더를 통해 mp4 파일로 인코딩한 다음에 불러와야만 했다.)

삼국지4PK 오프닝/엔딩 영상도 어차피 일본어 문장때문에 나중에는 편집해야 되니까, 하는 김에 한꺼번에 작업하기로 했다.

 

편집은 어떻게든 다 했고, 이제 동영상 인코딩 절차가 남았다. 그런데 코덱을 무엇으로 해야 하는가? 팟플레이어로 영상을 재생해보니 비디오 코덱은 wmv3, 오디오 코덱은 wma v2라고 되어있었다.

처음에 나는 이 영상을 프리미어 프로에서 H264 mp4로 인코딩하고, 샤나인코더로 wmv 파일로 재인코딩해서 게임에 집어넣었다. 삼국지5PK 쪽은 문제없이 재생되었다. 그러나 삼국지4PK 쪽에서 문제가 발생했다. 동영상이 중간까지만 재생되고 끊기는 것이다. 비디오 코덱을 wmv2 코덱으로 인코딩한 것이 문제였던 것이다.

무조건 wmv3으로 인코딩해야 한다는 건데, wmv3 코덱은 샤나인코더에서 인코딩할 수가 없었다. (wmv2까지만 있음) 그러면 어떻게 인코딩을 하란 말인가.

겨우 인터넷을 뒤져서 Windows Media Encoder 9를 어느 티스토리 블로그(링크)에서 찾아서 까니까 wmv3 코덱으로 영상을 인코딩할 수 있었다. 그런데 이렇게 하니 특정 위치에서 화면이 뭉개지는 현상이 발생하였다. 기존 파일과 용량을 비슷하게 맞추려고 비트레이트를 낮게 주니까 벌어지는 현상으로 추측된다.

 

처음에는 그냥 내버려두다가, 아무래도 안되겠다 싶어서 패치가 다 완성되고 공개 배포 며칠 전에 다시 프리미어 프로를 켰다. 혹시 내보내기 옵션에 wmv로도 내보낼 수 있는 지 확인해보기 위해서였다.

보니까... 있었다. 형식을 Windows Media로 하고 비디오 코덱을 Windows Media Video 9로 설정하니 wmv3 코덱으로 인코딩이 된다. 비트레이트도 WME9와 달리 자유롭게 설정할 수 있었다. 설정값은 다음과 같다.

 

형식 : Windows Media
비디오 코덱 : Windows Media Video 9 (wmv3)
640x480 29.97 종횡비 1.0
평균 비트 3000 / 최고 비트 8000

오디오 코덱 : Windows Media Audio 10 Professional
샘플 속도 : 44100Khz 또는 48000Khz (파일마다 다름)
비트 전송률 : 192Kbps

 

평균 비트를 3000까지 올려야 화면이 뭉개지는 현상이 일어나지 않는다.

이로 인해 원본 파일보다 용량이 2배 가까이 늘기는 했지만... 어쩔 수가 없다. 화질이 열화된 것보다는 나으니까.

 

 

9. 한글판 메시지 이식

이제 본격적인 메시지 한글화에 돌입한다.

삼국지5PK의 메시지 파일은 총 56개(000~055)로, 일판과 한글판 모두 개수가 같다. 하지만 한글판 파일을 그냥 통째로 교체하면 튕겨버리므로, 제어코드는 내버려두고 대사만 하나하나 복사+붙여넣기 신공을 해야 할 필요가 있다.

복붙 신공을 하기 위해 VB.Net으로 조악하게 메시지 에디터를 만들었다. 한국어나 일본어 한자만 문자로 출력시키고, 제어코드 같은 건 {00} 이런 식으로 나오게 하였다.

 

메시지 대사는 거의 대부분이 {01}" 로 시작하고 마지막은 전부가 {05}{05}{05} 로 끝난다.

중간에 {01}{xx}{yy}{00} 이런 식의 제어코드 집합이 있는데 문장의 뒤에 붙을 때는 장수의 말투와 치환되는 것으로 추측된다. [보십시오 / 보세요 / 보시게나] 뭐 이런 식으로...

문장의 앞에 붙을때는 이름이나 직위를 부르는 치환자로 추측된다. [**님, 주군] 이런 식으로...

아무튼 말투까지 신경쓰고 있을 시간이 없으므로 한글판 대사 파일에도 해당 제어코드 집합이 있으면 놔두고 없으면 밀어버리는 식으로 작업했다.

복붙 도중 오타가 있는 문장이 있으면 교정해서 붙여넣었다. (의외로 꽤 많이 있더라)

 

자작 에디터라 그런지, 일부 기호나 문자에 대해 바이트를 못 세는 문제가 있어서 그것도 고쳐가면서 계속 작업했다.

그렇게 조금씩 조금씩 일판 메시지 파일에 한글판 문장들을 넣고 있던 그때...

작업을 때려치는 걸 고민할 정도로 큰 문제에 직면하게 된다.

 

 

10. 메시지 파일의 용량 제한 문제

"장송이 몰래 그린같같같같같같"

메시지 018번 파일에 있는 보물 열전을 복붙하고, 제대로 나오나 한 번 들어가보았다.

그런데 세상에... 문장이 이딴 식으로 나오고 있다.

 

위치는 2A 62, 딱 저 지점부터 출력이 제대로 나오지 않기 시작한다.

'같' 자는 한국어 인코딩으로 0xB0B0... 2A 61의 값에는 B0이 들어가있다. 보아하니 2A 62를 읽지 못하고 2A 61의 값이 계속 반복해서 출력되는 것으로 보인다.

 

흠... 그럼 최대 바이트가 2A 61이 되도록 '말줄이기 신공' 이라도 해야 된다는 건가?

머리가 아찔해진다. 겨우 여기까지 왔는데... 다 때려치고 중단 선언이라도 해야 된단 말인가?

그럴 수는 없다. 뭔가 다른 방법은 없을까... 그렇게 생각하고 있으니 머릿속에서 어떤 단어가 어렴풋이 떠올랐다.

 

"런처 한글화"

 

런처 한글화? 들은 적은 있지만, 해 본적이 없어서 잘 모르는 분야다...

그런데 이걸 만들어야 한다고?

 

내가 제대로 기억하고 있다면, '런처 한글화'는 메모리에 올라온 문장이 게임 화면에 출력되기 전에 가로채서 바꿔치기하는 방식이었을 것이다. 그러면 프로그램은 영문도 모르고 바꿔치기된 문장을 게임 화면에 출력시킬 것이다.

뭐 아무튼, 일단은 메모리의 어느 주소에서 대사가 올라오는지 확인해봐야 한다.

위에서 추론한 '2A 62 부터 2A 61 값이 반복해서 출력된다' 라는 의심도 검증해 볼 필요가 있고 말이다.

치트엔진 상에는 한글이 지원이 안되서 바이트로 검색해야 했다. (Memory View - [Ctrl+F] - Byte로 검색 - 한국어 인코딩 기준의 바이트 입력 - 범위는 0부터 시작)

바이트를 검색해보니 0x4DB288에서 지금 화면에 출력되고 있는 문장이 나왔다. Crystaltile2 상에서 해당 바이트를 쳐보니까 맞아 떨어진다.

 

여기도 0x4DB288...

 

문제의 서촉지형도를 열어보니 확실히 2A 61 지점의 바이트가 반복되고 있다는 점을 확인할 수 있었다.

대략 254바이트를 잡아먹었다. 그럼 메모리에서 띄워줄 수 있는 대사의 최대 용량은 254바이트인가?

아무튼, 이 문장도 0x4DB288에 있다. 게임을 껐다가 켜도 똑같이 이 자리에 대사가 들어온다. 고정된 자리인가?

 

치트엔진 상에는 한글이 표시되지 않아서 좀 불편한 감이 있다. 그래서 나는 메모리 주소의 값을 읽어오는 소스를 구해서(출처) 0x4DB288의 값을 잘 가져오는 지 확인해보기로 했다.

ReadProcessMemory라는 함수를 통해 잘 가져오고 있는 게 확인되었다. 그렇다면 역으로 값을 우리가 메모리 주소에 집어넣을 수도 있지 않을까?

하지만 치트엔진 상에서는 아무리 값을 바꿔도 문장이 바뀌지 않았다. 이미 화면에 문장이 출력된 상태니까 바뀌지 않는 거겠지.

그러면 화면에 출력되기 전에 값을 바꿔치기 하면 어떻게 될까?

 

다시 Ollydbg로 실행파일을 열고 왼쪽 아래 바이트 창에서 0x4DB288로 이동한다. (Ctrl + G)

그런 다음, 4DB288의 바이트를 마우스 오른쪽 클릭하고 Breakpoint - memory, on write 눌러준다.

그리고 난 뒤 게임에서 메시지를 띄우니까... 00476085에서 멈춘다.

 

멈췄을때 F8을 눌러서 한 단계 진행하면 4DB288의 바이트 값이 변화한 것을 확인할 수 있었다. 바이트 값은 CL에 나와있고, 값을 넣을 메모리 주소는 EAX에 나와있다.

 

그럼 00476085가 속한 함수는 누가 호출하고 있는 것일까? 함수의 꼭대기(00476080)에서 Find reference를 해보니까 00477B38에서 부르고 있는 걸 알아냈다.

 

다른 바이트들에도 메모리 브레이크를 걸고 계속 조사해보니, 프로그램이 00477B62 지점에 도달해야 문장을 전부 읽은 것으로 판단이 된다. (다 안 읽었으면 JE 구문이 위로 올려버린다.)

 

시험 삼아 00476085에다 브레이크 포인트를 걸고 한 단계 진행 후 왼쪽 아래의 헥스코드를 수정하고 진행시켰더니 '새' 자를 '뼜' 자로 바꾸게 할 수도 있었다.

그럼, 이러한 작업을 어떻게 해야 프로그램이 자동으로 처리하게 할 수 있을까?

일단 좀 더 정보가 더 필요하다. 문장을 어디까지 읽을 것인지를 판단하는 메모리 주소를 찾아야 한다.

 

00476085의 주변에 나온 주소들을 뒤져보던 중... 004DA764에서 바이트를 +1씩 더하고 있는 모습이 포착되었다.

흠... 어쩌면 얘가 글자 바이트를 세고 있는 것일까? 의심스럽다.

그렇게 나는 Ollydbg에서 삽질을 또 시작했고, 마침내...

 

출력 문장의 시작 주소와, 출력 문장의 끝 주소, 바이트 카운트를 담당하는 메모리 주소를 찾아낼 수 있었다.

출력 문장 시작 주소는 항상 0x4DB288로 같았고, 끝 주소와 바이트 수는 문장의 길이에 따라 달라졌다.

 

00477B62 (문장 완성 시점) 에서 브레이크 포인트를 건 상태로, 출력 문장 끝 주소바이트 카운트 주소의 값을 바꿔주고 진행하니 문장도 이에 맞춰서 화면에 출력되었다.

이런 식으로 메모리에 들어온 문장을 바꿔서 화면에 출력시킬 수가 있다. 바로 이 방법이 메시지 파일 용량 제한 문제를 돌파할 수 있는 열쇠인 것이다.

...그런데 이걸 어떻게 해야 프로그램으로 구현이 가능하지? 눈 앞이 캄캄하기만 하다...

 

 

- 스크롤이 너무 길어져서 3부에서 계속 -