Skip to content

Commit adf8ef2

Browse files
committed
Add Code
1 parent 39b3f89 commit adf8ef2

2 files changed

Lines changed: 252 additions & 0 deletions

File tree

main.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import ctypes
2+
3+
try:
4+
import customtkinter
5+
import threading
6+
from tkinter.filedialog import askopenfilename
7+
import os
8+
import zipfile
9+
import shutil
10+
import patcher
11+
except ImportError:
12+
ctypes.windll.user32.MessageBoxW(0, "You need to install the required packages to run this program.", "Error", 1)
13+
exit(1)
14+
15+
try:
16+
customtkinter.set_appearance_mode("System") # Modes: system (default), light, dark
17+
customtkinter.set_default_color_theme("green") # Themes: blue (default), dark-blue, green
18+
except Exception as e:
19+
ctypes.windll.user32.MessageBoxW(0, f"An error occurred while setting the appearance mode: {e}", "Error", 1)
20+
exit(1)
21+
22+
try:
23+
app = customtkinter.CTk() # create CTk window like you do with the Tk window
24+
app.geometry("400x650")
25+
app.title("Project Earth Patcher")
26+
except Exception as e:
27+
ctypes.windll.user32.MessageBoxW(0, f"An error occurred while creating the window: {e}", "Error", 1)
28+
exit(1)
29+
30+
def cleanup():
31+
try:
32+
if os.path.exists('data/ipa.zip'):
33+
os.remove('data/ipa.zip')
34+
if os.path.exists('data/ipa.ipa'):
35+
os.remove('data/ipa.ipa')
36+
if shutil.os.path.exists('data/ipa'):
37+
shutil.rmtree('data/ipa')
38+
if shutil.os.path.exists('data'):
39+
shutil.rmtree('data')
40+
except Exception as e:
41+
ctypes.windll.user32.MessageBoxW(0, f"An error occurred while cleaning up: {e}", "Error", 1)
42+
exit(1)
43+
44+
try:
45+
os.makedirs("data", exist_ok=True)
46+
except Exception as e:
47+
cleanup()
48+
ctypes.windll.user32.MessageBoxW(0, f"An error occurred while creating the data directory: {e}", "Error", 1)
49+
exit(1)
50+
51+
52+
def log(level, text, prefix):
53+
print(f"[{prefix}] {level}: {text}")
54+
textbox.configure(state="normal")
55+
textbox.insert("end", f"[{prefix}] {level}: {text}\n")
56+
textbox.configure(state="disabled")
57+
58+
59+
def patch_sync():
60+
button.configure(state="disabled")
61+
ip = ip_entry.get()
62+
ip_entry.configure(state="disabled")
63+
if len(ip) > 27:
64+
log("ERROR", "IP too long!", "PATCHER")
65+
cleanup()
66+
ctypes.windll.user32.MessageBoxW(0, f"Please enter a valid IP!", "Error", 1)
67+
return
68+
log("INFO", f"Starting to patch with ip: {ip}", "PATCHER")
69+
filename = askopenfilename(title="Select the .ipa file", filetypes=[("IPA files", "*.ipa")])
70+
if filename == '':
71+
log("ERROR", "No file selected", "PATCHER")
72+
cleanup()
73+
ctypes.windll.user32.MessageBoxW(0, f"Please select a file!", "Error", 1)
74+
return
75+
shutil.copyfile(filename, "./data/ipa.zip")
76+
# extract the zip
77+
log("INFO", "Extracting the ipa", "PATCHER")
78+
with zipfile.ZipFile("./data/ipa.zip", 'r') as zip_ref:
79+
zip_ref.extractall("./data/ipa")
80+
# check payload
81+
if not patcher.hex_bytes_in_file("68747470733A2F2F6C6F6361746F722E6D6365736572762E6E6574", "./data/ipa/Payload/minecraftearthtf.app/minecraftearthtf"):
82+
log("INFO", "This file is encrypted!", "PATCHER")
83+
ctypes.windll.user32.MessageBoxW(0, f"This file is encrypted!", "Error", 1)
84+
cleanup()
85+
return
86+
else:
87+
log("INFO", "This file is not encrypted and ready to patch!", "PATCHER")
88+
# patch the file
89+
log("INFO", "Patching the file", "PATCHER")
90+
log("INFO", "Patching App Name", "PATCHER")
91+
patcher.patch_app_name()
92+
log("INFO", "Patched App Name", "PATCHER")
93+
log("INFO", "Removing DRM", "PATCHER")
94+
patcher.remove_drm()
95+
log("INFO", "Removed DRM", "PATCHER")
96+
log("INFO", "Removing Useless Files", "PATCHER")
97+
patcher.remove_useless_files()
98+
log("INFO", "Removed Useless Files", "PATCHER")
99+
log("INFO", "Patching IP", "PATCHER")
100+
patcher.patch_ip(ip)
101+
log("INFO", "Patched IP", "PATCHER")
102+
log("INFO", "Patching Sunset Time", "PATCHER")
103+
patcher.patch_sunset_time()
104+
log("INFO", "Patched Sunset Time", "PATCHER")
105+
# zip the file
106+
log("INFO", "Zipping the file", "PATCHER")
107+
patcher.zip_folder_contents("./data/ipa/", "./ipa.ipa")
108+
log("INFO", "Zipped the file", "PATCHER")
109+
log("INFO", "Patching done!", "PATCHER")
110+
cleanup()
111+
112+
def patch():
113+
# Patch method in a new thread
114+
threading.Thread(target=patch_sync).start()
115+
116+
patcher_label = customtkinter.CTkLabel(master=app, text="Project Earth Patcher", font=("Arial", 20))
117+
patcher_label.place(relx=0.5, rely=0.3, anchor=customtkinter.CENTER)
118+
119+
# IP Textbox
120+
ip_label = customtkinter.CTkLabel(master=app, text="Server IP:")
121+
ip_label.place(relx=0.2, rely=0.4, anchor=customtkinter.CENTER)
122+
123+
ip_entry = customtkinter.CTkEntry(master=app)
124+
ip_entry.place(relx=0.5, rely=0.4, anchor=customtkinter.CENTER)
125+
126+
button = customtkinter.CTkButton(master=app, text="Patch!", command=patch)
127+
button.place(relx=0.5, rely=0.5, anchor=customtkinter.CENTER)
128+
129+
textbox = customtkinter.CTkTextbox(app, state="disabled")
130+
textbox.place(relx=0.5, rely=0.7, anchor=customtkinter.CENTER)
131+
132+
133+
app.mainloop()

patcher.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import os
2+
import shutil
3+
import zipfile
4+
5+
def ascii_to_hex(input_string):
6+
if len(input_string) < 27:
7+
input_string = input_string.ljust(27, '\x00')
8+
hex_string = input_string.encode().hex()
9+
return hex_string
10+
11+
def replace_bytes_in_file(file_path, offset, search_bytes, replace_bytes):
12+
with open(file_path, 'rb+') as file:
13+
file.seek(offset)
14+
15+
# Read the content at the specified offset
16+
data = file.read(len(search_bytes))
17+
18+
# Check if the bytes at the specified offset match the search_bytes
19+
if data == search_bytes:
20+
# Move the file pointer back to the beginning of the replacement area
21+
file.seek(offset)
22+
23+
# Replace the bytes with the new ones
24+
file.write(replace_bytes)
25+
else:
26+
pass
27+
28+
def replace_hex_bytes(file_path, search_hex, replace_hex):
29+
with open(file_path, 'rb') as file:
30+
file_data = file.read()
31+
32+
search_bytes = bytes.fromhex(search_hex)
33+
replace_bytes = bytes.fromhex(replace_hex)
34+
35+
# Search for the hex bytes in the file data
36+
index = file_data.find(search_bytes)
37+
38+
if index != -1:
39+
# Replace the hex bytes with the new hex bytes
40+
file_data = file_data[:index] + replace_bytes + file_data[index + len(search_bytes):]
41+
42+
with open(file_path, 'wb') as file:
43+
file.write(file_data)
44+
else:
45+
pass
46+
47+
def rmtree(directory_path):
48+
# Use tqdm to create the progress bar
49+
# Walk through the directory and remove each item
50+
for root, dirs, files in os.walk(directory_path, topdown=False):
51+
for file in files:
52+
file_path = os.path.join(root, file)
53+
os.remove(file_path)
54+
for dir_name in dirs:
55+
dir_path = os.path.join(root, dir_name)
56+
os.rmdir(dir_path)
57+
# Finally, remove the top-level directory itself
58+
os.rmdir(directory_path)
59+
60+
def hex_bytes_in_file(hex_sequence, file_path):
61+
with open(file_path, 'rb') as file:
62+
file_content = file.read()
63+
hex_content = ''.join([format(byte, '02x') for byte in file_content])
64+
65+
hex_sequence = hex_sequence.lower() # Convert to lowercase for case-insensitive comparison
66+
return hex_sequence in hex_content
67+
68+
def zip_folder_contents(folder_path, zip_file_path):
69+
with zipfile.ZipFile(zip_file_path, 'w') as zip_file:
70+
for root, dirs, files in os.walk(folder_path):
71+
for file in files:
72+
file_path = os.path.join(root, file)
73+
arcname = os.path.relpath(file_path, folder_path)
74+
zip_file.write(file_path, arcname=arcname)
75+
76+
def patch_app_name():
77+
plistlocation = './data/ipa/Payload/minecraftearthtf.app/Info.plist'
78+
plist = open(plistlocation, mode='r')
79+
plistdata = plist.readlines()
80+
plistmod = []
81+
for x in plistdata:
82+
y = x
83+
if 'Minecraft Earth' in x:
84+
y = y.replace('Minecraft Earth', 'Project Earth')
85+
plistmod.append(y)
86+
plist.close()
87+
plist = open(plistlocation, mode='w')
88+
plist.writelines(plistmod)
89+
plist.close()
90+
91+
def remove_drm():
92+
codesig = './data/ipa/Payload/minecraftearthtf.app/_CodeSignature/'
93+
scinfo = './data/ipa/Payload/minecraftearthtf.app/SC_Info/'
94+
if os.path.exists(codesig):
95+
print("Removing Code Signature")
96+
rmtree(codesig)
97+
print("Removed Code Signature")
98+
if os.path.exists(scinfo):
99+
print("Removing SC_Info")
100+
rmtree(scinfo)
101+
print("Removed SC_Info")
102+
103+
def remove_useless_files():
104+
rndfile = './data/ipa/Payload/minecraftearthtf.app/Zachary@Cracks( 14.4 ok)'
105+
if os.path.exists(rndfile):
106+
os.remove(rndfile)
107+
108+
def patch_ip(ip):
109+
ipinhex = ascii_to_hex(ip)
110+
oldip = '68747470733A2F2F6C6F6361746F722E6D6365736572762E6E6574'
111+
replace_hex_bytes('./data/ipa/Payload/minecraftearthtf.app/minecraftearthtf', oldip, ipinhex)
112+
113+
def patch_sunset_time():
114+
file_path = './data/ipa/Payload/minecraftearthtf.app/minecraftearthtf'
115+
offset = 0x1129080
116+
search_bytes = b'\xEA\x05\x00\x54'
117+
replace_bytes = b'\xE1\x05\x00\x54'
118+
119+
replace_bytes_in_file(file_path, offset, search_bytes, replace_bytes)

0 commit comments

Comments
 (0)