Skip to content

Commit

Permalink
1.1.0
Browse files Browse the repository at this point in the history
- Removed incompatible unit tests
- Scaled all images to the same size to reduce file size
- Compressed binaries with 7-Zip to reduce file size
- The 'Your Phone' app is no longer classified as bloatware
- Replaced OSHI library with native Java to reduce file size
- Minor code changes and improvements
  • Loading branch information
Foulest committed Jun 20, 2024
1 parent 1f8f098 commit c10617b
Show file tree
Hide file tree
Showing 53 changed files with 105 additions and 340 deletions.
31 changes: 1 addition & 30 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {
}

group = 'net.foulest'
version = '1.0.9'
version = '1.1.0'
description = 'RepairKit'

compileJava.options.encoding = 'UTF-8'
Expand All @@ -29,15 +29,6 @@ dependencies {
// https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform
implementation group: 'net.java.dev.jna', name: 'jna-platform', version: '5.14.0'

// OSHI - for system information
// https://central.sonatype.com/artifact/com.github.oshi/oshi-core
implementation group: 'com.github.oshi', name: 'oshi-core', version: '6.6.1'

// SLF4J Simple - for logging
// https://mvnrepository.com/artifact/org.slf4j/slf4j-simple
implementation group: 'org.slf4j', name: 'slf4j-simple', version: '2.0.13'
testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: '2.0.13'

// JetBrains Annotations - for code inspection and documentation
// https://mvnrepository.com/artifact/org.jetbrains/annotations
compileOnly group: 'org.jetbrains', name: 'annotations', version: '24.1.0'
Expand All @@ -46,18 +37,6 @@ dependencies {
// https://projectlombok.org
compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.32'
annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.32'

// JUnit 5 - for testing
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.11.0-M2'
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.11.0-M2'

// Mockito - for mocking objects
// https://mvnrepository.com/artifact/org.mockito/mockito-core
// https://mvnrepository.com/artifact/org.mockito/mockito-inline
testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.11.0'
testImplementation group: 'org.mockito', name: 'mockito-inline', version: '4.11.0'
}

launch4j {
Expand Down Expand Up @@ -138,14 +117,6 @@ tasks {
finalizedBy(zipLaunch4jDir)
}

test {
useJUnitPlatform()

testLogging {
events "passed", "skipped", "failed"
}
}

compileJava {
dependsOn(clean)
}
Expand Down
2 changes: 0 additions & 2 deletions src/main/java/net/foulest/repairkit/RepairKit.java
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,6 @@ public JPanel createBannerPanel() {
// Creates the RepairKit icon image.
debug("Creating the RepairKit icon image...");
ImageIcon imageIcon = getImageIcon("icons/RepairKit.png");
Image scaledImage = imageIcon.getImage().getScaledInstance(40, 40, Image.SCALE_SMOOTH);
imageIcon = new ImageIcon(scaledImage);
JLabel iconLabel = new JLabel(imageIcon);
iconLabel.setBounds(10, 10, 40, 40);
bannerPanel.add(iconLabel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,6 @@ private static void removeBloatware() {
"Microsoft.WindowsFeedbackHub", // Feedback Hub
"Microsoft.WindowsMaps", // Maps
"Microsoft.WindowsPhone", // Windows Phone
"Microsoft.YourPhone", // Your Phone
"PandoraMediaInc.29680B314EFC2", // Pandora
"ShazamEntertainmentLtd.Shazam", // Shazam
"king.com.CandyCrushSaga", // Candy Crush Saga
Expand Down Expand Up @@ -921,13 +920,12 @@ private static void runServiceTweaks() {
new String[]{"RetailDemo", "Retail Demo"},
new String[]{"VSStandardCollectorService150", "Visual Studio Standard Collector Service"},
new String[]{"WMPNetworkSvc", "Windows Media Player Network Sharing Service"},
new String[]{"WpcMonSvc", "Parental Controls"},
new String[]{"diagnosticshub.standardcollector.service", "Diagnostics Hub Standard Collector Service"},
new String[]{"diagsvc", "Diagnostic Execution Service"},
new String[]{"dmwappushservice", "WAP Push Message Routing Service"},
new String[]{"fhsvc", "File History Service"},
new String[]{"lmhosts", "TCP/IP NetBIOS Helper"},
new String[]{"wercplsupport", "wercplsupport"},
new String[]{"wercplsupport", "Problem Reports Control Panel Support"},
new String[]{"wersvc", "wersvc"}
);

Expand Down Expand Up @@ -1118,7 +1116,7 @@ private static void runWindowsDefenderTweaks() {
() -> runPowerShellCommand("Add-MpPreference"
+ " -AttackSurfaceReductionRules_Ids "
+ "56a863a9-875e-4185-98a7-b882c64b5ce5," // ASR: Warn against abuse of exploited vulnerable signed drivers
+ "a8f5898e-1dc8-49a9-9878-85004b8a61e6" // ASR: Warn against Webshell creation for Servers
+ "a8f5898e-1dc8-49a9-9878-85004b8a61e6" // ASR: Warn against Webshell creation for servers
+ " -AttackSurfaceReductionRules_Actions Warn", false)
);

Expand Down
28 changes: 14 additions & 14 deletions src/main/java/net/foulest/repairkit/panels/UsefulPrograms.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public void setupCPUZ() {
"Displays system hardware information.",
new Rectangle(baseWidth, baseHeight + 50, 200, 30),
new Color(200, 200, 200),
"CPU-Z.zip",
"CPU-Z.7z",
"CPU-Z.exe",
true, tempDirectory.getPath()
);
Expand Down Expand Up @@ -155,7 +155,7 @@ public void setupHWMonitor() {
"Displays hardware voltages & temperatures.",
new Rectangle(baseWidth, baseHeight + 50, 200, 30),
new Color(200, 200, 200),
"HWMonitor.zip",
"HWMonitor.7z",
"HWMonitor.exe",
true, tempDirectory.getPath()
);
Expand Down Expand Up @@ -195,7 +195,7 @@ public void setupAutoruns() {
"Displays startup items.",
new Rectangle(baseWidth, baseHeight + 50, 200, 30),
new Color(200, 200, 200),
"Autoruns.zip",
"Autoruns.7z",
"Autoruns.exe",
true, tempDirectory.getPath()
);
Expand Down Expand Up @@ -235,7 +235,7 @@ public void setupProcessExplorer() {
"Displays system processes.",
new Rectangle(baseWidth, baseHeight + 50, 200, 30),
new Color(200, 200, 200),
"ProcessExplorer.zip",
"ProcessExplorer.7z",
"ProcessExplorer.exe",
true, tempDirectory.getPath()
);
Expand Down Expand Up @@ -286,7 +286,7 @@ public void setupTreeSize() {
"Displays system files organized by size.",
new Rectangle(baseWidth, baseHeight + 50, 200, 30),
new Color(200, 200, 200),
"TreeSize.zip",
"TreeSize.7z",
"TreeSize.exe",
true, tempDirectory.getPath()
);
Expand Down Expand Up @@ -327,7 +327,7 @@ public void setupEverything() {
"Displays all files on your system.",
new Rectangle(baseWidth, baseHeight + 50, 200, 30),
new Color(200, 200, 200),
"Everything.zip",
"Everything.7z",
"Everything.exe",
true, tempDirectory.getPath()
);
Expand Down Expand Up @@ -388,7 +388,7 @@ public void setupFanControl() {
// If FanControl is not running, extract and launch it.
// Otherwise, launch the existing instance.
if (!isProcessRunning("FanControl.exe")) {
launchApplication("FanControl.zip", "\\FanControl.exe",
launchApplication("FanControl.7z", "\\FanControl.exe",
true, System.getenv("APPDATA") + "\\FanControl");
} else {
runCommand("start \"\" \"" + fanControlPath + "\"", false);
Expand Down Expand Up @@ -440,9 +440,9 @@ public void setupNVCleanstall() {
return;
}

try (InputStream input = RepairKit.class.getClassLoader().getResourceAsStream("bin/NVCleanstall.zip")) {
saveFile(Objects.requireNonNull(input), tempDirectory + "\\NVCleanstall.zip", true);
unzipFile(tempDirectory + "\\NVCleanstall.zip", tempDirectory.getPath());
try (InputStream input = RepairKit.class.getClassLoader().getResourceAsStream("bin/NVCleanstall.7z")) {
saveFile(Objects.requireNonNull(input), tempDirectory + "\\NVCleanstall.7z", true);
unzipFile(tempDirectory + "\\NVCleanstall.7z", tempDirectory.getPath());
runCommand(tempDirectory + "\\NVCleanstall.exe", true);
} catch (IOException ex) {
ex.printStackTrace();
Expand Down Expand Up @@ -496,7 +496,7 @@ public void setupEmsisoftScan() {
"Scans for malware with Emsisoft.",
new Rectangle(baseWidth, baseHeight + 50, 200, 30),
new Color(200, 200, 200),
"Emsisoft.zip",
"Emsisoft.7z",
"Emsisoft.exe",
true, tempDirectory.getPath()
);
Expand Down Expand Up @@ -537,9 +537,9 @@ public void setupSophosScan() {
"Scans for malware with Sophos.",
new Rectangle(baseWidth, baseHeight + 50, 200, 30),
new Color(200, 200, 200), () -> {
try (InputStream input = RepairKit.class.getClassLoader().getResourceAsStream("bin/Sophos.zip")) {
saveFile(Objects.requireNonNull(input), tempDirectory + "\\Sophos.zip", true);
unzipFile(tempDirectory + "\\Sophos.zip", tempDirectory.getPath());
try (InputStream input = RepairKit.class.getClassLoader().getResourceAsStream("bin/Sophos.7z")) {
saveFile(Objects.requireNonNull(input), tempDirectory + "\\Sophos.7z", true);
unzipFile(tempDirectory + "\\Sophos.7z", tempDirectory.getPath());
runCommand("start \"\" \"" + tempDirectory + "\\Sophos.exe\"", true);
} catch (IOException ex) {
ex.printStackTrace();
Expand Down
73 changes: 52 additions & 21 deletions src/main/java/net/foulest/repairkit/util/DebugUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import oshi.hardware.GraphicsCard;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.util.FormatUtil;

import java.io.IOException;
import java.nio.file.Files;
Expand All @@ -21,6 +15,7 @@
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

import static net.foulest.repairkit.util.CommandUtil.getCommandOutput;
import static net.foulest.repairkit.util.UpdateUtil.CONNECTED_TO_INTERNET;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
Expand Down Expand Up @@ -66,6 +61,10 @@ public static void printSystemInfo(String[] args) {
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MMM dd, yyyy");
String formattedDate = LocalDate.now().format(dateFormatter);

List<String> cpuInfo = getCommandOutput("wmic cpu get name,NumberOfCores,NumberOfLogicalProcessors", false, false);
List<String> memoryInfo = getCommandOutput("wmic memorychip get capacity", false, false);
List<String> gpuInfo = getCommandOutput("wmic path win32_VideoController get name", false, false);

debug("");
debug("RepairKit Version: " + UpdateUtil.getLatestReleaseVersion());
debug("System Date: " + formattedDate);
Expand All @@ -80,24 +79,12 @@ public static void printSystemInfo(String[] args) {
debug("- Temp Directory: " + System.getenv("TEMP"));
debug("- Security Software: " + (securitySoftware.isEmpty() ? "No Antivirus Found" : String.join(", ", securitySoftware)));

SystemInfo systemInfo = new SystemInfo();
HardwareAbstractionLayer hal = systemInfo.getHardware();
CentralProcessor processor = hal.getProcessor();
GlobalMemory memory = hal.getMemory();

debug("");
debug("Hardware Information");

debug("- CPU: " + processor.getProcessorIdentifier().getName().trim() + " ("
+ processor.getPhysicalProcessorCount() + "c, "
+ processor.getLogicalProcessorCount() + "t)");

for (GraphicsCard card : hal.getGraphicsCards()) {
debug("- GPU: " + card.getName().trim() + " (" + FormatUtil.formatBytes(card.getVRam()) + ")");
}

debug("- Memory: " + FormatUtil.formatBytes(memory.getAvailable())
+ " / " + FormatUtil.formatBytes(memory.getTotal()));
debug(formatCpuInfo(cpuInfo));
debug(formatMemoryInfo(memoryInfo));
debug(formatGpuInfo(gpuInfo));

debug("- Network Connection: " + CONNECTED_TO_INTERNET);
debug("");
Expand All @@ -112,4 +99,48 @@ public static void createLogFile() {
ex.printStackTrace();
}
}

private static @NotNull String formatCpuInfo(@NotNull List<String> cpuInfo) {
for (String line : cpuInfo) {
if (line.trim().isEmpty() || line.contains("Name")) {
continue;
}

String[] parts = line.trim().split("\\s{2,}");

if (parts.length == 3) {
return "- CPU: " + parts[0] + " (" + parts[1] + "c, " + parts[2] + "t)";
}
}
return "- CPU: Information not available";
}

private static @NotNull String formatMemoryInfo(@NotNull List<String> memoryInfo) {
long totalMemory = 0;

for (String line : memoryInfo) {
if (line.trim().isEmpty() || line.contains("Capacity")) {
continue;
}

try {
totalMemory += Long.parseLong(line.trim());
} catch (NumberFormatException ex) {
ex.printStackTrace();
}
}

long totalMemoryGB = totalMemory / (1024 * 1024 * 1024);
return "- Memory: " + totalMemoryGB / 2 + " GB available (" + totalMemoryGB + " GB total)";
}

private static @NotNull String formatGpuInfo(@NotNull List<String> gpuInfo) {
for (String line : gpuInfo) {
if (line.trim().isEmpty() || line.contains("Name")) {
continue;
}
return "- GPU: " + line.trim();
}
return "- GPU: Information not available";
}
}
62 changes: 36 additions & 26 deletions src/main/java/net/foulest/repairkit/util/FileUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static net.foulest.repairkit.util.CommandUtil.runCommand;
import static net.foulest.repairkit.util.DebugUtil.debug;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
Expand All @@ -54,40 +55,49 @@ public static void unzipFile(String fileZip, String fileDest) {

debug("Unzipping file: " + fileZip + " to " + fileDest);

try {
Path sourcePath = Paths.get(fileZip);
Path targetPath = Paths.get(fileDest);
if (fileZip.endsWith(".zip")) {
try {
Path sourcePath = Paths.get(fileZip);
Path targetPath = Paths.get(fileDest);

try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(sourcePath))) {
ZipEntry zipEntry = zis.getNextEntry();
try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(sourcePath))) {
ZipEntry zipEntry = zis.getNextEntry();

while (zipEntry != null) {
debug("Opening zip entry: " + zipEntry.getName());
Path newPath = targetPath.resolve(zipEntry.getName()).normalize();
while (zipEntry != null) {
debug("Opening zip entry: " + zipEntry.getName());
Path newPath = targetPath.resolve(zipEntry.getName()).normalize();

// Check for path traversal vulnerabilities
if (!newPath.startsWith(targetPath)) {
throw new IOException("Bad zip entry (potential path traversal): " + zipEntry.getName());
}
// Check for path traversal vulnerabilities
if (!newPath.startsWith(targetPath)) {
throw new IOException("Bad zip entry (potential path traversal): " + zipEntry.getName());
}

if (zipEntry.isDirectory()) {
debug("Creating directory: " + newPath);
Files.createDirectories(newPath);
} else {
debug("Creating file: " + newPath);
Files.createDirectories(newPath.getParent());
if (zipEntry.isDirectory()) {
debug("Creating directory: " + newPath);
Files.createDirectories(newPath);
} else {
debug("Creating file: " + newPath);
Files.createDirectories(newPath.getParent());

debug("Copying file: " + newPath);
Files.copy(zis, newPath, StandardCopyOption.REPLACE_EXISTING);
}
debug("Copying file: " + newPath);
Files.copy(zis, newPath, StandardCopyOption.REPLACE_EXISTING);
}

debug("Closing zip entry: " + zipEntry.getName());
zipEntry = zis.getNextEntry();
debug("Closing zip entry: " + zipEntry.getName());
zipEntry = zis.getNextEntry();
}
}
} catch (IOException ex) {
debug("[WARN] Failed to unzip file: " + fileZip + " to " + fileDest);
ex.printStackTrace();
}
} else {
try (InputStream input = RepairKit.class.getClassLoader().getResourceAsStream("bin/7zr.exe")) {
saveFile(Objects.requireNonNull(input), tempDirectory + "\\7zr.exe", true);
runCommand("\"" + tempDirectory + "\\7zr.exe\" x \"" + fileZip + "\"" + " -y -o\"" + tempDirectory, false);
} catch (IOException ex) {
ex.printStackTrace();
}
} catch (IOException ex) {
debug("[WARN] Failed to unzip file: " + fileZip + " to " + fileDest);
ex.printStackTrace();
}
}

Expand Down
Binary file added src/main/resources/bin/7zr.exe
Binary file not shown.
Binary file added src/main/resources/bin/Autoruns.7z
Binary file not shown.
Binary file removed src/main/resources/bin/Autoruns.zip
Binary file not shown.
Binary file added src/main/resources/bin/CPU-Z.7z
Binary file not shown.
Binary file removed src/main/resources/bin/CPU-Z.zip
Binary file not shown.
Binary file not shown.
Binary file removed src/main/resources/bin/Emsisoft.zip
Binary file not shown.
Binary file added src/main/resources/bin/Everything.7z
Binary file not shown.
Binary file removed src/main/resources/bin/Everything.zip
Binary file not shown.
Binary file added src/main/resources/bin/FanControl.7z
Binary file not shown.
Binary file removed src/main/resources/bin/FanControl.zip
Binary file not shown.
Binary file added src/main/resources/bin/HWMonitor.7z
Binary file not shown.
Binary file removed src/main/resources/bin/HWMonitor.zip
Binary file not shown.
Binary file not shown.
Binary file added src/main/resources/bin/ProcessExplorer.7z
Binary file not shown.
Binary file removed src/main/resources/bin/ProcessExplorer.zip
Binary file not shown.
Binary file added src/main/resources/bin/Sophos.7z
Binary file not shown.
Binary file not shown.
Binary file modified src/main/resources/icons/AppsFeatures.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/Autoruns.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/CPU-Z.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/DeviceManager.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/DiskCleanup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/DisplaySettings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/Emsisoft.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/Everything.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/FanControl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/HWMonitor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/MSConfig.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/NVCleanstall.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/ProcessExplorer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/main/resources/icons/RepairKit-32x32.png
Binary file not shown.
Binary file modified src/main/resources/icons/RepairKit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/Sophos.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/SoundSettings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/StartupApps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/StorageSettings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/TaskManager.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/TrafficLight.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/TreeSize.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/WindowsSecurity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/WindowsUpdate.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/main/resources/icons/uBlockOrigin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit c10617b

Please sign in to comment.