diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..3bda29a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,129 @@ +name: Build All Platforms +on: + workflow_dispatch: + +jobs: + # --- Windows builds (MSBuild + vcpkg) --- + windows: + strategy: + matrix: + include: + - platform: x64 + artifact: winrar-keygen-x64 + - platform: Win32 + artifact: winrar-keygen-x86 + - platform: ARM64 + artifact: winrar-keygen-arm64 + name: Windows ${{ matrix.platform }} + runs-on: windows-latest + steps: + - uses: actions/checkout@v6.0.2 + + - name: Setup vcpkg + uses: lukka/run-vcpkg@v11 + with: + vcpkgGitCommitId: 'a34c873a9717a888f58dc05268dea15592c2f0ff' + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Build + run: | + msbuild winrar-keygen.sln /p:Configuration=Release /p:Platform=${{ matrix.platform }} /p:VcpkgEnableManifest=true + + - name: Prepare artifact + shell: pwsh + run: | + $src = "bin/${{ matrix.platform }}-Release/winrar-keygen.exe" + Copy-Item $src "${{ matrix.artifact }}.exe" + + - name: Upload artifact + uses: actions/upload-artifact@v6.0.0 + with: + name: ${{ matrix.artifact }} + path: ${{ matrix.artifact }}.exe + + # --- Linux x64 build (CMake) --- + linux-x64: + name: Linux x64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6.0.2 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgmp-dev + + - name: Build + run: | + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + cmake --build . --config Release + + - name: Prepare artifact + run: | + cp build/winrar-keygen winrar-keygen-linux-x64 + chmod +x winrar-keygen-linux-x64 + + - name: Upload artifact + uses: actions/upload-artifact@v6.0.0 + with: + name: winrar-keygen-linux-x64 + path: winrar-keygen-linux-x64 + + # --- Linux ARM64 build (native runner) --- + linux-arm64: + name: Linux ARM64 + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v6.0.2 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgmp-dev + + - name: Build + run: | + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + cmake --build . --config Release + + - name: Prepare artifact + run: | + cp build/winrar-keygen winrar-keygen-linux-arm64 + chmod +x winrar-keygen-linux-arm64 + + - name: Upload artifact + uses: actions/upload-artifact@v6.0.0 + with: + name: winrar-keygen-linux-arm64 + path: winrar-keygen-linux-arm64 + + # --- macOS ARM64 build (CMake) --- + macos: + name: macOS ARM64 + runs-on: macos-latest + steps: + - uses: actions/checkout@v6.0.2 + + - name: Install dependencies + run: brew install gmp + + - name: Build + run: | + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + cmake --build . --config Release + + - name: Prepare artifact + run: | + cp build/winrar-keygen winrar-keygen-macos-arm64 + chmod +x winrar-keygen-macos-arm64 + + - name: Upload artifact + uses: actions/upload-artifact@v6.0.0 + with: + name: winrar-keygen-macos-arm64 + path: winrar-keygen-macos-arm64 diff --git a/BigInteger.hpp b/BigInteger.hpp index b5d3b70..01749f3 100644 --- a/BigInteger.hpp +++ b/BigInteger.hpp @@ -1,7 +1,24 @@ #pragma once #include #include +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4146 4244 4267) +#endif #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// MPIR (used by vcpkg x86/x64) provides mpz_*_sx/mpz_*_ux for intmax_t. +// Standard GMP (used by ARM64 and Linux/macOS) does not; fall back to long variants. +#ifndef __MPIR_VERSION +#define mpz_init_set_sx(z, v) mpz_init_set_si((z), static_cast(v)) +#define mpz_init_set_ux(z, v) mpz_init_set_ui((z), static_cast(v)) +#define mpz_set_sx(z, v) mpz_set_si((z), static_cast(v)) +#define mpz_set_ux(z, v) mpz_set_ui((z), static_cast(v)) +#endif + #include #include #include @@ -300,11 +317,11 @@ public: } bool TestBit(size_t i) const noexcept { - return mpz_tstbit(_Value, i) != 0; + return mpz_tstbit(_Value, static_cast(i)) != 0; } void SetBit(size_t i) noexcept { - mpz_setbit(_Value, i); + mpz_setbit(_Value, static_cast(i)); } std::string ToString(size_t Base, bool LowerCase) const { diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..70c0eec --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.16) +project(winrar-keygen LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Version +set(APP_VERSION "4.1.0.0") + +add_executable(winrar-keygen + _tmain.cpp + BigInteger.hpp + EllipticCurveGF2m.hpp + GaloisField.hpp + Hasher.hpp + HasherCrc32Traits.hpp + HasherSha1Traits.hpp + WinRarConfig.hpp + WinRarKeygen.hpp +) + +target_compile_definitions(winrar-keygen PRIVATE + APP_VERSION="${APP_VERSION}" +) + +# Find GMP +find_path(GMP_INCLUDE_DIR gmp.h REQUIRED) +target_include_directories(winrar-keygen PRIVATE ${GMP_INCLUDE_DIR}) + +# macOS Universal Binary support +if(APPLE AND CMAKE_OSX_ARCHITECTURES) + message(STATUS "Building macOS Universal Binary: ${CMAKE_OSX_ARCHITECTURES}") +endif() + +# Static linking on Linux for portable binaries +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_library(GMP_STATIC_LIBRARY libgmp.a) + if(GMP_STATIC_LIBRARY) + target_link_libraries(winrar-keygen PRIVATE ${GMP_STATIC_LIBRARY}) + target_link_options(winrar-keygen PRIVATE -static-libgcc -static-libstdc++) + message(STATUS "Using static GMP: ${GMP_STATIC_LIBRARY}") + else() + find_library(GMP_LIBRARY gmp REQUIRED) + target_link_libraries(winrar-keygen PRIVATE ${GMP_LIBRARY}) + message(STATUS "Using dynamic GMP: ${GMP_LIBRARY}") + endif() +else() + find_library(GMP_LIBRARY gmp REQUIRED) + target_link_libraries(winrar-keygen PRIVATE ${GMP_LIBRARY}) +endif() diff --git a/HasherSha1Traits.hpp b/HasherSha1Traits.hpp index 2c2aead..6a961ff 100644 --- a/HasherSha1Traits.hpp +++ b/HasherSha1Traits.hpp @@ -149,5 +149,139 @@ public: } } }; -#endif +#else +#include +#include + +struct HasherSha1Traits { +public: + static constexpr size_t BlockSize = 512 / 8; + static constexpr size_t DigestSize = 160 / 8; + + struct DigestType { + uint8_t Bytes[DigestSize]; + }; + + struct ContextType { + uint32_t State[5]; + uint64_t Count; + uint8_t Buffer[64]; + + ContextType() noexcept : State{}, Count(0), Buffer{} {} + ContextType(const ContextType&) noexcept = default; + ContextType& operator=(const ContextType&) noexcept = default; + ContextType(ContextType&& Other) noexcept = default; + ContextType& operator=(ContextType&& Other) noexcept = default; + }; + +private: + static inline uint32_t RotateLeft(uint32_t x, int n) noexcept { + return (x << n) | (x >> (32 - n)); + } + + static void ProcessBlock(uint32_t state[5], const uint8_t block[64]) noexcept { + uint32_t w[80]; + for (int i = 0; i < 16; ++i) { + w[i] = (static_cast(block[i * 4]) << 24) | + (static_cast(block[i * 4 + 1]) << 16) | + (static_cast(block[i * 4 + 2]) << 8) | + (static_cast(block[i * 4 + 3])); + } + for (int i = 16; i < 80; ++i) { + w[i] = RotateLeft(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + } + + uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; + for (int i = 0; i < 80; ++i) { + uint32_t f, k; + if (i < 20) { f = (b & c) | (~b & d); k = 0x5A827999; } + else if (i < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1; } + else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; } + else { f = b ^ c ^ d; k = 0xCA62C1D6; } + uint32_t temp = RotateLeft(a, 5) + f + e + k + w[i]; + e = d; d = c; c = RotateLeft(b, 30); b = a; a = temp; + } + state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; + } + +public: + static inline ContextType ContextCreate() { + ContextType Ctx; + Ctx.State[0] = 0x67452301; + Ctx.State[1] = 0xEFCDAB89; + Ctx.State[2] = 0x98BADCFE; + Ctx.State[3] = 0x10325476; + Ctx.State[4] = 0xC3D2E1F0; + Ctx.Count = 0; + return Ctx; + } + + static inline ContextType ContextCreate(const void* lpBuffer, size_t cbBuffer) { + ContextType Ctx = ContextCreate(); + ContextUpdate(Ctx, lpBuffer, cbBuffer); + return Ctx; + } + + static inline ContextType ContextCopy(const ContextType& Ctx) { + return Ctx; + } + + static inline void ContextUpdate(ContextType& Ctx, const void* lpBuffer, size_t cbBuffer) { + auto data = reinterpret_cast(lpBuffer); + size_t bufferOffset = static_cast(Ctx.Count % 64); + Ctx.Count += cbBuffer; + + if (bufferOffset > 0) { + size_t toCopy = 64 - bufferOffset; + if (toCopy > cbBuffer) toCopy = cbBuffer; + std::memcpy(Ctx.Buffer + bufferOffset, data, toCopy); + data += toCopy; + cbBuffer -= toCopy; + bufferOffset += toCopy; + if (bufferOffset == 64) { + ProcessBlock(Ctx.State, Ctx.Buffer); + bufferOffset = 0; + } + } + + while (cbBuffer >= 64) { + ProcessBlock(Ctx.State, data); + data += 64; + cbBuffer -= 64; + } + + if (cbBuffer > 0) { + std::memcpy(Ctx.Buffer, data, cbBuffer); + } + } + + static inline void ContextEvaluate(const ContextType& Ctx, DigestType& Digest) { + ContextType tmp = Ctx; + uint64_t totalBits = tmp.Count * 8; + uint8_t pad = 0x80; + ContextUpdate(tmp, &pad, 1); + uint8_t zero = 0; + while (tmp.Count % 64 != 56) { + ContextUpdate(tmp, &zero, 1); + } + uint8_t lenBytes[8]; + for (int i = 7; i >= 0; --i) { + lenBytes[i] = static_cast(totalBits); + totalBits >>= 8; + } + ContextUpdate(tmp, lenBytes, 8); + for (int i = 0; i < 5; ++i) { + Digest.Bytes[i * 4] = static_cast(tmp.State[i] >> 24); + Digest.Bytes[i * 4 + 1] = static_cast(tmp.State[i] >> 16); + Digest.Bytes[i * 4 + 2] = static_cast(tmp.State[i] >> 8); + Digest.Bytes[i * 4 + 3] = static_cast(tmp.State[i]); + } + } + + static inline void ContextDestroy(ContextType& Ctx) noexcept { + std::memset(&Ctx, 0, sizeof(Ctx)); + } +}; + +#endif diff --git a/WinRarKeygen.hpp b/WinRarKeygen.hpp index e8aa1ab..093d10e 100644 --- a/WinRarKeygen.hpp +++ b/WinRarKeygen.hpp @@ -9,6 +9,12 @@ #include #include +#ifdef _MSC_VER +#define BSWAP32(x) _byteswap_ulong(x) +#else +#define BSWAP32(x) __builtin_bswap32(x) +#endif + template class WinRarKeygen { public: @@ -40,7 +46,7 @@ private: Sha1Digest = Sha1.Evaluate(); for (unsigned i = 0; i < 5; ++i) { - Generator[i + 1] = _byteswap_ulong(reinterpret_cast(Sha1Digest.Bytes)[i]); + Generator[i + 1] = BSWAP32(reinterpret_cast(Sha1Digest.Bytes)[i]); } } else { Generator[1] = 0xeb3eb781; @@ -59,7 +65,7 @@ private: Sha1Digest = Sha1.Evaluate(); RawPrivateKey[i] = static_cast( - _byteswap_ulong(reinterpret_cast(Sha1Digest.Bytes)[0]) + BSWAP32(reinterpret_cast(Sha1Digest.Bytes)[0]) ); } @@ -112,7 +118,7 @@ private: HasherSha1Traits::DigestType Sha1Digest = Sha1.Evaluate(); for (size_t i = 0; i < 5; ++i) { - RawHash[i] = _byteswap_ulong(reinterpret_cast(Sha1Digest.Bytes)[i]); + RawHash[i] = BSWAP32(reinterpret_cast(Sha1Digest.Bytes)[i]); } // SHA1("") with all-zeroed initial value diff --git a/_tmain.cpp b/_tmain.cpp index 55934c6..aafb939 100644 --- a/_tmain.cpp +++ b/_tmain.cpp @@ -1,17 +1,35 @@ +#ifdef _WIN32 #include +#include #include #include +#else +#include +#include +#include +#include +#include +#include +#endif + #include #include #include #include -#include #include "WinRarConfig.hpp" #include "WinRarKeygen.hpp" -#include +#include +#ifdef _WIN32 #pragma comment(lib, "Version.lib") +#pragma comment(lib, "winhttp.lib") +#endif +#ifndef APP_VERSION +#define APP_VERSION "4.1.0.0" +#endif + +#ifdef _WIN32 std::string WideToUtf8(const std::wstring& wstr) { if (wstr.empty()) return {}; int size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr); @@ -43,20 +61,30 @@ std::string WideToAnsi(const std::wstring& wstr) { &result[0], size, nullptr, nullptr); return result; } +#endif enum class Encoding { ASCII, ANSI, UTF8 }; struct Options { +#ifdef _WIN32 std::wstring username; std::wstring license; Encoding encoding = Encoding::UTF8; std::wstring outputFile = L"rarreg.key"; +#else + std::string username; + std::string license; + Encoding encoding = Encoding::UTF8; + std::string outputFile = "rarreg.key"; +#endif bool textOnly = false; bool activate = false; bool showVersion = false; bool showHelp = false; + bool checkUpdate = false; }; +#ifdef _WIN32 bool ParseArguments(int argc, wchar_t* argv[], Options& opts) { std::vector positional; for (int i = 1; i < argc; ++i) { @@ -73,6 +101,9 @@ bool ParseArguments(int argc, wchar_t* argv[], Options& opts) { if (arg == L"-a" || arg == L"--activate") { opts.activate = true; continue; } + if (arg == L"-u" || arg == L"--update") { + opts.checkUpdate = true; return true; + } if (arg == L"-e" || arg == L"--encoding") { if (++i >= argc) { std::wcerr << L"Error: Missing value for " << arg << L"\n"; @@ -114,7 +145,70 @@ bool ParseArguments(int argc, wchar_t* argv[], Options& opts) { << positional.size() << L"\n"; return false; } +#else +bool ParseArguments(int argc, char* argv[], Options& opts) { + std::vector positional; + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "-v" || arg == "--version" || strcasecmp(arg.c_str(), "ver") == 0) { + opts.showVersion = true; return true; + } + if (arg == "-h" || arg == "--help" || strcasecmp(arg.c_str(), "help") == 0) { + opts.showHelp = true; return true; + } + if (arg == "-t" || arg == "--text") { + opts.textOnly = true; continue; + } + if (arg == "-a" || arg == "--activate") { + opts.activate = true; continue; + } + if (arg == "-u" || arg == "--update") { + opts.checkUpdate = true; return true; + } + if (arg == "-e" || arg == "--encoding") { + if (++i >= argc) { + std::cerr << "Error: Missing value for " << arg << "\n"; + return false; + } + std::string val = argv[i]; + if (strcasecmp(val.c_str(), "ascii") == 0) opts.encoding = Encoding::ASCII; + else if (strcasecmp(val.c_str(), "ansi") == 0) opts.encoding = Encoding::ANSI; + else if (strcasecmp(val.c_str(), "utf8") == 0 || strcasecmp(val.c_str(), "utf-8") == 0) + opts.encoding = Encoding::UTF8; + else { + std::cerr << "Error: Unknown encoding '" << val << "'. Use: ascii, ansi, utf8\n"; + return false; + } + continue; + } + if (arg == "-o" || arg == "--output") { + if (++i >= argc) { + std::cerr << "Error: Missing value for " << arg << "\n"; + return false; + } + opts.outputFile = argv[i]; continue; + } + if (!arg.empty() && arg[0] == '-') { + std::cerr << "Error: Unknown option '" << arg << "'\n"; + return false; + } + positional.push_back(arg); + } + if (positional.size() == 2) { + opts.username = positional[0]; + opts.license = positional[1]; + return true; + } + if (positional.empty()) { + opts.showHelp = true; return true; + } + std::cerr << "Error: Expected 2 arguments (Username, LicenseName), got " + << positional.size() << "\n"; + return false; +} +#endif +#ifdef _WIN32 void ShowHelp(const std::wstring& version) { std::wcout << L"WinRAR Keygen v" << version << L"\n\n"; std::wcout << L"Usage:\n"; @@ -126,17 +220,43 @@ void ShowHelp(const std::wstring& version) { std::wcout << L" -o, --output Output file (default: rarreg.key)\n"; std::wcout << L" -a, --activate Write to %APPDATA%\\WinRAR\\rarreg.key\n"; std::wcout << L" -t, --text Print to console only, don't write file\n"; + std::wcout << L" -u, --update Check for updates on GitHub\n"; std::wcout << L" -v, --version Show version\n"; std::wcout << L" -h, --help Show this help\n\n"; std::wcout << L"Examples:\n"; std::wcout << L" winrar-keygen.exe \"Github\" \"Single PC usage license\"\n"; - std::wcout << L" winrar-keygen.exe \"Github\" \"Single PC usage license\" -e ansi\n"; - std::wcout << L" winrar-keygen.exe \"YourName\" \"Single PC usage license\" -e utf8\n"; - std::wcout << L" winrar-keygen.exe \"Github\" \"Single PC usage license\" -o \"D:\\keys\\rarreg.key\"\n"; - std::wcout << L" winrar-keygen.exe \"Github\" \"Single PC usage license\" --activate\n"; + std::wcout << L" winrar-keygen.exe \"Github\" \"Single PC usage license\" -e ascii\n"; + std::wcout << L" winrar-keygen.exe \"Github\" \"Single PC usage license\" -a\n"; std::wcout << L" winrar-keygen.exe \"Github\" \"Single PC usage license\" -t\n"; } +#else +void ShowHelp(const std::string& version) { + std::cout << "WinRAR Keygen v" << version << "\n\n"; + std::cout << "Usage:\n"; + std::cout << " winrar-keygen [options]\n"; + std::cout << " winrar-keygen -v | --version\n"; + std::cout << " winrar-keygen -h | --help\n\n"; + std::cout << "Options:\n"; + std::cout << " -e, --encoding utf8 (default), ascii\n"; + std::cout << " -o, --output Output file (default: rarreg.key)\n"; +#ifdef __APPLE__ + std::cout << " -a, --activate Write to ~/Library/Application Support/com.rarlab.WinRAR/rarreg.key\n"; +#else + std::cout << " -a, --activate Write to ~/.rarkey\n"; +#endif + std::cout << " -t, --text Print to console only, don't write file\n"; + std::cout << " -u, --update Check for updates on GitHub\n"; + std::cout << " -v, --version Show version\n"; + std::cout << " -h, --help Show this help\n\n"; + std::cout << "Examples:\n"; + std::cout << " winrar-keygen \"Github\" \"Single PC usage license\"\n"; + std::cout << " winrar-keygen \"Github\" \"Single PC usage license\" -e ascii\n"; + std::cout << " winrar-keygen \"Github\" \"Single PC usage license\" -a\n"; + std::cout << " winrar-keygen \"Github\" \"Single PC usage license\" -t\n"; +} +#endif +#ifdef _WIN32 std::wstring GetExecutableVersion() { wchar_t exePath[MAX_PATH] = {}; DWORD pathLen = GetModuleFileNameW(nullptr, exePath, MAX_PATH); @@ -169,7 +289,13 @@ std::wstring GetExecutableVersion() { std::to_wstring(HIWORD(fixedInfo->dwFileVersionLS)) + L"." + std::to_wstring(LOWORD(fixedInfo->dwFileVersionLS)); } +#else +std::string GetAppVersion() { + return APP_VERSION; +} +#endif +#ifdef _WIN32 void PrintRegisterInfo(const WinRarKeygen::RegisterInfo& Info, const std::wstring& wUser, const std::wstring& wLicense) { std::wstring uid = Utf8ToWide(Info.UID); @@ -184,6 +310,19 @@ void PrintRegisterInfo(const WinRarKeygen::RegisterInfo& Info, std::wcout << data.substr(i, 54) << L"\n"; } } +#else +void PrintRegisterInfo(const WinRarKeygen::RegisterInfo& Info, + const std::string& user, const std::string& license) { + std::cout << "RAR registration data\n"; + std::cout << user << "\n"; + std::cout << license << "\n"; + std::cout << "UID=" << Info.UID << "\n"; + + for (size_t i = 0; i < Info.HexData.length(); i += 54) { + std::cout << Info.HexData.substr(i, 54) << "\n"; + } +} +#endif std::string BuildRegFileContent(const WinRarKeygen::RegisterInfo& Info) { std::string s; @@ -197,6 +336,7 @@ std::string BuildRegFileContent(const WinRarKeygen::RegisterInfo& return s; } +#ifdef _WIN32 bool WriteRegFile(const std::wstring& filePath, const std::string& content) { FILE* fp = nullptr; if (_wfopen_s(&fp, filePath.c_str(), L"wb") != 0 || !fp) @@ -205,7 +345,17 @@ bool WriteRegFile(const std::wstring& filePath, const std::string& content) { fclose(fp); return written == content.size(); } +#else +bool WriteRegFile(const std::string& filePath, const std::string& content) { + FILE* fp = fopen(filePath.c_str(), "wb"); + if (!fp) return false; + size_t written = fwrite(content.data(), 1, content.size(), fp); + fclose(fp); + return written == content.size(); +} +#endif +#ifdef _WIN32 bool IsConsoleHandle(HANDLE handle) { if (handle == nullptr || handle == INVALID_HANDLE_VALUE) { return false; @@ -238,7 +388,187 @@ void ConfigureConsoleOutput() { std::wcerr << L"Failed to set stderr _O_U8TEXT\n"; } } +#endif +struct Version { + int major = 0, minor = 0, patch = 0; + bool valid = false; +}; + +Version ParseVersion(const std::string& verStr) { + Version v; + std::string s = verStr; + if (!s.empty() && (s[0] == 'v' || s[0] == 'V')) s = s.substr(1); +#ifdef _MSC_VER + int count = sscanf_s(s.c_str(), "%d.%d.%d", &v.major, &v.minor, &v.patch); +#else + int count = sscanf(s.c_str(), "%d.%d.%d", &v.major, &v.minor, &v.patch); +#endif + v.valid = (count >= 2); + return v; +} + +bool IsNewer(const Version& remote, const Version& local) { + if (remote.major != local.major) return remote.major > local.major; + if (remote.minor != local.minor) return remote.minor > local.minor; + return remote.patch > local.patch; +} + +std::string ExtractTagFromJson(const std::string& responseBody) { + std::string tagKey = "\"tag_name\""; + size_t pos = responseBody.find(tagKey); + if (pos == std::string::npos) return ""; + pos = responseBody.find('\"', pos + tagKey.length()); + if (pos == std::string::npos) return ""; + size_t end = responseBody.find('\"', pos + 1); + if (end == std::string::npos) return ""; + return responseBody.substr(pos + 1, end - pos - 1); +} + +int CompareAndPrintUpdate(const std::string& currentVersion, const std::string& remoteTag) { + Version local = ParseVersion(currentVersion); + Version remote = ParseVersion(remoteTag); + + if (!remote.valid) { +#ifdef _WIN32 + std::wcerr << L"Error: Could not parse remote version '" << Utf8ToWide(remoteTag) << L"'.\n"; +#else + std::cerr << "Error: Could not parse remote version '" << remoteTag << "'.\n"; +#endif + return -1; + } + + if (IsNewer(remote, local)) { +#ifdef _WIN32 + std::wcout << L"\n New version available: " << Utf8ToWide(remoteTag) + << L" (current: v" << Utf8ToWide(currentVersion) << L")\n"; + std::wcout << L" Download: https://github.com/bitcookies/winrar-keygen/releases/latest\n\n"; +#else + std::cout << "\n New version available: " << remoteTag + << " (current: v" << currentVersion << ")\n"; + std::cout << " Download: https://github.com/bitcookies/winrar-keygen/releases/latest\n\n"; +#endif + } else { +#ifdef _WIN32 + std::wcout << L"Already up to date. (v" << Utf8ToWide(currentVersion) << L")\n"; +#else + std::cout << "Already up to date. (v" << currentVersion << ")\n"; +#endif + } + + return 0; +} + +#ifdef _WIN32 +int CheckForUpdate(const std::string& currentVersion) { + std::wcout << L"Checking for updates...\n"; + + HINTERNET hSession = WinHttpOpen(L"winrar-keygen-updater", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + if (!hSession) { + std::wcerr << L"Error: Failed to initialize WinHTTP.\n"; + return -1; + } + + HINTERNET hConnect = WinHttpConnect(hSession, L"api.github.com", + INTERNET_DEFAULT_HTTPS_PORT, 0); + if (!hConnect) { + std::wcerr << L"Error: Failed to connect to api.github.com.\n"; + WinHttpCloseHandle(hSession); + return -1; + } + + HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", + L"/repos/bitcookies/winrar-keygen/releases/latest", + nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); + if (!hRequest) { + std::wcerr << L"Error: Failed to create HTTP request.\n"; + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return -1; + } + + BOOL bResult = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, 0, 0); + if (!bResult) { + std::wcerr << L"Error: Failed to send HTTP request. Check your network connection.\n"; + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return -1; + } + + bResult = WinHttpReceiveResponse(hRequest, nullptr); + if (!bResult) { + std::wcerr << L"Error: No response from GitHub API.\n"; + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return -1; + } + + std::string responseBody; + DWORD dwSize = 0; + DWORD dwDownloaded = 0; + do { + dwSize = 0; + WinHttpQueryDataAvailable(hRequest, &dwSize); + if (dwSize == 0) break; + std::vector buffer(dwSize); + WinHttpReadData(hRequest, buffer.data(), dwSize, &dwDownloaded); + responseBody.append(buffer.data(), dwDownloaded); + } while (dwSize > 0); + + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + + if (responseBody.empty()) { + std::wcerr << L"Error: Empty response from GitHub API.\n"; + return -1; + } + + std::string remoteTag = ExtractTagFromJson(responseBody); + if (remoteTag.empty()) { + std::wcerr << L"Error: Could not find version info in GitHub response.\n"; + return -1; + } + + return CompareAndPrintUpdate(currentVersion, remoteTag); +} +#else +int CheckForUpdate(const std::string& currentVersion) { + std::cout << "Checking for updates...\n"; + + FILE* pipe = popen("curl -s https://api.github.com/repos/bitcookies/winrar-keygen/releases/latest 2>/dev/null", "r"); + if (!pipe) { + std::cerr << "Error: Failed to run curl. Make sure curl is installed.\n"; + return -1; + } + + std::string responseBody; + char buf[4096]; + while (fgets(buf, sizeof(buf), pipe)) { + responseBody += buf; + } + int status = pclose(pipe); + + if (status != 0 || responseBody.empty()) { + std::cerr << "Error: Failed to fetch update info. Check your network connection.\n"; + return -1; + } + + std::string remoteTag = ExtractTagFromJson(responseBody); + if (remoteTag.empty()) { + std::cerr << "Error: Could not find version info in GitHub response.\n"; + return -1; + } + + return CompareAndPrintUpdate(currentVersion, remoteTag); +} +#endif + +#ifdef _WIN32 int wmain(int argc, wchar_t* argv[]) { ConfigureConsoleOutput(); @@ -248,6 +578,7 @@ int wmain(int argc, wchar_t* argv[]) { } std::wstring version = GetExecutableVersion(); + std::string versionUtf8 = WideToUtf8(version); if (opts.showVersion) { std::wcout << L"winrar-keygen v" << version << L"\n"; @@ -257,6 +588,9 @@ int wmain(int argc, wchar_t* argv[]) { ShowHelp(version); return 0; } + if (opts.checkUpdate) { + return CheckForUpdate(versionUtf8); + } if (opts.activate && opts.outputFile != L"rarreg.key") { std::wcerr << L"Error: --activate and -o cannot be used together.\n"; @@ -271,7 +605,7 @@ int wmain(int argc, wchar_t* argv[]) { wchar_t appdata[MAX_PATH] = {}; DWORD len = ExpandEnvironmentStringsW(L"%APPDATA%\\WinRAR", appdata, MAX_PATH); if (len == 0 || len > MAX_PATH) { - std::wcerr << L"Error: Failed to resolve %%APPDATA%% path.\n"; + std::wcerr << L"Error: Failed to resolve %APPDATA% path.\n"; return -1; } CreateDirectoryW(appdata, nullptr); @@ -355,3 +689,132 @@ int wmain(int argc, wchar_t* argv[]) { return 0; } +#else +int main(int argc, char* argv[]) { + Options opts; + if (!ParseArguments(argc, argv, opts)) { + return -1; + } + + std::string version = GetAppVersion(); + + if (opts.showVersion) { + std::cout << "winrar-keygen v" << version << "\n"; + return 0; + } + if (opts.showHelp) { + ShowHelp(version); + return 0; + } + if (opts.checkUpdate) { + return CheckForUpdate(version); + } + + if (opts.activate && opts.outputFile != "rarreg.key") { + std::cerr << "Error: --activate and -o cannot be used together.\n"; + return -1; + } + if (opts.activate && opts.textOnly) { + std::cerr << "Error: --activate and -t cannot be used together.\n"; + return -1; + } + + if (opts.activate) { +#ifdef __APPLE__ + const char* home = getenv("HOME"); + if (!home) { + std::cerr << "Error: Failed to resolve $HOME path.\n"; + return -1; + } + std::string activateDir = std::string(home) + "/Library/Application Support/com.rarlab.WinRAR"; + mkdir(activateDir.c_str(), 0755); + opts.outputFile = activateDir + "/rarreg.key"; +#else + const char* home = getenv("HOME"); + if (!home) { + std::cerr << "Error: Failed to resolve $HOME path.\n"; + return -1; + } + opts.outputFile = std::string(home) + "/.rarkey"; +#endif + } + + try { + if (opts.username.empty() || opts.license.empty()) { + std::cerr << "Error: Username and License Name must not be empty.\n"; + return -1; + } + if (opts.username.length() > 200 || opts.license.length() > 200) { + std::cerr << "Error: Username and License Name must not exceed 200 characters.\n"; + return -1; + } + + std::string displayUser = opts.username; + std::string displayLicense = opts.license; + std::string user, license; + + switch (opts.encoding) { + case Encoding::UTF8: { + auto hasNonAscii = [](const std::string& s) { + for (unsigned char c : s) + if (c > 127) return true; + return false; + }; + if (hasNonAscii(displayUser) && + (displayUser.length() < 5 || displayUser.substr(0, 5) != "utf8:")) + displayUser = "utf8:" + displayUser; + if (hasNonAscii(displayLicense) && + (displayLicense.length() < 5 || displayLicense.substr(0, 5) != "utf8:")) + displayLicense = "utf8:" + displayLicense; + user = displayUser; + license = displayLicense; + break; + } + case Encoding::ANSI: + std::cerr << "Warning: ANSI encoding is not supported on this platform. Using UTF-8.\n"; + user = displayUser; + license = displayLicense; + break; + case Encoding::ASCII: + default: + user = displayUser; + license = displayLicense; + for (unsigned char c : user) + if (c > 127) + throw std::runtime_error( + "Username contains non-ASCII characters. Use '-e utf8'."); + for (unsigned char c : license) + if (c > 127) + throw std::runtime_error( + "License name contains non-ASCII characters. Use '-e utf8'."); + break; + } + + auto Info = WinRarKeygen::GenerateRegisterInfo(user.c_str(), license.c_str()); + + if (opts.textOnly) { + PrintRegisterInfo(Info, displayUser, displayLicense); + } else { + std::string content = BuildRegFileContent(Info); + if (!WriteRegFile(opts.outputFile, content)) { + std::cerr << "Error: Failed to write file: " << opts.outputFile << "\n"; + return -1; + } + + const char* encName = (opts.encoding == Encoding::UTF8) ? "UTF-8" : + (opts.encoding == Encoding::ANSI) ? "ANSI" : "ASCII"; + + std::cout << "\n"; + PrintRegisterInfo(Info, displayUser, displayLicense); + std::cout << "\nDone! " << opts.outputFile << " has been generated. (" + << encName << ")\n"; + } + } + catch (std::exception& e) { + std::cerr << "Error: " << e.what() << "\n"; + return -1; + } + + return 0; +} +#endif diff --git a/bin/Win32-Release/winrar-keygen.exe b/bin/Win32-Release/winrar-keygen.exe deleted file mode 100644 index dbac7f5..0000000 Binary files a/bin/Win32-Release/winrar-keygen.exe and /dev/null differ diff --git a/bin/x64-Release/winrar-keygen.exe b/bin/x64-Release/winrar-keygen.exe index 8f1d499..904ef5e 100644 Binary files a/bin/x64-Release/winrar-keygen.exe and b/bin/x64-Release/winrar-keygen.exe differ diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..10a9a0f --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,7 @@ +{ + "name": "winrar-keygen", + "version": "4.1.0.0", + "dependencies": [ + "gmp" + ] +} diff --git a/winrar-keygen.rc b/winrar-keygen.rc index 405560d..a2d9441 100644 --- a/winrar-keygen.rc +++ b/winrar-keygen.rc @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,0,1,0 - PRODUCTVERSION 4,0,1,0 + FILEVERSION 4,1,0,0 + PRODUCTVERSION 4,1,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BEGIN VALUE "CompanyName", "WinRAR Keygen" VALUE "FileDescription", "WinRAR Key Generation Tool" - VALUE "FileVersion", "4.0.1.0" + VALUE "FileVersion", "4.1.0.0" VALUE "InternalName", "winrar-keygen.exe" VALUE "LegalCopyright", "Copyright (C) 2021 Bitcookies" VALUE "OriginalFilename", "winrar-keygen.exe" VALUE "ProductName", "WinRAR Keygen" - VALUE "ProductVersion", "4.0.1.0" + VALUE "ProductVersion", "4.1.0.0" END END BLOCK "VarFileInfo" diff --git a/winrar-keygen.sln b/winrar-keygen.sln index d5ffe5e..f86b3ef 100644 --- a/winrar-keygen.sln +++ b/winrar-keygen.sln @@ -1,14 +1,16 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29009.5 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.37111.16 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winrar-keygen", "winrar-keygen.vcxproj", "{2443AA55-9534-4451-9BCC-48AC0982A0CC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 + Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection @@ -21,6 +23,10 @@ Global {2443AA55-9534-4451-9BCC-48AC0982A0CC}.Release|x64.Build.0 = Release|x64 {2443AA55-9534-4451-9BCC-48AC0982A0CC}.Release|x86.ActiveCfg = Release|Win32 {2443AA55-9534-4451-9BCC-48AC0982A0CC}.Release|x86.Build.0 = Release|Win32 + {2443AA55-9534-4451-9BCC-48AC0982A0CC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2443AA55-9534-4451-9BCC-48AC0982A0CC}.Debug|ARM64.Build.0 = Debug|ARM64 + {2443AA55-9534-4451-9BCC-48AC0982A0CC}.Release|ARM64.ActiveCfg = Release|ARM64 + {2443AA55-9534-4451-9BCC-48AC0982A0CC}.Release|ARM64.Build.0 = Release|ARM64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/winrar-keygen.vcxproj b/winrar-keygen.vcxproj index cb4ac3e..e0d14e1 100644 --- a/winrar-keygen.vcxproj +++ b/winrar-keygen.vcxproj @@ -17,6 +17,14 @@ Release x64 + + Debug + ARM64 + + + Release + ARM64 + 16.0 @@ -26,6 +34,7 @@ 10.0 x86-windows-static x64-windows-static + arm64-windows-static @@ -58,6 +67,21 @@ Unicode false + + Application + true + v143 + Unicode + false + + + Application + false + v143 + true + Unicode + false + @@ -75,6 +99,12 @@ + + + + + + true @@ -96,6 +126,16 @@ $(SolutionDir)bin\$(Platform)-$(Configuration)\ $(SolutionDir)obj\$(Platform)-$(Configuration)\ + + true + $(SolutionDir)bin\$(Platform)-$(Configuration)\ + $(SolutionDir)obj\$(Platform)-$(Configuration)\ + + + false + $(SolutionDir)bin\$(Platform)-$(Configuration)\ + $(SolutionDir)obj\$(Platform)-$(Configuration)\ + Level3 @@ -157,6 +197,43 @@ true stdcpp17 MultiThreaded + /utf-8 %(AdditionalOptions) + + + Console + true + true + true + + + + + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + MultiThreadedDebug + /utf-8 %(AdditionalOptions) + + + Console + true + + + + + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + MultiThreaded + /utf-8 %(AdditionalOptions) Console