#include #include #include #include #include #include #include #include #include #include #include using namespace nall; #include using namespace phoenix; struct Application : Window { file database; string source; string target; bool loadFile(const string &filename, string &suffix, uint8_t *&data, unsigned &size); void scanFamicom(const string &filename, uint8_t *data, unsigned size); void outputFamicom(const string &filename, uint8_t *data, unsigned size); void scanSuperFamicom(const string &filename, uint8_t *data, unsigned size); void outputSuperFamicom(const string &filename, uint8_t *data, unsigned size); void scanGameBoy(const string &filename, uint8_t *data, unsigned size); void outputGameBoy(const string &filename, uint8_t *data, unsigned size); void scanGameBoyColor(const string &filename, uint8_t *data, unsigned size); void outputGameBoyColor(const string &filename, uint8_t *data, unsigned size); void scanGameBoyAdvance(const string &filename, uint8_t *data, unsigned size); void outputGameBoyAdvance(const string &filename, uint8_t *data, unsigned size); void scanDirectory(); void outputDirectory(); }; bool Application::loadFile(const string &filename, string &suffix, uint8_t *&data, unsigned &size) { print("-> ", notdir(filename), "\n"); if(filename.endswith(".zip")) { zip archive; if(archive.open(filename) == false) return print("* failed to open archive\n"), false; if(archive.file.size() != 1) return print("* file count (", archive.file.size(), ") incorrect\n"), false; if(archive.extract(archive.file[0], data, size) == false) return print("* failed to extract ", filename, "\n"), false; uint32_t crc32 = crc32_calculate(data, size); if(crc32 != archive.file[0].crc32) return delete[] data, print("* CRC32 mismatch"), false; suffix = extension(archive.file[0].name); return true; } suffix = extension(filename); return file::read(filename, data, size); } // void Application::scanFamicom(const string &filename, uint8_t *data, unsigned size) { if(size & 255 != 16) return print("* ", filename, " missing iNES header\n"); string sha256 = nall::sha256(data, size); database.print(sha256, "{}"); database.print("0x", hex<8>(size - 16), "{}"); for(unsigned n = 0; n < 16; n++) { database.print(hex<2>(data[n]), n != 15 ? " " : "{}"); } database.print(notdir(nall::basename(filename)), "\n"); } void Application::outputFamicom(const string &filename, uint8_t *data, unsigned size) { if(size & 255 != 16) return print("* ", filename, " missing iNES header\n"); string markup = FamicomCartridge(data, size).markup; string path = {target, nall::basename(filename), ".fc/"}; mkdir(path, 0777); if(file::exists({path, "program.rom"}) == false) { file::write({path, "program.rom"}, data + 16, size - 16); } file::write({path, "manifest.xml"}, (const uint8_t*)markup(), markup.length()); } // // void Application::scanSuperFamicom(const string &filename, uint8_t *data, unsigned size) { if(size & 32767 == 512) size -= 512, data += 512; string sha256 = nall::sha256(data, size); database.print(sha256, "{}"); database.print("0x", hex<8>(size), "{}"); database.print(notdir(nall::basename(filename)), "\n"); } void Application::outputSuperFamicom(const string &filename, uint8_t *data, unsigned size) { if(size & 32767 == 512) size -= 512, data += 512; string markup = SuperFamicomCartridge(data, size).markup; string path = {target, nall::basename(filename), ".sfc/"}; mkdir(path, 0777); if(file::exists({path, "program.rom"}) == false) { file::write({path, "program.rom"}, data, size); } file::write({path, "manifest.xml"}, (const uint8_t*)markup(), markup.length()); } // // void Application::scanGameBoy(const string &filename, uint8_t *data, unsigned size) { string markup = GameBoyCartridge(data, size).markup; XML::Document document(markup); string sha256 = nall::sha256(data, size); database.print(sha256, "{}"); database.print("0x", hex<8>(size), "{}"); database.print(document["cartridge"]["mapper"].data, "{}"); database.print(notdir(nall::basename(filename)), "\n"); } void Application::outputGameBoy(const string &filename, uint8_t *data, unsigned size) { string markup = GameBoyCartridge(data, size).markup; string path = {target, nall::basename(filename), ".gb/"}; mkdir(path, 0777); if(file::exists({path, "program.rom"}) == false) { file::write({path, "program.rom"}, data, size); } file::write({path, "manifest.xml"}, (const uint8_t*)markup(), markup.length()); } // // void Application::scanGameBoyColor(const string &filename, uint8_t *data, unsigned size) { string markup = GameBoyCartridge(data, size).markup; XML::Document document(markup); string sha256 = nall::sha256(data, size); database.print(sha256, "{}"); database.print("0x", hex<8>(size), "{}"); database.print(document["cartridge"]["mapper"].data, "{}"); database.print(data[0x0143] == 0xc0 ? "N" : "Y", "{}"); database.print(notdir(nall::basename(filename)), "\n"); } void Application::outputGameBoyColor(const string &filename, uint8_t *data, unsigned size) { string markup = GameBoyCartridge(data, size).markup; string path = {target, nall::basename(filename), data[0x0143] == 0xc0 ? ".gbc/" : ".gbb/"}; mkdir(path, 0777); if(file::exists({path, "program.rom"}) == false) { file::write({path, "program.rom"}, data, size); } file::write({path, "manifest.xml"}, (const uint8_t*)markup(), markup.length()); } // // void Application::scanGameBoyAdvance(const string &filename, uint8_t *data, unsigned size) { string markup = GameBoyAdvanceCartridge(data, size).markup; string identifiers; if(auto start = markup.position("detected: ")) { if(auto finish = markup.position(" -->\n")) { identifiers = substr(markup, start() + 10, finish() - start() - 10); } } string sha256 = nall::sha256(data, size); database.print(sha256, "{}"); database.print("0x", hex<8>(size), "{}"); database.print(identifiers, "{}"); database.print(notdir(nall::basename(filename)), "\n"); } void Application::outputGameBoyAdvance(const string &filename, uint8_t *data, unsigned size) { string markup = GameBoyAdvanceCartridge(data, size).markup; string path = {target, nall::basename(filename), ".gba/"}; mkdir(path, 0777); if(file::exists({path, "program.rom"}) == false) { file::write({path, "program.rom"}, data, size); } file::write({path, "manifest.xml"}, (const uint8_t*)markup(), markup.length()); } // void Application::scanDirectory() { if(directory::exists(source) == false) return print("* source directory does not exist\n"); if(database.open(target, file::mode::write) == false) return print("* cannot open target file\n"); lstring files = directory::files(source); for(auto &filename : files) { string suffix; uint8_t *data; unsigned size; if(loadFile({source, filename}, suffix, data, size) == false) continue; if(suffix == "nes" || suffix == "fc") scanFamicom(filename, data, size); if(suffix == "sfc" || suffix == "smc") scanSuperFamicom(filename, data, size); if(suffix == "gb") scanGameBoy(filename, data, size); if(suffix == "gbc" || suffix == "gbb") scanGameBoyColor(filename, data, size); if(suffix == "gba") scanGameBoyAdvance(filename, data, size); delete[] data; } database.close(); } void Application::outputDirectory() { if(directory::exists(source) == false) return print("* source directory does not exist\n"); if(directory::exists(target) == false) return print("* target directory does not exist\n"); lstring files = directory::files(source); for(auto &filename : files) { string suffix; uint8_t *data; unsigned size; if(loadFile({source, filename}, suffix, data, size) == false) continue; if(suffix == "nes" || suffix == "fc") outputFamicom(filename, data, size); if(suffix == "sfc" || suffix == "smc") outputSuperFamicom(filename, data, size); if(suffix == "gb") outputGameBoy(filename, data, size); if(suffix == "gbc" || suffix == "gbb") outputGameBoyColor(filename, data, size); if(suffix == "gba") outputGameBoyAdvance(filename, data, size); delete[] data; } } int main(int argc, char **argv) { Application *application = new Application; if(argc == 4 && cstring{argv[1]} == "scan") { application->source = argv[2]; application->target = argv[3]; application->scanDirectory(); } else if(argc == 4 && cstring{argv[1]} == "output") { application->source = argv[2]; application->target = argv[3]; application->outputDirectory(); } else { print("purify v01\n"); print("usage: purify [mode] source target\n\n"); print("modes:\n"); print(" scan - create database\n"); print(" output - create folder images\n"); } delete application; return 0; }