Skip to content

Instantly share code, notes, and snippets.

@progheal
Last active October 21, 2015 16:47
Show Gist options
  • Save progheal/ec5088cb8c99d33760b3 to your computer and use it in GitHub Desktop.
Save progheal/ec5088cb8c99d33760b3 to your computer and use it in GitHub Desktop.
Minecraft CJK-IME patchfile generator
#!/usr/bin/env python3
# encoding=UTF-8
# Minecraft CJK-IME patchfile generator (Minecraft 中文輸入更新檔產生程式) by LPH66 @ PTT (Minecraft IGN: proghealer)
#
# Version 0.6 probably final (2015/10/22)
#
# 適用範圍:Minecraft 1.6.4 ~ 1.8.8;但 snapshot/prerelease 版本不一定適用。
#
# 系統需求: python 3.x 執行環境。(關於 python 環境安裝等事項可至 python 官方網站查詢:https://www.python.org/ )
#
# 使用方法:
# 直接執行本檔案。可附帶參數表示 Minecraft 的 jar 檔位置;若無指定則會詢問之。
# (Windows 環境可在執行本檔案後拖放 jar 檔至所出現的命令提示字元視窗,再按 Enter;或是將 jar 檔拖放至本檔案上亦可)
# 執行後將會產生該 jar 檔所需的更新檔於本檔案所在資料夾中 (或是若使用者有更動工作目錄則產生在工作目錄下),
# 此後再使用更改 jar 檔的方式更新即可。
#
# 若想直接下載更新檔,可至 http://forum.gamer.com.tw/C.php?page=1&bsn=18673&snA=35796;若不清楚更新步驟者也可至此查詢。
# (該文目前因使用者考古而被鎖定,後續更新在該文作者的小屋裡: http://home.gamer.com.tw/homeindex.php?owner=a0918430588 )
# 如無特別意外的話,本程式產生的更新檔應與上述連結內的更新檔相同;若有不同者請回報。
# 此程式在 Mojang 對相關函式作更動時會失效,若這種事情發生時也請回報。
#
# 附記:Minecraft bugtracker 上有一個相關項目 MC-2781,若有興趣追蹤可至 https://bugs.mojang.com/browse/MC-2781。
# 我已將這支程式所進行的更改回報在這個項目上了,希望 1.9 出來時這支程式可以退休 :D
# 2015/10/22 再補記:MC-2781 在 15w43a (以及 1.9) 被修好了!本程式已改寫新增偵測是否為已修改完成版本。
#
# FAQ:
# Q: 為何不直接改寫 jar 檔?
# A: 由於新版 Minecraft Launcher 可以設定 profile 指到不同版本的 Minecraft,
# 直接更改 jar 檔會讓使用者搞混自己目前使用何種版本;
# 再者也許使用者已經自行新增了一些更改 jar 檔的 MOD,本程式無法完美偵測這種狀況。
# 因此本程式選擇產生更新檔,由使用者自行更新。
# 未來或許會改成跟安裝 Forge 類似的過程,偵測使用者的 .minecraft 來尋找 jar,
# 更改過後產生新的版本存放更改結果;但目前限於個人時間關係尚在研究當中。
# Q: 為何 snapshot/prerelease 版本不一定適用?
# A: Snapshot/prerelease 版本並不是正式版,因此程式內會留有許多方便 Mojang 程式人員除錯的資訊,
# 即使 Mojang 並未對目標函式更動,但除錯資訊的有無便會影響到目標函式的偵測。
# 本程式已經對一部份的可能性進行考慮,但無法保證永遠適用。
# (有些時候 snapshot/prerelease 的差異沒有大到使本程式無法產生更新檔,
# 這時本程式會印出一小段訊息,但依然會試著產生應該可以用的更新檔。
# 有對原檔做備份的話是可以試著使用這樣產生的更新檔的。)
# 對於正式版,本人會盡力使本程式能夠正常工作。
# Q: 為何只支援 1.6.4 以後的版本?
# A: 目前多數的 Vanilla 伺服器皆至少更新至 1.7.2,而較為流行的 MOD 所需的基礎版本則至少是 1.6.4,
# 也就是絕大多數的玩家使用的皆至少是 1.6.4 以後的版本,因此本程式僅支援這個範圍;
# 除非確實有一定量的需求否則不會對更早的版本進行支援。
# Q: 為何需求 python 3.x 環境?
# A: 其一,Minecraft 是跨平台的程式,因此必須也要選擇一個跨平台的寫作選項;
# 其二,選擇 python 而非與 Minecraft 相同的 java 是因為寫起來比較方便 (請解釋成:我比較懶 XD),
# 而且由於 python 程式的特性,開放原始碼的同時也可以保證執行的程式即是原始碼所代表的程式;
# 其三,選擇僅使用 python 3.x 而非另外相容 2.x 環境則是因為若要相容,程式需要增加不少較繁雜的修改。
# 若有人願意幫忙改寫也是相當歡迎的。
#
# 以下為慣例的版權宣告,採用 MIT License。
#
# Copyright (c) 2015 LPH66 @ PTT (Minecraft IGN: proghealer)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import sys
import platform
import os
import string
import zipfile
import struct
if sys.hexversion < 0x03000000:
sys.exit("需要 Python 3.x 的執行環境!")
if len(sys.argv) > 1:
jarfile = sys.argv[1]
else:
if platform.system() == 'Windows':
jarfile = input('輸入 jar 檔檔名 (或可拖放 jar 檔至此視窗後再按 Enter): ')
else:
jarfile = input('輸入 jar 檔檔名: ')
# 技術細節:
# 本程式搜尋 class 檔中需要新增條件的函式程式碼,找到後直接修改 bytecode,
# 因此若 Mojang 對這個函式的內容有修改則這裡就要重新取樣。
# (這也是為何 snapshot/prerelease 不一定可以使用。)
# 更詳細的技術細節恕在此不討論,可以自行對修改前後的 class 檔進行比較。
patterns = [
{
'desc': '1.6.4 ~ 1.7.2',
'expLength': [103],
'diffLength': 6,
'checks': [b'\0\3\0\3\0\0\0\x23\xB8', b'\x99\0\x1F\xB8', b'\x3C\xB8',
b'\x3D\x1B\x10\x57\xA0\0\x0B\x2A\xB4', b'\xB6',
b'\xB1\x2A\x1C\x1B\xB6', b'\xB1'],
'replace': [b'\0\3\0\3\0\0\0\x29\xB8', b'\x3C\x1B\x10\x20\xA2\x00\x09\xB8',
b'\x99\x00\x1B\xB8', b'\x3D\x1C\x10\x57\xA0\0\x0B\x2A\xB4',
b'\xB6', b'\xB1\x2A\x1B\x1C\xB6', b'\xB1'],
'varout': [2, 0, 1, 3, 4, 5]
},
{
'desc': '1.7.3 ~ 15w42a',
'expLength': [69, 87],
'diffLength': 8,
'checks': [b'\0\3\0\1\0\0\0\x18\xB8', b'\x99\0\x0D\x2A\xB8',
b'\xB8', b'\xB6', b'\x2A\xB4', b'\xB6', b'\xB1'],
'replace': [b'\0\3\0\2\0\0\0\x20\xB8', b'\x3C\x1B\x10\x20\xA2\x00\x09\xB8',
b'\x99\x00\x0B\x2A\x1B\xB8', b'\xB6', b'\x2A\xB4', b'\xB6', b'\xB1'],
'varout': [1, 0, 2, 3, 4, 5]
},
{
'desc': '15w43a+',
'expLength': [122],
'checks': [b'\0\3\0\2\0\0\0\x26\xB8', b'\x3C\xB8', b'\x9A\0\x09\x1B\x10\x20\xA2\0\x09\xB8',
b'\x99\0\x0B\x2A\x1B\xB8', b'\xB6', b'\x2A\xB4', b'\xB6', b'\xB1']
}
]
with zipfile.ZipFile(jarfile, 'r') as zf:
for fileinfo in zf.infolist():
if not fileinfo.filename.endswith('.class'):
continue
classfile = zf.read(fileinfo)
pfound = None
beginpos = 0
patternlen = 0
for p in patterns:
beginpos = classfile.find(p['checks'][0])
if beginpos == -1:
continue
patternlen = len(p['checks'][0])
pos = beginpos + patternlen + 2
passed = True
var = []
for s in p['checks'][1:]:
if classfile[pos:pos+len(s)] != s:
passed = False
break
var += [classfile[pos-2:pos]]
pos += len(s) + 2
patternlen += len(s) + 2
if passed:
pfound = p
break
if not pfound:
continue
desc = pfound['desc']
if desc == '15w43a+':
print('這是已經修正完的版本 (15w43a+, 1.9+),已不需修正檔!')
sys.exit()
checks = pfound['checks']
replace = pfound['replace']
varout = pfound['varout']
lengthfound = struct.unpack('>i',classfile[beginpos-4:beginpos])[0]
if lengthfound not in pfound['expLength']:
print('**警告** 此 jar 檔或許是 snapshot 或 prerelease 版,修正檔不一定能正常使用。')
print('[技術細節: 預想長度', pfound['expLength'], ',jar 內長度', lengthfound, ']')
print('進行針對', desc, '的修正 patch,產生修正檔……')
with open(fileinfo.filename, 'wb') as fout:
fout.write(classfile[0:beginpos-4])
fout.write(struct.pack('>i', lengthfound + pfound['diffLength']))
for i in range(len(checks)-1):
fout.write(replace[i])
fout.write(var[varout[i]])
fout.write(replace[-1])
fout.write(classfile[beginpos+patternlen:])
print('修正檔', fileinfo.filename, '已產生,請自行替換 jar 檔內容。')
input('按 Enter 鍵結束程式。')
sys.exit()
# Exiting loop iff no patch candidate found
print('找不到需修正的檔案。請確認此檔案:')
print(jarfile)
print('是否為未修改的 Minecraft jar 檔。')
print('若確實是 (且為 1.6.4 以後的版本) 則請聯絡本程式作者並回報版本以便更新程式。')
print('1.6.4 以前的目前並無計畫支援;詳情請參閱程式內註解。')
input('按 Enter 鍵結束程式。')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment