Alright, so I guess the first thing that should be mentioned is that Windows like to "block" downloads, and the wrappers for S3PI don't work unless they're unblocked. Many users use the default Windows archive extractor, which extracts all the contents of a blocked archive as blocked, and many of them come complaining, so I found a technical solution to this which is to unblock the contents of the folder of your program.
Below I have a class for platform-specific things, which is needed to determine whether a user is on Windows, so as to only unblock on Windows, as it'll throw a fatal exception on other platforms:
Code:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
public static class Platform
{
public static bool IsLinux
{
get
{
return (OS & OSFlags.Linux) != 0;
}
}
public static bool IsMacOS
{
get
{
return (OS & OSFlags.Darwin) != 0;
}
}
public static bool IsRunningUnderWine
{
get
{
return IsWindows && WineDetector.IsRunningUnderWine;
}
}
public static bool IsUnix
{
get
{
return (OS & OSFlags.Unix) != 0;
}
}
public static bool IsWindows
{
get
{
return (OS & OSFlags.Windows) != 0;
}
}
public static OSFlags OS
{
get
{
switch ((int)Environment.OSVersion.Platform)
{
case 4:
case 128:
var os = OSFlags.Unix;
var uname = GetCommandOutput("uname").TrimEnd('\n');
switch (uname)
{
case "Darwin":
case "Linux":
os |= (OSFlags)Enum.Parse(typeof(OSFlags), uname);
break;
}
return os;
default:
return OSFlags.Windows;
}
}
}
[Flags]
public enum OSFlags
{
Windows = 1,
Unix,
Linux = 4,
Darwin = 8
}
static class FileUnblocker
{
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteFile(string name);
public static bool Unblock(string filename)
{
return DeleteFile(filename + ":Zone.Identifier");
}
}
static class WineDetector
{
[DllImport("ntdll.dll", EntryPoint = "wine_get_version")]
static extern IntPtr WineGetVersion();
public static bool IsRunningUnderWine
{
get
{
try
{
return WineGetVersion() != IntPtr.Zero;
}
catch (EntryPointNotFoundException)
{
return false;
}
}
}
}
public static string GetCommandOutput(string command, string arguments = "")
{
using (var process = new Process
{
StartInfo = new ProcessStartInfo
{
Arguments = arguments,
CreateNoWindow = true,
FileName = command,
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false
}
})
{
process.Start();
string error = process.StandardError.ReadToEnd(),
output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return string.IsNullOrEmpty(error) ? output : string.Format("Error: {0}\nOutput: {1}", error, output);
}
}
public static bool UnblockFile(string filename)
{
return FileUnblocker.Unblock(filename);
}
}
Notice in the above snippet the "FileUnblocker" subclass.
This should be called for everything within the program's folder when the program first launches, as follows:
Code:
if (Platform.IsWindows)
{
foreach (var filename in System.IO.Directory.GetFiles(System.AppDomain.CurrentDomain.BaseDirectory))
{
Platform.UnblockFile(filename);
}
}