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