diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a5c956b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "OPL Game Installer/libcurl"] + path = OPL Game Installer/libcurl + url = https://github.com/curl/curl +[submodule "OPL Game Installer/lib9660"] + path = OPL Game Installer/lib9660 + url = https://github.com/erincandescent/lib9660 diff --git a/OPL Game Installer.sln b/OPL Game Installer.sln new file mode 100644 index 0000000..f187955 --- /dev/null +++ b/OPL Game Installer.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33815.320 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OPL Game Installer", "OPL Game Installer\OPL Game Installer.vcxproj", "{1157B2FE-476C-425F-98BB-4712E3D3E0AC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1157B2FE-476C-425F-98BB-4712E3D3E0AC}.Debug|x64.ActiveCfg = Debug|x64 + {1157B2FE-476C-425F-98BB-4712E3D3E0AC}.Debug|x64.Build.0 = Debug|x64 + {1157B2FE-476C-425F-98BB-4712E3D3E0AC}.Debug|x86.ActiveCfg = Debug|Win32 + {1157B2FE-476C-425F-98BB-4712E3D3E0AC}.Debug|x86.Build.0 = Debug|Win32 + {1157B2FE-476C-425F-98BB-4712E3D3E0AC}.Release|x64.ActiveCfg = Release|x64 + {1157B2FE-476C-425F-98BB-4712E3D3E0AC}.Release|x64.Build.0 = Release|x64 + {1157B2FE-476C-425F-98BB-4712E3D3E0AC}.Release|x86.ActiveCfg = Release|Win32 + {1157B2FE-476C-425F-98BB-4712E3D3E0AC}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7A169FAA-3D76-46AE-A474-9946DBB52021} + EndGlobalSection +EndGlobal diff --git a/OPL Game Installer/OPL Game Installer.aps b/OPL Game Installer/OPL Game Installer.aps new file mode 100644 index 0000000..6a65a86 Binary files /dev/null and b/OPL Game Installer/OPL Game Installer.aps differ diff --git a/OPL Game Installer/OPL Game Installer.rc b/OPL Game Installer/OPL Game Installer.rc new file mode 100644 index 0000000..f46204a Binary files /dev/null and b/OPL Game Installer/OPL Game Installer.rc differ diff --git a/OPL Game Installer/OPL Game Installer.vcxproj b/OPL Game Installer/OPL Game Installer.vcxproj new file mode 100644 index 0000000..a3c06e2 --- /dev/null +++ b/OPL Game Installer/OPL Game Installer.vcxproj @@ -0,0 +1,166 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + 16.0 + Win32Proj + {1157b2fe-476c-425f-98bb-4712e3d3e0ac} + FAT32Writer + 10.0 + + + + Application + true + v143 + MultiByte + + + Application + false + v143 + true + MultiByte + + + Application + true + v143 + MultiByte + + + Application + false + v143 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + libcurl/debug64;$(LibraryPath) + + + libcurl/release64;$(LibraryPath) + + + libcurl/release32;$(LibraryPath) + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;CURL_STATICLIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + _CRT_SECURE_NO_WARNINGS;CURL_STATICLIB;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + libcurl/include;%(AdditionalIncludeDirectories) + + + Console + true + true + true + libcurl_a.lib;%(AdditionalDependencies) + + + + + Level3 + true + _DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;CURL_STATICLIB;%(PreprocessorDefinitions) + true + libcurl/include;%(AdditionalIncludeDirectories) + + + Console + true + libcurl_a.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + _CRT_SECURE_NO_WARNINGS;CURL_STATICLIB;%(PreprocessorDefinitions) + true + libcurl/include;%(AdditionalIncludeDirectories) + + + Console + true + true + true + libcurl_a.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/OPL Game Installer/OPL Game Installer.vcxproj.filters b/OPL Game Installer/OPL Game Installer.vcxproj.filters new file mode 100644 index 0000000..3bbe420 --- /dev/null +++ b/OPL Game Installer/OPL Game Installer.vcxproj.filters @@ -0,0 +1,58 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Header Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/OPL Game Installer/OPL Game Installer.vcxproj.user b/OPL Game Installer/OPL Game Installer.vcxproj.user new file mode 100644 index 0000000..dc63f8a --- /dev/null +++ b/OPL Game Installer/OPL Game Installer.vcxproj.user @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file diff --git a/OPL Game Installer/isoparser.cpp b/OPL Game Installer/isoparser.cpp new file mode 100644 index 0000000..86660cf --- /dev/null +++ b/OPL Game Installer/isoparser.cpp @@ -0,0 +1,104 @@ +#include "isoparser.h" +#include "utilities.h" + +static FILE* isof; + +static bool readsect(l9660_fs* fs, void* buf, uint32_t sector) +{ + if (_fseeki64(isof, 2048 * sector, SEEK_SET)) { + fprintf(stderr, "Reading sector %u\n", sector); + perror("fseek"); + return false; + } + + if (fread(buf, 2048, 1, isof) != 1) { + fprintf(stderr, "Reading sector %u\n", sector); + perror("fread"); + return false; + } + + return true; +} + +char* IsoParser::readIsoFile(const char* path, const char* insidePath) { + isof = fopen(path, "rb"); + if (!isof) { + printf("[-] Failed to open '%s' path.\n", path); + return nullptr; + } + + l9660_fs fs; + l9660_dir dir; + l9660_openfs(&fs, readsect); + + l9660_fs_open_root(&dir, &fs); + + l9660_file file; + l9660_openat(&file, &dir, insidePath); + + size_t totalRead = 0; + + char* content = nullptr; + + while (true) { + char buf[128]; + size_t bytesRead; + l9660_read(&file, buf, 128, &bytesRead); + + if (bytesRead == 0) { + break; + } + + content = new char[bytesRead]; + std::memcpy(content, buf, bytesRead); + totalRead += bytesRead; + } + + if (totalRead > 0) { + content[totalRead] = '\0'; + } + + fclose(isof); + + return content; +} + +char* IsoParser::parseConfig(const char* conf, const char* option) { + std::string strBuffer(conf); + + size_t found = strBuffer.find(option); + if (found == std::string::npos) { + return nullptr; + } + + std::string afterOption = strBuffer.substr(found); + + size_t endOfLine = afterOption.find_first_of("\n"); + if (endOfLine == std::string::npos) { + return nullptr; + } + + std::string removeLines = strBuffer.substr(found, afterOption.find_first_of("\n") - 1); + std::string afterProp = removeLines.substr(strlen(option) + 3); + + return stringToCharp(afterProp); +} + +char* IsoParser::getGameID(const char* isoPath) { + + char* config = IsoParser::readIsoFile(isoPath, "SYSTEM.CNF"); + if (!config) { + return nullptr; + } + + std::string boot = IsoParser::parseConfig(config, "BOOT2"); + size_t p = boot.find_first_of("\\"); + if (p == std::string::npos) { + return nullptr; + } + + std::string boot2 = boot.substr(p + 1); + std::string gameId = boot2.substr(0, boot2.length() - 2); + + return stringToCharp(gameId); +} \ No newline at end of file diff --git a/OPL Game Installer/isoparser.h b/OPL Game Installer/isoparser.h new file mode 100644 index 0000000..c40c3d3 --- /dev/null +++ b/OPL Game Installer/isoparser.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +extern "C" { +#include "lib9660/lib9660.h" +} + +namespace IsoParser { + char* readIsoFile(const char* path, const char* insidePath); + char* parseConfig(const char* conf, const char* option); + char* getGameID(const char* isoPath); +}; \ No newline at end of file diff --git a/OPL Game Installer/lib9660 b/OPL Game Installer/lib9660 new file mode 160000 index 0000000..00d0277 --- /dev/null +++ b/OPL Game Installer/lib9660 @@ -0,0 +1 @@ +Subproject commit 00d027749ab556208c2aedebe793592a70ad30d0 diff --git a/OPL Game Installer/libcurl b/OPL Game Installer/libcurl new file mode 160000 index 0000000..ca6bafc --- /dev/null +++ b/OPL Game Installer/libcurl @@ -0,0 +1 @@ +Subproject commit ca6bafce95365fadd906a533bb16e47b638c5c2a diff --git a/OPL Game Installer/main.cpp b/OPL Game Installer/main.cpp new file mode 100644 index 0000000..f19beaa --- /dev/null +++ b/OPL Game Installer/main.cpp @@ -0,0 +1,408 @@ +#include +#include + +#include "isoparser.h" +#include "opl.h" +#include "utilities.h" + +#ifdef _WIN32 +#include +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#endif +#define WAIT(ms) Sleep(ms) +#define MKDIR(dir) CreateDirectoryA(dir, NULL) +#define SETUP_CONSOLE() \ + do { \ + DWORD outMode = 0; \ + HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); \ + \ + if (stdoutHandle == INVALID_HANDLE_VALUE || !GetConsoleMode(stdoutHandle, &outMode)) { \ + exit(GetLastError()); \ + } \ + \ + DWORD outModeInit = outMode; \ + outMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; \ + \ + if (!SetConsoleMode(stdoutHandle, outMode)) { \ + exit(GetLastError()); \ + } \ + } while(0) +#else +#include +#define WAIT(ms) usleep(ms * 1000) +#define MKDIR(dir) mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0 +#define SETUP_CONSOLE() +#endif +#define PAUSE() printf("\n\nPress any key to continue . . ."); (void)getchar(); +#define CHECK_YES_NO_INPUT(type) (__strcmpi(type, "y") == 0 || __strcmpi(type, "yes") == 0 ? 1 : (__strcmpi(type, "n") == 0 || __strcmpi(type, "no") == 0 ? 0 : -1)) +#define CLEAR() printf("\033[H\033[2J\033[3J") +#define CREATE_ASCII() printf("\x1B[31m%s\n\033[0m\t\t\n", R"( + ____ __ _ __ ____ + / __ \___ ____ ___ _____ _____/ /| |/ / / __ \___ _ __ + / /_/ / _ \/ __ `/ / / / _ \/ ___/ __| / / / / / _ | | / / + / _, _/ __/ /_/ / /_/ / __(__ / /_/ | / /_/ / __| |/ _ + /_/ |_|\___/\__, /\__,_/\___/____/\__/_/|_| /_____/\___/|___(_))") +#define CREATE_ERROR(msg, pause) CLEAR(); CREATE_ASCII(); printf(LIGHT_RED"%s" RESET, msg); if(pause) {PAUSE();} +#define SET_TITLE(title) printf("\033]0;%s\007", title); +#define RESET "\x1B[0m" +#define HIDE_CURSOR "\033[?25l" +#define SHOW_CURSOR "\033[?25h" +#define BLACK "\x1B[30m" +#define RED "\x1B[31m" +#define GREEN "\x1B[32m" +#define YELLOW "\x1B[33m" +#define BLUE "\x1B[34m" +#define MAGENTA "\x1B[35m" +#define CYAN "\x1B[36m" +#define WHITE "\x1B[37m" +#define LIGHT_BLACK "\x1B[90m" +#define LIGHT_RED "\x1B[91m" +#define LIGHT_GREEN "\x1B[92m" +#define LIGHT_YELLOW "\x1B[93m" +#define LIGHT_BLUE "\x1B[94m" +#define LIGHT_MAGENTA "\x1B[95m" +#define LIGHT_CYAN "\x1B[96m" +#define LIGHT_WHITE "\x1B[97m" +#define BG_BLACK "\x1B[40m" +#define BG_RED "\x1B[41m" +#define BG_GREEN "\x1B[42m" +#define BG_YELLOW "\x1B[43m" +#define BG_BLUE "\x1B[44m" +#define BG_MAGENTA "\x1B[45m" +#define BG_CYAN "\x1B[46m" +#define BG_WHITE "\x1B[47m" +#define BG_LIGHT_BLACK "\x1B[100m" +#define BG_LIGHT_RED "\x1B[101m" +#define BG_LIGHT_GREEN "\x1B[102m" +#define BG_LIGHT_YELLOW "\x1B[103m" +#define BG_LIGHT_BLUE "\x1B[104m" +#define BG_LIGHT_MAGENTA "\x1B[105m" +#define BG_LIGHT_CYAN "\x1B[106m" +#define BG_LIGHT_WHITE "\x1B[107m" + +const std::streamsize chunkSize = 1LL * (1024 * 1024 * 1024); // (byte) equals 1gb > number of bytes to seperate files +const std::streamsize readSize = 4LL * (1024); // (byte) equals 4096b > total number of bytes to read each +const std::streamsize maxSize = 4LL * (1024 * 1024 * 1024); // (byte) equals 4gb > fat32 supported max size + +int main(int argc, char* argv[]) { + SETUP_CONSOLE(); + CREATE_ASCII(); + SET_TITLE("OPL Game Installer - github.com/kruz1337"); + + std::string str_isoPath, str_copyFile, str_mediaType, str_outputDir, str_addCover, str_createVmc, str_sizeOfVmc; + + { + if (argc > 1) + { + if (!argv[1] || !argv[2] || !argv[3]) { + CREATE_ERROR("[-] Invalid argument type.", 0); + return 1; + } + + str_isoPath = argv[1]; + str_outputDir = argv[2]; + str_mediaType = argv[3]; + str_copyFile = findFromCharArray(argv, "--copy", argc) == -1 ? "n" : "y"; + str_addCover = findFromCharArray(argv, "--add-cover", argc) == -1 ? "n" : "y"; + + int vmcArgPos = findFromCharArray(argv, "--create-vmc", argc); + if (vmcArgPos == -1) { + str_createVmc = "n"; + } + else { + str_createVmc = "y"; + str_sizeOfVmc = argv[vmcArgPos + 1]; + } + } + } + + if (argc <= 1) { + printf("[?] Iso file path: \n> "); + std::getline(std::cin, str_isoPath); + } + + const char* isoPath = str_isoPath.c_str(); + std::ifstream input(isoPath, std::ios::binary); + if (!input.is_open()) { + CREATE_ERROR("[-] Failed to open input file.", 1); + return 1; + } + + char* gameId = IsoParser::getGameID(isoPath); + if (!gameId) + { + CREATE_ERROR("[-] Invalid iso file.", 1); + return 1; + } + + input.seekg(0, input.end); + std::streamsize isoSize = input.tellg(); + input.seekg(0, input.beg); + + int copyFile = 0; + + if (isoSize <= maxSize) { + if (argc <= 1) { + printf(YELLOW"\n[*] The file can be copied directly because the file size does not exceed the limit.\n" RESET); + printf("[?] Do you want the file copied? (y/N) [Default: y]\n> "); + std::getline(std::cin, str_copyFile); + } + + copyFile = str_copyFile == "" ? 1 : CHECK_YES_NO_INPUT(str_copyFile.c_str()); + if (copyFile == -1) { + CREATE_ERROR("[-] Invalid result type.", 1); + return 1; + } + } + + if (argc <= 1) { + printf("\n[?] Media type: (CD/DVD) [Default: DVD]\n> "); + std::getline(std::cin, str_mediaType); + } + + str_mediaType = str_mediaType == "" ? "DVD" : toUpperCase(str_mediaType); + + const char* mediaType = str_mediaType.c_str(); + if (strcmp(mediaType, "DVD") != 0 && strcmp(mediaType, "CD") != 0) { + CREATE_ERROR("[-] Invalid result type.", 1); + return 1; + } + + if (argc <= 1) { + printf("\n[?] OPL Root path: \n> "); + std::getline(std::cin, str_outputDir); + } + + const char* outputDir = str_outputDir.c_str(); + if (!isDirectory(outputDir)) { + CREATE_ERROR("[-] Failed to open root dir.", 1); + return 1; + } + + if (argc <= 1) { + printf(YELLOW "\n[*] This process need to internet connection." RESET); + printf("\n[?] Do you want to add cover image? (y/N) [Default: n]\n> "); + std::getline(std::cin, str_addCover); + } + + int addCover = str_addCover == "" ? 0 : CHECK_YES_NO_INPUT(str_addCover.c_str()); + if (addCover == -1) { + CREATE_ERROR("[-] Invalid result type.", 1); + return 1; + } + + if (argc <= 1) { + printf(YELLOW "\n[*] It doesn't delete existing memory cards, but replaces the first slot with a newly created memory card." RESET); + printf("\n[?] Do you want to create vmc for this game? (y/N) [Default: n]\n> "); + std::getline(std::cin, str_createVmc); + } + + int sizeOfVmc = 8; + int createVmc = str_createVmc == "" ? 0 : CHECK_YES_NO_INPUT(str_createVmc.c_str()); + if (createVmc == -1) { + CREATE_ERROR("[-] Invalid result type.", 1); + return 1; + } + else if (createVmc == 1) { + if (argc <= 1) { + printf(YELLOW "\n[*] Memory card size should be minimum 1 mb and maximum 1024 mb." RESET); + printf("\n[?] How much should be megabyte for memory card? [Default: 8]\n> "); + std::getline(std::cin, str_sizeOfVmc); + } + + if (str_sizeOfVmc != "") { + try { + sizeOfVmc = std::stoi(str_sizeOfVmc); + } + catch (...) { + CREATE_ERROR("[-] Invalid memory card size.", 1); + return 1; + } + } + + if ((sizeOfVmc < 1) || (sizeOfVmc > 1024)) { + CREATE_ERROR("[-] Invalid memory card size.", 1); + return 1; + } + } + + char* fileName = getFilenameByPath(isoPath, true); + char* gameName = getFilenameByPath(isoPath); + char* crcHex = decimalToHex(OPL::crc32Hex(gameName), 8); + + const char* newIsoDir = locationFix(std::string(outputDir) + "\\" + mediaType); + const char* newIsoPath = locationFix(std::string(newIsoDir) + "\\" + fileName); + const char* vmcName = locationFix(replaceStr(gameId, ".", "")); + const char* vmcDir = locationFix(std::string(outputDir) + "\\VMC"); + const char* vmcPath = locationFix((std::string(vmcDir) + "\\" + vmcName + ".bin")); + const char* cfgDir = locationFix(std::string(outputDir) + "\\CFG"); + const char* cfgPath = locationFix(std::string(cfgDir) + "\\" + gameId + ".cfg"); + const char* artDir = locationFix(std::string(outputDir) + "\\ART"); + + CLEAR(); + CREATE_ASCII(); + printf("[*] Process started.\n[*] Game title is \"%s\", copying to \"%s\" directory.\n" YELLOW"[*] Switch off the \"Check USB game fragmentation\" option in OPL settings, otherwise it will not run fragmented games.\n[*] Do not eject the disc during the process and do not interfere with the files in use.\n\n" RESET, gameName, newIsoDir); + + { + auto start = std::chrono::high_resolution_clock::now(); + std::streamsize totalReadByte = 0; + + bool writeJob_IsCompleted = false; + + auto writeJob = [&]() { + std::ofstream output; + char buffer[readSize]; + + if (copyFile == 0) { + int chunkCount = 0; + + while (!input.eof()) { + static std::streamsize partRead = chunkSize; + input.read(buffer, partRead + readSize > chunkSize ? abs(chunkSize - partRead) : readSize); + + if (partRead >= chunkSize) { + partRead = 0; + + std::string path = std::string(outputDir) + "\\ul." + crcHex + "." + gameId + "." + padStart(std::to_string(chunkCount), 2, '0'); + + output.close(); + output.open(path, std::ios::binary); + + if (!output.is_open()) { + writeJob_IsCompleted = true; + input.close(); + printf(LIGHT_RED "\n\n[-] Failed to open part path." RESET "\n[!] Process finished with errors."); + PAUSE(); + exit(1); + } + + chunkCount++; + } + + std::streamsize bytesRead = input.gcount(); + output.write(buffer, bytesRead); + totalReadByte += bytesRead; + partRead += bytesRead; + } + + int ulResult = OPL::writeUl(outputDir, gameName, gameId, mediaType, chunkCount); + if (ulResult < 0) { + writeJob_IsCompleted = true; + output.close(); + input.close(); + printf(LIGHT_RED "\n\n[-] Failed to create defragged game." RESET "\n[!] Process finished with errors."); + PAUSE(); + exit(1); + } + } + else if (copyFile == 1) { + if (!isDirectory(newIsoDir) && !MKDIR(newIsoDir)) { + writeJob_IsCompleted = true; + input.close(); + printf(LIGHT_RED "\n\n[-] Failed to create media directory." RESET "\n[!] Process finished with errors."); + PAUSE(); + exit(1); + } + + output.open(newIsoPath, std::ios::binary); + if (!output.is_open()) { + writeJob_IsCompleted = true; + input.close(); + printf(LIGHT_RED "\n\n[-] Failed to open part path." RESET "\n[!] Process finished with errors."); + PAUSE(); + exit(1); + } + + while (!input.eof()) { + input.read(buffer, readSize); + + std::streamsize bytesRead = input.gcount(); + output.write(buffer, bytesRead); + totalReadByte += bytesRead; + } + } + + input.close(); + output.close(); + writeJob_IsCompleted = true; + }; + + std::thread writeJob_Thread(writeJob); + + while (!writeJob_IsCompleted) { + double percentage = (double)totalReadByte / isoSize * 100; + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + + const char chars[] = { '\\', '|', '/', '-' }; + static int index = 0; + index = (index + 1) % 4; + + printf(LIGHT_BLACK HIDE_CURSOR "%c %llu bytes transferred in %llu ms from total of %llu bytes (%%%i completed) %c" RESET"\t\r", chars[index], totalReadByte, duration.count(), isoSize, (int)round(percentage), chars[index]); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + writeJob_Thread.join(); + + printf(SHOW_CURSOR RESET "\n"); + } + + if (createVmc == 1) { + if (!isDirectory(vmcDir) && !MKDIR(vmcDir)) { + printf(LIGHT_RED "\n[-] Failed to create virtual memory directory." RESET "\n[!] Process finished with errors."); + PAUSE(); + return 1; + } + + int vmcResult = OPL::createVmc(vmcPath, sizeOfVmc * 1024, 16); + if (vmcResult < 0) { + printf(LIGHT_RED "\n[-] Failed to create virtual memory card." RESET "\n[!] Process finished with errors."); + PAUSE(); + return 1; + } + + if (!isDirectory(cfgDir) && !MKDIR(cfgDir)) { + printf(LIGHT_RED "\n[-] Failed to create config directory." RESET "\n[!] Process finished with errors."); + PAUSE(); + return 1; + } + + bool cfgResult = OPL::writeCfg(cfgPath, "$VMC_0", vmcName); + if (!cfgResult) { + printf(LIGHT_RED "\n[-] Failed to set memory card slot." RESET "\n[!] Process finished with errors."); + PAUSE(); + return 1; + } + + printf(YELLOW"\n[*] Virtual memory card created and selected." RESET); + } + + if (addCover == 1) { + if (!isDirectory(artDir) && !MKDIR(artDir)) { + printf(LIGHT_RED "\n[-] Failed to create art directory." RESET "\n[!] Process finished with errors."); + PAUSE(); + return 1; + } + + int fetchResult = OPL::downloadArts(artDir, gameId); + if (fetchResult == -1) { + printf(LIGHT_RED "\n[-] Failed to write image data." RESET "\n[!] Process finished with errors."); + PAUSE(); + return 1; + } + else if (fetchResult == -2) { + printf(LIGHT_RED "\n[-] There is no game image on the server." RESET "\n[!] Process finished with errors."); + PAUSE(); + return 1; + } + + printf(YELLOW"\n[*] Game cover art installed." RESET); + + } + + printf("\n[+] Process succesfully finished."); + PAUSE(); + + return 0; +} \ No newline at end of file diff --git a/OPL Game Installer/opl.cpp b/OPL Game Installer/opl.cpp new file mode 100644 index 0000000..d7e4745 --- /dev/null +++ b/OPL Game Installer/opl.cpp @@ -0,0 +1,523 @@ +#include "opl.h" +#include "utilities.h" + +#include +#pragma comment(lib, "Normaliz.lib") +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "Wldap32.lib") +#pragma comment(lib, "ws2_32.lib") + +size_t curl_write_data_callback(void* contents, size_t size, size_t nmemb, std::vector* data) { + size_t total_size = size * nmemb; + data->insert(data->end(), static_cast(contents), static_cast(contents) + total_size); + return total_size; +} + +bool OPL::fetchImage(const char* url, std::vector& buffer) { + CURL* curl = curl_easy_init(); + if (!curl) { + return false; + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_data_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) { + curl_easy_cleanup(curl); + return false; + } + + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + if (http_code != 200) { + curl_easy_cleanup(curl); + return false; + } + + curl_easy_cleanup(curl); + + return true; +} + +int OPL::downloadArts(const char* dir, const char* gameId) { + std::vector buffer; + + int fetched = 0; + + const char* gameArts[] = { "LGO.png", "LAB.jpg", "ICO.png", "COV.jpg", "COV.png", "COV2.jpg", "SCR_00.jpg", "SCR_01.jpg", "BG_00.jpg" }; + for (auto& art : gameArts) { + buffer.clear(); + + std::string erasedName = std::string(gameId) + "_" + replaceMultiStr(art, { "_00", "_01"}, {"", "2"}); + std::string url("https://requestx.dev/ARTS/" + std::string(gameId) + "/" + std::string(gameId) + "_" + art); + + bool result = OPL::fetchImage(url.c_str(), buffer); + if (!result) { + continue; + } + + std::ofstream img(std::string(dir) + "\\" + erasedName, std::ios::binary); + if (!img) { + return -1; + } + + img.write(reinterpret_cast(&buffer[0]), buffer.size()); + img.close(); + + fetched++; + } + + return fetched > 0 ? 0 : -2; +} + +bool OPL::writeCfg(const char* path, const char* key, const char* value) { + std::ifstream input(path); + std::ofstream output(path); + + std::string cfg = ((std::string)key + '=' + value); + + if (input) { + bool f = false; + std::string line; + + while (input >> line) + { + if (line.find(key + std::string("=")) == 0) { + line = cfg; + f = true; + } + + line += "\n"; + output << line; + } + + if (!f) { + output << cfg; + } + + return true; + } + else if (output) { + output << cfg; + } + else { + return false; + } + + input.close(); + output.close(); + + return true; +} + +//These following functions are taked from https://github.com/ps2homebrew/Open-PS2-Loader/ +int OPL::writeUl(const char* drive, const char* game_name, const char* game_id, const char* media, int parts) +{ + typedef struct + { + char name[32]; + char image[15]; + unsigned char parts; + unsigned char media; + unsigned char pad[15]; + } cfg_t; + + FILE* fh_cfg; + cfg_t cfg; + char cfg_path[256]; + int r; + +#ifdef _WIN32 + if (strlen(drive) == 1) + sprintf(cfg_path, "%s:\\ul.cfg", drive); + else + sprintf(cfg_path, "%s\\ul.cfg", drive); +#else + sprintf(cfg_path, "%s/ul.cfg", drive); +#endif + memset(&cfg, 0, sizeof(cfg_t)); + + strncpy(cfg.name, game_name, 32); + sprintf(cfg.image, "ul.%s", game_id); + cfg.parts = parts; + cfg.pad[4] = 0x08; + + if (!strcmp(media, "CD")) + cfg.media = 0x12; + else if (!strcmp(media, "DVD")) + cfg.media = 0x14; + + fh_cfg = fopen(cfg_path, "ab"); + if (!fh_cfg) + return -1; + + r = fwrite(&cfg, 1, sizeof(cfg_t), fh_cfg); + if (r != sizeof(cfg_t)) { + fclose(fh_cfg); + return -2; + } + + fclose(fh_cfg); + + return 0; +} + +int OPL::crc32Hex(const char* string) +{ + int crctab[0x400]; + int crc, table, count, byte; + + for (table = 0; table < 256; table++) { + crc = table << 24; + + for (count = 8; count > 0; count--) { + if (crc < 0) + crc = crc << 1; + else + crc = (crc << 1) ^ 0x04C11DB7; + } + crctab[255 - table] = crc; + } + + do { + byte = string[count++]; + crc = crctab[byte ^ ((crc >> 24) & 0xFF)] ^ ((crc << 8) & 0xFFFFFF00); + } while (string[count - 1] != 0); + + return crc; +} + +typedef struct +{ + unsigned char magic[28]; + unsigned char version[12]; + unsigned short pagesize; + unsigned short pages_per_cluster; + unsigned short blocksize; + unsigned short unused; + unsigned int clusters_per_card; + unsigned int alloc_offset; + unsigned int alloc_end; + unsigned int rootdir_cluster; + unsigned int backup_block1; + unsigned int backup_block2; + unsigned char unused2[8]; + unsigned int ifc_list[32]; + int bad_block_list[32]; + unsigned char cardtype; + unsigned char cardflags; + unsigned short unused3; + unsigned int cluster_size; + unsigned int FATentries_per_cluster; + unsigned int clusters_per_block; + int cardform; + unsigned int rootdir_cluster2; + unsigned int unknown1; + unsigned int unknown2; + unsigned int max_allocatable_clusters; + unsigned int unknown3; + unsigned int unknown4; + int unknown5; +} MCDevInfo; + +static MCDevInfo devinfo; + +typedef struct _sceMcStDateTime +{ + unsigned char Resv2; + unsigned char Sec; + unsigned char Min; + unsigned char Hour; + unsigned char Day; + unsigned char Month; + unsigned short Year; +} sceMcStDateTime; + +static void long_multiply(unsigned int v1, unsigned int v2, unsigned int* HI, unsigned int* LO) +{ + register long a, b, c, d; + register long x, y; + + a = (v1 >> 16) & 0xffff; + b = v1 & 0xffff; + c = (v2 >> 16) & 0xffff; + d = v2 & 0xffff; + + *LO = b * d; + x = a * d + c * b; + y = ((*LO >> 16) & 0xffff) + x; + + *LO = (*LO & 0xffff) | ((y & 0xffff) << 16); + *HI = (y >> 16) & 0xffff; + + *HI += a * c; +} + +static int mc_getmcrtime(sceMcStDateTime* mctime) +{ + time_t rawtime; + struct tm* ptm; + + time(&rawtime); + ptm = gmtime(&rawtime); + + mctime->Resv2 = 0; + mctime->Sec = ((((ptm->tm_sec >> 4) << 2) + (ptm->tm_sec >> 4)) << 1) + (ptm->tm_sec & 0xf); + mctime->Min = ((((ptm->tm_min >> 4) << 2) + (ptm->tm_min >> 4)) << 1) + (ptm->tm_min & 0xf); + mctime->Hour = ((((ptm->tm_hour >> 4) << 2) + (ptm->tm_hour >> 4)) << 1) + (ptm->tm_hour & 0xf); + mctime->Day = ((((ptm->tm_mday >> 4) << 2) + (ptm->tm_mday >> 4)) << 1) + (ptm->tm_mday & 0xf); + + mctime->Month = (ptm->tm_mon + 1) & 0xf; + + mctime->Year = (((((ptm->tm_year - 100) >> 4) << 2) + ((ptm->tm_year - 100) >> 4)) << 1) + (((ptm->tm_year - 100) & 0xf) | 0x7d0); + + return 0; +} + +static int mc_writecluster(FILE* fd, int cluster, void* buf, int dup) +{ + register int r, size; + MCDevInfo* mcdi = (MCDevInfo*)&devinfo; + + fseek(fd, cluster * mcdi->cluster_size, SEEK_SET); + size = mcdi->cluster_size * dup; + r = fwrite(buf, 1, size, fd); + if (r != size) + return -1; + + return 0; +} + +int OPL::createVmc(const char* filename, int size_kb, int blocksize) { + typedef struct + { + unsigned short mode; + unsigned short unused; + unsigned int length; + sceMcStDateTime created; + unsigned int cluster; + unsigned int dir_entry; + sceMcStDateTime modified; + unsigned int attr; + unsigned int unused2[7]; + char name[32]; + unsigned char unused3[416]; + } McFsEntry; + + static char SUPERBLOCK_MAGIC[] = "Sony PS2 Memory Card Format "; + static char SUPERBLOCK_VERSION[] = "1.2.0.0"; + static unsigned char cluster_buf[(16 * 1024) + 16]; + static FILE* genvmc_fh = NULL; + + register int i, r, b, ifc_index, fat_index; + register int ifc_length, fat_length, alloc_offset; + register int ret, j = 0, z = 0; + MCDevInfo* mcdi = (MCDevInfo*)&devinfo; + + genvmc_fh = fopen(filename, "wb"); + if (genvmc_fh == NULL) + return -101; + + memset((void*)&mcdi->magic, 0, sizeof(mcdi->magic) + sizeof(mcdi->version)); + strcpy((char*)&mcdi->magic, SUPERBLOCK_MAGIC); + strcat((char*)&mcdi->magic, SUPERBLOCK_VERSION); + + mcdi->cluster_size = 1024; + mcdi->blocksize = blocksize; + mcdi->pages_per_cluster = 2; + mcdi->pagesize = mcdi->cluster_size / mcdi->pages_per_cluster; + mcdi->clusters_per_block = mcdi->blocksize / mcdi->pages_per_cluster; + mcdi->clusters_per_card = (size_kb * 1024) / mcdi->cluster_size; + mcdi->cardtype = 0x02; + mcdi->cardflags = 0x2b; + mcdi->cardform = -1; + mcdi->FATentries_per_cluster = mcdi->cluster_size / sizeof(unsigned int); + + for (i = 0; i < 32; i++) + mcdi->bad_block_list[i] = -1; + + memset(cluster_buf, 0xff, sizeof(cluster_buf)); + for (i = 0; i < mcdi->clusters_per_card; i += 16) { + r = mc_writecluster(genvmc_fh, i, cluster_buf, 16); + if (r < 0) { + r = -102; + ret = fclose(genvmc_fh); + if (!(ret < 0)) + genvmc_fh = NULL; + + return r; + } + } + + fat_length = (((mcdi->clusters_per_card << 2) - 1) / mcdi->cluster_size) + 1; + ifc_length = (((fat_length << 2) - 1) / mcdi->cluster_size) + 1; + + if (!(ifc_length <= 32)) { + ifc_length = 32; + fat_length = mcdi->FATentries_per_cluster << 5; + } + + for (i = 0; i < 32; i++) + mcdi->ifc_list[i] = -1; + ifc_index = mcdi->blocksize / 2; + i = ifc_index; + for (j = 0; j < ifc_length; j++, i++) + mcdi->ifc_list[j] = i; + + fat_index = i; + + unsigned char* ifc_mem = (unsigned char*)malloc((ifc_length * mcdi->cluster_size) + 0XFF); + if (ifc_mem == NULL) { + r = -103; + ret = fclose(genvmc_fh); + if (!(ret < 0)) + genvmc_fh = NULL; + + return r; + } + memset(ifc_mem, 0, ifc_length * mcdi->cluster_size); + + unsigned int* ifc = (unsigned int*)ifc_mem; + for (j = 0; j < fat_length; j++, i++) { + + if (i >= mcdi->clusters_per_card) { + free(ifc_mem); + r = -104; + ret = fclose(genvmc_fh); + if (!(ret < 0)) + genvmc_fh = NULL; + + return r; + } + ifc[j] = i; + } + + for (z = 0; z < ifc_length; z++) { + r = mc_writecluster(genvmc_fh, mcdi->ifc_list[z], &ifc_mem[z * mcdi->cluster_size], 1); + if (r < 0) { + + free(ifc_mem); + r = -105; + ret = fclose(genvmc_fh); + if (!(ret < 0)) + genvmc_fh = NULL; + + return r; + } + } + + free(ifc_mem); + + alloc_offset = i; + + mcdi->backup_block1 = (mcdi->clusters_per_card / mcdi->clusters_per_block) - 1; + mcdi->backup_block2 = (mcdi->clusters_per_card / mcdi->clusters_per_block) - 2; + + unsigned int hi, lo, temp; + long_multiply(mcdi->clusters_per_card, 0x10624dd3, &hi, &lo); + temp = (hi >> 6) - (mcdi->clusters_per_card >> 31); + mcdi->max_allocatable_clusters = (((((temp << 5) - temp) << 2) + temp) << 3) + 1; + j = alloc_offset; + + i = (mcdi->clusters_per_card / mcdi->clusters_per_block) - 2; + for (z = 0; j < (i * mcdi->clusters_per_block); j += mcdi->FATentries_per_cluster) { + + memset(cluster_buf, 0, mcdi->cluster_size); + unsigned int* fc = (unsigned int*)cluster_buf; + int sz_u32 = (i * mcdi->clusters_per_block) - j; + if (sz_u32 > mcdi->FATentries_per_cluster) + sz_u32 = mcdi->FATentries_per_cluster; + for (b = 0; b < sz_u32; b++) + fc[b] = 0x7fffffff; + + if (z == 0) { + mcdi->alloc_offset = j; + mcdi->rootdir_cluster = 0; + fc[0] = 0xffffffff; + } + z += sz_u32; + + r = mc_writecluster(genvmc_fh, fat_index++, cluster_buf, 1); + if (r < 0) { + r = -107; + ret = fclose(genvmc_fh); + if (!(ret < 0)) + genvmc_fh = NULL; + + return r; + } + } + + mcdi->alloc_end = (i * mcdi->clusters_per_block) - mcdi->alloc_offset; + + if (z < mcdi->clusters_per_block) { + r = -108; + ret = fclose(genvmc_fh); + if (!(ret < 0)) + genvmc_fh = NULL; + + return r; + } + + mcdi->unknown1 = 0; + mcdi->unknown2 = 0; + mcdi->unknown5 = -1; + mcdi->rootdir_cluster2 = mcdi->rootdir_cluster; + + McFsEntry* rootdir_entry[2]; + sceMcStDateTime time; + + mc_getmcrtime(&time); + rootdir_entry[0] = (McFsEntry*)&cluster_buf[0]; + rootdir_entry[1] = (McFsEntry*)&cluster_buf[sizeof(McFsEntry)]; + memset((void*)rootdir_entry[0], 0, sizeof(McFsEntry)); + memset((void*)rootdir_entry[1], 0, sizeof(McFsEntry)); + rootdir_entry[0]->mode = 0x8000 | 0x0400 | 0x20 | 0x01 | 0x02 | 0x04; + rootdir_entry[0]->length = 2; + memcpy((void*)&rootdir_entry[0]->created, (void*)&time, sizeof(sceMcStDateTime)); + memcpy((void*)&rootdir_entry[0]->modified, (void*)&time, sizeof(sceMcStDateTime)); + rootdir_entry[0]->cluster = 0; + rootdir_entry[0]->dir_entry = 0; + strcpy(rootdir_entry[0]->name, "."); + rootdir_entry[1]->mode = 0x8000 | 0x2000 | 0x0400 | 0x20 | 0x02 | 0x04; + rootdir_entry[1]->length = 2; + memcpy((void*)&rootdir_entry[1]->created, (void*)&time, sizeof(sceMcStDateTime)); + memcpy((void*)&rootdir_entry[1]->modified, (void*)&time, sizeof(sceMcStDateTime)); + rootdir_entry[1]->cluster = 0; + rootdir_entry[1]->dir_entry = 0; + strcpy(rootdir_entry[1]->name, ".."); + + r = mc_writecluster(genvmc_fh, mcdi->alloc_offset + mcdi->rootdir_cluster, cluster_buf, 1); + if (r < 0) { + r = -109; + ret = fclose(genvmc_fh); + if (!(ret < 0)) + genvmc_fh = NULL; + + return r; + } + + mcdi->cardform = 1; + + memset(cluster_buf, 0xff, mcdi->cluster_size); + memcpy(cluster_buf, (void*)mcdi, sizeof(MCDevInfo)); + r = mc_writecluster(genvmc_fh, 0, cluster_buf, 1); + if (r < 0) { + r = -110; + ret = fclose(genvmc_fh); + if (!(ret < 0)) + genvmc_fh = NULL; + + return r; + } + + r = fclose(genvmc_fh); + if (r < 0) + return -111; + genvmc_fh = NULL; + + return 0; +} \ No newline at end of file diff --git a/OPL Game Installer/opl.h b/OPL Game Installer/opl.h new file mode 100644 index 0000000..86b22af --- /dev/null +++ b/OPL Game Installer/opl.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include +#include + +namespace OPL { + bool writeCfg(const char* path, const char* key, const char* value); + int downloadArts(const char* dir, const char* gameId); + bool fetchImage(const char* url, std::vector& buffer); + + int writeUl(const char* drive, const char* game_name, const char* game_id, const char* media, int parts); + int crc32Hex(const char* string); + int createVmc(const char* filename, int size_kb, int blocksize); +}; \ No newline at end of file diff --git a/OPL Game Installer/resource.h b/OPL Game Installer/resource.h new file mode 100644 index 0000000..9432164 --- /dev/null +++ b/OPL Game Installer/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by OPL Game Installer.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/OPL Game Installer/utilities.h b/OPL Game Installer/utilities.h new file mode 100644 index 0000000..8ad322f --- /dev/null +++ b/OPL Game Installer/utilities.h @@ -0,0 +1,152 @@ +#pragma once + +#include +#include +#include +#include +#include + +static std::string padStart(std::string input, size_t length, char padChar) { + return input.length() >= length ? input : std::string(length - input.length(), padChar) + input; +} + +static std::string toUpperCase(std::string input) { + for (char& c : input) { + c = std::toupper(c); + } + + return input; +} + +static std::string replaceStr(std::string str, const std::string& from, const std::string& to) { + size_t start_pos = 0; + start_pos = str.find(from, start_pos); + if (start_pos != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + return str; +} + +static std::string replaceMultiStr(std::string str, std::initializer_list from, std::initializer_list to) { + + for (int i = 0; i < from.size(); i++) { + size_t start_pos = 0; + start_pos = str.find(from.begin()[i], start_pos); + + if (start_pos != std::string::npos) { + str.replace(start_pos, from.begin()[i].length(), to.begin()[i]); + start_pos += to.begin()[i].length(); + } + } + + return str; +} + +static std::string eraseUntilChar(std::string str, const std::string& from, const std::string& to) { + size_t firstPos = str.find(from); + size_t secondPos = str.find(to, firstPos); + + if (firstPos != std::string::npos && secondPos != std::string::npos && secondPos > firstPos) { + str.erase(firstPos, secondPos - firstPos); + } + + return str; +} + +static char* locationFix(std::string path) { + char* fixed = new char[path.length() + 1]; + std::strcpy(fixed, path.c_str()); + + std::replace(fixed, fixed + strlen(fixed), '\\', '/'); + + auto last = std::unique(fixed, fixed + strlen(fixed), [](char a, char b) { + return a == '/' && b == '/'; + }); + *last = '\0'; + +#ifdef _WIN32 + if (fixed[1] == ':') { + fixed[0] = std::toupper(fixed[0]); + } +#endif + + return fixed; +} + + +static char* generateHex(int length) { + std::srand(static_cast(std::time(nullptr))); + + static const char hex_characters[] = "0123456789ABCDEF"; + + char* result = new char[length + 1]; + for (int i = 0; i < length; ++i) { + int randomIndex = std::rand() % 16; + result[i] = hex_characters[randomIndex]; + } + + result[length] = '\0'; + return result; +} + +static bool isDirectory(const char* path) { + struct stat fileInfo; + if (stat(path, &fileInfo) != 0) { + return false; + } + + return (fileInfo.st_mode & S_IFDIR); +} + +static char* stringToCharp(std::string str) { + char* data = new char[str.size()]; + std::copy(str.begin(), str.end(), data); + data[str.size()] = '\0'; + return data; +} + +static char* getFilenameByPath(const char* path, bool ext = false) { + + std::string pathStr(path); + size_t found = pathStr.find_last_of("/\\"); + if (found == std::string::npos) { + return nullptr; + } + + std::string gameName = pathStr.substr(found + 1); + if (!ext) { + size_t last = gameName.find_last_of("."); + if (last != std::string::npos) { + gameName = gameName.substr(0, last); + } + } + + return stringToCharp(gameName); +} + +static char* decimalToHex(int number, int lenght) { + std::ostringstream stream; + stream << std::setw(lenght) << std::setfill('0') << std::uppercase << std::hex << number; + return stringToCharp(stream.str()); +} + +static int __strcmpi(const char* str1, const char* str2) { + int result; +#ifdef _WIN32 + result = _strcmpi(str1, str2); +#else + result = strcasecmp(str1, str2); +#endif + + return result; +} +static size_t findFromCharArray(char* arr[], const char* searchText, size_t arrSize) { + for (size_t i = 0; i < arrSize; ++i) { + if (strstr(arr[i], searchText) != nullptr) { + return i; + } + } + + return -1; +} \ No newline at end of file diff --git a/README.md b/README.md index 00b0395..d4c8935 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,54 @@ # OPL-Game-Installer -This repository has an Open PS2 Loader game installer for FAT32 disc, you can also create a custom sized virtual memory card and download arts of the game. +You cannot write more than 4GB of data to a FAT32 disc, so you can't easily install a game on a disc. This repository allows you to do this easily, it also creates a [Virtual Memory](https://en.wikipedia.org/wiki/Virtual_memory) Card of the desired size for the game and downloads the images of the game from the server. + +#### What is Virtual Memory Card (VMC)? +Virtual Memory Card allows you to save the game on the disc you use in games without using an memory card. + +![](https://img.shields.io/badge/language-c++-e76089?style=plastic) ![](https://img.shields.io/badge/license-GNU-green?style=plastic) ![](https://img.shields.io/badge/arch-x64%20%7C%20x86-d9654f?style=plastic) ![](https://img.shields.io/badge/config-Debug%20%7C%20Release-c0c0c0?style=plastic) + +![Image of RequestX International Developer Group on Discord](https://raw.githubusercontent.com/kruz1337/OPL-Game-Installer/main/thumbnail.png?token=GHSAT0AAAAAACLHJHX2ZRO2DKK4VHJSVUEAZLQ6XQQ) + +## How to build OPL-Game-Installer with Curl? +* If you don't know how do you do this go download released version. + +* First of all you should download project files on project page or clone this repository from GitBash or GitHub Desktop on your PC. [OPL-Game-Installer.zip](https://github.com/kruz1337/OPL-Game-Installer/releases) + +* If you download project files with manual method you need extract zip file. + +* Open the "include" folder into the "OPL Game Installer/libcurl" directory, then copy Curl includes files into it. + +* Then, compile the Curl library according to your build configuration and create a "release64" folder for "Release | x64" build. The folder layout should be like this: +``` +./OPL Game Installer/libcurl +├─include +├─release64 +├─release86 +├─debug64 +└─debug86 +``` + +* After then, copy Curl compiled library file into the folder you opened. + +* If the name is different, change it to "libcurl_a" or you should change it from ```"Linker > Additional Dependencies"``` in project settings. + +* Run .sln file on Visual Studio (2019+). + +* Press Build button or press CTRL+B on your keyboard. + +* Check out bin folder include that. + +## Usage with Arguments +```"./OPL Game Installer.exe" [OPTIONS]``` + +### Options: +``` +--copy If the size of the Iso file does not exceed the limit, its allows copy directly to the disc. +--add-cover Download arts of the game. +--create-vmc Creates virtual memory of the entered size once defined. (Size=MB) +``` + +## TO-DO List +* Add ability of fetching the Size, Title, Type, Star Count and Description of the game from the server. +* Add fetching random images from the server. +* Add property of converting ul to iso. +* Build for Linux. diff --git a/thumbnail.png b/thumbnail.png new file mode 100644 index 0000000..f14ce20 Binary files /dev/null and b/thumbnail.png differ