#include #include #include #include #include #include #include #include #include #include #include #include "defines.h" #include "utils.h" #include "api.h" /////////////////////////////////////// #define dump(msg) puts((msg));fflush(stdout); /////////////////////////////////////// typedef struct Array { int count; int capacity; void** items; } Array; static Array* Array_new(void) { Array* self = malloc(sizeof(Array)); self->count = 0; self->capacity = 8; self->items = malloc(sizeof(void*) * self->capacity); return self; } static void Array_push(Array* self, void* item) { if (self->count>=self->capacity) { self->capacity *= 2; self->items = realloc(self->items, sizeof(void*) * self->capacity); } self->items[self->count++] = item; } static void Array_unshift(Array* self, void* item) { if (self->count==0) return Array_push(self, item); Array_push(self, NULL); // ensures we have enough capacity for (int i=self->count-2; i>=0; i--) { self->items[i+1] = self->items[i]; } self->items[0] = item; } static void* Array_pop(Array* self) { if (self->count==0) return NULL; return self->items[--self->count]; } static void Array_reverse(Array* self) { int end = self->count-1; int mid = self->count/2; for (int i=0; iitems[i]; self->items[i] = self->items[end-i]; self->items[end-i] = item; } } static void Array_free(Array* self) { free(self->items); free(self); } static int StringArray_indexOf(Array* self, char* str) { for (int i=0; icount; i++) { if (exactMatch(self->items[i], str)) return i; } return -1; } static void StringArray_free(Array* self) { for (int i=0; icount; i++) { free(self->items[i]); } Array_free(self); } /////////////////////////////////////// typedef struct Hash { Array* keys; Array* values; } Hash; // not really a hash static Hash* Hash_new(void) { Hash* self = malloc(sizeof(Hash)); self->keys = Array_new(); self->values = Array_new(); return self; } static void Hash_free(Hash* self) { StringArray_free(self->keys); StringArray_free(self->values); free(self); } static void Hash_set(Hash* self, char* key, char* value) { Array_push(self->keys, strdup(key)); Array_push(self->values, strdup(value)); } static char* Hash_get(Hash* self, char* key) { int i = StringArray_indexOf(self->keys, key); if (i==-1) return NULL; return self->values->items[i]; } /////////////////////////////////////// enum EntryType { ENTRY_DIR, ENTRY_PAK, ENTRY_ROM, }; typedef struct Entry { char* path; char* name; char* unique; int type; int alpha; // index in parent Directory's alphas Array, which points to the index of an Entry in its entries Array :sweat_smile: int has_alt; int use_alt; } Entry; static Entry* Entry_new(char* path, int type) { char display_name[256]; getDisplayName(path, display_name); Entry* self = malloc(sizeof(Entry)); self->path = strdup(path); self->name = strdup(display_name); self->unique = NULL; self->type = type; self->alpha = 0; self->has_alt = type!=ENTRY_ROM?0:-1; self->use_alt = type!=ENTRY_ROM?0:-1; return self; } static void Entry_free(Entry* self) { free(self->path); free(self->name); if (self->unique) free(self->unique); free(self); } static int EntryArray_indexOf(Array* self, char* path) { for (int i=0; icount; i++) { Entry* entry = self->items[i]; if (exactMatch(entry->path, path)) return i; } return -1; } static int EntryArray_sortEntry(const void* a, const void* b) { Entry* item1 = *(Entry**)a; Entry* item2 = *(Entry**)b; return strcasecmp(item1->name, item2->name); } static void EntryArray_sort(Array* self) { qsort(self->items, self->count, sizeof(void*), EntryArray_sortEntry); } static void EntryArray_free(Array* self) { for (int i=0; icount; i++) { Entry_free(self->items[i]); } Array_free(self); } /////////////////////////////////////// #define INT_ARRAY_MAX 27 typedef struct IntArray { int count; int items[INT_ARRAY_MAX]; } IntArray; static IntArray* IntArray_new(void) { IntArray* self = malloc(sizeof(IntArray)); self->count = 0; memset(self->items, 0, sizeof(int) * INT_ARRAY_MAX); return self; } static void IntArray_push(IntArray* self, int i) { self->items[self->count++] = i; } static void IntArray_free(IntArray* self) { free(self); } /////////////////////////////////////// typedef struct Directory { char* path; char* name; Array* entries; IntArray* alphas; // rendering int selected; int start; int end; } Directory; static int getIndexChar(char* str) { char i = 0; char c = tolower(str[0]); if (c>='a' && c<='z') i = (c-'a')+1; return i; } static void getUniqueName(Entry* entry, char* out_name) { char* filename = strrchr(entry->path, '/')+1; char emu_tag[256]; getEmuName(entry->path, emu_tag); char *tmp; strcpy(out_name, entry->name); tmp = out_name + strlen(out_name); strcpy(tmp, " ("); tmp = out_name + strlen(out_name); strcpy(tmp, emu_tag); tmp = out_name + strlen(out_name); strcpy(tmp, ")"); } static void Directory_index(Directory* self) { int skip_index = exactMatch(FAUX_RECENT_PATH, self->path) || prefixMatch(COLLECTIONS_PATH, self->path); // not alphabetized Entry* prior = NULL; int alpha = -1; int index = 0; for (int i=0; ientries->count; i++) { Entry* entry = self->entries->items[i]; if (prior!=NULL && exactMatch(prior->name, entry->name)) { if (prior->unique) free(prior->unique); if (entry->unique) free(entry->unique); char* prior_filename = strrchr(prior->path, '/')+1; char* entry_filename = strrchr(entry->path, '/')+1; if (exactMatch(prior_filename, entry_filename)) { char prior_unique[256]; char entry_unique[256]; getUniqueName(prior, prior_unique); getUniqueName(entry, entry_unique); prior->unique = strdup(prior_unique); entry->unique = strdup(entry_unique); } else { prior->unique = strdup(prior_filename); entry->unique = strdup(entry_filename); } } if (!skip_index) { int a = getIndexChar(entry->name); if (a!=alpha) { index = self->alphas->count; IntArray_push(self->alphas, i); alpha = a; } entry->alpha = index; } prior = entry; } } static Array* getRoot(void); static Array* getRecents(void); static Array* getCollection(char* path); static Array* getDiscs(char* path); static Array* getEntries(char* path); static Directory* Directory_new(char* path, int selected) { char display_name[256]; getDisplayName(path, display_name); Directory* self = malloc(sizeof(Directory)); self->path = strdup(path); self->name = strdup(display_name); if (exactMatch(path, SDCARD_PATH)) { self->entries = getRoot(); } else if (exactMatch(path, FAUX_RECENT_PATH)) { self->entries = getRecents(); } else if (!exactMatch(path, COLLECTIONS_PATH) && prefixMatch(COLLECTIONS_PATH, path)) { self->entries = getCollection(path); } else if (suffixMatch(".m3u", path)) { self->entries = getDiscs(path); } else { self->entries = getEntries(path); } self->alphas = IntArray_new(); self->selected = selected; Directory_index(self); return self; } static void Directory_free(Directory* self) { free(self->path); free(self->name); EntryArray_free(self->entries); IntArray_free(self->alphas); free(self); } static void DirectoryArray_pop(Array* self) { Directory_free(Array_pop(self)); } static void DirectoryArray_free(Array* self) { for (int i=0; icount; i++) { Directory_free(self->items[i]); } Array_free(self); } /////////////////////////////////////// typedef struct Recent { char* path; // NOTE: this is without the SDCARD_PATH prefix! int available; } Recent; static int hasEmu(char* emu_name); static Recent* Recent_new(char* path) { Recent* self = malloc(sizeof(Recent)); char sd_path[256]; // only need to get emu name sprintf(sd_path, "%s%s", SDCARD_PATH, path); char emu_name[256]; getEmuName(sd_path, emu_name); self->path = strdup(path); self->available = hasEmu(emu_name); return self; } static void Recent_free(Recent* self) { free(self->path); free(self); } static int RecentArray_indexOf(Array* self, char* str) { for (int i=0; icount; i++) { Recent* item = self->items[i]; if (exactMatch(item->path, str)) return i; } return -1; } static void RecentArray_free(Array* self) { for (int i=0; icount; i++) { Recent_free(self->items[i]); } Array_free(self); } /////////////////////////////////////// static Directory* top; static Array* stack; // DirectoryArray static Array* recents; // RecentArray static int quit = 0; static int can_resume = 0; static int should_resume = 0; // set to 1 on BTN_RESUME but only if can_resume==1 static char slot_path[256]; static int restore_depth = -1; static int restore_relative = -1; static int restore_selected = 0; static int restore_start = 0; static int restore_end = 0; /////////////////////////////////////// #define MAX_RECENTS 24 // a multiple of all menu rows static void saveRecents(void) { FILE* file = fopen(RECENT_PATH, "w"); if (file) { for (int i=0; icount; i++) { Recent* recent = recents->items[i]; fputs(recent->path, file); putc('\n', file); } fclose(file); } } static void addRecent(char* path) { path += strlen(SDCARD_PATH); // makes paths platform agnostic int id = RecentArray_indexOf(recents, path); if (id==-1) { // add while (recents->count>=MAX_RECENTS) { Recent_free(Array_pop(recents)); } Array_unshift(recents, Recent_new(path)); } else if (id>0) { // bump to top for (int i=id; i>0; i--) { void* tmp = recents->items[i-1]; recents->items[i-1] = recents->items[i]; recents->items[i] = tmp; } } saveRecents(); } static int hasEmu(char* emu_name) { char pak_path[256]; sprintf(pak_path, "%s/Emus/%s.pak/launch.sh", PAKS_PATH, emu_name); if (exists(pak_path)) return 1; sprintf(pak_path, "%s/Emus/%s.pak/launch.sh", SDCARD_PATH, emu_name); return exists(pak_path); } static void getEmuPath(char* emu_name, char* pak_path) { sprintf(pak_path, "%s/Emus/%s.pak/launch.sh", SDCARD_PATH, emu_name); if (exists(pak_path)) return; sprintf(pak_path, "%s/Emus/%s.pak/launch.sh", PAKS_PATH, emu_name); } static int hasAlt(char* emu_name) { char pak_path[256]; sprintf(pak_path, "%s/Emus/%s.pak/has-alt", PAKS_PATH, emu_name); return exists(pak_path); } static int hasCue(char* dir_path, char* cue_path) { // NOTE: dir_path not rom_path char* tmp = strrchr(dir_path, '/') + 1; // folder name sprintf(cue_path, "%s/%s.cue", dir_path, tmp); return exists(cue_path); } static int hasM3u(char* rom_path, char* m3u_path) { // NOTE: rom_path not dir_path char* tmp; strcpy(m3u_path, rom_path); tmp = strrchr(m3u_path, '/') + 1; tmp[0] = '\0'; // path to parent directory char base_path[256]; strcpy(base_path, m3u_path); tmp = strrchr(m3u_path, '/'); tmp[0] = '\0'; // get parent directory name char dir_name[256]; tmp = strrchr(m3u_path, '/'); strcpy(dir_name, tmp); // dir_name is also our m3u file name tmp = m3u_path + strlen(m3u_path); strcpy(tmp, dir_name); // add extension tmp = m3u_path + strlen(m3u_path); strcpy(tmp, ".m3u"); return exists(m3u_path); } static int Entry_hasAlt(Entry* self) { // has_alt can be set by getEntries() // but won't be set by getRecents() // otherwise delayed until selected if (self->has_alt==-1) { // check char emu_name[256]; getEmuName(self->path, emu_name); self->has_alt = hasAlt(emu_name); } return self->has_alt; } static int Entry_useAlt(Entry* self) { // has to be checked on an individual basis // but delayed until selected if (self->use_alt==-1) { // check char emu_name[256]; getEmuName(self->path, emu_name); char rom_name[256]; char* tmp = strrchr(self->path, '/'); if (tmp) strcpy(rom_name, tmp+1); char use_alt[256]; sprintf(use_alt, "%s/.minui/%s/%s.use-alt", USERDATA_PATH, emu_name, rom_name); self->use_alt = exists(use_alt); } return self->use_alt; } static int Entry_toggleAlt(Entry* self) { if (!Entry_hasAlt(self)) return 0; self->use_alt = !Entry_useAlt(self); char emu_name[256]; getEmuName(self->path, emu_name); char rom_name[256]; char* tmp = strrchr(self->path, '/'); if (tmp) strcpy(rom_name, tmp+1); char use_alt_path[256]; sprintf(use_alt_path, "%s/.minui/%s/%s.use-alt", USERDATA_PATH, emu_name, rom_name); if (self->use_alt==1) touch(use_alt_path); else unlink(use_alt_path); return 1; } static int hasRecents(void) { int has = 0; Array* parent_paths = Array_new(); if (exists(CHANGE_DISC_PATH)) { char sd_path[256]; getFile(CHANGE_DISC_PATH, sd_path, 256); if (exists(sd_path)) { char* disc_path = sd_path + strlen(SDCARD_PATH); // makes path platform agnostic Recent* recent = Recent_new(disc_path); if (recent->available) has += 1; Array_push(recents, recent); char parent_path[256]; strcpy(parent_path, disc_path); char* tmp = strrchr(parent_path, '/') + 1; tmp[0] = '\0'; Array_push(parent_paths, strdup(parent_path)); } unlink(CHANGE_DISC_PATH); } FILE* file = fopen(RECENT_PATH, "r"); // newest at top if (file) { char line[256]; while (fgets(line,256,file)!=NULL) { normalizeNewline(line); trimTrailingNewlines(line); if (strlen(line)==0) continue; // skip empty lines char sd_path[256]; sprintf(sd_path, "%s%s", SDCARD_PATH, line); if (exists(sd_path)) { if (recents->countcount; i++) { char* path = parent_paths->items[i]; if (prefixMatch(path, parent_path)) { found = 1; break; } } if (found) continue; Array_push(parent_paths, strdup(parent_path)); } Recent* recent = Recent_new(line); if (recent->available) has += 1; Array_push(recents, recent); } } } fclose(file); } saveRecents(); StringArray_free(parent_paths); return has>0; } static int hasCollections(void) { int has = 0; if (!exists(COLLECTIONS_PATH)) return has; DIR *dh = opendir(COLLECTIONS_PATH); struct dirent *dp; while((dp = readdir(dh)) != NULL) { if (hide(dp->d_name)) continue; has = 1; break; } closedir(dh); return has; } static int hasRoms(char* dir_name) { int has = 0; char emu_name[256]; char rom_path[256]; getEmuName(dir_name, emu_name); // check for emu pak if (!hasEmu(emu_name)) return has; // check for at least one non-hidden file (we're going to assume it's a rom) sprintf(rom_path, "%s/%s/", ROMS_PATH, dir_name); DIR *dh = opendir(rom_path); if (dh!=NULL) { struct dirent *dp; while((dp = readdir(dh)) != NULL) { if (hide(dp->d_name)) continue; has = 1; break; } closedir(dh); } // if (!has) printf("No roms for %s!\n", dir_name); return has; } static Array* getRoot(void) { Array* root = Array_new(); if (hasRecents()) Array_push(root, Entry_new(FAUX_RECENT_PATH, ENTRY_DIR)); DIR *dh; Array* entries = Array_new(); dh = opendir(ROMS_PATH); if (dh!=NULL) { struct dirent *dp; char* tmp; char full_path[256]; sprintf(full_path, "%s/", ROMS_PATH); tmp = full_path + strlen(full_path); Array* emus = Array_new(); while((dp = readdir(dh)) != NULL) { if (hide(dp->d_name)) continue; if (hasRoms(dp->d_name)) { strcpy(tmp, dp->d_name); Array_push(emus, Entry_new(full_path, ENTRY_DIR)); } } EntryArray_sort(emus); Entry* prev_entry = NULL; for (int i=0; icount; i++) { Entry* entry = emus->items[i]; if (prev_entry!=NULL) { if (exactMatch(prev_entry->name, entry->name)) { Entry_free(entry); continue; } } Array_push(entries, entry); prev_entry = entry; } Array_free(emus); // just free the array part, entries now owns emus entries closedir(dh); } if (hasCollections()) { if (entries->count) Array_push(root, Entry_new(COLLECTIONS_PATH, ENTRY_DIR)); else { // no visible systems, promote collections to root dh = opendir(COLLECTIONS_PATH); if (dh!=NULL) { struct dirent *dp; char* tmp; char full_path[256]; sprintf(full_path, "%s/", COLLECTIONS_PATH); tmp = full_path + strlen(full_path); Array* collections = Array_new(); while((dp = readdir(dh)) != NULL) { if (hide(dp->d_name)) continue; strcpy(tmp, dp->d_name); Array_push(collections, Entry_new(full_path, ENTRY_DIR)); // yes, collections are fake directories } EntryArray_sort(collections); for (int i=0; icount; i++) { Array_push(entries, collections->items[i]); } Array_free(collections); // just free the array part, entries now owns collections entries closedir(dh); } } } // add systems to root for (int i=0; icount; i++) { Array_push(root, entries->items[i]); } Array_free(entries); // root now owns entries' entries char tools_path[256]; sprintf(tools_path, "%s/Tools", SDCARD_PATH); if (exists(tools_path)) Array_push(root, Entry_new(tools_path, ENTRY_DIR)); return root; } static Array* getRecents(void) { Array* entries = Array_new(); for (int i=0; icount; i++) { Recent* recent = recents->items[i]; if (!recent->available) continue; char sd_path[256]; sprintf(sd_path, "%s%s", SDCARD_PATH, recent->path); int type = suffixMatch(".pak", sd_path) ? ENTRY_PAK : ENTRY_ROM; // ??? Array_push(entries, Entry_new(sd_path, type)); } return entries; } static Array* getCollection(char* path) { Array* entries = Array_new(); FILE* file = fopen(path, "r"); if (file) { char line[256]; while (fgets(line,256,file)!=NULL) { normalizeNewline(line); trimTrailingNewlines(line); if (strlen(line)==0) continue; // skip empty lines char sd_path[256]; sprintf(sd_path, "%s%s", SDCARD_PATH, line); if (exists(sd_path)) { int type = suffixMatch(".pak", sd_path) ? ENTRY_PAK : ENTRY_ROM; // ??? Array_push(entries, Entry_new(sd_path, type)); // char emu_name[256]; // getEmuName(sd_path, emu_name); // if (hasEmu(emu_name)) { // Array_push(entries, Entry_new(sd_path, ENTRY_ROM)); // } } } fclose(file); } return entries; } static Array* getDiscs(char* path){ // TODO: does path have SDCARD_PATH prefix? Array* entries = Array_new(); char base_path[256]; strcpy(base_path, path); char* tmp = strrchr(base_path, '/') + 1; tmp[0] = '\0'; // TODO: limit number of discs supported (to 9?) FILE* file = fopen(path, "r"); if (file) { char line[256]; int disc = 0; while (fgets(line,256,file)!=NULL) { normalizeNewline(line); trimTrailingNewlines(line); if (strlen(line)==0) continue; // skip empty lines char disc_path[256]; sprintf(disc_path, "%s%s", base_path, line); if (exists(disc_path)) { disc += 1; Entry* entry = Entry_new(disc_path, ENTRY_ROM); free(entry->name); char name[16]; sprintf(name, "Disc %i", disc); entry->name = strdup(name); Array_push(entries, entry); } } fclose(file); } return entries; } static int getFirstDisc(char* m3u_path, char* disc_path) { // based on getDiscs() natch int found = 0; char base_path[256]; strcpy(base_path, m3u_path); char* tmp = strrchr(base_path, '/') + 1; tmp[0] = '\0'; FILE* file = fopen(m3u_path, "r"); if (file) { char line[256]; while (fgets(line,256,file)!=NULL) { normalizeNewline(line); trimTrailingNewlines(line); if (strlen(line)==0) continue; // skip empty lines sprintf(disc_path, "%s%s", base_path, line); if (exists(disc_path)) found = 1; break; } fclose(file); } return found; } static void addEntries(Array* entries, char* path) { DIR *dh = opendir(path); if (dh!=NULL) { struct dirent *dp; char* tmp; char full_path[256]; sprintf(full_path, "%s/", path); tmp = full_path + strlen(full_path); while((dp = readdir(dh)) != NULL) { if (hide(dp->d_name)) continue; strcpy(tmp, dp->d_name); int is_dir = dp->d_type==DT_DIR; int type; if (is_dir) { // TODO: this should make sure launch.sh exists if (suffixMatch(".pak", dp->d_name)) { type = ENTRY_PAK; } else { type = ENTRY_DIR; } } else { if (prefixMatch(COLLECTIONS_PATH, full_path)) { type = ENTRY_DIR; // :shrug: } else { type = ENTRY_ROM; } } Array_push(entries, Entry_new(full_path, type)); } closedir(dh); } } static int isConsoleDir(char* path) { char* tmp; char parent_dir[256]; strcpy(parent_dir, path); tmp = strrchr(parent_dir, '/'); tmp[0] = '\0'; return exactMatch(parent_dir, ROMS_PATH); } static Array* getEntries(char* path){ Array* entries = Array_new(); if (isConsoleDir(path)) { // top-level console folder, might collate char collated_path[256]; strcpy(collated_path, path); char* tmp = strrchr(collated_path, '('); if (tmp) { tmp[1] = '\0'; // 1 because we want to keep the opening parenthesis to avoid collating "Game Boy Color" and "Game Boy Advance" into "Game Boy" DIR *dh = opendir(ROMS_PATH); if (dh!=NULL) { struct dirent *dp; char full_path[256]; sprintf(full_path, "%s/", ROMS_PATH); tmp = full_path + strlen(full_path); // while loop so we can collate paths, see above while((dp = readdir(dh)) != NULL) { if (hide(dp->d_name)) continue; if (dp->d_type!=DT_DIR) continue; strcpy(tmp, dp->d_name); if (!prefixMatch(collated_path, full_path)) continue; addEntries(entries, full_path); } closedir(dh); } } } else addEntries(entries, path); // just a subfolder EntryArray_sort(entries); return entries; } /////////////////////////////////////// static void queueNext(char* cmd) { putFile("/tmp/next", cmd); quit = 1; } static char* escapeSingleQuotes(char* str) { // based on https://stackoverflow.com/a/31775567/145965 int replaceString(char *line, const char *search, const char *replace) { char *sp; // start of pattern if ((sp = strstr(line, search)) == NULL) { return 0; } int count = 1; int sLen = strlen(search); int rLen = strlen(replace); if (sLen > rLen) { // move from right to left char *src = sp + sLen; char *dst = sp + rLen; while((*dst = *src) != '\0') { dst++; src++; } } else if (sLen < rLen) { // move from left to right int tLen = strlen(sp) - sLen; char *stop = sp + rLen; char *src = sp + sLen + tLen; char *dst = sp + rLen + tLen; while(dst >= stop) { *dst = *src; dst--; src--; } } memcpy(sp, replace, rLen); count += replaceString(sp + rLen, search, replace); return count; } replaceString(str, "'", "'\\''"); return str; } /////////////////////////////////////// static void readyResumePath(char* rom_path, int type) { char* tmp; can_resume = 0; char path[256]; strcpy(path, rom_path); if (!prefixMatch(ROMS_PATH, path)) return; char auto_path[256]; if (type==ENTRY_DIR) { if (!hasCue(path, auto_path)) { // no cue? tmp = strrchr(auto_path, '.') + 1; // extension strcpy(tmp, "m3u"); // replace with m3u if (!exists(auto_path)) return; // no m3u } strcpy(path, auto_path); // cue or m3u if one exists } if (!suffixMatch(".m3u", path)) { char m3u_path[256]; if (hasM3u(path, m3u_path)) { // change path to m3u path strcpy(path, m3u_path); } } char emu_name[256]; getEmuName(path, emu_name); char rom_file[256]; tmp = strrchr(path, '/') + 1; strcpy(rom_file, tmp); sprintf(slot_path, "%s/.minui/%s/%s.txt", USERDATA_PATH, emu_name, rom_file); // /.userdata/.minui//.ext.txt can_resume = exists(slot_path); } static void readyResume(Entry* entry) { readyResumePath(entry->path, entry->type); } static void saveLast(char* path); static void loadLast(void); static int autoResume(void) { // NOTE: bypasses recents if (!exists(AUTO_RESUME_PATH)) return 0; char path[256]; getFile(AUTO_RESUME_PATH, path, 256); unlink(AUTO_RESUME_PATH); sync(); // make sure rom still exists char sd_path[256]; sprintf(sd_path, "%s%s", SDCARD_PATH, path); if (!exists(sd_path)) return 0; // make sure emu still exists char emu_name[256]; getEmuName(sd_path, emu_name); char emu_path[256]; getEmuPath(emu_name, emu_path); if (!exists(emu_path)) return 0; // putFile(LAST_PATH, FAUX_RECENT_PATH); // saveLast() will crash here because top is NULL char cmd[256]; sprintf(cmd, "'%s' '%s'", escapeSingleQuotes(emu_path), escapeSingleQuotes(sd_path)); putInt(RESUME_SLOT_PATH, AUTO_RESUME_SLOT); queueNext(cmd); return 1; } static void openPak(char* path) { // NOTE: escapeSingleQuotes() modifies the passed string // so we need to save the path before we call that // if (prefixMatch(ROMS_PATH, path)) { // addRecent(path); // } saveLast(path); char cmd[256]; sprintf(cmd, "'%s/launch.sh'", escapeSingleQuotes(path)); queueNext(cmd); } static void openRom(char* path, char* last) { char sd_path[256]; strcpy(sd_path, path); char m3u_path[256]; int has_m3u = hasM3u(sd_path, m3u_path); char recent_path[256]; strcpy(recent_path, has_m3u ? m3u_path : sd_path); if (has_m3u && suffixMatch(".m3u", sd_path)) { getFirstDisc(m3u_path, sd_path); } char emu_name[256]; getEmuName(sd_path, emu_name); if (should_resume) { char slot[16]; getFile(slot_path, slot, 16); putFile(RESUME_SLOT_PATH, slot); should_resume = 0; if (has_m3u) { char rom_file[256]; strcpy(rom_file, strrchr(m3u_path, '/') + 1); // get disc for state char disc_path_path[256]; sprintf(disc_path_path, "%s/.minui/%s/%s.%s.txt", USERDATA_PATH, emu_name, rom_file, slot); // /.userdata/.minui//.ext.0.txt if (exists(disc_path_path)) { // switch to disc path char disc_path[256]; getFile(disc_path_path, disc_path, 256); if (disc_path[0]=='/') strcpy(sd_path, disc_path); // absolute else { // relative strcpy(sd_path, m3u_path); char* tmp = strrchr(sd_path, '/') + 1; strcpy(tmp, disc_path); } } } } else putInt(RESUME_SLOT_PATH,8); // resume hidden default state char emu_path[256]; getEmuPath(emu_name, emu_path); // NOTE: escapeSingleQuotes() modifies the passed string // so we need to save the path before we call that addRecent(recent_path); saveLast(last==NULL ? sd_path : last); char cmd[256]; sprintf(cmd, "'%s' '%s'", escapeSingleQuotes(emu_path), escapeSingleQuotes(sd_path)); queueNext(cmd); } static void openDirectory(char* path, int auto_launch) { char auto_path[256]; if (hasCue(path, auto_path) && auto_launch) { openRom(auto_path, path); return; } char m3u_path[256]; strcpy(m3u_path, auto_path); char* tmp = strrchr(m3u_path, '.') + 1; // extension strcpy(tmp, "m3u"); // replace with m3u if (exists(m3u_path) && auto_launch) { auto_path[0] = '\0'; if (getFirstDisc(m3u_path, auto_path)) { openRom(auto_path, path); return; } // TODO: doesn't handle empty m3u files } int selected = 0; int start = selected; int end = 0; if (top && top->entries->count>0) { if (restore_depth==stack->count && top->selected==restore_relative) { selected = restore_selected; start = restore_start; end = restore_end; } } top = Directory_new(path, selected); top->start = start; top->end = end ? end : ((top->entries->countentries->count : MAIN_ROW_COUNT); Array_push(stack, top); } static void closeDirectory(void) { restore_selected = top->selected; restore_start = top->start; restore_end = top->end; DirectoryArray_pop(stack); restore_depth = stack->count; top = stack->items[stack->count-1]; restore_relative = top->selected; } static void Entry_open(Entry* self) { if (self->type==ENTRY_ROM) { char *last = NULL; if (prefixMatch(COLLECTIONS_PATH, top->path)) { char* tmp; char filename[256]; tmp = strrchr(self->path, '/'); if (tmp) strcpy(filename, tmp+1); char last_path[256]; sprintf(last_path, "%s/%s", top->path, filename); last = last_path; } openRom(self->path, last); } else if (self->type==ENTRY_PAK) { openPak(self->path); } else if (self->type==ENTRY_DIR) { openDirectory(self->path, 1); } } /////////////////////////////////////// static void saveLast(char* path) { // special case for recently played if (exactMatch(top->path, FAUX_RECENT_PATH)) { // NOTE: that we don't have to save the file because // your most recently played game will always be at // the top which is also the default selection path = FAUX_RECENT_PATH; } putFile(LAST_PATH, path); } static void loadLast(void) { // call after loading root directory if (!exists(LAST_PATH)) return; char last_path[256]; getFile(LAST_PATH, last_path, 256); char full_path[256]; strcpy(full_path, last_path); char* tmp; char filename[256]; tmp = strrchr(last_path, '/'); if (tmp) strcpy(filename, tmp); Array* last = Array_new(); while (!exactMatch(last_path, SDCARD_PATH)) { Array_push(last, strdup(last_path)); char* slash = strrchr(last_path, '/'); last_path[(slash-last_path)] = '\0'; } while (last->count>0) { char* path = Array_pop(last); if (!exactMatch(path, ROMS_PATH)) { // romsDir is effectively root as far as restoring state after a game char collated_path[256]; collated_path[0] = '\0'; if (suffixMatch(")", path) && isConsoleDir(path)) { strcpy(collated_path, path); tmp = strrchr(collated_path, '('); if (tmp) tmp[1] = '\0'; // 1 because we want to keep the opening parenthesis to avoid collating "Game Boy Color" and "Game Boy Advance" into "Game Boy" } for (int i=0; ientries->count; i++) { Entry* entry = top->entries->items[i]; // NOTE: strlen() is required for collated_path, '\0' wasn't reading as NULL for some reason if (exactMatch(entry->path, path) || (strlen(collated_path) && prefixMatch(collated_path, entry->path)) || (prefixMatch(COLLECTIONS_PATH, full_path) && suffixMatch(filename, entry->path))) { top->selected = i; if (i>=top->end) { top->start = i; top->end = top->start + MAIN_ROW_COUNT; if (top->end>top->entries->count) { top->end = top->entries->count; top->start = top->end - MAIN_ROW_COUNT; } } if (last->count==0 && !exactMatch(entry->path, FAUX_RECENT_PATH) && !(!exactMatch(entry->path, COLLECTIONS_PATH) && prefixMatch(COLLECTIONS_PATH, entry->path))) break; // don't show contents of auto-launch dirs if (entry->type==ENTRY_DIR) { openDirectory(entry->path, 0); break; } } } } free(path); // we took ownership when we popped it } StringArray_free(last); } /////////////////////////////////////// static void Menu_init(void) { stack = Array_new(); // array of open Directories recents = Array_new(); openDirectory(SDCARD_PATH, 0); loadLast(); // restore state when available } static void Menu_quit(void) { RecentArray_free(recents); DirectoryArray_free(stack); } /////////////////////////////////////// int main (int argc, char *argv[]) { if (autoResume()) return 0; // nothing to do dump("MinUI"); SDL_Surface* screen = GFX_init(MODE_MAIN); InitSettings(); PAD_reset(); SDL_Surface* version = NULL; Menu_init(); PAD_reset(); int dirty = 1; int show_setting = 0; // 1=brightness,2=volume while (!quit) { GFX_startFrame(); unsigned long frame_start = SDL_GetTicks(); PAD_poll(); int selected = top->selected; int total = top->entries->count; if (total>0) { if (PAD_justRepeated(BTN_UP)) { if (selected==0 && !PAD_justPressed(BTN_UP)) { // stop at top } else { selected -= 1; if (selected<0) { selected = total-1; int start = total - MAIN_ROW_COUNT; top->start = (start<0) ? 0 : start; top->end = total; } else if (selectedstart) { top->start -= 1; top->end -= 1; } } } else if (PAD_justRepeated(BTN_DOWN)) { if (selected==total-1 && !PAD_justPressed(BTN_DOWN)) { // stop at bottom } else { selected += 1; if (selected>=total) { selected = 0; top->start = 0; top->end = (total=top->end) { top->start += 1; top->end += 1; } } } if (PAD_justRepeated(BTN_LEFT)) { selected -= MAIN_ROW_COUNT; if (selected<0) { selected = 0; top->start = 0; top->end = (totalstart) { top->start -= MAIN_ROW_COUNT; if (top->start<0) top->start = 0; top->end = top->start + MAIN_ROW_COUNT; } } else if (PAD_justRepeated(BTN_RIGHT)) { selected += MAIN_ROW_COUNT; if (selected>=total) { selected = total-1; int start = total - MAIN_ROW_COUNT; top->start = (start<0) ? 0 : start; top->end = total; } else if (selected>=top->end) { top->end += MAIN_ROW_COUNT; if (top->end>total) top->end = total; top->start = top->end - MAIN_ROW_COUNT; } } } if (PAD_justRepeated(BTN_L1)) { // previous alpha Entry* entry = top->entries->items[selected]; int i = entry->alpha-1; if (i>=0) { selected = top->alphas->items[i]; if (total>MAIN_ROW_COUNT) { top->start = selected; top->end = top->start + MAIN_ROW_COUNT; if (top->end>total) top->end = total; top->start = top->end - MAIN_ROW_COUNT; } } } else if (PAD_justRepeated(BTN_R1)) { // next alpha Entry* entry = top->entries->items[selected]; int i = entry->alpha+1; if (ialphas->count) { selected = top->alphas->items[i]; if (total>MAIN_ROW_COUNT) { top->start = selected; top->end = top->start + MAIN_ROW_COUNT; if (top->end>total) top->end = total; top->start = top->end - MAIN_ROW_COUNT; } } } if (selected!=top->selected) { top->selected = selected; dirty = 1; } if (dirty && total>0) readyResume(top->entries->items[top->selected]); if (total>0 && can_resume && PAD_justReleased(BTN_RESUME)) { should_resume = 1; Entry_open(top->entries->items[top->selected]); dirty = 1; } else if (total>0 && PAD_justPressed(BTN_A)) { Entry_open(top->entries->items[top->selected]); total = top->entries->count; dirty = 1; if (total>0) readyResume(top->entries->items[top->selected]); } else if (PAD_justPressed(BTN_B) && stack->count>1) { closeDirectory(); total = top->entries->count; dirty = 1; // can_resume = 0; if (total>0) readyResume(top->entries->items[top->selected]); } POW_update(&dirty, &show_setting, NULL, NULL); if (dirty) { GFX_clear(screen); int ox; int oy; int ow = GFX_blitHardwareGroup(screen, show_setting); // list if (total>0) { int selected_row = top->selected - top->start; for (int i=top->start,j=0; iend; i++,j++) { Entry* entry = top->entries->items[i]; char* entry_name = entry->unique ? entry->unique : entry->name; int max_width = SCREEN_WIDTH - SCALE1(PADDING * 2); if (i==top->start) max_width -= ow; SDL_Color text_color = COLOR_WHITE; char display_name[256]; int text_width = GFX_truncateText(font.large, entry_name, display_name, max_width); max_width = MIN(max_width, text_width); if (j==selected_row) { GFX_blitPill(ASSET_WHITE_PILL, screen, &(SDL_Rect){ SCALE1(PADDING), SCALE1(PADDING+(j*PILL_SIZE)), max_width, SCALE1(PILL_SIZE) }); text_color = COLOR_BLACK; } SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, display_name, text_color); SDL_BlitSurface(text, &(SDL_Rect){ 0, 0, max_width-SCALE1(BUTTON_PADDING*2), text->h }, screen, &(SDL_Rect){ SCALE1(PADDING+BUTTON_PADDING), SCALE1(PADDING+(j*PILL_SIZE)+4) }); SDL_FreeSurface(text); } } else { GFX_blitMessage("Empty folder", screen, NULL); } // buttons if (show_setting) { if (PAD_isPressed(BTN_MENU)) GFX_blitButtonGroup((char*[]){ BRIGHTNESS_BUTTON_LABEL,"BRIGHTNESS", NULL }, screen, 0); else GFX_blitButtonGroup((char*[]){ "MENU","BRIGHTNESS", NULL }, screen, 0); } else if (can_resume) { GFX_blitButtonGroup((char*[]){ "X","RESUME", NULL }, screen, 0); } else { GFX_blitButtonGroup((char*[]){ "POWER","SLEEP", NULL }, screen, 0); } if (total==0) { if (stack->count>1) { GFX_blitButtonGroup((char*[]){ "B","BACK", NULL }, screen, 1); } } else { if (stack->count>1) { GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OPEN", NULL }, screen, 1); } else { GFX_blitButtonGroup((char*[]){ "A","OPEN", NULL }, screen, 1); } } } // scroll long names if (total>0) { int selected_row = top->selected - top->start; Entry* entry = top->entries->items[top->selected]; // if (GFX_scrollMenu(screen, entry->name, entry->path, entry->unique, selected_row, top->selected, was_dirty, dirty)) dirty = 1; } if (dirty) { GFX_flip(screen); dirty = 0; } else { // slow down to 60fps unsigned long frame_duration = SDL_GetTicks() - frame_start; #define TARGET_FRAME_DURATION 17 if (frame_duration