Skip to content

fix(mem-swap): 修复无法在无管理员权限下调用的问题#2587

Merged
ruattd merged 20 commits intodevfrom
fix/mem-swap
Apr 9, 2026
Merged

fix(mem-swap): 修复无法在无管理员权限下调用的问题#2587
ruattd merged 20 commits intodevfrom
fix/mem-swap

Conversation

@tangge233
Copy link
Copy Markdown
Contributor

@tangge233 tangge233 commented Mar 10, 2026

由 Sourcery 生成的摘要

重构并将内存优化功能集中到专用的 MemSwap 服务中,该服务可在具有或不具有管理员权限的情况下调用,同时改进大小格式化工具并提升进程启动的可靠性。

新功能:

  • 引入具有可配置交换范围(swap scopes)的 MemSwap 服务,用于执行系统内存优化操作,并对外提供可复用的 API。
  • 在 ByteStream 中新增统一的字节大小格式化辅助工具,支持多种单位和负值。

错误修复:

  • 通过正确启动具有适当 Shell 执行设置的提升权限进程,修复在非管理员权限下运行时的内存优化调用问题。
  • 确保在解析嵌套子命令时,正确构建没有前导点的命令前缀。
  • 防止在构建自定义下载任务时使用错误的 loader 变量。
  • 通过收紧已释放内存的上报阈值,避免在内存变化不显著时仍报告“内存优化成功”。

改进:

  • 在整个应用中复用共享的 ByteStream 大小格式化逻辑,替换各处自定义实现。
  • 改进与内存优化相关的日志记录和退出码处理,更好地报告优化前/后的内存使用情况及错误信息。
  • 使用信号量保护内存交换操作以避免并发运行,并通过作用域化、基于标志位的控制来决定执行哪些内存操作。
  • 清理遗留的互操作结构体和 UI 代码中的内联内存操作,将其移入专用的工具类中。
  • 规范 UI 处理程序中的异步调用和参数传递等细微代码风格问题。
Original summary in English

Summary by Sourcery

Refactor and centralize the memory optimization feature into a dedicated MemSwap service that can be invoked with or without admin privileges while improving size formatting utilities and process startup reliability.

New Features:

  • Introduce a MemSwap service with configurable swap scopes to perform system memory optimization operations and expose a reusable API.
  • Add a unified byte-size formatting helper in ByteStream that supports multiple units and negative values.

Bug Fixes:

  • Fix memory optimization invocation when running without administrator privileges by correctly starting an elevated process with proper shell execution settings.
  • Ensure command parsing for nested subcommands correctly builds the command prefix without leading dots.
  • Prevent using an incorrect loader variable when constructing custom download tasks.
  • Avoid reporting successful memory optimization for insignificant changes by tightening the threshold on reported freed memory.

Enhancements:

  • Reuse the shared ByteStream size formatting logic throughout the app, replacing custom implementations.
  • Improve logging and exit-code handling around memory optimization to better report before/after memory usage and errors.
  • Guard memory swap operations with a semaphore to avoid concurrent runs and add scoped, flag-based control over which memory operations are executed.
  • Clean up legacy interop structs and inline memory operations from UI code by moving them into dedicated utility classes.
  • Normalize minor code style issues in async calls and argument passing in UI handlers.

@pcl-ce-automation pcl-ce-automation bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 size: L PR 大小评估:大型 labels Mar 10, 2026
Copy link
Copy Markdown
Contributor

@ruattd ruattd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 用基于 RPC 的 LifecycleCommandHandler 而不是自己处理参数,申请管理员的特性把 PromoteService 改造一下复用已存在的权限,另外注意改权限不要影响其他线程
  2. 不要在 Essentials 里面新建文件夹了,以及这种非核心服务不适合丢这里(考虑在 App 里面加一个 Tools?
  3. 这点东西怎么能造出来 3 个文件的
  4. 有一吨无意义的更改,你是不是应该考虑丢掉 VS 这个垃圾

@tangge233
Copy link
Copy Markdown
Contributor Author

3: 一个类型一个文件 qwq
4: 那些是 UTF8 with BOM 改到 UTF8

@tangge233
Copy link
Copy Markdown
Contributor Author

LifeCycleCommandHandler 会通过 RPC 把调用传回原程序(没权限),这对吗😰

@tangge233 tangge233 marked this pull request as draft March 16, 2026 06:11
@pcl-ce-automation pcl-ce-automation bot added 🚧 正在处理 开发人员正在对该内容进行开发、测试或修复,进展中 and removed 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 labels Mar 16, 2026
@ruattd
Copy link
Copy Markdown
Contributor

ruattd commented Mar 16, 2026

这对的,应该在原程序里面走线程提权并复用,而不是每次都现场跟用户申请权限

@tangge233 tangge233 marked this pull request as ready for review April 6, 2026 06:07
@pcl-ce-automation pcl-ce-automation bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 and removed 🚧 正在处理 开发人员正在对该内容进行开发、测试或修复,进展中 labels Apr 6, 2026
@tangge233
Copy link
Copy Markdown
Contributor Author

@ruattd MemSwap 暂时先这样,等 LifecycleCommandHandler 优化好了后面再改吧

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 6, 2026

Reviewer's Guide

将内存优化(mem-swap)功能重构为一个独立的生命周期服务,提供正确的权限处理和管理员提权逻辑;用可复用的 C# helper 替换临时 interop 代码;统一人类可读的大小格式化实现,并修复工具页与核心工具类中的若干小 bug 和样式问题。

从 UI 触发的更新后 mem swap 流程时序图

sequenceDiagram
    actor User
    participant PageToolsTest
    participant ProcessInterop
    participant ElevatedPCL as PCL_Process_admin
    participant MemSwapService_elev as MemSwapService_admin
    participant KernelInterop
    participant Context

    User->>PageToolsTest: ClickMemoryOptimizeButton
    PageToolsTest->>PageToolsTest: AskTrulyWantMemoryOptimize()
    PageToolsTest->>PageToolsTest: Check ProcessInterop.IsAdmin()
    alt HasAdmin
        PageToolsTest->>MemSwapService_elev: AcquirePrivileges()
        PageToolsTest->>MemSwapService_elev: MemorySwap()
        MemSwapService_elev->>SwapWorks: Execute swap operations
        SwapWorks-->>MemSwapService_elev: Completed
        MemSwapService_elev-->>PageToolsTest: true or false
        PageToolsTest->>KernelInterop: GetAvailablePhysicalMemoryBytes()
        KernelInterop-->>PageToolsTest: MemAfter
        PageToolsTest->>User: Show result hint
    else NoAdmin
        PageToolsTest->>ProcessInterop: Start(Basics.ExecutablePath, "memory", runAsAdmin true)
        ProcessInterop->>ElevatedPCL: Launch with UseShellExecute and verb runas
        ElevatedPCL->>MemSwapService_elev: Initialize lifecycle services
        MemSwapService_elev->>MemSwapService_elev: _CheckRequest()
        MemSwapService_elev->>MemSwapService_elev: Check Basics.CommandLineArguments == ["memory"]
        MemSwapService_elev->>ProcessInterop: IsAdmin()
        alt ElevatedHasAdmin
            MemSwapService_elev->>KernelInterop: GetPhysicalMemoryBytes().Available before
            MemSwapService_elev->>MemSwapService_elev: AcquirePrivileges()
            MemSwapService_elev->>MemSwapService_elev: MemorySwap(SwapScope.All)
            MemSwapService_elev->>SwapWorks: Execute swap operations
            SwapWorks-->>MemSwapService_elev: Completed
            MemSwapService_elev->>KernelInterop: GetPhysicalMemoryBytes().Available after
            MemSwapService_elev->>Context: RequestExit(diffInKB)
        else ElevatedNoAdmin
            MemSwapService_elev->>Context: Error("缺少管理员权限")
            MemSwapService_elev->>Context: RequestExit(-1)
        end
        ElevatedPCL-->>PageToolsTest: Process exit code
        PageToolsTest->>PageToolsTest: num = ExitCode * 1024
        PageToolsTest->>User: Show result hint if num > 1024L
    end
Loading

MemSwapService.MemorySwap 与 SwapWorks 的时序图

sequenceDiagram
    participant Caller as MemSwapService
    participant MemSwapService
    participant ProcessInterop
    participant SwapWorks
    participant NtInterop

    Caller->>MemSwapService: MemorySwap(scope)
    MemSwapService->>MemSwapService: _memSwapLock.Wait(0)
    alt LockBusy
        MemSwapService-->>Caller: false
    else LockAcquired
        MemSwapService->>ProcessInterop: IsAdmin()
        alt NotAdmin
            MemSwapService-->>Caller: false
        else IsAdmin
            MemSwapService->>MemSwapService: Check flags in scope
            opt EmptyWorkingSets
                MemSwapService->>SwapWorks: EmptyWorkingSets()
                SwapWorks->>NtInterop: SetSystemInformation(SystemMemoryListInformation, 2)
            end
            opt FlushFileCache
                MemSwapService->>SwapWorks: FlushFileCache()
                SwapWorks->>NtInterop: SetSystemInformation(SystemFileCacheInformationEx, SYSTEM_FILECACHE_INFORMATION)
            end
            opt FlushModifiedList
                MemSwapService->>SwapWorks: FlushModifiedList()
                SwapWorks->>NtInterop: SetSystemInformation(SystemMemoryListInformation, 3)
            end
            opt PurgeStandbyList
                MemSwapService->>SwapWorks: PurgeStandbyList()
                SwapWorks->>NtInterop: SetSystemInformation(SystemMemoryListInformation, 4)
            end
            opt PurgeLowPriorityStandbyList
                MemSwapService->>SwapWorks: PurgeLowPriorityStandbyList()
                SwapWorks->>NtInterop: SetSystemInformation(SystemMemoryListInformation, 5)
            end
            opt RegistryReconciliation
                MemSwapService->>SwapWorks: RegistryReconciliation()
                SwapWorks->>NtInterop: SetSystemInformation(SystemRegistryReconciliationInformation, 0)
            end
            opt CombinePhysicalMemory
                MemSwapService->>SwapWorks: CombinePhysicalMemory()
                SwapWorks->>NtInterop: SetSystemInformation(SystemCombinePhysicalMemoryInformation, MEMORY_COMBINE_INFORMATION_EX)
            end
            MemSwapService-->>Caller: true
        end
        MemSwapService->>MemSwapService: _memSwapLock.Release()
    end
Loading

更新后的 mem swap 与 IO 工具类图

classDiagram
    class MemSwapService {
        <<sealed>>
        - static SemaphoreSlim _memSwapLock
        + static void AcquirePrivileges()
        + static bool MemorySwap(SwapScope scope)
        - static void _CheckRequest()
    }

    class SwapWorks {
        <<static>>
        - static void _ExecuteMemoryListOperation(int infoValue)
        - static void _ExecuteStructureOperation~T~(T structure, NtInterop.SystemInformationClass infoClass)
        + static void EmptyWorkingSets()
        + static void FlushFileCache()
        + static void FlushModifiedList()
        + static void PurgeStandbyList()
        + static void PurgeLowPriorityStandbyList()
        + static void RegistryReconciliation()
        + static void CombinePhysicalMemory()
    }

    class SwapScope {
        <<enum>>
        None
        EmptyWorkingSets
        FlushFileCache
        FlushModifiedList
        PurgeStandbyList
        PurgeLowPriorityStandbyList
        RegistryReconciliation
        CombinePhysicalMemory
        All
    }

    class ByteStream {
        - Stream BaseStream
        + long Length
        + ByteStream(Stream stream)
        + int Read(byte[] buffer, int offset, int count)
        + void Write(byte[] buffer, int offset, int count)
        + long Seek(long offset, SeekOrigin origin)
        + void Flush()
        + long Position
        + void Close()
        + string GetReadableLength()
        - static string[] _Units
        + static string GetReadableLength(long length, int startUnit)
    }

    class ModBase {
        <<module>>
        + string GetString(long FileSize)
    }

    class ProcessInterop {
        <<static>>
        + bool IsAdmin()
        + Process Start(string path, string arguments, bool runAsAdmin)
    }

    class NtInterop {
        <<static>>
        + void SetPrivilege(SePrivilege privilege, bool enable, bool thread)
        + void SetSystemInformation(SystemInformationClass infoClass, IntPtr info, uint length)
    }

    class KernelInterop {
        <<static>>
        + PhysicalMemoryInfo GetPhysicalMemoryBytes()
        + ulong GetAvailablePhysicalMemoryBytes()
    }

    class Context {
        <<static>>
        + void Info(string message)
        + void Warn(string message)
        + void Error(string message)
        + void Error(string message, Exception ex)
        + void RequestExit(int code)
    }

    ModBase ..> ByteStream : uses
    MemSwapService ..> SwapScope : uses
    MemSwapService ..> SwapWorks : uses
    MemSwapService ..> NtInterop : uses
    MemSwapService ..> KernelInterop : uses
    MemSwapService ..> ProcessInterop : uses
    MemSwapService ..> ByteStream : uses
    MemSwapService ..> Context : uses
    SwapWorks ..> NtInterop : uses
    ProcessInterop ..> SystemDiagnosticsProcess : wraps

    class SystemDiagnosticsProcess {
        <<framework>>
    }
Loading

文件级变更

Change Details Files
将内存优化逻辑重构到独立的 MemSwap 服务和基于作用域的 API 中,从工具页面中移除内联的 NtInterop/PInvoke 代码。
  • 用对 MemSwapService.AcquirePrivileges 的调用,替换 PageToolsTest.MemoryOptimizeInternal 中管理员权限设置的逻辑
  • 用单一的 MemSwapService.MemorySwap() 调用替换内联的 NtInterop.SetSystemInformation 内存操作,并在失败时对用户给出提示
  • 从工具页文件中移除现已冗余的 SYSTEM_FILECACHE_INFORMATION 和 MEMORY_COMBINE_INFORMATION_EX 结构体
  • 将非管理员内存优化路径改为使用带有 "memory" 命令的方式重新启动应用,并由 MemSwapService 处理该命令,替换之前的 "--memory" CLI 分支
  • 收紧内存优化收益上报的成功阈值,从检查 num > 0L 调整为检查 num > 1024L
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.vb
Plain Craft Launcher 2/Application.xaml.vb
引入 MemSwapService、SwapWorks helper 类以及 SwapScope 枚举,将所有操作系统级的内存列表和缓存操作封装到一个可复用、具备生命周期感知能力的服务中。
  • 新增 MemSwapService 作为一个生命周期服务,用于检查命令行参数中是否存在 "memory" 请求、强制要求管理员权限、获取特权、调用 MemorySwap()、通过 ByteStream.GetReadableLength 记录前后内存,并以清理出的 KB 数作为进程退出码
  • 实现基于 SemaphoreSlim 锁的线程安全 MemorySwap(),包含管理员检查,并根据带标志位的枚举 scope 有选择地执行各项 swap 操作
  • 添加 AcquirePrivileges() helper,通过 NtInterop 设置 SeProfileSingleProcessPrivilege 和 SeIncreaseQuotaPrivilege
  • 新增 SwapWorks helper 类,用于封装 NtInterop.SetSystemInformation 的调用,包括基于 GCHandle 的整型与结构泛型 helper,以及针对每个内存列表操作的具体方法
  • 新增带 [Flags] 的 SwapScope 枚举,用于表达独立和组合的 swap 操作,并提供一个涵盖所有已支持标志位的 All 值
PCL.Core/App/Tools/MemSwap/MemSwapService.cs
PCL.Core/App/Tools/MemSwap/SwapWorks.cs
PCL.Core/App/Tools/MemSwap/SwapScope.cs
将人类可读的字节大小格式化逻辑集中到 ByteStream 中,并在 VB 代码中复用。
  • 用 ByteStream.GetReadableLength(FileSize) 替换 ModBase.vb 中之前手写的 GetString 实现
  • 扩展 ByteStream.GetReadableLength,使其成为更健壮的实现:支持负值、任意起始单位以及最大到 YB,使用十进制运算并采用格式 "0.##"
  • 在 ByteStream 中引入共享单位数组,对溢出进行校验,当 length 超出支持的单位范围时抛出 ArgumentOutOfRangeException
PCL.Core/IO/ByteStream.cs
Plain Craft Launcher 2/Modules/Base/ModBase.vb
修复管理员提权进程启动和生命周期命令映射中的边界情况。
  • 确保当请求 runAsAdmin 时,ProcessInterop.Start 使用 UseShellExecute=true,这是 Windows 上 "runas" verb 能稳定工作的前提
  • 调整 StartupService 命令解析逻辑,以便构建命令路径前缀时不再带有前导点,避免在第一个片段前生成一个空键条目
PCL.Core/Utils/OS/ProcessInterop.cs
PCL.Core/App/Essentials/StartupService.cs
在工具页和核心文件中进行若干小 bug 修复和样式清理。
  • 修复 LoaderCombo 构造逻辑,使用命名正确的 loaderdownload 变量而非 loaderDownload,避免引用错误
  • 在多个 C# 和 VB 文件中统一 using/import 语句及空白风格
  • 修正 BtnCrash 点击事件处理函数中 VB 命名参数的调用语法
  • 在成就图片的异步加载与保存逻辑中进行清理(Await 大小写、缩进和日志记录)
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.vb
PCL.Core/IO/ByteStream.cs
Plain Craft Launcher 2/Modules/Base/ModBase.vb
PCL.Core/App/Essentials/UpdateService.cs
PCL.Core/App/IoC/Attributes.cs
PCL.Core/Utils/OS/NtInterop.cs

可能关联的问题

  • #: Issue 报告管理员模式内存优化权限获取失败,PR 用 MemSwapService 重写提权与内存交换逻辑修复该问题。

Tips and commands

Interacting with Sourcery

  • 触发新一次 Review: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的 review 评论。
  • 从 review 评论生成一个 GitHub issue: 在某条 review 评论下回复,要求 Sourcery 基于该评论创建 issue。你也可以直接回复该评论 @sourcery-ai issue 来从中创建 issue。
  • 生成 pull request 标题: 在 pull request 标题中任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title 来随时(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文任意位置写上 @sourcery-ai summary,即可在正文的对应位置生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary 来随时(重新)生成摘要。
  • 生成 Reviewer's Guide: 在 pull request 中评论 @sourcery-ai guide,即可随时(重新)生成 reviewer's guide。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,即可将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论且不希望再看到它们,这会很方便。
  • 忽略所有 Sourcery reviews: 在 pull request 中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery reviews。尤其适合你希望以一次全新的 review 开始时使用——别忘了再评论 @sourcery-ai review 来触发新的 review!

Customizing Your Experience

打开你的 dashboard 以便:

  • 启用或禁用诸如 Sourcery 自动生成的 pull request 摘要、reviewer's guide 等 review 功能。
  • 修改 review 所使用的语言。
  • 添加、移除或编辑自定义 review 指南。
  • 调整其它 review 相关设置。

Getting Help

Original review guide in English

Reviewer's Guide

Refactors the memory optimization (mem-swap) feature into a dedicated lifecycle service with proper privilege handling and admin elevation, replaces ad‑hoc interop code with reusable C# helpers, unifies human‑readable size formatting, and fixes several smaller bugs and style issues in the tools page and core utilities.

Sequence diagram for the updated mem swap flow from UI

sequenceDiagram
    actor User
    participant PageToolsTest
    participant ProcessInterop
    participant ElevatedPCL as PCL_Process_admin
    participant MemSwapService_elev as MemSwapService_admin
    participant KernelInterop
    participant Context

    User->>PageToolsTest: ClickMemoryOptimizeButton
    PageToolsTest->>PageToolsTest: AskTrulyWantMemoryOptimize()
    PageToolsTest->>PageToolsTest: Check ProcessInterop.IsAdmin()
    alt HasAdmin
        PageToolsTest->>MemSwapService_elev: AcquirePrivileges()
        PageToolsTest->>MemSwapService_elev: MemorySwap()
        MemSwapService_elev->>SwapWorks: Execute swap operations
        SwapWorks-->>MemSwapService_elev: Completed
        MemSwapService_elev-->>PageToolsTest: true or false
        PageToolsTest->>KernelInterop: GetAvailablePhysicalMemoryBytes()
        KernelInterop-->>PageToolsTest: MemAfter
        PageToolsTest->>User: Show result hint
    else NoAdmin
        PageToolsTest->>ProcessInterop: Start(Basics.ExecutablePath, "memory", runAsAdmin true)
        ProcessInterop->>ElevatedPCL: Launch with UseShellExecute and verb runas
        ElevatedPCL->>MemSwapService_elev: Initialize lifecycle services
        MemSwapService_elev->>MemSwapService_elev: _CheckRequest()
        MemSwapService_elev->>MemSwapService_elev: Check Basics.CommandLineArguments == ["memory"]
        MemSwapService_elev->>ProcessInterop: IsAdmin()
        alt ElevatedHasAdmin
            MemSwapService_elev->>KernelInterop: GetPhysicalMemoryBytes().Available before
            MemSwapService_elev->>MemSwapService_elev: AcquirePrivileges()
            MemSwapService_elev->>MemSwapService_elev: MemorySwap(SwapScope.All)
            MemSwapService_elev->>SwapWorks: Execute swap operations
            SwapWorks-->>MemSwapService_elev: Completed
            MemSwapService_elev->>KernelInterop: GetPhysicalMemoryBytes().Available after
            MemSwapService_elev->>Context: RequestExit(diffInKB)
        else ElevatedNoAdmin
            MemSwapService_elev->>Context: Error("缺少管理员权限")
            MemSwapService_elev->>Context: RequestExit(-1)
        end
        ElevatedPCL-->>PageToolsTest: Process exit code
        PageToolsTest->>PageToolsTest: num = ExitCode * 1024
        PageToolsTest->>User: Show result hint if num > 1024L
    end
Loading

Sequence diagram for MemSwapService.MemorySwap and SwapWorks

sequenceDiagram
    participant Caller as MemSwapService
    participant MemSwapService
    participant ProcessInterop
    participant SwapWorks
    participant NtInterop

    Caller->>MemSwapService: MemorySwap(scope)
    MemSwapService->>MemSwapService: _memSwapLock.Wait(0)
    alt LockBusy
        MemSwapService-->>Caller: false
    else LockAcquired
        MemSwapService->>ProcessInterop: IsAdmin()
        alt NotAdmin
            MemSwapService-->>Caller: false
        else IsAdmin
            MemSwapService->>MemSwapService: Check flags in scope
            opt EmptyWorkingSets
                MemSwapService->>SwapWorks: EmptyWorkingSets()
                SwapWorks->>NtInterop: SetSystemInformation(SystemMemoryListInformation, 2)
            end
            opt FlushFileCache
                MemSwapService->>SwapWorks: FlushFileCache()
                SwapWorks->>NtInterop: SetSystemInformation(SystemFileCacheInformationEx, SYSTEM_FILECACHE_INFORMATION)
            end
            opt FlushModifiedList
                MemSwapService->>SwapWorks: FlushModifiedList()
                SwapWorks->>NtInterop: SetSystemInformation(SystemMemoryListInformation, 3)
            end
            opt PurgeStandbyList
                MemSwapService->>SwapWorks: PurgeStandbyList()
                SwapWorks->>NtInterop: SetSystemInformation(SystemMemoryListInformation, 4)
            end
            opt PurgeLowPriorityStandbyList
                MemSwapService->>SwapWorks: PurgeLowPriorityStandbyList()
                SwapWorks->>NtInterop: SetSystemInformation(SystemMemoryListInformation, 5)
            end
            opt RegistryReconciliation
                MemSwapService->>SwapWorks: RegistryReconciliation()
                SwapWorks->>NtInterop: SetSystemInformation(SystemRegistryReconciliationInformation, 0)
            end
            opt CombinePhysicalMemory
                MemSwapService->>SwapWorks: CombinePhysicalMemory()
                SwapWorks->>NtInterop: SetSystemInformation(SystemCombinePhysicalMemoryInformation, MEMORY_COMBINE_INFORMATION_EX)
            end
            MemSwapService-->>Caller: true
        end
        MemSwapService->>MemSwapService: _memSwapLock.Release()
    end
Loading

Updated class diagram for mem swap and IO utilities

classDiagram
    class MemSwapService {
        <<sealed>>
        - static SemaphoreSlim _memSwapLock
        + static void AcquirePrivileges()
        + static bool MemorySwap(SwapScope scope)
        - static void _CheckRequest()
    }

    class SwapWorks {
        <<static>>
        - static void _ExecuteMemoryListOperation(int infoValue)
        - static void _ExecuteStructureOperation~T~(T structure, NtInterop.SystemInformationClass infoClass)
        + static void EmptyWorkingSets()
        + static void FlushFileCache()
        + static void FlushModifiedList()
        + static void PurgeStandbyList()
        + static void PurgeLowPriorityStandbyList()
        + static void RegistryReconciliation()
        + static void CombinePhysicalMemory()
    }

    class SwapScope {
        <<enum>>
        None
        EmptyWorkingSets
        FlushFileCache
        FlushModifiedList
        PurgeStandbyList
        PurgeLowPriorityStandbyList
        RegistryReconciliation
        CombinePhysicalMemory
        All
    }

    class ByteStream {
        - Stream BaseStream
        + long Length
        + ByteStream(Stream stream)
        + int Read(byte[] buffer, int offset, int count)
        + void Write(byte[] buffer, int offset, int count)
        + long Seek(long offset, SeekOrigin origin)
        + void Flush()
        + long Position
        + void Close()
        + string GetReadableLength()
        - static string[] _Units
        + static string GetReadableLength(long length, int startUnit)
    }

    class ModBase {
        <<module>>
        + string GetString(long FileSize)
    }

    class ProcessInterop {
        <<static>>
        + bool IsAdmin()
        + Process Start(string path, string arguments, bool runAsAdmin)
    }

    class NtInterop {
        <<static>>
        + void SetPrivilege(SePrivilege privilege, bool enable, bool thread)
        + void SetSystemInformation(SystemInformationClass infoClass, IntPtr info, uint length)
    }

    class KernelInterop {
        <<static>>
        + PhysicalMemoryInfo GetPhysicalMemoryBytes()
        + ulong GetAvailablePhysicalMemoryBytes()
    }

    class Context {
        <<static>>
        + void Info(string message)
        + void Warn(string message)
        + void Error(string message)
        + void Error(string message, Exception ex)
        + void RequestExit(int code)
    }

    ModBase ..> ByteStream : uses
    MemSwapService ..> SwapScope : uses
    MemSwapService ..> SwapWorks : uses
    MemSwapService ..> NtInterop : uses
    MemSwapService ..> KernelInterop : uses
    MemSwapService ..> ProcessInterop : uses
    MemSwapService ..> ByteStream : uses
    MemSwapService ..> Context : uses
    SwapWorks ..> NtInterop : uses
    ProcessInterop ..> SystemDiagnosticsProcess : wraps

    class SystemDiagnosticsProcess {
        <<framework>>
    }
Loading

File-Level Changes

Change Details Files
Refactor memory optimization logic into a dedicated MemSwap service and scope-based API, removing inline NtInterop/PInvoke code from the tools page.
  • Replace PageToolsTest.MemoryOptimizeInternal admin privilege setup with a call to MemSwapService.AcquirePrivileges
  • Replace inlined NtInterop.SetSystemInformation memory operations with a single MemSwapService.MemorySwap() call and user hinting on failure
  • Remove now-redundant SYSTEM_FILECACHE_INFORMATION and MEMORY_COMBINE_INFORMATION_EX structs from the tools page file
  • Change non-admin memory optimization path to spawn the app with a "memory" command handled by MemSwapService instead of the previous "--memory" CLI branch
  • Tighten success threshold for reporting memory optimization gain by checking num > 1024L instead of num > 0L
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.vb
Plain Craft Launcher 2/Application.xaml.vb
Introduce MemSwapService, SwapWorks helpers, and SwapScope enum to encapsulate all OS-level memory list and cache operations behind a reusable, lifecycle-aware service.
  • Add MemSwapService as a lifecycle service that inspects command line args for a "memory" request, enforces admin rights, acquires privileges, invokes MemorySwap(), logs before/after memory using ByteStream.GetReadableLength, and exits with cleaned KB as process exit code
  • Implement thread-safe MemorySwap() with a SemaphoreSlim lock, admin check, and selective execution of swap operations based on a flag enum scope
  • Add AcquirePrivileges() helper that sets SeProfileSingleProcessPrivilege and SeIncreaseQuotaPrivilege via NtInterop
  • Add SwapWorks helper class that wraps NtInterop.SetSystemInformation calls, including generic GCHandle-based helpers for integer and struct operations and concrete methods for each memory list operation
  • Add SwapScope [Flags] enum expressing individual and combined swap operations, including an All value covering all supported flags
PCL.Core/App/Tools/MemSwap/MemSwapService.cs
PCL.Core/App/Tools/MemSwap/SwapWorks.cs
PCL.Core/App/Tools/MemSwap/SwapScope.cs
Centralize human-readable byte size formatting in ByteStream and reuse it from VB code.
  • Replace the previous manual GetString implementation in ModBase.vb with a call to ByteStream.GetReadableLength(FileSize)
  • Extend ByteStream.GetReadableLength into a more robust implementation that supports negative values, arbitrary start units, and up to YB, using decimal math and format "0.##"
  • Introduce a shared units array in ByteStream and validate against overflow, throwing ArgumentOutOfRangeException when length exceeds supported units
PCL.Core/IO/ByteStream.cs
Plain Craft Launcher 2/Modules/Base/ModBase.vb
Fix admin-elevation process launching and lifecycle command mapping edge cases.
  • Ensure ProcessInterop.Start uses UseShellExecute=true when runAsAdmin is requested, which is required for the "runas" verb to work reliably on Windows
  • Adjust StartupService command parsing so that command path prefixes are built without a leading dot, preventing an empty-key entry before the first segment
PCL.Core/Utils/OS/ProcessInterop.cs
PCL.Core/App/Essentials/StartupService.cs
Minor bug fixes and stylistic cleanups in the tools page and core files.
  • Fix LoaderCombo construction to use the correctly named loaderdownload variable instead of loaderDownload, preventing a reference error
  • Normalize various using/import statements and whitespace across multiple C# and VB files
  • Correct BtnCrash click handler call-site argument syntax for named parameters in VB
  • Async-related cleanups in achievement image loading and saving (Await capitalization, indentation, and logging)
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.vb
PCL.Core/IO/ByteStream.cs
Plain Craft Launcher 2/Modules/Base/ModBase.vb
PCL.Core/App/Essentials/UpdateService.cs
PCL.Core/App/IoC/Attributes.cs
PCL.Core/Utils/OS/NtInterop.cs

Possibly linked issues

  • #: Issue 报告管理员模式内存优化权限获取失败,PR 用 MemSwapService 重写提权与内存交换逻辑修复该问题。

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并给出了一些高层次的反馈:

  • ByteStream.GetReadableLength 中,当参数为 long.MinValue 时,取负号 -length 会发生溢出;建议在取绝对值前先转换为 decimal,或者使用安全的辅助方法来处理这个边界情况。
  • MemSwapService._CheckRequest 中,当 MemorySwap 返回 false 时,该方法只记录日志并返回,但不会退出,这改变了之前 CLI 对内存请求总是终止的行为;建议在这种情况下返回一个非零退出码,以便让调用方的行为契约保持可预测。
  • MemSwapService 中的 _memSwapLock 字段在初始化之后就不再发生变化;将其标记为 readonly 能更好地表达意图并避免意外的重新赋值。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In ByteStream.GetReadableLength, the negation `-length` will overflow for `long.MinValue`; consider casting to decimal before taking the absolute value or using a safe helper to handle that edge case.
- In MemSwapService._CheckRequest, when MemorySwap returns false the method just logs and returns without exiting, which changes the previous CLI behavior of always terminating for memory requests; consider returning a non-zero exit code there to keep the contract predictable for callers.
- The `_memSwapLock` field in MemSwapService never changes after initialization; marking it as `readonly` would better express intent and avoid accidental reassignment.

## Individual Comments

### Comment 1
<location path="PCL.Core/IO/ByteStream.cs" line_range="12-21" />
<code_context>
+    private static readonly string[] _Units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
</code_context>
<issue_to_address>
**issue (bug_risk):** Negative or out-of-range `startUnit` values can cause index errors when accessing `_Units`.

In `GetReadableLength(long length, int startUnit = 0)`, `startUnit` is used directly as `unitIndex` and then applied as `_Units[unitIndex]`. If `startUnit` is negative or >= `_Units.Length`, this will throw `IndexOutOfRangeException` before your intended `ArgumentOutOfRangeException`. Add a guard (e.g., `0 <= startUnit && startUnit < _Units.Length`) before indexing to ensure consistent, explicit error handling.
</issue_to_address>

Sourcery 对开源项目是免费的 —— 如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据反馈改进后续的评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • In ByteStream.GetReadableLength, the negation -length will overflow for long.MinValue; consider casting to decimal before taking the absolute value or using a safe helper to handle that edge case.
  • In MemSwapService._CheckRequest, when MemorySwap returns false the method just logs and returns without exiting, which changes the previous CLI behavior of always terminating for memory requests; consider returning a non-zero exit code there to keep the contract predictable for callers.
  • The _memSwapLock field in MemSwapService never changes after initialization; marking it as readonly would better express intent and avoid accidental reassignment.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In ByteStream.GetReadableLength, the negation `-length` will overflow for `long.MinValue`; consider casting to decimal before taking the absolute value or using a safe helper to handle that edge case.
- In MemSwapService._CheckRequest, when MemorySwap returns false the method just logs and returns without exiting, which changes the previous CLI behavior of always terminating for memory requests; consider returning a non-zero exit code there to keep the contract predictable for callers.
- The `_memSwapLock` field in MemSwapService never changes after initialization; marking it as `readonly` would better express intent and avoid accidental reassignment.

## Individual Comments

### Comment 1
<location path="PCL.Core/IO/ByteStream.cs" line_range="12-21" />
<code_context>
+    private static readonly string[] _Units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
</code_context>
<issue_to_address>
**issue (bug_risk):** Negative or out-of-range `startUnit` values can cause index errors when accessing `_Units`.

In `GetReadableLength(long length, int startUnit = 0)`, `startUnit` is used directly as `unitIndex` and then applied as `_Units[unitIndex]`. If `startUnit` is negative or >= `_Units.Length`, this will throw `IndexOutOfRangeException` before your intended `ArgumentOutOfRangeException`. Add a guard (e.g., `0 <= startUnit && startUnit < _Units.Length`) before indexing to ensure consistent, explicit error handling.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@tangge233 tangge233 linked an issue Apr 6, 2026 that may be closed by this pull request
4 tasks
@pcl-ce-automation pcl-ce-automation bot added size: XL PR 大小评估:超大型 and removed size: L PR 大小评估:大型 labels Apr 8, 2026
@tangge233
Copy link
Copy Markdown
Contributor Author

[16:12:26.433] [INFO] [Promote] [提权服务|promote] 正在连接提权通信管道
[16:12:26.433] [DBG] [STA] [生命周期|lifecycle] 状态 BeforeLoading 共用时 191 ms
[16:12:26.436] [INFO] [Promote] [提权服务|promote] 已连接,开始通信
[16:12:26.438] [DBG] [Promote] [提权服务|promote] 正在执行: mem-swap
[16:12:26.441] [WARN] [Promote] [提权服务|promote] 操作出错
System.InvalidOperationException: Not initialized
   at PCL.Core.App.Tools.MemSwapService.get_Context()
   at PCL.Core.App.Tools.MemSwapService._AcquirePrivileges()
   at PCL.Core.App.Tools.MemSwapService.OnPromoteMemorySwapOperation(String arg)
   at PCL.Core.App.Essentials.PromoteService.<>c.<get_Operate>b__20_0(String command)
[16:12:26.441] [TRA] [Promote] [提权服务|promote] 返回结果: ERR_UNHANDLED_EXCEPTION
[16:12:26.441] [TRA] [Promote] [提权服务|promote] 返回成功

程序想要写日志,但是没有初始化,没有 Context @ruattd

Copy link
Copy Markdown
Contributor

@Hill23333 Hill23333 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

没看代码但测试没问题

@pcl-ce-automation pcl-ce-automation bot added 🕑 等待合并 已处理完毕,正在等待代码合并入主分支 and removed 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 labels Apr 9, 2026
@ruattd ruattd merged commit 13f471f into dev Apr 9, 2026
3 checks passed
@pcl-ce-automation pcl-ce-automation bot added 👌 完成 相关问题已修复或功能已实现,计划在下次版本更新时正式上线 and removed 🕑 等待合并 已处理完毕,正在等待代码合并入主分支 labels Apr 9, 2026
@ruattd ruattd deleted the fix/mem-swap branch April 9, 2026 15:18
@LuLu-ling LuLu-ling mentioned this pull request Apr 9, 2026
81 tasks
LuLu-ling added a commit to PCL-Community/PCL-CSharpE that referenced this pull request Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: XL PR 大小评估:超大型 👌 完成 相关问题已修复或功能已实现,计划在下次版本更新时正式上线

Projects

None yet

Development

Successfully merging this pull request may close these issues.

在内存优化时不会向用户请求管理员

3 participants