Compare commits
20 commits
Author | SHA1 | Date | |
---|---|---|---|
27095ae6c7 | |||
8521e33d05 | |||
6998fb1e1e | |||
ff10e884dc | |||
0797c9488a | |||
4a99882118 | |||
a377bf6f00 | |||
67a5aa000e | |||
6c7cde03e9 | |||
3c7f3d02fd | |||
f1988072a4 | |||
|
df5fd8b05b | ||
|
65652d3a2a | ||
|
952b184f10 | ||
|
b959059755 | ||
|
117da9d24a | ||
6ffaeb801a | |||
|
703db561bb | ||
|
f9d072ea5a | ||
|
b9b3c8ef81 |
16 changed files with 932 additions and 723 deletions
BIN
Content/icons/sky-portal-icon-black.png
Normal file
BIN
Content/icons/sky-portal-icon-black.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
18
Content/icons/sky-portal-icon-black.svg
Normal file
18
Content/icons/sky-portal-icon-black.svg
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 404 404" width="404" height="404">
|
||||||
|
<title>sky-portal-icon</title>
|
||||||
|
<style>
|
||||||
|
.s0 { fill: #000000 }
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<path id="Circle" class="s0" d="m168.6 0l23 46h20.8l23-46zm-9.5 21.1q-5 1.2-9.9 2.7-4.9 1.4-9.7 3.2-4.8 1.7-9.6 3.7-4.7 2.1-9.2 4.3l7.2 30.2q5.2-2.9 10.7-5.4 5.5-2.5 11.1-4.6 5.7-2 11.5-3.6 5.8-1.7 11.7-2.8zm86.1 0l-13.9 27.7q5.9 1.2 11.7 2.8 5.8 1.6 11.5 3.6 5.6 2.1 11.1 4.6 5.5 2.5 10.7 5.4l7.3-30.2q-4.6-2.2-9.3-4.2-4.7-2.1-9.6-3.8-4.8-1.8-9.7-3.2-4.9-1.5-9.8-2.7zm59.2 4l-11.9 50 16 13.4 47.2-20.5c0 0-51.3-42.9-51.3-42.9zm-204.6 0l-51.2 43 47.2 20.4 16-13.4c0 0-12-50-12-50zm249.7 65.3l-28.5 12.3q3.7 4.7 7.2 9.6 3.4 5 6.4 10.2 3 5.2 5.6 10.7 2.6 5.4 4.8 11l24.9-18.5q-2.1-4.7-4.4-9.2-2.3-4.6-4.8-9-2.6-4.5-5.4-8.7-2.8-4.3-5.8-8.4zm-294.7 0q-3 4.1-5.8 8.4-2.9 4.3-5.4 8.7-2.6 4.4-4.9 9-2.3 4.6-4.3 9.3l24.9 18.4q2.1-5.6 4.7-11 2.6-5.5 5.6-10.7 3.1-5.2 6.5-10.1 3.4-5 7.2-9.7zm337.5 41.2l-41.3 30.6 3.7 20.6 49.2 14.6c0 0-11.6-65.8-11.6-65.8zm-380.3 0l-11.6 65.9 49.3-14.7 3.6-20.5c0 0-41.3-30.7-41.3-30.7zm343.1 70.1c-0.3 16.5-3.2 32.4-8.3 47.2l30.9 1.9q1.5-5 2.6-9.9 1.2-5 2.1-10.1 0.9-5 1.5-10.1 0.6-5.1 0.9-10.2zm-306 0.1l-29.6 8.8q0.3 5.1 0.9 10.2 0.6 5.1 1.5 10.1 0.8 5 2 10 1.2 5 2.6 9.9l31-1.8c-5.2-14.9-8.1-30.7-8.4-47.2zm290.8 64.7l-10.5 18.1 28.4 42.9 33.4-57.9zm-275.4 0.1l-51.4 3.1 33.5 57.9 28.3-43zm253.2 32.8q-3 3.5-6.2 6.7-3.2 3.3-6.6 6.4-3.4 3-7 5.9-3.5 2.9-7.2 5.5l14.7 26.5q4-2.8 7.8-5.7 3.8-2.9 7.5-6.1 3.7-3.1 7.2-6.4 3.5-3.4 6.9-6.9zm-230.9 0.1l-17.2 25.9q3.4 3.5 6.9 6.8 3.5 3.4 7.2 6.5 3.6 3.1 7.5 6 3.8 3 7.8 5.7l14.7-26.5q-3.7-2.6-7.3-5.5-3.5-2.8-6.9-5.9-3.4-3-6.6-6.3-3.1-3.2-6.1-6.7zm13.3 79.7l62.9 22.9-5.9-51.1-19.6-7.1c0 0-37.4 35.3-37.4 35.3zm79.1 6.4q5.1 0.6 10.2 0.8 5.1 0.3 10.2 0.3 5.1 0 10.2-0.2 5.1-0.3 10.2-0.9l3.6-30.8q-6 0.9-12 1.4-6 0.5-12 0.4-6 0-12-0.4-6-0.5-12-1.5zm57.1 16.5l62.8-22.9-37.3-35.3-19.6 7.1c0 0-5.9 51.1-5.9 51.1z"/>
|
||||||
|
<g id="Vortex">
|
||||||
|
<path id="Vortex7" class="s0" d="m182.3 194.1c-2.1 3.5-19.8 50.8 46.8 73.2 50.6 11.2 85.3-32.6 85.3-32.6 0 0-14.6 66.8-95.6 54.5-73.9-23.5-54.9-83.4-36.5-95.1z"/>
|
||||||
|
<path id="Vortex6" class="s0" d="m197.4 181.2c-4.1 0.4-52.2 15.8-28.5 82 22.5 46.7 78.3 46.9 78.3 46.9 0 0-61.5 29.8-101.9-41.5-27.3-72.5 31.5-94.6 52.1-87.4z"/>
|
||||||
|
<path id="Vortex5" class="s0" d="m217.4 185.2c-2.8-3-44.2-32-82.6 26.9-23.5 46.1 10.1 90.7 10.1 90.7 0 0-61-30.9-28.7-106.2 41.3-65.6 94.5-32.2 101.2-11.4z"/>
|
||||||
|
<path id="Vortex4" class="s0" d="m182.4 212.2c1.1 3.9 23.5 49.2 85.4 15.9 42.8-29.2 34.6-84.5 34.6-84.5 0 0 38.7 56.4-25.7 107-67.7 37.9-98.3-17-94.3-38.4z"/>
|
||||||
|
<path id="Vortex3" class="s0" d="m197 225.2c3.7 1.6 52.8 13.2 66.6-55.7 4.7-51.6-43.1-80.5-43.1-80.5 0 0 68.1 6.1 66.1 88-13.9 76.2-75.7 64.9-89.6 48.2z"/>
|
||||||
|
<path id="Vortex2" class="s0" d="m218 220.7c3.5-2.1 40.5-36.4-9-86.3-40.1-32.8-90.8-9.3-90.8-9.3 0 0 43.1-53.1 109.9-5.6 55.5 54.1 11.6 99.1-10.1 101.2z"/>
|
||||||
|
<path id="Vortex1" class="s0" d="m226.2 203.4c0.6-4.1-1.6-54.5-71.7-49.1-50.9 9.4-65.8 63.3-65.8 63.3 0 0-12.6-67.2 66.8-87.5 77.2-7.2 83.1 55.3 70.7 73.3z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
BIN
Content/icons/sky-portal-icon-white.png
Normal file
BIN
Content/icons/sky-portal-icon-white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
18
Content/icons/sky-portal-icon-white.svg
Normal file
18
Content/icons/sky-portal-icon-white.svg
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 404 404" width="404" height="404">
|
||||||
|
<title>sky-portal-icon</title>
|
||||||
|
<style>
|
||||||
|
.s0 { fill: #ffffff }
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<path id="Circle" class="s0" d="m168.6 0l23 46h20.8l23-46zm-9.5 21.1q-5 1.2-9.9 2.7-4.9 1.4-9.7 3.2-4.8 1.7-9.6 3.7-4.7 2.1-9.2 4.3l7.2 30.2q5.2-2.9 10.7-5.4 5.5-2.5 11.1-4.6 5.7-2 11.5-3.6 5.8-1.7 11.7-2.8zm86.1 0l-13.9 27.7q5.9 1.2 11.7 2.8 5.8 1.6 11.5 3.6 5.6 2.1 11.1 4.6 5.5 2.5 10.7 5.4l7.3-30.2q-4.6-2.2-9.3-4.2-4.7-2.1-9.6-3.8-4.8-1.8-9.7-3.2-4.9-1.5-9.8-2.7zm59.2 4l-11.9 50 16 13.4 47.2-20.5c0 0-51.3-42.9-51.3-42.9zm-204.6 0l-51.2 43 47.2 20.4 16-13.4c0 0-12-50-12-50zm249.7 65.3l-28.5 12.3q3.7 4.7 7.2 9.6 3.4 5 6.4 10.2 3 5.2 5.6 10.7 2.6 5.4 4.8 11l24.9-18.5q-2.1-4.7-4.4-9.2-2.3-4.6-4.8-9-2.6-4.5-5.4-8.7-2.8-4.3-5.8-8.4zm-294.7 0q-3 4.1-5.8 8.4-2.9 4.3-5.4 8.7-2.6 4.4-4.9 9-2.3 4.6-4.3 9.3l24.9 18.4q2.1-5.6 4.7-11 2.6-5.5 5.6-10.7 3.1-5.2 6.5-10.1 3.4-5 7.2-9.7zm337.5 41.2l-41.3 30.6 3.7 20.6 49.2 14.6c0 0-11.6-65.8-11.6-65.8zm-380.3 0l-11.6 65.9 49.3-14.7 3.6-20.5c0 0-41.3-30.7-41.3-30.7zm343.1 70.1c-0.3 16.5-3.2 32.4-8.3 47.2l30.9 1.9q1.5-5 2.6-9.9 1.2-5 2.1-10.1 0.9-5 1.5-10.1 0.6-5.1 0.9-10.2zm-306 0.1l-29.6 8.8q0.3 5.1 0.9 10.2 0.6 5.1 1.5 10.1 0.8 5 2 10 1.2 5 2.6 9.9l31-1.8c-5.2-14.9-8.1-30.7-8.4-47.2zm290.8 64.7l-10.5 18.1 28.4 42.9 33.4-57.9zm-275.4 0.1l-51.4 3.1 33.5 57.9 28.3-43zm253.2 32.8q-3 3.5-6.2 6.7-3.2 3.3-6.6 6.4-3.4 3-7 5.9-3.5 2.9-7.2 5.5l14.7 26.5q4-2.8 7.8-5.7 3.8-2.9 7.5-6.1 3.7-3.1 7.2-6.4 3.5-3.4 6.9-6.9zm-230.9 0.1l-17.2 25.9q3.4 3.5 6.9 6.8 3.5 3.4 7.2 6.5 3.6 3.1 7.5 6 3.8 3 7.8 5.7l14.7-26.5q-3.7-2.6-7.3-5.5-3.5-2.8-6.9-5.9-3.4-3-6.6-6.3-3.1-3.2-6.1-6.7zm13.3 79.7l62.9 22.9-5.9-51.1-19.6-7.1c0 0-37.4 35.3-37.4 35.3zm79.1 6.4q5.1 0.6 10.2 0.8 5.1 0.3 10.2 0.3 5.1 0 10.2-0.2 5.1-0.3 10.2-0.9l3.6-30.8q-6 0.9-12 1.4-6 0.5-12 0.4-6 0-12-0.4-6-0.5-12-1.5zm57.1 16.5l62.8-22.9-37.3-35.3-19.6 7.1c0 0-5.9 51.1-5.9 51.1z"/>
|
||||||
|
<g id="Vortex">
|
||||||
|
<path id="Vortex7" class="s0" d="m182.3 194.1c-2.1 3.5-19.8 50.8 46.8 73.2 50.6 11.2 85.3-32.6 85.3-32.6 0 0-14.6 66.8-95.6 54.5-73.9-23.5-54.9-83.4-36.5-95.1z"/>
|
||||||
|
<path id="Vortex6" class="s0" d="m197.4 181.2c-4.1 0.4-52.2 15.8-28.5 82 22.5 46.7 78.3 46.9 78.3 46.9 0 0-61.5 29.8-101.9-41.5-27.3-72.5 31.5-94.6 52.1-87.4z"/>
|
||||||
|
<path id="Vortex5" class="s0" d="m217.4 185.2c-2.8-3-44.2-32-82.6 26.9-23.5 46.1 10.1 90.7 10.1 90.7 0 0-61-30.9-28.7-106.2 41.3-65.6 94.5-32.2 101.2-11.4z"/>
|
||||||
|
<path id="Vortex4" class="s0" d="m182.4 212.2c1.1 3.9 23.5 49.2 85.4 15.9 42.8-29.2 34.6-84.5 34.6-84.5 0 0 38.7 56.4-25.7 107-67.7 37.9-98.3-17-94.3-38.4z"/>
|
||||||
|
<path id="Vortex3" class="s0" d="m197 225.2c3.7 1.6 52.8 13.2 66.6-55.7 4.7-51.6-43.1-80.5-43.1-80.5 0 0 68.1 6.1 66.1 88-13.9 76.2-75.7 64.9-89.6 48.2z"/>
|
||||||
|
<path id="Vortex2" class="s0" d="m218 220.7c3.5-2.1 40.5-36.4-9-86.3-40.1-32.8-90.8-9.3-90.8-9.3 0 0 43.1-53.1 109.9-5.6 55.5 54.1 11.6 99.1-10.1 101.2z"/>
|
||||||
|
<path id="Vortex1" class="s0" d="m226.2 203.4c0.6-4.1-1.6-54.5-71.7-49.1-50.9 9.4-65.8 63.3-65.8 63.3 0 0-12.6-67.2 66.8-87.5 77.2-7.2 83.1 55.3 70.7 73.3z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
|
@ -1,6 +1,7 @@
|
||||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
#include "SkyPortal.h"
|
#include "SkyPortal.h"
|
||||||
|
#include "SkyPortalSubsystem.h"
|
||||||
|
|
||||||
#define LOCTEXT_NAMESPACE "FSkyPortalModule"
|
#define LOCTEXT_NAMESPACE "FSkyPortalModule"
|
||||||
|
|
||||||
|
@ -13,6 +14,10 @@ void FSkyPortalModule::ShutdownModule()
|
||||||
{
|
{
|
||||||
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||||
// we call this function before unloading the module.
|
// we call this function before unloading the module.
|
||||||
|
|
||||||
|
if (GEngine && GEngine->GetEngineSubsystem<USkyPortalSubsystem>()) {
|
||||||
|
GEngine->GetEngineSubsystem<USkyPortalSubsystem>()->Deinitialize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef LOCTEXT_NAMESPACE
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
|
@ -1,81 +1,57 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "SkyPortalFigure.h"
|
#include "SkyPortalFigure.h"
|
||||||
#include "SkyPortalSubsystem.h"
|
|
||||||
|
|
||||||
uint32 GetFigureID(const FigureDataBlock& DataBlock)
|
UFigureData::UFigureData()
|
||||||
{
|
{
|
||||||
int16_t OutFigureID;
|
ClearData();
|
||||||
// Figure ID is stored in Block 0, bytes 0 to 3 (32-bit integer, little-endian)
|
GenerationID = EVariantIDGeneration::UNKNOW;
|
||||||
OutFigureID = DataBlock.blockdata[0][0] |
|
}
|
||||||
(DataBlock.blockdata[0][1] << 8) |
|
|
||||||
(DataBlock.blockdata[0][2] << 16) |
|
int32 UFigureData::GetFigureID()
|
||||||
(DataBlock.blockdata[0][3] << 24);
|
{
|
||||||
|
if (ID == 0) {
|
||||||
|
int16_t OutFigureID = 0;
|
||||||
|
|
||||||
|
// The figure ID is stored in Block 1 of Sector 0 (i.e., data[1]), bytes 0 and 1 (16-bit integer, little-endian)
|
||||||
|
OutFigureID = decryptedData[1][0] | // Least significant byte
|
||||||
|
(data[1][1] << 8); // Most significant byte, shifted left by 8 bits
|
||||||
|
|
||||||
|
ID = OutFigureID;// Return the 16-bit figure ID
|
||||||
|
return OutFigureID;
|
||||||
|
}
|
||||||
|
else { return ID; }
|
||||||
/*
|
/*
|
||||||
// Variant ID is stored in Block 0, bytes 4 to 5 (16-bit integer, little-endian)
|
// Variant ID is stored in Block 0, bytes 4 to 5 (16-bit integer, little-endian)
|
||||||
OutVariantID = (DataBlock.blockdata[0][5] << 8) |
|
OutVariantID = (DataBlock.blockdata[0][5] << 8) |
|
||||||
DataBlock.blockdata[0][4];
|
DataBlock.blockdata[0][4];
|
||||||
*/
|
*/
|
||||||
return OutFigureID;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int32 UFigureData::GetVariantID() {
|
||||||
|
|
||||||
void FigureData::ReadData(uint8 index)
|
int16_t OutFigureID = 0;
|
||||||
{
|
|
||||||
ClearData();
|
|
||||||
for (uint8 i = 0; i < 64; i++)
|
|
||||||
{
|
|
||||||
USkyPortalSubsystem* SkySubsystem = GEngine->GetEngineSubsystem<USkyPortalSubsystem>();
|
|
||||||
TArray<uint8> output = SkySubsystem->QueryBlock(index, i);
|
|
||||||
|
|
||||||
uint8[] blockData = new uint8[0x10];
|
// The figure ID is stored in Block 1 of Sector 0 (i.e., data[1]), bytes 0 and 1 (16-bit integer, little-endian)
|
||||||
Array.Copy(output, 3, blockData, 0, 16);
|
OutFigureID = decryptedData[1][0] | // Least significant byte
|
||||||
|
(data[1][1] << 8); // Most significant byte, shifted left by 8 bits
|
||||||
|
|
||||||
// block 1 is sometimes a duplicate of block 0
|
VariantID = OutFigureID;// Return the 16-bit figure ID
|
||||||
if (i == 1)
|
return OutFigureID;
|
||||||
{
|
|
||||||
if (blockData.SequenceEqual(data[0]))
|
|
||||||
{
|
|
||||||
i -= 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Array.Copy(blockData, 0, data[i], 0, 16);
|
EVariantIDGeneration UFigureData::GetGeneration()
|
||||||
|
|
||||||
if (((i + 1) % 4 == 0) || i < 8)
|
|
||||||
{
|
{
|
||||||
Array.Copy(data[i], 0, decryptedData[i], 0, 16);
|
return static_cast<EVariantIDGeneration>(VariantID);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uint8[] hashIn = new byte[0x56];
|
|
||||||
data[0].CopyTo(hashIn, 0);
|
|
||||||
data[1].CopyTo(hashIn, 0x10);
|
|
||||||
hashIn[0x20] = i;
|
|
||||||
HASH_CONST.CopyTo(hashIn, 0x21);
|
|
||||||
|
|
||||||
uint8[] key = MD5.Create().ComputeHash(hashIn);
|
|
||||||
|
|
||||||
uint8[] decrypted = new byte[0x10];
|
|
||||||
|
|
||||||
using (Aes aesAlg = Aes.Create())
|
|
||||||
{
|
|
||||||
aesAlg.Key = key;
|
|
||||||
aesAlg.Mode = CipherMode.ECB;
|
|
||||||
aesAlg.Padding = PaddingMode.Zeros;
|
|
||||||
|
|
||||||
ICryptoTransform decryptor = aesAlg.CreateDecryptor();
|
|
||||||
|
|
||||||
decrypted = decryptor.TransformFinalBlock(data[i], 0, data[i].Length);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Array.Copy(decrypted, 0, decryptedData[i], 0, 16);
|
void UFigureData::ClearData()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FigureData::ClearData()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UFigureData::Dump(bool decrypted, FString filePath)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
#include "SkyPortalIO.h"
|
#include "SkyPortalIO.h"
|
||||||
|
#include "Misc/AES.h"
|
||||||
|
|
||||||
|
#include "HAL/UnrealMemory.h"
|
||||||
|
#include "openssl/aes.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DEFINE_LOG_CATEGORY(LogHIDApi);
|
DEFINE_LOG_CATEGORY(LogHIDApi);
|
||||||
DEFINE_LOG_CATEGORY(LogSkyportalIO);
|
DEFINE_LOG_CATEGORY(LogSkyportalIO);
|
||||||
|
@ -7,7 +13,7 @@ DEFINE_LOG_CATEGORY(LogSkyportalIO);
|
||||||
//Constructor
|
//Constructor
|
||||||
USkyPortalIO::USkyPortalIO()
|
USkyPortalIO::USkyPortalIO()
|
||||||
{
|
{
|
||||||
OpenPortalHandle();
|
bPortalReady = OpenPortalHandle();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +22,7 @@ bool USkyPortalIO::OpenPortalHandle() {
|
||||||
//reset
|
//reset
|
||||||
if (PortalDevice) {
|
if (PortalDevice) {
|
||||||
hid_close(PortalDevice);
|
hid_close(PortalDevice);
|
||||||
|
bPortalReady = false;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
Declare two pointers to hold information about HID devices.
|
Declare two pointers to hold information about HID devices.
|
||||||
|
@ -57,7 +64,6 @@ bool USkyPortalIO::OpenPortalHandle() {
|
||||||
|
|
||||||
// Free the device list
|
// Free the device list
|
||||||
hid_free_enumeration(list);
|
hid_free_enumeration(list);
|
||||||
//bPortalConnected = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,11 +110,11 @@ struct hid_device_ {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void USkyPortalIO::Write(FWriteBlock* pb)
|
bool USkyPortalIO::Write(FWriteBlock* pb)
|
||||||
{
|
{
|
||||||
if (!ensure(PortalDevice)) {
|
if (!ensure(PortalDevice)) {
|
||||||
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
|
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
BOOL res;
|
BOOL res;
|
||||||
|
|
||||||
|
@ -123,39 +129,90 @@ void USkyPortalIO::Write(FWriteBlock* pb)
|
||||||
(unsigned char*)pb->data, write_buf_size,
|
(unsigned char*)pb->data, write_buf_size,
|
||||||
&bytes_returned, &ol);
|
&bytes_returned, &ol);
|
||||||
ensureMsgf(res, TEXT("Unable to write to Portal"));
|
ensureMsgf(res, TEXT("Unable to write to Portal"));
|
||||||
|
pb->BytesTransferred = bytes_returned;
|
||||||
|
return res;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
void USkyPortalIO::Write(FWriteBlock* pb) {
|
bool USkyPortalIO::Write(FWriteBlock* pb) {
|
||||||
|
|
||||||
if (!ensure(PortalDevice)) {
|
if (!ensure(PortalDevice)) {
|
||||||
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
|
UE_LOG(LogSkyportalIO, Error, TEXT("No Portal found"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pb->buf[0] = 0; // Use report 0
|
pb->data[0] = 0; // Use report 0
|
||||||
|
|
||||||
|
res = hid_write(PortalDevice, pb->data, write_buf_size);
|
||||||
ensureMsgf(hid_write(PortalDevice, pb->data, read_buf_size) != -1, TEXT("Unable to write to Portal, %s"), hid_error(PortalDevice));
|
ensureMsgf(res != -1, TEXT("Unable to write to Portal, %s"), hid_error(PortalDevice));
|
||||||
UE_LOG(LogSkyportalIO, Error, TEXT("Unable to write to Portal. error:\n %s"), hid_error(PortalDevice));
|
pb->BytesTransferred = res;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void USkyPortalIO::WriteRaw(FWriteBlock* block)
|
void USkyPortalIO::WriteRaw(const TArray<uint8>* block)
|
||||||
{
|
{
|
||||||
int res = hid_write(PortalDevice, block->data, write_buf_size);
|
;
|
||||||
|
int res = hid_write(PortalDevice, block->GetData(), block->GetAllocatedSize()); //return the number of byte written
|
||||||
if (res == -1)
|
if (res == -1)
|
||||||
{
|
{
|
||||||
UE_LOG(LogSkyportalIO, Error, TEXT("Unable to write raw data to Portal. error:\n %s"), hid_error(PortalDevice));
|
UE_LOG(LogSkyportalIO, Error, TEXT("Unable to write raw data to Portal. error:\n %s"), hid_error(PortalDevice));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
block->BytesTransferred = res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8* USkyPortalIO::Read()
|
uint8* USkyPortalIO::Read()
|
||||||
{
|
{
|
||||||
uint8* output = 0;
|
uint8* output = new uint8[0x20];
|
||||||
hid_read_timeout(PortalDevice, output, 0x20, TIMEOUT);
|
int result = hid_read(PortalDevice, output, 0x20);
|
||||||
|
|
||||||
|
if (result == -1) {
|
||||||
|
UE_LOG(LogSkyportalIO, Error, TEXT("Unable to read data from Portal. Error:\n%s"), hid_error(PortalDevice));
|
||||||
|
delete[] output; // Prevent memory leak
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == 0) { // No data was read
|
||||||
|
UE_LOG(LogSkyportalIO, Warning, TEXT("No data read from portal"));
|
||||||
|
delete[] output;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8* USkyPortalIO::QueryBlock(uint8 FigureIndex, uint8 BlockIndex)
|
||||||
|
{
|
||||||
|
UE_LOG(LogSkyportalIO, Verbose, TEXT("Querying block %d - Start"), BlockIndex);
|
||||||
|
FWriteBlock command;
|
||||||
|
memset(command.data, 0, write_buf_size); //maybe not needed here
|
||||||
|
command.data[1] = 'Q';
|
||||||
|
command.data[2] = FigureIndex; // Figure index (0x00-0x0F)
|
||||||
|
command.data[3] = BlockIndex;
|
||||||
|
unsigned char* output = nullptr;
|
||||||
|
int attempt=0;
|
||||||
|
bool write;
|
||||||
|
do {
|
||||||
|
attempt++;
|
||||||
|
UE_LOG(LogSkyportalIO, Verbose, TEXT("Trying to write... attempt:%d"), attempt);
|
||||||
|
write = Write(&command);
|
||||||
|
if (write) {
|
||||||
|
output = Read();
|
||||||
|
if (output != nullptr && output != 0) {
|
||||||
|
UE_LOG(LogSkyportalIO, Verbose, TEXT("Read success: output[0]=%d, output[1]=%d, output[2]=%d"), output[0], output[1], output[2]);
|
||||||
|
}else{
|
||||||
|
UE_LOG(LogSkyportalIO, Error, TEXT("Read failed, output is null"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (write == false && output && (output[0] != 'Q' || (output[1] % 0x10 != FigureIndex && output[1] != 0x01) || output[2] != BlockIndex) && attempt < 10);
|
||||||
|
if (output == nullptr) {
|
||||||
|
UE_LOG(LogSkyportalIO, Error, TEXT("Query failed after %d attempts"), attempt);
|
||||||
|
return 0; // Return early if the loop failed to get valid output
|
||||||
|
}
|
||||||
|
UE_LOG(LogSkyportalIO, Verbose, TEXT("Querying block %d - success"), BlockIndex);
|
||||||
|
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("Data block %d = \n %s"), BlockIndex,*OutputToString(output));
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,4 +220,208 @@ void USkyPortalIO::Close() {
|
||||||
if (PortalDevice) {
|
if (PortalDevice) {
|
||||||
hid_close(PortalDevice);
|
hid_close(PortalDevice);
|
||||||
}
|
}
|
||||||
|
bPortalReady = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* TODO: Should not be here
|
||||||
|
bool USkyPortalIO::IsFalsePositive() const
|
||||||
|
{
|
||||||
|
int dif = FMath::Abs(CurrentStatusData.Counter - OldStatusData.Counter);
|
||||||
|
return (dif <= 2 || dif >= 254);
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
EPortalCommand GetPortalCommandFromChar(unsigned char Char)
|
||||||
|
{
|
||||||
|
switch (Char)
|
||||||
|
{
|
||||||
|
case 'A':
|
||||||
|
return EPortalCommand::A;
|
||||||
|
case 'C':
|
||||||
|
return EPortalCommand::C;
|
||||||
|
case 'J':
|
||||||
|
return EPortalCommand::J;
|
||||||
|
case 'L':
|
||||||
|
return EPortalCommand::L;
|
||||||
|
case 'M':
|
||||||
|
return EPortalCommand::M;
|
||||||
|
case 'Q':
|
||||||
|
return EPortalCommand::Q;
|
||||||
|
case 'R':
|
||||||
|
return EPortalCommand::R;
|
||||||
|
case 'S':
|
||||||
|
return EPortalCommand::S;
|
||||||
|
default:
|
||||||
|
// Handle the case when the character doesn't match any enum
|
||||||
|
// Return a default or invalid value, or handle the error
|
||||||
|
UE_LOG(LogSkyportalIO, Warning, TEXT("Invalid character for Portal Command: %c"), TCHAR(Char));
|
||||||
|
return EPortalCommand::S; // 'S' for Status as a default
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
FString DataToString(const uint8* data)
|
||||||
|
{
|
||||||
|
FString OutString;
|
||||||
|
for (int32 i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
OutString += FString::Printf(TEXT("%02x"), data[i]);
|
||||||
|
}
|
||||||
|
return OutString;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString OutputToString(const unsigned char* output) {
|
||||||
|
FString OutString;
|
||||||
|
for (int32 i = 0; i < 0x20; i++)
|
||||||
|
{
|
||||||
|
OutString += FString::Printf(TEXT("%02x"), output[i]);
|
||||||
|
}
|
||||||
|
return OutString;
|
||||||
|
}
|
||||||
|
|
||||||
|
FPortalStatusData ParsePortalStatus(const uint8* StatusResponse)
|
||||||
|
{
|
||||||
|
FPortalStatusData result;
|
||||||
|
result.Counter = StatusResponse[5];
|
||||||
|
result.bIsReady = (StatusResponse[6] == 0x01);
|
||||||
|
|
||||||
|
// Parse the figure status array (little-endian 32-bit integer)
|
||||||
|
uint32 FigureStatusArray = 0;
|
||||||
|
// Reading the 32-bit integer (character status array) from the buffer
|
||||||
|
FigureStatusArray |= StatusResponse[1]; // 1st byte
|
||||||
|
FigureStatusArray |= (StatusResponse[2] << 8); // 2nd byte
|
||||||
|
FigureStatusArray |= (StatusResponse[3] << 16); // 3rd byte
|
||||||
|
FigureStatusArray |= (StatusResponse[4] << 24); // 4th byte
|
||||||
|
|
||||||
|
bool bChangeBitsSet = false;
|
||||||
|
|
||||||
|
TStaticArray<EFigureStatus, 16> tempArray;
|
||||||
|
// For each of the 16 entries, extract the 2-bit status and map it to EFigureStatus
|
||||||
|
for (int32 i = 0; i < 16; ++i)
|
||||||
|
{
|
||||||
|
uint8 StatusBits = (FigureStatusArray >> (i * 2)) & 0b11; // Extract 2 bits
|
||||||
|
EFigureStatus FigureStatus;
|
||||||
|
|
||||||
|
switch (StatusBits)
|
||||||
|
{
|
||||||
|
case 0b00:
|
||||||
|
FigureStatus = EFigureStatus::NOT_PRESENT;
|
||||||
|
break;
|
||||||
|
case 0b01:
|
||||||
|
FigureStatus = EFigureStatus::PRESENT;
|
||||||
|
break;
|
||||||
|
case 0b11:
|
||||||
|
FigureStatus = EFigureStatus::ADDED; // When it's a new NUID detected
|
||||||
|
break;
|
||||||
|
case 0b10:
|
||||||
|
FigureStatus = EFigureStatus::REMOVED; // New NUID removed
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FigureStatus = EFigureStatus::NOT_PRESENT; // Default case
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the array of figure statuses
|
||||||
|
//PortalStatusData.StatusArray.Insert(FigureStatus, i);
|
||||||
|
tempArray[i] = FigureStatus;
|
||||||
|
}
|
||||||
|
result.StatusArray.SetNum(0);
|
||||||
|
result.StatusArray.Append(tempArray);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DecryptAES128(uint8* OutData, const uint8* InData, const uint8* Key)
|
||||||
|
{
|
||||||
|
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("DECRYPT -- Starting decryting data %s with %s key"), *DataToString(InData), *DataToString(Key));
|
||||||
|
AES_KEY AesKey;
|
||||||
|
// Set the decryption key (16 bytes for AES-128)
|
||||||
|
AES_set_decrypt_key(Key, 128, &AesKey);
|
||||||
|
|
||||||
|
// Perform AES-128 decryption (ECB mode)
|
||||||
|
AES_ecb_encrypt(InData, OutData, &AesKey, AES_DECRYPT);
|
||||||
|
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("DECRYPT -- data %s with %s key decryted"), *DataToString(InData), *DataToString(Key));
|
||||||
|
}
|
||||||
|
|
||||||
|
UFigureData* USkyPortalIO::ReadFigureBlocks(uint8 FigureIndex)
|
||||||
|
{
|
||||||
|
|
||||||
|
UFigureData* TempFigureData = NewObject<UFigureData>();
|
||||||
|
TempFigureData->dataError = false; // Initialize error flag
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Loop over all 64 blocks
|
||||||
|
for (uint8 BlockIndex = 0; BlockIndex < FIGURE_TOTAL_BLOCKS; ++BlockIndex)
|
||||||
|
{
|
||||||
|
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("Reading block %d"), BlockIndex);
|
||||||
|
|
||||||
|
// Query the block from the portal
|
||||||
|
uint8* output = QueryBlock(FigureIndex, BlockIndex);
|
||||||
|
if (output == 0) {
|
||||||
|
--BlockIndex;
|
||||||
|
UE_LOG(LogSkyportalIO, Error, TEXT("Query error, reprocessing..."));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy 16 bytes from the output, starting at the third byte
|
||||||
|
FMemory::Memcpy(TempFigureData->data[BlockIndex], output + 3, FIGURE_BLOCK_SIZE);
|
||||||
|
|
||||||
|
|
||||||
|
// Block 1 is sometimes a duplicate of block 0
|
||||||
|
if (BlockIndex == 1)
|
||||||
|
{
|
||||||
|
if (FMemory::Memcmp(TempFigureData->data[1], TempFigureData->data[0], FIGURE_BLOCK_SIZE) == 0)
|
||||||
|
{
|
||||||
|
// Decrement BlockIndex to reprocess block 1 if it's a duplicate of block 0
|
||||||
|
--BlockIndex;
|
||||||
|
UE_LOG(LogSkyportalIO, Verbose, TEXT("Block 1 is a duplicate of block 0, reprocessing..."));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((BlockIndex + 1) % 4 == 0) || BlockIndex < 8)
|
||||||
|
{
|
||||||
|
// Direct copy from data to decryptedData for certain blocks
|
||||||
|
FMemory::Memcpy(TempFigureData->decryptedData[BlockIndex], TempFigureData->data[BlockIndex], FIGURE_BLOCK_SIZE);
|
||||||
|
UE_LOG(LogSkyportalIO, Verbose, TEXT("Block %d doesnt need decryption"), BlockIndex);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/***** MD5 Hash Calculation *****/
|
||||||
|
// Prepare the hash input buffer
|
||||||
|
UE_LOG(LogSkyportalIO, Verbose, TEXT("DECRYPT -- Starting decryting block %d"), BlockIndex);
|
||||||
|
uint8 hashIn[0x56];
|
||||||
|
FMemory::Memcpy(hashIn, TempFigureData->data[0], 16);
|
||||||
|
FMemory::Memcpy(hashIn + 0x10, TempFigureData->data[1], 16);
|
||||||
|
hashIn[0x20] = BlockIndex;
|
||||||
|
// Append the 35-byte constant
|
||||||
|
FMemory::Memcpy(hashIn + 0x21, HASH_CONST, sizeof(HASH_CONST));
|
||||||
|
|
||||||
|
// Compute the MD5 hash
|
||||||
|
FMD5 MD5;
|
||||||
|
MD5.Update(hashIn, sizeof(hashIn));
|
||||||
|
uint8 key[16]; // MD5 produces 16 bytes
|
||||||
|
MD5.Final(key);
|
||||||
|
|
||||||
|
// AES-128 ECB decryption
|
||||||
|
|
||||||
|
FAES::FAESKey AesKey;
|
||||||
|
FMemory::Memcpy(AesKey.Key, key, 16);
|
||||||
|
|
||||||
|
//TODO: FAES implementation in Unreal is AES-256. We need AES-128
|
||||||
|
//FAES::DecryptData(TempFigureData.decryptedData[BlockIndex], TempFigureData.data[BlockIndex], 16, AesKey);
|
||||||
|
DecryptAES128(TempFigureData->decryptedData[BlockIndex], TempFigureData->data[BlockIndex], key);
|
||||||
|
UE_LOG(LogSkyportalIO, Verbose, TEXT("DECRYPT -- block %d decrypted"), BlockIndex);
|
||||||
|
}
|
||||||
|
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("block %d readed with success"), BlockIndex);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some verifications should happen. Like if dataError is set.
|
||||||
|
|
||||||
|
return TempFigureData; // Return the complete figure data
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,110 @@
|
||||||
#include "SkyPortalRunner.h"
|
#include "SkyPortalRunner.h"
|
||||||
|
#include "SkyPortalFigure.h"
|
||||||
|
#include "SkyPortalSubsystem.h"
|
||||||
|
|
||||||
|
FPortalStatusChecker::FPortalStatusChecker(UEngineSubsystem* InSubsystem, float InCheckInterval)
|
||||||
|
: SkyPortalSubsystemRef(InSubsystem), CheckInterval(InCheckInterval), bShouldRun(true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FPortalStatusChecker::Init()
|
||||||
|
{
|
||||||
|
// Initialization logic, if necessary (e.g., logging)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 FPortalStatusChecker::Run()
|
||||||
|
{
|
||||||
|
// Main loop of the thread, runs until Stop() is called
|
||||||
|
while (bShouldRun)
|
||||||
|
{
|
||||||
|
USkyPortalSubsystem* subref = Cast<USkyPortalSubsystem>(SkyPortalSubsystemRef);
|
||||||
|
if (subref && !(subref->bShouldPauseRunner)) {
|
||||||
|
// Check the portal status
|
||||||
|
CheckPortalStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep for the specified interval
|
||||||
|
FPlatformProcess::Sleep(CheckInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // Exit code for the thread
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPortalStatusChecker::Stop()
|
||||||
|
{
|
||||||
|
// Signal the thread to stop running
|
||||||
|
bShouldRun = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPortalStatusChecker::Exit()
|
||||||
|
{
|
||||||
|
// Cleanup after the thread has stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPortalStatusChecker::CheckPortalStatus()
|
||||||
|
{
|
||||||
|
// Ensure the subsystem is valid
|
||||||
|
if (SkyPortalSubsystemRef && Cast<USkyPortalSubsystem>(SkyPortalSubsystemRef))
|
||||||
|
{
|
||||||
|
USkyPortalSubsystem* subref = Cast<USkyPortalSubsystem>(SkyPortalSubsystemRef);
|
||||||
|
USkyPortalIO* PortalHandleRef = subref->PortalHandle;
|
||||||
|
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("Check portal"));
|
||||||
|
if (PortalHandleRef && PortalHandleRef->bPortalReady)
|
||||||
|
{
|
||||||
|
// Call the subsystem function to get portal status
|
||||||
|
uint8* output = PortalHandleRef->Read();
|
||||||
|
EPortalCommand CommandResponse = GetPortalCommandFromChar(output[0]);
|
||||||
|
//UFigureData figData;
|
||||||
|
switch (CommandResponse)
|
||||||
|
{
|
||||||
|
case S:
|
||||||
|
CurrentStatusData = ParsePortalStatus(output);
|
||||||
|
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("Output Data: %s"), *BytesToHex(output, sizeof(output)));
|
||||||
|
|
||||||
|
//Send delegate when new informations are received
|
||||||
|
if (OldStatusData != CurrentStatusData) {
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < CurrentStatusData.StatusArray.Num(); i++) {
|
||||||
|
if (CurrentStatusData.StatusArray[i] != OldStatusData.StatusArray[i]) {
|
||||||
|
if (
|
||||||
|
//!FalsePositive() //filter conflicting infos
|
||||||
|
true) {
|
||||||
|
int32 figID = 0;
|
||||||
|
UFigureData* figData = NewObject<UFigureData>();
|
||||||
|
//FigureDataBlock FigureData;
|
||||||
|
switch (CurrentStatusData.StatusArray[i])
|
||||||
|
{
|
||||||
|
case EFigureStatus::NOT_PRESENT:
|
||||||
|
case EFigureStatus::PRESENT:
|
||||||
|
break;
|
||||||
|
case EFigureStatus::ADDED:
|
||||||
|
figData = PortalHandleRef->ReadFigureBlocks(i);
|
||||||
|
if (figData)
|
||||||
|
{
|
||||||
|
figID = figData->GetFigureID();
|
||||||
|
}
|
||||||
|
subref->OnSkylanderAdded.Broadcast(figID, i);
|
||||||
|
break;
|
||||||
|
case EFigureStatus::REMOVED:
|
||||||
|
subref->OnSkylanderRemoved.Broadcast(00, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OldStatusData = CurrentStatusData;
|
||||||
|
subref->StatusData = CurrentStatusData;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do something with the status (log, notify, etc.)
|
||||||
|
UE_LOG(LogSkyportalIO, VeryVerbose, TEXT("Portal Status:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,18 +4,21 @@
|
||||||
#include "Engine/Engine.h"
|
#include "Engine/Engine.h"
|
||||||
|
|
||||||
#include "HAL/RunnableThread.h"
|
#include "HAL/RunnableThread.h"
|
||||||
#include "Misc/ScopeLock.h"
|
#include "SkyPortalFigure.h"
|
||||||
#include "SkyPortalIO.h"
|
#include <Async/TaskGraphInterfaces.h>
|
||||||
|
#include <Async/Async.h>
|
||||||
|
|
||||||
|
|
||||||
void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
{
|
{
|
||||||
|
|
||||||
Super::Initialize(Collection);
|
Super::Initialize(Collection);
|
||||||
// Start the status checker thread
|
FigureArray.SetNum(16);
|
||||||
StatusChecker = new FPortalStatusChecker(this, 0.1f); // Check every 5 seconds
|
// Create UFigureData objects dynamically
|
||||||
StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);
|
for (uint8 i = 0; i < FigureArray.Num(); i++)
|
||||||
|
{
|
||||||
|
FigureArray[i] = NewObject<UFigureData>(this);
|
||||||
|
}
|
||||||
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized"));
|
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,8 +38,8 @@ void USkyPortalSubsystem::Deinitialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect portal
|
// Disconnect portal
|
||||||
if (PortalDevice) {
|
if (PortalHandle) {
|
||||||
hid_close(PortalDevice);
|
PortalHandle->Close();
|
||||||
}
|
}
|
||||||
hid_exit();
|
hid_exit();
|
||||||
|
|
||||||
|
@ -45,38 +48,87 @@ void USkyPortalSubsystem::Deinitialize()
|
||||||
Super::Deinitialize();
|
Super::Deinitialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Antenna up / activate
|
|
||||||
void USkyPortalSubsystem::ActivatePortal(int active)
|
|
||||||
{
|
|
||||||
FWriteBlock req, res;
|
|
||||||
|
|
||||||
memset(req.data, 0, write_buf_size);
|
void USkyPortalSubsystem::PortalActivate(const bool bShouldActivate)
|
||||||
req.buf[1] = 'A';
|
|
||||||
req.buf[2] = active;
|
|
||||||
do { Write(&req); } while (CheckResponse(&res, 'A'));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start portal
|
|
||||||
void USkyPortalSubsystem::Ready()
|
|
||||||
{
|
{
|
||||||
FWriteBlock command;
|
FWriteBlock command;
|
||||||
|
|
||||||
//memset(command.data, 0, write_buf_size);
|
memset(command.data, 0, write_buf_size);
|
||||||
command.data[1] = 'R';
|
command.data[1] = 'A';
|
||||||
|
command.data[2] = bShouldActivate;
|
||||||
uint8* output;
|
uint8* output;
|
||||||
|
do {
|
||||||
|
PortalHandle->Write(&command);
|
||||||
|
output = PortalHandle->Read();
|
||||||
|
} while (output[0] != 'A');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void USkyPortalSubsystem::PortalReady()
|
||||||
|
{
|
||||||
|
FWriteBlock command;
|
||||||
|
|
||||||
|
memset(command.data, 0, write_buf_size); //maybe not needed here
|
||||||
|
command.data[1] = 'R';
|
||||||
|
unsigned char* output;
|
||||||
do {
|
do {
|
||||||
PortalHandle->Write(&command);
|
PortalHandle->Write(&command);
|
||||||
output = PortalHandle->Read();
|
output = PortalHandle->Read();
|
||||||
} while (output[0] != 'R');
|
} while (output[0] != 'R');
|
||||||
|
|
||||||
uint16 _PortalId = ((output[1] << 8) + output[2]);
|
unsigned char _PortalId[2] = { output[1],output[2] };
|
||||||
PortalId = _PortalId; //Need a conversion as uint16 is not supported in BP
|
uint16_t BE_PortalId = (_PortalId[0] << 8) | _PortalId[1]; // Big-endian format
|
||||||
|
uint32 BEU_PortalId = (_PortalId[0] << 8) | _PortalId[1]; // Big-endian format unsigned
|
||||||
|
uint16_t LE_PortalId = (_PortalId[1] << 8) | _PortalId[0]; //little endian
|
||||||
|
//uint32 LEU_PortalId = (_PortalId[1] << 8) | _PortalId[0]; //little endian unsigned
|
||||||
|
PortalId = BEU_PortalId; //Need a conversion as uint16 is not supported in BP
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FPortalStatusData USkyPortalSubsystem::PortalStatus() {
|
||||||
|
FWriteBlock command;
|
||||||
|
|
||||||
|
memset(command.data, 0, write_buf_size); //maybe not needed here
|
||||||
|
command.data[1] = 'S';
|
||||||
|
uint8* output;
|
||||||
|
|
||||||
|
do {
|
||||||
|
PortalHandle->Write(&command);
|
||||||
|
output = PortalHandle->Read();
|
||||||
|
} while (output[0] != 'S');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return ParsePortalStatus(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
SKYPORTAL_API bool USkyPortalSubsystem::RestartRunner()
|
||||||
|
{
|
||||||
|
if (StatusChecker)
|
||||||
|
{
|
||||||
|
StatusChecker->Stop();
|
||||||
|
StatusCheckerThread->WaitForCompletion();
|
||||||
|
|
||||||
|
delete StatusCheckerThread;
|
||||||
|
delete StatusChecker;
|
||||||
|
StatusChecker = nullptr;
|
||||||
|
StatusCheckerThread = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusChecker = new FPortalStatusChecker(this, RunnerInterval); // Check every 10 milliseconds
|
||||||
|
StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("SkyPortalSubsystem Initialized"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#pragma region Color functions
|
||||||
|
|
||||||
//{ Region Color & light functions
|
|
||||||
void USkyPortalSubsystem::ChangePortalColor(const FLinearColor& Color)
|
void USkyPortalSubsystem::ChangePortalColor(const FLinearColor& Color)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -84,510 +136,174 @@ void USkyPortalSubsystem::ChangePortalColor(const FLinearColor& Color)
|
||||||
unsigned char g = FMath::Clamp(Color.G * 100, 0.0f, 255.0f);
|
unsigned char g = FMath::Clamp(Color.G * 100, 0.0f, 255.0f);
|
||||||
unsigned char b = FMath::Clamp(Color.B * 100, 0.0f, 255.0f);
|
unsigned char b = FMath::Clamp(Color.B * 100, 0.0f, 255.0f);
|
||||||
|
|
||||||
RWBlock req;
|
FWriteBlock command;
|
||||||
|
|
||||||
memset(req.buf, 0, rw_buf_size);
|
memset(command.data, 0, write_buf_size);
|
||||||
|
|
||||||
req.buf[1] = 'C';
|
command.data[1] = 'C';
|
||||||
req.buf[2] = r; // R
|
command.data[2] = r; // R
|
||||||
req.buf[3] = g; // G
|
command.data[3] = g; // G
|
||||||
req.buf[4] = b; // B
|
command.data[4] = b; // B
|
||||||
|
|
||||||
// no response for this one.
|
// no response for this one.
|
||||||
Write(&req);
|
PortalHandle->Write(&command);
|
||||||
}
|
}
|
||||||
void USkyPortalSubsystem::ChangePortalColorside(const FLinearColor& Color, const EPortalSide PortalSide, const float BlendTime)
|
|
||||||
|
void USkyPortalSubsystem::ChangePortalColorSide(const FLinearColor& Color, const EPortalSide PortalSide, const float BlendTime)
|
||||||
{
|
{
|
||||||
|
|
||||||
unsigned char r = FMath::Clamp(Color.R * 100, 0, 255);
|
unsigned char r = FMath::Clamp(Color.R * 100, 0, 255);
|
||||||
unsigned char g = FMath::Clamp(Color.G * 100, 0, 255);
|
unsigned char g = FMath::Clamp(Color.G * 100, 0, 255);
|
||||||
unsigned char b = FMath::Clamp(Color.B * 100, 0, 255);
|
unsigned char b = FMath::Clamp(Color.B * 100, 0, 255);
|
||||||
EPortalSide _portalside;
|
EPortalSide _portalside;
|
||||||
RWBlock req, res;
|
FWriteBlock command;
|
||||||
|
|
||||||
memset(req.buf, 0, rw_buf_size);
|
memset(command.data, 0, write_buf_size);
|
||||||
|
|
||||||
if (PortalSide == EPortalSide::BOTH) {
|
if (PortalSide == EPortalSide::BOTH) {
|
||||||
_portalside = EPortalSide::LEFT;
|
_portalside = EPortalSide::LEFT;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
_portalside = PortalSide;
|
||||||
|
}
|
||||||
|
|
||||||
switch (_portalside) {
|
switch (_portalside) {
|
||||||
case EPortalSide::LEFT:
|
case EPortalSide::LEFT:
|
||||||
req.buf[1] = 'J';
|
command.data[1] = 'J';
|
||||||
req.buf[2] = 0x00;
|
command.data[2] = 0x00;
|
||||||
case EPortalSide::RIGHT:
|
case EPortalSide::RIGHT:
|
||||||
req.buf[1] = 'J';
|
command.data[1] = 'J';
|
||||||
req.buf[2] = 0x02;
|
command.data[2] = 0x02;
|
||||||
case EPortalSide::TRAP:
|
case EPortalSide::TRAP:
|
||||||
req.buf[1] = 'L';
|
command.data[1] = 'L';
|
||||||
req.buf[2] = 0x01;
|
command.data[2] = 0x01;
|
||||||
req.buf[3] = FMath::Max3(r, g, b); // calculate brightness
|
command.data[3] = FMath::Max3(r, g, b); // calculate brightness
|
||||||
Write(&req); //since it's a color command for trap, only 3 bytes are needed, no response.
|
PortalHandle->Write(&command); //since it's a color command for trap, only 3 bytes are needed, no response.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
req.buf[3] = r; // R
|
command.data[3] = r; // R
|
||||||
req.buf[4] = g; // G
|
command.data[4] = g; // G
|
||||||
req.buf[5] = b; // B
|
command.data[5] = b; // B
|
||||||
|
|
||||||
//Convert the time in millisecond into two bytes
|
//Convert the time in millisecond into two bytes
|
||||||
uint16_t _time = BlendTime;
|
uint16_t _time = BlendTime;
|
||||||
uint8_t _time_low = _time & 0xFF; // Get the low byte by masking the least significant 8 bits
|
uint8_t _time_low = _time & 0xFF; // Get the low byte by masking the least significant 8 bits
|
||||||
uint8_t _time_high = (_time >> 8) & 0xFF; // Get the high byte extracted by shifting the bits 8 positions to the right and masking the result
|
uint8_t _time_high = (_time >> 8) & 0xFF; // Get the high byte extracted by shifting the bits 8 positions to the right and masking the result
|
||||||
req.buf[6] = _time_low;
|
command.data[6] = _time_low;
|
||||||
req.buf[7] = _time_high;
|
command.data[7] = _time_high;
|
||||||
|
|
||||||
do { Write(&req); } while (CheckResponse(&res, 'J'));
|
uint8* output;
|
||||||
|
do {
|
||||||
|
PortalHandle->Write(&command);
|
||||||
|
output = PortalHandle->Read();
|
||||||
|
} while (output[0] != 'J');
|
||||||
|
|
||||||
if (PortalSide == EPortalSide::BOTH) {
|
if (PortalSide == EPortalSide::BOTH) {
|
||||||
ChangePortalColorside(Color, EPortalSide::RIGHT, BlendTime); // send a second command for the right side
|
ChangePortalColorSide(Color, EPortalSide::RIGHT, BlendTime); // send a second command for the right side
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//}
|
#pragma endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FPortalStatusData USkyPortalSubsystem::ParsePortalStatus(const RWBlock& ResponseBlock)
|
|
||||||
{
|
|
||||||
FPortalStatusData PortalStatusData;
|
|
||||||
|
|
||||||
// Parse the figure status array (little-endian 32-bit integer)
|
|
||||||
uint32 FigureStatusArray = 0;
|
|
||||||
// Reading the 32-bit integer (character status array) from the buffer
|
|
||||||
FigureStatusArray |= ResponseBlock.buf[1]; // 1st byte
|
|
||||||
FigureStatusArray |= (ResponseBlock.buf[2] << 8); // 2nd byte
|
|
||||||
FigureStatusArray |= (ResponseBlock.buf[3] << 16); // 3rd byte
|
|
||||||
FigureStatusArray |= (ResponseBlock.buf[4] << 24); // 4th byte
|
|
||||||
|
|
||||||
TStaticArray<EFigureStatus, 16> tempArray;
|
|
||||||
// For each of the 16 entries, extract the 2-bit status and map it to EFigureStatus
|
|
||||||
for (int32 i = 0; i < 16; ++i)
|
|
||||||
{
|
|
||||||
uint8 StatusBits = (FigureStatusArray >> (i * 2)) & 0b11; // Extract 2 bits
|
|
||||||
EFigureStatus FigureStatus;
|
|
||||||
|
|
||||||
switch (StatusBits)
|
|
||||||
{
|
|
||||||
case 0b00:
|
|
||||||
FigureStatus = EFigureStatus::NOT_PRESENT;
|
|
||||||
break;
|
|
||||||
case 0b01:
|
|
||||||
FigureStatus = EFigureStatus::PRESENT;
|
|
||||||
break;
|
|
||||||
case 0b11:
|
|
||||||
FigureStatus = EFigureStatus::ADDED;
|
|
||||||
break;
|
|
||||||
case 0b10:
|
|
||||||
FigureStatus = EFigureStatus::REMOVED;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
FigureStatus = EFigureStatus::NOT_PRESENT; // Default case
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to the array of figure statuses
|
|
||||||
//PortalStatusData.StatusArray.Insert(FigureStatus, i);
|
|
||||||
tempArray[i] = FigureStatus;
|
|
||||||
}
|
|
||||||
PortalStatusData.StatusArray.SetNum(0);
|
|
||||||
PortalStatusData.StatusArray.Append(tempArray);
|
|
||||||
// The next byte is the response counter
|
|
||||||
PortalStatusData.Counter = ResponseBlock.buf[5];
|
|
||||||
|
|
||||||
// The last byte is the boolean indicating whether the portal is ready
|
|
||||||
PortalStatusData.bIsReady = ResponseBlock.buf[6] != 0; // 0 means not ready, non-zero means ready
|
|
||||||
|
|
||||||
return PortalStatusData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void USkyPortalSubsystem::Sleep(int sleepMs) {
|
void USkyPortalSubsystem::Sleep(int sleepMs) {
|
||||||
FPlatformProcess::Sleep(sleepMs * 0.0001);
|
FPlatformProcess::Sleep(sleepMs * 0.0001);
|
||||||
}
|
}
|
||||||
|
|
||||||
void USkyPortalSubsystem::CheckComplexResponse() {
|
|
||||||
|
|
||||||
if (!PortalDevice) {
|
bool USkyPortalSubsystem::PortalConnect()
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RWBlock req, res;
|
|
||||||
|
|
||||||
memset(req.buf, 0, rw_buf_size);
|
|
||||||
req.buf[1] = 'S';
|
|
||||||
Write(&req);
|
|
||||||
|
|
||||||
int BuffResponse = hid_read_timeout(PortalDevice, res.buf, rw_buf_size, TIMEOUT);
|
|
||||||
if (BuffResponse < 0) {
|
|
||||||
UE_LOG(LogSkyportalIO, Error, TEXT("Error.\n %s"), hid_error(PortalDevice));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
EPortalCommand CommandResponse = GetPortalCommandFromChar(res.buf[0]);
|
|
||||||
switch (CommandResponse)
|
|
||||||
{
|
{
|
||||||
case A:
|
if (StatusChecker)
|
||||||
break;
|
|
||||||
case C:
|
|
||||||
break;
|
|
||||||
case J:
|
|
||||||
break;
|
|
||||||
case L:
|
|
||||||
break;
|
|
||||||
case M:
|
|
||||||
break;
|
|
||||||
case Q:
|
|
||||||
break;
|
|
||||||
case R:
|
|
||||||
break;
|
|
||||||
case S:
|
|
||||||
CurrentStatusData = ParsePortalStatus(res);
|
|
||||||
|
|
||||||
//Send delegate when new informations are received
|
|
||||||
if (OldStatusData != CurrentStatusData) {
|
|
||||||
|
|
||||||
for (int i = 0; i < CurrentStatusData.StatusArray.Num(); i++) {
|
|
||||||
if (CurrentStatusData.StatusArray[i] != OldStatusData.StatusArray[i]) {
|
|
||||||
if (
|
|
||||||
//!FalsePositive() //filter conflicting infos
|
|
||||||
true) {
|
|
||||||
FigureDataBlock FigureData;
|
|
||||||
switch (CurrentStatusData.StatusArray[i])
|
|
||||||
{
|
{
|
||||||
case EFigureStatus::NOT_PRESENT:
|
StatusChecker->Stop();
|
||||||
OnSkylanderRemoved.Broadcast(00, i);
|
StatusCheckerThread->WaitForCompletion();
|
||||||
break;
|
|
||||||
case EFigureStatus::PRESENT:
|
delete StatusCheckerThread;
|
||||||
FigureData = ReadFigureBlocks(i);
|
delete StatusChecker;
|
||||||
OnSkylanderAdded.Broadcast(GetFigureID(FigureData), i);
|
StatusChecker = nullptr;
|
||||||
break;
|
StatusCheckerThread = nullptr;
|
||||||
case EFigureStatus::ADDED:
|
|
||||||
FigureData = ReadFigureBlocks(i);
|
|
||||||
OnSkylanderAdded.Broadcast(GetFigureID(FigureData), i);
|
|
||||||
case EFigureStatus::REMOVED:
|
|
||||||
OnSkylanderRemoved.Broadcast(00, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
OldStatusData = CurrentStatusData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
TArray<uint8> USkyPortalSubsystem::QueryBlock(uint8 characterIndex, uint8 block)
|
|
||||||
{
|
|
||||||
TArray<uint8> QueryCommand;
|
|
||||||
QueryCommand.SetNum(0x21);
|
|
||||||
QueryCommand[1] = 'Q';
|
|
||||||
QueryCommand[2] = characterIndex;
|
|
||||||
QueryCommand[3] = block;
|
|
||||||
|
|
||||||
TArray<uint8> Output;
|
|
||||||
|
|
||||||
do {
|
|
||||||
portalConnection->Write(QueryCommand);
|
|
||||||
output = portalConnection->Read();
|
|
||||||
} while (output[0] != 'Q' || (output[1] % 0x10 != characterIndex && output[1] != 0x01) || output[2] != block);
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool USkyPortalSubsystem::FalsePositive() const
|
|
||||||
{
|
|
||||||
int dif = FMath::Abs(CurrentStatusData.Counter - OldStatusData.Counter);
|
|
||||||
return (dif <= 2 || dif >= 254);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Verify the command response, when only a character is sended by the portal.
|
|
||||||
*
|
|
||||||
*TODO: Refacto this function to handle better the response/output from the portal
|
|
||||||
*/
|
|
||||||
bool USkyPortalSubsystem::CheckResponse(RWBlock* res, char expect)
|
|
||||||
{
|
|
||||||
if (!PortalDevice) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int b = hid_read_timeout(PortalDevice, res->buf, rw_buf_size, TIMEOUT);
|
|
||||||
|
|
||||||
if (b < 0) {
|
|
||||||
UE_LOG(LogSkyportalIO, Error, TEXT("Error.\n %s"), hid_error(PortalDevice));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
res->BytesTransferred = b;
|
|
||||||
|
|
||||||
/* this is here to debug the different responses from the portal.
|
|
||||||
#if DEBUG
|
|
||||||
SkylanderIO* skio;
|
|
||||||
skio = new SkylanderIO();
|
|
||||||
printf("<<<\n");
|
|
||||||
skio->fprinthex(stdout, res->buf, 0x21);
|
|
||||||
delete skio;
|
|
||||||
#endif
|
|
||||||
*/
|
|
||||||
|
|
||||||
// found wireless USB but portal is not connected
|
|
||||||
if (res->buf[0] == 'Z')
|
|
||||||
{
|
|
||||||
UE_LOG(LogSkyportalIO, Error, TEXT("found wireless USB but portal is not connected"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status says no skylander on portal
|
|
||||||
if (res->buf[0] == 'Q' && res->buf[1] == 0) {
|
|
||||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Status says no skylander on portal"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res->buf[0] == 'R' && res->buf[1] == 0) {
|
|
||||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Status says no skylander on portal"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (res->buf[0] != expect);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
EPortalCommand USkyPortalSubsystem::GetPortalCommandFromChar(unsigned char Char)
|
|
||||||
{
|
|
||||||
switch (Char)
|
|
||||||
{
|
|
||||||
case 'A':
|
|
||||||
return EPortalCommand::A;
|
|
||||||
case 'C':
|
|
||||||
return EPortalCommand::C;
|
|
||||||
case 'J':
|
|
||||||
return EPortalCommand::J;
|
|
||||||
case 'L':
|
|
||||||
return EPortalCommand::L;
|
|
||||||
case 'M':
|
|
||||||
return EPortalCommand::M;
|
|
||||||
case 'Q':
|
|
||||||
return EPortalCommand::Q;
|
|
||||||
case 'R':
|
|
||||||
return EPortalCommand::R;
|
|
||||||
case 'S':
|
|
||||||
return EPortalCommand::S;
|
|
||||||
default:
|
|
||||||
// Handle the case when the character doesn't match any enum
|
|
||||||
// Return a default or invalid value, or handle the error
|
|
||||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Invalid character for Portal Command: %c"), TCHAR(Char));
|
|
||||||
return EPortalCommand::S; // 'S' for Status as a default
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool USkyPortalSubsystem::ReadBlock(unsigned int block, unsigned char data[0x10], int skylanderIndex) {
|
|
||||||
RWBlock req, res; //request and response buffers
|
|
||||||
unsigned char followup;
|
|
||||||
|
|
||||||
UE_LOG(LogSkyportalIO, Verbose, TEXT("PortalIO:ReadBlock :%X"), block);
|
|
||||||
|
|
||||||
|
|
||||||
// Checking if the block is not out of range
|
|
||||||
if (!ensure(block < 0x40)) {
|
|
||||||
UE_LOG(LogSkyportalIO, Error, TEXT("PortalIO:ReadBlock failed, block out of range"));
|
|
||||||
return false; // Early return instead of throwing an exception
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send query request
|
|
||||||
|
|
||||||
// Trying to read data 15x
|
|
||||||
for (int attempt = 0; attempt < 15; attempt++)
|
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
memset(req.buf, 0, rw_buf_size); // Clear the request buffer (initialize all bytes to zero)
|
|
||||||
req.buf[1] = 'Q';
|
|
||||||
followup = 0x10 + skylanderIndex;
|
|
||||||
req.buf[2] = followup;
|
|
||||||
if (block == 0) {
|
|
||||||
req.buf[2] = followup + 0x10; // may not be needed
|
|
||||||
}
|
|
||||||
req.buf[3] = (unsigned char)block;
|
|
||||||
|
|
||||||
memset(&(res.buf), 0, rw_buf_size); // Clear the response buffer to prepare for incoming data
|
|
||||||
|
|
||||||
|
|
||||||
do { Write(&req); } while (CheckResponse(&res, 'Q'));
|
|
||||||
|
|
||||||
if (res.buf[0] == 'Q' && res.buf[2] == (unsigned char)block) {
|
|
||||||
// Got our query back
|
|
||||||
if (res.buf[1] == followup) {
|
|
||||||
// got the query back with no error
|
|
||||||
memcpy(data, res.buf + 3, 0x10);
|
|
||||||
UE_LOG(LogSkyportalIO, Verbose, TEXT("PortalIO:ReadBlock success"));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // retries
|
|
||||||
|
|
||||||
UE_LOG(LogSkyportalIO, Fatal, TEXT("PortalIO:ReadBlock failed after retries"));
|
|
||||||
ensureMsgf(false, TEXT("PortalIO: Failed to read block after multiple retries"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool USkyPortalSubsystem::ConnectPortal()
|
|
||||||
{
|
|
||||||
PortalHandle = NewObject<USkyPortalIO>();
|
PortalHandle = NewObject<USkyPortalIO>();
|
||||||
if (IsValidLowLevelFast(PortalHandle)) {
|
if (IsValid(PortalHandle) && PortalHandle->bPortalReady) {
|
||||||
UE_LOG(LogSkyportalIO, Log, TEXT("Portal connected: "));
|
UE_LOG(LogSkyportalIO, Log, TEXT("Portal connected: "));
|
||||||
Ready();
|
PortalReady();
|
||||||
ActivatePortal(1);
|
PortalActivate(1);
|
||||||
|
|
||||||
Sleep(500);
|
Sleep(500);
|
||||||
ChangePortalColor(FLinearColor(0.5, 0.5, 0.5));
|
ChangePortalColor(FLinearColor(0.5, 0.5, 0.5));
|
||||||
|
|
||||||
|
// Start the status checker thread
|
||||||
|
StatusChecker = new FPortalStatusChecker(this, RunnerInterval);
|
||||||
|
StatusCheckerThread = FRunnableThread::Create(StatusChecker, TEXT("PortalStatusCheckerThread"), 0, TPri_AboveNormal);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool USkyPortalSubsystem::bIsPortalReady()
|
bool USkyPortalSubsystem::IsPortalReady()
|
||||||
{
|
{
|
||||||
|
if (PortalHandle) {
|
||||||
|
return PortalHandle->bPortalReady;
|
||||||
|
}
|
||||||
|
//maybe should send a status request ?
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void USkyPortalSubsystem::SendPortalCommand(EPortalCommand Command)
|
void USkyPortalSubsystem::PortalMusic(const USoundWave* Sound)
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USkyPortalSubsystem::PortalAnalyzeAsync(const uint8 Index)
|
||||||
|
{
|
||||||
|
bShouldPauseRunner = true;
|
||||||
|
UE_LOG(LogSkyportalIO, Log, TEXT("Starting async figure analysis for index %d..."), Index);
|
||||||
|
|
||||||
|
// Run the PortalAnalyze function asynchronously on a background thread
|
||||||
|
AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, Index]()
|
||||||
|
{
|
||||||
|
UE_LOG(LogSkyportalIO, Verbose, TEXT("Async Task started for figure index %d"), Index);
|
||||||
|
|
||||||
|
// Perform the blocking operation on the background thread
|
||||||
|
PortalAnalyze(Index);
|
||||||
|
|
||||||
|
// Once done, switch back to the main game thread
|
||||||
|
AsyncTask(ENamedThreads::GameThread, [this, Index]()
|
||||||
|
{
|
||||||
|
// This will be executed on the game thread after the async task is complete
|
||||||
|
bShouldPauseRunner = false;
|
||||||
|
UE_LOG(LogSkyportalIO, Log, TEXT("Figure analysis complete for index: %d"), Index);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void USkyPortalSubsystem::PortalAnalyze(const uint8 index)
|
||||||
|
{
|
||||||
|
|
||||||
|
UE_LOG(LogSkyportalIO, Log, TEXT("Reading figure..."));
|
||||||
|
if (FigureArray.IsValidIndex(index) && StatusData.StatusArray[index] == EFigureStatus::PRESENT) {
|
||||||
|
if (IsValid(PortalHandle)) {
|
||||||
|
FigureArray[index] = PortalHandle->ReadFigureBlocks(index);
|
||||||
|
UE_LOG(LogSkyportalIO, Log, TEXT("Figure ID : %d"), FigureArray[index]->GetFigureID());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UE_LOG(LogSkyportalIO, Error, TEXT("PortalHandle unreachable"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UE_LOG(LogSkyportalIO, Error, TEXT("Error in FigureArray"));
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void USkyPortalSubsystem::SendPortalSound(USoundWave* Sound)
|
void USkyPortalSubsystem::GetFigureArray() {
|
||||||
{
|
FigureArray;
|
||||||
|
for (int i = 0; 16; i++) {
|
||||||
|
FigureArray[i] = PortalHandle->ReadFigureBlocks(i);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
FPortalStatusChecker::FPortalStatusChecker(USkyPortalSubsystem* InSubsystem, float InCheckInterval)
|
|
||||||
: SkyPortalSubsystem(InSubsystem), CheckInterval(InCheckInterval), bShouldRun(true)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FPortalStatusChecker::Init()
|
|
||||||
{
|
|
||||||
// Initialization logic, if necessary (e.g., logging)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32 FPortalStatusChecker::Run()
|
|
||||||
{
|
|
||||||
// Main loop of the thread, runs until Stop() is called
|
|
||||||
while (bShouldRun)
|
|
||||||
{
|
|
||||||
// Check the portal status
|
|
||||||
CheckPortalStatus();
|
|
||||||
|
|
||||||
// Sleep for the specified interval
|
|
||||||
FPlatformProcess::Sleep(CheckInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0; // Exit code for the thread
|
|
||||||
}
|
|
||||||
|
|
||||||
void FPortalStatusChecker::Stop()
|
|
||||||
{
|
|
||||||
// Signal the thread to stop running
|
|
||||||
bShouldRun = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FPortalStatusChecker::Exit()
|
|
||||||
{
|
|
||||||
// Cleanup after the thread has stopped
|
|
||||||
}
|
|
||||||
|
|
||||||
void FPortalStatusChecker::CheckPortalStatus()
|
|
||||||
{
|
|
||||||
// Ensure the subsystem is valid
|
|
||||||
if (SkyPortalSubsystem && SkyPortalSubsystem->bPortalConnected)
|
|
||||||
{
|
|
||||||
UE_LOG(LogSkyportalIO, Verbose, TEXT("Check portal"));
|
|
||||||
|
|
||||||
// Call the subsystem function to get portal status
|
|
||||||
|
|
||||||
SkyPortalSubsystem->CheckComplexResponse();
|
|
||||||
|
|
||||||
// Do something with the status (log, notify, etc.)
|
|
||||||
UE_LOG(LogSkyportalIO, Verbose, TEXT("Portal Status:"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FigureDataBlock USkyPortalSubsystem::ReadFigureBlocks(uint8 FigureIndex)
|
|
||||||
{
|
|
||||||
FigureDataBlock FigureData;
|
|
||||||
FigureData.error = false; // Initialize error flag
|
|
||||||
|
|
||||||
RWBlock req, res;
|
|
||||||
|
|
||||||
// Loop over all 64 blocks
|
|
||||||
for (uint8 BlockIndex = 0; BlockIndex < FIGURE_TOTAL_BLOCKS; ++BlockIndex)
|
|
||||||
{
|
|
||||||
// Prepare the request buffer
|
|
||||||
memset(req.buf, 0, rw_buf_size);
|
|
||||||
req.buf[1] = 'Q'; // Command character
|
|
||||||
req.buf[2] = FigureIndex; // Figure index (0x00 to 0x0F)
|
|
||||||
req.buf[3] = BlockIndex; // Block index (0x00 to 0x3F)
|
|
||||||
|
|
||||||
// Send the request
|
|
||||||
Write(&req);
|
|
||||||
|
|
||||||
// Read the response
|
|
||||||
int BuffResponse = hid_read_timeout(PortalDevice, res.buf, rw_buf_size, TIMEOUT);
|
|
||||||
|
|
||||||
// Check if the response was received successfully
|
|
||||||
if (BuffResponse < rw_buf_size)
|
|
||||||
{
|
|
||||||
FigureData.error = true; // Mark error flag
|
|
||||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Error receiving data for block %d of figure %d"), BlockIndex, FigureIndex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the response status
|
|
||||||
uint8 StatusByte = res.buf[2];
|
|
||||||
uint8 ReturnedBlockIndex = res.buf[3];
|
|
||||||
|
|
||||||
// Check for errors in the status byte
|
|
||||||
if (StatusByte == 0x01) // Error in response
|
|
||||||
{
|
|
||||||
FigureData.error = true;
|
|
||||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Error reading block %d for figure %d"), BlockIndex, FigureIndex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (StatusByte != (0x10 + 'Q')) // Unexpected status byte
|
|
||||||
{
|
|
||||||
FigureData.error = true;
|
|
||||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Unexpected status byte 0x%02X for block %d"), StatusByte, BlockIndex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the block index matches the requested block
|
|
||||||
if (ReturnedBlockIndex != BlockIndex)
|
|
||||||
{
|
|
||||||
FigureData.error = true;
|
|
||||||
UE_LOG(LogSkyportalIO, Warning, TEXT("Mismatched block index. Expected %d, got %d"), BlockIndex, ReturnedBlockIndex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy block data into FigureData
|
|
||||||
memcpy(FigureData.blockdata[BlockIndex], &res.buf[4], FIGURE_BLOCK_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return FigureData; // Return the complete figure data
|
|
||||||
}
|
}
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Modules/ModuleManager.h"
|
#include "Modules/ModuleManager.h"
|
||||||
#include "SkyPortalSubsystem.h"
|
|
||||||
|
|
||||||
class FSkyPortalModule : public IModuleInterface
|
class FSkyPortalModule : public IModuleInterface
|
||||||
{
|
{
|
||||||
|
|
83
Source/SkyPortal/Public/SkyPortalDefinitions.h
Normal file
83
Source/SkyPortal/Public/SkyPortalDefinitions.h
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "SkyPortalDefinitions.generated.h"
|
||||||
|
|
||||||
|
// This file is not supposed to have a c++ counterpart. Use it as a library
|
||||||
|
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EFigureStatus : uint8
|
||||||
|
{
|
||||||
|
NOT_PRESENT = 0b00 UMETA(DisplayName = "Not Present"),
|
||||||
|
PRESENT = 0b01 UMETA(DisplayName = "Present"),
|
||||||
|
ADDED = 0b11 UMETA(DisplayName = "Added"),
|
||||||
|
REMOVED = 0b10 UMETA(DisplayName = "Removed")
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum EPortalSide {
|
||||||
|
LEFT UMETA(DisplayName = "Left side"),
|
||||||
|
RIGHT UMETA(DisplayName = "Right side"),
|
||||||
|
BOTH UMETA(DisplayName = "Both left and right"),
|
||||||
|
TRAP UMETA(DisplayName = "Trap")
|
||||||
|
};
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum EPortalCommand {
|
||||||
|
A UMETA(DisplayName = "Activate"),
|
||||||
|
C UMETA(DisplayName = "Color"),
|
||||||
|
J UMETA(DisplayName = "Advanced color"),
|
||||||
|
L UMETA(DisplayName = "Trap color"),
|
||||||
|
M UMETA(DisplayName = "Music"),
|
||||||
|
Q UMETA(DisplayName = "Query"),
|
||||||
|
R UMETA(DisplayName = "Ready"),
|
||||||
|
S UMETA(DisplayName = "Status")
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FPortalStatusData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
// Array of statuses
|
||||||
|
UPROPERTY(BlueprintReadOnly, EditFixedSize, Category = "SkyPortal|Figure", meta = (EditFixedOrder))
|
||||||
|
TArray<EFigureStatus> StatusArray;
|
||||||
|
|
||||||
|
// timestamp.
|
||||||
|
// only one byte long. This means that after the value 0xFF, it overflows back to 0x00.
|
||||||
|
// Since these are so far apart, it can be assumed that 0x00 is newer than anything in the range 0xF0 - 0xFF.
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
|
||||||
|
uint8 Counter;
|
||||||
|
|
||||||
|
// Should always be true
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
|
||||||
|
bool bIsReady;
|
||||||
|
|
||||||
|
explicit FPortalStatusData(uint8 ArraySize = 16, EFigureStatus DefaultStatus = EFigureStatus::NOT_PRESENT) :
|
||||||
|
Counter(0),
|
||||||
|
bIsReady(true)
|
||||||
|
{
|
||||||
|
// Initialisation du tableau StatusArray avec 16 éléments par défaut
|
||||||
|
StatusArray.Init(DefaultStatus, ArraySize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload the == operator
|
||||||
|
// Different only between
|
||||||
|
bool operator==(const FPortalStatusData& Other) const
|
||||||
|
{
|
||||||
|
if (bIsReady == Other.bIsReady && Counter == Other.Counter) {
|
||||||
|
if (StatusArray == Other.StatusArray) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload the != operator
|
||||||
|
bool operator!=(const FPortalStatusData& Other) const
|
||||||
|
{
|
||||||
|
return !(*this == Other);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,36 +1,55 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
//#include "SkyPortalFigure.generated.h"
|
#include "SkyPortalFigure.generated.h"
|
||||||
|
|
||||||
|
//Copyright (C) 2010 Activision.All Rights Reserved.
|
||||||
UENUM(BlueprintType)
|
static const uint8 HASH_CONST[] = {
|
||||||
enum class EFigureStatus : uint8
|
0x20, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x43, 0x29, 0x20, 0x32, // Copyright (C) 2
|
||||||
{
|
0x30, 0x31, 0x30, 0x20, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6F, 0x6E, 0x2E, 0x20, // 010 Activision.
|
||||||
NOT_PRESENT = 0b00 UMETA(DisplayName = "Not Present"),
|
0x41, 0x6C, 0x6C, 0x20, 0x52, 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, // All Rights Reser
|
||||||
PRESENT = 0b01 UMETA(DisplayName = "Present"),
|
0x76, 0x65, 0x64, 0x2E, 0x20 }; // ved.
|
||||||
ADDED = 0b11 UMETA(DisplayName = "Added"),
|
|
||||||
REMOVED = 0b10 UMETA(DisplayName = "Removed")
|
|
||||||
};
|
|
||||||
|
|
||||||
#define FIGURE_TOTAL_BLOCKS 64
|
#define FIGURE_TOTAL_BLOCKS 64
|
||||||
#define FIGURE_BLOCK_SIZE 16
|
#define FIGURE_BLOCK_SIZE 16
|
||||||
|
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum EVariantIDGeneration {
|
||||||
|
UNKNOW = -1,
|
||||||
|
SPYROS_ADVENTURE = 0,
|
||||||
|
GIANTS = 1,
|
||||||
|
SWAP_FORCE = 2,
|
||||||
|
TRAP_TEAM = 3,
|
||||||
|
SUPERCHARGERS = 4,
|
||||||
|
IMAGINATORS = 5
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned char blockdata[FIGURE_TOTAL_BLOCKS][FIGURE_BLOCK_SIZE]; bool error;
|
unsigned char blockdata[FIGURE_TOTAL_BLOCKS][FIGURE_BLOCK_SIZE]; bool error;
|
||||||
} FigureDataBlock;
|
} FigureDataBlock;
|
||||||
|
*/
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|Figure"))
|
UCLASS(BlueprintType,Blueprintable)
|
||||||
uint32 GetFigureID(const FigureDataBlock& DataBlock);
|
class UFigureData : public UObject {
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|Figure"))
|
|
||||||
uint32 GetFigureIdByIndex(uint32 index);
|
|
||||||
|
|
||||||
class FigureData {
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
UFigureData();
|
||||||
|
|
||||||
// Data Arrays
|
// Data Arrays
|
||||||
uint8 data[64][16];
|
uint8 data[64][16];
|
||||||
uint8 decryptedData[64][16];
|
uint8 decryptedData[64][16];
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
bool bDecrypted;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
bool dataError;
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
#pragma region manufacturer
|
#pragma region manufacturer
|
||||||
uint32 NUID; // should under no circumstances be corrupted. 4-byte
|
uint32 NUID; // should under no circumstances be corrupted. 4-byte
|
||||||
|
@ -44,25 +63,36 @@ public:
|
||||||
uint64 FullToyCodeNumber;
|
uint64 FullToyCodeNumber;
|
||||||
FString ToyCode;
|
FString ToyCode;
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
int16 ID;
|
int16 ID; //Skylander ID
|
||||||
int16 VariantID;
|
int16 VariantID; //Skylander Variant ID
|
||||||
|
EVariantIDGeneration GenerationID;
|
||||||
#pragma region counters
|
#pragma region counters
|
||||||
uint8 counter1;
|
uint8 counter1;
|
||||||
uint8 couter2;
|
uint8 counter2;
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
FString Nickname;
|
FString Nickname;
|
||||||
|
|
||||||
FigureData()
|
|
||||||
{
|
|
||||||
ClearData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
|
UFUNCTION(BlueprintCallable,BlueprintPure, meta = (Category = "SkyPortal|Figure"))
|
||||||
|
int32 GetFigureID();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,BlueprintPure, meta = (Category = "SkyPortal|Figure"))
|
||||||
|
int32 GetVariantID();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|Figure"))
|
||||||
|
EVariantIDGeneration GetGeneration();
|
||||||
|
|
||||||
void ReadData(uint8 index);
|
void ReadData(uint8 index);
|
||||||
void ClearData();
|
void ClearData();
|
||||||
uint8 GetByte(int block, int offset);
|
uint8 GetByte(int block, int offset);
|
||||||
void SetByte(int block, int offset, uint8 value);
|
void SetByte(int block, int offset, uint8 value);
|
||||||
uint16 GetShort(int block, int offset);
|
uint16 GetShort(int block, int offset);
|
||||||
uint32 GetUInt(int block, int offset);
|
uint32 GetUInt(int block, int offset);
|
||||||
void Dump(bool decrypted, FString filePath);
|
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
bool Dump(bool decrypted, FString filePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,22 +4,32 @@
|
||||||
*/
|
*/
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "hidapi.h"
|
#include "hidapi.h"
|
||||||
|
#include "SkyPortalDefinitions.h"
|
||||||
|
#include "SkyPortalFigure.h"
|
||||||
#include "SkyPortalIO.generated.h"
|
#include "SkyPortalIO.generated.h"
|
||||||
|
|
||||||
|
#pragma region Definitions
|
||||||
|
|
||||||
DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All);
|
DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All);
|
||||||
DECLARE_LOG_CATEGORY_EXTERN(LogSkyportalIO, Log, All);
|
DECLARE_LOG_CATEGORY_EXTERN(LogSkyportalIO, Log, All);
|
||||||
|
|
||||||
constexpr auto write_buf_size = 0x21;
|
constexpr auto write_buf_size = 0x21; //buffer size for all the write command on portal. Should always be 0x21 - 33
|
||||||
constexpr auto TIMEOUT = 30000; //milliseconds
|
constexpr auto TIMEOUT = 30000; //milliseconds
|
||||||
|
|
||||||
/* Portal physicial device IDs */
|
/* Portal physicial device IDs */
|
||||||
const int VendorIds[4] = { 0x12ba, 0x54c, 0x1430, 0x1430 };
|
const int VendorIds[4] = { 0x12ba, 0x54c, 0x1430, 0x1430 };
|
||||||
const int ProductIds[4] = { 0x150, 0x967, 0x1f17 };
|
const int ProductIds[4] = { 0x150, 0x967, 0x1f17 };
|
||||||
|
|
||||||
/* WriteBlock
|
#pragma endregion
|
||||||
|
|
||||||
|
|
||||||
|
FPortalStatusData ParsePortalStatus(const uint8* StatusResponse);
|
||||||
|
EPortalCommand GetPortalCommandFromChar(unsigned char Char);
|
||||||
|
FString DataToString(const uint8* data);
|
||||||
|
FString OutputToString(const unsigned char* output);
|
||||||
|
|
||||||
|
/* Contain all the data that pass inside hidapi write + the number of bytes in case of successful write
|
||||||
*
|
*
|
||||||
* Contain all the data that pass inside hidapi write + the number of bytes in case of successful write
|
|
||||||
* @param data all the block data, should be 0x21 sized
|
* @param data all the block data, should be 0x21 sized
|
||||||
* @param BytesTransferred In case of successful write, print the number of byte transfered. Could be used for verifications
|
* @param BytesTransferred In case of successful write, print the number of byte transfered. Could be used for verifications
|
||||||
*/
|
*/
|
||||||
|
@ -39,32 +49,50 @@ class USkyPortalIO : public UObject
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// Constructor
|
||||||
|
USkyPortalIO();
|
||||||
|
|
||||||
/* hidapi will write error message in here*/
|
/* hidapi will write error message in here*/
|
||||||
UPROPERTY(BlueprintReadOnly)
|
UPROPERTY(BlueprintReadOnly)
|
||||||
FString HidError;
|
FString HidError;
|
||||||
|
|
||||||
/*Portal ready to be listened or to receive commands*/
|
/*Portal ready to be listened or to receive commands*/
|
||||||
UPROPERTY(BlueprintReadOnly)
|
UPROPERTY(BlueprintReadOnly)
|
||||||
bool bPortalReady;
|
bool bPortalReady = false;
|
||||||
|
|
||||||
/* Connect to Portal - will write the PortalDevice var if success */
|
/* Connect to Portal - will write the PortalDevice var if success */
|
||||||
bool OpenPortalHandle();
|
bool OpenPortalHandle();
|
||||||
|
|
||||||
void Write(FWriteBlock* Block);
|
/* Send block to portal. In windows, don't use the hidapi */
|
||||||
void WriteRaw(FWriteBlock* Block);
|
bool Write(FWriteBlock* Block);
|
||||||
|
|
||||||
|
/* Send raw command to portal */
|
||||||
|
void WriteRaw(const TArray<uint8>* block);
|
||||||
|
|
||||||
/* Listen to portal
|
/* Listen to portal
|
||||||
* @return The data sended by the portal device
|
* @return The data sended by the portal device
|
||||||
*/
|
*/
|
||||||
UFUNCTION()
|
|
||||||
uint8* Read();
|
uint8* Read();
|
||||||
|
|
||||||
|
|
||||||
|
/*Trying to read a figure
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
unsigned char* QueryBlock(uint8 characterIndex, uint8 block = 0x00);
|
||||||
|
|
||||||
|
UFigureData* ReadFigureBlocks(uint8 FigureIndex);
|
||||||
|
|
||||||
|
/* Close connection to Portal*/
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
/*TODO: Should not be here
|
||||||
|
bool IsFalsePositive() const;
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
/* Portal ref used in the subsystem */
|
/* Portal device ref, useful for hidapi commands */
|
||||||
hid_device* PortalDevice;
|
hid_device* PortalDevice;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,63 +1,18 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "SkyPortalDefinitions.h"
|
||||||
#include "HAL/Runnable.h"
|
#include "HAL/Runnable.h"
|
||||||
#include "SkyPortalFigure.h"
|
|
||||||
|
|
||||||
class SkyPortalRunner
|
|
||||||
{
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
USTRUCT(BlueprintType)
|
|
||||||
struct FPortalStatusData
|
|
||||||
{
|
|
||||||
GENERATED_USTRUCT_BODY()
|
|
||||||
|
|
||||||
// Array of statuses
|
|
||||||
UPROPERTY(BlueprintReadOnly, EditFixedSize, Category = "SkyPortal|Figure", meta = (EditFixedOrder))
|
|
||||||
TArray<EFigureStatus> StatusArray;
|
|
||||||
|
|
||||||
// timestamp.
|
|
||||||
// only one byte long. This means that after the value 0xFF, it overflows back to 0x00.
|
|
||||||
// Since these are so far apart, it can be assumed that 0x00 is newer than anything in the range 0xF0 - 0xFF.
|
|
||||||
UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
|
|
||||||
uint8 Counter;
|
|
||||||
|
|
||||||
// Should always be true
|
|
||||||
UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
|
|
||||||
bool bIsReady;
|
|
||||||
|
|
||||||
explicit FPortalStatusData(uint8 ArraySize = 16, EFigureStatus DefaultStatus = EFigureStatus::NOT_PRESENT) :
|
|
||||||
Counter(0), // Utilisation correcte de l'initialisation directe
|
|
||||||
bIsReady(true) // Utilisation correcte de l'initialisation directe
|
|
||||||
{
|
|
||||||
// Initialisation du tableau StatusArray avec 16 éléments par défaut
|
|
||||||
StatusArray.Init(DefaultStatus, ArraySize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overload the == operator
|
|
||||||
bool operator==(const FPortalStatusData& Other) const
|
|
||||||
{
|
|
||||||
if (bIsReady == Other.bIsReady && Counter == Other.Counter) {
|
|
||||||
if (StatusArray == Other.StatusArray) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overload the != operator
|
|
||||||
bool operator!=(const FPortalStatusData& Other) const
|
|
||||||
{
|
|
||||||
return !(*this == Other);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class FPortalStatusChecker : public FRunnable {
|
class FPortalStatusChecker : public FRunnable {
|
||||||
public:
|
public:
|
||||||
// Constructor: pass the subsystem and desired check interval (in seconds)
|
/* Constructor : pass the subsystem and desired check interval(in seconds)
|
||||||
FPortalStatusChecker(USkyPortalSubsystem* InSubsystem, float InCheckInterval);
|
*
|
||||||
|
* @param InSubsystem Should always be the SkyPortalSubsystem
|
||||||
|
*/
|
||||||
|
FPortalStatusChecker(UEngineSubsystem* InSubsystem, float InCheckInterval);
|
||||||
|
|
||||||
// FRunnable interface
|
// FRunnable interface
|
||||||
virtual bool Init() override;
|
virtual bool Init() override;
|
||||||
|
@ -65,9 +20,15 @@ public:
|
||||||
virtual void Stop() override;
|
virtual void Stop() override;
|
||||||
virtual void Exit() override;
|
virtual void Exit() override;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FPortalStatusData CurrentStatusData;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FPortalStatusData OldStatusData;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Pointer to the subsystem that contains PortalStatus()
|
// Pointer to the subsystem that contains PortalStatus()
|
||||||
USkyPortalSubsystem* SkyPortalSubsystem;
|
UEngineSubsystem* SkyPortalSubsystemRef;
|
||||||
|
|
||||||
// Time interval (in seconds) for status checking
|
// Time interval (in seconds) for status checking
|
||||||
float CheckInterval;
|
float CheckInterval;
|
||||||
|
|
|
@ -1,53 +1,27 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Subsystems/EngineSubsystem.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "SkyPortalFigure.h"
|
||||||
|
#include "SkyPortalIO.h"
|
||||||
#include "SkyPortalRunner.h"
|
#include "SkyPortalRunner.h"
|
||||||
|
#include "Subsystems/EngineSubsystem.h"
|
||||||
|
#include "Delegates/DelegateCombinations.h"
|
||||||
|
#include <Containers/Array.h>
|
||||||
|
|
||||||
#include "SkyPortalSubsystem.generated.h"
|
#include "SkyPortalSubsystem.generated.h"
|
||||||
|
|
||||||
|
|
||||||
#pragma region Definitions
|
#pragma region Definitions
|
||||||
|
|
||||||
UENUM(BlueprintType)
|
|
||||||
enum EPortalSide {
|
|
||||||
LEFT UMETA(DisplayName = "Left side"),
|
|
||||||
RIGHT UMETA(DisplayName = "Right side"),
|
|
||||||
BOTH UMETA(DisplayName = "Both left and right"),
|
|
||||||
TRAP UMETA(DisplayName = "Trap")
|
|
||||||
};
|
|
||||||
|
|
||||||
UENUM(BlueprintType)
|
|
||||||
enum EPortalCommand {
|
|
||||||
A UMETA(DisplayName = "Activate"),
|
|
||||||
C UMETA(DisplayName = "Color"),
|
|
||||||
J UMETA(DisplayName = "Advanced color"),
|
|
||||||
L UMETA(DisplayName = "Trap color"),
|
|
||||||
M UMETA(DisplayName = "Music"),
|
|
||||||
Q UMETA(DisplayName = "Query"),
|
|
||||||
R UMETA(DisplayName = "Ready"),
|
|
||||||
S UMETA(DisplayName = "Status")
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//// Delegates
|
//// Delegates
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderAddedDelegate, int32, SkylanderID, int32, Index);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderAddedDelegate, int32, SkylanderID, int32, Index);
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderRemovedDelegate, int32, SkylanderID, int32, Index);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderRemovedDelegate, int32, SkylanderID, int32, Index);
|
||||||
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handle all the blueprints functions and game logic.
|
* Handle all the blueprints functions and game logic.
|
||||||
*
|
*
|
||||||
|
@ -58,48 +32,78 @@ class USkyPortalSubsystem : public UEngineSubsystem
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Override initialization and deinitialization methods
|
|
||||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
virtual void Deinitialize() override;
|
virtual void Deinitialize() override;
|
||||||
|
|
||||||
/********* Portal Actions *************/
|
/***********************************************************/
|
||||||
|
/********************* Portal Actions *********************/
|
||||||
|
/***********************************************************/
|
||||||
|
|
||||||
/* The first function to run, before anything else.
|
/* The first function to run, before anything else.
|
||||||
* It will re-init and prepare the portal for receiving/sending inputs
|
* It will re-init and prepare the portal for receiving/sending inputs
|
||||||
*
|
*
|
||||||
* return false if portal is not found
|
* @return false if portal is not found
|
||||||
*/
|
*/
|
||||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal"))
|
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal"))
|
||||||
SKYPORTAL_API bool ConnectPortal();
|
SKYPORTAL_API bool PortalConnect();
|
||||||
|
|
||||||
|
|
||||||
|
/* Make sure that the connection to the portal works and that the device that is connected is actually a portal.
|
||||||
|
*
|
||||||
|
* In case of success, will update the PortalId, wich is different for each portal model.
|
||||||
|
*/
|
||||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal|Commands"))
|
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal|Commands"))
|
||||||
SKYPORTAL_API void Ready();
|
SKYPORTAL_API void PortalReady();
|
||||||
|
|
||||||
|
/* Activate all the main functions of the portal, like figure detection or wireless signal.
|
||||||
|
* This command should be used before any other commands with the exception of the Ready command
|
||||||
|
*
|
||||||
|
* When the portal is activated, it will periodically send out Status responses even when no Status request has been sent. If the portal has not been activated, or if it has been deactivated again, Status responses will still be sent when requested.
|
||||||
|
* If a Deactivate request is received by a portal, all LEDs will turn off. They can be turned on again by their respective commands.
|
||||||
|
*
|
||||||
|
* @param bShouldActivate Optional : If false, send a Deactivate command.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal|Commands"))
|
||||||
|
SKYPORTAL_API void PortalActivate(const bool bShouldActivate = true);
|
||||||
|
|
||||||
|
|
||||||
|
/* Change portal color, ideally should be called just at the start. For gameplay usage, use ChangePortalColorSide()
|
||||||
|
*
|
||||||
|
* @param NextColor New color
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "NextColor", Category = "SkyPortal|Commands|Cosmetic", HideAlphaChannel))
|
||||||
|
SKYPORTAL_API void ChangePortalColor(const FLinearColor& NextColor = FLinearColor::Green);
|
||||||
|
|
||||||
/*Send a **Status** command, to see if the portal is ready to receive new commands*/
|
/*Send a **Status** command, to see if the portal is ready to receive new commands*/
|
||||||
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
|
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal"))
|
||||||
SKYPORTAL_API bool bIsPortalReady();
|
SKYPORTAL_API bool IsPortalReady();
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
|
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
|
||||||
SKYPORTAL_API void SendPortalCommand(EPortalCommand Command);
|
SKYPORTAL_API void PortalMusic(const USoundWave* Sound);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
|
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|Figure|Debug"))
|
||||||
SKYPORTAL_API void SendPortalSound(USoundWave* Sound);
|
SKYPORTAL_API void PortalAnalyzeAsync(const uint8 index);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|Commands|Figure"))
|
||||||
|
SKYPORTAL_API void GetFigureArray();
|
||||||
|
|
||||||
|
/**/
|
||||||
|
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal|Commands"))
|
||||||
|
SKYPORTAL_API FPortalStatusData PortalStatus();
|
||||||
|
|
||||||
|
/**/
|
||||||
|
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal"))
|
||||||
|
SKYPORTAL_API bool RestartRunner();
|
||||||
|
|
||||||
|
|
||||||
|
/* Change the color of the portal, can separate side and even trap ligth
|
||||||
/* Change portal color, ideally should be called just at the start.For gameplay usage, use ChangePortalColorside()*/
|
|
||||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "Color", Category = "SkyPortal|Cosmetic"))
|
|
||||||
SKYPORTAL_API void ChangePortalColor(const FLinearColor& Color = FLinearColor::Green);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the color of the portal, can separate side and even trap ligth
|
|
||||||
* @param NextColor New color
|
* @param NextColor New color
|
||||||
* @param PortalSide The actors to record
|
* @param PortalSide The actors to record
|
||||||
* @param BlendTime Blend between current color and NextColor, in milliseconds
|
* @param BlendTime Blend between current color and NextColor, in milliseconds
|
||||||
*/
|
*/
|
||||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "NextColor", Category = "SkyPortal|Cosmetic", HideAlphaChannel))
|
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "NextColor", Category = "SkyPortal|Commands|Cosmetic", HideAlphaChannel))
|
||||||
SKYPORTAL_API void ChangePortalColorside(const FLinearColor& NextColor = FLinearColor::Green, const EPortalSide PortalSide = EPortalSide::BOTH, const float BlendTime = 500.0f);
|
SKYPORTAL_API void ChangePortalColorSide(const FLinearColor& NextColor = FLinearColor::Green, const EPortalSide PortalSide = EPortalSide::BOTH, const float BlendTime = 500.0f);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Blueprint-assignable event property
|
// Blueprint-assignable event property
|
||||||
|
@ -111,47 +115,48 @@ public:
|
||||||
FOnSkylanderRemovedDelegate OnSkylanderRemoved;
|
FOnSkylanderRemovedDelegate OnSkylanderRemoved;
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly)
|
UPROPERTY(BlueprintReadOnly)
|
||||||
FPortalStatusData CurrentStatusData;
|
FPortalStatusData StatusData;
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly)
|
UPROPERTY(BlueprintReadOnly)
|
||||||
FPortalStatusData OldStatusData;
|
TArray<UFigureData*> FigureArray;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadWrite)
|
||||||
|
float RunnerInterval = 0.01f; //In seconds
|
||||||
|
|
||||||
|
/* Different for each portal model.
|
||||||
|
*
|
||||||
|
* SSA :
|
||||||
|
* GIANTS :
|
||||||
|
* SWAP FORCE : 0000547 ? , 515
|
||||||
|
* TRAP TEAM :
|
||||||
|
* SUPERCHARGERS :
|
||||||
|
* IMAGINATORS :
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
int PortalId;
|
||||||
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly)
|
UPROPERTY(BlueprintReadOnly)
|
||||||
uint32 PortalId;
|
|
||||||
|
|
||||||
EPortalCommand GetPortalCommandFromChar(unsigned char Char);
|
|
||||||
|
|
||||||
|
|
||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
|
||||||
bool bPortalConnected = false;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
UFUNCTION()
|
|
||||||
TArray<uint8> QueryBlock(uint8 characterIndex, uint8 block = 0x00);
|
|
||||||
|
|
||||||
USkyPortalIO* PortalHandle;
|
USkyPortalIO* PortalHandle;
|
||||||
|
|
||||||
|
/**/
|
||||||
|
UPROPERTY()
|
||||||
|
bool bPortalConnected_DEPRECATED = PortalHandle ? PortalHandle->bPortalReady : false;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
bool bShouldPauseRunner;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FPortalStatusData ParsePortalStatus(const WriteBlock& ResponseBlock);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Sleep the system
|
||||||
|
*
|
||||||
|
* @param sleepMs Sleep time (in milliseconds)
|
||||||
|
*/
|
||||||
static void Sleep(int sleepMs);
|
static void Sleep(int sleepMs);
|
||||||
|
|
||||||
|
void PortalAnalyze(const uint8 Index);
|
||||||
|
|
||||||
void ActivatePortal(int active);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Block/byte related data write/read functions
|
|
||||||
|
|
||||||
|
|
||||||
FigureDataBlock ReadFigureBlocks(uint8 FigureIndex);
|
|
||||||
bool FalsePositive() const;
|
|
||||||
|
|
||||||
// Pointer to the status checker thread
|
// Pointer to the status checker thread
|
||||||
FPortalStatusChecker* StatusChecker;
|
FPortalStatusChecker* StatusChecker;
|
||||||
|
|
|
@ -62,8 +62,9 @@ public class SkyPortal : ModuleRules
|
||||||
{
|
{
|
||||||
"Core",
|
"Core",
|
||||||
"CoreUObject",
|
"CoreUObject",
|
||||||
"Engine"
|
"Engine",
|
||||||
// ... add other public dependencies that you statically link with here ...
|
"OpenSSL",
|
||||||
|
"SSL"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -75,7 +76,7 @@ public class SkyPortal : ModuleRules
|
||||||
"Engine",
|
"Engine",
|
||||||
"Slate",
|
"Slate",
|
||||||
"SlateCore",
|
"SlateCore",
|
||||||
// ... add private dependencies that you statically link with here ...
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue