470 lines
No EOL
23 KiB
C#
470 lines
No EOL
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Threading;
|
|
using DjRedo.Controls;
|
|
using DjRedo.Core.Configuration;
|
|
using DjRedo.Core.Extensions;
|
|
using DjRedo.Core.Managers;
|
|
using DjRedo.Core.Trees;
|
|
using DjRedo.ViewModels;
|
|
using JD.Serialization;
|
|
using Microsoft.Win32;
|
|
using NLog;
|
|
using Plume.Interfaces.WPF.Modules;
|
|
using Plume.Perforce.Service.Api;
|
|
using Waapinet.Core;
|
|
using Waapinet.Core.GeneratedWwiseApi;
|
|
using WaapiNet.Ak;
|
|
using WaapiNet.Wwise.Objects;
|
|
|
|
namespace DjRedo.Core
|
|
{
|
|
public class DjRedoLauncher : IDjRedoStepProvider
|
|
{
|
|
private Dispatcher _dispatcher;
|
|
private IWpfModuleContext _context;
|
|
private static readonly ILogger Logs = LogManager.GetCurrentClassLogger();
|
|
private Process _currentWwiseProcess;
|
|
|
|
private static void WwiseProcessOutputDataReceived(object sender, DataReceivedEventArgs e)
|
|
{
|
|
Debug.WriteLine($"Wwise process is talking: {e.Data}");
|
|
}
|
|
|
|
private async Task CleanWwiseHierarchy(IStepData stepData,IWwiseService wwiseService, object allBuses)
|
|
{
|
|
if (allBuses == null) return;
|
|
|
|
await Task.Factory.StartNew(() =>
|
|
{
|
|
if (allBuses.GetType().IsArray)
|
|
{
|
|
if (!(allBuses is IObjectReturn[] allValue)) return;
|
|
|
|
float stepDataProgress = 100f / allValue.Length;
|
|
foreach (IObjectReturn objectReturn in allValue)
|
|
{
|
|
wwiseService.Core.Object.Delete(objectReturn.Path);
|
|
stepData.Progress += stepDataProgress;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (allBuses is IObjectReturn child)
|
|
{
|
|
wwiseService.Core.Object.Delete(child.Path);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private static BusNode GenerateBusHierarchy(DjRedoLauncherContext context, BusDef[] rakiBuses)
|
|
{
|
|
BusNode[] busNodes = rakiBuses.Select(b => new BusNode(b)).ToArray();
|
|
foreach (BusNode busNode in busNodes)
|
|
{
|
|
context.CurrentBusesState.Add(busNode.BusDef.Name ?? "master", busNode);
|
|
}
|
|
|
|
SortBusesByParents(context);
|
|
|
|
return context.CurrentBusesState.First().Value;
|
|
}
|
|
|
|
private static void SortBusesByParents(DjRedoLauncherContext context)
|
|
{
|
|
foreach (KeyValuePair<string, BusNode> pair in context.CurrentBusesState)
|
|
{
|
|
BusNode def = pair.Value;
|
|
if (def.BusDef.Outputs.Any() && def.BusDef.Outputs.First() != "")
|
|
{
|
|
def.Parent = context.CurrentBusesState[def.BusDef.Outputs.First()];
|
|
// add the bus to his parent's children list
|
|
context.CurrentBusesState[def.BusDef.Outputs.First()].Children.Add(def.BusDef.Name, def);
|
|
}
|
|
}
|
|
|
|
KeyValuePair<string, BusNode>[] allToRemove = context.CurrentBusesState.Where(b => b.Key != "master").ToArray();
|
|
|
|
foreach (KeyValuePair<string, BusNode> keyValuePair in allToRemove)
|
|
{
|
|
context.CurrentBusesState.Remove(keyValuePair.Key);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
public RunnableStepTaskCollection ProvideSteps(Dispatcher dispatcher)
|
|
{
|
|
_dispatcher = dispatcher;
|
|
|
|
StepTask[] allSteps =
|
|
{
|
|
new StepTask(new DjRedoStepViewModel(1 , "Chech Wwise Alive", "Check Wwise Alive", infinitespin: true), CheckWwiseAlive),
|
|
new StepTask(new DjRedoStepViewModel(2 , "Deserialize DjRedoConfiguration", "Deserialize DjRedoConfiguration", infinitespin: true), DeserializeConfiguration),
|
|
new StepTask(new DjRedoStepViewModel(3 , "Build P4 tree", "Build P4 tree", infinitespin: true), BuildP4Tree),
|
|
new StepTask(new DjRedoStepViewModel(4 , "Extract Nodes to push to wwise", "Extract Nodes to push to wwise", infinitespin: true), ExtractNodes),
|
|
new StepTask(new DjRedoStepViewModel(5 , "Open Wwise Project", "Open Wwise Project", infinitespin: true), OpenWwise),
|
|
new StepTask(new DjRedoStepViewModel(6 , "Extract Windows plateform", "Extract Windows plateform", infinitespin: true), ExtractWindowsPlatform),
|
|
new StepTask(new DjRedoStepViewModel(7 , "Initialize Wwise service managers", "Initialize Wwise service managers"), InitializeManagers),
|
|
new StepTask(new DjRedoStepViewModel(8 , "Clean Wwise Buses", "Clean Wwise Buses", infinitespin: true), CleanBuses),
|
|
new StepTask(new DjRedoStepViewModel(9 , "Clean audio tree", "Clean Wwise tree", infinitespin: true), CleanAudioNodes),
|
|
new StepTask(new DjRedoStepViewModel(10, "Push buses to wwise", "Push buses to wwise"), PushBusesToWwise),
|
|
new StepTask(new DjRedoStepViewModel(11, "Create Wwise audio tree", "Create Wwise audio tree"), PushAudioNodesToWwise),
|
|
new StepTask(new DjRedoStepViewModel(12, "Save Project", "Save Project", infinitespin: true), SaveProject),
|
|
new StepTask(new DjRedoStepViewModel(SocketListenningOrder, "Listen to Wwise", "Listen to Wwise - READY to work", infinitespin: true), ListenToWwise),
|
|
new StepTask(new DjRedoStepViewModel(14, "Close Wwise channel", "Close Wwise channel", infinitespin: true), CloseWwiseChannel),
|
|
};
|
|
|
|
return new RunnableStepTaskCollection(allSteps);
|
|
}
|
|
|
|
public const int SocketListenningOrder = 13;
|
|
|
|
private async Task<object[]> CheckWwiseAlive(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
Logs.Info("Check that wwise is alive");
|
|
string WwiseProcessName = "Wwise";
|
|
string wwisePath = $"{Environment.GetEnvironmentVariable("WWISEROOT")}\\Authoring\\x64\\Release\\bin\\Wwise.exe";
|
|
|
|
await Task.Factory.StartNew(() =>
|
|
{
|
|
Process[] wwiseProcess = Process.GetProcessesByName(WwiseProcessName);
|
|
|
|
if (!wwiseProcess.Any())
|
|
{
|
|
//wwise is not started and should be
|
|
try
|
|
{
|
|
bool isWwiseInstalled = false;
|
|
string registry_key = @"SOFTWARE\Audiokinetic Inc.\Wwise";
|
|
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(registry_key))
|
|
{
|
|
foreach (string subkey_name in key.GetSubKeyNames())
|
|
{
|
|
using (RegistryKey subkey = key.OpenSubKey(subkey_name))
|
|
{
|
|
isWwiseInstalled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isWwiseInstalled)
|
|
{
|
|
//
|
|
// Setup the process with the ProcessStartInfo class.
|
|
//
|
|
ProcessStartInfo start = new ProcessStartInfo
|
|
{
|
|
FileName = wwisePath,
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
};
|
|
|
|
|
|
_currentWwiseProcess?.Kill();
|
|
_currentWwiseProcess = Process.Start(start);
|
|
_currentWwiseProcess.OutputDataReceived += WwiseProcessOutputDataReceived;
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("Please Installed Wwise before trying to start it");
|
|
}
|
|
}
|
|
catch (NullReferenceException)
|
|
{
|
|
}
|
|
}
|
|
});
|
|
|
|
Logs.Info("Alive!");
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return new object[]{ new DjRedoLauncherContext()};
|
|
}
|
|
|
|
private async Task<object[]> DeserializeConfiguration(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
await Task.Factory.StartNew(() =>
|
|
DjRedoBindingManager.Current.Initialize(DjRedoConfigurationManager.Current.Configuration.ExtractBindingsIncludingProviders()));
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> BuildP4Tree(IStepData stepdata, object[] parameters)
|
|
{
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext) parameters.First();
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
Logs.Info("Build the Perforce tree and extract the nodes to push to Wwise");
|
|
IPerforceService perforceService = _context.ServiceManager.Get<IPerforceService>();
|
|
if (perforceService == null)
|
|
{
|
|
throw new Exception("The perforce service is missing");
|
|
}
|
|
context.DatasInfos = new DatasInfos(perforceService);
|
|
await DjRedoTreeManager.Current.BuildPerforceTree(context.DatasInfos);
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> ExtractNodes(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext)parameters.First();
|
|
int numberOfNodes = 0;
|
|
if (DjRedoTreeManager.Current.Tree.HasBeenBuilt)
|
|
{
|
|
numberOfNodes = await Task.Factory.StartNew(()=>
|
|
{
|
|
int count = DjRedoTreeManager.Current.Tree.Yield().Count(DjRedoTreeManager.IsValidNode);
|
|
return count;
|
|
});
|
|
context.ExtractedNodeCount = numberOfNodes;
|
|
}
|
|
|
|
Logs.Info($"Done! node number: {numberOfNodes}");
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> OpenWwise(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext)parameters.First();
|
|
DjRedoPerforceManager.Current.CheckoutWwiseProject(context.DatasInfos);
|
|
context.WwiseService = await Task.Factory.StartNew(() =>
|
|
{
|
|
return WwiseChannelFactory.CreateChannel<IWwiseService>(new DnsEndPoint(
|
|
DjRedoConfigurationManager.Current.Configuration.WwiseConfiguration.WwiseEndpoint.Uri,
|
|
DjRedoConfigurationManager.Current.Configuration.WwiseConfiguration.WwiseEndpoint.Port));
|
|
});
|
|
|
|
Logs.Info($"Opening JD Wwise project");
|
|
await Task.Factory.StartNew(() =>
|
|
{
|
|
context.WwiseService.Ui.BringToForeground();
|
|
//watch out we bypass the save so the previous work might be erased
|
|
context.WwiseService.Ui.Project.Open(context.DatasInfos.WwiseProjectPath, OnUpgradeValues.Migrate, _bypasssave: true);
|
|
});
|
|
Logs.Info($"JD Wwise project openned");
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> ExtractWindowsPlatform(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext)parameters.First();
|
|
|
|
Logs.Info("Fetching all declared platforms");
|
|
await Task.Factory.StartNew(() =>
|
|
{
|
|
IPlatform[] allPlatforms =
|
|
(IPlatform[]) context.WwiseService.Core.Object.Get(FromFactory.StringOfType(WwiseObjectReference.PlatformObjectReference));
|
|
context.WindowsPlatformId = allPlatforms.Single(p => p.Name == "Windows").Id;
|
|
|
|
foreach (IPlatform platform in allPlatforms)
|
|
{
|
|
Logs.Info($"Platform found: {platform.Name} ({platform.Id})");
|
|
}
|
|
});
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> InitializeManagers(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext)parameters.First();
|
|
Logs.Info("Start and Initialize managers");
|
|
DjRedoTreeManager.Current.InitializeWwiseService(context.WwiseService);
|
|
const float managerCount = 8f;
|
|
const float managersStepProgress = 100 / managerCount;
|
|
await _dispatcher.InvokeAsync(() => stepdata.Progress += managersStepProgress);
|
|
DjRedoWwiseSelectionManager.Current.InitializeWwiseService(context.WwiseService);
|
|
await _dispatcher.InvokeAsync(() => stepdata.Progress += managersStepProgress);
|
|
DjRedoBindingManager.Current.InitializeWwiseService(context.WwiseService);
|
|
await _dispatcher.InvokeAsync(() => stepdata.Progress += managersStepProgress);
|
|
DjRedoTplLoadManager.Current.InitializeWwiseService(context.WwiseService);
|
|
await _dispatcher.InvokeAsync(() => stepdata.Progress += managersStepProgress);
|
|
DjRedoTplFlushManager.Current.InitializeWwiseService(context.WwiseService);
|
|
await _dispatcher.InvokeAsync(() => stepdata.Progress += managersStepProgress);
|
|
DjRedoWwiseAssetUpdater.Current.InitializeWwiseService(context.WwiseService);
|
|
await _dispatcher.InvokeAsync(() => stepdata.Progress += managersStepProgress);
|
|
DjRedoChildrenManager.Current.InitializeWwiseService(context.WwiseService);
|
|
await _dispatcher.InvokeAsync(() => stepdata.Progress += managersStepProgress);
|
|
DjRedoAssetManager.Current.InitializeWwiseService(context.WwiseService);
|
|
await _dispatcher.InvokeAsync(() => stepdata.Progress += managersStepProgress);
|
|
//Todo activate this manager as soon as the profiler receiving the datas has been identified
|
|
//DjRedoWwiseMessageManager.Current.InitializeWwiseService(wwiseService);
|
|
|
|
|
|
Logs.Info("Managers ready");
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> CleanBuses(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext)parameters.First();
|
|
Logs.Info("Start cleaning Wwise buses");
|
|
object allBuses = await Task.Factory.StartNew(()=>
|
|
{
|
|
return context.WwiseService.Core.Object.Get(
|
|
FromFactory.Path(DjRedoConfigurationManager.Current.Configuration.WwiseConfiguration.MasterMixerHierarchyPath),
|
|
TransformFactory.InitialTransform(TransformFactory.Select(SelectType.Children)));
|
|
});
|
|
await CleanWwiseHierarchy(stepdata, context.WwiseService, allBuses);
|
|
Logs.Info("Wwise buses cleaned");
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> CleanAudioNodes(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext)parameters.First();
|
|
object allChildren = await Task.Factory.StartNew(() =>
|
|
{
|
|
return context.WwiseService.Core.Object.Get(
|
|
FromFactory.Path(DjRedoConfigurationManager.Current.Configuration.WwiseConfiguration.ActorMixerHierarchyPath),
|
|
TransformFactory.InitialTransform(TransformFactory.Select(SelectType.Children)));
|
|
});
|
|
await CleanWwiseHierarchy(stepdata, context.WwiseService, allChildren);
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> PushBusesToWwise(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext)parameters.First();
|
|
Logs.Info("Extract buses from SoundConfig");
|
|
await Task.Factory.StartNew(async () =>
|
|
{
|
|
var serializer = new LuaSerializer();
|
|
serializer.InitializeEngineTypes();
|
|
//"D:\\JD_DATA_STREAM\\JD2020\\data\\EngineData\\GameConfig\\soundconfig.isg"
|
|
string wwiseConfigurationSoundConfigPath = DjRedoConfigurationManager.Current.Configuration.WwiseConfiguration.SoundConfigPath;
|
|
string soundconfigPath = System.IO.Path.Combine(context.DatasInfos.ProjectRoot,
|
|
context.DatasInfos.DiskRootFolder, wwiseConfigurationSoundConfigPath);
|
|
Logs.Info($"SoundConfig path: {soundconfigPath}");
|
|
SoundConfig_Template rakiBuses = (SoundConfig_Template)serializer.LoadObjectFromFile(soundconfigPath, typeof(SoundConfig_Template));
|
|
|
|
if (rakiBuses != null)
|
|
{
|
|
|
|
Logs.Info("Generate bus tree");
|
|
BusDef[] busDefs = rakiBuses.Busses.ToArray();
|
|
DjRedoBindingManager.Current.Buses = GenerateBusHierarchy(context, busDefs);
|
|
await DjRedoBindingManager.Current.Buses.TraverseAsync(async node =>
|
|
{
|
|
if (node.BusDef.Name != "master")
|
|
{
|
|
string fullNodePath = $"{DjRedoConfigurationManager.Current.Configuration.WwiseConfiguration.MasterMixerHierarchyPath}\\{node.WwisePath}".TrimEnd('\\');
|
|
context.WwiseService.Core.Object.Create(node.BusDef.Name, fullNodePath, _platform: context.WindowsPlatformId, _purpletype: WwiseObjectReference.BusObjectReference.RealName);
|
|
|
|
object createdBus = context.WwiseService.Core.Object.Get(FromFactory.Path($"{fullNodePath}\\{node.BusDef.Name}".TrimEnd('\\')));
|
|
node.WwiseBus = (IBus)createdBus;
|
|
}
|
|
else
|
|
{
|
|
node.WwiseBus = (IBus)context.WwiseService.Core.Object.Get(FromFactory.Path(DjRedoConfigurationManager.Current.Configuration.WwiseConfiguration.MasterMixerHierarchyPath));
|
|
}
|
|
|
|
await _dispatcher.InvokeAsync(() => stepdata.Progress += 100f / busDefs.Length);
|
|
});
|
|
|
|
Logs.Info("Bus tree done");
|
|
|
|
}
|
|
});
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> PushAudioNodesToWwise(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext)parameters.First();
|
|
|
|
Logs.Info("Createaudio tree");
|
|
int fileNode = 0;
|
|
await DjRedoTreeManager.Current.CreateWwiseTreeView(context.WindowsPlatformId, async (i, node) =>
|
|
{
|
|
if (node.LoadedTpl != null)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.Progress += ++fileNode / (float)context.ExtractedNodeCount);
|
|
}
|
|
});
|
|
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> SaveProject(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext)parameters.First();
|
|
context.WwiseService.Core.Project.Save();
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> ListenToWwise(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext)parameters.First();
|
|
while (stepdata.State == DjRedoStepState.Started)
|
|
{
|
|
await Task.Delay(10);
|
|
}
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
private async Task<object[]> CloseWwiseChannel(IStepData stepdata, object[] parameters)
|
|
{
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Started);
|
|
DjRedoLauncherContext context = (DjRedoLauncherContext)parameters.First();
|
|
context.WwiseService.CloseRelatedChannel();
|
|
DjRedoConfigurationManager.Current.Reset();
|
|
DjRedoPerforceManager.Current.CleanWwiseProject(context.DatasInfos);
|
|
_currentWwiseProcess.Kill();
|
|
_currentWwiseProcess = null;
|
|
Application.Current.Shutdown();
|
|
await _dispatcher.InvokeAsync(() => stepdata.State = DjRedoStepState.Successed);
|
|
return parameters;
|
|
}
|
|
|
|
public void Initialize(IWpfModuleContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
}
|
|
|
|
public class DjRedoLauncherContext
|
|
{
|
|
|
|
public Dictionary<string, BusNode> CurrentBusesState { get; } = new Dictionary<string, BusNode>();
|
|
public IWwiseService WwiseService { get; set; }
|
|
public DatasInfos DatasInfos { get; set; }
|
|
|
|
public int ExtractedNodeCount { get; set; }
|
|
|
|
public Guid WindowsPlatformId { get; set; }
|
|
}
|
|
} |