Branch data Line data Source code
1 : : #include "asar.h"
2 : : #include "assocarr.h"
3 : : #include "libstr.h"
4 : : #include "libcon.h"
5 : : #include "libsmw.h"
6 : : #include "errors.h"
7 : : #include "warnings.h"
8 : : #include "platform/file-helpers.h"
9 : : #include "virtualfile.h"
10 : : #include "interface-shared.h"
11 : : #include "assembleblock.h"
12 : : #include "asar_math.h"
13 : :
14 : : #ifdef TIMELIMIT
15 : : # if defined(linux)
16 : : # include <sys/resource.h>
17 : : # include <signal.h>
18 : : # elif defined(_WIN32)
19 : : //WARNING: The Windows equivalent of SIGXCPU, job limits, is very poorly suited for short-running
20 : : // tasks like this; it's only checked approximately every seven seconds on the machine I tested on,
21 : : // and it kills the process instantly once this happens. (Additionally, due to an implementation
22 : : // quirk, it'll bug up if you ask for anything above about seven minutes, so don't do that.)
23 : : # include <windows.h>
24 : : # else
25 : : # error Time limits not configured for this OS.
26 : : # endif
27 : : #endif
28 : :
29 : : extern const char asarver[];
30 : :
31 : 16 : void print(const char * str)
32 : : {
33 : 16 : puts(str);
34 : 16 : }
35 : :
36 : : static FILE * errloc=stderr;
37 : : static int errnum=0;
38 : :
39 : 117 : void error_interface(int errid, int whichpass, const char * e_)
40 : : {
41 : 117 : errored = true;
42 [ + + ]: 117 : if (pass == whichpass)
43 : : {
44 : 49 : errnum++;
45 : : // don't show current block if the error came from an error command
46 [ + + + + ]: 49 : bool show_block = (thisblock && (errid != error_id_error_command));
47 [ + - + - : 131 : fputs(STR getdecor() + "error: (" + get_error_name((asar_error_id)errid) + "): " + e_ + (show_block ? (STR" [" + thisblock + "]") : STR "") + "\n", errloc);
+ - + + +
+ - - -
- ]
48 : : static const int max_num_errors = 20;
49 [ - + ]: 49 : if (errnum == max_num_errors + 1) asar_throw_error(pass, error_type_fatal, error_id_limit_reached, max_num_errors);
50 : : }
51 : 117 : }
52 : :
53 : : static bool werror=false;
54 : : static bool warned=false;
55 : :
56 : 44 : void warn(int errid, const char * e_)
57 : : {
58 : : // don't show current block if the warning came from a warn command
59 [ + + + + ]: 44 : bool show_block = (thisblock && (errid != warning_id_warn_command));
60 [ + - + - : 124 : fputs(STR getdecor()+"warning: (" + get_warning_name((asar_warning_id)errid) + "): " + e_ + (show_block ? (STR" [" + thisblock + "]") : STR "") + "\n", errloc);
+ - + + +
+ - - -
- ]
61 : 44 : warned=true;
62 : 44 : }
63 : :
64 : : #ifdef TIMELIMIT
65 : : #if defined(linux)
66 : : void onsigxcpu(int ignored)
67 : : {
68 : : error<errnull>(pass, "Time limit exceeded.");
69 : : exit(1);
70 : : }
71 : : #elif defined(_WIN32)
72 : : //null
73 : : #endif
74 : : #endif
75 : :
76 : :
77 : :
78 : 95 : int main(int argc, char * argv[])
79 : : {
80 : : #ifdef TIMELIMIT
81 : : #if defined(linux)
82 : : rlimit lim;
83 : : lim.rlim_cur=TIMELIMIT;
84 : : lim.rlim_max=RLIM_INFINITY;
85 : : setrlimit(RLIMIT_CPU, &lim);
86 : : signal(SIGXCPU, onsigxcpu);
87 : : #elif defined(_WIN32)
88 : : HANDLE hjob=CreateJobObject(NULL, nullptr);
89 : : AssignProcessToJobObject(hjob, GetCurrentProcess());
90 : : JOBOBJECT_BASIC_LIMIT_INFORMATION jbli;
91 : : jbli.LimitFlags=JOB_OBJECT_LIMIT_PROCESS_TIME;
92 : : jbli.PerProcessUserTimeLimit.LowPart=10*1000*1000*TIMELIMIT;
93 : : jbli.PerProcessUserTimeLimit.HighPart=0;
94 : : SetInformationJobObject(hjob, JobObjectBasicLimitInformation, &jbli, sizeof(jbli));
95 : : #endif
96 : : #endif
97 : : #define pause(sev) do { if (pause>=pause_##sev) libcon_pause(); } while(0)
98 : :
99 : : enum {
100 : : pause_no,
101 : : pause_err,
102 : : pause_warn,
103 : : pause_yes,
104 : : } pause=pause_no;
105 : :
106 : : enum cmdlparam
107 : : {
108 : : cmdlparam_none,
109 : :
110 : : cmdlparam_addincludepath,
111 : : cmdlparam_adddefine,
112 : :
113 : : cmdlparam_count
114 : : };
115 : :
116 : : try
117 : : {
118 : 95 : romdata_r = nullptr;
119 [ + - - + ]: 190 : string version=STR"Asar "+dec(asarver_maj)+"."+dec(asarver_min)+((asarver_bug>=10 || asarver_min>=10)?".":"")+
120 [ - + ]: 285 : dec(asarver_bug)+(asarver_beta?"pre":"")+", originally developed by Alcaro, maintained by Asar devs.\n"+
121 : : "Source code: https://github.com/RPGHacker/asar\n";
122 : 95 : char * myname=argv[0];
123 [ + - ]: 95 : if (strrchr(myname, '/')) myname=strrchr(myname, '/')+1;
124 : : //char * dot=strrchr(myname, '.');
125 : : //if (dot) *dot='\0';
126 [ - + ]: 95 : if (!strncasecmp(myname, "xkas", strlen("xkas"))) {
127 : : // RPG Hacker: no asar_throw_Warning() here, because we didn't have a chance to disable warnings yet.
128 : : // Also seems like warning aren't even registered at this point yet.
129 : 0 : puts("Warning: xkas support is being deprecated and will be removed in the next release of asar!!!");
130 : 0 : puts("(this was triggered by renaming asar.exe to xkas.exe, which activated a compatibility feature.)");
131 : 0 : errloc=stdout;
132 : : }
133 : : //if (dot) *dot='.';
134 [ + - ]: 95 : libcon_init(argc, argv,
135 : : "[options] asm_file [rom_file]\n\n"
136 : : "Supported options:\n\n"
137 : : " --version \n"
138 : : " Display version information.\n\n"
139 : : " -v, --verbose \n"
140 : : " Enable verbose mode.\n\n"
141 : : " --symbols=<none/wla/nocash>\n"
142 : : " Specifies the format of the symbols output file. (Default is none for no symbols file)\n\n"
143 : : " --symbols-path=<filename>\n"
144 : : " Override the default path to the symbols output file. The default is the ROM's base name with an\n"
145 : : " extension of '.sym'.\n\n"
146 : : " --no-title-check\n"
147 : : " Disable verifying ROM title. (Note that irresponsible use will likely corrupt your ROM)\n\n"
148 : : " --pause-mode=<never/on-error/on-warning/always>\n"
149 : : " Specify when Asar should pause the application. (Never, on error, on warning or always)\n\n"
150 : : " --fix-checksum=<on/off>\n"
151 : : " Override Asar's checksum generation, allowing you to manually enable/disable generating a checksum\n\n"
152 : : " -I<path> \n"
153 : : " --include <path> \n"
154 : : " Add an include search path to Asar.\n\n"
155 : : " -D<def>[=<val>] \n"
156 : : " --define <def>[=<val>]\n"
157 : : " Add a define (optionally with a value) to Asar.\n\n"
158 : : " -werror \n"
159 : : " Treat warnings as errors.\n\n"
160 : : " -w<ID> \n"
161 : : " Enable a specific warning.\n\n"
162 : : " -wno<ID> \n"
163 : : " Disable a specific warning.\n\n"
164 : : );
165 : 95 : ignoretitleerrors=false;
166 : 95 : string par;
167 : 95 : bool verbose=libcon_interactive;
168 : 95 : string symbols="";
169 : 95 : string symfilename="";
170 : :
171 : 95 : autoarray<string> includepaths;
172 : 95 : autoarray<const char*> includepath_cstrs;
173 : :
174 [ + - + + ]: 950 : while ((par=libcon_option()))
175 : : {
176 : : cmdlparam postprocess_param = cmdlparam_none;
177 : : const char* postprocess_arg = nullptr;
178 : :
179 : : #define checkstartmatch(arg, stringliteral) (!strncmp(arg, stringliteral, strlen(stringliteral)))
180 : :
181 [ - + ]: 380 : if (par=="--no-title-check") ignoretitleerrors=true;
182 [ + - + - ]: 380 : else if (par == "-v" || par=="--verbose") verbose=true;
183 [ - + ]: 380 : else if (checkstartmatch(par, "--symbols="))
184 : : {
185 [ # # ]: 0 : if (par == "--symbols=none") symbols = "";
186 [ # # ]: 0 : else if (par=="--symbols=wla") symbols="wla";
187 [ # # ]: 0 : else if (par=="--symbols=nocash") symbols="nocash";
188 : 0 : else libcon_badusage();
189 : : }
190 [ - + ]: 380 : else if (checkstartmatch(par, "--symbols-path=")) {
191 : 0 : symfilename=((const char*)par) + strlen("--symbols-path=");
192 : : }
193 [ - + ]: 380 : else if (par=="--version")
194 : : {
195 : 0 : puts(version);
196 : : return 0;
197 : : }
198 [ - + ]: 380 : else if (checkstartmatch(par, "--pause-mode="))
199 : : {
200 [ # # ]: 0 : if (par=="--pause-mode=never") pause=pause_no;
201 [ # # ]: 0 : else if (par=="--pause-mode=on-error") pause=pause_err;
202 [ # # ]: 0 : else if (par=="--pause-mode=on-warning") pause=pause_warn;
203 [ # # ]: 0 : else if (par=="--pause-mode=always") pause=pause_yes;
204 : 0 : else libcon_badusage();
205 : : }
206 [ - + ]: 380 : else if(checkstartmatch(par, "--fix-checksum=")) {
207 [ # # ]: 0 : if(par=="--fix-checksum=on") {
208 : 0 : force_checksum_fix = true;
209 : 0 : checksum_fix_enabled = true;
210 [ # # ]: 0 : } else if(par=="--fix-checksum=off") {
211 : 0 : force_checksum_fix = true;
212 : 0 : checksum_fix_enabled = false;
213 : 0 : } else libcon_badusage();
214 : : }
215 [ + + ]: 380 : else if (checkstartmatch(par, "-I"))
216 : : {
217 : : postprocess_param = cmdlparam_addincludepath;
218 : 95 : postprocess_arg = ((const char*)par) + strlen("-I");
219 : : }
220 [ + + ]: 285 : else if (checkstartmatch(par, "-D"))
221 : : {
222 : : postprocess_param = cmdlparam_adddefine;
223 : 190 : postprocess_arg = ((const char*)par) + strlen("-D");
224 : : }
225 [ - + ]: 95 : else if (par == "--include")
226 : : {
227 : 0 : postprocess_arg = libcon_option_value();
228 [ # # ]: 0 : if (postprocess_arg != nullptr)
229 : : {
230 : : postprocess_param = cmdlparam_addincludepath;
231 : : }
232 : : }
233 [ + - ]: 95 : else if (par == "--define")
234 : : {
235 : 95 : postprocess_arg = libcon_option_value();
236 [ - + ]: 95 : if (postprocess_arg != nullptr)
237 : : {
238 : : postprocess_param = cmdlparam_adddefine;
239 : : }
240 : : }
241 [ # # ]: 0 : else if (checkstartmatch(par, "-w"))
242 : : {
243 : 0 : const char* w_param = ((const char*)par) + strlen("-w");
244 : :
245 [ # # ]: 0 : if (checkstartmatch(w_param, "error"))
246 : : {
247 : 0 : werror = true;
248 : : }
249 [ # # ]: 0 : else if (checkstartmatch(w_param, "no"))
250 : : {
251 : 0 : asar_warning_id warnid = parse_warning_id_from_string(w_param + strlen("no"));
252 : :
253 [ # # ]: 0 : if (warnid != warning_id_end)
254 : : {
255 : 0 : set_warning_enabled(warnid, false);
256 : : }
257 : : else
258 : : {
259 : 0 : asar_throw_error(pass, error_type_null, error_id_invalid_warning_id, "-wno", (int)(warning_id_start + 1), (int)(warning_id_end - 1));
260 : : }
261 : : }
262 : : else
263 : : {
264 : 0 : asar_warning_id warnid = parse_warning_id_from_string(w_param);
265 : :
266 [ # # ]: 0 : if (warnid != warning_id_end)
267 : : {
268 : 0 : set_warning_enabled(warnid, true);
269 : : }
270 : : else
271 : : {
272 : 0 : asar_throw_error(pass, error_type_null, error_id_invalid_warning_id, "-w", (int)(warning_id_start + 1), (int)(warning_id_end - 1));
273 : : }
274 : : }
275 : :
276 : : }
277 : 0 : else libcon_badusage();
278 : :
279 [ + + ]: 380 : if (postprocess_param == cmdlparam_addincludepath)
280 : : {
281 : 95 : includepaths.append(postprocess_arg);
282 : : }
283 [ + - ]: 285 : else if (postprocess_param == cmdlparam_adddefine)
284 : : {
285 [ + + ]: 285 : if (strchr(postprocess_arg, '=') != nullptr)
286 : : {
287 : : // argument contains value, not only name
288 : : const char* eq_loc = strchr(postprocess_arg, '=');
289 : 190 : string name = string(postprocess_arg, (int)(eq_loc - postprocess_arg));
290 : 190 : name = strip_whitespace(name);
291 [ + - ]: 190 : name = strip_prefix(name, '!', false); // remove leading ! if present
292 : :
293 [ + - - + : 190 : if (!validatedefinename(name)) asar_throw_error(pass, error_type_null, error_id_cmdl_define_invalid, "command line defines", name.data());
- - ]
294 : :
295 [ - + ]: 190 : if (clidefines.exists(name)) {
296 : 0 : asar_throw_error(pass, error_type_null, error_id_cmdl_define_override, "Command line define", name.data());
297 : 0 : pause(err);
298 : : return 1;
299 : : }
300 : 190 : clidefines.create(name) = eq_loc + 1;
301 : 190 : }
302 : : else
303 : : {
304 : : // argument doesn't have a value, only name
305 : 95 : string name = postprocess_arg;
306 : 95 : name = strip_whitespace(name);
307 [ + - ]: 95 : name = strip_prefix(name, '!', false); // remove leading ! if present
308 : :
309 [ + - - + : 95 : if (!validatedefinename(name)) asar_throw_error(pass, error_type_null, error_id_cmdl_define_invalid, "command line defines", name.data());
- - ]
310 : :
311 [ - + ]: 95 : if (clidefines.exists(name)) {
312 : 0 : asar_throw_error(pass, error_type_null, error_id_cmdl_define_override, "Command line define", name.data());
313 : 0 : pause(err);
314 : : return 1;
315 : : }
316 : 95 : clidefines.create(name) = "";
317 : 95 : }
318 : : }
319 : : }
320 [ - + ]: 95 : if (verbose)
321 : : {
322 : 0 : puts(version);
323 : : }
324 : 95 : string asmname=libcon_require_filename("Enter patch name:");
325 : 95 : string romname=libcon_optional_filename("Enter ROM name:", nullptr);
326 : : //char * outname=libcon_optional_filename("Enter output ROM name:", nullptr);
327 : 95 : libcon_end();
328 [ - + - - : 95 : if (!strchr(asmname, '.') && !file_exists(asmname)) asmname+=".asm";
- - ]
329 [ - + ]: 95 : if (!romname)
330 : : {
331 : 0 : string romnametmp = get_base_name(asmname);
332 [ # # # # ]: 0 : if (file_exists(romnametmp+".sfc")) romname=romnametmp+".sfc";
333 [ # # # # ]: 0 : else if (file_exists(romnametmp+".smc")) romname=romnametmp+".smc";
334 : 0 : else romname=STR romnametmp+".sfc";
335 : 0 : }
336 [ - + - - : 95 : else if (!strchr(romname, '.') && !file_exists(romname))
- - ]
337 : : {
338 [ # # # # ]: 0 : if (file_exists(STR romname+".sfc")) romname+=".sfc";
339 [ # # # # ]: 0 : else if (file_exists(STR romname+".smc")) romname+=".smc";
340 : : }
341 [ + - - + ]: 95 : if (!file_exists(romname))
342 : : {
343 : 0 : FILE * f=fopen(romname, "wb");
344 [ # # ]: 0 : if (!f)
345 : : {
346 : 0 : asar_throw_error(pass, error_type_fatal, error_id_create_rom_failed);
347 : : }
348 : 0 : fclose(f);
349 : : }
350 [ + - - + ]: 95 : if (!openrom(romname, false))
351 : : {
352 : : thisfilename= nullptr;
353 : 0 : asar_throw_error(pass, error_type_null, openromerror);
354 : 0 : pause(err);
355 : 0 : return 1;
356 : : }
357 : : //check if the ROM title and checksum looks sane
358 [ + + + - ]: 95 : if (romlen>=32768 && !ignoretitleerrors)
359 : : {
360 : 23 : bool validtitle=setmapper();
361 [ - + ]: 23 : if (!validtitle)
362 : : {
363 : 0 : string title;
364 [ # # ]: 0 : for (int i=0;i<21;i++)
365 : : {
366 : 0 : unsigned char c=romdata[snestopc(0x00FFC0+i)];
367 [ # # ]: 0 : if (c==7) c=14;
368 [ # # ]: 0 : if (c==8) c=27;//to not generate more hard-to-print characters than needed
369 [ # # ]: 0 : if (c==9) c=26;//random characters are picked in accordance with the charset Windows-1252, but they should be garbage in all charsets
370 [ # # ]: 0 : if (c=='\r') c=17;
371 [ # # ]: 0 : if (c=='\n') c=25;
372 [ # # ]: 0 : if (c=='\0') c=155;
373 : 0 : title+=(char)c;
374 : : }
375 [ # # ]: 0 : if (libcon_interactive)
376 : : {
377 [ # # # # ]: 0 : if (!libcon_question_bool(STR"Warning: The ROM title appears to be \""+title+"\", which looks like garbage. "
378 : : "Is this your ROM title? (Note that inproperly answering \"yes\" will crash your ROM.)", false))
379 : : {
380 : 0 : puts("Assembling aborted. snespurify should be able to fix your ROM.");
381 : : return 1;
382 : : }
383 : : }
384 : : else
385 : : {
386 : 0 : puts(STR"Error: The ROM title appears to be \""+title+"\", which looks like garbage. "
387 : : "If this is the ROM title, add --no-title-check to the command line options. If the ROM title is something else, use snespurify on your ROM.");
388 : 0 : pause(err);
389 : 0 : return 1;
390 : : }
391 : 0 : }
392 : : }
393 : :
394 : 190 : string stdincludespath = STR dir(argv[0]) + "stdincludes.txt";
395 : 95 : parse_std_includes(stdincludespath, includepaths);
396 : :
397 [ + + ]: 285 : for (int i = 0; i < includepaths.count; ++i)
398 : : {
399 : 190 : includepath_cstrs.append((const char*)includepaths[i]);
400 : : }
401 : :
402 : 95 : size_t includepath_count = (size_t)includepath_cstrs.count;
403 : 95 : virtual_filesystem new_filesystem;
404 : 95 : new_filesystem.initialize(&includepath_cstrs[0], includepath_count);
405 : 95 : filesystem = &new_filesystem;
406 : :
407 : 190 : string stddefinespath = STR dir(argv[0]) + "stddefines.txt";
408 : 95 : parse_std_defines(stddefinespath);
409 : :
410 [ + + ]: 374 : for (pass=0;pass<3;pass++)
411 : : {
412 : : //pass 1: find which bank all labels are in, for label optimizations
413 : : // freespaces are listed as above 0xFFFFFF, to find if it's in the ROM or if it's dynamic
414 : : //pass 2: find where exactly all labels are
415 : : //pass 3: assemble it all
416 : 281 : initstuff();
417 : 281 : assemblefile(asmname, true);
418 : 279 : finishpass();
419 : : }
420 : :
421 : 93 : closecachedfiles(); // this needs the vfs so do it before destroying it
422 : 93 : new_filesystem.destroy();
423 : 93 : filesystem = nullptr;
424 : :
425 [ - + - - : 93 : if (werror && warned) asar_throw_error(pass, error_type_null, error_id_werror);
- - ]
426 [ + + + - ]: 93 : if (checksum_fix_enabled) fixchecksum();
427 : : //if (pcpos>romlen) romlen=pcpos;
428 [ + + ]: 93 : if (errored)
429 : : {
430 : 20 : puts("Errors were detected while assembling the patch. Assembling aborted. Your ROM has not been modified.");
431 : 20 : closerom(false);
432 : 20 : reseteverything();
433 : 20 : pause(err);
434 : 20 : return 1;
435 : : }
436 [ + + ]: 73 : if (warned)
437 : : {
438 [ - + ]: 13 : if (libcon_interactive)
439 : : {
440 [ # # # # ]: 0 : if (!libcon_question_bool("One or more warnings were detected while assembling the patch. "
441 : : "Do you want insert the patch anyways? (Default: yes)", true))
442 : : {
443 : 0 : puts("ROM left unchanged.");
444 : 0 : closerom(false);
445 : 0 : reseteverything();
446 : : return 1;
447 : : }
448 : : }
449 : : else
450 : : {
451 [ - + - - ]: 13 : if (verbose) puts("Assembling completed, but one or more warnings were detected.");
452 : 13 : pause(warn);
453 : : }
454 : : }
455 : : else
456 : : {
457 [ - + - - ]: 60 : if (verbose) puts("Assembling completed without problems.");
458 : 60 : pause(yes);
459 : : }
460 : 73 : unsigned int romCrc = closerom();
461 [ - + ]: 73 : if (symbols)
462 : : {
463 [ # # # # ]: 0 : if (!symfilename) symfilename = get_base_name(romname)+".sym";
464 : 0 : string contents = create_symbols_file(symbols, romCrc);
465 : 0 : FILE * symfile = fopen(symfilename, "wt");
466 [ # # ]: 0 : if (!symfile)
467 : : {
468 : 0 : puts(STR"Failed to create symbols file: \"" + symfilename + "\".");
469 : 0 : pause(err);
470 : : return 1;
471 : : }
472 : : else
473 : : {
474 : 0 : fputs(contents, symfile);
475 : 0 : fclose(symfile);
476 : : }
477 : 0 : }
478 : 73 : reseteverything();
479 : 135 : }
480 : 2 : catch(errfatal&)
481 : : {
482 : 2 : puts("A fatal error was detected while assembling the patch. Assembling aborted. Your ROM has not been modified.");
483 : 2 : closerom(false);
484 : 2 : reseteverything();
485 : 2 : pause(err);
486 : : return 1;
487 : 2 : }
488 : 73 : return 0;
489 : : }
|