mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-07-24 12:06:34 +00:00
137 lines
7.3 KiB
C#
137 lines
7.3 KiB
C#
using Daybreak.API.Interop;
|
|
using Daybreak.API.Interop.GuildWars;
|
|
using Daybreak.API.Services.Interop;
|
|
using Daybreak.Shared.Models.Api;
|
|
using Daybreak.Shared.Models.Guildwars;
|
|
using System.Extensions.Core;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
|
|
namespace Daybreak.API.Services;
|
|
|
|
public sealed class InventoryService(
|
|
GameContextService gameContextService,
|
|
GameThreadService gameThreadService,
|
|
UIService uIService,
|
|
ILogger<InventoryService> logger)
|
|
{
|
|
private readonly GameContextService gameContextService = gameContextService;
|
|
private readonly GameThreadService gameThreadService = gameThreadService;
|
|
private readonly UIService uIService = uIService;
|
|
private readonly ILogger<InventoryService> logger = logger;
|
|
|
|
public async Task<InventoryInformation?> GetInventoryInformation(CancellationToken cancellationToken)
|
|
{
|
|
var scopedLogger = this.logger.CreateScopedLogger();
|
|
var itemTuples = await this.gameThreadService.QueueOnGameThread(() =>
|
|
{
|
|
unsafe
|
|
{
|
|
var gameContext = this.gameContextService.GetGameContext();
|
|
if (gameContext.IsNull)
|
|
{
|
|
scopedLogger.LogError("Game context is null");
|
|
return default;
|
|
}
|
|
|
|
var inventory = gameContext.Pointer->Items->Inventory;
|
|
if (inventory is null)
|
|
{
|
|
scopedLogger.LogError("Inventory is null");
|
|
return default;
|
|
}
|
|
|
|
var itemTuples = new List<(GWCA.GW.Constants.BagType Type, List<(uint ModelId, string EncodedCompleteName, string EncodedSingleName, string EncodedName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType, uint Interaction, uint ModelFileId, Byte DyeTint, ushort Value)>)>();
|
|
for (var i = 0; i < 23; i++)
|
|
{
|
|
// Use GWCA helper to fetch bag pointers by index — safer than reading an inline array directly.
|
|
var bag = GWCA.GW.Items.GetBagByIndex((uint)i);
|
|
if (bag is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var retBag = new List<(uint ModelId, string EncodedCompleteName, string EncodedSingleName, string EncodedName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType, uint Interaction, uint ModelFileId, Byte DyeTint, ushort Value)>();
|
|
itemTuples.Add((bag->BagType, retBag));
|
|
if (bag->ItemsCount is 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var itemPtr in bag->Items.Value)
|
|
{
|
|
if (itemPtr is 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var item = (ItemStruct*)itemPtr;
|
|
var singleItemName = new string((char*)item->SingleItemName);
|
|
var nameEncoded = new string((char*)item->NameEnc);
|
|
var completeNameEncoded = new string((char*)item->CompleteNameEnc);
|
|
var modifiers = new uint[item->ModStructSize];
|
|
for (var j = 0; j < item->ModStructSize; j++)
|
|
{
|
|
modifiers[j] = item->ModStruct[j].Mod;
|
|
}
|
|
|
|
retBag.Add((item->ModelId, completeNameEncoded, singleItemName, nameEncoded, (item->Interaction & 0x08000000) != 0, item->Quantity, modifiers, (ItemType)item->Type, item->Interaction, item->ModelFileId, item->Dye.DyeTint, item->Value));
|
|
}
|
|
}
|
|
|
|
return itemTuples;
|
|
}
|
|
}, cancellationToken);
|
|
|
|
if (itemTuples is null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
// Decode strings sequentially to avoid race conditions with the game's TextParser language field.
|
|
// Parallel decoding causes crashes because multiple operations race to set/restore the language.
|
|
var decodedItemTuples = new List<(GWCA.GW.Constants.BagType Type, List<(uint ModelId, string EncodedName, string? DecodedName, string EncodedSingleName, string? DecodedSingleName, string EncodedCompleteName, string? DecodedCompleteName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType, uint Interaction, uint ModelFileId, byte DyeTint, ushort Value)> Items)>();
|
|
foreach (var tuple in itemTuples)
|
|
{
|
|
var decodedItems = new List<(uint ModelId, string EncodedName, string? DecodedName, string EncodedSingleName, string? DecodedSingleName, string EncodedCompleteName, string? DecodedCompleteName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType, uint Interaction, uint ModelFileId, byte DyeTint, ushort Value)>();
|
|
foreach (var item in tuple.Item2)
|
|
{
|
|
var decodedName = await this.uIService.DecodeString(item.EncodedName, (GWCA.GW.Constants.Language)Language.English, cancellationToken);
|
|
var decodedCompleteName = await this.uIService.DecodeString(item.EncodedCompleteName, (GWCA.GW.Constants.Language)Language.English, cancellationToken);
|
|
var decodedSingleName = await this.uIService.DecodeString(item.EncodedSingleName, (GWCA.GW.Constants.Language)Language.English, cancellationToken);
|
|
decodedItems.Add((item.ModelId, item.EncodedName, decodedName, item.EncodedSingleName, decodedSingleName, item.EncodedCompleteName, decodedCompleteName, item.Inscribable, item.Quantity, item.Modifiers, item.ItemType, item.Interaction, item.ModelFileId, item.DyeTint, item.Value));
|
|
}
|
|
|
|
decodedItemTuples.Add((tuple.Type, decodedItems));
|
|
}
|
|
|
|
return new InventoryInformation(
|
|
Bags: [.. decodedItemTuples.Select(tuple =>
|
|
new BagEntry(
|
|
BagType: tuple.Type.ToString(),
|
|
Items: [.. tuple.Items.Select(item =>
|
|
new ItemEntry(
|
|
ItemModelId: item.ModelId,
|
|
EncodedName: ToBase64(item.EncodedName),
|
|
DecodedName: item.DecodedName ?? string.Empty,
|
|
EncodedSingleName: ToBase64(item.EncodedSingleName),
|
|
DecodedSingleName: item.DecodedSingleName ?? string.Empty,
|
|
EncodedCompleteName: ToBase64(item.EncodedCompleteName),
|
|
DecodedCompleteName: item.DecodedCompleteName ?? string.Empty,
|
|
Inscribable: item.Inscribable,
|
|
Quantity: item.Quantity,
|
|
Modifiers: item.Modifiers,
|
|
Properties: [.. ItemProperty.ParseItemModifiers([.. item.Modifiers.Select(m => (Shared.Models.Guildwars.ItemModifier)m)])],
|
|
ItemType: item.ItemType.ToString(),
|
|
Interaction: item.Interaction,
|
|
ModelFileId: item.ModelFileId,
|
|
DyeTint: item.DyeTint,
|
|
Value: item.Value))]))]);
|
|
}
|
|
|
|
private static string ToBase64(string encoded)
|
|
{
|
|
return Convert.ToBase64String(Encoding.Unicode.GetBytes(encoded));
|
|
}
|
|
}
|