Skip to content
100 changes: 100 additions & 0 deletions PCL.Core/App/Essentials/MemSwap/MemSwapService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using PCL.Core.App.IoC;
using PCL.Core.IO;
using PCL.Core.Utils.OS;
using System;
using System.Threading;

namespace PCL.Core.App.Essentials.MemSwap;

[LifecycleService(LifecycleState.BeforeLoading, Priority = 10)]
[LifecycleScope("mem-swap", "内存交换", false)]
public partial class MemSwapService
{
[LifecycleStart]
private static void _CheckRequest()
{
var args = Basics.CommandLineArguments;

if (args is ["memory"])
{
Context.Info("检测到内存交换请求,开始处理");

if (!ProcessInterop.IsAdmin())
{
Context.Error("缺少管理员权限,退出内存处理");
Context.RequestExit(-1);
return;
}

try
{
var before = KernelInterop.GetPhysicalMemoryBytes().Available;
Context.Info($"处理前内存量 {ByteStream.GetReadableLength((long)before)}");
AcquirePrivileges();
if (!MemorySwap(SwapScope.All))
{
Context.Error("请求无法处理,返回");
return;
}

var after = KernelInterop.GetPhysicalMemoryBytes().Available;
Context.Info($"处理后内存量 {ByteStream.GetReadableLength((long)after)}");
var diff = Math.Max(0, after - before);
Context.Info($"处理结束,总共处理 {ByteStream.GetReadableLength((long)diff)}");
diff /= 1024;
if (diff > int.MaxValue) diff = int.MaxValue;

Context.RequestExit((int)diff);
}
catch (Exception ex)
{
Context.Error("内存处理失败", ex);
Context.RequestExit(-1);
}
}
}



private static SemaphoreSlim _memSwapLock = new(1, 1);
public static bool MemorySwap(SwapScope scope = SwapScope.All)
{
if (!_memSwapLock.Wait(0))
{
Context.Warn("检测到正在进行的内存处理,取消当前处理");
return false;
}

try
{
if (!ProcessInterop.IsAdmin()) return false;

Context.Info($"开始处理,区域请求:{(int)scope}");
if (scope.HasFlag(SwapScope.EmptyWorkingSets)) SwapWorks.EmptyWorkingSets();
if (scope.HasFlag(SwapScope.FlushFileCache)) SwapWorks.FlushFileCache();
if (scope.HasFlag(SwapScope.FlushModifiedList)) SwapWorks.FlushModifiedList();
if (scope.HasFlag(SwapScope.PurgeStandbyList)) SwapWorks.PurgeStandbyList();
if (scope.HasFlag(SwapScope.PurgeLowPriorityStandbyList)) SwapWorks.PurgeLowPriorityStandbyList();
if (scope.HasFlag(SwapScope.RegistryReconciliation)) SwapWorks.RegistryReconciliation();
if (scope.HasFlag(SwapScope.CombinePhysicalMemory)) SwapWorks.CombinePhysicalMemory();

return true;
}
catch (Exception ex)
{
Context.Error("内存处理出现异常", ex);
return false;
}
finally
{
_memSwapLock.Release();
}
}

public static void AcquirePrivileges()
{
Context.Info("获取权限……");
NtInterop.SetPrivilege(NtInterop.SePrivilege.SeProfileSingleProcessPrivilege, true, false);
NtInterop.SetPrivilege(NtInterop.SePrivilege.SeIncreaseQuotaPrivilege, true, false);
}
}
17 changes: 17 additions & 0 deletions PCL.Core/App/Essentials/MemSwap/SwapScope.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace PCL.Core.App.Essentials.MemSwap;

[Flags]
public enum SwapScope
{
None = 0,
EmptyWorkingSets = 1 << 0,
FlushFileCache = 1 << 1,
FlushModifiedList = 1 << 2,
PurgeStandbyList = 1 << 3,
PurgeLowPriorityStandbyList = 1 << 4,
RegistryReconciliation = 1 << 5,
CombinePhysicalMemory = 1 << 6,
All = 0b111111
}
92 changes: 92 additions & 0 deletions PCL.Core/App/Essentials/MemSwap/SwapWorks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using PCL.Core.Utils.OS;
using System;
using System.Runtime.InteropServices;

namespace PCL.Core.App.Essentials.MemSwap;

internal static class SwapWorks
{
private static void _ExecuteMemoryListOperation(int infoValue)
{
GCHandle handle = GCHandle.Alloc(infoValue, GCHandleType.Pinned);
try
{
NtInterop.SetSystemInformation(
NtInterop.SystemInformationClass.SystemMemoryListInformation,
handle.AddrOfPinnedObject(),
(uint)Marshal.SizeOf(infoValue));
}
finally
{
if (handle.IsAllocated) handle.Free();
}
}

private static void _ExecuteStructureOperation<T>(T structure, NtInterop.SystemInformationClass infoClass)
{
GCHandle handle = GCHandle.Alloc(structure, GCHandleType.Pinned);
try
{
NtInterop.SetSystemInformation(
infoClass,
handle.AddrOfPinnedObject(),
(uint)Marshal.SizeOf(structure));
}
finally
{
if (handle.IsAllocated) handle.Free();
}
}

public static void EmptyWorkingSets() => _ExecuteMemoryListOperation(2);

[StructLayout(LayoutKind.Sequential)]
private struct SYSTEM_FILECACHE_INFORMATION
{
public UIntPtr CurrentSize;
public UIntPtr PeakSize;
public UIntPtr PageFaultCount;
public UIntPtr MinimumWorkingSet;
public UIntPtr MaximumWorkingSet;
public UIntPtr CurrentSizeIncludingTransitionInPages;
public UIntPtr PeakSizeIncludingTransitionInPages;
public UIntPtr TransitionRePurposeCount;
public UIntPtr Flags;
}

public static void FlushFileCache()
{
var scfi = new SYSTEM_FILECACHE_INFORMATION
{
MaximumWorkingSet = uint.MaxValue,
MinimumWorkingSet = uint.MaxValue
};
_ExecuteStructureOperation(scfi, NtInterop.SystemInformationClass.SystemFileCacheInformationEx);
}

public static void FlushModifiedList() => _ExecuteMemoryListOperation(3);

public static void PurgeStandbyList() => _ExecuteMemoryListOperation(4);

internal static void PurgeLowPriorityStandbyList() => _ExecuteMemoryListOperation(5);

public static void RegistryReconciliation() =>
NtInterop.SetSystemInformation(
NtInterop.SystemInformationClass.SystemRegistryReconciliationInformation,
IntPtr.Zero,
0);

[StructLayout(LayoutKind.Sequential)]
private struct MEMORY_COMBINE_INFORMATION_EX
{
public IntPtr Handle;
public UIntPtr PagesCombined;
public uint Flags;
}

public static void CombinePhysicalMemory()
{
var combineInfoEx = new MEMORY_COMBINE_INFORMATION_EX();
_ExecuteStructureOperation(combineInfoEx, NtInterop.SystemInformationClass.SystemCombinePhysicalMemoryInformation);
}
}
2 changes: 1 addition & 1 deletion PCL.Core/App/Essentials/UpdateService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using PCL.Core.App.IoC;
Expand Down
41 changes: 28 additions & 13 deletions PCL.Core/IO/ByteStream.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;

namespace PCL.Core.IO;
Expand All @@ -9,21 +9,36 @@ public class ByteStream(Stream stream)

public string GetReadableLength() => GetReadableLength(this.Length);

public static string GetReadableLength(long length)
private static readonly string[] _Units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
/// <summary>
/// 格式化大小
/// </summary>
/// <param name="length">字节</param>
/// <param name="startUnit">开始单位</param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static string GetReadableLength(long length, int startUnit = 0)
{
string[] unit = ["B", "KB", "MB", "GB", "TB", "PB"];
var displayCount = length * 100;
var displayUnit = 0;
while (displayCount >= 102400)
bool isNegative = length < 0;
decimal absBytes = isNegative ? -length : length;

if (absBytes == 0)
return "0 B";

int unitIndex = startUnit;
decimal value = absBytes;

while (value >= 1024 && unitIndex < _Units.Length - 1)
{
displayCount >>= 10;
displayUnit++;
value /= 1024;
unitIndex++;
}

if (displayUnit > unit.Length)
throw new IndexOutOfRangeException("Why there is no enough unit to show :(");
var displayText = displayCount.ToString();
displayText = displayText.Insert(displayText.Length - 2, ".");
return $"{displayText} {unit[displayUnit]}";
if (unitIndex >= _Units.Length)
throw new ArgumentOutOfRangeException(nameof(length),
"Value too large for predefined units.");

string sign = isNegative ? "-" : "";
return $"{sign}{value:0.##} {_Units[unitIndex]}";
}
}
2 changes: 1 addition & 1 deletion PCL.Core/Utils/OS/NtInterop.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

Expand Down
9 changes: 7 additions & 2 deletions PCL.Core/Utils/OS/ProcessInterop.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using System.Management;
Expand Down Expand Up @@ -39,7 +39,12 @@ public static bool IsAdmin() =>
public static Process? Start(string path, string? arguments = null, bool runAsAdmin = false) {
var psi = new ProcessStartInfo(path);
if (arguments != null) psi.Arguments = arguments;
if (runAsAdmin) psi.Verb = "runas";
if (runAsAdmin)
{
psi.UseShellExecute = true;
psi.Verb = "runas";
}

return Process.Start(psi);
}

Expand Down
14 changes: 0 additions & 14 deletions Plain Craft Launcher 2/Application.xaml.vb
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,6 @@ Public Class Application
Catch ex As Exception
Environment.Exit(ProcessReturnValues.Fail)
End Try
ElseIf args(0).StartsWithF("--memory") Then
'内存优化
Dim Ram = KernelInterop.GetAvailablePhysicalMemoryBytes()
Try
PageToolsTest.MemoryOptimizeInternal(False)
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Critical, "内存优化失败")
Environment.Exit(-1)
End Try
If KernelInterop.GetAvailablePhysicalMemoryBytes() < Ram Then '避免 ULong 相减出现负数
Environment.Exit(0)
Else
Environment.Exit((KernelInterop.GetAvailablePhysicalMemoryBytes() - Ram) / 1024) '返回清理的内存量(K)
End If
End If
End If
'初始化文件结构
Expand Down
20 changes: 2 additions & 18 deletions Plain Craft Launcher 2/Modules/Base/ModBase.vb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Imports PCL.Core.Utils
Imports System.Windows
Imports PCL.Core.Utils.Codecs
Imports PCL.Core.Utils.OS
Imports PCL.Core.IO

Public Module ModBase

Expand Down Expand Up @@ -1390,24 +1391,7 @@ RetryDir:
''' </summary>
''' <param name="FileSize">以字节为单位的大小表示。</param>
Public Function GetString(FileSize As Long) As String
Dim IsNegative = FileSize < 0
If IsNegative Then FileSize *= -1
If FileSize < 1000 Then
'B 级
Return If(IsNegative, "-", "") & FileSize & " B"
ElseIf FileSize < 1024 * 1000 Then
'K 级
Dim RoundResult As String = Math.Round(FileSize / 1024)
Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024, CInt(MathClamp(3 - RoundResult.Length, 0, 2))) & " K"
ElseIf FileSize < 1024 * 1024 * 1000 Then
'M 级
Dim RoundResult As String = Math.Round(FileSize / 1024 / 1024)
Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024 / 1024, CInt(MathClamp(3 - RoundResult.Length, 0, 2))) & " M"
Else
'G 级
Dim RoundResult As String = Math.Round(FileSize / 1024 / 1024 / 1024)
Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024 / 1024 / 1024, CInt(MathClamp(3 - RoundResult.Length, 0, 2))) & " G"
End If
Return ByteStream.GetReadableLength(FileSize)
End Function

''' <summary>
Expand Down
Loading