mirror of
https://github.com/jkocon/g-helper.git
synced 2026-02-23 13:00:52 +01:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a71a64c96 | ||
|
|
ac69f1317e | ||
|
|
4b38d380b5 | ||
|
|
9a2f9afe5b |
144
AppConfig.cs
Normal file
144
AppConfig.cs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
public class AppConfig
|
||||||
|
{
|
||||||
|
|
||||||
|
string appPath;
|
||||||
|
string configFile;
|
||||||
|
|
||||||
|
public Dictionary<string, object> config = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
public AppConfig()
|
||||||
|
{
|
||||||
|
|
||||||
|
appPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\GHelper";
|
||||||
|
configFile = appPath + "\\config.json";
|
||||||
|
|
||||||
|
if (!System.IO.Directory.Exists(appPath))
|
||||||
|
System.IO.Directory.CreateDirectory(appPath);
|
||||||
|
|
||||||
|
if (File.Exists(configFile))
|
||||||
|
{
|
||||||
|
string text = File.ReadAllText(configFile);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
config = JsonSerializer.Deserialize<Dictionary<string, object>>(text);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
initConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
initConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initConfig()
|
||||||
|
{
|
||||||
|
config = new Dictionary<string, object>();
|
||||||
|
config["performance_mode"] = 0;
|
||||||
|
string jsonString = JsonSerializer.Serialize(config);
|
||||||
|
File.WriteAllText(configFile, jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getConfig(string name)
|
||||||
|
{
|
||||||
|
if (config.ContainsKey(name))
|
||||||
|
return int.Parse(config[name].ToString());
|
||||||
|
else return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string getConfigString(string name)
|
||||||
|
{
|
||||||
|
if (config.ContainsKey(name))
|
||||||
|
return config[name].ToString();
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfig(string name, int value)
|
||||||
|
{
|
||||||
|
config[name] = value;
|
||||||
|
string jsonString = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
File.WriteAllText(configFile, jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfig(string name, string value)
|
||||||
|
{
|
||||||
|
config[name] = value;
|
||||||
|
string jsonString = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
File.WriteAllText(configFile, jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string getParamName(int device, string paramName = "fan_profile")
|
||||||
|
{
|
||||||
|
int mode = getConfig("performance_mode");
|
||||||
|
string name;
|
||||||
|
|
||||||
|
if (device == 1)
|
||||||
|
name = "gpu";
|
||||||
|
else
|
||||||
|
name = "cpu";
|
||||||
|
|
||||||
|
return paramName+"_" + name + "_" + mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getFanConfig(int device)
|
||||||
|
{
|
||||||
|
string curveString = getConfigString(getParamName(device));
|
||||||
|
byte[] curve = { };
|
||||||
|
|
||||||
|
if (curveString is not null)
|
||||||
|
curve = StringToBytes(curveString);
|
||||||
|
|
||||||
|
return curve;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFanConfig(int device, byte[] curve)
|
||||||
|
{
|
||||||
|
string bitCurve = BitConverter.ToString(curve);
|
||||||
|
setConfig(getParamName(device), bitCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte[] StringToBytes(string str)
|
||||||
|
{
|
||||||
|
String[] arr = str.Split('-');
|
||||||
|
byte[] array = new byte[arr.Length];
|
||||||
|
for (int i = 0; i < arr.Length; i++) array[i] = Convert.ToByte(arr[i], 16);
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getDefaultCurve(int device)
|
||||||
|
{
|
||||||
|
int mode = getConfig("performance_mode");
|
||||||
|
byte[] curve;
|
||||||
|
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
if (device == 1)
|
||||||
|
curve = StringToBytes("14-3F-44-48-4C-50-54-62-16-1F-26-2D-39-47-55-5F");
|
||||||
|
else
|
||||||
|
curve = StringToBytes("14-3F-44-48-4C-50-54-62-11-1A-22-29-34-43-51-5A");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (device == 1)
|
||||||
|
curve = StringToBytes("3C-41-42-46-47-4B-4C-62-08-11-11-1D-1D-26-26-2D");
|
||||||
|
else
|
||||||
|
curve = StringToBytes("3C-41-42-46-47-4B-4C-62-03-0C-0C-16-16-22-22-29");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (device == 1)
|
||||||
|
curve = StringToBytes("3A-3D-40-44-48-4D-51-62-0C-16-1D-1F-26-2D-34-4A");
|
||||||
|
else
|
||||||
|
curve = StringToBytes("3A-3D-40-44-48-4D-51-62-08-11-16-1A-22-29-30-45");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return curve;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
Fans.cs
11
Fans.cs
@@ -43,10 +43,19 @@ namespace GHelper
|
|||||||
chart.ChartAreas[0].AxisX.Minimum = 10;
|
chart.ChartAreas[0].AxisX.Minimum = 10;
|
||||||
chart.ChartAreas[0].AxisX.Maximum = 100;
|
chart.ChartAreas[0].AxisX.Maximum = 100;
|
||||||
chart.ChartAreas[0].AxisX.Interval = 10;
|
chart.ChartAreas[0].AxisX.Interval = 10;
|
||||||
|
|
||||||
chart.ChartAreas[0].AxisY.Minimum = 0;
|
chart.ChartAreas[0].AxisY.Minimum = 0;
|
||||||
chart.ChartAreas[0].AxisY.Maximum = 100;
|
chart.ChartAreas[0].AxisY.Maximum = 100;
|
||||||
|
|
||||||
chart.ChartAreas[0].AxisX.Interval = 10;
|
chart.ChartAreas[0].AxisY.LabelStyle.Font = new Font("Arial", 7F);
|
||||||
|
|
||||||
|
chart.ChartAreas[0].AxisY.CustomLabels.Add(-2, 2, "OFF");
|
||||||
|
|
||||||
|
for (int i = 1; i<= 9;i++)
|
||||||
|
chart.ChartAreas[0].AxisY.CustomLabels.Add(i*10-2, i*10+2, (1800+400*i).ToString());
|
||||||
|
|
||||||
|
chart.ChartAreas[0].AxisY.CustomLabels.Add(98, 102, "RPM");
|
||||||
|
|
||||||
chart.ChartAreas[0].AxisY.Interval = 10;
|
chart.ChartAreas[0].AxisY.Interval = 10;
|
||||||
|
|
||||||
if (chart.Legends.Count > 0)
|
if (chart.Legends.Count > 0)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<AssemblyName>GHelper</AssemblyName>
|
<AssemblyName>GHelper</AssemblyName>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
|
||||||
<AssemblyVersion>0.11.0</AssemblyVersion>
|
<AssemblyVersion>0.12.0</AssemblyVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
144
Program.cs
144
Program.cs
@@ -1,150 +1,6 @@
|
|||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Management;
|
using System.Management;
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
public class AppConfig
|
|
||||||
{
|
|
||||||
|
|
||||||
string appPath;
|
|
||||||
string configFile;
|
|
||||||
|
|
||||||
public Dictionary<string, object> config = new Dictionary<string, object>();
|
|
||||||
|
|
||||||
public AppConfig()
|
|
||||||
{
|
|
||||||
|
|
||||||
appPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\GHelper";
|
|
||||||
configFile = appPath + "\\config.json";
|
|
||||||
|
|
||||||
if (!System.IO.Directory.Exists(appPath))
|
|
||||||
System.IO.Directory.CreateDirectory(appPath);
|
|
||||||
|
|
||||||
if (File.Exists(configFile))
|
|
||||||
{
|
|
||||||
string text = File.ReadAllText(configFile);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
config = JsonSerializer.Deserialize<Dictionary<string, object>>(text);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
initConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
initConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initConfig()
|
|
||||||
{
|
|
||||||
config = new Dictionary<string, object>();
|
|
||||||
config["performance_mode"] = 0;
|
|
||||||
string jsonString = JsonSerializer.Serialize(config);
|
|
||||||
File.WriteAllText(configFile, jsonString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getConfig(string name)
|
|
||||||
{
|
|
||||||
if (config.ContainsKey(name))
|
|
||||||
return int.Parse(config[name].ToString());
|
|
||||||
else return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string getConfigString(string name)
|
|
||||||
{
|
|
||||||
if (config.ContainsKey(name))
|
|
||||||
return config[name].ToString();
|
|
||||||
else return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConfig(string name, int value)
|
|
||||||
{
|
|
||||||
config[name] = value;
|
|
||||||
string jsonString = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
|
|
||||||
File.WriteAllText(configFile, jsonString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConfig(string name, string value)
|
|
||||||
{
|
|
||||||
config[name] = value;
|
|
||||||
string jsonString = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
|
|
||||||
File.WriteAllText(configFile, jsonString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string getFanName(int device)
|
|
||||||
{
|
|
||||||
int mode = getConfig("performance_mode");
|
|
||||||
string name;
|
|
||||||
|
|
||||||
if (device == 1)
|
|
||||||
name = "gpu";
|
|
||||||
else
|
|
||||||
name = "cpu";
|
|
||||||
|
|
||||||
return "fan_profile_" + name + "_" + mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getFanConfig(int device)
|
|
||||||
{
|
|
||||||
string curveString = getConfigString(getFanName(device));
|
|
||||||
byte[] curve = { };
|
|
||||||
|
|
||||||
if (curveString is not null)
|
|
||||||
curve = StringToBytes(curveString);
|
|
||||||
|
|
||||||
return curve;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFanConfig(int device, byte[] curve)
|
|
||||||
{
|
|
||||||
string bitCurve = BitConverter.ToString(curve);
|
|
||||||
setConfig(getFanName(device), bitCurve);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static byte[] StringToBytes(string str)
|
|
||||||
{
|
|
||||||
String[] arr = str.Split('-');
|
|
||||||
byte[] array = new byte[arr.Length];
|
|
||||||
for (int i = 0; i < arr.Length; i++) array[i] = Convert.ToByte(arr[i], 16);
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getDefaultCurve(int device)
|
|
||||||
{
|
|
||||||
int mode = getConfig("performance_mode");
|
|
||||||
byte[] curve;
|
|
||||||
|
|
||||||
switch (mode)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
if (device == 1)
|
|
||||||
curve = StringToBytes("14-3F-44-48-4C-50-54-62-16-1F-26-2D-39-47-55-5F");
|
|
||||||
else
|
|
||||||
curve = StringToBytes("14-3F-44-48-4C-50-54-62-11-1A-22-29-34-43-51-5A");
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
if (device == 1)
|
|
||||||
curve = StringToBytes("3C-41-42-46-47-4B-4C-62-08-11-11-1D-1D-26-26-2D");
|
|
||||||
else
|
|
||||||
curve = StringToBytes("3C-41-42-46-47-4B-4C-62-03-0C-0C-16-16-22-22-29");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (device == 1)
|
|
||||||
curve = StringToBytes("3A-3D-40-44-48-4D-51-62-0C-16-1D-1F-26-2D-34-4A");
|
|
||||||
else
|
|
||||||
curve = StringToBytes("3A-3D-40-44-48-4D-51-62-08-11-16-1A-22-29-30-45");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return curve;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HardwareMonitor
|
public class HardwareMonitor
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ A small utility that allows you do almost everyting you could do with Armory Cra
|
|||||||
1. Switch between default **Performance modes** - Silent / Balanced / Turbo and apply default fan curves
|
1. Switch between default **Performance modes** - Silent / Balanced / Turbo and apply default fan curves
|
||||||
2. Switch between Eco / Standard or Ultimate **GPU modes**
|
2. Switch between Eco / Standard or Ultimate **GPU modes**
|
||||||
3. Change laptop screen refresh rate - 60hz or your maximum (120hz, 144hz, etc depending on the model) with display overdrive (OD)
|
3. Change laptop screen refresh rate - 60hz or your maximum (120hz, 144hz, etc depending on the model) with display overdrive (OD)
|
||||||
4. View default fan profiles for every mode and apply custom ones
|
4. View default fan profiles for every mode and **auto apply** custom ones
|
||||||
5. Control keyboard backlit animation and colors
|
5. Control keyboard backlit animation and colors
|
||||||
6. Set battery charge limit to preserve battery
|
6. Set battery charge limit to preserve battery
|
||||||
7. Monitor CPU temperature, fan speeds and battery discharge rate
|
7. Monitor CPU temperature, fan speeds and battery discharge rate
|
||||||
|
|||||||
12
Settings.Designer.cs
generated
12
Settings.Designer.cs
generated
@@ -132,12 +132,12 @@
|
|||||||
// labelGPUFan
|
// labelGPUFan
|
||||||
//
|
//
|
||||||
labelGPUFan.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
labelGPUFan.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||||
labelGPUFan.Location = new Point(410, 262);
|
labelGPUFan.Location = new Point(338, 262);
|
||||||
labelGPUFan.Margin = new Padding(4, 0, 4, 0);
|
labelGPUFan.Margin = new Padding(4, 0, 4, 0);
|
||||||
labelGPUFan.Name = "labelGPUFan";
|
labelGPUFan.Name = "labelGPUFan";
|
||||||
labelGPUFan.Size = new Size(276, 32);
|
labelGPUFan.Size = new Size(348, 32);
|
||||||
labelGPUFan.TabIndex = 8;
|
labelGPUFan.TabIndex = 8;
|
||||||
labelGPUFan.Text = "GPU Fan : 0%";
|
labelGPUFan.Text = "GPU Fan";
|
||||||
labelGPUFan.TextAlign = ContentAlignment.TopRight;
|
labelGPUFan.TextAlign = ContentAlignment.TopRight;
|
||||||
//
|
//
|
||||||
// tableGPU
|
// tableGPU
|
||||||
@@ -226,12 +226,12 @@
|
|||||||
// labelCPUFan
|
// labelCPUFan
|
||||||
//
|
//
|
||||||
labelCPUFan.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
labelCPUFan.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||||
labelCPUFan.Location = new Point(410, 38);
|
labelCPUFan.Location = new Point(320, 38);
|
||||||
labelCPUFan.Margin = new Padding(4, 0, 4, 0);
|
labelCPUFan.Margin = new Padding(4, 0, 4, 0);
|
||||||
labelCPUFan.Name = "labelCPUFan";
|
labelCPUFan.Name = "labelCPUFan";
|
||||||
labelCPUFan.Size = new Size(276, 32);
|
labelCPUFan.Size = new Size(366, 32);
|
||||||
labelCPUFan.TabIndex = 12;
|
labelCPUFan.TabIndex = 12;
|
||||||
labelCPUFan.Text = "CPU Fan : 0%";
|
labelCPUFan.Text = "CPU Fan";
|
||||||
labelCPUFan.TextAlign = ContentAlignment.TopRight;
|
labelCPUFan.TextAlign = ContentAlignment.TopRight;
|
||||||
//
|
//
|
||||||
// tablePerf
|
// tablePerf
|
||||||
|
|||||||
28
Settings.cs
28
Settings.cs
@@ -75,12 +75,21 @@ namespace GHelper
|
|||||||
labelVersion.Text = "Version " + Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
labelVersion.Text = "Version " + Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||||
labelVersion.Click += LabelVersion_Click;
|
labelVersion.Click += LabelVersion_Click;
|
||||||
|
|
||||||
|
labelCPUFan.Click += LabelCPUFan_Click;
|
||||||
|
labelGPUFan.Click += LabelCPUFan_Click;
|
||||||
|
|
||||||
SetTimer();
|
SetTimer();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LabelCPUFan_Click(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Program.config.setConfig("fan_rpm", (Program.config.getConfig("fan_rpm") == 1) ? 0 : 1);
|
||||||
|
RefreshSensors();
|
||||||
|
}
|
||||||
|
|
||||||
private void LabelVersion_Click(object? sender, EventArgs e)
|
private void LabelVersion_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Process.Start(new ProcessStartInfo("http://github.com/seerge/g-helper/releases") { UseShellExecute = true });
|
Process.Start(new ProcessStartInfo("http://github.com/seerge/g-helper/releases") { UseShellExecute = true });
|
||||||
@@ -128,7 +137,8 @@ namespace GHelper
|
|||||||
if (fans.Visible)
|
if (fans.Visible)
|
||||||
{
|
{
|
||||||
fans.Hide();
|
fans.Hide();
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
fans.Show();
|
fans.Show();
|
||||||
}
|
}
|
||||||
@@ -402,10 +412,20 @@ namespace GHelper
|
|||||||
aTimer.Enabled = false;
|
aTimer.Enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static string FormatFan(int fan)
|
||||||
|
{
|
||||||
|
if (Program.config.getConfig("fan_rpm") == 1)
|
||||||
|
return " Fan: " + (fan * 100).ToString() + "RPM";
|
||||||
|
else
|
||||||
|
return " Fan: " + Math.Round(fan / 0.6).ToString() + "%"; // relatively to 6000 rpm
|
||||||
|
}
|
||||||
|
|
||||||
private static void RefreshSensors()
|
private static void RefreshSensors()
|
||||||
{
|
{
|
||||||
string cpuFan = " Fan: " + Math.Round(Program.wmi.DeviceGet(ASUSWmi.CPU_Fan) / 0.6).ToString() + "%";
|
|
||||||
string gpuFan = " Fan: " + Math.Round(Program.wmi.DeviceGet(ASUSWmi.GPU_Fan) / 0.6).ToString() + "%";
|
string cpuFan = FormatFan(Program.wmi.DeviceGet(ASUSWmi.CPU_Fan));
|
||||||
|
string gpuFan = FormatFan(Program.wmi.DeviceGet(ASUSWmi.GPU_Fan));
|
||||||
|
|
||||||
string cpuTemp = "";
|
string cpuTemp = "";
|
||||||
string gpuTemp = "";
|
string gpuTemp = "";
|
||||||
@@ -443,7 +463,7 @@ namespace GHelper
|
|||||||
this.Top = Screen.FromControl(this).WorkingArea.Height - 10 - this.Height;
|
this.Top = Screen.FromControl(this).WorkingArea.Height - 10 - this.Height;
|
||||||
this.Activate();
|
this.Activate();
|
||||||
|
|
||||||
aTimer.Interval = 500;
|
aTimer.Interval = 100;
|
||||||
aTimer.Enabled = true;
|
aTimer.Enabled = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
11
Startup.cs
11
Startup.cs
@@ -10,17 +10,6 @@ public class Startup
|
|||||||
public static bool IsScheduled()
|
public static bool IsScheduled()
|
||||||
{
|
{
|
||||||
TaskService taskService = new TaskService();
|
TaskService taskService = new TaskService();
|
||||||
|
|
||||||
// cleanup of OLD autorun
|
|
||||||
try
|
|
||||||
{
|
|
||||||
taskService.RootFolder.DeleteTask("GSharpHelper");
|
|
||||||
} catch
|
|
||||||
{
|
|
||||||
Debug.WriteLine("Not running as admin");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (taskService.RootFolder.AllTasks.Any(t => t.Name == taskName));
|
return (taskService.RootFolder.AllTasks.Any(t => t.Name == taskName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.3 MiB |
Reference in New Issue
Block a user