C++版clover支持安装net3.5,仅限于本地,因为下载站不会放那么大的包
大石头 authored at 2025-07-07 20:34:42
21.05 KiB
LuckyClover
// 指定为 Windows XP
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501

#include "BaseInclude.h"
#include "CDownloadFile.h"
#include "CGetOSVersion.h"
#include "CRegistryVisit.h"

using namespace std;

// 版本常量,必须与 Shared/NetVersions.cs 保持一致
#define NET6_VERSION  "6.0.36"
#define NET8_VERSION  "8.0.33"
#define NET10_VERSION "10.0.14"
#define NET11_VERSION "11.0.0"

static bool _silent = false;
static bool _force = false;
static bool _dryRun = false;
static string _server = "http://x.newlifex.com";
static string _serverPath = "/dotnet/";
static OSVersionInfo _os;

// 前置声明:这两个函数在很多位置被调用,定义见文件后部
static bool InstallPackage(const string& fileName, const string& args);
static string ExplainExitCode(DWORD code);

/// <summary>获取程序所在目录</summary>
static string GetExeDir()
{
    char path[MAX_PATH] = { 0 };
    GetModuleFileNameA(NULL, path, MAX_PATH);

    string file = path;
    auto p = file.find_last_of('\\');

    return p == string::npos ? "." : file.substr(0, p);
}

/// <summary>解析服务器地址,拆分为服务器与基准路径</summary>
static void ParseServerUrl(const string& url)
{
    auto line = Trim(url);
    if (line.empty()) return;

    auto pos = line.find("://");
    if (pos == string::npos)
    {
        _server = line;
        return;
    }

    auto slash = line.find('/', pos + 3);
    if (slash == string::npos)
    {
        _server = line;
        return;
    }

    _server = line.substr(0, slash);
    auto path = line.substr(slash);
    if (path.empty()) return;
    if (path[path.size() - 1] != '/') path += '/';

    _serverPath = path;
}

/// <summary>读取本目录 server.txt 中的服务器地址</summary>
static void LoadServerConfig()
{
    auto file = GetExeDir() + "\\server.txt";
    ifstream infile(file.c_str());
    if (!infile) return;

    string line;
    if (!getline(infile, line)) return;

    ParseServerUrl(line);
}

/// <summary>下载文件,已存在则直接返回本地路径</summary>
static string DownloadFile(const string& fileName, const string& remotePath)
{
    auto file = GetExeDir() + "\\" + fileName;

    // 已存在且非空则直接使用,空文件(可能是中断留下的)重新下载
    WIN32_FILE_ATTRIBUTE_DATA fad;
    if (GetFileAttributesExA(file.c_str(), GetFileExInfoStandard, &fad) &&
        (fad.nFileSizeHigh > 0 || fad.nFileSizeLow > 0)) return file;

    cout << "下载:" << _server << remotePath << endl;
    cout << "保存:" << file << endl;

    CDownloadFile cDownFile;
    for (auto i = 0; i < 15; i++)
    {
        if (cDownFile.Download(file, _server, remotePath)) return file;

        // 沉默1秒后重试
        Sleep(1000);
    }

    return "";
}

/// <summary>执行进程并等待退出,必要时通过 runas 触发 UAC</summary>
static bool RunProcess(const string& file, const string& args)
{
    if (file.empty()) return false;

    cout << "执行:" << file << " " << args << endl;

    STARTUPINFOA si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    auto cmdLine = "\"" + file + "\"";
    if (!args.empty()) cmdLine += " " + args;

    auto ok = CreateProcessA(NULL, &cmdLine[0], NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
    auto err = GetLastError();

    if (!ok && err == ERROR_ELEVATION_REQUIRED)
    {
        // 安装程序要求管理员权限时,通过 runas 弹出UAC确认
        SHELLEXECUTEINFOA sei;
        ZeroMemory(&sei, sizeof(sei));
        sei.cbSize = sizeof(sei);
        sei.fMask = SEE_MASK_NOCLOSEPROCESS;
        sei.lpVerb = "runas";
        sei.lpFile = file.c_str();
        sei.lpParameters = args.empty() ? NULL : args.c_str();
        sei.nShow = SW_SHOWNORMAL;

        if (!ShellExecuteExA(&sei))
        {
            cout << "启动安装程序失败:" << GetLastError() << endl;
            return false;
        }

        pi.hProcess = sei.hProcess;
    }
    else if (!ok)
    {
        cout << "启动安装程序失败:" << err << endl;
        return false;
    }

    WaitForSingleObject(pi.hProcess, INFINITE);

    DWORD code = 0;
    GetExitCodeProcess(pi.hProcess, &code);
    CloseHandle(pi.hProcess);
    if (pi.hThread != NULL) CloseHandle(pi.hThread);

    // 3010/1641 表示安装成功但需要重启
    if (code == 0 || code == 3010 || code == 1641)
    {
        cout << "安装完成!" << endl;
        return true;
    }

    cout << "安装失败!退出码:" << code << endl;

    auto tip = ExplainExitCode(code);
    if (!tip.empty()) cout << "原因:" << tip << endl;

    return false;
}

/// <summary>把安装器退出码翻译成可执行的建议</summary>
static string ExplainExitCode(DWORD code)
{
    switch (code)
    {
    case 5: return "拒绝访问,需要以管理员身份运行";
    case 1603: return "致命错误:XP/2003 常见原因是缺少 WIC(Windows Imaging Component),也可能是权限不足、源文件损坏或已存在更高版本";
    case 5100: return "系统中已存在更高版本的 .NET";
    case 1638: return "已安装同版本或更高版本";
    case 1618: return "另一个安装正在进行,请等待其完成后重试";
    default: return "";
    }
}

/// <summary>读取 PE 文件的目标架构(IMAGE_FILE_MACHINE_*),不是 PE 文件时返回 0</summary>
static DWORD GetPeMachine(const string& file)
{
    auto h = CreateFileA(file.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (h == INVALID_HANDLE_VALUE) return 0;

    DWORD machine = 0;
    BYTE head[1024];
    DWORD read = 0;
    if (ReadFile(h, head, sizeof(head), &read, NULL) && read >= 0x40 && head[0] == 'M' && head[1] == 'Z')
    {
        auto pe = *(DWORD*)(head + 0x3C);
        if (pe > 0 && pe + 6 <= read && head[pe] == 'P' && head[pe + 1] == 'E') machine = *(WORD*)(head + pe + 4);
    }
    CloseHandle(h);

    return machine;
}

/// <summary>校验安装包架构与本机是否匹配</summary>
static bool CheckArch(const string& file)
{
    auto machine = GetPeMachine(file);
    if (machine == 0) return true;

    auto is64 = machine == 0x8664 || machine == 0xAA64 || machine == 0x0200;
    auto name = machine == 0x014C ? "x86" : machine == 0x8664 ? "x64" : machine == 0xAA64 ? "arm64" : "ia64";

    // 架构不符时尽早报错,否则只会得到“不是有效的 Win32 应用程序”这类无效信息
    if (is64 && !_os.Is64Bit)
    {
        cout << "跳过 " << name << " 版安装包,当前系统只有 32 位:" << file << endl;

        return false;
    }

    return true;
}

/// <summary>XP/2003 安装 .NET4.0 的前置:Windows Imaging Component</summary>
static bool EnsureWic()
{
    char win[MAX_PATH] = { 0 };
    if (GetWindowsDirectoryA(win, MAX_PATH) > 0)
    {
        auto dll = string(win) + "\\System32\\WindowsCodecs.dll";
        if (GetFileAttributesA(dll.c_str()) != INVALID_FILE_ATTRIBUTES) return true;
    }

    auto file = _os.Is64Bit ? "wic_x64.exe" : "wic_x86.exe";
    cout << "缺少 WIC(Windows Imaging Component),先安装 " << file << endl;

    auto args = _silent ? string("/quiet /norestart") : string();
    if (InstallPackage(file, args)) return true;

    cout << "WIC 未能自动安装,.NET4.0 会以 1603 失败;请先手动安装 WIC 后重试" << endl;

    return false;
}

/// <summary>下载并安装一个安装包</summary>
static bool InstallPackage(const string& fileName, const string& args)
{
    auto file = DownloadFile(fileName, _serverPath + fileName);
    if (file.empty())
    {
        cout << "下载失败!" << endl;
        return false;
    }

    if (!CheckArch(file)) return false;

    return RunProcess(file, args);
}

/// <summary>判断核心运行时是否支持当前操作系统</summary>
static bool IsSupported(const string& version)
{
    int major = 0;
    sscanf_s(version.c_str(), "%d", &major);

    // Win10 1607 起支持所有核心运行时
    if (_os.Major >= 10) return _os.Build >= 14393;

    if (major == 6)
    {
        if (_os.Major != 6) return false;

        // .NET6 支持 Win7 SP1、Win8.1 与 Server 2012+,Win8 客户端不支持
        if (_os.Minor == 1) return _os.Build >= 7601;
        if (_os.Minor == 2) return _os.IsServer;

        return true;
    }

    // .NET8/10/11 仅支持 Server 2012/2012 R2 及以上
    if (major == 8 || major == 10 || major == 11) return _os.IsServer && _os.Major == 6 && _os.Minor >= 2;

    return false;
}

/// <summary>按操作系统获取自动安装的运行时列表</summary>
static vector<string> GetAutoTargets()
{
    vector<string> list;

    // Windows 2000/XP/2003 只支持 .NET Framework 2.0/4.0
    if (_os.Major <= 5)
    {
        list.push_back("net20");
        list.push_back("net40");
        return list;
    }

    // Vista/Server 2008,4.5.2 是最后支持的版本
    if (_os.Major == 6 && _os.Minor == 0)
    {
        list.push_back("net45");
        return list;
    }

    // Win7/Server 2008 R2,只有 SP1 才支持 .NET Core
    if (_os.Major == 6 && _os.Minor == 1)
    {
        list.push_back("net48");
        if (_os.Build >= 7601) list.push_back("net6");
        return list;
    }

    // Win8/8.1/Server 2012/2012 R2
    if (_os.Major == 6)
    {
        list.push_back("net48");

        // Win8(6.2)客户端不受 .NET Core 支持,Server 2012 受支持
        if (_os.Minor >= 3 || _os.IsServer) list.push_back("net6");

        return list;
    }

    // Win10 1607+/Win11/Server 2016+
    if (_os.Major >= 10 && _os.Build >= 14393)
    {
        list.push_back("net48");
        list.push_back("net10");
        return list;
    }

    list.push_back("net48");
    return list;
}

/// <summary>安装 .NET Framework 2.0</summary>
static bool InstallNet20(CRegistryVisit& reg)
{
    if (reg.CheckIsInstallNet(_T("2.0")))
    {
        cout << "已安装 .NET2.0" << endl;
        return true;
    }

    // .NET3.5 包含 2.0 运行时
    if (_os.Major >= 6 && reg.CheckIsInstallNet(_T("3.5")))
    {
        cout << "已通过 .NET3.5 提供 .NET2.0" << endl;
        return true;
    }

    if (_os.Major >= 6 && _os.Minor >= 2)
    {
        cout << "Windows 8 及以上请在“启用或关闭 Windows 功能”中安装 .NET Framework 3.5" << endl;
        return false;
    }

    if (_dryRun)
    {
        cout << "将安装 .NET2.0" << endl;
        return true;
    }

    // .NET2.0 SP2 要求 XP SP2 / 2003 SP1 及以上;低于该版本时安装器会长时间无响应,先给出结论
    if (_os.Major == 5)
    {
        auto need = _os.Minor == 1 ? 2 : 1;
        if (_os.ServicePack < need && !_force)
        {
            cout << "当前系统 Service Pack " << _os.ServicePack << " 低于 .NET2.0 要求的 SP" << need << ",请先安装系统补丁包,或加 -force 强制尝试" << endl;
            return false;
        }
    }

    // 64 位系统装 x64 包:x86 包会挂起在会话 0(实测 2003 R2 x64)
    auto file = _os.Is64Bit ? "NetFx20SP2_x64.exe" : "NetFx20SP2_x86.exe";

    return InstallPackage(file, _silent ? "/quiet /norestart" : "");
}

/// <summary>安装 .NET Framework 4.0</summary>
static bool InstallNet40(CRegistryVisit& reg)
{
    if (reg.CheckIsInstallNet(_T("4.0")))
    {
        cout << "已安装 .NET4.0" << endl;
        return true;
    }

    if (_dryRun)
    {
        cout << "将安装 .NET4.0" << endl;
        return true;
    }

    if (_os.Major <= 5)
    {
        // .NET4.0 官方要求 XP SP3 / 2003 SP2,低版本安装器会以 1603 失败
        auto need = _os.Minor == 1 ? 3 : 2;
        if (_os.ServicePack < need && !_force) cout << "注意:.NET4.0 要求 SP" << need << ",当前为 SP" << _os.ServicePack << ",安装可能失败" << endl;

        // XP/2003 上 .NET4.0 依赖 WIC,缺了必然 1603
        EnsureWic();
    }

    return InstallPackage("dotNetFx40_Full_x86_x64.exe", _silent ? "/passive /norestart" : "");
}

/// <summary>安装 .NET Framework 4.5.2</summary>
static bool InstallNet45(CRegistryVisit& reg)
{
    if (reg.CheckIsInstallNet(_T("4.5")))
    {
        cout << "已安装 .NET4.5" << endl;
        return true;
    }

    if (_os.Major <= 5)
    {
        cout << "Windows XP 最高支持 .NET4.0" << endl;
        return false;
    }

    if (_dryRun)
    {
        cout << "将安装 .NET4.5.2" << endl;
        return true;
    }

    // 语言包只需安装一个,中文优先,失败再回退英文版
    auto args = _silent ? string("/passive /norestart") : string();
    if (InstallPackage("NDP452-KB2901907-x86-x64-AllOS-CHS.exe", args)) return true;

    return InstallPackage("NDP452-KB2901907-x86-x64-AllOS-ENU.exe", args);
}

/// <summary>安装 .NET Framework 4.8/4.8.1</summary>
static bool InstallNet48(CRegistryVisit& reg)
{
    // win10/win11 中安装 .NET4.8.1
    auto isWin10 = _os.Major >= 10;
    auto target = isWin10 ? _T("4.8.1") : _T("4.8");
    if (reg.CheckIsInstallNet(target))
    {
        cout << "已安装 .NET" << (isWin10 ? "4.8.1" : "4.8") << endl;
        return true;
    }

    if (_os.Major <= 5 || (_os.Major == 6 && _os.Minor == 0))
    {
        cout << "当前系统最高支持 .NET4.5.2,请使用 net45" << endl;
        return false;
    }

    if (_dryRun)
    {
        cout << "将安装 .NET" << (isWin10 ? "4.8.1" : "4.8") << endl;
        return true;
    }

    auto args = _silent ? string("/passive /norestart /showfinalerror") : string();
    if (isWin10)
    {
        if (InstallPackage("ndp481-x86-x64-allos-chs.exe", args)) return true;
        return InstallPackage("ndp481-x86-x64-allos-enu.exe", args);
    }

    if (InstallPackage("ndp48-x86-x64-allos-chs.exe", args)) return true;
    return InstallPackage("ndp48-x86-x64-allos-enu.exe", args);
}

/// <summary>安装 .NET 核心运行时</summary>
static bool InstallNetCore(CRegistryVisit& reg, const string& version)
{
    int major = 0;
    sscanf_s(version.c_str(), "%d", &major);

    auto installed = reg.GetNetCoreVersion(major);
    if (!installed.empty() && !_force && !IsNewerVersion(version, installed))
    {
        cout << "已安装 .NET" << version << "(当前 " << installed << ")" << endl;
        return true;
    }

    if (!IsSupported(version))
    {
        cout << "当前系统不支持 .NET" << version << ",如需强制安装请加 -force" << endl;
        if (!_force) return false;
    }

    if (_dryRun)
    {
        cout << "将安装 .NET" << version << endl;
        return true;
    }

    auto arch = _os.Is64Bit ? "x64" : "x86";

    // win7需要vc运行时与UCRT补丁,失败不阻塞后续安装
    if (_os.Major == 6 && _os.Minor == 1)
    {
        auto kb = string("Windows6.1-KB3063858-") + arch + ".msu";
        auto kbFile = DownloadFile(kb, _serverPath + "win7/" + kb);
        if (!kbFile.empty()) RunProcess("wusa.exe", "\"" + kbFile + "\" /quiet /norestart");

        auto vc = string("VC_redist.") + arch + ".exe";
        auto vcFile = DownloadFile(vc, _serverPath + "vc/" + vc);
        if (!vcFile.empty()) RunProcess(vcFile, "/install /quiet /norestart");
    }

    auto args = _silent ? string("/install /quiet /norestart") : string();

    // 服务器安装托管捆绑包(含运行时与ASP.NET Core),客户端安装桌面运行时
    if (_os.IsServer)
    {
        auto file = "dotnet-hosting-" + version + "-win.exe";
        return InstallPackage(file, args);
    }
    else
    {
        auto file = "windowsdesktop-runtime-" + version + "-win-" + arch + ".exe";
        return InstallPackage(file, args);
    }
}

/// <summary>获取 .NET Framework 版本名称</summary>
static string GetNetFxName(DWORD release)
{
    if (release >= 533320) return "4.8.1";
    if (release >= 528040) return "4.8";
    if (release >= 461808) return "4.7.2";
    if (release >= 460798) return "4.7";
    if (release >= 394254) return "4.6.2";
    if (release >= 393295) return "4.6";
    if (release >= 379893) return "4.5.2";
    if (release >= 378389) return "4.5";

    return "";
}

/// <summary>打印已安装版本</summary>
static void PrintInstalled(CRegistryVisit& reg)
{
    cout << endl;
    cout << "已安装版本:" << endl;

    const char* versions[] = { "2.0", "3.5" };
    for (auto item : versions)
    {
        if (reg.CheckIsInstallNet(CString(item))) cout << "v" << item << endl;
    }

    auto release = reg.GetNetFxRelease();
    auto fx = GetNetFxName(release);
    if (!fx.empty())
        cout << "v" << fx << endl;
    else if (reg.CheckIsInstallNet(_T("4.0")))
        cout << "v4.0" << endl;

    const int majors[] = { 6, 8, 10, 11 };
    for (auto item : majors)
    {
        auto ver = reg.GetNetCoreVersion(item);
        if (!ver.empty()) cout << "v" << ver << endl;
    }
}

/// <summary>打印用法</summary>
static void PrintUsage()
{
    cout << "用法:clover.exe [命令] [选项]" << endl;
    cout << "  auto                 按操作系统自动选择(默认)" << endl;
    cout << "  net20|net40|net45|net48|net6|net8|net10|net11   安装指定运行时" << endl;
    cout << "  all                  安装当前系统支持的全部运行时" << endl;
    cout << "  list                 只列出已安装版本与自动安装计划" << endl;
    cout << "选项:" << endl;
    cout << "  -silent              静默安装" << endl;
    cout << "  -force               已安装也强制安装,并忽略系统支持性检查" << endl;
    cout << "  -dryrun              只打印将要执行的操作,不下载不安装" << endl;
    cout << "  -server <url>        指定下载服务器,如 http://x.newlifex.com" << endl;
    cout << "本地 server.txt 文件可设置下载服务器地址。" << endl;
}

/// <summary>解析命令行参数</summary>
static bool ParseArgs(int argc, char* argv[], string& cmd)
{
    cmd = "";

    for (auto i = 1; i < argc; i++)
    {
        string arg = argv[i];

        if (arg == "-silent" || arg == "--silent")
            _silent = true;
        else if (arg == "-force" || arg == "--force")
            _force = true;
        else if (arg == "-dryrun" || arg == "--dryrun" || arg == "-dry-run")
            _dryRun = true;
        else if (arg == "-help" || arg == "--help" || arg == "-?" || arg == "/?")
            cmd = "help";
        else if ((arg == "-server" || arg == "--server") && i + 1 < argc)
            ParseServerUrl(argv[++i]);
        else if (!arg.empty() && arg[0] == '-')
        {
            cout << "无法识别的参数:" << arg << endl;
            return false;
        }
        else if (cmd.empty())
            cmd = arg;
        else
        {
            cout << "多余的参数:" << arg << endl;
            return false;
        }
    }

    return true;
}

/// <summary>安装一个运行时</summary>
static bool InstallOne(const string& name, CRegistryVisit& reg)
{
    if (name == "net20" || name == "net2") return InstallNet20(reg);
    if (name == "net40" || name == "net4") return InstallNet40(reg);
    if (name == "net45") return InstallNet45(reg);
    if (name == "net48") return InstallNet48(reg);
    if (name == "net6") return InstallNetCore(reg, NET6_VERSION);
    if (name == "net8") return InstallNetCore(reg, NET8_VERSION);
    if (name == "net10") return InstallNetCore(reg, NET10_VERSION);
    if (name == "net11") return InstallNetCore(reg, NET11_VERSION);

    cout << "无法识别的运行时:" << name << endl;

    return false;
}

int main(int argc, char* argv[])
{
    //检测OS版本状态
    CGetOSVersion cOSVer;
    _os = cOSVer.GetVersion();

    LoadServerConfig();

    string cmd;
    if (!ParseArgs(argc, argv, cmd))
    {
        PrintUsage();
        return 2;
    }

    cout << "幸运草 Clover v1.2 NewLife" << endl;
    cout << "检测并安装主流.NET运行时。" << endl;
    cout << "操作系统: " << _os.Desc << (_os.Is64Bit ? "(64位)" : "(32位)") << endl;

    if (cmd == "help")
    {
        PrintUsage();
        return 0;
    }

    CRegistryVisit reg;
    PrintInstalled(reg);

    vector<string> targets;
    if (cmd.empty() || cmd == "auto" || cmd == "list")
        targets = GetAutoTargets();
    else if (cmd == "all")
    {
        const char* all[] = { "net20", "net40", "net45", "net48", "net6", "net8", "net10", "net11" };
        for (auto item : all) targets.push_back(item);
    }
    else
    {
        if (!(cmd == "net20" || cmd == "net2" || cmd == "net40" || cmd == "net4" || cmd == "net45" || cmd == "net48" ||
            cmd == "net6" || cmd == "net8" || cmd == "net10" || cmd == "net11"))
        {
            cout << "无法识别的命令:" << cmd << endl;
            PrintUsage();
            return 2;
        }

        targets.push_back(cmd);
    }

    if (cmd == "list")
    {
        cout << endl;
        cout << "自动安装计划:" << endl;
        for (auto item : targets) cout << "  " << item << endl;
        return 0;
    }

    cout << endl;
    cout << "目标运行时:" << endl;
    for (auto item : targets) cout << "  " << item << endl;

    auto failures = 0;
    for (auto item : targets)
    {
        cout << endl;
        cout << "===== " << item << " =====" << endl;

        if (!InstallOne(item, reg)) failures++;
    }

    cout << endl;
    if (failures == 0)
    {
        cout << "全部完成!" << endl;
        return 0;
    }

    cout << failures << " 个组件安装失败!" << endl;

    return 1;
}