From a2a72158157fb338449bcebf9277502b19153292 Mon Sep 17 00:00:00 2001 From: baromas Date: Sat, 7 Nov 2026 21:47:08 +0300 Subject: [PATCH] fix: resolve OoT voice streams with repeated RomFS separators Both Russian and English opening runs request the English BWAV with a repeated separator before Stream. The title already chooses a supported voice locale. Normalize repeated separators for mounted OoT paths before read-only index lookup, including entry type and directory lookups, and keep rejecting traversal and backslashes. Keep the strict parser for other games and overlay manifests. Validated with 16 focused tests and the production RomFS probe against v131072, including byte-for-byte reads of the same opening voice asset through canonical and repeated-separator paths and 65 sampled files. Gameplay audibility is a separate check. --- games/totk/runtime/romfs.c | 24 +++++++++++++++++------- games/totk/runtime/romfs_path.h | 21 ++++++++++++++++----- tests/test_romfs_paths.c | 13 ++++++++++++- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/games/totk/runtime/romfs.c b/games/totk/runtime/romfs.c index 220242fdf..ff4594107 100644 --- a/games/totk/runtime/romfs.c +++ b/games/totk/runtime/romfs.c @@ -283,8 +283,11 @@ void runtime_fs_init(void) { fprintf(stderr,"[totk] native read-only RomFS ready: %llu files\n",(unsigned long long)file_count); } static int path_ok(const char *p) {return romfs_path_valid_relative(p);} -static const char *rom_path(const char *path) { - for(unsigned i=0;i4094)return NOT_FOUND; + char normalized[4096];const char *p=rom_path(path,normalized,sizeof normalized);if(!p)return NOT_FOUND;size_t n=strlen(p);if(n>4094)return NOT_FOUND; char prefix[4096];memcpy(prefix,p,n);prefix[n]='/';prefix[n+1]=0; uint64_t i=lower(prefix),seen=0;char last[4096]=""; for(;ipath,O_RDONLY|O_CLOEXEC);struct stat st; if(fd<0||fstat(fd,&st)!=0||(uint64_t)st.st_size!=o->size||st.st_dev!=o->dev||st.st_ino!=o->ino||st.st_mtimespec.tv_sec!=o->mtime.tv_sec||st.st_mtimespec.tv_nsec!=o->mtime.tv_nsec){ @@ -356,7 +359,7 @@ unsigned runtime_romfs_read(uint64_t *out,uint64_t h,int64_t offset,void *buffer unsigned runtime_romfs_size(int64_t *out,uint64_t h) {Entry e;if(!out||!handle_entry(h,&e))return INVALID;int fd;uint64_t base,size;handle_source(h,&e,&fd,&base,&size);*out=(int64_t)size;return 0;} void runtime_romfs_close(uint64_t h) {Entry e;if(!handle_entry(h,&e))runtime_fail("close invalid file handle");uint64_t j=h-0x100000000ULL;pthread_mutex_lock(&lock);int fd=handle_ovl[j]?handle_fd[j]:-1;handles[j]=0;handle_ovl[j]=0;handle_fd[j]=-1;pthread_mutex_unlock(&lock);if(fd>=0)close(fd);} unsigned runtime_romfs_entry_type(int *out,const char *path) { - if(!out)return INVALID;const char *p=rom_path(path);if(!p)return NOT_FOUND;if(file_index(p)!=UINT64_MAX){*out=1;return 0;} + if(!out)return INVALID;char normalized[4096];const char *p=rom_path(path,normalized,sizeof normalized);if(!p)return NOT_FOUND;if(file_index(p)!=UINT64_MAX){*out=1;return 0;} char dir[4096];if(snprintf(dir,sizeof(dir),"%s/",p)>=(int)sizeof(dir))return INVALID;if(folder_exists(dir)){*out=0;return 0;}return NOT_FOUND; } int runtime_romfs_owns(const char *path) {for(unsigned i=0;i= capacity) return NULL; + scratch[used++] = *p; + if (*p == '/') while (p[1] == '/') ++p; + } + scratch[used] = '\0'; + return romfs_path_valid_relative(scratch) ? scratch : NULL; } diff --git a/tests/test_romfs_paths.c b/tests/test_romfs_paths.c index 0bee84ff9..ad3a79b2b 100644 --- a/tests/test_romfs_paths.c +++ b/tests/test_romfs_paths.c @@ -34,7 +34,7 @@ int main(void) { {"rom", "rom://dir\\file", NULL}, }; for (size_t i = 0; i < sizeof cases / sizeof cases[0]; ++i) { - const char *result = romfs_path_relative(cases[i].mount, cases[i].path); + const char *result = romfs_path_relative(cases[i].mount, cases[i].path, NULL, 0); if ((!result != !cases[i].expected) || (result && strcmp(result, cases[i].expected))) { fprintf(stderr, "case %zu failed: %s -> %s (expected %s)\n", i, @@ -44,6 +44,17 @@ int main(void) { return 1; } } + char normalized[128]; + const char *voice = romfs_path_relative("content", "content://Voice/Resource//Stream/opening.bwav", + normalized, sizeof normalized); + if (!voice || strcmp(voice, "Voice/Resource/Stream/opening.bwav") || + romfs_path_relative("content", "content://Voice//../file", normalized, sizeof normalized) || + romfs_path_relative("content", "content://Voice//./file", normalized, sizeof normalized) || + romfs_path_relative("content", "content://Voice//dir\\file", normalized, sizeof normalized) || + romfs_path_relative("content", "content://Voice//file", normalized, 10)) { + fputs("OoT audio path normalization failed\n", stderr); + return 1; + } printf("PASS: %zu RomFS path cases\n", sizeof cases / sizeof cases[0]); return 0; }