/* * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG * Copyright (C) 2020 Rosalie Wanders * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "Rom.hpp" #include "Directories.hpp" #include "Error.hpp" #include "MediaLoader.hpp" #include "m64p/Api.hpp" #include "RomSettings.hpp" #include "Cheats.hpp" #include "osal/osal_files.hpp" #include #include #include #include #include // // Local Defines // #define UNZIP_READ_SIZE 67108860 /* 64 MiB */ // // Local Variables // static bool l_HasRomOpen = false; static bool l_HasDisk = false; static bool l_HasExtractedDisk = false; static std::filesystem::path l_ExtractedDiskPath; // // Local Functions // static std::string to_lower_str(std::string str) { std::string resultString = str; std::transform(resultString.begin(), resultString.end(), resultString.begin(), [](unsigned char c) { return std::tolower(c); } ); return resultString; } static voidpf zlib_filefunc_open(voidpf opaque, const void* filename, int mode) { std::filesystem::path path = *(std::filesystem::path*)filename; // we need a static filestream static std::ifstream fileStream; // attempt to open file fileStream.open(path, std::ios::binary); if (!fileStream.is_open()) { return nullptr; } return (voidpf)&fileStream; } static uLong zlib_filefunc_read(voidpf opaque, voidpf stream, void* buf, uLong size) { std::ifstream* fileStream = (std::ifstream*)stream; fileStream->read((char*)buf, size); return fileStream->fail() ? 0 : size; } static ZPOS64_T zlib_filefunc_tell(voidpf opaque, voidpf stream) { std::ifstream* fileStream = (std::ifstream*)stream; return fileStream->tellg(); } static long zlib_filefunc_seek(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) { std::ifstream* fileStream = (std::ifstream*)stream; std::ios_base::seekdir seekOrigin; switch (origin) { default: return 0; case ZLIB_FILEFUNC_SEEK_CUR: seekOrigin = std::ios::cur; break; case ZLIB_FILEFUNC_SEEK_END: seekOrigin = std::ios::end; break; case ZLIB_FILEFUNC_SEEK_SET: seekOrigin = std::ios::beg; break; } fileStream->seekg(offset, seekOrigin); return fileStream->fail() ? -1 : 0; } static int zlib_filefunc_close(voidpf opaque, voidpf stream) { std::ifstream* fileStream = (std::ifstream*)stream; fileStream->close(); return fileStream->fail() ? -1 : 0; } static int zlib_filefunc_testerror(voidpf opaque, voidpf stream) { return errno; } static bool read_zip_file(std::filesystem::path file, std::filesystem::path* extractedFileName, bool* isDisk, char** buf, int* size) { std::string error; std::fstream fileStream; int fileStreamLen; char* fileStreamBuf; unzFile zipFile; unz_global_info64 zipInfo; zlib_filefunc64_def filefuncs; filefuncs.zopen64_file = zlib_filefunc_open; filefuncs.zread_file = zlib_filefunc_read; filefuncs.zwrite_file = nullptr; filefuncs.ztell64_file = zlib_filefunc_tell; filefuncs.zseek64_file = zlib_filefunc_seek; filefuncs.zclose_file = zlib_filefunc_close; filefuncs.zerror_file = zlib_filefunc_testerror; filefuncs.opaque = nullptr; zipFile = unzOpen2_64((const void*)&file, &filefuncs); if (zipFile == nullptr) { if (errno == 0) { error = "read_zip_file: unzOpen Failed!"; } else { error = "read_zip_file: unzOpen Failed: "; error += "failed to open file: "; error += strerror(errno); error += " ("; error += std::to_string(errno); error += ")"; } CoreSetError(error); return false; } if (unzGetGlobalInfo64(zipFile, &zipInfo) != UNZ_OK) { error = "read_zip_file: unzGetGlobalInfo Failed!"; CoreSetError(error); return false; } for (int i = 0; i < zipInfo.number_entry; i++) { unz_file_info fileInfo; char fileName[PATH_MAX]; // if we can't retrieve file info, // skip the file if (unzGetCurrentFileInfo(zipFile, &fileInfo, fileName, PATH_MAX, nullptr, 0, nullptr, 0) != UNZ_OK) { continue; } // make sure file has supported file format, // if it does, read it in memory std::filesystem::path fileNamePath(fileName); std::string fileExtension = fileNamePath.has_extension() ? fileNamePath.extension().string() : ""; fileExtension = to_lower_str(fileExtension); if (fileExtension == ".z64" || fileExtension == ".v64" || fileExtension == ".n64" || fileExtension == ".ndd" || fileExtension == ".d64") { char* buffer; char* outBuffer; int dataSize = UNZIP_READ_SIZE; int total_bytes_read = 0; int bytes_read = 0; buffer = (char*)malloc(UNZIP_READ_SIZE); if (buffer == nullptr) { error = "read_zip_file Failed: malloc Failed!"; CoreSetError(error); return false; } outBuffer = (char*)malloc(UNZIP_READ_SIZE); if (outBuffer == nullptr) { free(buffer); error = "read_zip_file Failed: malloc Failed!"; CoreSetError(error); return false; } if (unzOpenCurrentFile(zipFile) != UNZ_OK) { free(buffer); free(outBuffer); error = "read_zip_file Failed: unzOpenCurrentFile Failed!"; CoreSetError(error); return false; } do { bytes_read = unzReadCurrentFile(zipFile, buffer, UNZIP_READ_SIZE); if (bytes_read < 0) { unzCloseCurrentFile(zipFile); unzClose(zipFile); free(buffer); free(outBuffer); error = "read_zip_file Failed: unzReadCurrentFile Failed: "; error += std::to_string(bytes_read); CoreSetError(error); return false; } if (bytes_read > 0) { if (total_bytes_read + bytes_read > dataSize) { outBuffer = (char*)realloc(outBuffer, total_bytes_read + bytes_read); dataSize += bytes_read; if (outBuffer == nullptr) { unzCloseCurrentFile(zipFile); unzClose(zipFile); free(buffer); free(outBuffer); error = "read_zip_file Failed: realloc Failed!"; CoreSetError(error); return false; } } memcpy((outBuffer + total_bytes_read), buffer, bytes_read); total_bytes_read += bytes_read; } } while (bytes_read > 0); *size = total_bytes_read; *buf = outBuffer; *extractedFileName = fileNamePath; *isDisk = (fileExtension == ".ndd" || fileExtension == ".d64"); unzCloseCurrentFile(zipFile); unzClose(zipFile); free(buffer); return true; } // break when we've iterated over all entries if ((i + 1) >= zipInfo.number_entry) { break; } // move to next file if (unzGoToNextFile(zipFile) != UNZ_OK) { unzClose(zipFile); error = "read_zip_file Failed: unzGoToNextFile Failed!"; CoreSetError(error); return false; } } error = "read_zip_file Failed: no valid ROMs found in zip!"; CoreSetError(error); unzClose(zipFile); return false; } static bool read_raw_file(std::filesystem::path file, char** buf, int* size) { std::string error; std::ifstream fileStream; int fileStreamLen; char* fileStreamBuf; // attempt to open file fileStream.open(file, std::ios::binary); if (!fileStream.is_open()) { error = "read_raw_file Failed: "; error += "failed to open file: "; error += strerror(errno); error += " ("; error += std::to_string(errno); error += ")"; CoreSetError(error); return false; } // attempt to retrieve file length fileStream.seekg(0, fileStream.end); fileStreamLen = fileStream.tellg(); fileStream.seekg(0, fileStream.beg); // try to create buffer fileStreamBuf = (char*)malloc(fileStreamLen); if (fileStreamBuf == nullptr) { error = "read_raw_file Failed: "; error += "malloc failed!"; CoreSetError(error); free(fileStreamBuf); fileStream.close(); return false; } // read file fileStream.read(fileStreamBuf, fileStreamLen); *buf = fileStreamBuf; *size = fileStreamLen; fileStream.close(); return true; } static bool write_file(std::filesystem::path file, char* buf, int size) { std::string error; std::ofstream fileStream; // attempt to open file fileStream.open(file, std::ios::binary); if (!fileStream.is_open()) { error = "write_file Failed: "; error += "failed to open file: "; error += strerror(errno); error += " ("; error += std::to_string(errno); error += ")"; CoreSetError(error); return false; } // write buffer to file fileStream.write(buf, size); fileStream.close(); return true; } // // Exported Functions // bool CoreOpenRom(std::filesystem::path file) { std::string error; m64p_error ret; char* buf = nullptr; int buf_size = 0; std::string file_extension; if (!m64p::Core.IsHooked()) { return false; } if (CoreHasRomOpen()) { error = "CoreOpenRom Failed: "; error += "cannot open ROM when another ROM is already open!"; CoreSetError(error); return false; } file_extension = file.has_extension() ? file.extension().string() : ""; file_extension = to_lower_str(file_extension); if (file_extension == ".zip") { std::filesystem::path extracted_file; bool is_disk = false; if (!read_zip_file(file, &extracted_file, &is_disk, &buf, &buf_size)) { return false; } if (is_disk) { std::filesystem::path disk_file; disk_file = CoreGetUserCacheDirectory(); disk_file += "/extracted_disks/"; disk_file += extracted_file.filename(); // attempt to create extraction directory try { if (!std::filesystem::exists(disk_file.parent_path())) { if (!std::filesystem::create_directory(disk_file.parent_path())) { throw std::exception(); } } } catch (...) { error = "CoreOpenRom Failed: "; error += "Failed to create \""; error += disk_file.parent_path().string(); error += "\"!"; CoreSetError(error); return false; } // attempt to write temporary file if (!write_file(disk_file, buf, buf_size)) { return false; } CoreMediaLoaderSetDiskFile(disk_file); l_ExtractedDiskPath = disk_file; } l_HasDisk = is_disk; l_HasExtractedDisk = is_disk; } else if (file_extension == ".d64" || file_extension == ".ndd") { CoreMediaLoaderSetDiskFile(file); l_HasDisk = true; l_HasExtractedDisk = false; } else { if (!read_raw_file(file, &buf, &buf_size)) { return false; } l_HasDisk = false; l_HasExtractedDisk = false; } if (l_HasDisk) { ret = m64p::Core.DoCommand(M64CMD_DISK_OPEN, 0, nullptr); error = "CoreOpenRom: m64p::Core.DoCommand(M64CMD_DISK_OPEN) Failed: "; } else { ret = m64p::Core.DoCommand(M64CMD_ROM_OPEN, buf_size, buf); error = "CoreOpenRom: m64p::Core.DoCommand(M64CMD_ROM_OPEN) Failed: "; free(buf); } if (ret != M64ERR_SUCCESS) { error += m64p::Core.ErrorMessage(ret); CoreSetError(error); } l_HasRomOpen = (ret == M64ERR_SUCCESS); if (l_HasRomOpen) { // store default ROM settings CoreStoreCurrentDefaultRomSettings(); // apply rom settings overlay CoreApplyRomSettingsOverlay(); } return l_HasRomOpen; } bool CoreHasRomOpen(void) { return l_HasRomOpen; } bool CoreGetRomType(CoreRomType& type) { std::string error; if (!l_HasRomOpen) { error = "CoreGetRomType Failed: "; error += "cannot retrieve ROM type when no ROM has been opened!"; CoreSetError(error); return false; } type = l_HasDisk ? CoreRomType::Disk : CoreRomType::Cartridge; return true; } bool CoreCloseRom(void) { std::string error; m64p_error ret; if (!m64p::Core.IsHooked()) { return false; } if (!CoreHasRomOpen()) { error = "CoreCloseRom Failed: "; error += "cannot close rom when no rom is open!"; CoreSetError(error); return false; } if (!CoreClearCheats()) { return false; } if (l_HasDisk) { ret = m64p::Core.DoCommand(M64CMD_DISK_CLOSE, 0, nullptr); error = "CoreCloseRom: m64p::Core.DoCommand(M64CMD_DISK_CLOSE) Failed: "; } else { ret = m64p::Core.DoCommand(M64CMD_ROM_CLOSE, 0, nullptr); error = "CoreCloseRom: m64p::Core.DoCommand(M64CMD_ROM_CLOSE) Failed: "; } if (ret != M64ERR_SUCCESS) { error += m64p::Core.ErrorMessage(ret); CoreSetError(error); return false; } // clear default ROM settings CoreClearCurrentDefaultRomSettings(); // if removing the temporary extracted disk file // fails, then we shouldn't break the state of // whether we currently have an open ROM/disk, // so we can safely say the ROM/disk isn't open // anymore here l_HasRomOpen = false; // attempt to clean temporary extracted disk file if (l_HasExtractedDisk) { try { if (!std::filesystem::remove(l_ExtractedDiskPath)) { throw std::exception(); } } catch (...) { error = "CoreCloseRom: Failed to remove \""; error += l_ExtractedDiskPath.string(); error += "\"!"; CoreSetError(error); return false; } } return true; }