diff --git a/Makefile b/Makefile index 1405ad5..0a39a7f 100644 --- a/Makefile +++ b/Makefile @@ -494,10 +494,33 @@ build/src/decor/decor_object_list.o: build/src/audio/clips.h ## Subtitles #################### -# TODO pull translations from vpk/Portal/portal/resource once the translations are loaded dynamically +SUBTITLE_LANGUAGES = english \ + brazilian \ + bulgarian \ + czech \ + danish \ + dutch \ + finnish \ + french \ + german \ + hungarian \ + italian \ + latam \ + norwegian \ + polish \ + portuguese \ + romanian \ + russian \ + spanish \ + swedish \ + thai \ + turkish -build/src/audio/subtitles.h build/src/audio/subtitles.c: resource/closecaption_english.txt tools/level_scripts/subtitle_generate.py - @python3 tools/level_scripts/subtitle_generate.py +SUBTITLE_SOURCES = $(SUBTITLE_LANGUAGES:%=build/src/audio/subtitles_%.c) +SUBTITLE_OBJECTS = $(SUBTITLE_LANGUAGES:%=build/src/audio/subtitles_%.o) + +build/src/audio/subtitles.h build/src/audio/subtitles.c build/subtitles.ld: tools/level_scripts/subtitle_generate.py + python3 tools/level_scripts/subtitle_generate.py $(SUBTITLE_LANGUAGES) #################### ## Linking @@ -528,10 +551,10 @@ $(CODESEGMENT)_no_debug.o: $(CODEOBJECTS_NO_DEBUG) $(LD) -o $(CODESEGMENT)_no_debug.o -r $(CODEOBJECTS_NO_DEBUG) $(LDFLAGS) -$(CP_LD_SCRIPT)_no_debug.ld: $(LD_SCRIPT) build/levels.ld build/dynamic_models.ld build/anims.ld +$(CP_LD_SCRIPT)_no_debug.ld: $(LD_SCRIPT) build/levels.ld build/dynamic_models.ld build/anims.ld build/subtitles.ld cpp -P -Wno-trigraphs $(LCDEFS) -DCODE_SEGMENT=$(CODESEGMENT)_no_debug.o -o $@ $< -$(BASE_TARGET_NAME).z64: $(CODESEGMENT)_no_debug.o $(OBJECTS) $(DATA_OBJECTS) $(CP_LD_SCRIPT)_no_debug.ld +$(BASE_TARGET_NAME).z64: $(CODESEGMENT)_no_debug.o $(OBJECTS) $(DATA_OBJECTS) $(SUBTITLE_OBJECTS) $(CP_LD_SCRIPT)_no_debug.ld $(LD) -L. -T $(CP_LD_SCRIPT)_no_debug.ld -Map $(BASE_TARGET_NAME)_no_debug.map -o $(BASE_TARGET_NAME).elf $(OBJCOPY) --pad-to=0x100000 --gap-fill=0xFF $(BASE_TARGET_NAME).elf $(BASE_TARGET_NAME).z64 -O binary makemask $(BASE_TARGET_NAME).z64 @@ -546,10 +569,10 @@ endif $(CODESEGMENT)_debug.o: $(CODEOBJECTS_DEBUG) $(LD) -o $(CODESEGMENT)_debug.o -r $(CODEOBJECTS_DEBUG) $(LDFLAGS) -$(CP_LD_SCRIPT)_debug.ld: $(LD_SCRIPT) build/levels.ld build/dynamic_models.ld build/anims.ld +$(CP_LD_SCRIPT)_debug.ld: $(LD_SCRIPT) build/levels.ld build/dynamic_models.ld build/anims.ld build/subtitles.ld cpp -P -Wno-trigraphs $(LCDEFS) -DCODE_SEGMENT=$(CODESEGMENT)_debug.o -o $@ $< -$(BASE_TARGET_NAME)_debug.z64: $(CODESEGMENT)_debug.o $(OBJECTS) $(DATA_OBJECTS) $(CP_LD_SCRIPT)_debug.ld +$(BASE_TARGET_NAME)_debug.z64: $(CODESEGMENT)_debug.o $(OBJECTS) $(DATA_OBJECTS) $(SUBTITLE_OBJECTS) $(CP_LD_SCRIPT)_debug.ld $(LD) -L. -T $(CP_LD_SCRIPT)_debug.ld -Map $(BASE_TARGET_NAME)_debug.map -o $(BASE_TARGET_NAME)_debug.elf $(OBJCOPY) --pad-to=0x100000 --gap-fill=0xFF $(BASE_TARGET_NAME)_debug.elf $(BASE_TARGET_NAME)_debug.z64 -O binary makemask $(BASE_TARGET_NAME)_debug.z64 diff --git a/portal.ld b/portal.ld index 1616339..d8ea7c1 100644 --- a/portal.ld +++ b/portal.ld @@ -80,6 +80,7 @@ SECTIONS #include "build/levels.ld" #include "build/dynamic_models.ld" #include "build/anims.ld" +#include "build/subtitles.ld" /* Discard everything not specifically mentioned above. */ /DISCARD/ : diff --git a/src/main.c b/src/main.c index 2af5206..72c23dd 100644 --- a/src/main.c +++ b/src/main.c @@ -1,28 +1,29 @@ #include #include +#include "audio/audio.h" +#include "audio/soundplayer.h" +#include "controls/controller_actions.h" +#include "controls/controller.h" +#include "controls/rumble_pak.h" #include "defs.h" #include "graphics/graphics.h" -#include "util/rom.h" -#include "scene/scene.h" -#include "menu/main_menu.h" -#include "util/time.h" -#include "util/memory.h" -#include "string.h" -#include "controls/controller.h" -#include "controls/controller_actions.h" -#include "scene/dynamic_scene.h" -#include "audio/soundplayer.h" -#include "audio/audio.h" -#include "scene/portal_surface.h" -#include "sk64/skelatool_defs.h" #include "levels/cutscene_runner.h" #include "levels/intro.h" +#include "menu/main_menu.h" +#include "menu/translations.h" #include "savefile/savefile.h" +#include "scene/dynamic_scene.h" +#include "scene/portal_surface.h" +#include "scene/scene.h" #include "sk64/skelatool_animator.h" +#include "sk64/skelatool_defs.h" +#include "string.h" #include "util/dynamic_asset_loader.h" +#include "util/memory.h" #include "util/profile.h" -#include "controls/rumble_pak.h" +#include "util/rom.h" +#include "util/time.h" #include "levels/levels.h" #include "savefile/checkpoint.h" @@ -240,6 +241,7 @@ static void gameProc(void* arg) { rumblePakClipInit(); initAudio(fps); soundPlayerInit(); + translationsLoad(gSaveData.controls.subtitleLanguage); skSetSegmentLocation(CHARACTER_ANIMATION_SEGMENT, (unsigned)_animation_segmentSegmentRomStart); gSceneCallbacks->initCallback(gSceneCallbacks->data); @@ -264,6 +266,7 @@ static void gameProc(void* arg) { portalSurfaceRevert(0); portalSurfaceCleanupQueueInit(); heapInit(_heapStart, memoryEnd); + translationsLoad(gSaveData.controls.subtitleLanguage); levelLoadWithCallbacks(levelGetQueued()); rumblePakClipInit(); cutsceneRunnerReset(); diff --git a/src/menu/audio_options.c b/src/menu/audio_options.c index f5a2c5b..9cdf903 100644 --- a/src/menu/audio_options.c +++ b/src/menu/audio_options.c @@ -8,6 +8,7 @@ #include "../build/assets/materials/ui.h" #include "../build/src/audio/clips.h" #include "../build/src/audio/languages.h" +#include "./translations.h" #define GAMEPLAY_Y 54 #define GAMEPLAY_WIDTH 252 @@ -182,6 +183,7 @@ enum MenuDirection audioOptionsUpdate(struct AudioOptions* audioOptions) { int temp = (int)((audioOptions->subtitles_language_temp * (1.0f/0xFFFF) * NUM_SUBTITLE_LANGUAGES)); temp = (int)minf(maxf(0.0, temp), NUM_SUBTITLE_LANGUAGES-1); gSaveData.controls.subtitleLanguage = temp; + translationsReload(temp); audioOptions->subtitlesLanguageDynamicText = menuBuildText(&gDejaVuSansFont, SubtitleLanguages[gSaveData.controls.subtitleLanguage], GAMEPLAY_X + 125, GAMEPLAY_Y + 88); break; case AudioOptionAudioLanguage: diff --git a/src/menu/translations.c b/src/menu/translations.c new file mode 100644 index 0000000..d133509 --- /dev/null +++ b/src/menu/translations.c @@ -0,0 +1,48 @@ +#include "translations.h" + +#include "../util/memory.h" +#include "../util/rom.h" + +#include "../build/src/audio/subtitles.h" + +char** gCurrentTranslations = NULL; + +void translationsLoad(int language) { + if (NUM_SUBTITLE_LANGUAGES == 0) { + gCurrentTranslations = NULL; + return; + } + + if (language < 0) { + language = 0; + } + + if (language >= NUM_SUBTITLE_LANGUAGES) { + language = NUM_SUBTITLE_LANGUAGES - 1; + } + + struct SubtitleBlock* block = &SubtitleLanguageBlocks[language]; + + int blockSize = (int)block->romEnd - (int)block->romStart; + char* blockStart = malloc(blockSize); + romCopy(block->romStart, blockStart, blockSize); + + gCurrentTranslations = CALC_RAM_POINTER(block->values, blockStart); + + for (int i = 0; i < NUM_SUBTITLE_MESSAGES; ++i) { + gCurrentTranslations[i] = CALC_RAM_POINTER(gCurrentTranslations[i], blockStart); + } +} + +void translationsReload(int language) { + free(gCurrentTranslations); + translationsLoad(language); +} + +char* translationsGet(int message) { + if (message < 0 || message >= NUM_SUBTITLE_MESSAGES || !gCurrentTranslations) { + return ""; + } + + return gCurrentTranslations[message]; +} \ No newline at end of file diff --git a/src/menu/translations.h b/src/menu/translations.h new file mode 100644 index 0000000..b52008e --- /dev/null +++ b/src/menu/translations.h @@ -0,0 +1,9 @@ +#ifndef __MENU_TRANSLATIONS_H__ +#define __MENU_TRANSLATIONS_H__ + +void translationsLoad(int language); +void translationsReload(int language); + +char* translationsGet(int message); + +#endif \ No newline at end of file diff --git a/src/scene/hud.c b/src/scene/hud.c index 443f6a3..6b5c634 100644 --- a/src/scene/hud.c +++ b/src/scene/hud.c @@ -7,6 +7,7 @@ #include "../levels/levels.h" #include "./scene.h" #include "../savefile/savefile.h" +#include "../menu/translations.h" #define HUD_CENTER_WIDTH 6 #define HUD_CENTER_HEIGHT 8 @@ -48,7 +49,6 @@ void hudInit(struct Hud* hud) { hud->subtitleOpacity = 0.0f; hud->backgroundOpacity = 0.0f; hud->subtitleFadeTime = SUBTITLE_SLOW_FADE_TIME; - hud->chosenLanguage = gSaveData.controls.subtitleLanguage; hud->flags = 0; hud->resolvedPrompts = 0; hud->lastPortalIndexShot = -1; @@ -78,8 +78,6 @@ void hudUpdate(struct Hud* hud) { } } - hud->chosenLanguage = gSaveData.controls.subtitleLanguage; - float targetPromptOpacity = (hud->flags & HudFlagsShowingPrompt) ? 1.0 : 0.0f; float targetSubtitleOpacity = ((hud->flags & HudFlagsShowingSubtitle) && (!(hud->flags & HudFlagsSubtitleQueued) || (hud->subtitleExpireTimer > 0.0f))) ? 0.85: 0.0f; float targetBackgroundOpacity = (hud->flags & HudFlagsShowingSubtitle && (!(hud->flags & HudFlagsSubtitleQueued) || (hud->subtitleExpireTimer > 0.0f))) ? 0.45: 0.0f; @@ -365,6 +363,6 @@ void hudRender(struct Hud* hud, struct Player* player, struct RenderState* rende } if (hud->subtitleOpacity > 0.0f && (gSaveData.controls.flags & ControlSaveSubtitlesEnabled || gSaveData.controls.flags & ControlSaveAllSubtitlesEnabled) && hud->subtitleKey != SubtitleKeyNone) { - controlsRenderSubtitle(SubtitleLanguageValues[hud->chosenLanguage][hud->subtitleKey], hud->subtitleOpacity, hud->backgroundOpacity, renderState, hud->subtitleType); + controlsRenderSubtitle(translationsGet(hud->subtitleKey), hud->subtitleOpacity, hud->backgroundOpacity, renderState, hud->subtitleType); } } \ No newline at end of file diff --git a/src/scene/hud.h b/src/scene/hud.h index 1cac6f8..a632cda 100644 --- a/src/scene/hud.h +++ b/src/scene/hud.h @@ -25,7 +25,6 @@ enum HudFlags { }; struct Hud { - int chosenLanguage; enum CutscenePromptType promptType; enum SubtitleKey subtitleKey; enum SubtitleKey queuedSubtitleKey; diff --git a/tools/level_scripts/subtitle_generate.py b/tools/level_scripts/subtitle_generate.py index 1c1c80b..74e3606 100644 --- a/tools/level_scripts/subtitle_generate.py +++ b/tools/level_scripts/subtitle_generate.py @@ -1,6 +1,14 @@ #!/usr/bin/env python3 import os import re +import sys + +def dump_lines(sourcefile_path, lines): + if not os.path.exists(os.path.dirname(os.path.abspath(sourcefile_path))): + os.makedirs(os.path.dirname(os.path.abspath(sourcefile_path))) + + with open(sourcefile_path, "w") as f: + f.writelines(lines) def get_caption_keys_values_language(lines): language = "English" @@ -62,9 +70,16 @@ def make_overall_subtitles_header(all_header_lines, languages_list): header_lines.append("#define __SUBTITLES_H__\n") header_lines.append("\n") header_lines.append(f"#define NUM_SUBTITLE_LANGUAGES {len(languages_list)}\n") + header_lines.append(f"#define NUM_SUBTITLE_MESSAGES 508\n") + header_lines.append("\n") + header_lines.append("struct SubtitleBlock {\n") + header_lines.append(" char* romStart;\n") + header_lines.append(" char* romEnd;\n") + header_lines.append(" char** values;\n") + header_lines.append("};\n") header_lines.append("\n") header_lines.append("extern char* SubtitleLanguages[];\n") - header_lines.append("extern char* SubtitleLanguageValues[][508];\n") + header_lines.append("extern struct SubtitleBlock SubtitleLanguageBlocks[];\n") header_lines.append("\n") if len(languages_list) > 0: header_lines.extend(all_header_lines) @@ -77,12 +92,7 @@ def make_overall_subtitles_header(all_header_lines, languages_list): header_lines.append("#endif") - headerfile_path = "build/src/audio/subtitles.h" - if not os.path.exists(os.path.dirname(os.path.abspath(headerfile_path))): - os.makedirs(os.path.dirname(os.path.abspath(headerfile_path))) - - with open(headerfile_path, "w") as f: - f.writelines(header_lines) + dump_lines("build/src/audio/subtitles.h", header_lines) def make_SubtitleKey_headerlines(keys): header_lines = [] @@ -96,33 +106,37 @@ def make_SubtitleKey_headerlines(keys): header_lines.append("\n") return header_lines -def make_SubtitleLanguageValues(values_list): - sourcefile_lines = [] - if len(values_list) <= 0: - sourcefile_lines.append("\n") - sourcefile_lines.append("char* SubtitleLanguageValues[][508] =\n") - sourcefile_lines.append("{\n") - sourcefile_lines.append(" {\n") - for val in range(508): - sourcefile_lines.append(f' "",\n') - sourcefile_lines.append(" },\n") - sourcefile_lines.append("};\n") - sourcefile_lines.append("\n") - return sourcefile_lines - sourcefile_lines.append("\n") - sourcefile_lines.append("char* SubtitleLanguageValues[][508] =\n") - sourcefile_lines.append("{\n") - for lang in values_list: - sourcefile_lines.append(" {\n") - sourcefile_lines.append(' "",\n') - for value in lang: - sourcefile_lines.append(f' "{value}",\n') - sourcefile_lines.append(" },\n") - sourcefile_lines.append("};\n") - sourcefile_lines.append("\n") - return sourcefile_lines +def make_subtitle_for_language(lang_lines, lang_name): + lines = [] -def make_overall_subtitles_sourcefile(other_sourcefile_lines, language_list): + lines.append("\n") + lines.append(f"char* gSubtitle{lang_name}[508] = {'{'}\n") + + for value in lang_lines: + lines.append(f' "{value}",\n') + + lines.append("};\n") + + dump_lines(f"build/src/audio/subtitles_{lang_name}.c", lines) + + +def make_subtitle_ld(languages): + lines = [] + + for language in languages: + language_name = language['name'] + + lines.append(f" BEGIN_SEG(subtitles_{language_name}, 0x04000000)\n") + lines.append(" {\n") + lines.append(f" build/src/audio/subtitles_{language_name}.o(.data);\n") + lines.append(f" build/src/audio/subtitles_{language_name}.o(.bss);\n") + lines.append(" }\n") + lines.append(f" END_SEG(subtitles_{language_name})\n") + lines.append("\n") + + dump_lines('build/subtitles.ld', lines) + +def make_overall_subtitles_sourcefile(language_list): sourcefile_lines = [] sourcefile_lines.append('#include "subtitles.h"\n') @@ -136,25 +150,38 @@ def make_overall_subtitles_sourcefile(other_sourcefile_lines, language_list): sourcefile_lines.append(f' "{language.upper()}",\n') sourcefile_lines.append("};\n") sourcefile_lines.append("\n") - sourcefile_lines.extend(other_sourcefile_lines) - sourcefile_path = "build/src/audio/subtitles.c" - if not os.path.exists(os.path.dirname(os.path.abspath(sourcefile_path))): - os.makedirs(os.path.dirname(os.path.abspath(sourcefile_path))) - with open(sourcefile_path, "w") as f: - f.writelines(sourcefile_lines) + for language in language_list: + sourcefile_lines.append(f"extern char _subtitles_{language}SegmentRomStart[];\n") + sourcefile_lines.append(f"extern char _subtitles_{language}SegmentRomEnd[];\n") + sourcefile_lines.append(f"extern char* gSubtitle{language}[508];\n") + sourcefile_lines.append("\n") -def process_all_closecaption_files(dir): + sourcefile_lines.append("struct SubtitleBlock SubtitleLanguageBlocks[] = {\n") + + for language in language_list: + sourcefile_lines.append(" {\n") + sourcefile_lines.append(f" _subtitles_{language}SegmentRomStart,\n") + sourcefile_lines.append(f" _subtitles_{language}SegmentRomEnd,\n") + sourcefile_lines.append(f" gSubtitle{language},\n") + sourcefile_lines.append(" },\n") + + sourcefile_lines.append("};\n") + sourcefile_lines.append("\n") + + dump_lines("build/src/audio/subtitles.c", sourcefile_lines) + +def process_all_closecaption_files(dir, language_names): values_list = [] header_lines = [] sourcefile_lines = [] language_list = [] + language_with_values_list = [] SubtitleKey_generated = False - lst = os.listdir(dir) - lst.sort() - for filename in lst: - if "closecaption_" not in filename: - continue + + for langauge_name in language_names: + filename = f"closecaption_{langauge_name}.txt" + try: filepath = os.path.join(dir, filename) lines = [] @@ -174,14 +201,23 @@ def process_all_closecaption_files(dir): header_lines = make_SubtitleKey_headerlines(k) SubtitleKey_generated = True language_list.append(l) + + language_with_values_list.append({ + 'value': v, + 'name': l, + }) print(filename, " - PASSED") except: print(filename, " - FAILED") continue - sourcefile_lines = make_SubtitleLanguageValues(values_list) + + for language in language_with_values_list: + make_subtitle_for_language(language['value'], language['name']) + make_subtitle_ld(language_with_values_list) + make_overall_subtitles_header(header_lines, language_list) - make_overall_subtitles_sourcefile(sourcefile_lines, language_list) + make_overall_subtitles_sourcefile(language_list) -process_all_closecaption_files("resource/") \ No newline at end of file +process_all_closecaption_files("vpk/Portal/portal/resource", sys.argv[1:]) \ No newline at end of file