mirror of
https://github.com/cemu-project/DS4Windows.git
synced 2024-11-23 01:39:17 +01:00
Initial commit with UdpServer change
This commit is contained in:
parent
1c1a3f747c
commit
d925452ed8
@ -39,6 +39,7 @@ namespace DS4Windows
|
|||||||
};
|
};
|
||||||
public bool suspending;
|
public bool suspending;
|
||||||
//SoundPlayer sp = new SoundPlayer();
|
//SoundPlayer sp = new SoundPlayer();
|
||||||
|
private UdpServer _udpServer;
|
||||||
|
|
||||||
private class X360Data
|
private class X360Data
|
||||||
{
|
{
|
||||||
@ -48,6 +49,75 @@ namespace DS4Windows
|
|||||||
|
|
||||||
private X360Data[] processingData = new X360Data[4];
|
private X360Data[] processingData = new X360Data[4];
|
||||||
|
|
||||||
|
void GetPadDetailForIdx(int padIdx, ref DualShockPadMeta meta)
|
||||||
|
{
|
||||||
|
//meta = new DualShockPadMeta();
|
||||||
|
meta.PadId = (byte) padIdx;
|
||||||
|
meta.Model = DsModel.DS4;
|
||||||
|
|
||||||
|
var d = DS4Controllers[padIdx];
|
||||||
|
if (d == null)
|
||||||
|
{
|
||||||
|
meta.PadMacAddress = null;
|
||||||
|
meta.PadState = DsState.Disconnected;
|
||||||
|
meta.ConnectionType = DsConnection.None;
|
||||||
|
meta.Model = DsModel.None;
|
||||||
|
meta.BatteryStatus = 0;
|
||||||
|
meta.IsActive = false;
|
||||||
|
|
||||||
|
//return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValidSerial = false;
|
||||||
|
//if (d.isValidSerial())
|
||||||
|
//{
|
||||||
|
string stringMac = d.getMacAddress();
|
||||||
|
if (!string.IsNullOrEmpty(stringMac))
|
||||||
|
{
|
||||||
|
stringMac = string.Join("", stringMac.Split(':'));
|
||||||
|
//stringMac = stringMac.Replace(":", "").Trim();
|
||||||
|
meta.PadMacAddress = System.Net.NetworkInformation.PhysicalAddress.Parse(stringMac);
|
||||||
|
isValidSerial = d.isValidSerial();
|
||||||
|
}
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (!isValidSerial)
|
||||||
|
{
|
||||||
|
//meta.PadMacAddress = null;
|
||||||
|
meta.PadState = DsState.Disconnected;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (d.isSynced() || d.IsAlive())
|
||||||
|
meta.PadState = DsState.Connected;
|
||||||
|
else
|
||||||
|
meta.PadState = DsState.Reserved;
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.ConnectionType = (d.getConnectionType() == ConnectionType.USB) ? DsConnection.Usb : DsConnection.Bluetooth;
|
||||||
|
meta.IsActive = !d.isDS4Idle();
|
||||||
|
|
||||||
|
if (d.isCharging() && d.getBattery() >= 100)
|
||||||
|
meta.BatteryStatus = DsBattery.Charged;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (d.getBattery() >= 95)
|
||||||
|
meta.BatteryStatus = DsBattery.Full;
|
||||||
|
else if (d.getBattery() >= 70)
|
||||||
|
meta.BatteryStatus = DsBattery.High;
|
||||||
|
else if (d.getBattery() >= 50)
|
||||||
|
meta.BatteryStatus = DsBattery.Medium;
|
||||||
|
else if (d.getBattery() >= 20)
|
||||||
|
meta.BatteryStatus = DsBattery.Low;
|
||||||
|
else if (d.getBattery() >= 5)
|
||||||
|
meta.BatteryStatus = DsBattery.Dying;
|
||||||
|
else
|
||||||
|
meta.BatteryStatus = DsBattery.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
//return meta;
|
||||||
|
}
|
||||||
|
|
||||||
public ControlService()
|
public ControlService()
|
||||||
{
|
{
|
||||||
//sp.Stream = Properties.Resources.EE;
|
//sp.Stream = Properties.Resources.EE;
|
||||||
@ -70,6 +140,8 @@ namespace DS4Windows
|
|||||||
PreviousState[i] = new DS4State();
|
PreviousState[i] = new DS4State();
|
||||||
ExposedState[i] = new DS4StateExposed(CurrentState[i]);
|
ExposedState[i] = new DS4StateExposed(CurrentState[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_udpServer = new UdpServer(GetPadDetailForIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WarnExclusiveModeFailure(DS4Device device)
|
private void WarnExclusiveModeFailure(DS4Device device)
|
||||||
@ -211,6 +283,20 @@ namespace DS4Windows
|
|||||||
{
|
{
|
||||||
this.On_Report(sender, e, tempIdx);
|
this.On_Report(sender, e, tempIdx);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (_udpServer != null)
|
||||||
|
{
|
||||||
|
EventHandler<EventArgs> tempEvnt = (sender, args) =>
|
||||||
|
{
|
||||||
|
DualShockPadMeta padDetail = new DualShockPadMeta();
|
||||||
|
GetPadDetailForIdx(tempIdx, ref padDetail);
|
||||||
|
_udpServer.NewReportIncoming(ref padDetail, CurrentState[tempIdx]);
|
||||||
|
};
|
||||||
|
|
||||||
|
device.Report += tempEvnt;
|
||||||
|
device.MotionEvent = tempEvnt;
|
||||||
|
}
|
||||||
|
|
||||||
TouchPadOn(i, device);
|
TouchPadOn(i, device);
|
||||||
CheckProfileOptions(i, device, true);
|
CheckProfileOptions(i, device, true);
|
||||||
device.StartUpdate();
|
device.StartUpdate();
|
||||||
@ -243,6 +329,24 @@ namespace DS4Windows
|
|||||||
}
|
}
|
||||||
|
|
||||||
running = true;
|
running = true;
|
||||||
|
|
||||||
|
if (_udpServer != null)
|
||||||
|
{
|
||||||
|
var UDP_SERVER_PORT = 26760;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_udpServer.Start(UDP_SERVER_PORT);
|
||||||
|
LogDebug("UDP server listening on port " + UDP_SERVER_PORT);
|
||||||
|
}
|
||||||
|
catch (System.Net.Sockets.SocketException ex)
|
||||||
|
{
|
||||||
|
var errMsg = String.Format("Couldn't start UDP server on port {0}, outside applications won't be able to access pad data ({1})", UDP_SERVER_PORT, ex.SocketErrorCode);
|
||||||
|
|
||||||
|
LogDebug(errMsg, true);
|
||||||
|
Log.LogToTray(errMsg, true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -319,6 +423,10 @@ namespace DS4Windows
|
|||||||
LogDebug(Properties.Resources.StoppingDS4);
|
LogDebug(Properties.Resources.StoppingDS4);
|
||||||
|
|
||||||
DS4Devices.stopControllers();
|
DS4Devices.stopControllers();
|
||||||
|
|
||||||
|
if (_udpServer != null)
|
||||||
|
_udpServer.Stop();
|
||||||
|
|
||||||
if (showlog)
|
if (showlog)
|
||||||
LogDebug(Properties.Resources.StoppedDS4Windows);
|
LogDebug(Properties.Resources.StoppedDS4Windows);
|
||||||
}
|
}
|
||||||
@ -388,6 +496,20 @@ namespace DS4Windows
|
|||||||
{
|
{
|
||||||
this.On_Report(sender, e, tempIdx);
|
this.On_Report(sender, e, tempIdx);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (_udpServer != null)
|
||||||
|
{
|
||||||
|
EventHandler<EventArgs> tempEvnt = (sender, args) =>
|
||||||
|
{
|
||||||
|
DualShockPadMeta padDetail = new DualShockPadMeta();
|
||||||
|
GetPadDetailForIdx(tempIdx, ref padDetail);
|
||||||
|
_udpServer.NewReportIncoming(ref padDetail, CurrentState[tempIdx]);
|
||||||
|
};
|
||||||
|
|
||||||
|
device.Report += tempEvnt;
|
||||||
|
device.MotionEvent = tempEvnt;
|
||||||
|
}
|
||||||
|
|
||||||
if (!getDInputOnly(Index) && device.isSynced())
|
if (!getDInputOnly(Index) && device.isSynced())
|
||||||
{
|
{
|
||||||
int xinputIndex = x360Bus.FirstController + Index;
|
int xinputIndex = x360Bus.FirstController + Index;
|
||||||
@ -855,6 +977,14 @@ namespace DS4Windows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*if (_udpServer != null)
|
||||||
|
{
|
||||||
|
DualShockPadMeta padDetail = new DualShockPadMeta();
|
||||||
|
GetPadDetailForIdx(ind, ref padDetail);
|
||||||
|
_udpServer.NewReportIncoming(ref padDetail, CurrentState[ind]);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Output any synthetic events.
|
// Output any synthetic events.
|
||||||
Mapping.Commit(ind);
|
Mapping.Commit(ind);
|
||||||
|
|
||||||
|
@ -164,6 +164,7 @@ namespace DS4Windows
|
|||||||
public event EventHandler<EventArgs> Removal = null;
|
public event EventHandler<EventArgs> Removal = null;
|
||||||
public event EventHandler<EventArgs> SyncChange = null;
|
public event EventHandler<EventArgs> SyncChange = null;
|
||||||
public event EventHandler<EventArgs> SerialChange = null;
|
public event EventHandler<EventArgs> SerialChange = null;
|
||||||
|
public EventHandler<EventArgs> MotionEvent = null;
|
||||||
|
|
||||||
public HidDevice HidDevice => hDevice;
|
public HidDevice HidDevice => hDevice;
|
||||||
public bool IsExclusive => HidDevice.IsExclusive;
|
public bool IsExclusive => HidDevice.IsExclusive;
|
||||||
@ -774,7 +775,7 @@ namespace DS4Windows
|
|||||||
//Console.WriteLine(MacAddress.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "" +
|
//Console.WriteLine(MacAddress.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "" +
|
||||||
// "> invalid CRC32 in BT input report: 0x" + recvCrc32.ToString("X8") + " expected: 0x" + calcCrc32.ToString("X8"));
|
// "> invalid CRC32 in BT input report: 0x" + recvCrc32.ToString("X8") + " expected: 0x" + calcCrc32.ToString("X8"));
|
||||||
|
|
||||||
//cState.PacketCounter = pState.PacketCounter + 1; //still increase so we know there were lost packets
|
cState.PacketCounter = pState.PacketCounter + 1; //still increase so we know there were lost packets
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -857,6 +858,7 @@ namespace DS4Windows
|
|||||||
|
|
||||||
utcNow = DateTime.UtcNow; // timestamp with UTC in case system time zone changes
|
utcNow = DateTime.UtcNow; // timestamp with UTC in case system time zone changes
|
||||||
resetHapticState();
|
resetHapticState();
|
||||||
|
cState.PacketCounter = pState.PacketCounter + 1;
|
||||||
cState.ReportTimeStamp = utcNow;
|
cState.ReportTimeStamp = utcNow;
|
||||||
cState.LX = inputReport[1];
|
cState.LX = inputReport[1];
|
||||||
cState.LY = inputReport[2];
|
cState.LY = inputReport[2];
|
||||||
@ -894,6 +896,8 @@ namespace DS4Windows
|
|||||||
cState.L3 = (tempByte & (1 << 6)) != 0;
|
cState.L3 = (tempByte & (1 << 6)) != 0;
|
||||||
cState.Options = (tempByte & (1 << 5)) != 0;
|
cState.Options = (tempByte & (1 << 5)) != 0;
|
||||||
cState.Share = (tempByte & (1 << 4)) != 0;
|
cState.Share = (tempByte & (1 << 4)) != 0;
|
||||||
|
cState.R2Btn = (inputReport[6] & (1 << 3)) != 0;
|
||||||
|
cState.L2Btn = (inputReport[6] & (1 << 2)) != 0;
|
||||||
cState.R1 = (tempByte & (1 << 1)) != 0;
|
cState.R1 = (tempByte & (1 << 1)) != 0;
|
||||||
cState.L1 = (tempByte & (1 << 0)) != 0;
|
cState.L1 = (tempByte & (1 << 0)) != 0;
|
||||||
|
|
||||||
@ -935,6 +939,18 @@ namespace DS4Windows
|
|||||||
timeStampPrevious = tempStamp;
|
timeStampPrevious = tempStamp;
|
||||||
elapsedDeltaTime = 0.000001 * deltaTimeCurrent; // Convert from microseconds to seconds
|
elapsedDeltaTime = 0.000001 * deltaTimeCurrent; // Convert from microseconds to seconds
|
||||||
cState.elapsedTime = elapsedDeltaTime;
|
cState.elapsedTime = elapsedDeltaTime;
|
||||||
|
cState.totalMicroSec = pState.totalMicroSec + deltaTimeCurrent;
|
||||||
|
|
||||||
|
//Simpler touch storing
|
||||||
|
cState.TrackPadTouch0.Id = (byte)(inputReport[35] & 0x7f);
|
||||||
|
cState.TrackPadTouch0.IsActive = (inputReport[35] & 0x80) == 0;
|
||||||
|
cState.TrackPadTouch0.X = (short)(((ushort)(inputReport[37] & 0x0f) << 8) | (ushort)(inputReport[36]));
|
||||||
|
cState.TrackPadTouch0.Y = (short)(((ushort)(inputReport[38]) << 4) | ((ushort)(inputReport[37] & 0xf0) >> 4));
|
||||||
|
|
||||||
|
cState.TrackPadTouch1.Id = (byte)(inputReport[39] & 0x7f);
|
||||||
|
cState.TrackPadTouch1.IsActive = (inputReport[39] & 0x80) == 0;
|
||||||
|
cState.TrackPadTouch1.X = (short)(((ushort)(inputReport[41] & 0x0f) << 8) | (ushort)(inputReport[40]));
|
||||||
|
cState.TrackPadTouch1.Y = (short)(((ushort)(inputReport[42]) << 4) | ((ushort)(inputReport[41] & 0xf0) >> 4));
|
||||||
|
|
||||||
// XXX DS4State mapping needs fixup, turn touches into an array[4] of structs. And include the touchpad details there instead.
|
// XXX DS4State mapping needs fixup, turn touches into an array[4] of structs. And include the touchpad details there instead.
|
||||||
try
|
try
|
||||||
@ -1367,7 +1383,7 @@ namespace DS4Windows
|
|||||||
return pState;
|
return pState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool isDS4Idle()
|
public bool isDS4Idle()
|
||||||
{
|
{
|
||||||
if (cState.Square || cState.Cross || cState.Circle || cState.Triangle)
|
if (cState.Square || cState.Cross || cState.Circle || cState.Triangle)
|
||||||
return false;
|
return false;
|
||||||
|
@ -4,10 +4,11 @@ namespace DS4Windows
|
|||||||
{
|
{
|
||||||
public class DS4State
|
public class DS4State
|
||||||
{
|
{
|
||||||
|
public uint PacketCounter;
|
||||||
public DateTime ReportTimeStamp;
|
public DateTime ReportTimeStamp;
|
||||||
public bool Square, Triangle, Circle, Cross;
|
public bool Square, Triangle, Circle, Cross;
|
||||||
public bool DpadUp, DpadDown, DpadLeft, DpadRight;
|
public bool DpadUp, DpadDown, DpadLeft, DpadRight;
|
||||||
public bool L1, L3, R1, R3;
|
public bool L1, L2Btn, L3, R1, R2Btn, R3;
|
||||||
public bool Share, Options, PS, Touch1, Touch2, TouchButton, TouchRight,
|
public bool Share, Options, PS, Touch1, Touch2, TouchButton, TouchRight,
|
||||||
TouchLeft, Touch1Finger, Touch2Fingers;
|
TouchLeft, Touch1Finger, Touch2Fingers;
|
||||||
public byte Touch1Identifier, Touch2Identifier;
|
public byte Touch1Identifier, Touch2Identifier;
|
||||||
@ -24,14 +25,27 @@ namespace DS4Windows
|
|||||||
public double RXUnit;
|
public double RXUnit;
|
||||||
public double RYUnit;
|
public double RYUnit;
|
||||||
public double elapsedTime = 0.0;
|
public double elapsedTime = 0.0;
|
||||||
|
public ulong totalMicroSec = 0;
|
||||||
public SixAxis Motion = null;
|
public SixAxis Motion = null;
|
||||||
public static readonly int DEFAULT_AXISDIR_VALUE = 127;
|
public static readonly int DEFAULT_AXISDIR_VALUE = 127;
|
||||||
|
|
||||||
|
public struct TrackPadTouch
|
||||||
|
{
|
||||||
|
public bool IsActive;
|
||||||
|
public byte Id;
|
||||||
|
public short X;
|
||||||
|
public short Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrackPadTouch TrackPadTouch0;
|
||||||
|
public TrackPadTouch TrackPadTouch1;
|
||||||
|
|
||||||
public DS4State()
|
public DS4State()
|
||||||
{
|
{
|
||||||
|
PacketCounter = 0;
|
||||||
Square = Triangle = Circle = Cross = false;
|
Square = Triangle = Circle = Cross = false;
|
||||||
DpadUp = DpadDown = DpadLeft = DpadRight = false;
|
DpadUp = DpadDown = DpadLeft = DpadRight = false;
|
||||||
L1 = L3 = R1 = R3 = false;
|
L1 = L2Btn = L3 = R1 = R2Btn = R3 = false;
|
||||||
Share = Options = PS = Touch1 = Touch2 = TouchButton = TouchRight = TouchLeft = false;
|
Share = Options = PS = Touch1 = Touch2 = TouchButton = TouchRight = TouchLeft = false;
|
||||||
Touch1Finger = Touch2Fingers = false;
|
Touch1Finger = Touch2Fingers = false;
|
||||||
LX = RX = LY = RY = 127;
|
LX = RX = LY = RY = 127;
|
||||||
@ -48,11 +62,15 @@ namespace DS4Windows
|
|||||||
RXUnit = 0.0;
|
RXUnit = 0.0;
|
||||||
RYUnit = 0.0;
|
RYUnit = 0.0;
|
||||||
elapsedTime = 0.0;
|
elapsedTime = 0.0;
|
||||||
|
totalMicroSec = 0;
|
||||||
Motion = new SixAxis(0, 0, 0, 0, 0, 0, 0.0);
|
Motion = new SixAxis(0, 0, 0, 0, 0, 0, 0.0);
|
||||||
|
TrackPadTouch0.IsActive = false;
|
||||||
|
TrackPadTouch1.IsActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DS4State(DS4State state)
|
public DS4State(DS4State state)
|
||||||
{
|
{
|
||||||
|
PacketCounter = state.PacketCounter;
|
||||||
ReportTimeStamp = state.ReportTimeStamp;
|
ReportTimeStamp = state.ReportTimeStamp;
|
||||||
Square = state.Square;
|
Square = state.Square;
|
||||||
Triangle = state.Triangle;
|
Triangle = state.Triangle;
|
||||||
@ -64,9 +82,11 @@ namespace DS4Windows
|
|||||||
DpadRight = state.DpadRight;
|
DpadRight = state.DpadRight;
|
||||||
L1 = state.L1;
|
L1 = state.L1;
|
||||||
L2 = state.L2;
|
L2 = state.L2;
|
||||||
|
L2Btn = state.L2Btn;
|
||||||
L3 = state.L3;
|
L3 = state.L3;
|
||||||
R1 = state.R1;
|
R1 = state.R1;
|
||||||
R2 = state.R2;
|
R2 = state.R2;
|
||||||
|
R2Btn = state.R2Btn;
|
||||||
R3 = state.R3;
|
R3 = state.R3;
|
||||||
Share = state.Share;
|
Share = state.Share;
|
||||||
Options = state.Options;
|
Options = state.Options;
|
||||||
@ -96,7 +116,10 @@ namespace DS4Windows
|
|||||||
RXUnit = state.RXUnit;
|
RXUnit = state.RXUnit;
|
||||||
RYUnit = state.RYUnit;
|
RYUnit = state.RYUnit;
|
||||||
elapsedTime = state.elapsedTime;
|
elapsedTime = state.elapsedTime;
|
||||||
|
totalMicroSec = state.totalMicroSec;
|
||||||
Motion = state.Motion;
|
Motion = state.Motion;
|
||||||
|
TrackPadTouch0 = state.TrackPadTouch0;
|
||||||
|
TrackPadTouch1 = state.TrackPadTouch1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DS4State Clone()
|
public DS4State Clone()
|
||||||
@ -106,6 +129,7 @@ namespace DS4Windows
|
|||||||
|
|
||||||
public void CopyTo(DS4State state)
|
public void CopyTo(DS4State state)
|
||||||
{
|
{
|
||||||
|
state.PacketCounter = PacketCounter;
|
||||||
state.ReportTimeStamp = ReportTimeStamp;
|
state.ReportTimeStamp = ReportTimeStamp;
|
||||||
state.Square = Square;
|
state.Square = Square;
|
||||||
state.Triangle = Triangle;
|
state.Triangle = Triangle;
|
||||||
@ -117,9 +141,11 @@ namespace DS4Windows
|
|||||||
state.DpadRight = DpadRight;
|
state.DpadRight = DpadRight;
|
||||||
state.L1 = L1;
|
state.L1 = L1;
|
||||||
state.L2 = L2;
|
state.L2 = L2;
|
||||||
|
state.L2Btn = L2Btn;
|
||||||
state.L3 = L3;
|
state.L3 = L3;
|
||||||
state.R1 = R1;
|
state.R1 = R1;
|
||||||
state.R2 = R2;
|
state.R2 = R2;
|
||||||
|
state.R2Btn = R2Btn;
|
||||||
state.R3 = R3;
|
state.R3 = R3;
|
||||||
state.Share = Share;
|
state.Share = Share;
|
||||||
state.Options = Options;
|
state.Options = Options;
|
||||||
@ -149,7 +175,10 @@ namespace DS4Windows
|
|||||||
state.RXUnit = RXUnit;
|
state.RXUnit = RXUnit;
|
||||||
state.RYUnit = RYUnit;
|
state.RYUnit = RYUnit;
|
||||||
state.elapsedTime = elapsedTime;
|
state.elapsedTime = elapsedTime;
|
||||||
|
state.totalMicroSec = totalMicroSec;
|
||||||
state.Motion = Motion;
|
state.Motion = Motion;
|
||||||
|
state.TrackPadTouch0 = TrackPadTouch0;
|
||||||
|
state.TrackPadTouch1 = TrackPadTouch1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void calculateStickAngles()
|
public void calculateStickAngles()
|
||||||
|
@ -158,6 +158,7 @@
|
|||||||
<DependentUpon>ScpHub.cs</DependentUpon>
|
<DependentUpon>ScpHub.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="DS4Control\ScpUtil.cs" />
|
<Compile Include="DS4Control\ScpUtil.cs" />
|
||||||
|
<Compile Include="DS4Control\UdpServer.cs" />
|
||||||
<Compile Include="DS4Control\X360Device.cs">
|
<Compile Include="DS4Control\X360Device.cs">
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
Loading…
Reference in New Issue
Block a user