-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgui.pyw
228 lines (177 loc) · 8.35 KB
/
gui.pyw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
import customtkinter as ctk
from tkinter import filedialog, messagebox
import os, json
def parse_ips_patch():
filePath = filedialog.askopenfilename(title="Select Patch Files", filetypes=(("IPS Patch Files", "*.ips"), ("All Files", "*.*")))
if not filePath:
status_label.configure(text="Operation Aborted.")
return
jsonDict = {"Patches": []}
magicStartingBytes = b"PATCH"
magicEndingBytes = b"EOF"
with open(filePath, 'rb') as f:
patchData = f.read()
if not patchData.startswith(magicStartingBytes) or magicEndingBytes not in patchData:
jsonDict["PatchValid"] = False
status_label.configure(text="Invalid IPS Patch.")
return jsonDict
jsonDict["PatchValid"] = True
index = 5
while index < len(patchData) - 3:
if patchData[index:index+3] == magicEndingBytes:
break
offset = int.from_bytes(patchData[index:index+3], "big")
size = int.from_bytes(patchData[index+3:index+5], "big")
index += 5
if size == 0:
rle_size = int.from_bytes(patchData[index:index+2], "big")
rle_byte = patchData[index+2]
index += 3
jsonDict["Patches"].append({
"Offset": hex(offset),
"Size": rle_size,
"Data": hex(rle_byte)
})
else:
data = patchData[index:index+size]
index += size
jsonDict["Patches"].append({
"Offset": hex(offset),
"Size": size,
"Data": data.hex()
})
jsonFile = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("Text JSON Files", "*.json"), ("All Files", "*.*")], title="Save JSON As")
if not jsonFile:
status_label.configure(text="Operation Aborted.")
return
with open(jsonFile, "w", encoding="utf-8") as json_out:
json.dump(jsonDict, json_out, indent=4)
status_label.configure(text=f"JSON Text File created {json_out}")
return jsonDict
patchList = []
def combinePatches(patchList: list):
outputFilePath = filedialog.asksaveasfilename(defaultextension=".ips", filetypes=[("IPS Patch Files", "*.ips"), ("All Files", "*.*")], title="Save IPS Patch As")
with open(outputFilePath, 'wb') as outF:
outF.write(b'PATCH')
for patch in patchList:
with open(patch, 'rb') as f:
data = f.read()
data = data[5:len(data)-3]
outF.write(data)
outF.write(b"EOF")
status_label.configure(text="Patches Combined Successfully!")
def addPatchFile():
files = filedialog.askopenfilenames(title="Select Patch Files", filetypes=(("IPS Patch Files", "*.ips"), ("All Files", "*.*")))
for file in files:
patchList.append(file)
patch_names = [os.path.basename(file) for file in patchList]
combineButtonClick()
def combineButtonClick():
if len(patchList) > 1:
combinePatches(patchList)
else:
status_label.configure(text="Please select more than one IPS Patch.")
def create_ips_patch(original_file, modified_file, patch_file):
"""Creates an IPS patch comparing an original and modified ROM."""
with open(original_file, "rb") as f:
original_data = f.read()
with open(modified_file, "rb") as f:
modified_data = f.read()
max_length = max(len(original_data), len(modified_data))
original_data = original_data.ljust(max_length, b'\x00')
modified_data = modified_data.ljust(max_length, b'\x00')
patch = bytearray(b"PATCH")
i = 0
while i < max_length:
if original_data[i] != modified_data[i]:
start = i
patch_data = []
while i < max_length and original_data[i] != modified_data[i]:
patch_data.append(modified_data[i])
i += 1
patch += start.to_bytes(3, "big")
patch += len(patch_data).to_bytes(2, "big")
patch += bytes(patch_data)
else:
i += 1
patch += b"EOF"
with open(patch_file, "wb") as f:
f.write(patch)
status_label.configure(text=f"IPS Patch created: {patch_file}")
def apply_ips_patch(rom_file, patch_file, output_file):
"""Applies an IPS patch to a ROM without external modules."""
with open(rom_file, "rb") as f:
rom_data = bytearray(f.read())
with open(patch_file, "rb") as f:
patch_data = f.read()
if not patch_data.startswith(b"PATCH") or not patch_data.endswith(b"EOF"):
status_label.configure(text="Invalid IPS Patch.")
return
index = 5
while index < len(patch_data) - 3:
offset = int.from_bytes(patch_data[index:index + 3], "big")
index += 3
length = int.from_bytes(patch_data[index:index + 2], "big")
index += 2
if length == 0:
status_label.configure(text="RLE encoding not supported.")
return
patch_bytes = patch_data[index:index + length]
index += length
rom_data[offset:offset + length] = patch_bytes
with open(output_file, "wb") as f:
f.write(rom_data)
status_label.configure(text=f"IPS patch applied: {output_file}")
def select_files_for_patch_creation():
"""Opens a single file dialog to select original, modified, and save location for IPS patch."""
original_file = filedialog.askopenfilename(title="Select The Original UNMODIFIED ROM", filetypes=[("BIN files", "*.bin")])
if not original_file:
status_label.configure(text="Operation Aborted.")
return
modified_file = filedialog.askopenfilename(title="Select The MODIFIED ROM", filetypes=[("BIN files", "*.bin")])
if not modified_file:
status_label.configure(text="Operation Aborted.")
return
patch_file = filedialog.asksaveasfilename(title="Save IPS Patch As", defaultextension=".ips", filetypes=[("IPS Patch", "*.ips")])
if not patch_file:
status_label.configure(text="No Save-Location Selected.")
return
create_ips_patch(original_file, modified_file, patch_file)
def select_files_for_patch_application():
"""Selects a ROM and IPS patch, then asks where to save the modified ROM."""
rom_file = filedialog.askopenfilename(title="Select ROM to Patch", filetypes=[("BIN files", "*.bin")])
if not rom_file:
status_label.configure(text="No ROM File Selected.")
return
patch_file = filedialog.askopenfilename(title="Select IPS Patch", filetypes=[("IPS Patch", "*.ips")])
if not patch_file:
status_label.configure(text="No IPS Patch Selected.")
return
output_file = filedialog.asksaveasfilename(title="Save Patched ROM File As", defaultextension=".bin", filetypes=[("BIN files", "*.bin")])
if not output_file:
status_label.configure(text="No Output ROM Selected.")
return
apply_ips_patch(rom_file, patch_file, output_file)
def about_app():
messagebox.showinfo("About - IPS Patch Tool", "Developers:\n- Cracko298\n- Pizzaleader\n- MC3DS Community\n\nGitHub:\nhttps://github.com/Minecraft-3DS-Community")
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
app = ctk.CTk()
app.title("MC3DS Community | IPS Patching Tool")
app.geometry("500x300")
app.resizable(False, False)
title_label = ctk.CTkLabel(app, text="IPS Patching Tool", font=("Arial", 18, "bold"))
title_label.pack(pady=(10, 5))
create_patch_button = ctk.CTkButton(app, text="Create IPS Patch", command=select_files_for_patch_creation)
create_patch_button.pack(pady=5)
apply_patch_button = ctk.CTkButton(app, text="Apply IPS Patch", command=select_files_for_patch_application)
apply_patch_button.pack(pady=5)
combine_button = ctk.CTkButton(app, text="Combine IPS Patches", command=addPatchFile)
combine_button.pack(pady=5)
parse_button = ctk.CTkButton(app, text="Parse IPS Patches", command=parse_ips_patch)
parse_button.pack(pady=5)
about_button = ctk.CTkButton(app, text="About", command=about_app)
about_button.pack(pady=5)
status_label = ctk.CTkLabel(app, text="", text_color="lightgray")
status_label.pack(pady=10)
app.mainloop()