diff --git a/.github/workflows/build-launcher(cmake-gcc).yml b/.github/workflows/build-launcher(cmake-gcc).yml new file mode 100644 index 0000000000..537bd2509f --- /dev/null +++ b/.github/workflows/build-launcher(cmake-gcc).yml @@ -0,0 +1,60 @@ +name: Build HMCLauncher (Using CMake and gcc) + +on: + push: + paths: + - 'HMCLauncher/**' + - '.github/workflows/build-launcher(cmake-gcc).yml' + pull_request: + paths: + - 'HMCLauncher/**' + - '.github/workflows/build-launcher(cmake-gcc).yml' + +jobs: + build: + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + steps: + - name: 'Setup MSYS2' + uses: msys2/setup-msys2@v2 + with: + msystem: mingw32 + update: true + install: >- + mingw-w64-i686-toolchain + mingw-w64-i686-ninja + mingw-w64-i686-cmake + git + - name: 'Checkout' + uses: actions/checkout@v4 + - name: Build HMCLauncher + run: | + cmake -S ./HMCLauncher/HMCL -B ./HMCLauncher/HMCL/build -DCMAKE_BUILD_TYPE=Release + cmake --build ./HMCLauncher/HMCL/build + - name: Copy HMCLauncher to assets + run: cp ./HMCLauncher/HMCL/build/HMCLauncher.exe ./HMCL/src/main/resources/assets/HMCLauncher.exe + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '11' + java-package: 'jdk+fx' + - name: Build with Gradle + run: ./gradlew makeExecutables --no-daemon + env: + MICROSOFT_AUTH_ID: ${{ secrets.MICROSOFT_AUTH_ID }} + MICROSOFT_AUTH_SECRET: ${{ secrets.MICROSOFT_AUTH_SECRET }} + CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }} + - name: Get short SHA + uses: benjlevesque/short-sha@v3.0 + with: + length: 7 + - name: Copy HMCLauncher to libs + run: cp ./HMCLauncher/HMCL/build/HMCLauncher.exe ./HMCL/build/libs/HMCLauncher.exe + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: HMCLauncher-${{ env.SHA }} + path: HMCL/build/libs/*.exe \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/HMCLauncher.exe b/HMCL/src/main/resources/assets/HMCLauncher.exe index 1278064551..48cedda62c 100644 Binary files a/HMCL/src/main/resources/assets/HMCLauncher.exe and b/HMCL/src/main/resources/assets/HMCLauncher.exe differ diff --git a/HMCLauncher/HMCL/CMakeLists.txt b/HMCLauncher/HMCL/CMakeLists.txt new file mode 100644 index 0000000000..b316635f95 --- /dev/null +++ b/HMCLauncher/HMCL/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.25) +project(HMCLauncher) +if(MSVC) + add_compile_options(/utf-8 /D_UNICODE /W4) +else() + add_compile_options(-municode -Wall -Wextra -Wpedantic) +endif() +if(MSVC) + add_link_options(/ENTRY:wWinMainCRTStartup) +else() + add_link_options(-municode) +endif() +OPTION(ENABLE_MINGW_STATIC_LINK_LIBSTDCXX "Link the C++ standard library statically to the executable file(mingw only)." ON) +if(ENABLE_MINGW_STATIC_LINK_LIBSTDCXX AND MINGW) + add_link_options(-static) +endif() +set(CMAKE_WIN32_EXECUTABLE ON) +add_executable(HMCLauncher WIN32 HMCL.rc java.cpp main.cpp os.cpp install.cpp stdafx.cpp Version.cpp) +target_link_libraries(HMCLauncher Version) +target_link_libraries(HMCLauncher wininet) +install(TARGETS HMCLauncher) \ No newline at end of file diff --git a/HMCLauncher/HMCL/HMCL.vcxproj b/HMCLauncher/HMCL/HMCL.vcxproj index fe4858207d..d05ea073ec 100644 --- a/HMCLauncher/HMCL/HMCL.vcxproj +++ b/HMCLauncher/HMCL/HMCL.vcxproj @@ -96,7 +96,7 @@ Windows true - version.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + version.lib;wininet.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) @@ -134,7 +134,7 @@ true true true - version.lib;%(AdditionalDependencies) + version.lib;wininet.lib;%(AdditionalDependencies) @@ -160,6 +160,7 @@ + @@ -170,6 +171,7 @@ + diff --git a/HMCLauncher/HMCL/HMCL.vcxproj.filters b/HMCLauncher/HMCL/HMCL.vcxproj.filters index fea247b7a7..18791dc943 100644 --- a/HMCLauncher/HMCL/HMCL.vcxproj.filters +++ b/HMCLauncher/HMCL/HMCL.vcxproj.filters @@ -33,6 +33,9 @@ 头文件 + + 头文件 + 头文件 @@ -53,6 +56,9 @@ 源文件 + + 源文件 + 源文件 diff --git a/HMCLauncher/HMCL/Version.h b/HMCLauncher/HMCL/Version.h index 5236904020..e289dcb009 100644 --- a/HMCLauncher/HMCL/Version.h +++ b/HMCLauncher/HMCL/Version.h @@ -28,4 +28,10 @@ class Version { if (ver[i] != other.ver[i]) return ver[i] < other.ver[i]; return true; } + + bool operator>=(const Version &other) const { + for (int i = 0; i < 4; ++i) + if (ver[i] != other.ver[i]) return ver[i] > other.ver[i]; + return true; + } }; diff --git a/HMCLauncher/HMCL/install.cpp b/HMCLauncher/HMCL/install.cpp new file mode 100644 index 0000000000..55667139ef --- /dev/null +++ b/HMCLauncher/HMCL/install.cpp @@ -0,0 +1,189 @@ +#include "stdafx.h" +#include "install.h" + +int InstallHMCLJRE(const std::wstring &home, const std::wstring &domain, + const std::wstring &file) { + WIN32_FIND_DATA ffd; + std::wstring runtimeDirectory, jreDownloadedFile, jreDownloadedDirectory; + + runtimeDirectory = home + L"runtime"; + jreDownloadedFile = home + L".hmclauncher-jre-"; + jreDownloadedFile.append(std::to_wstring(rand())); + jreDownloadedDirectory = jreDownloadedFile + L""; + jreDownloadedFile.append(L".zip"); + + HANDLE hFind = INVALID_HANDLE_VALUE; + + int err = 0; + + err = DownloadHMCLJRE(jreDownloadedFile, domain, file); + if (err != 0) { + goto cleanup; + } + + if (!CreateDirectory(jreDownloadedDirectory.c_str(), NULL)) { + err = GetLastError(); + goto cleanup; + } + + UncompressHMCLJRE(jreDownloadedFile, jreDownloadedDirectory); + + hFind = FindFirstFile((jreDownloadedDirectory + L"\\*").c_str(), &ffd); + if (hFind == INVALID_HANDLE_VALUE) { + err = GetLastError(); + goto cleanup; + } + + { + std::wstring targetFile = std::wstring(); + do { + std::wstring fileName = std::wstring(ffd.cFileName); + if (fileName.size() <= 2 && fileName[0] == L'.') { + continue; + } + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (targetFile.size() != 0) { + err = HMCL_ERR_INVALID_JRE_PACK; + goto cleanup; + } + + targetFile.append(fileName); + } + } while (FindNextFile(hFind, &ffd) != 0); + err = GetLastError(); + if (err != ERROR_NO_MORE_FILES) { + goto cleanup; + } + err = 0; + + if (targetFile.size() == 0) { + err = HMCL_ERR_INVALID_JRE_PACK; + goto cleanup; + } + + if (!MoveFile((jreDownloadedDirectory + L'\\' + targetFile).c_str(), runtimeDirectory.c_str())) { + err = GetLastError(); + goto cleanup; + } + } + + cleanup: + if (hFind != INVALID_HANDLE_VALUE) { + CloseHandle(hFind); + } + RemoveDirectory(jreDownloadedDirectory.c_str()); + DeleteFile(jreDownloadedFile.c_str()); + return err; +} + +int DownloadHMCLJRE(std::wstring &jreDownloadedFile, const std::wstring &domain, + const std::wstring &file) { + int err = 0; + HINTERNET hInternet = NULL, hConnection = NULL, hRequest = NULL; + byte buffer[4096]; + HANDLE fd = NULL; + + { + hInternet = InternetOpen(L"HMCLauncher", INTERNET_OPEN_TYPE_PRECONFIG, NULL, + NULL, 0); + if (hInternet == NULL) { + err = GetLastError(); + goto cleanup; + } + hConnection = + InternetConnect(hInternet, domain.c_str(), INTERNET_DEFAULT_HTTPS_PORT, + NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); + if (hConnection == NULL) { + err = GetLastError(); + goto cleanup; + } + + const wchar_t *ACCEPTS[] = {L"application/zip", NULL}; + hRequest = HttpOpenRequest(hConnection, L"GET", file.c_str(), NULL, NULL, + ACCEPTS, INTERNET_FLAG_SECURE, 0); + if (hRequest == NULL) { + err = GetLastError(); + goto cleanup; + } + + if (!HttpSendRequest(hRequest, NULL, 0, NULL, 0)) { + err = GetLastError(); + goto cleanup; + } + + { + DWORD receivedCount, unused; + + fd = CreateFile(jreDownloadedFile.c_str(), GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (fd == INVALID_HANDLE_VALUE) { + err = GetLastError(); + goto cleanup; + } + + while (InternetReadFile(hRequest, buffer, 4096, &receivedCount) && + receivedCount) { + if (!WriteFile(fd, buffer, receivedCount, &unused, NULL)) { + err = GetLastError(); + goto cleanup; + } + } + } + } + +cleanup: + if (hRequest != NULL) { + InternetCloseHandle(hRequest); + } + if (hConnection != NULL) { + InternetCloseHandle(hConnection); + } + if (hInternet != NULL) { + InternetCloseHandle(hInternet); + } + if (fd != NULL) { + CloseHandle(fd); + } + return err; +} + +int UncompressHMCLJRE(std::wstring jreDownloadedFile, std::wstring jreDownloadedDirectory) { + std::wstring command = L"tar.exe -xf \"" + jreDownloadedFile + L"\""; + int err = 0; + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + si.cb = sizeof(si); + ZeroMemory(&si, sizeof(si)); + ZeroMemory(&pi, sizeof(pi)); + if (!CreateProcess(NULL, &command[0], NULL, NULL, false, + NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, jreDownloadedDirectory.c_str(), &si, + &pi)) { + return GetLastError(); + } + + while (true) { + DWORD exitCode; + if (!GetExitCodeProcess(pi.hProcess, &exitCode)) { + err = GetLastError(); + goto cleanup; + } + if (exitCode != STILL_ACTIVE) { + if (exitCode != 0) { + err = exitCode; + goto cleanup; + } + break; + } + } + + cleanup: + CloseHandle(si.hStdInput); + CloseHandle(si.hStdOutput); + CloseHandle(si.hStdError); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return err; +} \ No newline at end of file diff --git a/HMCLauncher/HMCL/install.h b/HMCLauncher/HMCL/install.h new file mode 100644 index 0000000000..7c904309f6 --- /dev/null +++ b/HMCLauncher/HMCL/install.h @@ -0,0 +1,11 @@ +#include "stdafx.h" + +#define HMCL_ERR_INVALID_JRE_PACK -129 + +int InstallHMCLJRE(const std::wstring &home, const std::wstring &domain, + const std::wstring &file); + +int DownloadHMCLJRE(std::wstring &target, const std::wstring &domain, + const std::wstring &file); + +int UncompressHMCLJRE(std::wstring jreDownloadedFile, std::wstring jreDownloadedDirectory); \ No newline at end of file diff --git a/HMCLauncher/HMCL/java.cpp b/HMCLauncher/HMCL/java.cpp index 067c12fb2a..bd3df3d07a 100644 --- a/HMCLauncher/HMCL/java.cpp +++ b/HMCLauncher/HMCL/java.cpp @@ -5,14 +5,110 @@ const Version JAVA_8(L"1.8"), JAVA_11(L"11"); -const LPCWSTR JDK_NEW = L"SOFTWARE\\JavaSoft\\JDK"; -const LPCWSTR JRE_NEW = L"SOFTWARE\\JavaSoft\\JRE"; -const LPCWSTR JDK_OLD = L"SOFTWARE\\JavaSoft\\Java Development Kit"; -const LPCWSTR JRE_OLD = L"SOFTWARE\\JavaSoft\\Java Runtime Environment"; +const LPCWSTR JDK_HKEYS[] = {L"SOFTWARE\\JavaSoft\\JDK", + L"SOFTWARE\\JavaSoft\\JRE", + L"SOFTWARE\\JavaSoft\\Java Development Kit", + L"SOFTWARE\\JavaSoft\\Java Runtime Environment"}; -bool oldJavaFound = false; +const LPCWSTR VENDOR_DIRS[] = {L"Java", L"Microsoft", L"BellSoft", + L"Zulu", L"Eclipse Foundation", L"AdoptOpenJDK", + L"Semeru"}; -bool FindJavaByRegistryKey(HKEY rootKey, LPCWSTR subKey, std::wstring& path) { +const LPCWSTR PROGRAM_DIRS[] = {L"ProgramFiles", L"ProgramFiles(x86)", + L"ProgramW6432"}; + +/* Here we find the java from Environment Variable and Registry, which is fast, + * and store the result to 'path'. */ +void ScanJava(SYSTEM_INFO& systemInfo, std::wstring& result, int& status) { + status = JAVA_STATUS_NOT_FOUND; + + // If 'HMCL_JAVA_HOME' is settled, this value MUST be used without any check as it can be used. + if (ERROR_SUCCESS == MyGetEnvironmentVariable(L"HMCL_JAVA_HOME", result)) { + MyPathNormalize(result); + status = JAVA_STATUS_BEST; + return; + } + + { + std::wstring buffer; + if (SUCCEEDED(MySHGetFolderPath(CSIDL_APPDATA, buffer)) || + SUCCEEDED(MySHGetFolderPath(CSIDL_PROFILE, buffer))) { + MyPathNormalize(buffer); + MyPathAppend(buffer, L".hmcl\\runtime"); + MyPathAddBackslash(buffer); + + if (DeterminJavaHome(buffer, result, status) && + status != JAVA_STATUS_NOT_FOUND) { + return; + } + } + } + + std::wstring currentResult; + if (ERROR_SUCCESS == MyGetEnvironmentVariable(L"JAVA_HOME", currentResult)) { + MyPathNormalize(result); + if (DeterminJavaHome(currentResult, result, status) && + status == JAVA_STATUS_BEST) { + return; + } + } + + for (LPCWSTR hkey : JDK_HKEYS) { + ScanJavaRegistry(HKEY_LOCAL_MACHINE, hkey, result, status); + if (status == JAVA_STATUS_BEST) { + return; + } + } + + std::wstring envPath; + if (ERROR_SUCCESS == MyGetEnvironmentVariable(L"PATH", envPath)) { + int length = envPath.size(); + int lastI = 0; + for (int i = 0; i < length; i++) { + if (envPath[i] == L';') { + int partL = i - lastI; + if (partL > 0) { + std::wstring part = envPath.substr(lastI, partL); + MyPathNormalize(part); + MyPathAddBackslash(part); + int partL2 = part.size(); + if (part[partL2 - 5] == L'\\' && part[partL2 - 4] == L'b' && + part[partL2 - 3] == L'i' && part[partL2 - 2] == L'n' && + part[partL2 - 1] == L'\\') { + part.resize(partL2 - 5); + if (DeterminJavaHome(part, result, status) && + status == JAVA_STATUS_BEST) { + return; + } + } + } + + lastI = i + 1; + } + } + } + + std::wstring root; + for (LPCWSTR program : PROGRAM_DIRS) { + if (ERROR_SUCCESS != MyGetEnvironmentVariable(program, root)) { + continue; + } + + MyPathNormalize(root); + for (LPCWSTR vender : VENDOR_DIRS) { + std::wstring currentRoot = root + L""; + MyPathAppend(currentRoot, vender); + MyPathAddBackslash(currentRoot); + ScanJavaFolder(currentRoot, result, status); + if (status == JAVA_STATUS_BEST) { + return; + } + } + } +} + +void ScanJavaRegistry(HKEY rootKey, LPCWSTR subKey, std::wstring& path, + int& status) { WCHAR javaVer[MAX_KEY_LENGTH]; // buffer for subkey name, special for // JavaVersion DWORD cbName; // size of name string @@ -25,9 +121,10 @@ bool FindJavaByRegistryKey(HKEY rootKey, LPCWSTR subKey, std::wstring& path) { HKEY hKey; if (ERROR_SUCCESS != - (result = - RegOpenKeyEx(rootKey, subKey, 0, KEY_WOW64_64KEY | KEY_READ, &hKey))) - return false; + (result = RegOpenKeyEx(rootKey, subKey, 0, KEY_WOW64_64KEY | KEY_READ, + &hKey))) { + return; + } RegQueryInfoKey(hKey, // key handle NULL, // buffer for class name @@ -42,9 +139,10 @@ bool FindJavaByRegistryKey(HKEY rootKey, LPCWSTR subKey, std::wstring& path) { NULL, // security descriptor NULL); // last write time - if (!cSubKeys) return false; + if (!cSubKeys) { + return; + } - bool flag = false; for (DWORD i = 0; i < cSubKeys; ++i) { cbName = MAX_KEY_LENGTH; if (ERROR_SUCCESS != (result = RegEnumKeyEx(hKey, i, javaVer, &cbName, NULL, @@ -55,30 +153,60 @@ bool FindJavaByRegistryKey(HKEY rootKey, LPCWSTR subKey, std::wstring& path) { if (ERROR_SUCCESS != RegOpenKeyEx(hKey, javaVer, 0, KEY_READ, &javaKey)) continue; - if (ERROR_SUCCESS == MyRegQueryValue(javaKey, L"JavaHome", REG_SZ, path)) { - if (Version(javaVer) < JAVA_8) - oldJavaFound = true; - else - flag = true; - } + std::wstring currentPath; + if (ERROR_SUCCESS == + MyRegQueryValue(javaKey, L"JavaHome", REG_SZ, currentPath)) { + MyPathNormalize(currentPath); + Version v = Version(javaVer); - if (flag) break; + if (status < JAVA_STATUS_BEST && v >= JAVA_11) { + path = currentPath; + status = JAVA_STATUS_BEST; + break; + } else if (status < JAVA_STATUS_USABLE && v >= JAVA_8) { + path = currentPath; + status = JAVA_STATUS_USABLE; + } + } } RegCloseKey(hKey); - - return flag; } -bool FindJavaInRegistry(std::wstring& path) { - return FindJavaByRegistryKey(HKEY_LOCAL_MACHINE, JDK_NEW, path) || - FindJavaByRegistryKey(HKEY_LOCAL_MACHINE, JRE_NEW, path) || - FindJavaByRegistryKey(HKEY_LOCAL_MACHINE, JDK_OLD, path) || - FindJavaByRegistryKey(HKEY_LOCAL_MACHINE, JRE_OLD, path); -} +void ScanJavaFolder(std::wstring root, std::wstring& result, int& status) { + WIN32_FIND_DATA data; + HANDLE hFind = + FindFirstFile((root + L"*").c_str(), &data); // Search all subdirectory -bool FindJava(std::wstring& path) { - return ERROR_SUCCESS == MyGetEnvironmentVariable(L"HMCL_JAVA_HOME", path) || - ERROR_SUCCESS == MyGetEnvironmentVariable(L"JAVA_HOME", path) || - FindJavaInRegistry(path); + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (DeterminJavaHome(root + data.cFileName, result, status) && + status == JAVA_STATUS_BEST) { + goto done; + } + } while (FindNextFile(hFind, &data)); + + done: + FindClose(hFind); + } } + +boolean DeterminJavaHome(std::wstring target, std::wstring& result, + int& status) { + Version version(L""); + std::wstring currentRoot = target + L""; + MyPathAppend(currentRoot, std::wstring(L"bin\\javaw.exe")); + + if (!MyGetFileVersionInfo(currentRoot, version)) return false; + + if (status < JAVA_STATUS_BEST && version >= JAVA_11) { + result = target; + status = JAVA_STATUS_BEST; + return true; + } else if (status < JAVA_STATUS_USABLE && version >= JAVA_8) { + result = target; + status = JAVA_STATUS_USABLE; + return true; + } + return false; +} \ No newline at end of file diff --git a/HMCLauncher/HMCL/java.h b/HMCLauncher/HMCL/java.h index eb0a289f97..2985061cfb 100644 --- a/HMCLauncher/HMCL/java.h +++ b/HMCLauncher/HMCL/java.h @@ -2,8 +2,18 @@ #include #include -// Find Java installation in system registry -bool FindJavaInRegistry(std::wstring &path); +// These increasing values represent the priority of the Java. +// Be careful while changing their values! +const int JAVA_STATUS_NOT_FOUND = 0; +const int JAVA_STATUS_USABLE = 1; // Java 8 - 11 (Exclude) +const int JAVA_STATUS_BEST = 2; // Java 11 (Include) - ++ -// Find Java Installation in registry and environment variable -bool FindJava(std::wstring &path); +void ScanJava(SYSTEM_INFO& systemInfo, std::wstring& result, int& status); + +void ScanJavaRegistry(HKEY rootKey, LPCWSTR subKey, std::wstring& path, + int& status); + +void ScanJavaFolder(std::wstring root, std::wstring& result, int& status); + +boolean DeterminJavaHome(std::wstring target, std::wstring& result, + int& status); \ No newline at end of file diff --git a/HMCLauncher/HMCL/main.cpp b/HMCLauncher/HMCL/main.cpp index 78bdc48f47..fd1f9e1cba 100644 --- a/HMCLauncher/HMCL/main.cpp +++ b/HMCLauncher/HMCL/main.cpp @@ -3,60 +3,58 @@ #include "os.h" #include "java.h" #include "lang.h" +#include "install.h" #include Version J8(TEXT("8")); extern "C" { +#ifdef _MSC_VER __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; +#else +__attribute__ ((dllexport)) DWORD NvOptimusEnablement = 0x00000001; +__attribute__ ((dllexport)) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; +#endif } -LPCWSTR VENDOR_DIRS[] = { - L"Java", L"Microsoft", L"BellSoft", L"Zulu", L"Eclipse Foundation", L"AdoptOpenJDK", L"Semeru" -}; +void LaunchHMCLDirect(const std::wstring &javaPath, const std::wstring &workdir, + const std::wstring &jarPath, + const std::wstring &jvmOptions) { + HANDLE hProcess = MyCreateProcess(L"\"" + javaPath + L"\" " + jvmOptions + L" -jar \"" + + jarPath + L"\"", workdir); + if (hProcess == INVALID_HANDLE_VALUE) { + return; + } -void RawLaunchJVM(const std::wstring &javaPath, const std::wstring &workdir, - const std::wstring &jarPath, const std::wstring &jvmOptions) { - if (MyCreateProcess(L"\"" + javaPath + L"\" " + jvmOptions + L" -jar \"" + jarPath + L"\"", workdir)) + Sleep(5000); + DWORD exitCode; + if (!GetExitCodeProcess(hProcess, &exitCode)) { + GetLastError(); + CloseHandle(hProcess); + return; + } + CloseHandle(hProcess); + if (exitCode == STILL_ACTIVE || exitCode == 0) { exit(EXIT_SUCCESS); + } } -void LaunchJVM(const std::wstring &javaPath, const std::wstring &workdir, - const std::wstring &jarPath, const std::wstring &jvmOptions) { +void LaunchHMCL(const std::wstring &javaPath, const std::wstring &workdir, + const std::wstring &jarPath, const std::wstring &jvmOptions) { Version javaVersion(L""); if (!MyGetFileVersionInfo(javaPath, javaVersion)) return; if (J8 <= javaVersion) { - RawLaunchJVM(javaPath, workdir, jarPath, jvmOptions); - } -} - -void FindJavaInDirAndLaunchJVM(const std::wstring &baseDir, const std::wstring &workdir, - const std::wstring &jarPath, const std::wstring &jvmOptions) { - std::wstring pattern = baseDir + L"*"; - - WIN32_FIND_DATA data; - HANDLE hFind = FindFirstFile(pattern.c_str(), &data); // Search all subdirectory - - if (hFind != INVALID_HANDLE_VALUE) { - do { - std::wstring javaw = baseDir + data.cFileName + std::wstring(L"\\bin\\javaw.exe"); - if (FindFirstFileExists(javaw.c_str(), 0)) { - LaunchJVM(javaw, workdir, jarPath, jvmOptions); - } - } while (FindNextFile(hFind, &data)); - FindClose(hFind); + LaunchHMCLDirect(javaPath, workdir, jarPath, jvmOptions); } } -void OpenHelpPage() { - ShellExecute(0, 0, L"https://docs.hmcl.net/help.html", 0, 0, SW_SHOW); -} - int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { - std::wstring path, exeName, jvmOptions; + srand(GetTickCount64()); + + std::wstring exeName, jvmOptions; // Since Jar file is appended to this executable, we should first get the // location of JAR file. @@ -69,88 +67,65 @@ int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, exeName = exeName.substr(last_slash + 1); } - if (ERROR_SUCCESS != MyGetEnvironmentVariable(L"HMCL_JAVA_OPTS", jvmOptions)) { - jvmOptions = L"-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15"; // Default Options + if (ERROR_SUCCESS != + MyGetEnvironmentVariable(L"HMCL_JAVA_OPTS", jvmOptions)) { + jvmOptions = + L"-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15"; // Default Options } - bool useChinese = GetUserDefaultUILanguage() == 2052; // zh-CN + bool useChinese = GetUserDefaultUILanguage() == 2052; // zh-CN SYSTEM_INFO systemInfo; GetNativeSystemInfo(&systemInfo); // TODO: check whether the bundled JRE is valid. - // First try the Java packaged together. - bool isX64 = (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64); - bool isARM64 = (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64); + + // First, try the Java packaged together. + bool isX64 = + (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64); + bool isARM64 = + (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64); if (isARM64) { - RawLaunchJVM(L"jre-arm64\\bin\\javaw.exe", workdir, exeName, jvmOptions); + LaunchHMCLDirect(L"jre-arm64\\bin\\java.exe", workdir, exeName, + jvmOptions); } if (isX64) { - RawLaunchJVM(L"jre-x64\\bin\\javaw.exe", workdir, exeName, jvmOptions); + LaunchHMCLDirect(L"jre-x64\\bin\\java.exe", workdir, exeName, jvmOptions); } - RawLaunchJVM(L"jre-x86\\bin\\javaw.exe", workdir, exeName, jvmOptions); + LaunchHMCLDirect(L"jre-x86\\bin\\java.exe", workdir, exeName, jvmOptions); - if (FindJava(path)) LaunchJVM(path + L"\\bin\\javaw.exe", workdir, exeName, jvmOptions); + // Next, try the Java installed on this computer. + std::wstring path; + int status; - std::wstring programFiles; + ScanJava(systemInfo, path, status); - // Or we try to search Java in C:\Program Files - if (!SUCCEEDED(MySHGetFolderPath(CSIDL_PROGRAM_FILES, programFiles))) programFiles = L"C:\\Program Files\\"; - for (LPCWSTR vendorDir : VENDOR_DIRS) { - std::wstring dir = programFiles; - MyPathAppend(dir, vendorDir); - MyPathAddBackslash(dir); - - FindJavaInDirAndLaunchJVM(dir, workdir, exeName, jvmOptions); - } - - // Consider C:\Program Files (x86) - if (!SUCCEEDED(MySHGetFolderPath(CSIDL_PROGRAM_FILESX86, programFiles))) programFiles = L"C:\\Program Files (x86)\\"; - for (LPCWSTR vendorDir : VENDOR_DIRS) { - std::wstring dir = programFiles; - MyPathAppend(dir, vendorDir); - MyPathAddBackslash(dir); - - FindJavaInDirAndLaunchJVM(dir, workdir, exeName, jvmOptions); + if (status != JAVA_STATUS_NOT_FOUND) { + MyPathAppend(path, std::wstring(L"bin\\java.exe")); + LaunchHMCL(path, workdir, exeName, jvmOptions); } // Try java in PATH - RawLaunchJVM(L"javaw", workdir, exeName, jvmOptions); - - std::wstring hmclJavaDir; - { - std::wstring buffer; - if (SUCCEEDED(MySHGetFolderPath(CSIDL_APPDATA, buffer)) || SUCCEEDED(MySHGetFolderPath(CSIDL_PROFILE, buffer))) { - MyPathAppend(buffer, L".hmcl"); - MyPathAppend(buffer, L"java"); - if (isARM64) { - MyPathAppend(buffer, L"windows-arm64"); - } else if (isX64) { - MyPathAppend(buffer, L"windows-x86_64"); - } else { - MyPathAppend(buffer, L"windows-x86"); - } - MyPathAddBackslash(buffer); - hmclJavaDir = buffer; + LaunchHMCLDirect(L"java", workdir, exeName, jvmOptions); + + std::wstring home; + if (SUCCEEDED(MySHGetFolderPath(CSIDL_APPDATA, home)) || + SUCCEEDED(MySHGetFolderPath(CSIDL_PROFILE, home))) { + MyPathNormalize(home); + MyPathAppend(home, L".hmcl\\"); + + std::wstring file = L""; + if (isARM64) { + file = L"/java/11.0.24+9/bellsoft-jre11.0.24+9-windows-aarch64-full.zip"; + } else if (isX64) { + file = L"/java/11.0.24+9/bellsoft-jre11.0.24+9-windows-i586-full.zip"; + } else { + file = L"/java/11.0.24+9/bellsoft-jre11.0.24+9-windows-amd64-full.zip"; } - } + InstallHMCLJRE(home, L"download.bell-sw.com", file); - if (!hmclJavaDir.empty()) { - FindJavaInDirAndLaunchJVM(hmclJavaDir, workdir, exeName, jvmOptions); + LaunchHMCLDirect(home + L"runtime\\bin\\java.exe", workdir, exeName, jvmOptions); } - LPCWSTR downloadLink; - - if (isARM64) { - downloadLink = L"https://docs.hmcl.net/downloads/windows/arm64.html"; - } if (isX64) { - downloadLink = L"https://docs.hmcl.net/downloads/windows/x86_64.html"; - } else { - downloadLink = L"https://docs.hmcl.net/downloads/windows/x86.html"; - } - - if (IDOK == MessageBox(NULL, useChinese ? ERROR_PROMPT_ZH : ERROR_PROMPT, useChinese ? ERROR_TITLE_ZH : ERROR_TITLE, MB_ICONWARNING | MB_OKCANCEL)) { - ShellExecute(0, 0, downloadLink, 0, 0, SW_SHOW); - } - return 1; + return 0; } diff --git a/HMCLauncher/HMCL/main.h b/HMCLauncher/HMCL/main.h index d00d47e788..49ab1a0c81 100644 --- a/HMCLauncher/HMCL/main.h +++ b/HMCLauncher/HMCL/main.h @@ -1,3 +1,3 @@ #pragma once -#include "resource.h" +#include "resource.h" \ No newline at end of file diff --git a/HMCLauncher/HMCL/os.cpp b/HMCLauncher/HMCL/os.cpp index d632b422c8..2bcab02f79 100644 --- a/HMCLauncher/HMCL/os.cpp +++ b/HMCLauncher/HMCL/os.cpp @@ -30,21 +30,29 @@ LSTATUS MyGetModuleFileName(HMODULE hModule, std::wstring &out) { } LSTATUS MyGetEnvironmentVariable(LPCWSTR name, std::wstring &out) { - DWORD res, size = MAX_PATH; out = std::wstring(); - out.resize(size); - while ((res = GetEnvironmentVariable(name, &out[0], size)) == size) { - out.resize(size += MAX_PATH); - } - if (res == 0) - return GetLastError(); - else { - out.resize(size - MAX_PATH + res); - return ERROR_SUCCESS; + int size = MAX_PATH; + while (true) { + out.resize(size); + DWORD res = GetEnvironmentVariable(name, &out[0], size); + if (res == 0) { + return GetLastError(); + } else if (res == size) { + // This should NOT happen. + // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getenvironmentvariable + return ERROR_INVALID_FUNCTION; + } else { + out.resize(res); + if (res < size) { + return ERROR_SUCCESS; + } + // Allocate enough memory and try again. + size = res; + } } } -bool MyCreateProcess(const std::wstring &command, const std::wstring &workdir) { +HANDLE MyCreateProcess(const std::wstring &command, const std::wstring &workdir) { std::wstring writable_command = command; STARTUPINFO si; PROCESS_INFORMATION pi; @@ -52,14 +60,17 @@ bool MyCreateProcess(const std::wstring &command, const std::wstring &workdir) { ZeroMemory(&si, sizeof(si)); ZeroMemory(&pi, sizeof(pi)); - if (workdir.empty()) { - return CreateProcess(NULL, &writable_command[0], NULL, NULL, false, - NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi); - } else { - return CreateProcess(NULL, &writable_command[0], NULL, NULL, false, - NORMAL_PRIORITY_CLASS, NULL, workdir.c_str(), &si, - &pi); + if (!CreateProcess(NULL, &writable_command[0], NULL, NULL, false, + NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, workdir.empty() ? NULL : workdir.c_str(), &si, &pi)) { + return INVALID_HANDLE_VALUE; } + + CloseHandle(si.hStdInput); + CloseHandle(si.hStdOutput); + CloseHandle(si.hStdError); + CloseHandle(pi.hThread); + + return pi.hProcess; } bool FindFirstFileExists(LPCWSTR lpPath, DWORD dwFilter) { @@ -111,6 +122,15 @@ HRESULT MySHGetFolderPath(int csidl, std::wstring &out) { return res; } +void MyPathNormalize(std::wstring &path) { + int size = path.size(); + for (int i = 0; i < size; i++) { + if (path[i] == L'/') { + path[i] = L'\\'; + } + } +} + void MyPathAppend(std::wstring &filePath, const std::wstring &more) { if (filePath.back() != L'\\') { filePath += L'\\'; @@ -125,7 +145,8 @@ void MyPathAddBackslash(std::wstring &filePath) { } } -LSTATUS MyGetTempFile(const std::wstring &prefixString, const std::wstring &ext, std::wstring &out) { +LSTATUS MyGetTempFile(const std::wstring &prefixString, const std::wstring &ext, + std::wstring &out) { out.resize(MAX_PATH); DWORD res = GetTempPath(MAX_PATH, &out[0]); if (res == 0) { @@ -140,7 +161,7 @@ LSTATUS MyGetTempFile(const std::wstring &prefixString, const std::wstring &ext, WCHAR buffer[MAX_PATH]; int n = StringFromGUID2(guid, buffer, MAX_PATH); if (n == 0) { - return CO_E_PATHTOOLONG; + return CO_E_PATHTOOLONG; } MyPathAddBackslash(out); @@ -152,7 +173,8 @@ LSTATUS MyGetTempFile(const std::wstring &prefixString, const std::wstring &ext, return ERROR_SUCCESS; } -void MyAppendPathToCommandLine(std::wstring &commandLine, const std::wstring &path) { +void MyAppendPathToCommandLine(std::wstring &commandLine, + const std::wstring &path) { commandLine += L'"'; for (size_t i = 0; i < path.size(); i++) { WCHAR ch = path[i]; diff --git a/HMCLauncher/HMCL/os.h b/HMCLauncher/HMCL/os.h index d0c2fcd63e..f664ac9f0b 100644 --- a/HMCLauncher/HMCL/os.h +++ b/HMCLauncher/HMCL/os.h @@ -25,14 +25,16 @@ LSTATUS MyGetModuleFileName(HMODULE hModule, std::wstring &out); LSTATUS MyGetEnvironmentVariable(LPCWSTR name, std::wstring &out); // Create process by invoking CreateProcess, only pass command. -bool MyCreateProcess(const std::wstring &command, const std::wstring &workdir); +HANDLE MyCreateProcess(const std::wstring &command, const std::wstring &workdir); // Check if file lpPath exists. bool FindFirstFileExists(LPCWSTR lpPath, DWORD dwFilter); +HRESULT MySHGetFolderPath(int csidl, std::wstring &out); + bool MyGetFileVersionInfo(const std::wstring &filePath, Version &version); -HRESULT MySHGetFolderPath(int csidl, std::wstring &out); +void MyPathNormalize(std::wstring& path); void MyPathAppend(std::wstring &filePath, const std::wstring &more); diff --git a/HMCLauncher/HMCL/stdafx.h b/HMCLauncher/HMCL/stdafx.h index b7aace52a3..05c9439e66 100644 --- a/HMCLauncher/HMCL/stdafx.h +++ b/HMCLauncher/HMCL/stdafx.h @@ -3,5 +3,6 @@ #include "targetver.h" #include +#include #include -#include +#include \ No newline at end of file