Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions games/totk/runtime/romfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -283,16 +283,19 @@ 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;i<n_mount_names;i++){const char *r=romfs_path_relative(mount_names[i],path);if(r)return r;}
static const char *rom_path(const char *path,char *scratch,size_t capacity) {
#ifndef GAME_OOT
scratch=NULL;capacity=0;
#endif
for(unsigned i=0;i<n_mount_names;i++){const char *r=romfs_path_relative(mount_names[i],path,scratch,capacity);if(r)return r;}
return NULL;
}
static uint64_t lower(const char *name) {uint64_t lo=0,hi=file_count;while(lo<hi){uint64_t m=lo+(hi-lo)/2;if(strcmp(entry_name(entry(m)),name)<0)lo=m+1;else hi=m;}return lo;}
/* The index-th child of a RomFS directory: one path segment, its type (0 folder, 1 file) and, for a file, its size.
Children come from the sorted file paths (folders exist only as prefixes); added overlay files are not listed.
ponytail: re-scans from the binary-search point per call; keep a cursor per handle if listing ever gets hot. */
unsigned runtime_romfs_child(const char *path,uint64_t index,char *name,size_t cap,int *type,int64_t *size) {
const char *p=rom_path(path);if(!p)return NOT_FOUND;size_t n=strlen(p);if(n>4094)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(;i<file_count;i++){const char *nm=entry_name(entry(i));if(strncmp(nm,prefix,n+1))break;
Expand Down Expand Up @@ -325,7 +328,7 @@ static void fs_log(const char *what,const char *path){static int on=-1;static ui
void runtime_fs_log(const char *what,const char *path){fs_log(what,path);}
unsigned runtime_romfs_open(uint64_t *out,const char *path,int mode) {
fs_log("rom",path?path:"(null)");
if(!out||mode!=1)return UNSUPPORTED;const char *p=rom_path(path);if(!p)return NOT_FOUND;uint64_t i=file_index(p);if(i==UINT64_MAX)return NOT_FOUND;
if(!out||mode!=1)return UNSUPPORTED;char normalized[4096];const char *p=rom_path(path,normalized,sizeof normalized);if(!p)return NOT_FOUND;uint64_t i=file_index(p);if(i==UINT64_MAX)return NOT_FOUND;
uint32_t ov=overlay_slot(i);int fd=-1;
if(ov){const Overlay *o=&overlays[ov-1];fd=open(o->path,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){
Expand Down Expand Up @@ -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<n_mount_names;i++){size_t n=strlen(mount_names[i]);if(path&&!strncmp(path,mount_names[i],n)&&path[n]==':')return 1;}return 0;}
Expand All @@ -380,18 +383,25 @@ void runtime_fs_probe(void) {
/* Audio stream paths include an extra slash after the mount root. Verify
actual resource lookup and bytes, while still rejecting path traversal. */
{
#ifdef GAME_OOT
const char *name="Voice/Resource/Stream/USen_EventFlowMsg-_Dm_Kokiri_0000--Dm_Kokiri_0000_Text_000_b.bwav";
const char *joined="Voice/Resource//Stream/USen_EventFlowMsg-_Dm_Kokiri_0000--Dm_Kokiri_0000_Text_000_b.bwav";
#else
const char *name="Sound/Resource/Stream/Dm_OP_0001.bwav";
const char *joined=name;
#endif
char path[256];uint8_t expected[257],actual[257];uint64_t h,n;
snprintf(path,sizeof path,"rom:/%s",name);
need(!runtime_romfs_open(&h,path,1),"open normal audio path");
need(!runtime_romfs_read(&n,h,0,expected,sizeof expected)&&n==sizeof expected,"read normal audio path");runtime_romfs_close(h);
for(int slashes=2;slashes<=3;slashes++){
snprintf(path,sizeof path,slashes==2?"rom://%s":"rom:///%s",name);
snprintf(path,sizeof path,slashes==2?"rom://%s":"rom:///%s",joined);
int type=-1;need(!runtime_romfs_entry_type(&type,path)&&type==1,"normalized audio entry type");
need(!runtime_romfs_open(&h,path,1),"open normalized audio stream path");
need(!runtime_romfs_read(&n,h,0,actual,sizeof actual)&&n==sizeof actual&&!memcmp(actual,expected,sizeof actual),"normalized audio stream bytes");runtime_romfs_close(h);
}
need(runtime_romfs_open(&h,"rom://../Sound/Resource/Stream/Dm_OP_0001.bwav",1)!=0,"reject traversal after repeated root separator");
snprintf(path,sizeof path,"rom://../%s",name);
need(runtime_romfs_open(&h,path,1)==NOT_FOUND,"reject traversal after repeated root separator");
}
for(uint64_t i=0;i<file_count;i+=file_count/64){Entry e=entry(i);char p[4096];need(snprintf(p,sizeof(p),"rom:/%s",entry_name(e))<(int)sizeof(p),"probe path");uint64_t h=0,n=0;int64_t size;need(!runtime_romfs_open(&h,p,1)&&!runtime_romfs_size(&size,h)&&(uint64_t)size==e.size,"probe open/size");need(!runtime_romfs_read(&n,h,0,buffer,sizeof(buffer)),"probe partial read");for(uint64_t j=0;j<n;j++)sum=sum*131+buffer[j];need(!runtime_romfs_read(&n,h,size,buffer,1)&&n==0,"probe EOF");runtime_romfs_close(h);count++;}
for(size_t k=0;k<noverlay;k++){char p[4096];need(snprintf(p,sizeof(p),"rom:/%s",overlays[k].name)<(int)sizeof(p),"probe overlay path");
Expand Down
21 changes: 16 additions & 5 deletions games/totk/runtime/romfs_path.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,26 @@ static inline int romfs_path_valid_relative(const char *path) {
return 1;
}

static inline const char *romfs_path_relative(const char *mount, const char *path) {
static inline const char *romfs_path_relative(const char *mount, const char *path,
char *scratch, size_t capacity) {
if (!mount || !*mount || !path) return NULL;
size_t size = strlen(mount);
if (strncmp(path, mount, size) || path[size] != ':' || path[size + 1] != '/')
return NULL;
path += size + 2;
/* Audio stream requests join a mount root and a leading-slash resource,
producing content://Sound/.... Normalize root separators only; keep
rejecting traversal, backslashes and empty interior components. */
/* Audio stream requests can join a mount root and a leading-slash resource. */
while (*path == '/') ++path;
return romfs_path_valid_relative(path) ? path : NULL;
if (romfs_path_valid_relative(path)) return path;
if (!scratch || !strstr(path, "//")) return NULL;

/* OoT also joins audio subdirectories with duplicate separators, e.g.
Voice/Resource//Stream. Validate the collapsed path before lookup. */
size_t used = 0;
for (const char *p = path; *p; ++p) {
if (used + 1 >= capacity) return NULL;
scratch[used++] = *p;
if (*p == '/') while (p[1] == '/') ++p;
}
scratch[used] = '\0';
return romfs_path_valid_relative(scratch) ? scratch : NULL;
}
13 changes: 12 additions & 1 deletion tests/test_romfs_paths.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
}