-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmmagedit.py
294 lines (241 loc) · 9.16 KB
/
mmagedit.py
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import sys
from array import array
from src.mmdata import MMData
from src import emulaunch
import os
mmageditpath = os.path.dirname(os.path.realpath(__file__))
# as-lib
as_lib = "--as-lib" in sys.argv
gui_available=False
img_available=False
nesm_available=False
if not as_lib:
try:
import src.mmimage
img_available=True
except ImportError as e:
pass
try:
import src.mmgui
gui_available=True
except ImportError as e:
pass
nesm_available = emulaunch.find_emulator()
from src import constants
from src import util
def usage():
print(constants.mmname)
print()
print("Usage:")
print(" python3 mmagedit.py base.nes [-i hack.txt] [-o hack.txt] [-e modified.nes] [-p patch.ips] [--export-images]")
print("")
print("-i: open hack")
print("-o: save hack")
print("-e: export to rom")
print("-p: export to ips patch")
print("-b: export to bps patch")
print("--export-images: creates image sheet for levels")
print("--set-chr: sets chr rom (graphics data) to the data in the given image file.")
print("--zoom n: starts editor at zoom level n (n can be 0, 1, or 2)")
print("")
print("debug options:")
print("")
print("--help: show this message")
print("--deps: check dependencies")
print("--brx: breakpoint on byte edit")
print("--json: serialize data to json")
print("--select .field[a].field2[b:c]: (etc) select elements of json out")
print("--apply {...}: apply json to data")
print("")
print("playtest options:")
print("")
print("--level x: exported rom jumps to level x at start (x can be 1-14)")
print("--flag x: exported rom starts at the given checkpoint (requires --level)")
print("--ending: exported rom jumps to ending screen at start")
print("--players x: exported rom has x players (x can be 1-4)")
print("--hard: exported rom starts in hard mode (requires --level or --ending)")
print("--hell: exported rom starts in hell mode (requires --level or --ending)")
def main():
if "--help" in sys.argv or "-h" in sys.argv:
usage()
sys.exit()
if "--deps" in sys.argv:
if not img_available:
print("Not available: image")
sys.exit(1)
elif not gui_available:
print("Not available: gui")
sys.exit(1)
elif not nesm_available:
print("Not available: nesm")
sys.exit(1)
result, retcode = emulaunch.emulator_test()
if not result:
print("Warning: nesm available but cannot launch. Code:", retcode)
sys.exit(0)
print("All modules available.")
sys.exit(0)
outfile=""
infile=""
exportnes=""
outpatch=""
outbps=""
chrin=""
gui = True
zoom_idx=0
expimage = False
dojson = "--json" in sys.argv
jsonpath = ""
jsonapply = None
startlevel = 0
startplayers = 1
starthard = 0
startending = False
startflag = None
if "-i" in sys.argv[2:-1]:
infile = sys.argv[sys.argv.index("-i") + 1]
if "--select" in sys.argv[2:-1]:
jsonpath = sys.argv[sys.argv.index("--select") + 1]
if "--zoom" in sys.argv[2:-1]:
zoom_idx = int(sys.argv[sys.argv.index("--zoom") + 1])
if "--set-chr" in sys.argv[2:-1]:
chrin = sys.argv[sys.argv.index("--set-chr") + 1]
if "-o" in sys.argv[2:-1]:
gui = False
outfile = sys.argv[sys.argv.index("-o") + 1]
if "-e" in sys.argv[2:-1]:
gui = False
exportnes = sys.argv[sys.argv.index("-e") + 1]
if not exportnes.endswith(".nes"):
print("Error: exported ROM must have .nes extension.")
sys.exit(1)
if "-p" in sys.argv[2:-1]:
gui = False
outpatch = sys.argv[sys.argv.index("-p") + 1]
if not outpatch.endswith(".ips"):
print("Error: exported IPS must have .ips extension.")
sys.exit(1)
if "-b" in sys.argv[2:-1]:
gui = False
outbps = sys.argv[sys.argv.index("-b") + 1]
if not outbps.endswith(".bps"):
print("Error: exported BPS must have .bps extension.")
sys.exit(1)
if "--export-images" in sys.argv[2:]:
gui = False
expimage = True
if "--brx" in sys.argv[2:]:
src.mmdata.breakpoint_on_byte_edit = True
if "--apply" in sys.argv[2:-1]:
jsonapply = sys.argv[sys.argv.index("--apply") + 1]
if "--level" in sys.argv[2:-1]:
startlevel = int(sys.argv[sys.argv.index("--level") + 1])
if "--players" in sys.argv[2:-1]:
startplayers = int(sys.argv[sys.argv.index("--players") + 1])
startplayers = max(1, min(4, startplayers))
if "--flag" in sys.argv[2:-1]:
startflag = max(int(sys.argv[sys.argv.index("--flag") + 1])-1, 0)
if "--ending" in sys.argv[2:]:
startending = True
if "--hard" in sys.argv[2:]:
starthard = 1
if "--hell" in sys.argv[2:]:
starthard = 2
if dojson:
gui = False
bin = array('B')
filepath = None
if len(sys.argv) > 1:
filepath = sys.argv[1]
if filepath is not None and not filepath.endswith(".nes"):
usage()
sys.exit()
if gui:
if not gui_available:
print("gui requires tkinter and PIL (Pillow) to be available, including PIL.ImageTk.")
else:
gui = src.mmgui.Gui()
# set the zoom
if zoom_idx != 0:
gui.set_zoom(max(min(zoom_idx, 3), 0), True) # force zoom
# read the rom
if filepath != "" and filepath is not None:
print("Opening ROM: " + filepath, flush=True)
gui.fio_direct(filepath, "rom")
# read a hack file
if infile != "":
print("applying hack:", infile, flush=True)
gui.fio_direct(infile, "hack")
else:
# load whatever nes file is in the folder.
nesfiles = []
for file in os.listdir(mmageditpath):
if file.lower().endswith(".nes"):
nesfiles.append(file)
if len(nesfiles) > 1:
gui.showinfo("Multiple .nes files found in the editor folder. Please ensure there is exactly one .nes file there to be opened by default.")
elif len(nesfiles) == 1:
print("Opening ROM:", nesfiles[0], flush=True)
gui.fio_direct(nesfiles[0], "rom")
# refresh the display if a rom file was successfully loaded.
if gui.data:
print("Initializing display...", flush=True)
gui.refresh_all()
# mainloop (blocks)
print("Launching GUI...", flush=True)
gui.run()
# user doesn't want other things to happen after the gui is closed.
print("Closing.", flush=True)
sys.exit()
# directly load data and operate on it
elif filepath is not None:
# data in -------------------------
mmdata = MMData()
if not mmdata.read(filepath):
print("An error occurred while reading the rom.")
sys.exit()
result = True
if infile != "":
tprev = mmdata.title_screen.table
mmdata.parse(infile)
tnew = mmdata.title_screen.table
if chrin != "":
if not img_available:
print("--set-chr requires PIL (Pillow), which is not installed. (python3 -m pip install Pillow)")
else:
src.mmimage.set_chr_rom_from_image_path(mmdata, chrin)
if jsonapply is not None:
mmdata.deserialize_json_str(jsonapply)
# data out ---------------------------
if dojson:
j = mmdata.serialize_json_str(jsonpath)
result = result and j != "null"
print(j)
mmdata.startlevel = startlevel
mmdata.startdifficulty = starthard
mmdata.startscreen = startending
mmdata.startplayers = startplayers
mmdata.startflag = startflag
if exportnes != "":
result = result and mmdata.write(exportnes)
if outpatch != "":
result = result and mmdata.write_ips(outpatch)
if outbps != "":
result = result and mmdata.write_bps(outbps)
if expimage:
if not img_available:
print("Image export requires PIL (Pillow), which is not installed. (python3 -m pip install Pillow)")
else:
src.mmimage.export_images(mmdata)
if not expimage and exportnes == "" and outfile != "" and result and len(mmdata.errors) == 0:
mmdata.stat(outfile)
if len(mmdata.errors) > 0:
if result:
print("The following warning(s) occurred:")
else:
print("The following error(s) occurred:")
for error in mmdata.errors:
print("- " + error)
sys.exit(0 if result else 1)
if not as_lib:
main()