Last active
August 31, 2015 10:52
-
-
Save meitinger/e3ccc61ca1a8ac131c50 to your computer and use it in GitHub Desktop.
Utility that compiles multiple INFs into one compressed XML repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Copyright (C) 2015, Manuel Meitinger | |
* | |
* This program is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License as published by | |
* the Free Software Foundation, either version 2 of the License, or | |
* (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Globalization; | |
using System.IO; | |
using System.IO.Compression; | |
using System.Linq; | |
using System.Reflection; | |
using System.Runtime.InteropServices; | |
using System.Text; | |
using System.Xml; | |
[assembly: AssemblyTitle("Driver Compiler")] | |
[assembly: AssemblyDescription("Utility that compiles multiple INFs into one compressed XML repository.")] | |
[assembly: AssemblyProduct("Driver Management")] | |
[assembly: AssemblyCompany("AufBauWerk - Unternehmen für junge Menschen")] | |
[assembly: AssemblyCopyright("Copyright © 2015 by Manuel Meitinger")] | |
[assembly: AssemblyVersion("1.2.0.2")] | |
[assembly: ComVisible(false)] | |
namespace Aufbauwerk.Tools.DriverManagement.Compiler | |
{ | |
public sealed class Inf : IEnumerable<Inf.Section>, IDisposable | |
{ | |
#region SetupApi | |
readonly static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); | |
const string SetupApi = "SetupApi.dll"; | |
const uint INF_STYLE_WIN4 = 0x00000002; | |
const int MAX_PATH = 260; | |
const int ERROR_SUCCESS = 0; | |
const int ERROR_INSUFFICIENT_BUFFER = 122; | |
const int ERROR_NO_MORE_ITEMS = 259; | |
const int ERROR_LINE_NOT_FOUND = -536870654; | |
const uint SIGNERSCORE_UNKNOWN = 0xFF000000; | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] | |
struct SP_INF_SIGNER_INFO_V2 | |
{ | |
public int cbSize; | |
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] | |
public string CatalogFile; | |
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] | |
public string DigitalSigner; | |
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] | |
public string DigitalSignerVersion; | |
public uint SignerScore; | |
} | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] | |
internal struct INFCONTEXT | |
{ | |
public IntPtr Inf; | |
public IntPtr CurrentInf; | |
public int Section; | |
public int Line; | |
} | |
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)] | |
extern static bool SetupVerifyInfFile(string InfName, IntPtr AltPlatformInfo, ref SP_INF_SIGNER_INFO_V2 InfFileName); | |
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)] | |
extern static bool SetupEnumInfSections(IntPtr InfHandle, int EnumerationIndex, StringBuilder ReturnBuffer, int ReturnBufferSize, out int RequiredSize); | |
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)] | |
extern static IntPtr SetupOpenInfFile(string FileName, string InfClass, uint InfStyle, out int ErrorLine); | |
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)] | |
extern static int SetupGetLineCount(IntPtr InfHandle, string Section); | |
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)] | |
extern static bool SetupFindFirstLine(IntPtr InfHandle, string Section, string Key, ref INFCONTEXT Context); | |
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)] | |
extern static bool SetupGetStringField(ref INFCONTEXT Context, int FieldIndex, StringBuilder ReturnBuffer, int ReturnBufferSize, out int RequiredSize); | |
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)] | |
extern static bool SetupFindNextMatchLine(ref INFCONTEXT ContextIn, string Key, ref INFCONTEXT ContextOut); | |
[DllImport(SetupApi, ExactSpelling = true, SetLastError = true)] | |
extern static int SetupGetFieldCount(ref INFCONTEXT Context); | |
[DllImport(SetupApi, ExactSpelling = true, SetLastError = true)] | |
extern static void SetupCloseInfFile(IntPtr InfHandle); | |
#endregion | |
public class Exception : ApplicationException | |
{ | |
readonly string message; | |
public Exception(string path, string message) | |
{ | |
if (path == null) | |
throw new ArgumentNullException("path"); | |
if (message == null) | |
throw new ArgumentNullException("message"); | |
Path = path; | |
this.message = message; | |
} | |
public Exception(string path, int line, string message) | |
: this(path, message) | |
{ | |
if (line < 1) | |
throw new ArgumentOutOfRangeException("line"); | |
Line = line; | |
} | |
public Exception(string path, string section, string key, string message) | |
: this(path, message) | |
{ | |
if (section == null) | |
throw new ArgumentNullException("section"); | |
if (key == null) | |
throw new ArgumentNullException("key"); | |
Section = section; | |
Key = key; | |
} | |
internal Exception(Line line, string message) | |
{ | |
Path = line.Section.Inf.Path; | |
Line = line.Number; | |
Section = line.Section.Name; | |
Key = line.Key; | |
this.message = message; | |
} | |
public string Path { get; private set; } | |
public int Line { get; private set; } | |
public string Section { get; private set; } | |
public string Key { get; private set; } | |
public override string Message | |
{ | |
get | |
{ | |
// create the message | |
var builder = new StringBuilder(Path); | |
if (Line > 0) | |
builder.Append(", line ").Append(Line); | |
if (!string.IsNullOrEmpty(Section)) | |
builder.Append(", section ").Append(Section); | |
if (!string.IsNullOrEmpty(Key)) | |
builder.Append(", key ").Append(Key); | |
builder.Append(": "); | |
builder.Append(message); | |
return builder.ToString(); | |
} | |
} | |
} | |
public class Line | |
{ | |
INFCONTEXT context; | |
internal Line(Section section, INFCONTEXT context) | |
{ | |
// create a line | |
this.context = context; | |
Section = section; | |
} | |
string GetFieldInternal(int index) | |
{ | |
// retrieve the given field | |
Section.Inf.CheckDisposed(); | |
var requiredSize = 200; | |
var builder = new StringBuilder(requiredSize); | |
while (!SetupGetStringField(ref context, index, builder, builder.Capacity, out requiredSize)) | |
{ | |
if (Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) | |
{ | |
if (index == 0) | |
return null; | |
throw new Exception(this, "Cannot get string field: " + new Win32Exception().Message); | |
} | |
builder.Capacity = requiredSize; | |
} | |
return builder.ToString(); | |
} | |
public Section Section { get; private set; } | |
public string Key { get { return GetFieldInternal(0); } } | |
public int Number { get { return context.Line; } } | |
public int FieldCount | |
{ | |
get | |
{ | |
Section.Inf.CheckDisposed(); | |
var count = SetupGetFieldCount(ref context); | |
if (count == 0 && Marshal.GetLastWin32Error() != ERROR_SUCCESS) | |
throw new Exception(this, "Cannot get field count: " + new Win32Exception().Message); | |
return count; | |
} | |
} | |
public string GetField(int index) | |
{ | |
// retrieve the field at the given index | |
if (index < 0) | |
throw new ArgumentOutOfRangeException("index"); | |
var value = GetFieldInternal(index + 1); | |
return value; | |
} | |
public T GetFieldAs<T>(Converter<string, T> converter, int index) | |
{ | |
// try to convert the given field | |
if (converter == null) | |
throw new ArgumentNullException("converter"); | |
var field = GetField(index); | |
try { return converter(field); } | |
catch (FormatException e) { throw new Inf.Exception(this, "Cannot convert field: " + e.Message); } | |
catch (OverflowException e) { throw new Inf.Exception(this, "Cannot handle overflown value: " + e.Message); } | |
} | |
} | |
public class Section : IEnumerable<Line> | |
{ | |
internal Section(Inf inf, string name) | |
{ | |
Inf = inf; | |
Name = name; | |
} | |
IEnumerable<Line> GetLines(string key) | |
{ | |
// enumerate lines | |
Inf.CheckDisposed(); | |
var context = new INFCONTEXT(); | |
if (SetupFindFirstLine(Inf.Handle, Name, key, ref context)) | |
{ | |
do | |
{ | |
yield return new Line(this, context); | |
Inf.CheckDisposed(); | |
} | |
while (SetupFindNextMatchLine(ref context, key, ref context)); | |
} | |
if (Marshal.GetLastWin32Error() != ERROR_LINE_NOT_FOUND) | |
throw new Exception(Inf.Path, Name, key, "Cannot find line: " + new Win32Exception().Message); | |
} | |
public Inf Inf { get; private set; } | |
public string Name { get; private set; } | |
public bool Exists | |
{ | |
get | |
{ | |
Inf.CheckDisposed(); | |
return SetupGetLineCount(Inf.Handle, Name) > -1; | |
} | |
} | |
public bool IsEmpty | |
{ | |
get | |
{ | |
Inf.CheckDisposed(); | |
return SetupGetLineCount(Inf.Handle, Name) < 1; | |
} | |
} | |
public IEnumerable<Line> this[string key] | |
{ | |
get | |
{ | |
// enumerate all mathing lines | |
if (key == null) | |
throw new ArgumentNullException("key"); | |
return GetLines(key); | |
} | |
} | |
public Line Single(string key) | |
{ | |
// return the single item | |
var enumerator = GetLines(key).GetEnumerator(); | |
if (!enumerator.MoveNext()) | |
throw new Exception(Inf.Path, Name, key, "No such line exists."); | |
var result = enumerator.Current; | |
if (enumerator.MoveNext()) | |
throw new Exception(Inf.Path, Name, key, "Multi lines exists."); | |
return result; | |
} | |
public IEnumerator<Line> GetEnumerator() | |
{ | |
return GetLines(null).GetEnumerator(); | |
} | |
IEnumerator IEnumerable.GetEnumerator() | |
{ | |
return GetEnumerator(); | |
} | |
} | |
public Inf(string path) | |
{ | |
// parse the inf | |
if (path == null) | |
throw new ArgumentNullException("path"); | |
var errorLine = 0; | |
Path = path; | |
Handle = SetupOpenInfFile(Path, null, INF_STYLE_WIN4, out errorLine); | |
if (Handle == INVALID_HANDLE_VALUE) | |
throw new Exception(Path, errorLine, "Cannot open file: " + new Win32Exception().Message); | |
} | |
public Section this[string name] | |
{ | |
get | |
{ | |
// create the section | |
if (name == null) | |
throw new ArgumentNullException("name"); | |
return new Section(this, name); | |
} | |
} | |
IntPtr Handle { get; set; } | |
void CheckDisposed() | |
{ | |
// check if the object is disposed | |
if (Handle == INVALID_HANDLE_VALUE) | |
throw new ObjectDisposedException(Path); | |
} | |
public uint SignerScore | |
{ | |
get | |
{ | |
// query the signer score (SetupVerifyInfFile can fail) | |
var info = new SP_INF_SIGNER_INFO_V2() | |
{ | |
cbSize = Marshal.SizeOf(typeof(SP_INF_SIGNER_INFO_V2)), | |
SignerScore = SIGNERSCORE_UNKNOWN, | |
}; | |
SetupVerifyInfFile(Path, IntPtr.Zero, ref info); | |
return info.SignerScore; | |
} | |
} | |
public string Path { get; private set; } | |
public IEnumerator<Section> GetEnumerator() | |
{ | |
// enumerate all sections | |
CheckDisposed(); | |
var requiredLen = 200; | |
var buffer = new StringBuilder(requiredLen); | |
var index = 0; | |
while (true) | |
{ | |
if (SetupEnumInfSections(Handle, index, buffer, buffer.Capacity, out requiredLen)) | |
{ | |
yield return new Section(this, buffer.ToString()); | |
CheckDisposed(); | |
index++; | |
} | |
else | |
{ | |
switch (Marshal.GetLastWin32Error()) | |
{ | |
case ERROR_NO_MORE_ITEMS: | |
yield break; | |
case ERROR_INSUFFICIENT_BUFFER: | |
buffer.Capacity = requiredLen; | |
break; | |
default: | |
throw new Exception(Path, "Cannot enumerate sections: " + new Win32Exception().Message); | |
} | |
} | |
} | |
} | |
IEnumerator IEnumerable.GetEnumerator() | |
{ | |
return GetEnumerator(); | |
} | |
void IDisposable.Dispose() | |
{ | |
// close the inf file | |
if (Handle != INVALID_HANDLE_VALUE) | |
{ | |
SetupCloseInfFile(Handle); | |
Handle = INVALID_HANDLE_VALUE; | |
} | |
} | |
} | |
public enum Architecture : ushort | |
{ | |
x86 = 0, | |
ia64 = 6, | |
amd64 = 9, | |
} | |
public class TargetOSVersion | |
{ | |
public static readonly TargetOSVersion Empty = new TargetOSVersion(string.Empty); | |
static readonly Action<TargetOSVersion, string>[] Converters = new Action<TargetOSVersion, string>[] | |
{ | |
(t,v) => { t.Architecture = (Architecture)Enum.Parse(typeof(Architecture), v, true); }, | |
(t,v) => { t.MajorVersion = ushort.Parse(v,CultureInfo.InvariantCulture); }, | |
(t,v) => { t.MinorVersion = ushort.Parse(v,CultureInfo.InvariantCulture); }, | |
(t,v) => { t.ProductType = v.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? byte.Parse(v.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture) : byte.Parse(v, CultureInfo.InvariantCulture); }, | |
(t,v) => { t.SuiteMask = v.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? ushort.Parse(v.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture) : ushort.Parse(v, CultureInfo.InvariantCulture); }, | |
}; | |
private TargetOSVersion(string suffix) | |
{ | |
Suffix = suffix; | |
} | |
public Architecture? Architecture { get; private set; } | |
public uint? MajorVersion { get; private set; } | |
public uint? MinorVersion { get; private set; } | |
public byte? ProductType { get; private set; } | |
public ushort? SuiteMask { get; private set; } | |
public static TargetOSVersion Parse(string s) | |
{ | |
if (s == null) | |
throw new ArgumentNullException("s"); | |
// check the prefix and split the parts | |
if (!s.StartsWith("nt", StringComparison.OrdinalIgnoreCase)) | |
throw new FormatException("TargetOSVersion dosn't start with 'nt'"); | |
var parts = s.Substring(2).Split('.'); | |
if (parts.Length > Converters.Length) | |
throw new FormatException(string.Format("TargetOSVersion contains {0} parts, only {0} allowed", parts.Length, Converters.Length)); | |
// convert each part | |
var result = new TargetOSVersion("." + s); | |
for (var i = 0; i < parts.Length; i++) | |
{ | |
var part = parts[i].Trim(); | |
if (part.Length == 0) | |
continue; | |
try { Converters[i](result, part); } | |
catch (Exception e) { throw new FormatException(string.Format("TargetOSVersion part #{0} '{1}' is invalid: {2}", i, part, e.Message)); } | |
} | |
if (result.MinorVersion.HasValue && !result.MajorVersion.HasValue) | |
throw new FormatException(string.Format("TargetOSVersion contains minor but no major version")); | |
return result; | |
} | |
public string Suffix { get; private set; } | |
} | |
static class Program | |
{ | |
enum LogLevel | |
{ | |
Warning, | |
Error, | |
Fatal, | |
} | |
static void Log(LogLevel level, Exception e) | |
{ | |
Console.Error.WriteLine("{0}: {1}", level, e.Message); | |
} | |
const string InArg = "IN"; | |
const string OutArg = "OUT"; | |
static int Main(string[] args) | |
{ | |
try | |
{ | |
// parse the arguments and go over every file | |
var inputDir = (string)null; | |
var outputFile = (string)null; | |
foreach (var arg in args) | |
{ | |
// check the arg syntax | |
if (arg.Length == 0 || (arg[0] != '/' && arg[0] != '-')) | |
goto Usage; | |
var parts = arg.Split(new char[] { ':', '=' }, 2); | |
if (parts.Length != 2) | |
goto Usage; | |
// handle each arg | |
switch (parts[0].Substring(1).ToUpperInvariant()) | |
{ | |
case InArg: | |
if (inputDir != null) | |
goto Usage; | |
inputDir = parts[1]; | |
break; | |
case OutArg: | |
if (outputFile != null) | |
goto Usage; | |
outputFile = parts[1]; | |
break; | |
default: | |
goto Usage; | |
} | |
} | |
if (inputDir == null || outputFile == null) | |
goto Usage; | |
// create the repository xml file | |
var xml = new XmlDocument(); | |
var repository = xml.CreateElement("Repository"); | |
repository.SetAttribute("Location", inputDir); | |
xml.AppendChild(repository); | |
var failed = 0; | |
foreach (var infPath in Directory.EnumerateFiles(inputDir, "*.inf", SearchOption.AllDirectories)) | |
{ | |
try | |
{ | |
// read the inf file | |
using (var inf = new Inf(infPath)) | |
{ | |
var driver = xml.CreateElement("Driver"); | |
var version = inf["Version"]; | |
driver.SetAttribute("Date", version.Single("DriverVer").GetFieldAs(s => DateTime.ParseExact(s, "MM'/'dd'/'yyyy", CultureInfo.InvariantCulture), 0).ToString("yyyy'-'MM'-'dd", CultureInfo.InvariantCulture)); | |
driver.SetAttribute("Version", version.Single("DriverVer").GetFieldAs(s => { try { return Version.Parse(s); } catch (Exception e) { throw new FormatException(e.Message, e); } }, 1).ToString()); | |
driver.SetAttribute("ProviderName", version.Single("Provider").GetField(0)); | |
driver.SetAttribute("InfPath", inf.Path); | |
// collect all manufacturers | |
var mfs = inf["Manufacturer"]; | |
if (mfs.IsEmpty) | |
{ | |
Log(LogLevel.Warning, new Inf.Exception(inf.Path, string.Format("[Manufacturer] section is {0}.", mfs.Exists ? "empty" : "missing"))); | |
continue; | |
} | |
foreach (var mf in mfs.Where(mf => !string.IsNullOrEmpty(mf.Key))) | |
{ | |
// create the xml node and find the best target os version | |
var mfnode = xml.CreateElement("Manufacturer"); | |
mfnode.SetAttribute("Name", mf.Key); | |
var count = mf.FieldCount; | |
var baseSectionName = mf.GetField(0); | |
// get all matching versions | |
var osVersions = new TargetOSVersion[mf.FieldCount]; | |
osVersions[0] = TargetOSVersion.Empty; | |
for (var i = 1; i < osVersions.Length; i++) | |
osVersions[i] = mf.GetFieldAs(TargetOSVersion.Parse, i); | |
foreach | |
( | |
var osVersion in osVersions | |
.OrderByDescending(v => v.MajorVersion) | |
.ThenByDescending(v => v.MinorVersion) | |
.ThenByDescending(v => v.ProductType) | |
.ThenByDescending(v => v.SuiteMask) | |
.ThenByDescending(v => v.Architecture) | |
) | |
{ | |
// create the version node | |
var vernode = xml.CreateElement("TargetOSVersion"); | |
if (osVersion.Architecture.HasValue) | |
vernode.SetAttribute("Architecture", osVersion.Architecture.Value.ToString("D")); | |
if (osVersion.MajorVersion.HasValue) | |
vernode.SetAttribute("MajorVersion", osVersion.MajorVersion.Value.ToString(CultureInfo.InvariantCulture)); | |
if (osVersion.MinorVersion.HasValue) | |
vernode.SetAttribute("MinorVersion", osVersion.MinorVersion.Value.ToString(CultureInfo.InvariantCulture)); | |
if (osVersion.ProductType.HasValue) | |
vernode.SetAttribute("ProductType", osVersion.ProductType.Value.ToString(CultureInfo.InvariantCulture)); | |
if (osVersion.SuiteMask.HasValue) | |
vernode.SetAttribute("SuiteMask", osVersion.SuiteMask.Value.ToString(CultureInfo.InvariantCulture)); | |
// check if the section exists | |
var sectionName = baseSectionName + osVersion.Suffix; | |
var section = inf[sectionName]; | |
if (!section.Exists) | |
{ | |
// ignore only a missing default section and only if there are target os decorations | |
if (osVersion.Suffix.Length == 0 && osVersions.Length > 1) | |
continue; | |
Log(LogLevel.Warning, new Inf.Exception(mf, string.Format("Section [{0}] is missing.", sectionName))); | |
} | |
else | |
{ | |
// collect all devices | |
var suffixes = osVersion.Architecture.HasValue ? new string[] { ".nt" + osVersion.Architecture.Value, ".nt", string.Empty } : new string[] { ".nt", string.Empty }; | |
foreach (var dev in section) | |
{ | |
// try to get the feature score | |
var devnode = xml.CreateElement("Device"); | |
var ddinstallName = dev.GetField(0); | |
foreach (var suffix in suffixes) | |
{ | |
var ddinstall = inf[ddinstallName + suffix]; | |
if (ddinstall.Exists) | |
{ | |
var featureScore = ddinstall["FeatureScore"].FirstOrDefault(); | |
if (featureScore != null) | |
devnode.SetAttribute("FeatureScore", featureScore.GetFieldAs(s => byte.Parse(s.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? s.Substring(2) : s, NumberStyles.HexNumber, CultureInfo.InvariantCulture), 0).ToString(CultureInfo.InvariantCulture)); | |
break; | |
} | |
} | |
// add all ids | |
devnode.SetAttribute("Description", dev.Key); | |
devnode.SetAttribute("HardwareId", dev.GetField(1)); | |
for (var compatid = 2; compatid < dev.FieldCount; compatid++) | |
{ | |
var compatidnode = xml.CreateElement("CompatibleId"); | |
compatidnode.InnerText = dev.GetField(compatid); | |
devnode.AppendChild(compatidnode); | |
} | |
vernode.AppendChild(devnode); | |
} | |
} | |
// always add the version node (empty sections must be preserved) | |
mfnode.AppendChild(vernode); | |
} | |
// add the manufactur if there are matching versions | |
if (mfnode.ChildNodes.Count > 0) | |
driver.AppendChild(mfnode); | |
} | |
// add the driver and query the signer score if there are manufactures | |
if (driver.ChildNodes.Count > 0) | |
{ | |
driver.SetAttribute("SignerScore", inf.SignerScore.ToString(CultureInfo.InvariantCulture)); | |
repository.AppendChild(driver); | |
} | |
} | |
} | |
catch (Inf.Exception e) | |
{ | |
Log(LogLevel.Error, e); | |
failed++; | |
} | |
} | |
// save the file and return the number of failed infs | |
using (var outputFileStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write)) | |
using (var gzipStream = new GZipStream(outputFileStream, CompressionMode.Compress, true)) | |
xml.Save(gzipStream); | |
return failed; | |
Usage: | |
// write the usage | |
Console.Error.WriteLine | |
( | |
"USAGE: {0}\n" + | |
" /{1}:<path to inf root directory>\n" + | |
" /{2}:<path to gzipped xml output file>", | |
Environment.GetCommandLineArgs()[0], | |
InArg, | |
OutArg | |
); | |
return -1; | |
} | |
catch (Exception e) | |
{ | |
Log(LogLevel.Fatal, e); | |
return -1; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment