wip
Based on https://github.com/mandar1jn/SkylandersToolkit/blob/master
This commit is contained in:
parent
aed30481ea
commit
be72994dc0
7 changed files with 302 additions and 34 deletions
|
@ -16,5 +16,5 @@ void FSkyPortalModule::ShutdownModule()
|
|||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
|
||||
IMPLEMENT_MODULE(FSkyPortalModule, SkyPortal)
|
81
Source/SkyPortal/Private/SkyPortalFigure.cpp
Normal file
81
Source/SkyPortal/Private/SkyPortalFigure.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
#pragma once
|
||||
|
||||
#include "SkyPortalFigure.h"
|
||||
#include "SkyPortalSubsystem.h"
|
||||
|
||||
uint32 GetFigureID(const FigureDataBlock& DataBlock)
|
||||
{
|
||||
int16_t OutFigureID;
|
||||
// Figure ID is stored in Block 0, bytes 0 to 3 (32-bit integer, little-endian)
|
||||
OutFigureID = DataBlock.blockdata[0][0] |
|
||||
(DataBlock.blockdata[0][1] << 8) |
|
||||
(DataBlock.blockdata[0][2] << 16) |
|
||||
(DataBlock.blockdata[0][3] << 24);
|
||||
/*
|
||||
// Variant ID is stored in Block 0, bytes 4 to 5 (16-bit integer, little-endian)
|
||||
OutVariantID = (DataBlock.blockdata[0][5] << 8) |
|
||||
DataBlock.blockdata[0][4];
|
||||
*/
|
||||
return OutFigureID;
|
||||
};
|
||||
|
||||
|
||||
void FigureData::ReadData(uint8 index)
|
||||
{
|
||||
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];
|
||||
Array.Copy(output, 3, blockData, 0, 16);
|
||||
|
||||
// block 1 is sometimes a duplicate of block 0
|
||||
if (i == 1)
|
||||
{
|
||||
if (blockData.SequenceEqual(data[0]))
|
||||
{
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Array.Copy(blockData, 0, data[i], 0, 16);
|
||||
|
||||
if (((i + 1) % 4 == 0) || i < 8)
|
||||
{
|
||||
Array.Copy(data[i], 0, decryptedData[i], 0, 16);
|
||||
}
|
||||
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 FigureData::ClearData()
|
||||
{
|
||||
}
|
69
Source/SkyPortal/Private/SkyPortalIO.cpp
Normal file
69
Source/SkyPortal/Private/SkyPortalIO.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#include "SkyPortalIO.h"
|
||||
#include "Engine/Engine.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogHIDApi);
|
||||
DEFINE_LOG_CATEGORY(LogSkyportalIO);
|
||||
|
||||
bool OpenPortalHandle() {
|
||||
//reset
|
||||
if (PortalDevice) {
|
||||
hid_close(PortalDevice);
|
||||
}
|
||||
/*
|
||||
Declare two pointers to hold information about HID devices.
|
||||
"list" will point to the head of the linked list of devices,
|
||||
"attributes" will be used to iterate through the list.
|
||||
*/
|
||||
struct hid_device_info* list, * attributes;
|
||||
|
||||
list = hid_enumerate(0x0, 0x0);
|
||||
// If `list` is NULL, that means no devices were found or there was an error.
|
||||
// In this case, print an error message and terminate the program.
|
||||
if (!list) {
|
||||
UE_LOG(LogHIDApi, Error, TEXT("No devices found"));
|
||||
// Get the error message from the HIDAPI
|
||||
HidError = hid_error(NULL);
|
||||
UE_LOG(LogHIDApi, Error, TEXT("%s"), *HidError);
|
||||
return false;
|
||||
}
|
||||
attributes = list;
|
||||
|
||||
|
||||
|
||||
|
||||
// Iterate through the linked list of devices
|
||||
int vendorCount = sizeof(VendorIds) / sizeof(VendorIds[0]);
|
||||
int productCount = sizeof(ProductIds) / sizeof(ProductIds[0]);
|
||||
|
||||
while (attributes) {
|
||||
// Check if the devices match any of vendor_id and product_id
|
||||
for (int i = 0; i < vendorCount; i++) {
|
||||
for (int j = 0; j < productCount; j++) {
|
||||
if (attributes->vendor_id == VendorIds[i] && attributes->product_id == ProductIds[j]) {
|
||||
UE_LOG(LogHIDApi, Display, TEXT("Portal found"));
|
||||
UE_LOG(LogHIDApi, Log, TEXT("Vendor ID: 0x%x, Product ID: 0x%x"), attributes->vendor_id, attributes->product_id);
|
||||
|
||||
PortalDevice = hid_open(attributes->vendor_id, attributes->product_id, NULL);
|
||||
if (PortalDevice) {
|
||||
UE_LOG(LogHIDApi, Display, TEXT("Successful connection to Portal."));
|
||||
|
||||
// Free the device list
|
||||
hid_free_enumeration(list);
|
||||
//bPortalConnected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the next device in the list
|
||||
attributes = attributes->next;
|
||||
}
|
||||
|
||||
// Free the device list
|
||||
hid_free_enumeration(list);
|
||||
UE_LOG(LogHIDApi, Error, TEXT("No Portals found"));
|
||||
return false;
|
||||
}
|
|
@ -5,9 +5,8 @@
|
|||
|
||||
#include "HAL/RunnableThread.h"
|
||||
#include "Misc/ScopeLock.h"
|
||||
#include "SkyPortalIO.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogHIDApi);
|
||||
DEFINE_LOG_CATEGORY(LogSkyportalIO);
|
||||
|
||||
void USkyPortalSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
|
@ -289,6 +288,7 @@ FPortalStatusData USkyPortalSubsystem::ParsePortalStatus(const RWBlock& Response
|
|||
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)
|
||||
{
|
||||
|
@ -315,9 +315,11 @@ FPortalStatusData USkyPortalSubsystem::ParsePortalStatus(const RWBlock& Response
|
|||
}
|
||||
|
||||
// Add to the array of figure statuses
|
||||
PortalStatusData.StatusArray.Add(FigureStatus);
|
||||
//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];
|
||||
|
||||
|
@ -370,22 +372,27 @@ void USkyPortalSubsystem::CheckComplexResponse() {
|
|||
|
||||
//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()) {
|
||||
if (
|
||||
//!FalsePositive() //filter conflicting infos
|
||||
true) {
|
||||
FigureDataBlock FigureData;
|
||||
switch (CurrentStatusData.StatusArray[i])
|
||||
{
|
||||
case EFigureStatus::NOT_PRESENT:
|
||||
break;
|
||||
case EFigureStatus::PRESENT:
|
||||
break;
|
||||
case EFigureStatus::ADDED:
|
||||
//get SkylandID
|
||||
//ReadBlock;
|
||||
OnSkylanderAdded.Broadcast(01, i);
|
||||
case EFigureStatus::REMOVED:
|
||||
OnSkylanderRemoved.Broadcast(01, i)
|
||||
case EFigureStatus::NOT_PRESENT:
|
||||
OnSkylanderRemoved.Broadcast(00, i);
|
||||
break;
|
||||
case EFigureStatus::PRESENT:
|
||||
FigureData = ReadFigureBlocks(i);
|
||||
OnSkylanderAdded.Broadcast(GetFigureID(FigureData), i);
|
||||
break;
|
||||
case EFigureStatus::ADDED:
|
||||
FigureData = ReadFigureBlocks(i);
|
||||
OnSkylanderAdded.Broadcast(GetFigureID(FigureData), i);
|
||||
case EFigureStatus::REMOVED:
|
||||
OnSkylanderRemoved.Broadcast(00, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -394,7 +401,7 @@ void USkyPortalSubsystem::CheckComplexResponse() {
|
|||
|
||||
OldStatusData = CurrentStatusData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
break;
|
||||
default:
|
||||
|
@ -403,10 +410,30 @@ void USkyPortalSubsystem::CheckComplexResponse() {
|
|||
|
||||
}
|
||||
|
||||
bool USkyPortalSubsystem::FalsePositive()
|
||||
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);
|
||||
return (dif <= 2 || dif >= 254);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,60 @@
|
|||
#pragma once
|
||||
#include "CoreMinimal.h"
|
||||
//#include "SkyPortalFigure.generated.h"
|
||||
|
||||
#include "SkyPortalFigure.generated.h"
|
||||
|
||||
|
||||
#define FIGURE_TOTAL_BLOCKS 64
|
||||
#define FIGURE_BLOCK_SIZE 16
|
||||
|
||||
typedef struct {
|
||||
unsigned char blockdata[FIGURE_TOTAL_BLOCKS][FIGURE_BLOCK_SIZE]; bool error;
|
||||
} FigureDataBlock;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|Figure"))
|
||||
uint32 GetFigureID(const FigureDataBlock& DataBlock);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|Figure"))
|
||||
uint32 GetFigureIdByIndex(uint32 index);
|
||||
|
||||
class FigureData {
|
||||
public:
|
||||
// Data Arrays
|
||||
uint8 data[64][16];
|
||||
uint8 decryptedData[64][16];
|
||||
|
||||
// Properties
|
||||
#pragma region manufacturer
|
||||
uint32 NUID; // should under no circumstances be corrupted. 4-byte
|
||||
uint8 BCC; //Block Check Character. When this BCC does not match the NUID of the tag, the tag ceases to function. stored in block 0 at offset 0x4
|
||||
uint8 SAK = 0x81; //needs to be set to allow for the tag to be read correctly
|
||||
uint16 ATQA; //always set to 0x01 0x0F
|
||||
FString ProductionYear; //last 2 digits of the year that the tag was manufactured as Binary-coded decimal
|
||||
#pragma endregion
|
||||
int16 ID;
|
||||
#pragma region Toy code
|
||||
uint32 ToyCodeNumber1, ToyCodeNumber2;
|
||||
uint64 FullToyCodeNumber;
|
||||
FString ToyCode;
|
||||
#pragma endregion
|
||||
int16 VariantID;
|
||||
#pragma region counters
|
||||
uint8 counter1;
|
||||
uint8 couter2;
|
||||
#pragma endregion
|
||||
FString Nickname;
|
||||
|
||||
FigureData()
|
||||
{
|
||||
ClearData();
|
||||
}
|
||||
|
||||
// Methods
|
||||
void ReadData(uint8 index);
|
||||
void ClearData();
|
||||
uint8 GetByte(int block, int offset);
|
||||
void SetByte(int block, int offset, uint8 value);
|
||||
uint16 GetShort(int block, int offset);
|
||||
uint32 GetUInt(int block, int offset);
|
||||
void Dump(bool decrypted, FString filePath);
|
||||
};
|
33
Source/SkyPortal/Public/SkyPortalIO.h
Normal file
33
Source/SkyPortal/Public/SkyPortalIO.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
/*
|
||||
* This is the bridge between hidapi and unreal and contains all the rawdata of the portal
|
||||
*/
|
||||
#include "CoreMinimal.h"
|
||||
#include "hidapi.h"
|
||||
|
||||
#include "SkyPortalIO.generated.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All);
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogSkyportalIO, Log, All);
|
||||
|
||||
UCLASS(MinimalAPI)
|
||||
class SkyPortalIO : public UObjectBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
SkyPortalIO()
|
||||
{
|
||||
OpenPortalHandle();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool OpenPortalHandle();
|
||||
|
||||
//Portal ref used in the subsystem
|
||||
hid_device* PortalDevice;
|
||||
|
||||
|
||||
};
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/EngineSubsystem.h"
|
||||
#include "hidapi.h"
|
||||
#include "HAL/Runnable.h"
|
||||
|
@ -46,7 +45,7 @@ struct FPortalStatusData
|
|||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
// Array of statuses
|
||||
UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
|
||||
UPROPERTY(BlueprintReadOnly, EditFixedSize, Category = "SkyPortal|Figure", meta = (EditFixedOrder))
|
||||
TArray<EFigureStatus> StatusArray;
|
||||
|
||||
// timestamp.
|
||||
|
@ -59,6 +58,14 @@ struct FPortalStatusData
|
|||
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
|
||||
{
|
||||
|
@ -81,19 +88,12 @@ struct FPortalStatusData
|
|||
#define rw_buf_size 0x21
|
||||
#define TIMEOUT 30000
|
||||
#define DEBUG true
|
||||
#define FIGURE_TOTAL_BLOCKS 64
|
||||
#define FIGURE_BLOCK_SIZE 16
|
||||
|
||||
typedef struct {
|
||||
unsigned char buf[rw_buf_size]; int BytesTransferred;
|
||||
} RWBlock;
|
||||
|
||||
typedef struct {
|
||||
unsigned char blockdata[FIGURE_TOTAL_BLOCKS][FIGURE_BLOCK_SIZE]; bool error;
|
||||
} FigureDataBlock;
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All);
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogSkyportalIO, Log, All);
|
||||
|
||||
//// Delegates
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderAddedDelegate, int32, SkylanderID, int32, Index);
|
||||
|
@ -172,7 +172,7 @@ public:
|
|||
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
|
||||
SKYPORTAL_API void SendPortalSound(USoundWave* Sound);
|
||||
|
||||
|
||||
|
||||
|
||||
/* 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"))
|
||||
|
@ -184,7 +184,7 @@ public:
|
|||
* @param PortalSide The actors to record
|
||||
* @param BlendTime Blend between current color and NextColor, in milliseconds
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "NextColor", Category = "SkyPortal|Cosmetic"))
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "NextColor", Category = "SkyPortal|Cosmetic", HideAlphaChannel))
|
||||
SKYPORTAL_API void ChangePortalColorside(const FLinearColor& NextColor = FLinearColor::Green, const EPortalSide PortalSide = EPortalSide::BOTH, const float BlendTime = 500.0f);
|
||||
|
||||
|
||||
|
@ -214,8 +214,9 @@ public:
|
|||
bool WriteBlock(unsigned int, unsigned char[0x10], int);
|
||||
bool CheckResponse(RWBlock*, char);
|
||||
void CheckComplexResponse();
|
||||
|
||||
|
||||
UFUNCTION()
|
||||
TArray<uint8> QueryBlock(uint8 characterIndex, uint8 block = 0x00);
|
||||
|
||||
|
||||
private:
|
||||
|
@ -237,7 +238,7 @@ private:
|
|||
bool OpenPortalHandle();
|
||||
void Write(RWBlock*);
|
||||
FigureDataBlock ReadFigureBlocks(uint8 FigureIndex);
|
||||
bool FalsePositive();
|
||||
bool FalsePositive() const;
|
||||
|
||||
// Pointer to the status checker thread
|
||||
FPortalStatusChecker* StatusChecker;
|
||||
|
|
Loading…
Reference in a new issue