-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathOneTileset-creator.py
1228 lines (964 loc) · 45.1 KB
/
OneTileset-creator.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
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
# 10/23/16
# The OneTileset Creator wizard for end users.
import json
import os
import os.path
import re
import sys
import time
import traceback
from PyQt5 import QtCore, QtGui, QtWidgets; Qt = QtCore.Qt
from PyQt5.Qt import PYQT_VERSION_STR
import nsmbulib
import nsmbulib.Sarc
import nsmbulib.Tileset
import nsmbulib.Yaz0
VERSION = '1.0.0'
ONE_TILESET_DIR_NAME = 'OneTileset'
ONE_TILESET_SCRIPTS = {
(True, True): 'oneTilesetScript_nsmbu_nslu.json',
(True, False): 'oneTilesetScript_nsmbu.json',
(False, True): 'oneTilesetScript_nslu.json',
}
########################################################################
########################### Translation Stuff ##########################
########################################################################
########################################################################
# Hardcoded translations are best because I want this script to have as
# few dependencies as possible, and because there's not much text here
# in the first place.
translations = {
'None': { # Translation template
# Please don't translate "OneTileset" or "OneTileset Creator".
# Thanks!
'Language': '',
'Select a language:': '',
'Welcome': '',
'Welcome to OneTileset Creator [version] (running on Python '
'[pyversion], PyQt [pyqtversion] and nsmbulib [nsmbulibversion])!'
'<br><br>'
'This program will create OneTileset from retail level files for you.'
'<br><br>'
'This program was written by a member of the Newer Team '
'(http://newerteam.com), and is not sanctioned by Nintendo in '
'any way.<br><br>'
"It looks like you have all of the required files ready, so let's "
'get started!': '',
'Select input directories': '',
'Select your <i>New Super Mario Bros. U</i> and/or '
'<i>New Super Luigi U</i> <code>course_res_pack</code> '
'folders below. Only a subset of OneTileset can be created '
'unless you have both.<br><br>'
"If you don't have either, you'll need to dump the files from "
'your game disk (or eShop download). Google "wii u ddd" or '
'"tcpgecko dump files" to learn about some homebrew apps that '
"can do this. Once you've dumped the files, the "
'<code>course_res_pack</code> folder will be in '
'<code>/data/content/Common</code> (for '
'<i>New Super Mario Bros. U</i>) or '
'<code>/data/content/RDashRes</code> (for '
'<i>New Super Luigi U</i>).': '',
'<code>course_res_pack</code> folder '
'(<i>New Super Mario Bros. U</i>):': '',
'<code>course_res_pack</code> folder '
'(<i>New Super Luigi U</i>):': '',
'Select': '',
'Select the course_res_pack folder from New Super Mario Bros. U': '',
'Select the course_res_pack folder from New Super Luigi U': '',
'Warning': '',
"This doesn't seem to be a correct "
'<code>course_res_pack</code> folder from '
'<i>New Super Mario Bros. U</i>, so it may not be used '
'during OneTileset creation.': '',
"This doesn't seem to be a correct "
'<code>course_res_pack</code> folder from '
'<i>New Super Luigi U</i>, so it may not be used '
'during OneTileset creation.': '',
'Select output directory': '',
'Select the directory you would like the new '
'<code>OneTileset</code> folder to be placed in.': '',
'Select a folder to save OneTileset to': '',
'Please confirm': '',
'Please review all of the information below before '
'continuing. Once you click "Start", you won\'t be able '
'to go back and change it.': '',
'New Super Mario Bros. U': '',
'New Super Luigi U': '',
'Level files will be read from <code>[path]</code>': '',
'Will not be processed. (The OneTileset output '
"will be usable, but will lack this game's objects.)": '',
'Output will be written to <code>[path]</code>': '',
'Creating OneTileset': '',
'Please wait; this will take a while.': '',
'Setting up': '',
'Loading <code>[path]</code>': '',
'Processing <code>[tset]</code> from <code>[path]</code>': '',
'Finished.': '',
'Error': '',
'The following error occurred:<br><br>'
'<code>[traceback]</code><br><br>'
'OneTileset Creator will now close. Sorry. To '
'troubleshoot, first ensure that Python, PyQt5 and '
'nsmbulib are all updated to the latest versions.<br><br>'
'If the problem persists, screenshot this error message, '
'run OneTileset Creator again and screenshot the Welcome '
'page. Then post both screenshots to the OneTileset '
'Creator Help thread on http://rhcafe.us.to/ . (You can '
'use http://imgur.com to upload the screenshots.) Someone '
'will help you out there.': '',
'Finished': '',
'All done! Your new OneTileset folder is<br><br>'
'<code>[path]</code><br><br>'
'Please remember that the data in your OneTileset folder is '
'copyrighted by Nintendo. Do not redistribute it.<br><br>'
'Have a great day!': '',
},
'Polski': { # Polish (contributed by DS)
'Language': 'Język',
'Select a language:': 'Wybierz język:',
'Welcome': 'Hej!',
'Welcome to OneTileset Creator [version] (running on Python '
'[pyversion], PyQt [pyqtversion] and nsmbulib [nsmbulibversion])!'
'<br><br>'
'This program will create OneTileset from retail level files for you.'
'<br><br>'
'This program was written by a member of the Newer Team '
'(http://newerteam.com), and is not sanctioned by Nintendo in '
'any way.<br><br>'
"It looks like you have all of the required files ready, so let's "
'get started!':
'Witaj w programie OneTileset Creator [version] (działającym na '
'Pythonie [pyversion], PyQt [pyqtversion] i nsmbulib '
'[nsmbulibversion])!<br><br>'
'Stworzy on OneTileset z plików oficjalnej gry.<br><br>'
'Program ten został napisany przez członka Newer Team '
'(http://newerteam.com), i nie został w żaden sposób aprobowany przez '
'Nintendo. <br><br>Wygląda na to że masz już wszystkie potrzebne '
'pliki, więc zaczynajmy!',
'Select input directories': 'Wybierz folder z plikami wejścia',
'Select your <i>New Super Mario Bros. U</i> and/or '
'<i>New Super Luigi U</i> <code>course_res_pack</code> '
'folders below. Only a subset of OneTileset can be created '
'unless you have both.<br><br>'
"If you don't have either, you'll need to dump the files from "
'your game disk (or eShop download). Google "wii u ddd" or '
'"tcpgecko dump files" to learn about some homebrew apps that '
"can do this. Once you've dumped the files, the "
'<code>course_res_pack</code> folder will be in '
'<code>/data/content/Common</code> (for '
'<i>New Super Mario Bros. U</i>) or '
'<code>/data/content/RDashRes</code> (for '
'<i>New Super Luigi U</i>).':
'Wybierz foldery zawierające pliki z folderu '
'<code>course_res_pack</code> z <i>New Super Mario Bros. U</i> '
'i/lub <i>New Super Luigi U</i> poniżej. Tylko część OneTileset może '
'być utworzona jeśli nie posiadasz obu tych folderów.<br><br>'
'Jeśli nie masz żadnych z nich, to musisz zgrać pliki ze swojej płyty '
'z grą lub z wersji ściągniętej z serwisu eShop. Wygoogluj '
'"wii u ddd" i "tcp gecko dump files" by dowiedzieć się jakie '
'programy mogą to zrobić. Kiedy już zgrasz pliki, folder pod nazwą '
'<code>course_res_pack</code> będzie w '
'<code>/data/content/Common</code> (w przypadku '
'<i>New Super Mario Bros. U</i>) lub '
'<code>/data/content/RDashRes</code> (w przypadku '
'<i>New Super Luigi U</i>).',
'<code>course_res_pack</code> folder '
'(<i>New Super Mario Bros. U</i>):':
'Folder <code>course_res_pack</code> '
'(<i>New Super Mario Bros. U</i>):',
'<code>course_res_pack</code> folder '
'(<i>New Super Luigi U</i>):':
'Folder <code>course_res_pack</code> '
'(<i>New Super Luigi U</i>):',
'Select': 'Wybierz',
'Select the course_res_pack folder from New Super Mario Bros. U':
'Wybierz folder course_res_pack z New Super Mario Bros. U',
'Select the course_res_pack folder from New Super Luigi U':
'Wybierz folder course_res_pack z New Super Luigi U',
'Warning': 'Ostrzeżenie',
"This doesn't seem to be a correct "
'<code>course_res_pack</code> folder from '
'<i>New Super Mario Bros. U</i>, so it may not be used '
'during OneTileset creation.':
'To nie wygląda na prawidłowy folder '
'<code>course_res_pack</code> z '
'<i>New Super Mario Bros. U</i>, więc może nie zostać użyty '
'podczas tworzenia OneTileset.',
"This doesn't seem to be a correct "
'<code>course_res_pack</code> folder from '
'<i>New Super Luigi U</i>, so it may not be used '
'during OneTileset creation.':
"To nie wygląda na prawidłowy folder "
'<code>course_res_pack</code> z '
'<i>New Super Luigi. U</i>, więc może nie zostać użyty '
'podczas tworzenia OneTileset.',
'Select output directory': 'Wybierz folder wyjścia',
'Select the directory you would like the new '
'<code>OneTileset</code> folder to be placed in.':
'Wybierz folder w którym ma być zapisany <code>OneTileset</code>.',
'Select a folder to save OneTileset to':
'Wybierz folder w którym ma być zapisany OneTileset',
'Please confirm': 'Potwierdź',
'Please review all of the information below before '
'continuing. Once you click "Start", you won\'t be able '
'to go back and change it.':
'Przeanalizuj informacje poniżej zanim kontynuujesz. '
'Po naciśnięciu "Start" nie będzie już można wrócić i ich '
'zmienić.',
'New Super Mario Bros. U': 'New Super Mario Bros. U',
'New Super Luigi U': 'New Super Luigi U',
'Level files will be read from <code>[path]</code>':
'Pliki poziomów będą wzięte z <code>[path]</code>',
'Will not be processed. (The OneTileset output '
"will be usable, but will lack this game's objects.)":
'Te pliki nie będą przetworzone. (Zapisany OneTileset '
'będzie używalny, ale nie będzie zawierał obiektów z tej gry.)',
'Output will be written to <code>[path]</code>':
'OneTileset będzie zapisany w <code>[path]</code>',
'Creating OneTileset': 'Tworzenie OneTileset',
'Please wait; this will take a while.': 'Czekaj, to trochę potrwa...',
'Setting up': 'Przygotowywanie',
'Loading <code>[path]</code>': 'Ładowanie <code>[path]</code>',
'Processing <code>[tset]</code> from <code>[path]</code>':
'Przetwarzanie <code>[tset]</code> z <code>[path]</code>',
'Finished.': 'Ukończono.',
'Error': 'Błąd',
'The following error occurred:<br><br>'
'<code>[traceback]</code><br><br>'
'OneTileset Creator will now close. Sorry. To '
'troubleshoot, first ensure that Python, PyQt5 and '
'nsmbulib are all updated to the latest versions.<br><br>'
'If the problem persists, screenshot this error message, '
'run OneTileset Creator again and screenshot the Welcome '
'page. Then post both screenshots to the OneTileset '
'Creator Help thread on http://rhcafe.us.to/ . (You can '
'use http://imgur.com to upload the screenshots.) Someone '
'will help you out there.':
'Wystąpił następujący błąd:<br><br>'
'<code>[traceback]</code><br><br>'
'Program OneTileset Creator będzie zamknięty. Przepraszamy. '
'Upewnij się że Python, PyQt5 i nsmbulib są zaktualizowane do '
'najnowszych wersji.<br><br>'
'Jeśli problem się powtórzy, zrób zrzut ekranu tej wiadomości, '
'po czym uruchom program OneTileset Creator jeszcze raz i zrób zrzut '
'ekranu powitalnego. Wstaw oba te zrzuty ekranów na wątek pomocy '
'programu OneTileset Creator na http://rhcafe.us.to/ . (Możesz '
'wrzucić je na http://imgur.com.). Ktoś ci tam pomoże.',
'Finished': 'Ukończono',
'All done! Your new OneTileset folder is<br><br>'
'<code>[path]</code><br><br>'
'Please remember that the data in your OneTileset folder is '
'copyrighted by Nintendo. Do not redistribute it.<br><br>'
'Have a great day!':
'Wszystko gotowe! Twój folder OneTileset jest w<br><br>'
'<code>[path]</code><br><br>'
'Pamiętaj że wszelkie dane w folderze OneTileset są objęte '
'prawem autorskim i należą do Nintendo. Nie rozprowadzaj '
'ich.<br><br>Miłego dnia!',
},
'Nederlands': { # Dutch (contributed by Grop)
'Language': 'Taal',
'Select a language:': 'Selecteer een taal:',
'Welcome': 'Welkom',
'Welcome to OneTileset Creator [version] (running on Python '
'[pyversion], PyQt [pyqtversion] and nsmbulib [nsmbulibversion])!'
'<br><br>'
'This program will create OneTileset from retail level files for you.'
'<br><br>'
'This program was written by a member of the Newer Team '
'(http://newerteam.com), and is not sanctioned by Nintendo in '
'any way.<br><br>'
"It looks like you have all of the required files ready, so let's "
'get started!':
'Welkom bij OneTileset Creator [version] (op Python [pyversion], '
'PyQt [pyqtversion] en nsmbulib [nsmbulibversion])!<br><br>'
'Dit programma genereert OneTileset uit de originele levelbestanden.'
'<br><br>'
'Dit programma is geschreven door een lid van het Newer Team '
'(http://newerteam.com), en wordt op geen enkele wijze door Nintendo '
'ondersteund.<br><br>'
'Het lijkt erop dat je alle benodigde bestanden al klaar hebt staan, '
'dus laten we beginnen!',
'Select input directories': 'Selecteer mappen met levelbestanden',
'Select your <i>New Super Mario Bros. U</i> and/or '
'<i>New Super Luigi U</i> <code>course_res_pack</code> '
'folders below. Only a subset of OneTileset can be created '
'unless you have both.<br><br>'
"If you don't have either, you'll need to dump the files from "
'your game disk (or eShop download). Google "wii u ddd" or '
'"tcpgecko dump files" to learn about some homebrew apps that '
"can do this. Once you've dumped the files, the "
'<code>course_res_pack</code> folder will be in '
'<code>/data/content/Common</code> (for '
'<i>New Super Mario Bros. U</i>) or '
'<code>/data/content/RDashRes</code> (for '
'<i>New Super Luigi U</i>).':
'Selecteer de <i>New Super Mario Bros. U</i> en/of '
'<i>New Super Luigi U</i> <code>course_res_pack</code> mappen '
'hieronder. Slechts een gedeelte van OneTileset kan worden aangemaakt '
'als je niet beide mappen hebt.<br><br>'
'Als je geen van beide mappen hebt, moet je de bestanden van het spel '
'"dumpen". Google "wii u ddd" of "tcpgecko dump files" om meer te '
'weten te komen over enkele homebrew applicaties die dit kunnen doen. '
'Als je de bestanden hebt bemachtigd, bevindt de '
'<code>course_res_pack</code> map zich in '
'<code>/data/content/Common</code> (voor '
'<i>New Super Mario Bros. U</i>) of '
'<code>/data/content/RDashRes</code> (voor <i>New Super Luigi U</i>).',
'<code>course_res_pack</code> folder '
'(<i>New Super Mario Bros. U</i>):':
'<code>course_res_pack</code> map '
'(<i>New Super Mario Bros. U</i>):',
'<code>course_res_pack</code> folder '
'(<i>New Super Luigi U</i>):':
'<code>course_res_pack</code> map '
'(<i>New Super Luigi U</i>):',
'Select': 'Selecteer',
'Select the course_res_pack folder from New Super Mario Bros. U':
'Selecteer de map "course_res_pack" van New Super Mario Bros. U',
'Select the course_res_pack folder from New Super Luigi U':
'Selecteer de map "course_res_pack" van New Super Luigi U',
'Warning': 'Waarschuwing',
"This doesn't seem to be a correct "
'<code>course_res_pack</code> folder from '
'<i>New Super Mario Bros. U</i>, so it may not be used '
'during OneTileset creation.':
'Dit lijkt een foutieve '
'<code>course_res_pack</code> map van '
'<i>New Super Mario Bros. U</i> te zijn, dus het wordt '
'niet gebruikt tijdens het genereren van OneTileset.',
"This doesn't seem to be a correct "
'<code>course_res_pack</code> folder from '
'<i>New Super Luigi U</i>, so it may not be used '
'during OneTileset creation.':
'Dit lijkt een foutieve '
'<code>course_res_pack</code> map van '
'<i>New Super Luigi U</i> te zijn, dus het wordt '
'niet gebruikt tijdens het genereren van OneTileset.',
'Select output directory':
'Selecteer de map waarin het resultaat geplaatst wordt',
'Select the directory you would like the new '
'<code>OneTileset</code> folder to be placed in.':
'Selecteer de map waar <code>OneTileset</code> in aangemaakt '
'moet worden.',
'Select a folder to save OneTileset to':
'Selecteer een map waar OneTileset in opgeslagen moet worden',
'Please confirm': 'Controleer de gegevens',
'Please review all of the information below before '
'continuing. Once you click "Start", you won\'t be able '
'to go back and change it.':
'Controleer alsjeblieft alles wat je hebt ingevuld voordat je '
'op "Start" drukt. Als je eenmaal begonnen bent, kun je niets '
'meer veranderen.',
'New Super Mario Bros. U': 'New Super Mario Bros. U',
'New Super Luigi U': 'New Super Luigi U',
'Level files will be read from <code>[path]</code>':
'De levelbestanden zullen worden gelezen uit <code>[path]</code>',
'Will not be processed. (The OneTileset output '
"will be usable, but will lack this game's objects.)":
'Wordt niet behandeld. (Het resultaat zal bruikbaar zijn, maar '
'deze objecten uit het spel zullen ontbreken.)',
'Output will be written to <code>[path]</code>':
'Het resultaat wordt geplaatst in <code>[path]</code>',
'Creating OneTileset': 'OneTileset aan het genereren...',
'Please wait; this will take a while.':
'Heb geduld; dit gaat wel eventjes duren...',
'Setting up': 'Alles klaarmaken voor gebruik...',
'Loading <code>[path]</code>': '<code>[path]</code> aan het laden...',
'Processing <code>[tset]</code> from <code>[path]</code>':
'<code>[tset]</code> uit <code>[path]</code> aan het behandelen...',
'Finished.': 'Klaar!',
'Error': 'Error',
'The following error occurred:<br><br>'
'<code>[traceback]</code><br><br>'
'OneTileset Creator will now close. Sorry. To '
'troubleshoot, first ensure that Python, PyQt5 and '
'nsmbulib are all updated to the latest versions.<br><br>'
'If the problem persists, screenshot this error message, '
'run OneTileset Creator again and screenshot the Welcome '
'page. Then post both screenshots to the OneTileset '
'Creator Help thread on http://rhcafe.us.to/ . (You can '
'use http://imgur.com to upload the screenshots.) Someone '
'will help you out there.':
'De volgende fout is opgetreden:<br><br>'
'<code>[traceback]</code><br><br>'
'OneTileset Creator zal nu stoppen. Sorry. Om het '
'probleem op te lossen, controleer of Python, PyQt5 '
'en nsmbulib allemaal up-to-date zijn.<br><br>'
'Als het probleem aanhoudt, maak dan een screenshot '
'van deze foutmelding en start OneTileset Creator opnieuw '
'op en maak een screenshot van het welkomstscherm. Post '
'beide screenshots in de (Engelstalige) OneTileset hulp-thread '
'op http://rhcafe.us.to/ . (Je kunt http://imgur.com gebruiken '
'voor het uploaden van beide screenshots.) Iemand daar zal je '
'verder helpen.',
'Finished': 'Klaar',
'All done! Your new OneTileset folder is<br><br>'
'<code>[path]</code><br><br>'
'Please remember that the data in your OneTileset folder is '
'copyrighted by Nintendo. Do not redistribute it.<br><br>'
'Have a great day!':
'Helemaal klaar! Jouw nieuwe OneTileset map is<br><br>'
'<code>[path]</code><br><br>'
'Onthoud dat de bestanden in die map onder het auteursrecht '
'van Nintendo vallen. Geef deze bestanden dus niet weg.<br><br>'
'Fijne dag nog!',
},
}
del translations['None']
currentTranslation = ''
def _(englishString, **replacements):
"""
Very simple translation function.
"""
def doReplacements(original):
"""
Based on
https://code.activestate.com/recipes/81330-single-pass-multiple-replace/
. Replace in 'text' all occurences of any key in the given
dictionary by its corresponding value. Returns the new string.
"""
if not replacements: return original
# Create a regular expression from the dictionary keys
replacements2 = {'[%s]' % k: v for k, v in replacements.items()}
regex = re.compile(
"(%s)" % "|".join(map(re.escape, replacements2.keys())))
# For each match, look-up corresponding value in dictionary
return regex.sub(
lambda mo: replacements2[mo.string[mo.start():mo.end()]],
original)
if currentTranslation in translations:
if englishString in translations[currentTranslation]:
return doReplacements(
translations[currentTranslation][englishString])
return doReplacements(englishString)
_dynTransRegistry = []
def dynTrans(updateTextFunction, englishString, **kwargs):
"""
Register an update-text function with the dynamic-text-update system,
and provide an English string for it. The update-text function will
be called whenever the current app translation changes.
"""
_dynTransRegistry.append([updateTextFunction, englishString, kwargs])
updateTextFunction(_(englishString, **kwargs))
def modifyDynTrans(updateTextFunction, englishString, **kwargs):
"""
Replace the English string associated with this
previously-registered update-text function.
"""
for entry in _dynTransRegistry:
if entry[0] == updateTextFunction:
entry[1] = englishString
entry[2] = kwargs
entry[0](englishString, **kwargs)
return
def updateDynTrans():
"""
Update all dynamic translations.
"""
for updateFxn, engStr, kwargs in _dynTransRegistry:
updateFxn(_(engStr, **kwargs))
########################################################################
############################ File Processing ###########################
########################################################################
########################################################################
def export(object, name, path, info):
"""
Export object `object` to path `path`, with name `name` and info `info`.
"""
object.name = name
object.role = object.Role(info.get('role', '?'))
object.decorative = info.get('decorative', False)
object.description = info.get('description', '')
if not os.path.isdir(path):
os.makedirs(path)
objectFiles = object.asNewFormat()
for fn, fd in objectFiles.items():
fullFn = os.path.join(path, fn)
with open(fullFn, 'wb') as f:
f.write(fd)
def createOneTileset(updateProgress, nsmbuPath, nsluPath, outputPath):
"""
This is the actual main function that creates OneTileset.
`updateProgress` should be a callback that takes two parameters:
a string describing the current operation and an amount-done value
between 0 and 1. ONE_TILESET_DIR_NAME will be appended to
`outputPath` for you.
"""
updateProgress(_('Setting up'), 0)
# Sanitize paths and append ONE_TILESET_DIR_NAME to the output path
if not os.path.isdir(outputPath):
raise RuntimeError('The output path %s does not exist.' % outputPath)
outputPath = os.path.join(outputPath, ONE_TILESET_DIR_NAME)
if judgeFolder(nsmbuPath, 'NSMBU') < 1: nsmbuPath = None
if judgeFolder(nsluPath, 'NSLU') < 1: nsluPath = None
# Load the appropriate oneTilesetScript_*.json file
fn = ONE_TILESET_SCRIPTS[(nsmbuPath is not None, nsluPath is not None)]
with open(fn, 'r', encoding='utf-8') as f:
script = json.load(f)
# Count the total number of levels and tilesets we'll have to open.
# We count these together, and increment a counter whenever we
# finish loading either one.
totalThingsToLoad = 0
for levels in script.values():
for tilesets in levels.values():
totalThingsToLoad += len(tilesets) + 1
thingsLoaded = -1
for game, courseResPack in [('NSMBU', nsmbuPath), ('NSLU', nsluPath)]:
if courseResPack is None: continue
if game not in script: continue
for levelName, tilesets in script[game].items():
for levelFN in [levelName + '.szs', levelName + '.sarc']:
levelPath = os.path.join(courseResPack, levelFN)
if os.path.isfile(levelPath): break
else:
raise RuntimeError(levelName + ' could not be found.')
thingsLoaded += 1
updateProgress(
_('Loading <code>[path]</code>', path=levelPath),
thingsLoaded / totalThingsToLoad)
with open(levelPath, 'rb') as f:
levelData = f.read()
if nsmbulib.Yaz0.isCompressed(levelData):
levelData = nsmbulib.Yaz0.decompress(levelData)
level = nsmbulib.Sarc.load(levelData)
for tilesetName, objectDefs in tilesets.items():
thingsLoaded += 1
updateProgress(_(
'Processing <code>[tset]</code> from <code>[path]</code>',
tset=tilesetName, path=levelPath),
thingsLoaded / totalThingsToLoad)
if tilesetName not in level:
raise RuntimeError(tilesetName + ' could not be found.')
tileset = nsmbulib.Tileset.load(level[tilesetName])
for objectNum, objectDef in objectDefs.items():
objectName, newPath = objectDef['name'], objectDef['path']
del objectDef['name']; del objectDef['path']
export(
tileset[int(objectNum)],
objectName,
os.path.join(outputPath, newPath),
objectDef)
updateProgress(_('Finished.'), 1)
def judgeFolder(folder, game='NSMBU'):
"""
Check the validity of the folder given. Return codes are as follows:
-1: The folder is nonexistent.
0: The folder exists, but is missing some or all of the level files.
1: Everything checks out.
"""
if not os.path.isdir(folder):
return -1
# Try to ensure that the user actually selected the real folder.
# Here are some levels I picked somewhat arbitrarily that we can
# check for.
samples = ['1-1', '1-2', '2-1', '1-58', '7-20', '8-43', '9-9']
if game == 'NSMBU': samples += ['11-1', '13-2', '18-8']
for sample in samples:
for ext in ['.szs', '.sarc']:
if os.path.isfile(os.path.join(folder, sample + ext)):
break
else:
return 0
return 1
########################################################################
############################## Wizard GUI ##############################
########################################################################
########################################################################
def createLanguagePage(wizard):
"""
Create the select-your-language wizard page.
"""
page = QtWidgets.QWizardPage(wizard)
dynTrans(page.setTitle, 'Language')
# Introduction text label
label = QtWidgets.QLabel(page)
label.setWordWrap(True)
dynTrans(label.setText, 'Select a language:')
# Language-select dropdown
def dropdownChanged(newValue):
global currentTranslation
currentTranslation = newValue
updateDynTrans()
dropdown = QtWidgets.QComboBox(page)
dropdown.addItems(['English'] + sorted(translations))
dropdown.currentTextChanged.connect(dropdownChanged)
# Layout
L = QtWidgets.QVBoxLayout(page)
L.addWidget(label)
L.addWidget(dropdown)
return page
def createWelcomePage(wizard):
"""
Create the welcome wizard page.
"""
page = QtWidgets.QWizardPage(wizard)
dynTrans(page.setTitle, 'Welcome')
pyversion = sys.version.replace('\n', ' ')
# Welcome text label
label = QtWidgets.QLabel(page)
label.setWordWrap(True)
dynTrans(label.setText,
'Welcome to OneTileset Creator [version] (running on Python '
'[pyversion], PyQt [pyqtversion] and nsmbulib [nsmbulibversion])!'
'<br><br>'
'This program will create OneTileset from retail level files for you.'
'<br><br>'
'This program was written by a member of the Newer Team '
'(http://newerteam.com), and is not sanctioned by Nintendo in '
'any way.<br><br>'
"It looks like you have all of the required files ready, so let's "
'get started!',
version=VERSION, pyversion=pyversion, pyqtversion=PYQT_VERSION_STR,
nsmbulibversion="1.0")
# Layout
L = QtWidgets.QVBoxLayout(page)
L.addWidget(label)
return page
class ChooseInputPage(QtWidgets.QWizardPage):
"""
A wizard page that lets you choose input directories.
"""
def __init__(self, wizard):
super().__init__(wizard)
# 320 allows all the intro text to fit... in KDE5, at least
self.setMinimumHeight(320)
dynTrans(self.setTitle, 'Select input directories')
# Intro text label
introLabel = QtWidgets.QLabel(self)
introLabel.setWordWrap(True)
dynTrans(introLabel.setText,
'Select your <i>New Super Mario Bros. U</i> and/or '
'<i>New Super Luigi U</i> <code>course_res_pack</code> '
'folders below. Only a subset of OneTileset can be created '
'unless you have both.<br><br>'
"If you don't have either, you'll need to dump the files from "
'your game disk (or eShop download). Google "wii u ddd" or '
'"tcpgecko dump files" to learn about some homebrew apps that '
"can do this. Once you've dumped the files, the "
'<code>course_res_pack</code> folder will be in '
'<code>/data/content/Common</code> (for '
'<i>New Super Mario Bros. U</i>) or '
'<code>/data/content/RDashRes</code> (for '
'<i>New Super Luigi U</i>).')
# course_res_pack (NSMBU) label
nsmbuLabel = QtWidgets.QLabel(self)
nsmbuLabel.setWordWrap(True)
dynTrans(nsmbuLabel.setText,
'<code>course_res_pack</code> folder '
'(<i>New Super Mario Bros. U</i>):')
# course_res_pack (NSLU) label
nsluLabel = QtWidgets.QLabel(self)
nsluLabel.setWordWrap(True)
dynTrans(nsluLabel.setText,
'<code>course_res_pack</code> folder '
'(<i>New Super Luigi U</i>):')
# Folder-select line edits
nsmbuLE = QtWidgets.QLineEdit(self)
nsmbuLE.textChanged.connect(self.completeChanged)
wizard.chooseInputNSMBUEdit = nsmbuLE
nsluLE = QtWidgets.QLineEdit(self)
nsluLE.textChanged.connect(self.completeChanged)
wizard.chooseInputNSLUEdit = nsluLE
# Folder-select buttons
nsmbuSelBtn = QtWidgets.QPushButton(self)
dynTrans(nsmbuSelBtn.setText, 'Select')
nsmbuSelBtn.clicked.connect(self.nsmbuSelBtnClicked)
nsluSelBtn = QtWidgets.QPushButton(self)
dynTrans(nsluSelBtn.setText, 'Select')
nsluSelBtn.clicked.connect(self.nsluSelBtnClicked)
# Layout
nsmbuEntryLyt = QtWidgets.QHBoxLayout()
nsmbuEntryLyt.addWidget(nsmbuLE)
nsmbuEntryLyt.addWidget(nsmbuSelBtn)
nsluEntryLyt = QtWidgets.QHBoxLayout()
nsluEntryLyt.addWidget(nsluLE)
nsluEntryLyt.addWidget(nsluSelBtn)
L = QtWidgets.QVBoxLayout(self)
L.addWidget(introLabel)
L.addWidget(nsmbuLabel)
L.addLayout(nsmbuEntryLyt)
L.addWidget(nsluLabel)
L.addLayout(nsluEntryLyt)
def nsmbuSelBtnClicked(self):
"""
The "Select" button was clicked for the NSMBU line edit.
"""
fn = QtWidgets.QFileDialog.getExistingDirectory(self,
_('Select the course_res_pack folder from '
'New Super Mario Bros. U'),
self.wizard().chooseInputNSMBUEdit.text(),
)
if not fn: return
self.wizard().chooseInputNSMBUEdit.setText(fn)
judge = judgeFolder(fn, 'NSMBU')
if judge == 0:
QtWidgets.QMessageBox.warning(self, _('Warning'),
_("This doesn't seem to be a correct "
'<code>course_res_pack</code> folder from '
'<i>New Super Mario Bros. U</i>, so it may not be used '
'during OneTileset creation.'))
def nsluSelBtnClicked(self):
"""
The "Select" button was clicked for the NSLU line edit.
"""
fn = QtWidgets.QFileDialog.getExistingDirectory(self,
_('Select the course_res_pack folder from '
'New Super Luigi U'),
self.wizard().chooseInputNSLUEdit.text(),
)
if not fn: return
self.wizard().chooseInputNSLUEdit.setText(fn)
judge = judgeFolder(fn, 'NSLU')
if judge == 0:
QtWidgets.QMessageBox.warning(self, _('Warning'),
_("This doesn't seem to be a correct "
'<code>course_res_pack</code> folder from '
'<i>New Super Luigi U</i>, so it may not be used '
'during OneTileset creation.'))
def isComplete(self):
"""
Return True if a valid NSMBU and/or NSLU course_res_pack
directory has been entered, or False otherwise
"""
nsmbuCRP = self.wizard().chooseInputNSMBUEdit.text()
nsluCRP = self.wizard().chooseInputNSLUEdit.text()
return (judgeFolder(nsmbuCRP, 'NSMBU') == 1
or judgeFolder(nsluCRP, 'NSLU') == 1)
class ChooseOutputPage(QtWidgets.QWizardPage):
"""
A wizard page that lets you choose the OneTileset directory
"""
def __init__(self, wizard):
super().__init__(wizard)
dynTrans(self.setTitle, 'Select output directory')
# Intro text label
introLabel = QtWidgets.QLabel(self)
introLabel.setWordWrap(True)
dynTrans(introLabel.setText,
'Select the directory you would like the new '
'<code>OneTileset</code> folder to be placed in.')
# Folder-select line edit
le = QtWidgets.QLineEdit(self)
le.textChanged.connect(self.completeChanged)
wizard.chooseOutputEdit = le
# Folder-select button
selBtn = QtWidgets.QPushButton(self)
dynTrans(selBtn.setText, 'Select')
selBtn.clicked.connect(self.selBtnClicked)
# Layout
L = QtWidgets.QGridLayout(self)
L.addWidget(introLabel, 0, 0, 1, 2)
L.addWidget(le, 1, 0)
L.addWidget(selBtn, 1, 1)
def selBtnClicked(self):
"""
The "Select" button was clicked for the line edit.
"""
fn = QtWidgets.QFileDialog.getExistingDirectory(self,
_('Select a folder to save OneTileset to'),
self.wizard().chooseOutputEdit.text(),
)
if fn:
self.wizard().chooseOutputEdit.setText(fn)
def isComplete(self):
"""
Return True if a valid NSMBU and/or NSLU course_res_pack
directory has been entered, or False otherwise
"""
return os.path.isdir(self.wizard().chooseOutputEdit.text())
class ConfirmationPage(QtWidgets.QWizardPage):
"""
A wizard page that shows you all relevant info before the actual
OneTileset creation begins
"""
def __init__(self, wizard):
super().__init__(wizard)
self.setMinimumHeight(350) # Paths can be loooooooooong.
dynTrans(self.setTitle, 'Please confirm')
self.setCommitPage(True) # Disables the next page's Back button
# and some other nice stuff