首先想提醒大家的是,根據現行法律,未經著作權人等許可的游戲漢化是一種侵權行為,我曾經聽說過一種說法,即只要不以營利為目的進行漢化則沒問題,但我認為這是個有問題的說法。當然自我學習之用則可以。本心得以psv文字游戲《可塑性記憶》為例,向大家展示我漢化的流程。以下內容僅為個人觀點,若有欠妥之處我會及時修改。下面是干貨。
漢化總步驟:
|---- 解包
| (然后分析解包出來的文件,你至少得搞清楚要漢化的東西都在哪些文件里吧)
| |--- 提取文本
| |---文本漢化- | 翻譯
| | |---替換文本
|---- |
| | |---P圖
| |---圖片漢化-|
| |---保存(是的,這一步非常重要而且讓人掉頭發)
|
|---- 制作字庫
|
|
|---- 視頻漢化(如果你想的話)
|
|---- 打包
- 首先准備一個mai版的游戲文件,一般是壓縮包,解壓后游戲文件盡收眼底。
接着初步分析目錄:
- mai_moe文件夾:不用管,是安裝mai用的
- movie文件夾:無需多說,該游戲內的一些視頻
- sce_module文件夾:不知道干什么用的,里面文件后綴為.suprx,是psv的系統文件
- sce_sys文件夾:里面有些圖片,隨便看看你就知道是什么了,psv標志的貼紙和獎杯等
下面的文件都是未知格式xxx_body.bin和xxx_info.psb.bin等,而且是成組的,可以看到有幾個占了很大內存,猜測游戲本體就是由這些文件組成。
注:解包這些未知格式文件就是最棘手的問題了,你可能需要大量時間在網上尋找是否有前輩提供解包工具,或者自己造輪子,但是我想大部分人是沒有能力自己造的,所以只能碰運氣去找,找到了就有漢化的可能,找不到就只能放棄。
這次運氣好,在github上找到了某大神的一個工具FreeMote,貌似該工具的初衷並非用於漢化,但客觀上確實能解包該游戲的重要文件,其詳細信息在此不做贅述。(請正確上網,不FQ,github無需FQ)
2. 用FreeMote解包xxx_body.bin和xxx_info.psb.bin文件(解包方法略)
注:需要說明的是解包這類文件需要一個key,若想獲得該游戲的key需要在未解包時的bin文件源碼中找,總之挺麻煩。
以font_body.bin和font_info.psb.bin為例:解出來一個文件夾和兩個json文件
文件夾內也是json文件。先在psv上打開游戲看一下游戲第一句話,記下來。用notepad隨便打開一個json文件,search一下全文件(指定搜索路徑可以提高效率)是否存在第一句話,有的話則說明文本已經解出來了。接下來只要找到文本文件即可。
雖可以直接在源文件上翻譯文本,但效率過低,如能提取出來做成excel就可以批量翻譯了,我選擇python解決此問題。對於翻譯這種小項目,無需使用面向對象的方式編程,簡單的函數足以應付:
1. 提取文本小程序
1 import json #用於解析json文件 2 import csv #用於保存csv文件 3 4 def savefile(filename,words,name,nikname): 5 path2 = r"C:\Users\xxxxxx\scenariotest\get_word\{0}".format(filename) 6 with open(path2,'a+',encoding='utf-8') as csvfile: 7 writer = csv.writer(csvfile) 8 writer.writerow([name,nikname,words]) 9 csvfile.close() #該函數用於寫入csv文件並保存 10 11 def get_word(path): 12 file = path.split("\\")[-1] 13 change = file.split(".")[0] 14 filename = change + ".csv" 15 fp = open(path,encoding='utf-8') #注意解碼用utf-8 16 json_data = fp.read() #讀取json文件 17 data = json.loads(json_data) 18 data01 = data["scenes"] #定位到一級標簽 19 for m in data01: 20 if ('texts' in m.keys()): #定位到text標簽 21 print("loading.......................") 22 for i in m['texts']: 23 words = i[2] #獲取文本 24 name = i[0] 25 nikname = i[1] 26 if name == 'NULL': 27 name = ' ' 28 if nikname == 'NULL': 29 nikname = ' ' 30 savefile(filename,words,name,nikname) 31 else: 32 print("NULL") 33 continue 34 35 if __name__ == '__main__': 36 file_path = r"C:\Users\xxxxxx\pm1-01.txt.scn.m.json" # 改文件名 37 get_word(file_path)
思路:觀察得知每個文件中“text”標簽后即一堆數組,文本就是數組中的一個元素,用json包直接定位到文本即可,然后保存為csv格式。
使用方法:改一下程序中要提取的文件名后運行
運行結果:以示例文件名為例,生成一個文件:pm1-01.csv,用excel打開即可
2. 翻譯小程序
雖可逐句手動翻譯,但奈何我日語5級都難過,不如先讓度娘翻譯一遍,再修修補補即可:
1 import requests 2 import hashlib 3 import json 4 import csv 5 import time 6 # python3.0 已經取消了MD5,需要使用hashlib 7 # 該程序最后輸出的文件為xxxfanyi.csv 文件只包含翻譯結果,每隔一句空一行 8 9 # 獲取csv中文本 10 def getword(path): 11 csvfile = csv.reader(open(path,'r',encoding='utf-8')) 12 words = [] 13 i = 1 14 for line in csvfile: 15 if i%2 == 1: 16 words.append(line[2]) 17 else: 18 pass 19 i = i + 1 20 return words 21 22 # 翻譯函數,具體使用方法在api申請頁面有教程 23 def fanyi(str): 24 q = str # 句子 25 ifrom = 'jp' # 翻譯源:日語 26 to = 'zh' # 翻譯到:中文 27 appid = 'xxxxxxxx' # id號 28 key = 'xxxxxxxx' # 密鑰 29 salt = 'xxxxxxxx' # salt隨便取一個 30 # appid+q+salt+密鑰 的MD5值 31 sign_1 = appid + q + salt + key 32 sign = hashlib.md5(sign_1.encode(encoding='utf-8')).hexdigest() 33 # 產生url 34 url = 'https://fanyi-api.baidu.com/api/trans/vip/translate?q={0}&from={1}&to={2}&appid={3}&salt={4}&sign={5}'.format( 35 q, ifrom, to, appid, salt, sign) 36 try: 37 res = requests.get(url=url) 38 words = json.loads(res.text) 39 words = words['trans_result'] 40 words = words[0] 41 words = words['dst'] 42 return words 43 except BaseException as e: 44 print(e) 45 46 # 最后輸出 47 def pack(list01): 48 path = r'C:\Users\xxxxxxxx\pm1-01fanyi.csv' # 改文件名,這是最終生成的文件名 49 f = open(path,'w+',encoding='utf-8') 50 writer = csv.writer(f) 51 for n in list01: 52 writer.writerow([n]) 53 54 if __name__ == '__main__': 55 # 獲取word 56 path = r'C:\Users\xxxxxx\pm1-01.csv' # 也改一下文件名,這是之前生成的那個csv文件 57 list01 = [] 58 m = getword(path) 59 num = 0 # 計數用於調試 60 for j in m: 61 print('waiting.......') 62 a = fanyi(j) 63 list01.append(a) 64 time.sleep(1.5) 65 num = num+1 66 pack(list01)
思路:百度翻譯有個api接口,自己申請一個就可以用了,實名后貌似可以每秒翻譯10句,但網速實在不給力,而且我也懶得做錯誤處理了,就寫成個半成品。直接利用之前生成的csv翻譯再生成一個翻譯版csv,就這么簡單。
使用方法:在程序中改需要翻譯的文件名后運行
運行結果:以示例文件名為例,生成一個文件:pm1-01fanyi.csv,用excel打開即可
3. 導入小程序
1 import csv 2 import json 3 # 本文件需要03.csv和03mid.csv兩個文件 4 # 生成文件即03.csv,需要翻譯名字 5 6 def csv_read_word(path): 7 strword01 = [] 8 flag = 1 9 file = csv.reader(open(path, 'r', encoding='gbk')) 10 for line in file: 11 if flag%2 == 1: 12 # print(line) 13 strword01.append(line[0]) 14 flag = flag + 1 15 return strword01 16 17 def csv_read_name(path,h): 18 strword02 = [] 19 flag = 1 20 flag2 = 0 21 file = csv.reader(open(path, 'r', encoding='utf-8')) 22 for line in file: 23 if flag%2 == 1: 24 strword02.append(line[h]) 25 flag = flag + 1 26 return strword02 27 28 def recsv(word,name,nikname,csv_path): 29 with open(csv_path,'w+',encoding='utf-8') as csvfile: 30 writer = csv.writer(csvfile) 31 for i in range(0,len(word)-1): 32 # print([name[i],nikname[i],word[i]]) 33 writer.writerow([name[i],nikname[i],word[i]]) 34 csvfile.close() 35 36 if __name__ == '__main__': 37 mid_path = r'C:\Users\xxxxxxx\pm1-01mid.csv' # 改之前的pm1-01fanyi.csv文件名為這里的文件名 38 csv_path = r'C:\Users\xxxxxxx\pm1-01.csv' # 改文件名,為源文件名 39 word = csv_read_word(mid_path) 40 name = csv_read_name(csv_path,0) 41 nikname = csv_read_name(csv_path,1) 42 recsv(word,name,nikname,csv_path)
思路:這個程序就有點無厘頭了,不知道我當時怎么想的。。。總之有很大的修改空間。具體來說就是將之前翻譯好的pm1-01fanyi.csv導入到源文件中,即第一個程序的逆向,沒有什么技術難度。
注:這個程序一個大問題是,因為galgame的文本有對應的人物名字,我在第二個翻譯程序中忘了翻譯人名,導致這里想再翻譯人名就很麻煩。
使用方法:修修補補完成pm1-01fanyi.csv文件翻譯后,將該文件的文件名改為pm1-01mid.csv,然后改一下程序中的文件名,運行
運行結果:導入完成。
至此,文本翻譯告一段落。
1. 該游戲中,游戲菜單等內容並非以文本形式存儲,而是圖片,因此還要翻譯圖片。和解包文本相同,解包圖片后是一堆png格式圖片,用photoshop進行修圖翻譯即可。(這里要感謝FreeMote工具的大佬幫忙更新了工具,能夠打包了,否則只能解包,圖片漢化就不可能了)
2. 保存:保存之所以成為一個問題,是因為如果保存格式不正確,放回游戲后顯示會有問題,這就好比你用txt打開一個exe文件,改幾下后再保存,雖然它可能還是一個exe后綴的文件名,但電腦可能完全運行不了它了。同理,雖然你用ps保存為png格式,但與原來的那個png已經完全不同了。這個問題我暫時沒有找到好的解決辦法,只能嘗試改變保存時的各種參數來碰運氣。最難過的是,游戲中不同的圖片,保存格式也不同,而有一些圖片你試完了所有ps提供的可變項后,還是無法正確運行,好在只有少數不重要的圖片,不然你可能要氣炸了(忙了大半個月了就因為這玩意,毀了所有努力啊有木有!!!)有了解圖片格式的大佬可以自行研究解決。
關於視頻漢化,就是加字幕了,該游戲的一些劇情是以視頻形式表現的,但沒有字幕,日語聽力還是饒了我吧。。。我還未進行實踐,自然不知道結果如何,用pr加字幕是沒有問題,但是不是和圖片一樣有保存的問題就不得而知了。
字庫也一度讓我止步不前,但只要理解了原理,造輪子不是問題。
1. 字庫是什么:游戲中的文本本質上是圖片,字庫就是一堆字的圖片拼在一起而組成一張大圖,有點像排好的活字印刷術的活字。當需要顯示時,將映射表中的文字映射到大圖中的某個坐標從而調取到字圖,得以顯示。
2. 怎么做字庫:我找了很多生成字庫的工具,但總有各種問題,如字圖大小不一,順序混亂等,最終只能自己解決了。
- 到網上找常用字集合,我找了一個3500字集合,做成txt
注:應當為一個矩形,不要一行多一行少
- 在解包的文件中找到字庫,該游戲字庫在font文件夾中,且有好幾個字號的字庫,不知道為什么要這么多字號,實際只需改其中一個即可,直觀感受一下最終需要達到的成果
- 編程生成字庫
字庫小程序:
1 import os 2 from PIL import ImageFont,ImageDraw,Image 3 4 def Read(): 5 fp = open(r'F:\pycharm\xxxxxx\3500字_new.txt','r',True,encoding='utf-8-sig') 6 while True: 7 word = fp.read(1) 8 if not word: 9 break 10 yield word 11 fp.close() 12 13 if __name__ == '__main__': 14 m = 5 # x軸 15 n = 5 # y軸 16 wordlist = [] 17 text = Read() 18 im = Image.new("RGB", (2048, 2048), (255, 255, 255)) 19 dr = ImageDraw.Draw(im) 20 font = ImageFont.truetype(os.path.join("fonts", "goodfont01.ttf"), 22) 21 for word in text: 22 wordlist.append(word) 23 # 逐字寫入 24 for i in range(1, 46): # i列 25 for j in range(1, 79): # j行 26 flag = (i-1)*78+j # 第flag個字 27 if flag <= 3500: 28 dr.text((m, n), text=wordlist[flag-1], fill="#000000", font=font) 29 m = m + 26 30 else: 31 break 32 print("{0}層完成".format(i)) 33 m = 5 # 換行定位 34 n = n + 26 35 im.show() 36 im.save('font_new.png')
思路:制作字庫就三步,讀取文字、定位、寫入,讀取文字即讀取txt文件中的3500字中的一個,定位即定位到這個文字應該放在哪里,寫入不說了。我使用了PIL包,先生成一張底圖,再逐字寫入即可,這個包還可以設定底圖的顏色,字號,字體顏色等。但問題是我需要透明底圖,PIL貌似沒有透明這個選項。因此只能先白底黑字再ps反色摳圖解決了,實際效果強差人意,因為我不知如何將字調為單色透明度,如該游戲字庫圖實際上是白色字且有透明度的,摳圖的結果是一堆狗牙,但至少做到了排列整齊。
- 修改映射表
你可以在和字庫圖一起解包的json文件中找到,有很明顯的規律,只要將里面文字的部分用3500字重新寫一遍,覆蓋原內容即可,程序很簡單就不做解釋了。
映射表小程序:
1 if __name__ == '__main__': 2 alljs = [] 3 jstext = [] 4 x = 1 5 y = 213 6 7 # get words 8 fp = open(r'F:\pycharm\xxxxxxx\3500字_new.txt','r',True,encoding='utf-8-sig') 9 while True: 10 word = fp.read(1) 11 if not word: 12 break 13 unit = '"first": {"a": 0,"b": 20,"d": 24.0,"h": 24,"height": 24,"id": 0,"w": 24.0,"width": 24.0,"x": second,"y": third}' 14 unit2 = unit.replace('first',word) 15 jstext.append(unit2) 16 fp.close() 17 18 # change x,y 19 for i in range(1,46): 20 for j in range(1,79): 21 flag = (i-1)*78+j 22 if flag <= 3500: 23 str01 = jstext[flag-1].replace('second',str(x)) 24 str02 = str01.replace('third',str(y)) 25 alljs.append(str02) 26 x = x + 26 27 print('完成{0}行'.format(i)) 28 x = 1 29 y = y + 26 30 print('完成') 31 32 # write file 33 fp02 = open(r'F:\pycharm\xxxxxxx\font24.txt','w+',encoding='utf-8') 34 for m in alljs: 35 m = m + ',' + '\n' 36 fp02.write(m) 37 fp02.close()
最終結果無需追求源碼的縮進,只要符合json格式即可,如可以做成這樣:
- 如果翻譯中有些字在3500字中沒有怎么辦?先在映射表里找一個不常用的字,改掉它。再到字庫圖里把相應的字圖改掉。(或者重新生成)
打包
用FreeMote反向操作一下即可。這一步的問題是為了方便安裝,究竟是制作漢化補丁,還是整體打包替換,但該問題已經超出我想討論的話題了。
結語
若完成以上所有工作,該游戲即漢化完成。但可知這其中仍有很多問題有待解決,如對於人名的漢化我並未完成自動化,圖片的漢化尚存漏洞,字庫的顏色問題,當然還有丑的不行的代碼 T _ T。總之盡善盡美是很困難的。另外,上述方法也不具有通用性。最后希望這些看似已經脫離時代的東西能幫助到有需要的人吧。
最終效果: