1
1
#include " Immutable/Windows/ImmutablePKCEWindows.h"
2
2
3
+ #include " HAL/Runnable.h"
4
+ #include " HAL/RunnableThread.h"
3
5
#include " Interfaces/IPluginManager.h"
4
6
5
7
#include " Windows/AllowWindowsPlatformTypes.h"
@@ -14,12 +16,260 @@ THIRD_PARTY_INCLUDES_END
14
16
// Registry constants
15
17
static const TCHAR* DeepLinkRegistryValueName = TEXT(" DeepLink" );
16
18
19
+ // Static storage for registry watchers by protocol
20
+ static TMap<FString, TUniquePtr<FRegistryWatcherRunnable>> GRunnableMap;
21
+
22
+ // Helper function to get Windows error message from error code
23
+ static FString GetWindowsErrorMessage (DWORD ErrorCode)
24
+ {
25
+ LPWSTR MessageBuffer = nullptr ;
26
+ DWORD Size = FormatMessageW
27
+ (
28
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
29
+ nullptr , ErrorCode, 0 , (LPWSTR)&MessageBuffer, 0 , nullptr
30
+ );
31
+
32
+ FString ErrorMessage;
33
+
34
+ if (MessageBuffer)
35
+ {
36
+ ErrorMessage = FString (Size, MessageBuffer).TrimEnd ();
37
+ LocalFree (MessageBuffer);
38
+ }
39
+ else
40
+ {
41
+ ErrorMessage = FString::Printf (TEXT (" Unknown error code: %d" ), ErrorCode);
42
+ }
43
+
44
+ return ErrorMessage;
45
+ }
46
+
47
+ // Helper function to safely clean up a registry watcher thread
48
+ static void CleanupThreadForProtocol (const FString& Protocol)
49
+ {
50
+ if (GRunnableMap.Contains (Protocol))
51
+ {
52
+ // Remove from map - unique ptr will handle deletion and stopping the thread
53
+ GRunnableMap.Remove (Protocol);
54
+ }
55
+ }
56
+
57
+ FRegistryWatcherRunnable::FRegistryWatcherRunnable (const FString& InRegistryKeyPath, TWeakObjectPtr<UImmutablePKCEData> InPKCEData):
58
+ RegistryKeyPath(InRegistryKeyPath),
59
+ PKCEData(InPKCEData)
60
+ {
61
+ // Create Windows event objects for notification and termination
62
+ EventHandle = CreateEvent (NULL , false , false , NULL );
63
+ TerminateEventHandle = CreateEvent (NULL , true , false , NULL );
64
+ }
65
+
66
+ FRegistryWatcherRunnable::~FRegistryWatcherRunnable ()
67
+ {
68
+ // Stop the thread if running
69
+ FRegistryWatcherRunnable::Stop ();
70
+
71
+ // Wait for thread to complete if it exists
72
+ if (Thread)
73
+ {
74
+ Thread->WaitForCompletion ();
75
+ Thread.Reset ();
76
+ }
77
+
78
+ // Close any open registry key
79
+ if (DeepLinkKeyHandle)
80
+ {
81
+ RegCloseKey (DeepLinkKeyHandle);
82
+ DeepLinkKeyHandle = nullptr ;
83
+ }
84
+
85
+ // Close event handles
86
+ if (EventHandle)
87
+ {
88
+ CloseHandle (EventHandle);
89
+ EventHandle = nullptr ;
90
+ }
91
+
92
+ if (TerminateEventHandle)
93
+ {
94
+ CloseHandle (TerminateEventHandle);
95
+ TerminateEventHandle = nullptr ;
96
+ }
97
+ }
98
+
99
+ bool FRegistryWatcherRunnable::Init ()
100
+ {
101
+ // Validate event handles were created successfully
102
+ if (!EventHandle || !TerminateEventHandle)
103
+ {
104
+ IMTBL_ERR (" Failed to create event handles for registry watcher" );
105
+ return false ;
106
+ }
107
+
108
+ return true ;
109
+ }
110
+
111
+ bool FRegistryWatcherRunnable::StartThread (const FString& ProtocolName)
112
+ {
113
+ if (Thread)
114
+ {
115
+ IMTBL_WARN (" Thread already started for registry watcher" );
116
+ return true ;
117
+ }
118
+
119
+ // Create the thread that will run this runnable
120
+ Thread = TUniquePtr<FRunnableThread>
121
+ (
122
+ FRunnableThread::Create
123
+ (
124
+ this ,
125
+ *FString::Printf (TEXT (" RegistryWatcher_%s" ), *ProtocolName),
126
+ 0 , // Use the current thread's stack size
127
+ TPri_Normal
128
+ )
129
+ );
130
+
131
+ if (!Thread)
132
+ {
133
+ IMTBL_ERR (" Failed to create registry watcher thread for protocol: %s" , *ProtocolName);
134
+ return false ;
135
+ }
136
+
137
+ return true ;
138
+ }
139
+
140
+ uint32 FRegistryWatcherRunnable::Run ()
141
+ {
142
+ bool bKeyOpened = false ;
143
+
144
+ HANDLE WaitHandles[2 ] = {EventHandle, TerminateEventHandle};
145
+
146
+ // Lambda to close registry key and reset flag
147
+ auto CloseRegistryKey = [&]()
148
+ {
149
+ if (DeepLinkKeyHandle)
150
+ {
151
+ RegCloseKey (DeepLinkKeyHandle);
152
+ DeepLinkKeyHandle = nullptr ;
153
+ }
154
+ bKeyOpened = false ;
155
+ };
156
+
157
+ while (bShouldRun)
158
+ {
159
+ // Open the registry key if not already open
160
+ if (!bKeyOpened)
161
+ {
162
+ // Try to open existing key first
163
+ LONG OpenResult = RegOpenKeyExW (HKEY_CURRENT_USER, *RegistryKeyPath, 0 , KEY_READ | KEY_WRITE | KEY_NOTIFY, &DeepLinkKeyHandle);
164
+ if (OpenResult != ERROR_SUCCESS)
165
+ {
166
+ // Create the key if it doesn't exist
167
+ LONG CreateResult = RegCreateKeyExW (HKEY_CURRENT_USER, *RegistryKeyPath, 0 , nullptr , 0 , KEY_READ | KEY_WRITE | KEY_NOTIFY, nullptr , &DeepLinkKeyHandle, nullptr );
168
+ if (CreateResult != ERROR_SUCCESS)
169
+ {
170
+ // Failed to open or create the key, log error and retry after delay
171
+ IMTBL_ERR (" Failed to open or create registry key: %s (Error: %d - %s)" , *RegistryKeyPath, CreateResult, *GetWindowsErrorMessage (CreateResult));
172
+ FPlatformProcess::Sleep (1 .0f );
173
+ continue ;
174
+ }
175
+ }
176
+ bKeyOpened = true ;
177
+
178
+ // Set up registry change notification
179
+ LONG NotifyResult = RegNotifyChangeKeyValue (DeepLinkKeyHandle, false , REG_NOTIFY_CHANGE_LAST_SET, EventHandle, true );
180
+ if (NotifyResult != ERROR_SUCCESS)
181
+ {
182
+ CloseRegistryKey ();
183
+ FPlatformProcess::Sleep (1 .0f );
184
+ continue ;
185
+ }
186
+ }
187
+
188
+ // Wait for the registry change event or termination event
189
+ DWORD WaitResult = WaitForMultipleObjects (2 , WaitHandles, false , INFINITE);
190
+
191
+ if (WaitResult == WAIT_OBJECT_0) // Registry change event
192
+ {
193
+ // Event was signaled, check the registry for deep link
194
+ if (DeepLinkKeyHandle && !PKCEData.IsStale () && bShouldRun)
195
+ {
196
+ // Use Game Thread to process the deeplink
197
+ FFunctionGraphTask::CreateAndDispatchWhenReady ([this ]()
198
+ {
199
+ if (!PKCEData.IsStale ())
200
+ {
201
+ UImmutablePKCEWindows::CheckDeepLinkRegistry (PKCEData.Get (), DeepLinkKeyHandle);
202
+ }
203
+ }, TStatId (), nullptr , ENamedThreads::GameThread);
204
+ }
205
+
206
+ // Reset notification for the next change
207
+ if (bKeyOpened && bShouldRun)
208
+ {
209
+ LONG notifyResult = RegNotifyChangeKeyValue (DeepLinkKeyHandle, false , REG_NOTIFY_CHANGE_LAST_SET, EventHandle, true );
210
+ if (notifyResult != ERROR_SUCCESS)
211
+ {
212
+ CloseRegistryKey ();
213
+ }
214
+ }
215
+ }
216
+ else if (WaitResult == WAIT_OBJECT_0 + 1 ) // Termination event
217
+ {
218
+ IMTBL_LOG (" Registry watcher received termination signal" );
219
+ break ;
220
+ }
221
+ else if (WaitResult == WAIT_FAILED)
222
+ {
223
+ IMTBL_ERR (" WaitForMultipleObjects failed with error: %d - %s" , GetLastError (), *GetWindowsErrorMessage (GetLastError ()));
224
+ if (bKeyOpened)
225
+ {
226
+ CloseRegistryKey ();
227
+ }
228
+ FPlatformProcess::Sleep (0 .5f );
229
+ }
230
+ else
231
+ {
232
+ // Any other result is unexpected
233
+ IMTBL_WARN (" Unexpected result from WaitForMultipleObjects: %d" , WaitResult);
234
+ if (bKeyOpened)
235
+ {
236
+ CloseRegistryKey ();
237
+ }
238
+ FPlatformProcess::Sleep (0 .5f );
239
+ }
240
+ }
241
+
242
+ return 0 ;
243
+ }
244
+
245
+ void FRegistryWatcherRunnable::Stop ()
246
+ {
247
+ bShouldRun = false ;
248
+
249
+ // Signal termination event to wake up the thread
250
+ if (TerminateEventHandle)
251
+ {
252
+ SetEvent (TerminateEventHandle);
253
+ }
254
+ }
255
+
256
+ void FRegistryWatcherRunnable::Exit ()
257
+ {
258
+ // Clean up when thread exits
259
+ if (DeepLinkKeyHandle)
260
+ {
261
+ RegCloseKey (DeepLinkKeyHandle);
262
+ DeepLinkKeyHandle = nullptr ;
263
+ }
264
+ }
265
+
17
266
UImmutablePKCEData* UImmutablePKCEWindows::Initialise (const FImmutablePassportInitData& PassportInitData)
18
267
{
19
268
// Extract the protocol scheme from the redirect URI
20
269
FString Protocol;
21
270
if (!ExtractProtocolFromUri (PassportInitData.redirectUri , Protocol))
22
271
{
272
+ IMTBL_ERR (" Failed to extract protocol from redirect URI: %s" , *PassportInitData.redirectUri );
23
273
return nullptr ;
24
274
}
25
275
@@ -35,29 +285,49 @@ UImmutablePKCEData* UImmutablePKCEWindows::Initialise(const FImmutablePassportIn
35
285
IMTBL_LOG (" Registered protocol handler for %s" , *Protocol);
36
286
37
287
UImmutablePKCEData* PKCEData = NewObject<UImmutablePKCEData>();
38
-
39
288
PKCEData->PassportInitData = PassportInitData;
40
289
41
- // Setup periodic check for deep links
42
- if (GEngine)
290
+ // Setup registry watcher thread for deep links
291
+ FString RegistryKeyPath = GetDeepLinkRegistryKeyPath (Protocol);
292
+
293
+ // Clean up any existing thread for this protocol
294
+ CleanupThreadForProtocol (Protocol);
295
+
296
+ // Create new runnable and start its thread
297
+ TWeakObjectPtr<UImmutablePKCEData> WeakPKCEData (PKCEData);
298
+ TUniquePtr<FRegistryWatcherRunnable> Runnable = MakeUnique<FRegistryWatcherRunnable>(RegistryKeyPath, WeakPKCEData);
299
+
300
+ if (!Runnable->Init ())
43
301
{
44
- FTickerDelegate TickDelegate = FTickerDelegate::CreateWeakLambda (PKCEData, [PKCEData](float DeltaTime)
45
- {
46
- return UImmutablePKCEWindows::CheckDeepLinkTick (PKCEData, DeltaTime);
47
- });
48
- PKCEData->TickDelegateHandle = FTSTicker::GetCoreTicker ().AddTicker (TickDelegate, 1 .0f );
302
+ IMTBL_ERR (" Failed to initialize registry watcher for protocol: %s" , *Protocol);
303
+ return PKCEData;
304
+ }
305
+
306
+ if (!Runnable->StartThread (Protocol))
307
+ {
308
+ IMTBL_ERR (" Failed to start registry watcher thread for protocol: %s" , *Protocol);
309
+ return PKCEData;
49
310
}
50
311
312
+ // Store the runnable in the map
313
+ GRunnableMap.Add (Protocol, MoveTemp (Runnable));
314
+
51
315
return PKCEData;
52
316
}
53
317
54
318
bool UImmutablePKCEWindows::RegisterProtocolHandler (const FString& Protocol)
55
319
{
320
+ if (Protocol.IsEmpty ())
321
+ {
322
+ IMTBL_ERR (" Cannot register protocol handler: Protocol is empty" );
323
+ return false ;
324
+ }
325
+
56
326
// Create a PowerShell script that will handle incoming protocol activations
57
327
FString PowerShellFilePath = GetPowerShellScriptPath (Protocol);
58
328
FString LogFilePath = GetLogFilePath (Protocol);
59
329
60
- auto Cleanup = [&](bool bDeletePowerShellScript, bool bDeleteRegistryKey)
330
+ auto CleanupResources = [&](bool bDeletePowerShellScript, bool bDeleteRegistryKey)
61
331
{
62
332
if (bDeletePowerShellScript)
63
333
{
@@ -90,10 +360,11 @@ bool UImmutablePKCEWindows::RegisterProtocolHandler(const FString& Protocol)
90
360
// Create main protocol handler registry key
91
361
{
92
362
HKEY KeyHandle;
93
- if (RegCreateKeyExW (HKEY_CURRENT_USER, *KeyPath, 0 , nullptr , 0 , KEY_WRITE, nullptr , &KeyHandle, nullptr ) != ERROR_SUCCESS)
363
+ LONG Result = RegCreateKeyExW (HKEY_CURRENT_USER, *KeyPath, 0 , nullptr , 0 , KEY_WRITE, nullptr , &KeyHandle, nullptr );
364
+ if (Result != ERROR_SUCCESS)
94
365
{
95
- IMTBL_ERR (" Failed to create registry key for protocol handler" );
96
- Cleanup (true , false );
366
+ IMTBL_ERR (" Failed to create registry key for protocol handler (Error: %d - %s) " , Result, * GetWindowsErrorMessage (Result) );
367
+ CleanupResources (true , false );
97
368
return false ;
98
369
}
99
370
@@ -108,7 +379,7 @@ bool UImmutablePKCEWindows::RegisterProtocolHandler(const FString& Protocol)
108
379
if (!bSuccess)
109
380
{
110
381
IMTBL_ERR (" Failed to set registry values for protocol handler" );
111
- Cleanup (true , true );
382
+ CleanupResources (true , true );
112
383
return false ;
113
384
}
114
385
}
@@ -117,22 +388,24 @@ bool UImmutablePKCEWindows::RegisterProtocolHandler(const FString& Protocol)
117
388
{
118
389
FString ShellKeyPath = FString::Printf (TEXT (" %s\\ shell" ), *KeyPath);
119
390
HKEY ShellKeyHandle;
120
- if (RegCreateKeyExW (HKEY_CURRENT_USER, *ShellKeyPath, 0 , nullptr , 0 , KEY_WRITE, nullptr , &ShellKeyHandle, nullptr ) != ERROR_SUCCESS)
391
+ LONG Result = RegCreateKeyExW (HKEY_CURRENT_USER, *ShellKeyPath, 0 , nullptr , 0 , KEY_WRITE, nullptr , &ShellKeyHandle, nullptr );
392
+ if (Result != ERROR_SUCCESS)
121
393
{
122
- IMTBL_ERR (" Failed to create shell registry key" );
123
- Cleanup (true , true );
394
+ IMTBL_ERR (" Failed to create shell registry key (Error: %d - %s) " , Result, * GetWindowsErrorMessage (Result) );
395
+ CleanupResources (true , true );
124
396
return false ;
125
397
}
126
398
127
399
FString EmptyValue = TEXT (" " ); // Arbitrary value so that it will use the root for the deep link prompt
128
- bool bSuccess = RegSetValueExW (ShellKeyHandle, nullptr , 0 , REG_SZ, (const BYTE*)*EmptyValue, (EmptyValue.Len () + 1 ) * sizeof (TCHAR)) == ERROR_SUCCESS;
400
+ LONG SetResult = RegSetValueExW (ShellKeyHandle, nullptr , 0 , REG_SZ, (const BYTE*)*EmptyValue, (EmptyValue.Len () + 1 ) * sizeof (TCHAR));
401
+ bool bSuccess = SetResult == ERROR_SUCCESS;
129
402
130
403
RegCloseKey (ShellKeyHandle);
131
404
132
405
if (!bSuccess)
133
406
{
134
- IMTBL_ERR (" Failed to set shell registry value" );
135
- Cleanup (true , true );
407
+ IMTBL_ERR (" Failed to set shell registry value (Error: %d - %s) " , SetResult, * GetWindowsErrorMessage (SetResult) );
408
+ CleanupResources (true , true );
136
409
return false ;
137
410
}
138
411
}
@@ -141,91 +414,87 @@ bool UImmutablePKCEWindows::RegisterProtocolHandler(const FString& Protocol)
141
414
{
142
415
FString CommandKeyPath = FString::Printf (TEXT (" %s\\ shell\\ open\\ command" ), *KeyPath);
143
416
HKEY CommandKeyHandle;
144
- if (RegCreateKeyExW (HKEY_CURRENT_USER, *CommandKeyPath, 0 , nullptr , 0 , KEY_WRITE, nullptr , &CommandKeyHandle, nullptr ) != ERROR_SUCCESS)
417
+ LONG Result = RegCreateKeyExW (HKEY_CURRENT_USER, *CommandKeyPath, 0 , nullptr , 0 , KEY_WRITE, nullptr , &CommandKeyHandle, nullptr );
418
+ if (Result != ERROR_SUCCESS)
145
419
{
146
- IMTBL_ERR (" Failed to create command registry key" );
147
- Cleanup (true , true );
420
+ IMTBL_ERR (" Failed to create command registry key (Error: %d - %s) " , Result, * GetWindowsErrorMessage (Result) );
421
+ CleanupResources (true , true );
148
422
return false ;
149
423
}
150
424
151
- bool bSuccess = RegSetValueExW (CommandKeyHandle, nullptr , 0 , REG_SZ, (const BYTE*)*CommandLine, (CommandLine.Len () + 1 ) * sizeof (TCHAR)) == ERROR_SUCCESS;
425
+ LONG SetResult = RegSetValueExW (CommandKeyHandle, nullptr , 0 , REG_SZ, (const BYTE*)*CommandLine, (CommandLine.Len () + 1 ) * sizeof (TCHAR));
426
+ bool bSuccess = SetResult == ERROR_SUCCESS;
152
427
153
428
RegCloseKey (CommandKeyHandle);
154
429
155
430
if (!bSuccess)
156
431
{
157
- IMTBL_ERR (" Failed to set command registry value" );
158
- Cleanup (true , true );
432
+ IMTBL_ERR (" Failed to set command registry value (Error: %d - %s) " , SetResult, * GetWindowsErrorMessage (SetResult) );
433
+ CleanupResources (true , true );
159
434
return false ;
160
435
}
161
436
}
162
437
163
438
return true ;
164
439
}
165
440
166
- bool UImmutablePKCEWindows::CheckDeepLinkTick (UImmutablePKCEData* PKCEData, float DeltaTime )
441
+ bool UImmutablePKCEWindows::CheckDeepLinkRegistry (UImmutablePKCEData* PKCEData, HKEY DeepLinkKeyHandle )
167
442
{
168
- // Extract protocol from the redirect URI
169
- FString Protocol;
170
- if (!ExtractProtocolFromUri (PKCEData->PassportInitData .redirectUri , Protocol))
443
+ if (!PKCEData || !DeepLinkKeyHandle)
171
444
{
172
- return true ; // Continue ticking even if we can't extract the protocol
445
+ return false ;
173
446
}
174
447
175
- FString RegistryKeyPath = GetDeepLinkRegistryKeyPath (Protocol);
448
+ DWORD ValueType;
449
+ DWORD DataSize = 0 ;
176
450
177
- // Check if there's a deep link URL stored in the registry
178
- HKEY DeepLinkKeyHandle;
179
- if (RegOpenKeyExW (HKEY_CURRENT_USER, *RegistryKeyPath, 0 , KEY_READ | KEY_WRITE, &DeepLinkKeyHandle) == ERROR_SUCCESS )
451
+ // First query to get size of the registry value data
452
+ LONG QueryResult = RegQueryValueExW ( DeepLinkKeyHandle, DeepLinkRegistryValueName, nullptr , &ValueType, nullptr , &DataSize) ;
453
+ if (QueryResult == ERROR_SUCCESS && ValueType == REG_SZ && DataSize > 0 )
180
454
{
181
- DWORD Type ;
182
- DWORD DataSize = 0 ;
455
+ TArray<TCHAR> Buffer ;
456
+ Buffer. SetNumZeroed ( DataSize / sizeof (TCHAR) + 1 ) ;
183
457
184
- // First query to get size of the registry value data
185
- if (RegQueryValueExW (DeepLinkKeyHandle, DeepLinkRegistryValueName, nullptr , &Type, nullptr , &DataSize) == ERROR_SUCCESS && Type == REG_SZ && DataSize > 0 )
458
+ // Second query to get actual data content
459
+ LONG DataResult = RegQueryValueExW (DeepLinkKeyHandle, DeepLinkRegistryValueName, nullptr , nullptr , (BYTE*)Buffer.GetData (), &DataSize);
460
+ if (DataResult == ERROR_SUCCESS)
186
461
{
187
- TArray<TCHAR> Buffer;
188
- Buffer.SetNumZeroed (DataSize / sizeof (TCHAR) + 1 );
462
+ FString DeepLink = Buffer.GetData ();
189
463
190
- // Second query to get actual data content
191
- if (RegQueryValueExW (DeepLinkKeyHandle, DeepLinkRegistryValueName, nullptr , nullptr , (BYTE*)Buffer.GetData (), &DataSize) == ERROR_SUCCESS)
464
+ if (!DeepLink.IsEmpty ())
192
465
{
193
- FString DeepLink = Buffer.GetData ();
194
-
195
- if (!DeepLink.IsEmpty ())
196
- {
197
- IMTBL_LOG (" Found deep link in registry: %s" , *DeepLink);
466
+ IMTBL_LOG (" Found deep link in registry: %s" , *DeepLink);
198
467
199
- // Clear the registry value to avoid processing the same deep link multiple times
200
- FString EmptyValue = TEXT (" " );
201
- RegSetValueExW (DeepLinkKeyHandle, DeepLinkRegistryValueName, 0 , REG_SZ, (const BYTE*)*EmptyValue, (EmptyValue.Len () + 1 ) * sizeof (TCHAR));
202
- RegCloseKey (DeepLinkKeyHandle);
468
+ // Clear the registry value to avoid processing the same deep link multiple times
469
+ FString EmptyValue = TEXT (" " );
470
+ RegSetValueExW (DeepLinkKeyHandle, DeepLinkRegistryValueName, 0 , REG_SZ, (const BYTE*)*EmptyValue, (EmptyValue.Len () + 1 ) * sizeof (TCHAR));
203
471
204
- HandleDeepLink (PKCEData, DeepLink);
205
-
206
- return true ;
207
- }
472
+ HandleDeepLink (PKCEData, DeepLink);
473
+ return true ;
208
474
}
209
475
}
210
-
211
- RegCloseKey (DeepLinkKeyHandle);
212
- }
213
- else
214
- {
215
- // Create the registry key if it doesn't exist yet
216
- if (RegCreateKeyExW (HKEY_CURRENT_USER, *RegistryKeyPath, 0 , nullptr , 0 , KEY_WRITE, nullptr , &DeepLinkKeyHandle, nullptr ) == ERROR_SUCCESS)
476
+ else
217
477
{
218
- FString EmptyValue = TEXT (" " );
219
- RegSetValueExW (DeepLinkKeyHandle, DeepLinkRegistryValueName, 0 , REG_SZ, (const BYTE*)*EmptyValue, (EmptyValue.Len () + 1 ) * sizeof (TCHAR));
220
- RegCloseKey (DeepLinkKeyHandle);
478
+ IMTBL_WARN (" Failed to read deep link registry value data (Error: %d - %s)" , DataResult, *GetWindowsErrorMessage (DataResult));
221
479
}
222
480
}
481
+ else if (QueryResult != ERROR_FILE_NOT_FOUND)
482
+ {
483
+ // Only log error if it's not due to missing value (which is normal)
484
+ IMTBL_WARN (" Failed to query deep link registry value size (Error: %d - %s)" , QueryResult, *GetWindowsErrorMessage (QueryResult));
485
+ }
223
486
224
- return true ;
487
+ return false ;
225
488
}
226
489
227
490
void UImmutablePKCEWindows::HandleDeepLink (UImmutablePKCEData* PKCEData, const FString& DeepLink)
228
491
{
492
+ if (!PKCEData)
493
+ {
494
+ IMTBL_ERR (" Cannot handle deep link: PKCEData is nullptr" );
495
+ return ;
496
+ }
497
+
229
498
// Clean up the deep link URL by removing quotes and extra whitespace
230
499
FString CleanDeepLink = DeepLink.TrimStartAndEnd ().Replace (TEXT (" \" " ), TEXT (" " ));
231
500
IMTBL_LOG (" Handle Deep Link: %s" , *CleanDeepLink);
@@ -248,16 +517,20 @@ void UImmutablePKCEWindows::Reset(UImmutablePKCEData* PKCEData)
248
517
return ;
249
518
}
250
519
251
- // In shipping builds, clean up protocol handler registry entries and temp files
252
- // In non-shipping builds, skip cleanup for easier testing and debugging
253
- #if UE_BUILD_SHIPPING
254
520
// Extract the protocol scheme from the redirect URI
255
521
FString Protocol;
256
522
if (!ExtractProtocolFromUri (PKCEData->PassportInitData .redirectUri , Protocol))
257
523
{
524
+ IMTBL_WARN (" Failed to extract protocol from URI during reset: %s" , *PKCEData->PassportInitData .redirectUri );
258
525
return ;
259
526
}
260
527
528
+ // Clean up registry watcher thread
529
+ CleanupThreadForProtocol (Protocol);
530
+
531
+ // In shipping builds, clean up protocol handler registry entries and temp files
532
+ // In non-shipping builds, skip cleanup for easier testing and debugging
533
+ #if UE_BUILD_SHIPPING
261
534
// Delete the PowerShell script file
262
535
FString PowerShellFilePath = GetPowerShellScriptPath (Protocol);
263
536
@@ -289,26 +562,28 @@ void UImmutablePKCEWindows::Reset(UImmutablePKCEData* PKCEData)
289
562
290
563
// Delete registry keys
291
564
FString ProtocolKeyPath = GetProtocolRegistryKeyPath (Protocol);
292
- LSTATUS lResult = RegDeleteTreeW (HKEY_CURRENT_USER, *ProtocolKeyPath);
293
- if (lResult == ERROR_SUCCESS)
565
+ LSTATUS DeleteResult = RegDeleteTreeW (HKEY_CURRENT_USER, *ProtocolKeyPath);
566
+ if (DeleteResult == ERROR_SUCCESS)
294
567
{
295
568
IMTBL_LOG (" Deleted protocol handler registry key: %s" , *ProtocolKeyPath);
296
569
}
297
- else
570
+ else if (DeleteResult != ERROR_FILE_NOT_FOUND)
298
571
{
299
- IMTBL_WARN (" Failed to delete protocol handler registry key: %s (Error code: %d)" , *ProtocolKeyPath, lResult);
572
+ // Only log error if key exists but couldn't be deleted
573
+ IMTBL_WARN (" Failed to delete protocol handler registry key: %s (Error code: %d - %s)" , *ProtocolKeyPath, DeleteResult, *GetWindowsErrorMessage (DeleteResult));
300
574
}
301
575
302
576
// Delete the deep link registry key
303
577
FString DeepLinkRegistryKeyPath = GetDeepLinkRegistryKeyPath (Protocol);
304
- lResult = RegDeleteKeyW (HKEY_CURRENT_USER, *DeepLinkRegistryKeyPath);
305
- if (lResult == ERROR_SUCCESS)
578
+ DeleteResult = RegDeleteKeyW (HKEY_CURRENT_USER, *DeepLinkRegistryKeyPath);
579
+ if (DeleteResult == ERROR_SUCCESS)
306
580
{
307
581
IMTBL_LOG (" Deleted deep link registry key: %s" , *DeepLinkRegistryKeyPath);
308
582
}
309
- else
583
+ else if (DeleteResult != ERROR_FILE_NOT_FOUND)
310
584
{
311
- IMTBL_WARN (" Failed to delete deep link registry key: %s (Error code: %d)" , *DeepLinkRegistryKeyPath, lResult);
585
+ // Only log error if key exists but couldn't be deleted
586
+ IMTBL_WARN (" Failed to delete deep link registry key: %s (Error code: %d - %s)" , *DeepLinkRegistryKeyPath, DeleteResult, *GetWindowsErrorMessage (DeleteResult));
312
587
}
313
588
314
589
IMTBL_LOG (" PKCE cleanup completed for protocol: %s" , *Protocol);
@@ -317,13 +592,6 @@ void UImmutablePKCEWindows::Reset(UImmutablePKCEData* PKCEData)
317
592
auto ResetPKCEData = [](UImmutablePKCEData* PKCEData)
318
593
{
319
594
PKCEData->DynamicMulticastDelegate_DeepLinkCallback .Clear ();
320
-
321
- if (GEngine)
322
- {
323
- FTSTicker::GetCoreTicker ().RemoveTicker (PKCEData->TickDelegateHandle );
324
- PKCEData->TickDelegateHandle .Reset ();
325
- }
326
-
327
595
PKCEData->PassportInitData = {};
328
596
};
329
597
@@ -332,6 +600,12 @@ void UImmutablePKCEWindows::Reset(UImmutablePKCEData* PKCEData)
332
600
333
601
FString UImmutablePKCEWindows::GetProtocolDirectory (const FString& Protocol)
334
602
{
603
+ if (Protocol.IsEmpty ())
604
+ {
605
+ IMTBL_WARN (" Empty protocol provided to GetProtocolDirectory, using default" );
606
+ return FPaths::ProjectSavedDir () / TEXT (" Immutable" ) / TEXT (" DefaultProtocol" );
607
+ }
608
+
335
609
FString BaseDir;
336
610
337
611
// In shipping builds, store files in system temp directory
@@ -343,6 +617,7 @@ FString UImmutablePKCEWindows::GetProtocolDirectory(const FString& Protocol)
343
617
{
344
618
// Fallback if TEMP environment variable is not available
345
619
BaseDir = FPaths::ProjectSavedDir ();
620
+ IMTBL_WARN (" System TEMP directory not found, using project saved directory instead" );
346
621
}
347
622
#else
348
623
// In non-shipping builds, use saved directory for testing purposes
@@ -357,12 +632,16 @@ FString UImmutablePKCEWindows::GetProtocolDirectory(const FString& Protocol)
357
632
#endif
358
633
359
634
// Ensure the directory exists
360
- if (!FPlatformFileManager::Get ().GetPlatformFile ().DirectoryExists (*BaseDir))
635
+ FString ProtocolDir = FPaths::ConvertRelativePathToFull (BaseDir / Protocol);
636
+ if (!FPlatformFileManager::Get ().GetPlatformFile ().DirectoryExists (*ProtocolDir))
361
637
{
362
- FPlatformFileManager::Get ().GetPlatformFile ().CreateDirectoryTree (*BaseDir);
638
+ if (!FPlatformFileManager::Get ().GetPlatformFile ().CreateDirectoryTree (*ProtocolDir))
639
+ {
640
+ IMTBL_WARN (" Failed to create protocol directory: %s" , *ProtocolDir);
641
+ }
363
642
}
364
643
365
- return FPaths::ConvertRelativePathToFull (BaseDir / Protocol) ;
644
+ return ProtocolDir ;
366
645
}
367
646
368
647
FString UImmutablePKCEWindows::GetPowerShellScriptPath (const FString& Protocol)
@@ -387,13 +666,24 @@ FString UImmutablePKCEWindows::GetDeepLinkRegistryKeyPath(const FString& Protoco
387
666
388
667
bool UImmutablePKCEWindows::ExtractProtocolFromUri (const FString& Uri, FString& OutProtocol)
389
668
{
390
- FString Right;
669
+ if (Uri.IsEmpty ())
670
+ {
671
+ IMTBL_ERR (" Cannot extract protocol: URI is empty" );
672
+ return false ;
673
+ }
391
674
675
+ FString Right;
392
676
if (!Uri.Split (TEXT (" ://" ), &OutProtocol, &Right))
393
677
{
394
678
IMTBL_ERR (" Failed to extract protocol from URI: %s" , *Uri);
395
679
return false ;
396
680
}
397
681
682
+ if (OutProtocol.IsEmpty ())
683
+ {
684
+ IMTBL_ERR (" Extracted protocol is empty from URI: %s" , *Uri);
685
+ return false ;
686
+ }
687
+
398
688
return true ;
399
689
}
0 commit comments