Skip to content

Instantly share code, notes, and snippets.

@exceedsystem
Created May 25, 2022 14:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save exceedsystem/e07095a8bce91f683c357bde7062edd6 to your computer and use it in GitHub Desktop.
Save exceedsystem/e07095a8bce91f683c357bde7062edd6 to your computer and use it in GitHub Desktop.
EmoCheck automatic update tool
// EXCEEDSYSTEM EmoCheckUpdater
// https://www.exceedsystem.net/2022/05/26/how-to-update-emocheck-automatically
// License: MIT
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading;
namespace EmoCheckUpdater
{
internal static class Program
{
private const string APP_NAME = "EmoCheckUpdater";
private const string UPDATE_INFO_FILENAME = "updateinfo.json";
private const string EMOCHECK_FILENAME = "emocheck.exe";
private const string LOG_FILENAME = "emocheckupdater.log";
private const string EMOCHECK_API_URL = "https://api.github.com/repos/JPCERTCC/EmoCheck/releases/latest";
private const string USERAGENT_NAME = "Mozilla/5.0 (Windows NT 10.0)";
private const ProcessPriorityClass EMOCHECK_PROCESS_PRIORITY = ProcessPriorityClass.Idle;
private static readonly string MUTEX_NAME = $@"Global\MTX{Assembly.GetEntryAssembly().GetCustomAttribute<GuidAttribute>().Value}";
[STAThread]
public static int Main(string[] args)
{
if (Mutex.TryOpenExisting(MUTEX_NAME, out var mutex))
{
Log($"{APP_NAME} is already running.");
mutex.Dispose();
return -1;
}
using (mutex = new Mutex(true, MUTEX_NAME))
using (new Cleanup(() => mutex.ReleaseMutex()))
{
string ARCH = String.Empty;
switch (Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"))
{
case "x86":
ARCH = "x86";
break;
case "AMD64":
ARCH = "x64";
break;
}
if (ARCH.Length == 0)
{
Log("Unsupported processor architecture.");
return -1;
}
try
{
Log($"{APP_NAME} has started.");
var updateInfo = new UpdateInfo();
if (!File.Exists(UPDATE_INFO_FILENAME))
{
File.WriteAllText(UPDATE_INFO_FILENAME, JsonSerializer.Serialize<UpdateInfo>(updateInfo));
}
updateInfo = JsonSerializer.Deserialize<UpdateInfo>(File.ReadAllText(UPDATE_INFO_FILENAME));
if (updateInfo == null)
{
Log($"'{UPDATE_INFO_FILENAME}' is invalid data structure.");
return -1;
}
Latest latest = null;
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("User-Agent", USERAGENT_NAME);
latest = httpClient.GetFromJsonAsync<Latest>(EMOCHECK_API_URL).Result;
if (latest == null)
{
Log("Failed to get EmoCheck update information.");
return -1;
}
if (latest.tag_name != updateInfo.CurrentTagName)
{
if (latest.assets == null || latest.assets.Length == 0)
{
Log("Failed to get the tag information of EmoCheck.");
return -1;
}
var downloadUrl = latest.assets.First(asset => asset.browser_download_url.EndsWith($"{ARCH}.exe"))?.browser_download_url;
if (string.IsNullOrEmpty(downloadUrl))
{
Log($"Failed to get EmoCheck download URL.");
return -1;
}
var emocheckBin = httpClient.GetByteArrayAsync(downloadUrl).Result;
if (emocheckBin == null)
{
Log($"Failed to download the latest version of EmoCheck.");
return -1;
}
using (var fs = new FileStream(EMOCHECK_FILENAME, FileMode.Create, FileAccess.Write))
fs.Write(emocheckBin, 0, emocheckBin.Length);
Log($"Downloaded the latest version of EmoCheck. ({downloadUrl})");
updateInfo.CurrentTagName = latest.tag_name;
var js = JsonSerializer.Serialize<UpdateInfo>(updateInfo);
File.WriteAllText(UPDATE_INFO_FILENAME, js);
}
}
var emoCheckProcessStartupInfo = new ProcessStartInfo
{
UseShellExecute = false,
FileName = Path.Combine(Environment.CurrentDirectory, EMOCHECK_FILENAME),
Arguments = string.Join(" ", args),
CreateNoWindow = args.Select(s => s.ToUpper()).Contains("/QUIET"),
};
using (var emoCheckProcess = Process.Start(emoCheckProcessStartupInfo))
{
if (emoCheckProcess == null)
{
Log("Failed to start EmoCheck.");
return -1;
}
Log($"EmoCheck has started. ({latest.tag_name})");
emoCheckProcess.PriorityClass = EMOCHECK_PROCESS_PRIORITY;
emoCheckProcess.WaitForExit();
Log($"EmoCheck has completed.");
}
}
catch (Exception ex)
{
Log($"An unexpected error has occurred. ({ex.Message.Replace("\r\n", "␍␊")})");
return -1;
}
Log($"{APP_NAME} has completed.");
return 0;
}
}
private static void Log(string msg)
{
try
{
using (var proc = Process.GetCurrentProcess())
File.AppendAllText(LOG_FILENAME, $"{DateTime.Now:yyyy.MM.dd HH:mm:ss.fff} ({proc.Id}) {msg}\n");
}
catch { }
}
internal sealed class Cleanup : IDisposable
{
private readonly Action _cleanupAction;
public Cleanup(Action cleanUpAction) => _cleanupAction = cleanUpAction;
public void Dispose() => _cleanupAction();
}
#pragma warning disable IDE1006
internal sealed class Latest
{
public string tag_name { get; set; } = string.Empty;
public Asset[] assets { get; set; } = Array.Empty<Asset>();
public sealed class Asset
{
public string updated_at { get; set; } = string.Empty;
public string browser_download_url { get; set; } = string.Empty;
}
}
#pragma warning restore IDE1006
internal sealed class UpdateInfo
{
public string CurrentTagName { get; set; } = string.Empty;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment