Skip to content

Instantly share code, notes, and snippets.

@ITotalJustice
Created June 5, 2023 17:44
Show Gist options
  • Save ITotalJustice/241028c00e7f3ffaba52521b0d64055f to your computer and use it in GitHub Desktop.
Save ITotalJustice/241028c00e7f3ffaba52521b0d64055f to your computer and use it in GitHub Desktop.
nro scanning code. new code is faster by 0.07 seconds (64 entries, 48 nros) total time 0.30 seconds
auto nro_parse(FsFileSystem* fs, const char* path, const char* star_path, NroEntry& entry) -> bool {
Result rc{};
FsFile f{};
u64 bytes_read{};
if (R_FAILED(rc = fsFsOpenFile(fs, path, FsOpenMode_Read, &f))) {
// std::printf("failed to open: %s\n", path);
return false;
}
NroData data;
NroAssetHeader asset;
if (R_FAILED(rc = fsFileRead(&f, 0, &data, sizeof(data), 0, &bytes_read)) ||
data.header.magic != NROHEADER_MAGIC) {
// std::printf("bad headermagic: %X %X\n", data.header.magic, NROHEADER_MAGIC);
fsFileClose(&f);
return false;
}
if (R_FAILED(rc = fsFileRead(&f, data.header.size, &asset, sizeof(asset), 0, &bytes_read)) ||
asset.magic != NROASSETHEADER_MAGIC) {
// std::printf("bad NROASSETHEADER_MAGIC: %X %X\n", asset.magic, NROASSETHEADER_MAGIC);
fsFileClose(&f);
return false;
}
entry.icon.resize(asset.icon.size);
if (R_FAILED(rc = fsFileRead(&f, data.header.size + asset.icon.offset, entry.icon.data(), entry.icon.size(), 0, &bytes_read))) {
fsFileClose(&f);
return false;
}
if (R_FAILED(rc = fsFileRead(&f, data.header.size + asset.nacp.offset, &entry.nacp, sizeof(entry.nacp), 0, &bytes_read))) {
fsFileClose(&f);
return false;
}
std::strcpy(entry.path, path);
std::strcpy(entry.star_path, star_path);
fsFileClose(&f);
// check if it favourited
if (R_SUCCEEDED(fsFsOpenFile(fs, star_path, FsOpenMode_Read, &f))) {
entry.is_star = true;
fsFileClose(&f);
}
return true;
}
// this function is recursive by 1 level deep
// if the nro is in switch/folder/folder2/app.nro it will NOT be found
// switch/folder/app.nro for example will work fine.
auto nro_scan_internal(const char* _path, std::vector<NroEntry>& nros, bool root) -> bool {
Result rc{};
FsDir d{};
s64 count{};
char path[FS_MAX_PATH]{};
char fullpath[FS_MAX_PATH]{};
char starpath[FS_MAX_PATH]{};
strncpy(path, _path, sizeof(path)-1);
// fs is already open by fsdev
// NOTE: this only applies to applications,
// applets should open fs themselves!
auto fs = fsdevGetDeviceFileSystem("sdmc");
if (!fs) {
std::printf("failed to get device fs system :(\n");
return false;
}
if (R_FAILED(rc = fsFsOpenDirectory(fs, path, FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles | FsDirOpenMode_NoFileSize, &d))) {
std::printf("failed to open dir\n");
return false;
}
if (R_FAILED(rc = fsDirGetEntryCount(&d, &count))) {
std::printf("failed to get dir count\n");
fsDirClose(&d);
return false;
}
// we won't run out of memory here
std::vector<FsDirectoryEntry> entries(count);
if (R_FAILED(rc = fsDirRead(&d, &count, entries.size(), entries.data()))) {
std::printf("failed to read dir :/\n");
fsDirClose(&d);
return false;
}
std::printf("count %ld found: %zu\n", count, entries.size());
fsDirClose(&d);
// size may of changed
entries.resize(count);
for (const auto& e : entries) {
// skip hidden files / folders
if ('.' == e.name[0]) {
continue;
}
// todo: only scan
if (e.type == FsDirEntryType_Dir) {
if (root) {
std::snprintf(fullpath, sizeof(fullpath)-1, "%s/%s", path, e.name);
nro_scan_internal(fullpath, nros, false);
}
} else if (e.type == FsDirEntryType_File && std::string_view{e.name}.ends_with(".nro")) {
NroEntry entry{};
std::snprintf(fullpath, sizeof(fullpath)-1, "%s/%s", path, e.name);
std::snprintf(starpath, sizeof(starpath)-1, "%s/.%s.star", path, e.name);
if (nro_parse(fs, fullpath, starpath, entry)) {
nros.emplace_back(entry);
// std::printf("added: %s\n", fullpath);
if (root == false) {
fsDirClose(&d);
return true;
}
} else {
std::printf("error when trying to parse %s\n", fullpath);
}
}
}
return true;
}
auto nro_parse(const char* path, const char* star_path, NroEntry& entry) -> bool {
auto fp = std::fopen(path, "rb");
if (!fp) {
return false;
}
// todo: error check and handle
NroData data;
NroAssetHeader asset;
std::fread(&data, 1, sizeof(data), fp);
if (data.header.magic != NROHEADER_MAGIC) {
// std::printf("bad headermagic: %X %X\n", data.header.magic, NROHEADER_MAGIC);
fclose(fp);
return false;
}
std::fseek(fp, data.header.size, SEEK_SET);
std::fread(&asset, 1, sizeof(asset), fp);
if (asset.magic != NROASSETHEADER_MAGIC) {
// std::printf("bad NROASSETHEADER_MAGIC: %X %X\n", asset.magic, NROASSETHEADER_MAGIC);
fclose(fp);
return false;
}
entry.icon.resize(asset.icon.size);
std::fseek(fp, data.header.size + asset.icon.offset, SEEK_SET);
std::fread(entry.icon.data(), 1, entry.icon.size(), fp);
std::fseek(fp, data.header.size + asset.nacp.offset, SEEK_SET);
std::fread(&entry.nacp, 1, sizeof(entry.nacp), fp);
std::strcpy(entry.path, path);
std::strcpy(entry.star_path, star_path);
fclose(fp);
// check if it favourited
if (auto f = fopen(star_path, "rb")) {
entry.is_star = true;
fclose(f);
}
return true;
}
auto nro_scan_internal(const char* path, std::vector<NroEntry>& nros, bool root) -> bool {
char fullpath[FS_MAX_PATH]{};
char starpath[FS_MAX_PATH]{};
auto dir = opendir(path);
if (!dir) {
std::printf("failed to open dir: %s\n", path);
return false;
}
while (auto d = readdir(dir)) {
if ('.' == d->d_name[0]) {
continue;
}
if (d->d_type == DT_DIR) {
if (root) {
std::sprintf(fullpath, "%s/%s", path, d->d_name);
nro_scan_internal(fullpath, nros, false);
}
} else if (d->d_type == DT_REG && std::string_view{d->d_name}.ends_with(".nro")) {
std::sprintf(fullpath, "%s/%s", path, d->d_name);
std::sprintf(starpath, "%s/.%s.star", path, d->d_name);
NroEntry entry{};
if (nro_parse(fullpath, starpath, entry)) {
nros.emplace_back(entry);
// std::printf("added: %s\n", fullpath);
if (root == false) {
closedir(dir);
return true;
}
} else {
std::printf("error when trying to parse %s\n", fullpath);
}
}
}
closedir(dir);
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment