-
Notifications
You must be signed in to change notification settings - Fork 3
/
main.c
2389 lines (2282 loc) · 107 KB
/
main.c
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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// single-c-file is the simplest way to get a static EXE and WASM
// from a common source, also without requiring project/make files and LTO
// see google doc for documentation on old (numbered) patches
// the idea is to "manually" patch the game to a state where we simply swap
// out some numbers to make it random (without rewriting/relocating everything)
#define VERSION "v051d"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#ifdef _MSC_VER
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#else
#include <strings.h> // strcasecmp
#endif
#include <ctype.h>
#include <stdint.h>
#include <stdbool.h>
#include "tinymt64.h"
#ifndef NO_UI // includes and helpers for UI
#if defined(WIN32) || defined(_WIN32)
#include <process.h>
#include <windows.h>
int getch(void); // can't #include <conio.h> with <windows.h>
void clrscr(void)
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
COORD pos = {0,0};
int i;
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
for (i=0; i<csbi.srWindow.Bottom-csbi.srWindow.Top+1+3; i++)
printf("\n");
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
#else
#include <termios.h>
#include <unistd.h>
#define clrscr() printf("\e[1;1H\e[2J")
char getch() {
static struct termios oldt, newt;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
char c = getchar();
tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
return c;
}
#endif
#endif
#define pause() do{printf("\nPress any key to quit ...\n"); getch();}while(false)
#if defined(WIN32) || defined(_WIN32)
#define DIRSEP '\\'
#ifndef PRIx64
#define PRIx64 "I64x"
#define uint64_t unsigned __int64
#endif
#else
#define DIRSEP '/'
#endif
#if !defined NO_MULTIWORLD && !defined WITH_MULTIWORLD && !defined __EMSCRIPTEN__
#define WITH_MULTIWORLD
#endif
#include "util.h"
static bool stris(const char* s, const char* t) { if (s==t) return true; return strcmp(s,t)==0; }
#ifdef NO_UI
static bool batch = true;
#else
static bool batch = false;
#endif
#ifndef die
void die(const char* msg)
{
if (msg) fprintf(stderr, "%s", msg);
#if (defined(WIN32) || defined(_WIN32)) && !defined(NO_UI)
if (!batch) pause();
#else
(void)batch; // ignore warnings
#endif
exit(1);
}
#endif
const char B32[] = "abcdefghijklmnopqrstuvwxyz234567=";
static char b32(uint64_t v) { return B32[v&0x1f]; }
#define APPLY_PATCH(buf, patch, loc) memcpy(buf+loc, patch, sizeof(patch)-1)
#include "rng.h"
#define progressive_armor (true) // NOTE: gourdomizer only supports true
#include "gourds.h" // generated list of gourds and gourd drops
#include "sniff.h" // generated list of sniffing spots
#include "data.h"
#define float DO_NOT_USE_FLOATS
#define double DO_NOT_USE_FLOATS
// Misc consts
const char DIFFICULTY_CHAR[] = {'e','n','h','x'};
const char* const DIFFICULTY_NAME[] = {"Easy","Normal","Hard","Random"};
const char* OFF_ON[] = { "Off", "On", NULL };
const char* OFF_ON_FULL[] = { "Off", "On", "Full", NULL };
const char* OFF_ON_POOL[] = { "Off", "On", "Pool", NULL };
const char* OFF_ON_LOGIC[] = { "Off", "On", "In Logic", NULL };
const char* VANILLA_RANDOM[] = { "Vanilla", "Random", NULL };
const char* ENERGY_CORE_VALUES[] = { "Vanilla", "Shuffle", "Fragments", NULL };
const char* POOL_STRATEGY_VALUES[] = { "Balance", "Random", "Bosses", NULL };
#define ON 1
#define FULL 2
#define POOL 2
#define LOGIC 2
#define STRATEGY_BALANCED 0
#define STRATEGY_RANDOM 1
#define STRATEGY_BOSSES 2
#define ENERGY_CORE_VANILLA 0
#define ENERGY_CORE_SHUFFLE 1
#define ENERGY_CORE_FRAGMENTS 2
#define DEFAULT_difficulty 1
struct option { char key; uint8_t def; const char* text; const char* info; const char* description; const char** state_names; const char* section; const char* subsection;};
const static struct option options[] = {
{ 0, 1, "Open World", NULL, "Make windwalker available in every firepit", OFF_ON, NULL, NULL },
{ '4', 0, "All accessible", NULL, "Make sure all key items are obtainable", OFF_ON, "General", NULL },
{ 'l', 0, "Spoiler Log", NULL, "Generate a spoiler log file", OFF_ON, "General", NULL },
{ 'z', 1, "Energy Core", NULL, "How to obtain the Energy Core. Random and Fragments convert the vanilla spot to a gourd. "
"Fragments replace ingredients in gourds.", ENERGY_CORE_VALUES, "General", "Key items" },
{ 'a', 1, "Alchemizer", NULL, "Shuffle learned alchemy formulas. Select 'pool' to add this pool to the mixed pool.", OFF_ON_POOL, "General", "Key items" },
{ 'b', 1, "Boss dropamizer", NULL, "Shuffle boss drops. Select 'pool' to add this pool to the mixed pool.", OFF_ON_POOL, "General", "Key items" },
{ 'g', 1, "Gourdomizer", NULL, "Shuffle gourd drops. Select 'pool' to add this pool to the mixed pool.", OFF_ON_POOL, "General", "Key items" },
{ 's', 1, "Sniffamizer", NULL, "Shuffle sniff drops. Select 'pool' to add this pool to the mixed pool.", OFF_ON_POOL, "General", "Key items" },
{ 'o', 0, "Mixed Pool Strategy", NULL, "Key item placement strategy for the mixed pool. Requires at least one option on 'pool'\n"
"Balanced will keep the original distribution of key items per pool (4 or 5 in gourds, 2 in alchemy, 11 in boss drops)\n"
"Random will randomly distribute key items into any selected pool\n"
"Bosses will try to place all key items into boss drops. Requires boss dropamizer on 'pool'", POOL_STRATEGY_VALUES, "General", "Key items" },
{ 'v', 0, "Sniff ingredients", NULL, "Vanilla or random ingredient drops", VANILLA_RANDOM, "General", "Other" },
{ 'i', 1, "Ingredienizer", NULL, "Shuffle ('on') or randomize ('full') ingredients required for formulas", OFF_ON_FULL, "General", "Other" },
{ 'c', 1, "Callbeadamizer", NULL, "Shuffle call bead characters ('on') or shuffle individual spells ('full')", OFF_ON_FULL, "General", "Other" },
{ 'd', 0, "Doggomizer", "Act1-3", "Random dog per act ('on') or per room ('full')", OFF_ON_FULL, "General", "Other" },
{ 'p', 0, "Pupdunk mode", "Act0 dog", "Everpupper everywhere! Overrides Doggomizer", OFF_ON, "General", "Other" },
{ 'm', 0, "Musicmizer", "Demo", "Random music for some rooms", OFF_ON, "General", "Cosmetic" },
{ 'j', 0, "Sequence breaks", NULL, "Off (default) fixes some sequence breaks: Volcano rock, final boss hatch (not out of bounds). "
"'Logic' may expect the player to break sequence or skip invisible bridges to finish.", OFF_ON_LOGIC, "Accessibility", NULL },
{ 'u', 0, "Out of bounds", NULL, "Off (default) fixes dog collision when leaving West of Crustacia. "
"'Logic' may expect a player to use OoB to finish.", OFF_ON_LOGIC, "Accessibility", NULL },
{ '2', 0, "Allow cheats", NULL, "Don't fix vanilla \\\"cheats\\\": Infinite call beads", OFF_ON, "Accessibility", NULL },
{ '5', 1, "Allow infinite ammo", NULL, "Don't fix bug that would have bazooka ammo not drain", OFF_ON, "Accessibility", NULL },
{ '6', 1, "Allow atlas glitch", NULL, "Don't fix status effects cancelling with pixie dust", OFF_ON, "Accessibility", NULL },
{ '7', 1, "Allow wings glitch", NULL, "Don't fix wings granting invincibility if they \\\"did not work\\\"", OFF_ON, "Accessibility", NULL },
{ '8', 1, "Allow perma status", NULL, "Don't fix status effect becoming permanent with the 5th", OFF_ON, "Accessibility", NULL },
{ 'f', 1, "Short boss rush", NULL, "Start boss rush at Metal Magmar, cut HP in half", OFF_ON, "Accessibility", NULL },
{ 'k', 1, "Keep dog", NULL, "Keep dog in some places to avoid softlocks", OFF_ON, "Quality of Life", NULL },
{ '9', 1, "Shorter dialogs", "Few", "Shorten some dialogs/cutscenes. Ongoing effort.", OFF_ON, "Quality of Life", NULL },
{ 't', 0, "Turdo Mode", NULL, "Replaces all offensive spells with \\\"turd balls\\\", weakens weapons, reduces enemies' Magic Def.", OFF_ON, "\\\"Fun\\\"", NULL },
};
enum option_indices {
openworld_idx,
accessible_idx,
spoilerlog_idx,
energy_core_idx,
alchemizer_idx,
bossdropamizer_idx,
gourdomizer_idx,
sniffamizer_idx,
mixedpool_idx,
sniffingredients_idx,
ingredienizer_idx,
callbeadamizer_idx,
doggomizer_idx,
pupdunk_idx,
/*enemizer_idx,*/
musicmizer_idx,
sequencebreaks_idx,
oob_idx,
cheats_idx,
ammoglitch_idx,
atlasglitch_idx,
wingsglitch_idx,
permastatus_idx,
shortbossrush_idx,
keepdog_idx,
shortdialogs_idx,
turdomode_idx,
};
#define D(IDX) options[ IDX ].def
#define O(IDX) option_values[ IDX ]
#define C(IDX) ( O(IDX) != D(IDX) )
#define openworld O(openworld_idx)
#define keepdog O(keepdog_idx)
#define sequencebreaks O(sequencebreaks_idx)
#define oob O(oob_idx)
#define cheats O(cheats_idx)
#define accessible O(accessible_idx)
#define ammoglitch O(ammoglitch_idx)
#define atlasglitch O(atlasglitch_idx)
#define wingsglitch O(wingsglitch_idx)
#define permastatus O(permastatus_idx)
#define shortdialogs O(shortdialogs_idx)
#define ingredienizer O(ingredienizer_idx)
#define alchemizer O(alchemizer_idx)
#define bossdropamizer O(bossdropamizer_idx)
#define gourdomizer O(gourdomizer_idx)
#define sniffamizer O(sniffamizer_idx)
#define mixedpool O(mixedpool_idx)
#define sniffingredients O(sniffingredients_idx)
#define callbeadamizer O(callbeadamizer_idx)
#define doggomizer O(doggomizer_idx)
#define pupdunk O(pupdunk_idx)
#define enemizer O(enemizer_idx)
#define musicmizer O(musicmizer_idx)
#define shortbossrush O(shortbossrush_idx)
#define turdomode O(turdomode_idx)
#define spoilerlog O(spoilerlog_idx)
#define energy_core O(energy_core_idx)
#define DEFAULT_SETTINGS() do {\
difficulty=DEFAULT_difficulty;\
for (size_t i=0; i<ARRAY_SIZE(options); i++)\
O(i) = D(i);\
} while (false)
#define SETTINGS2STR(s)\
do {\
char* t = s;\
assert(ARRAY_SIZE(s)>ARRAY_SIZE(options)+2);\
*t++ = 'r'; *t++ = DIFFICULTY_CHAR[difficulty];\
for (size_t i=0; i<ARRAY_SIZE(options); i++)\
if (C(i) && options[i].key) *t++ = ( (O(i)==FULL) ? toupper(options[i].key) : tolower(options[i].key) );\
*t++ = 0;\
} while (false)
#ifdef __EMSCRIPTEN__
struct preset {const char* name; const char* settings; int exp; int money;};
const static struct preset presets[] = {
{"First-Timer", "rel", 300, 300},
{"Beginner", "rel", 200, 250},
{"Advanced", "rlABGvCd67", 150, 200},
{"Pro", "rhlABGIvCD567", 125, 150},
{"Hell", "rhlABGOISvCp567f", 75, 75},
{"Menblock", "rhlABGOISvCp567f", 1, 1},
{"Turdo", "rxlABGoISvCDm567t", 125, 200},
{"Full Random", "rxlABGoISvCDm67", -1, -1} //-1 => weekly-esque random value
};
#endif
#ifdef NO_UI
#define _FLAGS "[-o <dst file.sfc>|-d <dst directory>] [--dry-run] [--money <money%%>] [--exp <exp%%>] " \
"[--available-fragments <n>] [--required-fragments <n>] "
#else
#define _FLAGS "[-b|-i] [-o <dst file.sfc>|-d <dst directory>] [--dry-run] [--money <money%%>] [--exp <exp%%>] "\
"[--available-fragments <n>] [--required-fragments <n>] [--mystery] "
#endif
#ifdef WITH_MULTIWORLD
#define FLAGS _FLAGS "[--id <128 hex nibbles>] [--placement <placement.txt>] [--death-link] "
#else
#define FLAGS _FLAGS
#endif
#define APPNAME "Evermizer"
#define ARGS " [settings [seed]]"
// The actual program
static void print_json(const char* str)
{
// escape \n to \\n
while (true) {
const char* lf = strchr(str, '\n');
if (lf) {
printf("%.*s\\n", (int)(lf-str), str);
str = lf+1;
} else {
printf("%s", str);
break;
}
}
}
static void print_usage(const char* appname)
{
fprintf(stderr, "Usage: %s " FLAGS "<src file.sfc>" ARGS "\n", appname);
fprintf(stderr, " %s --help show this output\n", appname);
fprintf(stderr, " %s --version print version\n", appname);
fprintf(stderr, " %s --verify <rom.sfc> check if rom is compatible\n", appname);
#ifndef __EMSCRIPTEN__
fprintf(stderr, " %s --settings show available settings\n", appname);
fprintf(stderr, " %s --settings.json above as json\n", appname);
#else
fprintf(stderr, " %s --settings.json available settings as json\n", appname);
fprintf(stderr, " %s --presets.json available presets as json\n", appname);
#endif
#if defined(WIN32) || defined(_WIN32)
fprintf(stderr, " or simply drag & drop your ROM onto the EXE\n");
#ifndef NO_UI
if (!batch) pause();
#endif
#endif
}
#ifndef __EMSCRIPTEN__
static void print_settings()
{
printf("%s %s settings:\n", APPNAME, VERSION);
printf("Difficulty:\n");
for (uint8_t i=0; i<ARRAY_SIZE(DIFFICULTY_CHAR); i++)
printf(" %c: %s%s\n", DIFFICULTY_CHAR[i], DIFFICULTY_NAME[i],
i==DEFAULT_difficulty?" (default)":"");
printf("Options:\n");
for (size_t i=0; i<ARRAY_SIZE(options); i++) {
const struct option* opt = options+i;
if (! opt->key || ! opt->state_names) continue;
const char* defaultTextModifier = strcmp(opt->state_names[!opt->def],"Off")==0 ? "No " :
strcmp(opt->state_names[!opt->def],"On")==0 ? "" :
opt->state_names[!opt->def];
const char* defaultTextSpace = (defaultTextModifier == opt->state_names[!opt->def]) ? " " : "";
printf(" %c: %s%s%s%s%s%s\n", tolower(opt->key), defaultTextModifier, defaultTextSpace,
opt->text, opt->info?" [":"", opt->info?opt->info:"", opt->info?"]":"");
if (opt->state_names[2])
printf(" %c: %s %s\n", toupper(opt->key), opt->state_names[2], opt->text);
if (opt->description)
printf(" %s\n", opt->description);
}
printf("\n");
}
#endif
static void print_settings_json()
{
printf("{\"Difficulty\": [\n");
for (uint8_t i=0; i<ARRAY_SIZE(DIFFICULTY_CHAR); i++) {
if (i != 0) printf(",\n");
printf(" [ \"%c\", \"%s\", %s ]", DIFFICULTY_CHAR[i], DIFFICULTY_NAME[i],
i==DEFAULT_difficulty?"true":"false");
}
printf("\n ],\n");
printf(" \"Difficulty Description\": "
"\"Judges item placement based on logic depth (amount of key items needed to reach the goal) "
"and gameplay difficulty (early call beads, strong weapons or spells, hard-to-reach-checks, etc.) "
"and will reroll until these parameters are within bounds for the selected difficulty. "
"Also affects spell cost with Ingredienizer.\\n"
"Easy will exclude key items at hidden gourds (behind walkthru walls and not near alchemist) "
"from Gourdomizer and make sure Atlas is usable before act 4.\\n"
"Normal will make sure Atlas is reachable before boss rush.\\n"
"Random will allow any seed.\",\n");
printf(" \"Options\": [\n");
bool first_opt = true;
for (size_t i=0; i<ARRAY_SIZE(options); i++) {
const struct option* opt = options+i;
if (! opt->key) continue;
if (!first_opt) printf(",\n");
first_opt = false;
printf(" [ \"%c\", \"%s%s%s%s\", %d, \"", opt->key, opt->text,
opt->info?" [":"", opt->info?opt->info:"", opt->info?"]":"",
opt->def);
print_json(opt->description?opt->description:"");
printf("\", [");
for (size_t j=0; opt->state_names && opt->state_names[j]; j++) {
if (j != 0) printf(", ");
printf("\"%s\"", opt->state_names[j]);
}
printf("], \"%s\", \"%s\" ]", opt->section, opt->subsection==NULL?"":opt->subsection);
}
printf("\n ],\n");
printf(" \"Args\": [\n");
printf(" [ \"exp\", \"Exp%%\", \"int\", \"100\", \"Character, alchemy and weapon experience modifier in percent. (1-2500)\", \"Accessibility\", \"\", 1, 2500 ],\n");
printf(" [ \"money\", \"Money%%\", \"int\", \"100\", \"Enemy money modifier in percent. (1-2500)\", \"Accessibility\", \"\", 1, 2500 ],\n");
printf(" [ \"required-fragments\", \"Required fragments\", \"int\", \"10\","
"\"Number of fragments required to get Energy Core in 'Fragment' mode. (1-99)\","
"\"General\", \"Key items\", 1, 99 ],\n");
printf(" [ \"available-fragments\", \"Available fragments\", \"int\", \"11\","
"\"Number of fragments placed in the world in 'Fragment' mode. (1-99)\","
"\"General\", \"Key items\", 1, 99 ]\n");
printf(" ]\n}\n\n");
}
#ifdef __EMSCRIPTEN__
static void print_presets_json()
{
printf("[\n");
bool first = true;
for (size_t i=0; i<ARRAY_SIZE(presets); i++) {
const struct preset* preset = presets+i;
if (! preset->name) continue;
if (!first) printf(",\n");
first = false;
printf(" {\n");
printf(" \"Name\": \"%s\",\n", preset->name);
printf(" \"Settings\": \"%s\",\n", preset->settings);
printf(" \"Args\": [\n");
printf(" [ \"exp\", %i],\n", preset->exp);
printf(" [ \"money\", %i]\n", preset->money);
printf(" ]\n");
printf(" }");
}
printf("\n]\n");
}
#endif
static void shuffle_pools(uint16_t* pool1, size_t len1, uint16_t* pool2, size_t len2, uint8_t strategy)
{
assert(len2<65536);
if (strategy == STRATEGY_BALANCED) {
// iterate over shorter pool1. 50:50 chance to swap with an item from pool2 of the same type (key / non-key)
size_t key2_count = count_real_progression_from_packed(pool2, len2);
size_t nonkey2_count = len2-key2_count;
assert(nonkey2_count>0);
// cache key and non-key item indices from pool2
size_t* key2_indices = calloc(key2_count, sizeof(size_t));
size_t* nonkey2_indices = calloc(nonkey2_count, sizeof(size_t));
for (size_t i=0, j=0, k=0; i<len2; i++) {
if (is_real_progression_from_packed(pool2[i])) {
assert(key2_count);
key2_indices[j++] = i;
} else {
nonkey2_indices[k++] = i;
}
}
for (size_t i=0; i<len1; i++) {
if (rand_u8(0,1)) {
if (is_real_progression_from_packed(pool1[i])) {
if (!key2_count)
continue;
// swap with key item from pool2
size_t n = rand_u16(0, key2_count-1);
SWAP(pool1[i], pool2[key2_indices[n]], uint16_t);
} else {
// swap with non-key item from pool2
size_t n = rand_u16(0, nonkey2_count-1);
SWAP(pool1[i], pool2[nonkey2_indices[n]], uint16_t);
}
}
}
// cleanup
free(key2_indices);
free(nonkey2_indices);
}
else if (strategy == STRATEGY_RANDOM) {
// iterate over shorter pool1. 50:50 chance to swap with *any* item from pool2
for (size_t i=0; i<len1; i++) {
if (rand_u8(0,1)) {
size_t j = rand_u16(0, (uint16_t)len2-1);
SWAP(pool1[i], pool2[j], uint16_t);
}
}
}
else if (strategy == STRATEGY_BOSSES) {
// pool1 has to be bosses
// iterate over pool2. swap all key items with non-key from pool2
size_t nonkey1 = len1-count_real_progression_from_packed(pool1,len1);
for (size_t i=0; i<len2; i++) {
if (nonkey1 == 0) {
int tmp1 = count_real_progression_from_packed(pool1, len1);
int tmp2 = count_real_progression_from_packed(pool2, len2);
printf("no more spots to fill (%d, %d)\n", tmp1, tmp2);
break;
}
if (is_real_progression_from_packed(pool2[i])) {
size_t n = rand_u16(0, (uint16_t)nonkey1-1);
size_t j = 0;
while (true) {
if (!is_real_progression_from_packed(pool1[j])) {
if (n == 0)
break;
n--;
}
j++;
assert(j < len1); // otherwise we miscounted somewhere
}
SWAP(pool1[j], pool2[i], uint16_t);
nonkey1--;
}
}
// iterate over pool1. 50:50 chance to swap non-key item
for (size_t i=0; i<len1; i++) {
if (!is_real_progression_from_packed(pool1[i])) {
if (rand_u8(0,1)) {
size_t j = rand_u16(0, (uint16_t)len2-1);
assert(!is_real_progression_from_packed(pool2[j])); // pool2 should not have any more key items
SWAP(pool1[i], pool2[j], uint16_t);
}
}
}
} else {
// bad strategy
assert(false);
}
}
int main(int argc, const char** argv)
{
#ifndef __EMSCRIPTEN__
const char* appname = argv[0];
#else
const char* appname = APPNAME;
#endif
// verify at least one argument is given
if (argc<2 || !argv[1] || !argv[1][0]) {
print_usage(appname);
exit(1);
}
uint8_t difficulty;
uint8_t option_values[ARRAY_SIZE(options)] = {0};
DEFAULT_SETTINGS();
bool verify = false; // verify ROM and exit
bool dry = false; // dry run: don't write ROM
const char* ofn = NULL;
const char* dstdir = NULL;
bool modeforced = false;
#if !defined NO_UI
bool interactive; // set later
#endif
uint8_t money_num = 0;
uint8_t money_den = 0;
uint8_t exp_num = 0;
uint8_t exp_den = 0;
uint8_t available_fragments = 11;
uint8_t required_fragments = 10;
bool mystery = false;
#ifdef WITH_MULTIWORLD
uint8_t id_data[64];
bool id_data_set = false;
const size_t id_loc = 0x3d0040;
const char* placement_file = NULL;
bool death_link = false;
#else
#define placement_file false
#define death_link false
#endif
// parse command line arguments
while (argc>1) {
if (strcmp(argv[1], "-b") == 0) {
modeforced = true;
batch = true;
#if !defined NO_UI
interactive = false;
#endif
argv++; argc--;
} else if (strcmp(argv[1], "-i") == 0) {
modeforced = true;
#if defined NO_UI
fprintf(stderr, "Requested interactive mode, but not compiled in!\n");
print_usage(appname);
exit(1);
#endif
argv++; argc--;
} else if (strcmp(argv[1], "-o") == 0) {
ofn = argv[2];
argv+=2; argc-=2;
if (dstdir) die("Can't have -o and -d\n");
} else if (strcmp(argv[1], "-d") == 0) {
dstdir = argv[2];
argv+=2; argc-=2;
if (ofn) die("Can't have -o and -d\n");
} else if (strcmp(argv[1], "--version") == 0) {
printf("%s\n", VERSION);
return 0;
} else if (strcmp(argv[1], "--help") == 0) {
print_usage(appname);
return 0;
#ifndef __EMSCRIPTEN__
} else if (strcmp(argv[1], "--settings") == 0) {
print_settings();
return 0;
#endif
} else if (strcmp(argv[1], "--settings.json") == 0) {
print_settings_json();
return 0;
#ifdef __EMSCRIPTEN__
} else if (strcmp(argv[1], "--presets.json") == 0) {
print_presets_json();
return 0;
#endif
} else if (strcmp(argv[1], "--verify") == 0) {
argv++; argc--;
verify=true;
} else if (strcmp(argv[1], "--dry-run") == 0) {
argv++; argc--;
dry=true;
} else if (strcmp(argv[1], "--money") == 0 && argc > 2) {
int money = atoi(argv[2]);
if (money>2500) money=2500; // limit to 25x
if (money>0) percent_to_u8_fraction(money, &money_num, &money_den);
argv+=2; argc-=2;
} else if (strcmp(argv[1], "--exp") == 0 && argc > 2) {
int exp = atoi(argv[2]);
if (exp>2500) exp=2500; // limit to 25x
if (exp>0) percent_to_u8_fraction(exp, &exp_num, &exp_den);
argv+=2; argc-=2;
} else if (strcmp(argv[1], "--required-fragments") == 0 && argc > 2) {
// NOTE: fragment count will be checked but ignored for normal core
int val = atoi(argv[2]);
if (val<1 || val>99) {
fprintf(stderr, "Required fragments has to be in 1..99\n");
break;
}
required_fragments = (uint8_t)val;
argv+=2; argc-=2;
} else if (strcmp(argv[1], "--available-fragments") == 0 && argc > 2) {
// NOTE: fragment count will be checked but ignored for normal core
int val = atoi(argv[2]);
if (val<1 || val>99) {
fprintf(stderr, "Available fragments has to be in 1..99\n");
break;
}
available_fragments = (uint8_t)val;
argv+=2; argc-=2;
#ifdef WITH_MULTIWORLD
} else if (strcmp(argv[1], "--id") == 0 && argc > 2) {
id_data_set = parse_id(id_data, sizeof(id_data), argv[2]);
if (!id_data_set) {
fprintf(stderr, "Error in id syntax\n");
break;
}
argv+=2; argc-=2;
} else if (strcmp(argv[1], "--placement") == 0 && argc > 2) {
placement_file = argv[2];
argv+=2; argc-=2;
} else if (strcmp(argv[1], "--death-link") == 0) {
argv++; argc--;
death_link = true;
#endif
} else if (strcmp(argv[1], "--mystery") == 0) {
argv++; argc--;
mystery = true;
} else {
break;
}
}
if (!modeforced) {
#if !defined NO_UI
interactive = argc<4;
#endif
}
// verify number of command line arguments
if (argc<2 || !argv[1] || !argv[1][0] || argc>4) {
print_usage(appname);
exit(1);
}
// parse settings command line argument
if (argc>=3) {
for (const char* s=argv[2]; *s; s++) {
char c = *s;
for (size_t i=0; i<ARRAY_SIZE(DIFFICULTY_CHAR); i++) {
if (c == DIFFICULTY_CHAR[i]) { difficulty = (uint8_t)i; c = 0; }
}
for (size_t i=0; i<ARRAY_SIZE(options); i++) {
if (options[i].key && c == tolower(options[i].key)) {
option_values[i] = options[i].def ? 0 : 1;
c=0; break;
}
if (options[i].key && c == toupper(options[i].key)) {
if (options[i].state_names && options[i].state_names[2]) {
option_values[i] = 2;
} else {
option_values[i] = options[i].def ? 0 : 1;
}
c=0; break;
}
}
if (c == 'r') DEFAULT_SETTINGS();
else if (c != 0) {
fprintf(stderr, "Unknown setting '%c' in \"%s\"\n",
c, argv[2]);
die(NULL);
}
}
if (turdomode)
ammoglitch=0;
}
// parse source file command line argument
const char* src = argv[1];
// load rom
FILE* fsrc = fopen(src,"rb");
if (!fsrc) die("Could not open input file!\n");
uint8_t zbuf[5] = {0,0,0,0,0};
if (fread(zbuf, sizeof(*zbuf), sizeof(zbuf), fsrc) && zbuf[0]=='P' &&
zbuf[1]=='K' && zbuf[2]==3 && zbuf[3]==4 && zbuf[4]>=10)
{
fclose(fsrc);
die("Compressed ROMs not supported! Please extract first!\n");
}
fseek(fsrc, 0L, SEEK_END);
size_t sz = ftell(fsrc);
if (sz != 3145728 && sz != 3145728+512 && sz != 3670016 && sz != 3670016+512) {
fclose(fsrc);
die("ROM has to be 3MB or 3.5MB SFC with or without header!\n");
}
fseek(fsrc, 0L, SEEK_SET);
const size_t rom_off = (sz == 3145728+512 || sz == 3670016+512) ? 512 : 0;
bool grow = false; // will be set by patches if gowing the rom is required
#define GROW_BY (4*1024*1024-sz+rom_off) // 0.5 or 1MB to a round 4MB
assert(GROW_BY == 1024*1024 || GROW_BY == 512*1024);
uint8_t* buf = (uint8_t*)malloc(sz+GROW_BY); // allow to grow by 1MB
memset(buf+sz, 0, GROW_BY); // or 0xff?
size_t len = fread(buf, 1, sz, fsrc);
if (len!=sz) { free(buf); fclose(fsrc); die("Could not read input file!\n"); }
// check ROM header
const char cart_header[] = "SECRET OF EVERMORE \x31\x02\x0c\x03\x01\x33\x00";
const char soe2p_header[]= "SoE 2-Player FuSoYa \x31\x02\x0c\x03\x01\x33\x00";
const size_t cart_header_loc = 0xFFC0;
bool is_2p = false;
if (memcmp((char*)buf + rom_off + cart_header_loc, cart_header, sizeof(cart_header)-1) == 0)
{
// ok
}
else if (memcmp((char*)buf + rom_off + cart_header_loc, soe2p_header, sizeof(soe2p_header)-1) == 0)
{
// SoE 2P
is_2p = true;
}
else
{
// unknown/unsupported rom
size_t i = rom_off+cart_header_loc + 0x15;
fprintf(stderr, "Wrong Header: %.21s %02x %02x %02x %02x %02x %02x %02x\n"
"Expected: SECRET OF EVERMORE 31 02 0c 03 01 33 00\n",
(char*)buf+rom_off+cart_header_loc,
buf[i+0], buf[i+1], buf[i+2], buf[i+3],
buf[i+4], buf[i+5], buf[i+6]);
free(buf);
fclose(fsrc);
die(NULL);
}
if (verify) {
printf("OK\n");
free(buf);
fclose(fsrc);
return 0;
}
// show command line settings in batch mode
char settings[ARRAY_SIZE(options)+3];
SETTINGS2STR(settings);
// random seed number
time_t t = 0;
srand64((uint64_t)time(&t));
uint64_t seed = rand64();
// parse command line seed number
if (argc>3 && argv[3] && isxdigit(argv[3][0]))
seed = strtoull(argv[3], NULL, 16);
// show UI in interactive mode
#ifndef NO_UI // TODO: UI for OW
if (interactive)
{
clrscr();
printf(APPNAME " " VERSION "\n");
if (argc<4) {
char seedbuf[18];
printf("Seed (ENTER for random): ");
fflush(stdout);
if (! fgets(seedbuf, sizeof(seedbuf), stdin)) die("\nAborting...\n");
if (isxdigit(seedbuf[0])) seed = strtoull(seedbuf, NULL, 16);
}
if (!exp_den) {
char expbuf[6];
printf("Exp%%: ");
if (!money_den) printf(" "); // align
fflush(stdout);
if (! fgets(expbuf, sizeof(expbuf), stdin)) die("\nAborting...\n");
int exp = atoi(expbuf);
if (exp>2500) exp=2500;
if (exp>0) percent_to_u8_fraction(exp, &exp_num, &exp_den);
}
if (!money_den) {
char moneybuf[6];
printf("Money%%: ");
fflush(stdout);
if (! fgets(moneybuf, sizeof(moneybuf), stdin)) die("\nAborting...\n");
int money = atoi(moneybuf);
if (money>2500) money=2500; // limit to 25x
if (money>0) percent_to_u8_fraction(money, &money_num, &money_den);
}
while (true) {
clrscr();
printf(APPNAME " " VERSION "\n");
printf("Seed: %" PRIx64 " Exp:%4d%% Money:%4d%%\n", seed,
u8_fraction_to_percent(exp_num,exp_den),
u8_fraction_to_percent(money_num,money_den));
SETTINGS2STR(settings);
printf("Settings: %-20s(Press R to reset)\n", settings);
printf("\n");
printf("Difficulty: %-9s(%c",
DIFFICULTY_NAME[difficulty], toupper(DIFFICULTY_CHAR[0]));
for (size_t i=1; i<ARRAY_SIZE(DIFFICULTY_CHAR); i++)
printf("/%c", toupper(DIFFICULTY_CHAR[i]));
printf(" to change)\n");
for (size_t i=0; i<ARRAY_SIZE(options); i++) {
const struct option* opt = options+i;
char col1[32]; snprintf(col1, sizeof(col1), "%s:", opt->text);
printf("%-20s %-9s%c%c%s%s%s%s\n", col1, opt->state_names[O(i)],
opt->key?'(':' ', opt->key?toupper(opt->key):' ', opt->key?" to toggle)":"",
opt->info?" [":"", opt->info?opt->info:"", opt->info?"]":"");
}
printf("\n");
printf("Press ESC to abort, ENTER to continue");
fflush(stdout);
char c = getch();
if (c == '\x1b' || c == EOF || c == '\x04' || tolower(c) == 'q') {
fclose(fsrc); free(buf);
die("\nAborting...\n");
}
if (c == '\r' || c == '\n') break;
c = tolower(c);
for (size_t i=0; i<ARRAY_SIZE(DIFFICULTY_CHAR); i++)
if (c == DIFFICULTY_CHAR[i]) difficulty = (uint8_t)i;
for (size_t i=0; i<ARRAY_SIZE(options); i++) {
if (c == options[i].key) {
option_values[i]++;
if (!options[i].state_names[option_values[i]])
option_values[i]=0;
}
}
if (c == 'r') DEFAULT_SETTINGS();
if (turdomode)
ammoglitch=0;
}
clrscr();
}
#endif
printf(APPNAME " " VERSION "\n");
printf("Seed: %" PRIx64 "\n", seed);
srand64(seed);
bool randomized = alchemizer || ingredienizer || bossdropamizer ||
gourdomizer || sniffamizer || doggomizer || callbeadamizer ||
sniffingredients || placement_file /*||enemizer*/;
bool randomized_difficulty = alchemizer || ingredienizer || bossdropamizer ||
gourdomizer;
printf("Settings: %-10s\n\n", settings);
if (placement_file) {
available_fragments = required_fragments;
} else if (energy_core == ENERGY_CORE_FRAGMENTS) {
// validate and fix up values for core fragments
// NOTE: with rising number of required fragments, difficulty balancing becomes near impossible
if (required_fragments > 40 && difficulty < 1) {
free(buf);
die("Easy only supports 40 fragments.\n");
}
if (required_fragments > 55 && difficulty < 2) {
free(buf);
die("Normal only supports 55 fragments.\n");
} else if (available_fragments < required_fragments) {
available_fragments = required_fragments;
fprintf(stderr, "Warning: Available fragments too low.\n"
"Changing available to required\n");
}
}
// define patches
#define DEF_LOC(n, location)\
const size_t PATCH_LOC##n = location
#define DEF(n, location, content)\
const size_t PATCH_LOC##n = location;\
const char PATCH##n[] = content
#include "patches.h" // hand-written c code patches
#include "gen.h" // generated from patches/
#include "doggo.h" // generated list of doggo changes
DEF(JUKEBOX_SJUNGLE, 0x938664 - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOX_RAPTORS_1, 0x9391fa - 0x800000, "\x29\x14\x03\x0f\x4d\x4d"); // CALL jukebox3, NOP, NOP
DEF(JUKEBOX_RATPROS_3, 0x938878 - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOX_DEFEND, 0x94e5b9 - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOX_NJUNGLE, 0x939664 - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOX_EJUNGLE, 0x93b28a - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOX_ECRUSTACIA, 0x95bb46 - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOX_ECRUSTACIAR, 0x95ba0b - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOX_CRUSTACIAFP, 0x97c125 - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOX_NOBILIAF, 0x95d72c - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOX_NOBILIAFP, 0x97c579 - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOX_STRONGHHE, 0x94e625 - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOK_SWAMPPEPPER, 0x94dde6 - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
DEF(JUKEBOK_SWAMPSLEEP, 0x94def3 - 0x800000, "\x29\x00\x03\x0f"); // CALL jukebox1
//DEF(JUKEBOX_MARKET1, 0x96b80a - 0x800000, "\x29\x70\x00\x0f"); // this requires different code if we really want to randomize this
//DEF(JUKEBOX_MARKET2, 0x96b80f - 0x800000, "\x29\x70\x00\x0f"); // ^
//DEF(JUKEBOX_NMARKET1, 0x95cb4e - 0x800000, "\x29\x70\x00\x0f"); // ^
//DEF(JUKEBOX_NMARKET2, 0x95cb53 - 0x800000, "\x29\x70\x00\x0f"); // ^
//DEF(JUKEBOX_SQUARE1, 0x95e216 - 0x800000, "\x29\x70\x00\x0f"); // ^
//DEF(JUKEBOX_SQUARE2, 0x95e21b - 0x800000, "\x29\x70\x00\x0f"); // ^
//DEF(JUKEBOX_RAPTORS_2, 0x9387b5 - 0x800000, "\x29\x79\x00\x0f\x4d\x4d"); // CALL jukebox2, NOP, NOP // can't change boss music :(
//DEF(JUKEBOX_PRISON, 0x98b0fa - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // raptors glitch out
//DEF(JUKEBOX_VOLCANO_PATH, 0x93ed69 - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // raptors glitch out
//DEF(JUKEBOX_BBM, 0x93c417 - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // bbm bridges glitch out
//DEF(JUKEBOX_WCRUSTACIA, 0x96bd85 - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // exploding rocks
//DEF(JUKEBOX_EHORACE, 0x96c4da - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // exploding rocks
//DEF(JUKEBOX_PALACEG, 0x96d636 - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // doggo fountain sounds
//DEF(JUKEBOX_FEGATHERING, 0x94c312 - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // naming character glitches out
//DEF(JUKEBOX_WSWAMP, 0x948999 - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // frippo sounds and leafpads
//DEF(JUKEBOX_SWAMP, 0x9492d5 - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // frippo sounds and leafpads
//DEF(JUKEBOX_ACIDRAIN, 0x93af47 - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // shopping menu glitches out
//DEF(JUKEBOX_STRONGHHI, 0x94e7cf - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // shopping menu glitches out
//DEF(JUKEBOX_BLIMPSCAVE, 0x95b377 - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // shopping menu glitches out
//DEF(JUKEBOX_FEVILLAGE, 0x94cea4 - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // shopping menu glitches out
//DEF(JUKEBOX_HALLS_MAIN, 0x9795af - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // disabled until tested
//DEF(JUKEBOX_HALLS_NE, 0x97a381 - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // disabled until tested
//DEF(JUKEBOX_HALLS_NE2, 0x97a16d - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // disabled until tested
//DEF(JUKEBOX_PALACE, 0x95d43f - 0x800000, "\x29\x70\x00\x0f"); // CALL jukebox1 // wall sounds in past-vigor cutscene glitch out
struct formula ingredients[ALCHEMY_COUNT];
// preset to vanilla for logic checking without ingredienizer
{
const uint8_t ingredient_types[] = INGREDIENT_TYPES; // includes laser
const uint8_t ingredient_amounts[] = INGREDIENT_AMOUNTS; // includes laser
for (size_t i=0; i<ALCHEMY_COUNT; i++) {
ingredients[i].type1 = ingredient_types[2*alchemy_locations[i].id+0];
ingredients[i].type2 = ingredient_types[2*alchemy_locations[i].id+1];
ingredients[i].amount1 = ingredient_amounts[2*alchemy_locations[i].id+0];
ingredients[i].amount2 = ingredient_amounts[2*alchemy_locations[i].id+1];
}
}
// NOTE: alchemy, boss_drops and gourd_drops are 6 msb type + 10 lsb index
// NOTE: the same init is repeated below during fill to reset everything
uint16_t alchemy[ALCHEMY_COUNT];
_Static_assert(ARRAY_SIZE(alchemy) == 34, "Bad alchemy count");
const uint16_t vanilla_boss_drops[] = BOSS_DROPS;
uint16_t gourd_drops[ARRAY_SIZE(gourd_drops_data)];
uint16_t boss_drops[ARRAY_SIZE(vanilla_boss_drops)];
// similar to traps and extras, we encode the actual item in the (lower 10 bits of) sniff drops
uint16_t sniff_drops[ARRAY_SIZE(sniff_data)];
// dog swap data
uint8_t doggo_map[ARRAY_SIZE(doggo_vals)]; // for non-full only
for (size_t i=0; i<ARRAY_SIZE(doggo_map); i++)
doggo_map[i] = doggo_vals[i];
uint8_t doggo_changes[ARRAY_SIZE(doggos)]; // preset to vanilla or pupdunk
for (size_t i=0; i<ARRAY_SIZE(doggo_changes); i++) {
doggo_changes[i] = (pupdunk) ? doggo_vals[0] : doggos[i].val;
}
// vanilla call bead spell / menu IDs, can be shuffled
uint8_t callbead_menus[] = {0,2,4,6};
uint16_t callbead_spells[] = {
0x00f8, 0x00fa, 0x00fc, 0x00fe, // fire eyes
0x0100, 0x0102, 0x0104, // horace (without regenerate and aura)
0x010a, 0x010c, 0x010e, 0x0110, // camellia
0x0112, 0x0114, 0x0116 // sidney
};
#ifdef WITH_MULTIWORLD
if (placement_file) {
for (size_t i=0; i<ALCHEMY_COUNT; i++)
alchemy[i] = (CHECK_ALCHEMY<<10) + (uint16_t)i;
for (size_t i=0; i<ARRAY_SIZE(boss_drops); i++)
boss_drops[i] = (CHECK_BOSS<<10) + vanilla_boss_drops[i];
for (size_t i=0; i<ARRAY_SIZE(gourd_drops); i++)
gourd_drops[i] = (CHECK_GOURD<<10) + (uint16_t)i;
if (sniffingredients) {
// fill sniff spots with random ingredients
for (size_t i=0; i<ARRAY_SIZE(sniff_drops); i++)
sniff_drops[i] = (CHECK_SNIFF<<10) + rand_u16(0x0200, 0x0200 + ARRAY_SIZE(ingredient_names) - 1);
} else {
// fill sniff spots with vanilla ingredients
for (size_t i=0; i<ARRAY_SIZE(sniff_drops); i++)
sniff_drops[i] = (CHECK_SNIFF<<10) + (sniff_data[i].item & 0x3ff); // NOTE: this is 0x01f for iron bracer
}
FILE* f = fopen(placement_file, "rb");
if (!f || fseek(f, 0, SEEK_END) != 0) {
free(buf);
die("Could not open placement file!\n");
}
long placement_size = ftell(f);
rewind(f);
char* placement = (char*)malloc(placement_size+1);
if (placement && placement_size>=0 && fread(placement, 1, placement_size, f) != (size_t)placement_size) {
free(buf);
free(placement);
fclose(f);
die("Error reading placement data!\n");
}
placement[placement_size] = 0;