Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Installer/Installer.iss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#define MyAppPublisher "ThreadPilot"
#define MyAppURL "https://github.com/"
#define MyAppExeName "ThreadPilot.exe"
#define MyAppVersion "1.4.2"
#define MyAppVersion "1.4.3"

#ifndef MyWizardStyle
#define MyWizardStyle "modern dynamic windows11"
Expand Down
2 changes: 1 addition & 1 deletion Installer/ThreadPilot.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Package
Name="ThreadPilot"
Manufacturer="Prime Build"
Version="1.4.2.0"
Version="1.4.3.0"
UpgradeCode="PUT-GENERATED-UPGRADE-CODE-HERE"
Language="1033">
<SummaryInformation Description="ThreadPilot MSI template" />
Expand Down
2 changes: 1 addition & 1 deletion Installer/setup.iss
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#endif

#ifndef MyAppVersion
#define MyAppVersion "1.4.2"
#define MyAppVersion "1.4.3"
#endif

#ifndef MyAppSourceDir
Expand Down
8 changes: 7 additions & 1 deletion Services/PowerPlanService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class PowerPlanService : IPowerPlanService
private static readonly Lazy<string> powerPlansPath = new(GetPowerPlansPath);
private static readonly string powerCfgExecutablePath = Path.Combine(Environment.SystemDirectory, "powercfg.exe");
private static readonly TimeSpan powerCfgTimeout = TimeSpan.FromSeconds(20);
private static readonly Regex powerSchemeRegex = new(@"Power Scheme GUID: (.*?) \((.*?)\)", RegexOptions.Multiline | RegexOptions.Compiled);
private static readonly Regex powerSchemeRegex = new(@"^Power Scheme GUID: (.*?) \((.*)\)(?:\s+\*)?$", RegexOptions.Multiline | RegexOptions.Compiled);
private static readonly Regex pathTraversalRegex = new(@"(^|[\\/])\.\.([\\/]|$)", RegexOptions.Compiled);

private static string PowerPlansPath => powerPlansPath.Value;
Expand Down Expand Up @@ -517,6 +517,12 @@ private bool TryNormalizePowerPlanPath(string filePath, out string normalizedPat
}

var fileInfo = new FileInfo(normalizedPath);
if (fileInfo.Length == 0)
{
error = "Power plan file is empty.";
return false;
}

if (fileInfo.Length > 10 * 1024 * 1024)
{
error = "Power plan file size exceeds 10 MB limit.";
Expand Down
127 changes: 127 additions & 0 deletions Tests/ThreadPilot.Core.Tests/BundledPowerPlanAssetTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
namespace ThreadPilot.Core.Tests
{
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using ThreadPilot.Services;
using ThreadPilot.Services.Abstractions;

public sealed class BundledPowerPlanAssetTests
{
private static readonly string[] NewPlanFiles =
[
"0 Synez_Public_Power.pow",
"arsenza low latency (INTEL FIX THREAD-DIRECTOR).pow",
"arsenza low latency.pow",
"AutoOS.pow",
"BEYOND-PERFORMANCE-AMD+INTEL.pow",
"Bitsum Highest Performance.pow",
"cactusOS.pow",
"FPSHEAVEN2026.pow",
"GALA's ultimate performance(AMD).pow",
"Gavot Performance.pow",
"GTweaks Power Plan V3.pow",
"imribiy2026.pow",
"IrisFixed.pow",
"J o k r O S P o w e r P l a n.pow",
"Jackpot2026.pow",
"Kizzimo's Extreme Low Latency.pow",
"KSOS11.pow",
"melody LowestLatency.pow",
"Microsoft High performance.pow",
"Microsoft Ultimate Performance.pow",
"Mitstas IDLE ENABLED.pow",
"n1kobg GPU_Booster_Power_Plan.pow",
"Prodazin Power Plan.pow",
"Reticle v2.pow",
"RevisionPowerPlanV2.8.pow",
"RIP Tweaks Power Plan.pow",
"Rosca Tweaks v2.pow",
"Velo's Power Plan.pow",
"VTRL Optimized.pow",
"XNRL Pro Plan.pow",
];

[Fact]
public void BundledPlans_AreValidUniqueRegistryHives()
{
var directory = GetPowerPlansDirectory();
var files = Directory.GetFiles(directory, "*", SearchOption.TopDirectoryOnly);

Assert.All(files, file => Assert.Equal(".pow", Path.GetExtension(file), ignoreCase: true));
Assert.Equal(files.Length, files.Select(Path.GetFileNameWithoutExtension).Distinct(StringComparer.OrdinalIgnoreCase).Count());

foreach (var file in files)
{
var info = new FileInfo(file);
Assert.InRange(info.Length, 4, 10 * 1024 * 1024);
Assert.Equal("regf", Encoding.ASCII.GetString(File.ReadAllBytes(file), 0, 4));
Assert.DoesNotContain(Path.GetFileName(file), character => Path.GetInvalidFileNameChars().Contains(character));
Assert.DoesNotContain("#U", Path.GetFileName(file), StringComparison.OrdinalIgnoreCase);
}
}

[Fact]
public async Task NewAndExistingPlans_ArePresentDiscoverableAndNotBinaryDuplicates()
{
var directory = GetPowerPlansDirectory();
var files = Directory.GetFiles(directory, "*.pow", SearchOption.TopDirectoryOnly);
var newPaths = NewPlanFiles.Select(file => Path.Combine(directory, file)).ToArray();

Assert.All(newPaths, path => Assert.True(File.Exists(path), $"Missing bundled power plan: {Path.GetFileName(path)}"));
Assert.All(["Amit.pow", "Beyond.pow", "L1 Final Version.pow"], file => Assert.True(File.Exists(Path.Combine(directory, file))));

var newHashes = newPaths.ToDictionary(path => path, GetSha256);
Assert.Equal(newHashes.Count, newHashes.Values.Distinct(StringComparer.Ordinal).Count());
Assert.All(
newHashes,
entry => Assert.DoesNotContain(files, file => !newPaths.Contains(file, StringComparer.OrdinalIgnoreCase) && GetSha256(file) == entry.Value));

var logger = new Mock<IEnhancedLoggingService>(MockBehavior.Loose);
var runner = new Mock<IProcessRunner>(MockBehavior.Strict);
var service = new PowerPlanService(
NullLogger<PowerPlanService>.Instance,
logger.Object,
runner.Object,
() => directory);

var discovered = await service.GetCustomPowerPlansAsync();
Assert.All(NewPlanFiles, file => Assert.Contains(discovered, plan => plan.Name == Path.GetFileNameWithoutExtension(file)));
Assert.Equal(files.Length, discovered.Count);
}

[Fact]
public void Project_ConfiguresAllPowerPlansForBuildAndPublish()
{
var project = File.ReadAllText(Path.Combine(FindRepositoryRoot(), "ThreadPilot.csproj"));

Assert.Contains(@"assets\Powerplans\**\*.pow", project, StringComparison.Ordinal);
Assert.Contains(@"<TargetPath>Powerplans\%(RecursiveDir)%(Filename)%(Extension)</TargetPath>", project, StringComparison.Ordinal);
Assert.Contains("<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>", project, StringComparison.Ordinal);
Assert.Contains("<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>", project, StringComparison.Ordinal);
}

private static string GetPowerPlansDirectory() =>
Path.Combine(FindRepositoryRoot(), "assets", "Powerplans");

private static string GetSha256(string path) =>
Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(path)));

private static string FindRepositoryRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory != null)
{
if (File.Exists(Path.Combine(directory.FullName, "ThreadPilot.csproj")))
{
return directory.FullName;
}

directory = directory.Parent;
}

throw new InvalidOperationException("Repository root could not be located.");
}
}
}
6 changes: 3 additions & 3 deletions Tests/ThreadPilot.Core.Tests/PackagingMetadataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ namespace ThreadPilot.Core.Tests

public sealed partial class PackagingMetadataTests
{
private const string ReleaseVersion = "1.4.2";
private const string ReleaseAssemblyVersion = "1.4.2.0";
private const string ReleaseVersion = "1.4.3";
private const string ReleaseAssemblyVersion = "1.4.3.0";

[Fact]
public void InnoInstallers_UseStableDisplayNameAndSeparateVersionMetadata()
Expand Down Expand Up @@ -100,7 +100,7 @@ private static string FindRepositoryRoot()
throw new InvalidOperationException("Repository root could not be located.");
}

[GeneratedRegex("#define MyAppVersion \"1\\.4\\.2\"", RegexOptions.CultureInvariant)]
[GeneratedRegex("#define MyAppVersion \"1\\.4\\.3\"", RegexOptions.CultureInvariant)]
private static partial Regex MyAppVersionRegex();
}
}
26 changes: 26 additions & 0 deletions Tests/ThreadPilot.Core.Tests/PowerPlanServiceSecurityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ public async Task ImportCustomPowerPlan_ReturnsFalse_ForInvalidExtension()
}
}

[Fact]
public async Task ImportCustomPowerPlan_ReturnsFalse_ForEmptyFile()
{
var service = CreateService();
var filePath = Path.Combine(Path.GetTempPath(), $"threadpilot-empty-{Guid.NewGuid():N}.pow");
await File.WriteAllBytesAsync(filePath, []);

try
{
Assert.False(await service.ImportCustomPowerPlan(filePath));
}
finally
{
File.Delete(filePath);
}
}

[Fact]
public async Task ImportCustomPowerPlan_ReturnsFalse_ForMissingFile()
{
var service = CreateService();
var filePath = Path.Combine(Path.GetTempPath(), $"threadpilot-missing-{Guid.NewGuid():N}.pow");

Assert.False(await service.ImportCustomPowerPlan(filePath));
}

[Theory]
[InlineData("")]
[InlineData("invalid-guid")]
Expand Down
19 changes: 19 additions & 0 deletions Tests/ThreadPilot.Core.Tests/PowerPlanServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ public async Task GetActivePowerPlan_ParsesPowerCfgOutput()
Assert.True(result.IsActive);
}

[Fact]
public async Task GetActivePowerPlan_PreservesParenthesesInDisplayName()
{
const string guid = "381b4222-f694-41f0-9685-ff5bb260df2e";
var runner = new RecordingProcessRunner
{
ResultFactory = _ => new ProcessRunResult(
0,
$"Power Scheme GUID: {guid} (Ultimate Performance (AMD)) *",
string.Empty),
};
var service = CreateService(runner);

var result = await service.GetActivePowerPlan();

Assert.NotNull(result);
Assert.Equal("Ultimate Performance (AMD)", result.Name);
}

[Fact]
public async Task SetActivePowerPlanByGuidAsync_SkipsChange_WhenAlreadyActive()
{
Expand Down
8 changes: 4 additions & 4 deletions ThreadPilot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
<TrimMode>link</TrimMode>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>CS1998;CS0067;CS0414;WFAC010;IL3000;MVVMTK0034</NoWarn>
<Version>1.4.2</Version>
<AssemblyVersion>1.4.2.0</AssemblyVersion>
<FileVersion>1.4.2.0</FileVersion>
<InformationalVersion>1.4.2</InformationalVersion>
<Version>1.4.3</Version>
<AssemblyVersion>1.4.3.0</AssemblyVersion>
<FileVersion>1.4.3.0</FileVersion>
<InformationalVersion>1.4.3</InformationalVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion app.manifest
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.4.2.0" name="ThreadPilot.app"/>
<assemblyIdentity version="1.4.3.0" name="ThreadPilot.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
Expand Down
Binary file added assets/Powerplans/0 Synez_Public_Power.pow
Binary file not shown.
Binary file added assets/Powerplans/AutoOS.pow
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added assets/Powerplans/FPSHEAVEN2026.pow
Binary file not shown.
Binary file not shown.
Binary file added assets/Powerplans/GTweaks Power Plan V3.pow
Binary file not shown.
Binary file added assets/Powerplans/Gavot Performance.pow
Binary file not shown.
Binary file modified assets/Powerplans/IIIEXOIII_LOW_LATENCY.pow
Binary file not shown.
Binary file added assets/Powerplans/IrisFixed.pow
Binary file not shown.
Binary file not shown.
Binary file added assets/Powerplans/Jackpot2026.pow
Binary file not shown.
Binary file added assets/Powerplans/KSOS11.pow
Binary file not shown.
Binary file not shown.
Binary file modified assets/Powerplans/LLG-CΞRT1F1ΞD-FOR-PARKING+E-CORES-FIX.pow
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added assets/Powerplans/Mitstas IDLE ENABLED.pow
Binary file not shown.
Binary file added assets/Powerplans/Prodazin Power Plan.pow
Binary file not shown.
Binary file not shown.
Binary file added assets/Powerplans/Reticle v2.pow
Binary file not shown.
Binary file added assets/Powerplans/RevisionPowerPlanV2.8.pow
Binary file not shown.
Binary file added assets/Powerplans/Rosca Tweaks v2.pow
Binary file not shown.
Binary file removed assets/Powerplans/Sazinho-Idle-OFF (1).pow
Binary file not shown.
Binary file modified assets/Powerplans/Slower.pow
Binary file not shown.
Binary file added assets/Powerplans/VTRL Optimized.pow
Binary file not shown.
Binary file added assets/Powerplans/Velo's Power Plan.pow
Binary file not shown.
Binary file added assets/Powerplans/XNRL Pro Plan.pow
Binary file not shown.
Binary file not shown.
Binary file added assets/Powerplans/arsenza low latency.pow
Binary file not shown.
Binary file added assets/Powerplans/cactusOS.pow
Binary file not shown.
Binary file added assets/Powerplans/imribiy2026.pow
Binary file not shown.
Binary file added assets/Powerplans/melody LowestLatency.pow
Binary file not shown.
Binary file not shown.
Binary file modified assets/Powerplans/xilly.pow
Binary file not shown.
2 changes: 1 addition & 1 deletion build/build-installer.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
param(
[string]$Version = "1.4.2",
[string]$Version = "1.4.3",
[string]$Configuration = "Release",
[switch]$SkipPublish
)
Expand Down
2 changes: 1 addition & 1 deletion build/build-release.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
param(
[string]$Version = "1.4.2",
[string]$Version = "1.4.3",
[string]$Configuration = "Release",
[string]$Runtime = "win-x64"
)
Expand Down
2 changes: 1 addition & 1 deletion build/package-release-zips.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
param(
[string]$Version = "1.4.2"
[string]$Version = "1.4.3"
)

$ErrorActionPreference = "Stop"
Expand Down
4 changes: 2 additions & 2 deletions chocolatey/threadpilot.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>threadpilot</id>
<version>1.4.2</version>
<version>1.4.3</version>
<title>ThreadPilot</title>
<authors>Prime Build</authors>
<projectUrl>https://github.com/PrimeBuild-pc/ThreadPilot</projectUrl>
Expand All @@ -15,7 +15,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Advanced Windows process and power plan manager with rules automation and performance controls.</description>
<summary>ThreadPilot process and power plan manager.</summary>
<releaseNotes>https://github.com/PrimeBuild-pc/ThreadPilot/releases/tag/v1.4.2</releaseNotes>
<releaseNotes>https://github.com/PrimeBuild-pc/ThreadPilot/releases/tag/v1.4.3</releaseNotes>
<tags>threadpilot process powerplan performance windows</tags>
</metadata>
<files>
Expand Down
13 changes: 13 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

All notable changes to this project are documented in this file.

## v1.4.3 - Expanded bundled power-plan catalog

### Added

- Added 30 new bundled Windows power plans.
- Added structural, discovery, duplicate, packaging, and invalid-file tests for bundled power plans.

### Changed

- Updated four existing bundled power plans and removed one redundant duplicate asset.
- Fixed parsing of power-plan display names containing parentheses.
- Confirmed automatic inclusion in build, publish, installer, and portable ZIP output.

## v1.4.2 - Persistent CPU-priority rule verification and retry

### Fixed
Expand Down
26 changes: 13 additions & 13 deletions docs/release/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
## ThreadPilot v1.4.2
## ThreadPilot v1.4.3

Patch release fixing persistent CPU-priority rules that are reverted shortly after process startup. Fixes #32.
Patch release expanding and validating ThreadPilot's bundled Windows power-plan catalog.

### Fixed
### Added

- Persistent CPU-priority rules now verify the applied priority.
- ThreadPilot detects when a process resets its CPU priority shortly after startup.
- A single bounded retry is performed when a verified priority is reverted.
- Retry state is cleared when the process exits.
- Improved activity logging distinguishes initial apply, reversion, retry, verification, and final failure.
- Added attribution for internal CPU-priority writes.
- Fixed misleading success reporting when the requested priority did not remain applied.
- Added 30 new bundled Windows power plans: 0 Synez Public Power, arsenha low latency, arsenha low latency (Intel Thread Director fix), AutoOS, BEYOND PERFORMANCE AMD+INTEL, Bitsum Highest Performance, cactusOS, FPSHEAVEN2026, GALA's ultimate performance (AMD), Gavot Performance, GTweaks Power Plan V3, imribiy2026, IrisFixed, JokrOS Power Plan, Jackpot2026, Kizzimo's Extreme Low Latency, KSOS11, melody LowestLatency, Microsoft High performance, Microsoft Ultimate Performance, Mitstas IDLE ENABLED, n1kobg GPU Booster Power Plan, Prodazin Power Plan, Reticle v2, RevisionPowerPlanV2.8, RIP Tweaks Power Plan, Rosca Tweaks v2, Velo's Power Plan, VTRL Optimized, and XNRL Pro Plan.
- Added structural, discovery, duplicate, packaging, and invalid-file tests for bundled power plans.

### Changed

- Updated four existing bundled plans: IIIEXOIII LOW LATENCY, LLG parking/E-core fix, Slower, and xilly.
- Removed a redundant duplicate Sazinho power-plan file.
- Power-plan display-name parsing now preserves names containing parentheses.

### Validation

- 555 automated tests passed with no failures.
- The general apply → verify → detect revert → one retry → final verify lifecycle was validated with a real process that resets its priority after startup.
- No executable-specific retry logic or continuous polling was added.
- Every changed `.pow` file was imported successfully with `powercfg` using a temporary GUID and then removed; the active Windows power plan remained unchanged.
- Bundled assets are discovered automatically and copied to build, publish, portable ZIP, and installer output through the existing project and release workflow.
20 changes: 20 additions & 0 deletions docs/releases/v1.4.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ThreadPilot v1.4.3

Patch release expanding and validating ThreadPilot's bundled Windows power-plan catalog.

## Added

- Added 30 new bundled Windows power plans.
- Added tests covering bundled asset structure, names, duplicates, discovery, build/publish configuration, and invalid files.

## Changed

- Updated IIIEXOIII LOW LATENCY, LLG parking/E-core fix, Slower, and xilly.
- Removed a redundant duplicate Sazinho power-plan file.
- Preserved parentheses in power-plan display names returned by `powercfg`.

## Validation

- Every changed `.pow` file was imported successfully with `powercfg` using a temporary GUID and removed immediately afterward.
- The active Windows power plan remained unchanged.
- Existing MSBuild, installer, portable ZIP, and GitHub Actions globs include the complete `Powerplans` directory without a separate manifest.
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
sonar.projectKey=threadpilot
sonar.projectName=ThreadPilot
sonar.projectVersion=1.4.2
sonar.projectVersion=1.4.3

sonar.sourceEncoding=UTF-8
sonar.sources=.
Expand Down
Loading