소프트웨어 지식으로 할 수 있는 일들/한국어 패치 개발

뱀파이어 더 마스커레이드: 블러드라인(VtMB) 1 번역 및 국문출력 패치 #2

souldeveloper 2025. 12. 21. 23:05

lip 파일 출력 이슈 해결에 드디어 성공했다.

UTF-16LE 강제 로직을 차단하고, 미번역문 또는 원문이 필요한 경우가 아닌 한 PHRASE unicode 절을 쓰지 않고 PHRASE char 절을 사용하여 CP949 디코딩 출력으로 자막변환부 서브루틴을 강제하는 것을 목적으로 하였다. 이로써 '占쏙옙'으로 가득한 lip 자막 출력에서 벗어날 수 있었다.

 

그러나 인젝션 패치에 성공한 뒤로도 계속 깨짐이 발생했는데, 특히 주목할 수 있었던 것은 한 실험에서였다.

대개의 깨짐문자는 식별할 수도 없는 글자나 "?" 같은 깨짐문자로 fallback되어 버리므로 단서를 찾지 못하고 있었는데, 밑져야 본전이라고 치고 적어도 1글자가 1덩어리의 '占쏙옙'으로 출력되는 이슈에서는 벗어났으니, 깨짐에도 일정한 입력-출력 간의 관계가 생겨 있을 것이라고 가설을 세웠다. 그런 다음, CP949 모드로 '가가 가가가가'를 입력했다. (b0a1b0a1 b0a1b0a1b0a1b0a1)

그러자, 媛媛 媛媛媛媛 가 출력되는 것을 발견하였다.

이것은 중요한 함의가 있었다. 깨짐문자가 아니면서 원래의 문자열과 같은 규칙성을 지니는 형태로 출력되었으므로, 저 녀석이 출력되기 위해 어떻게 인코딩된 바이트가 입력된 것일지 추론해 볼 수 있게 되었기 때문이다. 미녀 원媛 자이므로... 거기서, 혹시 엉뚱하게 유니코드 식별자 이슈나, 또는 Windows 10/11의 베타버전 기능으로 시스템 기본 문자열을 UTF8로 처리하는 경우의 fallback이 작용하여 강제로 문자열을 UTF8화한 것이 아닐까 하는 데에 생각이 미쳤다.

'가가'는 UTF8로는 eab080eab0800a이 된다. 0x80은 CP949에서는 깨진 글자이니 분철되거나 ?가 되거나 탈락해 버렸으리라고 생각해 보면, 실제 lip의 강제 CP949 출력부가 디코딩하려고 하는 바이트는 eab0 eab0 만 남았으리라고 짐작해 볼 수 있다.

...그리고, EUC-KR(CP949) 인코딩에서, eab0라는 바이트가 바로 媛자이다!

 

순서대로 정리하면,

b0a1b0a1(lip 파일에 기재) ☜ 가가(EUC-KR; CP949) 문자열 출력을 의도함

(CP949로 디코딩) -> '가가(CP949)'

*** (문자인코딩 변환) -> 가가(UTF8) ☜ 시스템 ACP가 UTF-8로 잡히게 하는 Windows 10/11 베타 기능이나 또는 기타 사유로 발생함

(다시 바이트로 인코딩) -> eab0eab0

(출력을 위해 CP949로 최종 디코딩) -> 媛媛

 

이러한 현상으로 깨짐이 발생하고 있었던 것이다.

따라서, lip의 PHRASE가 unicode 대신 char 옵션으로 들어올 때, 자막 변환부 코드페이지가 0x00 (=시스템 ACP)를 쓰는 것을 막고 강제로 코드페이지 949(=CP949)를 쓰도록 하는 코드(PUSH 949)를 engine.dll의 훅 영역 오프셋에 주입해 주어야 하는 것이었다.

PHRASE 출력에 골머리를 썩게 만들던 lip 파일의 출력 최초 성공

 

다소간의 씨름 끝에, 마침내 UTF8 폴백 영역의 hook을 차단하고, PHRASE char 키워드 발견 시 CP949 출력을 강제하도록 하였다. 자막의 변환블록 진입부 오프셋 위치에서 시스템 ACP가 65001(즉, UTF8)로 잡혀 있는 경우에 관계 없이 CP949를 강제할 수 있어야 했다. 따라서 hook의 오프셋인 0x001BBE30~ 0x001BBE40 블록 영역에 PUSH 949 코드를 주입하였다. 그 결과, 위와 같이 더 이상 '媛媛 媛媛媛媛 媛 媛媛媛媛.'이 아니라, 원래의 '가가 가가가가 가 가가가가.'가 디코딩되어 출력되는 것을 확인할 수 있었다.

OPTIONS의 speaker_name도, PHRASE char 절도 모두 CP949 출력이 원활해졌다.

 

이제는 그러면 원래의 의도대로 lip을 처리하고, 앞으로는 번역을 거친 lip파일 내의 PHRASE가 unicode 대신 char 옵션으로 처리되게 편집한 뒤 인코딩을 CP949로 지정하여 저장하고 사용하도록 하면 되는 것이다.

 

제대로 된 번역문(오프닝에서 '사이어'가 주인공을 흡혈하여 포옹Embrace해 버리기 직전의 순간 나오는 자막이다)을 하나 시험삼아 잡아서 적용해 보았다. 구체적으로는, lip 파일 자막의 재생과 표정/동작데이터에 대한 결손 없이 PHRASE에 대해, unicode가 아닌 char 옵션을 주고 삽입한 CP949 인코딩 한글 문자열 바이트스트림의 원활한 디코딩과 출력에 성공하였다.

 

이로써 VtMB 1의 모든 한글 출력 이슈 해결을 완료하였다.

남은 것은 원클릭 패처로서 구성하기 위한, 덮어쓰기할 번역결과물 데이터 및 scheme 데이터, 그리고 일련의 패치와 dll injector의 실행파일 패키징이다. 이 블로그 및 기술유투브 채널 링크를 넣은 실행파일 세트를 빌드해 주면 될 것이다.

 

아, 그리고 물론 지난한 번역 과정 그 자체도 남아 있다. 처음 이 게임을 접했을 때에는 공부하는 셈치고 조악하게 번역했었지만... 나이가 들고, 다시 번역하면서 문장력을 발휘해 보노라니, 즐거우면서도 까마득한 기분이다.