diff --git a/Source/SkyPortal/Private/SkyPortal.cpp b/Source/SkyPortal/Private/SkyPortal.cpp index e540ab4..03542a6 100644 --- a/Source/SkyPortal/Private/SkyPortal.cpp +++ b/Source/SkyPortal/Private/SkyPortal.cpp @@ -16,5 +16,5 @@ void FSkyPortalModule::ShutdownModule() } #undef LOCTEXT_NAMESPACE - + IMPLEMENT_MODULE(FSkyPortalModule, SkyPortal) \ No newline at end of file diff --git a/Source/SkyPortal/Private/SkyPortalFigure.cpp b/Source/SkyPortal/Private/SkyPortalFigure.cpp new file mode 100644 index 0000000..3da3c04 --- /dev/null +++ b/Source/SkyPortal/Private/SkyPortalFigure.cpp @@ -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(); + TArray 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() +{ +} diff --git a/Source/SkyPortal/Private/SkyPortalIO.cpp b/Source/SkyPortal/Private/SkyPortalIO.cpp new file mode 100644 index 0000000..3ab0643 --- /dev/null +++ b/Source/SkyPortal/Private/SkyPortalIO.cpp @@ -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; +} diff --git a/Source/SkyPortal/Private/SkyPortalSubsystem.cpp b/Source/SkyPortal/Private/SkyPortalSubsystem.cpp index ca80fd3..7a1819c 100644 --- a/Source/SkyPortal/Private/SkyPortalSubsystem.cpp +++ b/Source/SkyPortal/Private/SkyPortalSubsystem.cpp @@ -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 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 USkyPortalSubsystem::QueryBlock(uint8 characterIndex, uint8 block) +{ + TArray QueryCommand; + QueryCommand.SetNum(0x21); + QueryCommand[1] = 'Q'; + QueryCommand[2] = characterIndex; + QueryCommand[3] = block; + + TArray 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); } diff --git a/Source/SkyPortal/Public/SkyPortalFigure.h b/Source/SkyPortal/Public/SkyPortalFigure.h index 4c690bc..07da0e2 100644 --- a/Source/SkyPortal/Public/SkyPortalFigure.h +++ b/Source/SkyPortal/Public/SkyPortalFigure.h @@ -1,3 +1,60 @@ #pragma once +#include "CoreMinimal.h" +//#include "SkyPortalFigure.generated.h" -#include "SkyPortalFigure.generated.h" \ No newline at end of file + + +#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); +}; \ No newline at end of file diff --git a/Source/SkyPortal/Public/SkyPortalIO.h b/Source/SkyPortal/Public/SkyPortalIO.h new file mode 100644 index 0000000..5936ea2 --- /dev/null +++ b/Source/SkyPortal/Public/SkyPortalIO.h @@ -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; + + +}; + diff --git a/Source/SkyPortal/Public/SkyPortalSubsystem.h b/Source/SkyPortal/Public/SkyPortalSubsystem.h index bae6e45..40ef96d 100644 --- a/Source/SkyPortal/Public/SkyPortalSubsystem.h +++ b/Source/SkyPortal/Public/SkyPortalSubsystem.h @@ -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 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 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;