1492 lines
No EOL
39 KiB
C
1492 lines
No EOL
39 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <SDL/SDL.h>
|
|
#include <SDL/SDL_image.h>
|
|
#include <SDL/SDL_ttf.h>
|
|
#include <msettings.h>
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "defines.h"
|
|
#include "utils.h"
|
|
#include "api.h"
|
|
|
|
///////////////////////////////////////
|
|
|
|
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; i<mid; i++) {
|
|
void* item = self->items[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; i<self->count; i++) {
|
|
if (exactMatch(self->items[i], str)) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
static void StringArray_free(Array* self) {
|
|
for (int i=0; i<self->count; 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:
|
|
} 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;
|
|
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; i<self->count; 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; i<self->count; 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; i<self->entries->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; i<self->count; 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; i<self->count; 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; i<self->count; 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; i<recents->count; 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/%s.pak/launch.sh", SDCARD_PATH, PLATFORM, emu_name);
|
|
return exists(pak_path);
|
|
}
|
|
static void getEmuPath(char* emu_name, char* pak_path) {
|
|
sprintf(pak_path, "%s/Emus/%s/%s.pak/launch.sh", SDCARD_PATH, PLATFORM, emu_name);
|
|
if (exists(pak_path)) return;
|
|
sprintf(pak_path, "%s/Emus/%s.pak/launch.sh", PAKS_PATH, emu_name);
|
|
}
|
|
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 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->count<MAX_RECENTS) {
|
|
// this logic replaces an existing disc from a multi-disc game with the last used
|
|
char m3u_path[256];
|
|
if (hasM3u(sd_path, m3u_path)) { // TODO: this might tank launch speed
|
|
char parent_path[256];
|
|
strcpy(parent_path, line);
|
|
char* tmp = strrchr(parent_path, '/') + 1;
|
|
tmp[0] = '\0';
|
|
|
|
int found = 0;
|
|
for (int i=0; i<parent_paths->count; 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; i<emus->count; 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; i<collections->count; 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; i<entries->count; i++) {
|
|
Array_push(root, entries->items[i]);
|
|
}
|
|
Array_free(entries); // root now owns entries' entries
|
|
|
|
char* tools_path = SDCARD_PATH "/Tools/" PLATFORM;
|
|
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; i<recents->count; 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/<EMU>/<romname>.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/<EMU>/<romname>.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->count<MAIN_ROW_COUNT) ? top->entries->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; i<top->entries->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
|
|
|
|
LOG_info("MinUI\n");
|
|
|
|
SDL_Surface* screen = GFX_init(MODE_MAIN);
|
|
InitSettings();
|
|
PAD_reset();
|
|
|
|
SDL_Surface* version = NULL;
|
|
|
|
Menu_init();
|
|
|
|
// now that (most of) the heavy lifting is done, take a load off
|
|
POW_setCPUSpeed(CPU_SPEED_MENU);
|
|
GFX_setVsync(VSYNC_STRICT);
|
|
|
|
PAD_reset();
|
|
int dirty = 1;
|
|
int show_version = 0;
|
|
int show_setting = 0; // 1=brightness,2=volume
|
|
while (!quit) {
|
|
GFX_startFrame();
|
|
unsigned long now = SDL_GetTicks();
|
|
|
|
PAD_poll();
|
|
|
|
int selected = top->selected;
|
|
int total = top->entries->count;
|
|
|
|
if (show_version) {
|
|
if (PAD_justPressed(BTN_B) || PAD_tappedMenu(now)) {
|
|
show_version = 0;
|
|
dirty = 1;
|
|
}
|
|
}
|
|
else {
|
|
if (PAD_tappedMenu(now)) {
|
|
show_version = 1;
|
|
dirty = 1;
|
|
}
|
|
else 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 (selected<top->start) {
|
|
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<MAIN_ROW_COUNT) ? total : MAIN_ROW_COUNT;
|
|
}
|
|
else if (selected>=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 = (total<MAIN_ROW_COUNT) ? total : MAIN_ROW_COUNT;
|
|
}
|
|
else if (selected<top->start) {
|
|
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 (i<top->alphas->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);
|
|
|
|
if (show_version) {
|
|
if (!version) {
|
|
// TODO: load from a file
|
|
char release[256];
|
|
getFile(ROOT_SYSTEM_PATH "/version.txt", release, 256);
|
|
|
|
char *tmp,*commit;
|
|
commit = strrchr(release, '\n');
|
|
commit[0] = '\0';
|
|
commit = strrchr(release, '\n')+1;
|
|
tmp = strchr(release, '\n');
|
|
tmp[0] = '\0';
|
|
|
|
char* model = "Anbernic RG35XX";
|
|
|
|
SDL_Surface* release_txt = TTF_RenderUTF8_Blended(font.large, "Release", COLOR_DARK_TEXT);
|
|
SDL_Surface* version_txt = TTF_RenderUTF8_Blended(font.large, release, COLOR_WHITE);
|
|
SDL_Surface* commit_txt = TTF_RenderUTF8_Blended(font.large, "Commit", COLOR_DARK_TEXT);
|
|
SDL_Surface* hash_txt = TTF_RenderUTF8_Blended(font.large, commit, COLOR_WHITE);
|
|
|
|
SDL_Surface* model_txt = TTF_RenderUTF8_Blended(font.large, "Model", COLOR_DARK_TEXT);
|
|
SDL_Surface* type_txt = TTF_RenderUTF8_Blended(font.large, model, COLOR_WHITE);
|
|
|
|
#define VERSION_LINE_HEIGHT 24
|
|
int x = release_txt->w + SCALE1(8);
|
|
int w = x + version_txt->w;
|
|
int h = SCALE1(VERSION_LINE_HEIGHT*4);
|
|
version = SDL_CreateRGBSurface(0,w,h,16,0,0,0,0);
|
|
|
|
SDL_BlitSurface(release_txt, NULL, version, &(SDL_Rect){0, 0});
|
|
SDL_BlitSurface(version_txt, NULL, version, &(SDL_Rect){x,0});
|
|
SDL_BlitSurface(commit_txt, NULL, version, &(SDL_Rect){0,SCALE1(VERSION_LINE_HEIGHT)});
|
|
SDL_BlitSurface(hash_txt, NULL, version, &(SDL_Rect){x,SCALE1(VERSION_LINE_HEIGHT)});
|
|
|
|
SDL_BlitSurface(model_txt, NULL, version, &(SDL_Rect){0,SCALE1(VERSION_LINE_HEIGHT*3)});
|
|
SDL_BlitSurface(type_txt, NULL, version, &(SDL_Rect){x,SCALE1(VERSION_LINE_HEIGHT*3)});
|
|
|
|
SDL_FreeSurface(release_txt);
|
|
SDL_FreeSurface(version_txt);
|
|
SDL_FreeSurface(commit_txt);
|
|
SDL_FreeSurface(hash_txt);
|
|
SDL_FreeSurface(model_txt);
|
|
SDL_FreeSurface(type_txt);
|
|
}
|
|
SDL_BlitSurface(version, NULL, screen, &(SDL_Rect){(SCREEN_WIDTH-version->w)/2,(SCREEN_HEIGHT-version->h)/2});
|
|
|
|
// buttons (duped and trimmed from below)
|
|
if (show_setting) {
|
|
if (show_setting==1) GFX_blitButtonGroup((char*[]){ BRIGHTNESS_BUTTON_LABEL,"BRIGHTNESS", NULL }, screen, 0);
|
|
else GFX_blitButtonGroup((char*[]){ "MENU","BRIGHTNESS", NULL }, screen, 0);
|
|
}
|
|
else {
|
|
GFX_blitButtonGroup((char*[]){ "POWER","SLEEP", NULL }, screen, 0);
|
|
}
|
|
|
|
GFX_blitButtonGroup((char*[]){ "B","BACK", NULL }, screen, 1);
|
|
}
|
|
else {
|
|
// list
|
|
if (total>0) {
|
|
int selected_row = top->selected - top->start;
|
|
for (int i=top->start,j=0; i<top->end; i++,j++) {
|
|
Entry* entry = top->entries->items[i];
|
|
char* entry_name = entry->name;
|
|
char* entry_unique = entry->unique;
|
|
int available_width = SCREEN_WIDTH - SCALE1(PADDING * 2);
|
|
if (i==top->start) available_width -= ow;
|
|
|
|
SDL_Color text_color = COLOR_WHITE;
|
|
|
|
trimSortingMeta(&entry_name);
|
|
|
|
char display_name[256];
|
|
int text_width = GFX_truncateText(font.large, entry_unique ? entry_unique : entry_name, display_name, available_width);
|
|
int max_width = MIN(available_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;
|
|
}
|
|
else if (entry->unique) {
|
|
trimSortingMeta(&entry_unique);
|
|
char unique_name[256];
|
|
GFX_truncateText(font.large, entry_unique, unique_name, available_width);
|
|
|
|
SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, unique_name, COLOR_DARK_TEXT);
|
|
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)
|
|
});
|
|
|
|
GFX_truncateText(font.large, entry_name, display_name, available_width);
|
|
}
|
|
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 (show_setting==1) 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
GFX_flip(screen);
|
|
dirty = 0;
|
|
}
|
|
else GFX_sync();
|
|
}
|
|
|
|
if (version) SDL_FreeSurface(version);
|
|
SDL_FreeSurface(screen);
|
|
|
|
Menu_quit();
|
|
GFX_quit();
|
|
QuitSettings();
|
|
} |