// WBFS FAT by oggzee #include #include #include #include #include #include #include #include #include #include #include "settings/CSettings.h" #include "usbloader/disc.h" #include "fatmounter.h" #include "wbfs_fat.h" #include "prompts/ProgressWindow.h" #include "wbfs_rw.h" #include "gecko.h" #define MAX_FAT_PATH 1024 #define TITLE_LEN 64 using namespace std; char Wbfs_Fat::wbfs_fs_drive[16]; char Wbfs_Fat::wbfs_fat_dir[16] = "/wbfs"; char Wbfs_Fat::invalid_path[] = "/\\:|<>?*\"'"; struct discHdr *Wbfs_Fat::fat_hdr_list = NULL; u32 Wbfs_Fat::fat_hdr_count = 0; extern "C" { int _FAT_get_fragments(const char *path, _frag_append_t append_fragment, void *callback_data); extern FragList *frag_list; } u32 Wbfs_Fat::fat_sector_size = 512; Wbfs_Fat::Wbfs_Fat(u32 device, u32 lba, u32 size) : Wbfs(device, lba, size) { } s32 Wbfs_Fat::Open() { if (device == WBFS_DEVICE_USB && lba == fat_usb_sec) { strcpy(wbfs_fs_drive, "USB:"); } else if (device == WBFS_DEVICE_SDHC && lba == fat_sd_sec) { strcpy(wbfs_fs_drive, "SD:"); } else { if (WBFSDevice_Init(lba)) return -1; strcpy(wbfs_fs_drive, "WBFS:"); } return 0; } wbfs_disc_t* Wbfs_Fat::OpenDisc(u8 *discid) { char fname[MAX_FAT_PATH]; // wbfs 'partition' file if (!FindFilename(discid, fname, sizeof(fname))) return NULL; if (strcasecmp(strrchr(fname, '.'), ".iso") == 0) { // .iso file // create a fake wbfs_disc int fd; fd = open(fname, O_RDONLY); if (fd == -1) return NULL; wbfs_disc_t *iso_file = (wbfs_disc_t *) calloc(sizeof(wbfs_disc_t), 1); if (iso_file == NULL) return NULL; // mark with a special wbfs_part wbfs_iso_file.wbfs_sec_sz = 512; iso_file->p = &wbfs_iso_file; iso_file->header = (wbfs_disc_info_t*) fd; return iso_file; } wbfs_t *part = OpenPart(fname); if (!part) return NULL; return wbfs_open_disc(part, discid); } void Wbfs_Fat::CloseDisc(wbfs_disc_t* disc) { if (!disc) return; wbfs_t *part = disc->p; // is this really a .iso file? if (part == &wbfs_iso_file) { close((int) disc->header); free(disc); return; } wbfs_close_disc(disc); ClosePart(part); return; } s32 Wbfs_Fat::GetCount(u32 *count) { *count = 0; GetHeadersCount(); if (fat_hdr_count && fat_hdr_list) { // for compacter mem - move up as it will be freed later int size = fat_hdr_count * sizeof(struct discHdr); struct discHdr *buf = (struct discHdr *) malloc(size); if (buf) { memcpy(buf, fat_hdr_list, size); SAFE_FREE( fat_hdr_list ); fat_hdr_list = buf; } } *count = fat_hdr_count; return 0; } s32 Wbfs_Fat::GetHeaders(struct discHdr *outbuf, u32 cnt, u32 len) { u32 i; if (len > sizeof(struct discHdr)) { len = sizeof(struct discHdr); } #ifdef DEBUG_WBFS gprintf( "\n\tGetHeaders" ); #endif for (i = 0; i < cnt && i < fat_hdr_count; i++) { memcpy(&outbuf[i], &fat_hdr_list[i], len); } SAFE_FREE( fat_hdr_list ); fat_hdr_count = 0; #ifdef DEBUG_WBFS gprintf( "...ok" ); #endif return 0; } s32 Wbfs_Fat::AddGame(void) { static struct discHdr header ATTRIBUTE_ALIGN( 32 ); char path[MAX_FAT_PATH]; wbfs_t *part = NULL; s32 ret; // read ID from DVD Disc_ReadHeader(&header); // path GetDir(&header, path); // create wbfs 'partition' file part = CreatePart(header.id, path); if (!part) return -1; /* Add game to device */ partition_selector_t part_sel = ALL_PARTITIONS; int copy_1_1 = Settings.fullcopy; part_sel = (partition_selector_t) Settings.InstallPartitions; if (copy_1_1) { part_sel = ALL_PARTITIONS; } wbfs_t *old_hdd = hdd; hdd = part; // used by spinner ret = wbfs_add_disc(part, __ReadDVD, NULL, ProgressCallback, part_sel, copy_1_1); hdd = old_hdd; wbfs_trim(part); ClosePart(part); if (ret < 0) return ret; mk_title_txt(&header, path); return 0; } s32 Wbfs_Fat::RemoveGame(u8 *discid) { char fname[MAX_FAT_PATH]; int loc; // wbfs 'partition' file loc = FindFilename(discid, fname, sizeof(fname)); if (!loc) return -1; split_create(&split, fname, 0, 0, true); split_close(&split); if (loc == 1) return 0; // game is in subdir // remove optional .txt file DIR_ITER *dir_iter; struct stat st; char path[MAX_FAT_PATH]; char name[MAX_FAT_PATH]; strncpy(path, fname, sizeof(path)); char *p = strrchr(path, '/'); if (p) *p = 0; dir_iter = diropen(path); if (!dir_iter) return 0; while (dirnext(dir_iter, name, &st) == 0) { if (name[0] == '.') continue; if (name[6] != '_') continue; if (strncmp(name, (char*) discid, 6) != 0) continue; p = strrchr(name, '.'); if (!p) continue; if (strcasecmp(p, ".txt") != 0) continue; snprintf(fname, sizeof(fname), "%s/%s", path, name); remove(fname); break; } dirclose(dir_iter); // remove game subdir unlink(path); return 0; } s32 Wbfs_Fat::DiskSpace(f32 *used, f32 *free) { f32 size; int ret; struct statvfs wbfs_fat_vfs; *used = 0; *free = 0; ret = statvfs(wbfs_fs_drive, &wbfs_fat_vfs); if (ret) return -1; /* FS size in GB */ size = (f32) wbfs_fat_vfs.f_frsize * (f32) wbfs_fat_vfs.f_blocks / GB_SIZE; *free = (f32) wbfs_fat_vfs.f_frsize * (f32) wbfs_fat_vfs.f_bfree / GB_SIZE; *used = size - *free; return 0; } s32 Wbfs_Fat::RenameGame(u8 *discid, const void *newname) { wbfs_t *part = OpenPart((char *) discid); if (!part) return -1; s32 ret = wbfs_ren_disc(part, discid, (u8*) newname); ClosePart(part); return ret; } s32 Wbfs_Fat::ReIDGame(u8 *discid, const void *newID) { wbfs_t *part = OpenPart((char *) discid); if (!part) return -1; s32 ret = wbfs_rID_disc(part, discid, (u8*) newID); ClosePart(part); if (ret == 0) { char fname[100]; char fnamenew[100]; s32 cnt = 0x31; Filename(discid, fname, sizeof(fname), NULL); Filename((u8*) newID, fnamenew, sizeof(fnamenew), NULL); int stringlength = strlen(fname); while (rename(fname, fnamenew) == 0) { fname[stringlength] = cnt; fname[stringlength + 1] = 0; fnamenew[stringlength] = cnt; fnamenew[stringlength + 1] = 0; cnt++; } } return ret; } f32 Wbfs_Fat::EstimateGameSize() { wbfs_t *part = NULL; u64 size = (u64) 143432 * 2 * 0x8000ULL; u32 n_sector = size / fat_sector_size; u32 wii_sec_sz; // init a temporary dummy part // as a placeholder for wbfs_size_disc part = wbfs_open_partition(nop_rw_sector, nop_rw_sector, NULL, fat_sector_size, n_sector, 0, 1); if (!part) return -1; wii_sec_sz = part->wii_sec_sz; partition_selector_t part_sel; if (Settings.fullcopy) { part_sel = ALL_PARTITIONS; } else { part_sel = (partition_selector_t) Settings.InstallPartitions; } return wbfs_estimate_disc(part, __ReadDVD, NULL, part_sel); } // TITLE [GAMEID] bool Wbfs_Fat::CheckLayoutB(char *fname, int len, u8* id, char *fname_title) { if (len <= 8) return false; if (fname[len - 8] != '[' || fname[len - 1] != ']') return false; if (!is_gameid(&fname[len - 7])) return false; strncpy(fname_title, fname, TITLE_LEN); // cut at '[' fname_title[len - 8] = 0; int n = strlen(fname_title); if (n == 0) return false; // cut trailing _ or ' ' if (fname_title[n - 1] == ' ' || fname_title[n - 1] == '_') { fname_title[n - 1] = 0; } if (strlen(fname_title) == 0) return false; if (id) { memcpy(id, &fname[len - 7], 6); id[6] = 0; } return true; } s32 Wbfs_Fat::GetHeadersCount() { char path[MAX_FAT_PATH]; char fname[MAX_FAT_PATH]; char fpath[MAX_FAT_PATH]; struct discHdr tmpHdr; struct stat st; wbfs_t *part = NULL; u8 id[8]; int ret; char *p; u32 size; int is_dir; int len; char dir_title[65]; char fname_title[TITLE_LEN]; char *title; DIR_ITER *dir_iter; //dbg_time1(); SAFE_FREE( fat_hdr_list ); fat_hdr_count = 0; strcpy(path, wbfs_fs_drive); strcat(path, wbfs_fat_dir); dir_iter = diropen(path); if (!dir_iter) return 0; dir_iter = diropen(path); if (!dir_iter) return 0; while (dirnext(dir_iter, fname, &st) == 0) { //printf("found: %s\n", fname); Wpad_WaitButtonsCommon(); if ((char) fname[0] == '.') continue; len = strlen(fname); if (len < 8) continue; // "GAMEID_x" memcpy(id, fname, 6); id[6] = 0; *fname_title = 0; is_dir = S_ISDIR( st.st_mode ); //printf("mode: %d %d %x\n", is_dir, st.st_mode, st.st_mode); if (is_dir) { int lay_a = 0; int lay_b = 0; if (fname[6] == '_' && is_gameid((char*) id)) { // usb:/wbfs/GAMEID_TITLE/GAMEID.wbfs lay_a = 1; } if (CheckLayoutB(fname, len, NULL, fname_title)) { // usb:/wbfs/TITLE[GAMEID]/GAMEID.wbfs lay_b = 1; } if (!lay_a && !lay_b) continue; if (lay_a) { strncpy(dir_title, &fname[7], sizeof(dir_title)); } else { try_lay_b: if (!CheckLayoutB(fname, len, id, fname_title)) continue; } snprintf(fpath, sizeof(fpath), "%s/%s/%s.wbfs", path, fname, id); //printf("path2: %s\n", fpath); // if more than 50 games, skip second stat to improve speed // but if ambiguous layout check anyway if (fat_hdr_count < 50 || (lay_a && lay_b)) { if (stat(fpath, &st) == -1) { //printf("missing: %s\n", fpath); // try .iso strcpy(strrchr(fpath, '.'), ".iso"); // replace .wbfs with .iso if (stat(fpath, &st) == -1) { //printf("missing: %s\n", fpath); // try .ciso strcpy(strrchr(fpath, '.'), ".ciso"); // replace .iso with .ciso if (stat(fpath, &st) == -1) { if (lay_a && lay_b == 1) { // mark lay_b so that the stat check is still done, // but lay_b is not re-tried again lay_b = 2; // retry with layout b goto try_lay_b; } continue; } } } } else { st.st_size = 1024 * 1024; } } else { // usb:/wbfs/GAMEID.wbfs // or usb:/wbfs/GAMEID.iso // or usb:/wbfs/GAMEID.ciso p = strrchr(fname, '.'); if (!p) continue; if ((strcasecmp(p, ".wbfs") != 0) && (strcasecmp(p, ".iso") != 0) && (strcasecmp(p, ".ciso") != 0)) continue; int n = p - fname; // length withouth .wbfs if (n != 6) { // TITLE [GAMEID].wbfs if (!CheckLayoutB(fname, n, id, fname_title)) continue; } snprintf(fpath, sizeof(fpath), "%s/%s", path, fname); } //printf("found: %s %d MB\n", fpath, (int)(st.st_size/1024/1024)); // size must be at least 1MB to be considered a valid wbfs file if (st.st_size < 1024 * 1024) continue; // if we have titles.txt entry use that title = cfg_get_title(id); // if no titles.txt get title from dir or file name if (!title && *fname_title) { title = fname_title; } if (title) { memset(&tmpHdr, 0, sizeof(tmpHdr)); memcpy(tmpHdr.id, id, 6); strncpy(tmpHdr.title, title, sizeof(tmpHdr.title) - 1); tmpHdr.magic = 0x5D1C9EA3; goto add_hdr; } // else read it from file directly if (strcasecmp(strrchr(fpath, '.'), ".wbfs") == 0) { // wbfs file directly FILE *fp = fopen(fpath, "rb"); if (fp != NULL) { fseek(fp, 512, SEEK_SET); fread(&tmpHdr, sizeof(struct discHdr), 1, fp); fclose(fp); tmpHdr.is_ciso = 0; if ((tmpHdr.magic == 0x5D1C9EA3) && (memcmp(tmpHdr.id, id, 6) == 0)) { goto add_hdr; } } // no title found, read it from wbfs file // but this is a little bit slower // open 'partition' file part = OpenPart(fpath); if (!part) { continue; } // Get header ret = wbfs_get_disc_info(part, 0, (u8*) &tmpHdr, sizeof(struct discHdr), &size); ClosePart(part); if (ret == 0) { goto add_hdr; } } else if (strcasecmp(strrchr(fpath, '.'), ".iso") == 0) { // iso file FILE *fp = fopen(fpath, "rb"); if (fp != NULL) { fseek(fp, 0, SEEK_SET); fread(&tmpHdr, sizeof(struct discHdr), 1, fp); fclose(fp); tmpHdr.is_ciso = 0; if ((tmpHdr.magic == 0x5D1C9EA3) && (memcmp(tmpHdr.id, id, 6) == 0)) { goto add_hdr; } } } else if (strcasecmp(strrchr(fpath, '.'), ".ciso") == 0) { // ciso file FILE *fp = fopen(fpath, "rb"); if (fp != NULL) { fseek(fp, 0x8000, SEEK_SET); fread(&tmpHdr, sizeof(struct discHdr), 1, fp); fclose(fp); tmpHdr.is_ciso = 1; if ((tmpHdr.magic == 0x5D1C9EA3) && (memcmp(tmpHdr.id, id, 6) == 0)) { goto add_hdr; } } } // fail: continue; // succes: add tmpHdr to list: add_hdr: memset(&st, 0, sizeof(st)); //printf("added: %.6s %.20s\n", tmpHdr.id, tmpHdr.title); Wpad_WaitButtons(); fat_hdr_count++; fat_hdr_list = (struct discHdr *) realloc(fat_hdr_list, fat_hdr_count * sizeof(struct discHdr)); memcpy(&fat_hdr_list[fat_hdr_count - 1], &tmpHdr, sizeof(struct discHdr)); } dirclose(dir_iter); //dbg_time2("\nFAT_GetCount"); Wpad_WaitButtonsCommon(); return 0; } int Wbfs_Fat::FindFilename(u8 *id, char *fname, int len) { struct stat st; // look for direct .wbfs file Filename(id, fname, len, NULL); if (stat(fname, &st) == 0) return 1; // look for direct .iso file strcpy(strrchr(fname, '.'), ".iso"); // replace .wbfs with .iso if (stat(fname, &st) == 0) return 1; // look for direct .ciso file strcpy(strrchr(fname, '.'), ".ciso"); // replace .iso with .ciso if (stat(fname, &st) == 0) return 1; // direct file not found, check subdirs *fname = 0; DIR_ITER *dir_iter; char path[MAX_FAT_PATH]; char name[MAX_FAT_PATH]; strcpy(path, wbfs_fs_drive); strcat(path, wbfs_fat_dir); dir_iter = diropen(path); //printf("dir: %s %p\n", path, dir); Wpad_WaitButtons(); if (!dir_iter) { return 0; } while (dirnext(dir_iter, name, &st) == 0) { //dbg_printf("name:%s\n", name); if (name[0] == '.') continue; int n = strlen(name); if (n < 8) continue; if (S_ISDIR( st.st_mode )) { if (name[6] == '_') { // GAMEID_TITLE if (strncmp(name, (char*) id, 6) != 0) goto try_alter; } else { try_alter: // TITLE [GAMEID] if (name[n - 8] != '[' || name[n - 1] != ']') continue; if (strncmp(&name[n - 7], (char*) id, 6) != 0) continue; } // look for .wbfs file snprintf(fname, len, "%s/%s/%.6s.wbfs", path, name, id); if (stat(fname, &st) == 0) break; // look for .iso file snprintf(fname, len, "%s/%s/%.6s.iso", path, name, id); if (stat(fname, &st) == 0) break; // look for .ciso file snprintf(fname, len, "%s/%s/%.6s.ciso", path, name, id); } else { // TITLE [GAMEID].wbfs char fn_title[TITLE_LEN]; u8 fn_id[8]; char *p = strrchr(name, '.'); if (!p) continue; if ((strcasecmp(p, ".wbfs") != 0) && (strcasecmp(p, ".iso") != 0) && (strcasecmp(p, ".ciso") != 0)) continue; int n = p - name; // length withouth .wbfs if (!CheckLayoutB(name, n, fn_id, fn_title)) continue; if (strncmp((char*) fn_id, (char*) id, 6) != 0) continue; snprintf(fname, len, "%s/%s", path, name); } if (stat(fname, &st) == 0) break; *fname = 0; } dirclose(dir_iter); if (*fname) { // found //printf("found:%s\n", fname); return 2; } // not found return 0; } wbfs_t* Wbfs_Fat::OpenPart(char *fname) { wbfs_t *part = NULL; int ret; // wbfs 'partition' file ret = split_open(&split, fname); if (ret) return NULL; part = wbfs_open_partition(split_read_sector, nop_rw_sector, //readonly //split_write_sector, &split, fat_sector_size, split.total_sec, 0, 0); if (!part) { split_close(&split); } return part; } void Wbfs_Fat::ClosePart(wbfs_t* part) { if (!part) return; split_info_t *s = (split_info_t*) part->callback_data; wbfs_close(part); if (s) split_close(s); } void Wbfs_Fat::Filename(u8 *id, char *fname, int len, char *path) { if (path == NULL) { snprintf(fname, len, "%s%s/%.6s.wbfs", wbfs_fs_drive, wbfs_fat_dir, id); } else { snprintf(fname, len, "%s/%.6s.wbfs", path, id); } } void Wbfs_Fat::GetDir(struct discHdr *header, char *path) { strcpy(path, wbfs_fs_drive); strcat(path, wbfs_fat_dir); if (Settings.FatInstallToDir) { strcat(path, "/"); int layout = 0; if (Settings.FatInstallToDir == 2) layout = 1; mk_gameid_title(header, path + strlen(path), 0, layout); } } wbfs_t* Wbfs_Fat::CreatePart(u8 *id, char *path) { char fname[MAX_FAT_PATH]; wbfs_t *part = NULL; u64 size = (u64) 143432 * 2 * 0x8000ULL; u32 n_sector = size / 512; int ret; //printf("CREATE PART %s %lld %d\n", id, size, n_sector); snprintf(fname, sizeof(fname), "%s%s", wbfs_fs_drive, wbfs_fat_dir); mkdir(fname, 0777); // base usb:/wbfs mkdir(path, 0777); // game subdir Filename(id, fname, sizeof(fname), path); printf("Writing to %s\n", fname); ret = split_create(&split, fname, OPT_split_size, size, true); if (ret) return NULL; // force create first file u32 scnt = 0; int fd = split_get_file(&split, 0, &scnt, 0); if (fd < 0) { printf("ERROR creating file\n"); sleep(2); split_close(&split); return NULL; } part = wbfs_open_partition(split_read_sector, split_write_sector, &split, fat_sector_size, n_sector, 0, 1); if (!part) { split_close(&split); } return part; } void Wbfs_Fat::mk_title_txt(struct discHdr *header, char *path) { char fname[MAX_FAT_PATH]; FILE *f; strcpy(fname, path); strcat(fname, "/"); mk_gameid_title(header, fname + strlen(fname), 1, 0); strcat(fname, ".txt"); f = fopen(fname, "wb"); if (!f) return; fprintf(f, "%.6s = %.64s\n", header->id, get_title(header)); fclose(f); printf("Info file: %s\n", fname); } void Wbfs_Fat::mk_gameid_title(struct discHdr *header, char *name, int re_space, int layout) { int i, len; char title[65]; char id[8]; memcpy(name, header->id, 6); name[6] = 0; strncpy(title, get_title(header), sizeof(title)); title_filename(title); if (layout == 0) { sprintf(name, "%s_%s", id, title); } else { sprintf(name, "%s [%s]", title, id); } // replace space with '_' if (re_space) { len = strlen(name); for (i = 0; i < len; i++) { if (name[i] == ' ') name[i] = '_'; } } } void Wbfs_Fat::title_filename(char *title) { int i, len; // trim leading space len = strlen(title); while (*title == ' ') { memmove(title, title + 1, len); len--; } // trim trailing space - not allowed on windows directories while (len && title[len - 1] == ' ') { title[len - 1] = 0; len--; } // replace silly chars with '_' for (i = 0; i < len; i++) { if (strchr(invalid_path, title[i]) || iscntrl((int) title[i])) { title[i] = '_'; } } } bool Wbfs_Fat::is_gameid(char *id) { int i; for (i = 0; i < 6; i++) { if (!isalnum((u32) id[i])) return false; } return true; } int Wbfs_Fat::GetFragList(u8 *id) { char fname[1024]; char fname1[1024]; struct stat st; FragList *fs = NULL; FragList *fa = NULL; FragList *fw = NULL; int ret; int i; int is_wbfs = 0; int ret_val = -1; ret = FindFilename(id, fname, sizeof(fname)); if (!ret) return -1; if (strcasecmp(strrchr(fname, '.'), ".wbfs") == 0) { is_wbfs = 1; } fs = (FragList *) malloc(sizeof(FragList)); fa = (FragList *) malloc(sizeof(FragList)); fw = (FragList *) malloc(sizeof(FragList)); frag_init(fa, MAX_FRAG); for (i = 0; i < 10; i++) { frag_init(fs, MAX_FRAG); if (i > 0) { fname[strlen(fname) - 1] = '0' + i; if (stat(fname, &st) == -1) break; } strcpy(fname1, fname); if ((ret = GetFragList((char *) &fname, &_frag_append, fs))) { ret_val = ret; goto out; } frag_concat(fa, fs); } frag_list = (FragList *) malloc(sizeof(FragList)); frag_init(frag_list, MAX_FRAG); if (is_wbfs) { // if wbfs file format, remap. //printf("=====\n"); wbfs_disc_t *d = OpenDisc(id); if (!d) goto out; frag_init(fw, MAX_FRAG); ret = wbfs_get_fragments(d, &_frag_append, fw); if (ret) goto out; CloseDisc(d); // DEBUG: //frag_list->num = MAX_FRAG-10; // stress test ret = frag_remap(frag_list, fw, fa); if (ret) goto out; } else { // .iso does not need remap just copy //printf("fa:\n"); memcpy(frag_list, fa, sizeof(FragList)); } ret_val = 0; out: if (ret_val) { // error SAFE_FREE( frag_list ); } SAFE_FREE( fs ); SAFE_FREE( fa ); SAFE_FREE( fw ); return ret_val; } int Wbfs_Fat::GetFragList(char *filename, _frag_append_t append_fragment, FragList *fs) { return _FAT_get_fragments(filename, append_fragment, fs); } bool Wbfs_Fat::ShowFreeSpace(void) { return false; }