Fix bug with app list, adding proc and debug stuff.

This commit is contained in:
Greg
2022-12-27 17:08:56 -07:00
parent 5ed7ca16a4
commit d13180cb0e
21 changed files with 362 additions and 184 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ void API::Init()
Proc = new class Proc();
Apps = new class Apps();
Debug = new class Debug();
Target = new class Target();
Target = new class Target(Debug);
Listener = new SocketListener(ListenerCallback, NULL, 6900);
Running = true;
}
+24 -1
View File
@@ -146,7 +146,8 @@ struct APIPacket
struct ProcPacket
{
int32_t ProcessId;
int AppId;
int ProcessId;
char Name[32];
char TitleId[10];
};
@@ -162,6 +163,28 @@ enum AppState
STATE_SUSPENDED,
};
struct AppInfoPacket
{
char TitleId[10];
char ContentId[100];
char TitleName[200];
char MetaDataPath[100];
char LastAccessTime[100];
int Visible;
int SortPriority;
int DispLocation;
bool CanRemove;
char Category[10];
int ContentSize;
char InstallDate[100];
char UICategory[10];
};
struct AppListPacket
{
AppInfoPacket Apps[];
};
#pragma endregion
#pragma region Debug
+1 -6
View File
@@ -93,14 +93,9 @@ void Apps::GetAppsList(OrbisNetId Sock)
return;
}
// Send the number of apps.
SockSendInt(Sock, AppList.size());
// Send all of the apps.
for (const auto& App : AppList)
{
sceNetSend(Sock, &App, sizeof(AppDatabase::AppInfo), 0);
}
SendLargeData(Sock, (unsigned char*)AppList.data(), AppList.size() * sizeof(AppInfoPacket));
}
void Apps::GetAppInfoString(OrbisNetId Sock, const char* TitleId)
+76
View File
@@ -1,5 +1,8 @@
#include "Common.h"
#include "Debug.h"
#include "APIHelper.h"
#include <sys/ptrace.h>
void Debug::HandleAPI(OrbisNetId Sock, APIPacket* Packet)
{
@@ -8,10 +11,83 @@ void Debug::HandleAPI(OrbisNetId Sock, APIPacket* Packet)
default:
break;
case API_DBG_ATTACH:
break;
case API_DBG_DETACH:
break;
}
}
bool Debug::TryDetach(int pid)
{
// Check if we are even attached.
if (!IsDebugging)
{
return true;
}
// Detach from the process.
int res = ptrace(PT_DETACH, pid, nullptr, 0);
if (res != 0)
{
klog("DetachProcess(): ptrace(PT_DETACH) failed with error %llX\n", res);
return false;
}
// TODO: Kill the watching thread and Remove any Watchpoints / Breakpoints now.
IsDebugging = false;
CurrentPID = 0;
return true;
}
void Debug::Attach(OrbisNetId Sock)
{
auto pid = RecieveInt(Sock);
// If we are currently debugging another process lets detach from it.
if (!TryDetach(pid))
{
klog("Attach(): TryDetach Failed. :(\n");
SendStatus(Sock, 0);
return;
}
// Use ptrace to attach to begin debugging this pid.
int res = ptrace(PT_ATTACH, pid, nullptr, 0);
if (res != 0)
{
klog("Attach(): ptrace(PT_ATTACH) failed with error %llX\n", res);
SendStatus(Sock, 0);
return;
}
// Attaching by default will stop execution of the remote process. Lets continue it now.
res = ptrace(PT_CONTINUE, pid, nullptr, 0);
if (res != 0)
{
klog("Attach(): ptrace(PT_CONTINUE) failed with error %llX\n", res);
SendStatus(Sock, 0);
return;
}
IsDebugging = true;
CurrentPID = pid;
SendStatus(Sock, 1);
}
void Debug::Detach(OrbisNetId Sock)
{
auto pid = RecieveInt(Sock);
auto result = TryDetach(pid);
SendStatus(Sock, result ? 1 : 0);
}
Debug::Debug()
{
+6 -2
View File
@@ -3,12 +3,16 @@
class Debug
{
public:
bool IsDebugging;
int CurrentPID;
Debug();
~Debug();
void HandleAPI(OrbisNetId Sock, APIPacket* Packet);
private:
bool IsDebugging;
int CurrentPID;
bool TryDetach(int pid);
void Attach(OrbisNetId Sock);
void Detach(OrbisNetId Sock);
};
+13 -22
View File
@@ -13,8 +13,6 @@ void Proc::HandleAPI(OrbisNetId Sock, APIPacket* Packet)
SendProcessList(Sock);
SendStatus(Sock, APIResults::API_OK);
break;
case API_PROC_LOAD_ELF:
@@ -39,38 +37,31 @@ Proc::~Proc()
void Proc::SendProcessList(OrbisNetId Sock)
{
// TODO: Update
// Allocate space for packet to send.
/*auto Packet = (ProcPacket*)malloc(sizeof(ProcPacket));
auto Packet = (ProcPacket*)malloc(sizeof(ProcPacket));
// Gets list of running processes.
auto procInfo = (ProcInfo*)malloc(sizeof(ProcInfo) * 200);
memset(procInfo, 0, sizeof(ProcInfo) * 200);
int ProcessCount = KDriver::GetProcessList(200, procInfo);
// Get the list of running processes.
std::vector<kinfo_proc> processList;
GetProcessList(processList);
// Send the number of processes running.
sceNetSend(Sock, &ProcessCount, sizeof(int), 0);
SockSendInt(Sock, processList.size());
// Populate each process packet and send it.
for (auto i = 0; i < ProcessCount; i++)
for (const auto& i : processList)
{
memset(Packet, 0, sizeof(ProcPacket));
Packet->ProcessID = procInfo[i].PID;
Packet->Attached = procInfo[i].Attached;
// Get the app info using the pid.
OrbisAppInfo appInfo;
sceKernelGetAppInfo(i.pid, &appInfo);
strcpy(Packet->ProcName, procInfo[i].ProcName);
strcpy(Packet->TitleID, procInfo[i].TitleID);
Packet->TextSegmentBase = procInfo[i].TextSegmentBase;
Packet->TextSegmentLen = procInfo[i].TextSegmentLen;
Packet->DataSegmentBase = procInfo[i].DataSegmentBase;
Packet->DataSegmentLen = procInfo[i].DataSegmentLen;
Packet->AppId = appInfo.AppId;
Packet->ProcessId = i.pid;
strcpy(Packet->Name, i.name);
strcpy(Packet->TitleId, appInfo.TitleId);
sceNetSend(Sock, Packet, sizeof(ProcPacket), 0);
}
// Cleanup.
free(Packet);
free(procInfo);*/
}
+10 -5
View File
@@ -1,6 +1,7 @@
#include "Common.h"
#include "Target.h"
#include "APIHelper.h"
#include "Debug.h"
#include <orbis/SysCore.h>
void Target::HandleAPI(OrbisNetId Sock, APIPacket* Packet)
@@ -66,9 +67,9 @@ void Target::HandleAPI(OrbisNetId Sock, APIPacket* Packet)
}
}
Target::Target()
Target::Target(class Debug* Debug)
{
this->Debug = Debug;
}
Target::~Target()
@@ -87,6 +88,7 @@ void Target::SendTargetInfo(OrbisNetId Sock)
Packet->CPUTemp = GetCPUTemp();
Packet->SOCTemp = GetSOCTemp();
// Current Big App.
auto bigAppAppId = sceSystemServiceGetAppIdOfBigApp();
if (bigAppAppId > 0)
{
@@ -129,11 +131,14 @@ void Target::SendTargetInfo(OrbisNetId Sock)
GetPSID(Packet->PSID);
Packet->ConsoleType = GetConsoleType();
// TODO: Debugging Added Later.
Packet->Attached = false; // TODO: Add funcionality.
//Packet->CurrentProc
// Debugging.
Packet->AttachedPid = Debug->CurrentPID;
Packet->Attached = Debug->IsDebugging;
// User.
sceUserServiceGetForegroundUser(&Packet->ForegroundAccountId);
// Storage Stats.
auto res = ShellCoreUtil::sceShellCoreUtilGetFreeSizeOfUserPartition(&Packet->FreeSpace, &Packet->TotalSpace);
// Perf Stats. TODO: Move from toolbox
+4 -2
View File
@@ -1,9 +1,11 @@
#pragma once
class Debug;
class Target
{
public:
Target();
Target(Debug* Debug);
~Target();
void HandleAPI(OrbisNetId Sock, APIPacket* Packet);
@@ -12,5 +14,5 @@ public:
void SetSettings(OrbisNetId Sock);
private:
Debug* Debug;
};
+26
View File
@@ -202,6 +202,32 @@ bool SockRecvInt(OrbisNetId Sock, int* val)
return !(sceNetRecv(Sock, val, sizeof(int), 0) < 0);
}
#define MIN(a,b) ((a) < (b) ? (a) : (b))
bool SendLargeData(OrbisNetId Sock, unsigned char* data, size_t dataLen)
{
unsigned char* CurrentPosition = data;
size_t DataLeft = dataLen;
int res = 0;
while (DataLeft > 0)
{
size_t DataChunkSize = MIN(8192, DataLeft);
res = sceNetSend(Sock, CurrentPosition, DataChunkSize, 0);
if (res < 0)
{
return false;
}
DataLeft -= res;
CurrentPosition += res;
}
return true;
}
#pragma endregion
void hexdump(void* ptr, int buflen) {
+2
View File
@@ -15,9 +15,11 @@ bool Jailbreak();
bool CopySflash();
int getMacAddress(OrbisNetIfName ifName_Num, char* strOut, size_t strlen);
//TODO: Make socket class.
// Networking
bool SockSendInt(OrbisNetId Sock, int val);
bool SockRecvInt(OrbisNetId Sock, int* val);
bool SendLargeData(OrbisNetId Sock, unsigned char* data, size_t dataLen);
struct kinfo_proc {
int structSize; //0x00
+11 -11
View File
@@ -1,11 +1,11 @@
#pragma once
#define ORBISLIB_MAJOR 3
#define ORBISLIB_MINOR 0
#define ORBISLIB_BUILDVERSION 693
#define stringify(a) stringify_(a)
#define stringify_(a) #a
#if defined(_DEBUG)
#define ORBISLIB_BUILDSTRING ("[OrbisLib Daemon " stringify(ORBISLIB_MAJOR) "." stringify(ORBISLIB_MINOR) "] Dev Build " stringify(ORBISLIB_BUILDVERSION) " " __DATE__ " " __TIME__)
#else
#define ORBISLIB_BUILDSTRING ("[OrbisLib Daemon " stringify(ORBISLIB_MAJOR) "." stringify(ORBISLIB_MINOR) "] Build " stringify(ORBISLIB_BUILDVERSION) " " __DATE__ " " __TIME__)
#endif
#pragma once
#define ORBISLIB_MAJOR 3
#define ORBISLIB_MINOR 0
#define ORBISLIB_BUILDVERSION 708
#define stringify(a) stringify_(a)
#define stringify_(a) #a
#if defined(_DEBUG)
#define ORBISLIB_BUILDSTRING ("[OrbisLib Daemon " stringify(ORBISLIB_MAJOR) "." stringify(ORBISLIB_MINOR) "] Dev Build " stringify(ORBISLIB_BUILDVERSION) " " __DATE__ " " __TIME__)
#else
#define ORBISLIB_BUILDSTRING ("[OrbisLib Daemon " stringify(ORBISLIB_MAJOR) "." stringify(ORBISLIB_MINOR) "] Build " stringify(ORBISLIB_BUILDVERSION) " " __DATE__ " " __TIME__)
#endif
+1 -1
View File
@@ -84,7 +84,7 @@ int main()
// Init App install utils.
sceAppInstUtilInitialize();
#define LOADTOOLBOX
//#define LOADTOOLBOX
#ifdef LOADTOOLBOX
auto handle = sys_sdk_proc_prx_load("SceShellUI", "/user/data/Orbis Toolbox/OrbisToolbox-2.0.sprx");
if (handle > 0) {
@@ -2,6 +2,7 @@
using OrbisLib2.Common.Helpers;
using OrbisLib2.Targets;
using System.Net.Sockets;
using System.Runtime.InteropServices;
namespace OrbisLib2.Common.API
{
@@ -81,6 +82,34 @@ namespace OrbisLib2.Common.API
return (APIResults)Sock.RecvInt32();
}
/// <summary>
/// Recieves the next packet.
/// </summary>
/// <typeparam name="T">The packet type.</typeparam>
/// <param name="Sock">Socket to recieve the packet on.</param>
/// <param name="Packet">The packet to be recieved on.</param>
/// <returns>Returns true if successful.</returns>
public static bool RecieveNextPacket<T>(Socket Sock, ref T Packet)
{
try
{
var RawPacket = new byte[Marshal.SizeOf(Packet)];
var bytes = Sock.Receive(RawPacket);
if (bytes <= 0)
return false;
// Convert the recieved bytes to a struct.
Helper.BytesToStruct(RawPacket, ref Packet);
return true;
}
catch
{
return false;
}
}
/// <summary>
///
/// </summary>
@@ -140,11 +140,12 @@ namespace OrbisLib2.Common.API
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Ansi)]
public struct ProcPacket
{
public int ProcessID;
public int AppId;
public int ProcessId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
public string TitleID;
public string TitleId;
}
#endregion
@@ -159,7 +160,7 @@ namespace OrbisLib2.Common.API
STATE_SUSPENDED,
};
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Ansi), Serializable]
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Ansi, Size = 652), Serializable]
public struct AppInfoPacket
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
@@ -1,8 +1,10 @@
using System.Net.NetworkInformation;
using System.Data;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using static SQLite.SQLite3;
namespace OrbisLib2.Common.Helpers
{
@@ -89,6 +91,21 @@ namespace OrbisLib2.Common.Helpers
return BitConverter.ToInt32(Data, 0);
}
public static void RecvLarge(this Socket s, byte[] data)
{
int Left = data.Length;
int Received = 0;
while(Left > 0)
{
var chunkSize = Math.Min(s.ReceiveBufferSize, Left);
var res = s.Receive(data, Received, chunkSize, 0);
Received += res;
Left -= res;
}
}
/// <summary>
/// Easily connect to a socket and handle the time out.
/// </summary>
@@ -1,5 +1,7 @@
using OrbisLib2.Common.API;
using OrbisLib2.Common.Helpers;
using System.Data.Entity.Core.Metadata.Edm;
using System.Drawing;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
@@ -29,25 +31,25 @@ namespace OrbisLib2.Targets
public List<AppInfo> GetAppList()
{
var AppList = new List<AppInfo>();
var result = API.SendCommand(Target, 5, APICommands.API_APPS_GET_LIST, (Socket Sock, APIResults Result) =>
{
// Get the number of apps installed.
int Count = Sock.RecvInt32();
var Count = Sock.RecvInt32();
// Itterate through the count to recieve all the apps details.
for (int i = 0; i < Count; i++)
// Recieve all of the arrary as one large packet.
var dataSize = Count * Marshal.SizeOf(typeof(AppInfoPacket));
var data = new byte[dataSize];
Sock.RecvLarge(data);
// Allocate and copy the packet to begin marshaling it.
IntPtr ptr = Marshal.AllocHGlobal(dataSize);
Marshal.Copy(data, 0, ptr, dataSize);
for(int i = 0; i < Count; i++)
{
// Recieve the bytes of the struct.
// Marshal each part of the buffer to a struct.
var Packet = new AppInfoPacket();
var RawPacket = new byte[Marshal.SizeOf(Packet)];
var bytes = Sock.Receive(RawPacket);
if (bytes <= 0)
break;
// Convert the recieved bytes to a struct.
Helper.BytesToStruct(RawPacket, ref Packet);
Packet = (AppInfoPacket)Marshal.PtrToStructure(IntPtr.Add(ptr, i * Marshal.SizeOf(typeof(AppInfoPacket))), typeof(AppInfoPacket));
// Try to parse the date time strings.
if (!DateTime.TryParse(Packet.LastAccessTime, out DateTime LastAccessTime))
@@ -60,10 +62,11 @@ namespace OrbisLib2.Targets
var firstNullIndex = Array.FindIndex(Packet.TitleName, b => b == 0);
string titleName = Encoding.UTF8.GetString(Packet.TitleName, 0, firstNullIndex);
// Add the entry to the list.
AppList.Add(new AppInfo(Packet.TitleId, Packet.ContentId, titleName, Packet.MetaDataPath, LastAccessTime, Packet.Visible,
Packet.SortPriority, Packet.DispLocation, Packet.CanRemove == 1, Packet.Category, Packet.ContentSize, InstallDate, Packet.UICategory));
}
Marshal.FreeHGlobal(ptr);
});
return AppList;
@@ -0,0 +1,42 @@
using OrbisLib2.Common.API;
using OrbisLib2.Common.Helpers;
using System.Net.Sockets;
using System.Runtime.InteropServices;
namespace OrbisLib2.Targets
{
public record ProcInfo(int AppId, int ProcessId, string Name, string TitleId);
public class Process
{
private Target Target;
public Process(Target Target)
{
this.Target = Target;
}
public List<ProcInfo> GetList()
{
var list = new List<ProcInfo>();
API.SendCommand(Target, 4, APICommands.API_PROC_GET_LIST, (Socket Sock, APIResults Result) =>
{
var processCount = Sock.RecvInt32();
for(int i = 0; i < processCount; i++)
{
var Packet = new ProcPacket();
if(!API.RecieveNextPacket(Sock, ref Packet))
{
continue;
}
list.Add(new ProcInfo(Packet.AppId, Packet.ProcessId, Packet.Name, Packet.TitleId));
}
});
return list;
}
}
}
@@ -83,7 +83,7 @@ namespace OrbisLib2.Targets
public TargetEvents Events;
// public Debug Debug;
public Payload Payload;
// public Process Process;
public Process Process;
public FTP FTP;
public Application Application;
@@ -94,7 +94,7 @@ namespace OrbisLib2.Targets
Events = new TargetEvents(this);
// Debug = new Debug(this);
Payload = new Payload(this);
// Process = new Process(PS4, this);
Process = new Process(this);
FTP = new FTP(this);
Application = new Application(this);
}
@@ -46,24 +46,24 @@ namespace OrbisNeighborHood.MVVM.View
"CUSA01000"
};
private List<AppPanel> PanelList = new List<AppPanel>();
public AppListView()
{
InitializeComponent();
Events.DBTouched += Events_DBTouched;
Events.TargetStateChanged += Events_TargetStateChanged;
// TODO: add event for selected target changing.
// Refresh the info about the current target.
RefreshTargetInfo();
Task.Run(() =>
{
// Initially populate the app list.
InitAppList();
// Set Item source for listbox.
AppList.ItemsSource = PanelList;
// Create task to periodically check for app.db changes.
// Task.Run(() => CheckAppDatabase());
});
// Create task to periodically check for app.db changes.
Task.Run(() => CheckAppDatabase());
}
#region Properties
@@ -110,95 +110,6 @@ namespace OrbisNeighborHood.MVVM.View
#endregion
public void AddApp(string appCachePath, AppInfo App)
{
var currentTarget = TargetManager.SelectedTarget;
// Make sure the titleId format is correct. Helps weed out bad entries and folders.
if (!Regex.IsMatch(App.TitleId, @"[a-zA-Z]{4}\d{5}"))
return;
// Skip the Destiny entries that just exist for some reason even after a restore?... lol
if ((App.TitleId.Equals("CUSA00219") || App.TitleId.Equals("CUSA00568") || App.TitleId.Equals("CUSA01000")) && App.ContentSize <= 0)
return;
// Skip some that aren't technically an app.
if (TitleIdExlusionList.Contains(App.TitleId))
return;
// Weed out some more bad entries created by default.
if (App.TitleName.Length <= 2)
return;
// Make sure only add ones with a category.
if (App.UICategory.Length <= 0 || App.Category.Length <= 0)
return;
// Directory to cache stuff for app.
string currentAppPath = Path.Combine(appCachePath, App.TitleId);
// Create Directory for current app.
if (!Directory.Exists(currentAppPath))
{
Directory.CreateDirectory(currentAppPath);
}
// Cache icon0.png for app if we have not already.
if (!File.Exists(Path.Combine(currentAppPath, "icon0.png")) && !string.IsNullOrEmpty(App.MetaDataPath) && currentTarget.Info.IsAvailable) //TODO: Maybe add a isFTPAvailable.
{
currentTarget.FTP.DownloadFile($"{App.MetaDataPath}/icon0.png", Path.Combine(currentAppPath, "icon0.png"));
}
// Fetch the App version.
var appVersion = currentTarget.Application.GetAppInfoString(App.TitleId, "APP_VER");
// Add or update app list item.
Dispatcher.Invoke(() =>
{
var panel = AppList.Items.Cast<AppPanel>().ToList().Find(x => x.App.TitleId == App.TitleId);
if (panel != null)
{
panel.Update(App, appVersion);
}
else
{
AppList.Items.Add(new AppPanel(App, appVersion));
}
});
}
private void InitAppList()
{
// Clear lists so we can re-populate them.
Dispatcher.Invoke(() => AppList.Items.Clear());
// Make sure we have a target we can pull the db from.
if (TargetManager.Targets.Count <= 0)
return;
// Make sure the Target is online so we can pull the db.
var currentTarget = TargetManager.SelectedTarget;
if (currentTarget == null)
{
Console.WriteLine("No current target we can use to load apps.");
return;
}
// Make sure we have the appCache folder.
string appCachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"Orbis Suite\AppCache\");
if (!Directory.Exists(appCachePath))
{
Directory.CreateDirectory(appCachePath);
}
var appList = currentTarget.Application.GetAppList();
Parallel.ForEach(appList, app =>
{
AddApp(appCachePath, app);
});
}
private async Task CheckAppDatabase()
{
while (true)
@@ -216,30 +127,81 @@ namespace OrbisNeighborHood.MVVM.View
continue;
}
// Appcache location.
string appCachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"Orbis Suite\AppCache\");
// Get the current app list.
var appList = currentTarget.Application.GetAppList();
// Check for deletions.
Parallel.ForEach(AppList.Items.Cast<AppPanel>().ToList(), app =>
// Check for adding apps.
Parallel.ForEach(appList, app =>
{
if (appList.Find(x => x.TitleId == app.App.TitleId) == null)
var currentTarget = TargetManager.SelectedTarget;
string appCachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"Orbis Suite\AppCache\");
// Make sure the titleId format is correct. Helps weed out bad entries and folders.
if (!Regex.IsMatch(app.TitleId, @"[a-zA-Z]{4}\d{5}"))
return;
// Skip the Destiny entries that just exist for some reason even after a restore?... lol
if ((app.TitleId.Equals("CUSA00219") || app.TitleId.Equals("CUSA00568") || app.TitleId.Equals("CUSA01000")) && app.ContentSize <= 0)
return;
// Skip some that aren't technically an app.
if (TitleIdExlusionList.Contains(app.TitleId))
return;
// Weed out some more bad entries created by default.
if (app.TitleName.Length <= 2)
return;
// Make sure only add ones with a category.
if (app.UICategory.Length <= 0 || app.Category.Length <= 0)
return;
// Directory to cache stuff for app.
string currentAppPath = Path.Combine(appCachePath, app.TitleId);
// Create Directory for current app.
if (!Directory.Exists(currentAppPath))
{
Dispatcher.Invoke(() => AppList.Items.Remove(app));
Directory.CreateDirectory(currentAppPath);
}
// Cache icon0.png for app if we have not already.
if (!File.Exists(Path.Combine(currentAppPath, "icon0.png")) && !string.IsNullOrEmpty(app.MetaDataPath) && currentTarget.Info.IsAvailable) //TODO: Maybe add a isFTPAvailable.
{
currentTarget.FTP.DownloadFile($"{app.MetaDataPath}/icon0.png", Path.Combine(currentAppPath, "icon0.png"));
}
// Fetch the App version.
var appVersion = currentTarget.Application.GetAppInfoString(app.TitleId, "APP_VER");
Dispatcher.Invoke(() =>
{
var pannel = PanelList.Find(x => x.App.TitleId == app.TitleId);
if(pannel != null)
{
pannel.Update(app, appVersion);
}
else
{
PanelList.Add(new AppPanel(app, appVersion));
}
});
});
// Check to remove apps.
Parallel.ForEach(PanelList, panel =>
{
var app = appList.Find(x => x.TitleId == panel.App.TitleId);
if(app == null)
{
PanelList.Remove(panel);
}
});
// Check for new apps / updates.
Parallel.ForEach(appList, app =>
// Update view.
Dispatcher.Invoke(() =>
{
var currentAppList = AppList.Items.Cast<AppPanel>().ToList();
if (currentAppList.Find(x => x.App.TitleId == app.TitleId) == null)
{
AddApp(appCachePath, app);
}
AppList.Items.Refresh();
});
await Task.Delay(2000);
@@ -1 +1 @@
2073
2161
@@ -1 +1 @@
Version 3.0.2073 Debug Build Friday December 23 2022 11:44 PM
Version 3.0.2161 Debug Build Saturday December 24 2022 11:40 AM