Skip to content

Commit

Permalink
Merge pull request #5 from Citrinate/keyvalue
Browse files Browse the repository at this point in the history
  • Loading branch information
Citrinate authored Feb 22, 2024
2 parents f1a92e5 + fc0f846 commit 855b4a4
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 113 deletions.
1 change: 0 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ jobs:
dotnet publish ${{ env.PLUGIN_NAME }} -c "Release" -o "out/generic" -p:ContinuousIntegrationBuild=true --nologo
mkdir -p ./out/dist/${{ env.PLUGIN_NAME }}
cp ./out/generic/${{ env.PLUGIN_NAME }}.dll ./out/dist/${{ env.PLUGIN_NAME }}
cp ./out/generic/ValveKeyValue.dll ./out/dist/${{ env.PLUGIN_NAME }}
- name: Create README
uses: docker://pandoc/core:3.1
Expand Down
1 change: 0 additions & 1 deletion CS2Interface/CS2Interface.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

<ItemGroup>
<PackageReference Include="System.Composition.AttributedModel" Version="8.0.0" />
<PackageReference Include="ValveKeyValue" Version="0.8.2.162" GeneratePathProperty="true" />
</ItemGroup>

<ItemGroup>
Expand Down
19 changes: 11 additions & 8 deletions CS2Interface/Data/GameDataItems.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ValveKeyValue;
using SteamKit2;

namespace CS2Interface {
internal class GameDataItems : GameDataResource {
private KVObject? Data;
private KeyValue? Data;

internal GameDataItems(string url) : base(url) {}

internal async Task<bool> Update() {
KVObject? data = await FetchKVResource().ConfigureAwait(false);
KeyValue? data = await FetchKVResource().ConfigureAwait(false);
if (data == null) {
ASF.ArchiLogger.LogGenericError(String.Format("Couldn't load game data from: {0}", Url));

Expand All @@ -23,22 +25,23 @@ internal async Task<bool> Update() {
return true;
}

internal KVObject? this[string? key] {
internal List<KeyValue>? this[string? key] {
get {
if (key == null || Data == null) {
return null;
}

return Data.Search(key);
return Data.Children.Where(x => x.Name == key).SelectMany(x => x.Children).ToList();
}
}

internal KVObject? GetDef(string value, string index, bool suppressErrorLogs = false) {
internal KeyValue? GetDef(string value, string index, bool suppressErrorLogs = false) {
if (Data == null) {
return null;
}

var def = this[value]?[index];
KeyValue? def = this[value]?.Where(x => x.Name?.ToUpper().Trim() == index.ToUpper().Trim()).FirstOrDefault();

if (def == null) {
if (!suppressErrorLogs) {
ASF.ArchiLogger.LogGenericError(String.Format("Couldn't find definition: {0}[{1}]", value, index));
Expand All @@ -47,7 +50,7 @@ internal KVObject? this[string? key] {
return null;
}

return new KVObject(value, def);
return def;
}
}
}
2 changes: 1 addition & 1 deletion CS2Interface/Data/GameDataItemsCDN.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal async Task<bool> Update() {

internal string? this[string? key] {
get {
if (key == null || Data == null) {
if (key == null || Data == null || !Data.ContainsKey(key)) {
return null;
}

Expand Down
11 changes: 7 additions & 4 deletions CS2Interface/Data/GameDataResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using ValveKeyValue;
using SteamKit2;

namespace CS2Interface {
internal class GameDataResource {
Expand All @@ -14,12 +14,15 @@ internal GameDataResource(string url) {
Url = new Uri(url);
}

protected async Task<KVObject?> FetchKVResource(KVSerializerOptions? options = null) {
protected async Task<KeyValue?> FetchKVResource() {
HttpClient httpClient = new();
using (Stream response = await httpClient.GetStreamAsync(Url).ConfigureAwait(false)) {
KVSerializer serializer = KVSerializer.Create(KVSerializationFormat.KeyValues1Text);
KeyValue kv = new KeyValue();
if (!kv.ReadAsText(response)) {
return null;
}

return serializer.Deserialize(response, options);
return kv;
}
}

Expand Down
12 changes: 7 additions & 5 deletions CS2Interface/Data/GameDataText.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ValveKeyValue;
using SteamKit2;

namespace CS2Interface {
internal class GameDataText : GameDataResource {
private KVObject? Data;
private List<KeyValue>? Data;

internal GameDataText(string url) : base(url) {}

internal async Task<bool> Update() {
KVObject? data = (await FetchKVResource(new KVSerializerOptions { HasEscapeSequences = true, EnableValveNullByteBugBehavior = true }).ConfigureAwait(false))?.Search("Tokens");
KeyValue? data = await FetchKVResource().ConfigureAwait(false);
if (data == null) {
ASF.ArchiLogger.LogGenericError(String.Format("Couldn't load game data from: {0}", Url));

return false;
}

Data = data;
Data = data.Children.Where(x => x.Name == "Tokens").SelectMany(x => x.Children).ToList();
Updated = true;

return true;
Expand All @@ -29,7 +31,7 @@ internal string? this[string? key] {
return null;
}

return Data.SearchFirst(key)?.Value.ToString();
return Data.Where(x => x.Name?.ToUpper().Trim() == key.ToUpper().Trim()).FirstOrDefault()?.Value;
}
}
}
Expand Down
27 changes: 12 additions & 15 deletions CS2Interface/Data/InventoryItem.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ArchiSteamFarm.Core;
using Newtonsoft.Json;
using SteamKit2;
using SteamKit2.GC.CSGO.Internal;
using ValveKeyValue;

namespace CS2Interface {
internal sealed class InventoryItem : Item {
Expand Down Expand Up @@ -90,7 +91,7 @@ private bool CanBeMoved() {
}

if (GetAttribute("cannot trade")?.ToUInt32() == 1
|| ItemData!.ItemDef.GetValue("attributes", "cannot trade")?.ToString() == "1"
|| ItemData!.ItemDef.GetValue("attributes", "cannot trade") == "1"
) {
return false;
}
Expand All @@ -103,7 +104,7 @@ private bool CanBeMoved() {
// Apparently certain case keys can't be put in storage units? untested, might not be necessary
// https://github.com/nombersDev/casemove/blob/8289ea35cb6d76c553ee4955adecdf9a02622764/src/main/helpers/classes/steam/items/index.js#L506
// https://dev.doctormckay.com/topic/4086-inventory-and-music-kits/#comment-10610
if (ItemInfo.flags == 10 && (ItemData!.ItemDef.GetValue("prefab")?.ToString() == "valve weapon_case_key" || ItemData!.ItemDef.GetValue("prefab")?.ToString() == "weapon_case_key")) {
if (ItemInfo.flags == 10 && (ItemData!.ItemDef.GetValue("prefab") == "valve weapon_case_key" || ItemData!.ItemDef.GetValue("prefab") == "weapon_case_key")) {
return false;
}

Expand All @@ -130,31 +131,27 @@ private bool ParseAttributes(List<CSOEconItemAttribute>? attributes) {
}

foreach (CSOEconItemAttribute attribute in attributes) {
KVObject? attribute_def = GameData.ItemsGame.GetDef("attributes", attribute.def_index.ToString());
KeyValue? attribute_def = GameData.ItemsGame.GetDef("attributes", attribute.def_index.ToString());
if (attribute_def == null) {
return false;
}

string? attribute_name = attribute_def["name"].ToString();
string? attribute_name = attribute_def["name"].Value;
if (attribute_name == null) {
ASF.ArchiLogger.LogGenericError(String.Format("Missing name for attribute: {0}", attribute.def_index.ToString()));

return false;
}

if (attribute_def["attribute_type"]?.ToString() == "vector") {
ASF.ArchiLogger.LogGenericError(String.Format("zzzz vector for: {0}", attribute_name));
}

switch (attribute_def["attribute_type"]?.ToString()) {
switch (attribute_def["attribute_type"].Value) {
case "uint32":
case null when attribute_def["stored_as_integer"]?.ToString() == "1":
Attributes.Add(attribute_name, new Attribute<uint>(attribute_name, BitConverter.ToUInt32(attribute.value_bytes)));
case null when attribute_def["stored_as_integer"].Value == "1":
Attributes.Add(attribute_name, new Attribute<uint>(attribute_name, BitConverter.ToUInt32(attribute.value_bytes.ToArray(), 0)));
break;

case "float":
case null when attribute_def["stored_as_integer"]?.ToString() == "0":
Attributes.Add(attribute_name, new Attribute<float>(attribute_name, BitConverter.ToSingle(attribute.value_bytes)));
case null when attribute_def["stored_as_integer"].Value == "0":
Attributes.Add(attribute_name, new Attribute<float>(attribute_name, BitConverter.ToSingle(attribute.value_bytes.ToArray(), 0)));
break;

case "string":
Expand All @@ -163,7 +160,7 @@ private bool ParseAttributes(List<CSOEconItemAttribute>? attributes) {

case "vector":
default:
ASF.ArchiLogger.LogGenericError(String.Format("Unknown attribute type: {0}, value: {1}", attribute_def["attribute_type"].ToString(), Convert.ToBase64String(attribute.value_bytes)));
ASF.ArchiLogger.LogGenericError(String.Format("Unknown attribute type: {0}, value: {1}", attribute_def["attribute_type"].Value, Convert.ToBase64String(attribute.value_bytes)));
return false;
}
}
Expand Down
66 changes: 34 additions & 32 deletions CS2Interface/Data/Item.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Linq;
using Newtonsoft.Json;
using ValveKeyValue;
using SteamKit2;

namespace CS2Interface {
internal class Item {
Expand Down Expand Up @@ -117,7 +117,7 @@ internal static void SetSerializationProperties(bool should_serialize_additional

protected bool SetAdditionalProperties() {
try {
ItemData = new(this);
ItemData = new ItemData(this);
} catch (Exception) {
GameData.Update(true);

Expand All @@ -126,25 +126,25 @@ protected bool SetAdditionalProperties() {

{ // Set rarity name, which differs based on the type of item
string locKey = "loc_key"; // General rarities
if (ItemData.ItemDef.GetValue("taxonomy", "weapon")?.ToString() == "1") {
if (ItemData.ItemDef.GetValue("taxonomy", "weapon") == "1") {
locKey = "loc_key_weapon"; // Weapon skin rarities
} else if (ItemData.ItemDef.GetValue("item_slot")?.ToString() == "customplayer") {
} else if (ItemData.ItemDef.GetValue("item_slot") == "customplayer") {
locKey = "loc_key_character"; // Agent rarities
}
RarityName = GameData.CsgoEnglish[GameData.ItemsGame["rarities"]?.FirstOrDefault(x => (uint) x["value"] == Rarity)?[locKey].ToString()];
RarityName = GameData.CsgoEnglish[GameData.ItemsGame["rarities"]?.FirstOrDefault(x => x["value"].Value == Rarity.ToString())?[locKey].Value];
}

TypeName = GameData.CsgoEnglish[ItemData.ItemDef.GetValue("item_type_name")?.ToString()?.Substring(1)];
QualityName = GameData.CsgoEnglish[GameData.ItemsGame["qualities"]?.FirstOrDefault(x => (uint) x["value"] == Quality)?.Name];
TypeName = GameData.CsgoEnglish[ItemData.ItemDef.GetValue("item_type_name")?.Substring(1)];
QualityName = GameData.CsgoEnglish[GameData.ItemsGame["qualities"]?.FirstOrDefault(x => x["value"].Value == Quality.ToString())?.Name];
OriginName = GameData.GetOriginName(Origin);

// Set the item name, which will be something like: what kind of sticker it is, or the name of the weapon skin, or the type of pin/coin
// If an item has a wear value, but uses the default paint_kit (vanilla knives for example), this will be "-"
ItemName = GameData.CsgoEnglish[(ItemData.MusicDef?.GetValue("loc_name") ?? ItemData.StickerKitDef?.GetValue("item_name") ?? ItemData.PaintKitDef?.GetValue("description_tag") ?? ItemData.ItemDef.GetValue("item_name"))?.ToString()?.Substring(1)];
ItemName = GameData.CsgoEnglish[(ItemData.MusicDef?.GetValue("loc_name") ?? ItemData.StickerKitDef?.GetValue("item_name") ?? ItemData.PaintKitDef?.GetValue("description_tag") ?? ItemData.ItemDef.GetValue("item_name"))?.Substring(1)];

// Set the tool named, used for various things like differentiating between Graffiti and Sealed Graffiti
if (ItemData.ItemDef.GetValue("prefab")?.ToString() == "csgo_tool") {
ToolName = GameData.CsgoEnglish[ItemData.ItemDef.GetValue("item_name")?.ToString()?.Substring(1)];
if (ItemData.ItemDef.GetValue("prefab") == "csgo_tool") {
ToolName = GameData.CsgoEnglish[ItemData.ItemDef.GetValue("item_name")?.Substring(1)];
}

// Set the graffiti color, ignore if tint_id is 0 (Multicolor)
Expand All @@ -153,8 +153,8 @@ protected bool SetAdditionalProperties() {
}

// Set various weapon-only attributes
if (ItemData.ItemDef.GetValue("taxonomy", "weapon")?.ToString() == "1") {
WeaponName = GameData.CsgoEnglish[ItemData.ItemDef.GetValue("item_name")?.ToString()?.Substring(1)];
if (ItemData.ItemDef.GetValue("taxonomy", "weapon") == "1") {
WeaponName = GameData.CsgoEnglish[ItemData.ItemDef.GetValue("item_name")?.Substring(1)];

if (Wear != null) {
WearName = GameData.GetWearName(Wear.Value);
Expand All @@ -165,9 +165,9 @@ protected bool SetAdditionalProperties() {
// Set the weapon image url
string? cdnNameID;
if (PaintIndex == 0) {
cdnNameID = ItemData.ItemDef.GetValue("name")?.ToString(); // Vanilla Knives
cdnNameID = ItemData.ItemDef.GetValue("name"); // Vanilla Knives
} else {
cdnNameID = String.Format("{0}_{1}", ItemData.ItemDef.GetValue("name")?.ToString(), ItemData.PaintKitDef!.GetValue("name")?.ToString()); // Everything else
cdnNameID = String.Format("{0}_{1}", ItemData.ItemDef.GetValue("name"), ItemData.PaintKitDef!.GetValue("name")); // Everything else
}
WeaponImageURL = GameData.ItemsGameCdn[cdnNameID];
}
Expand All @@ -190,29 +190,31 @@ protected bool SetAdditionalProperties() {

// Set the name id, used for determining related set and crate
if (PaintIndex == 0 && ItemData.StickerKitDef == null && ItemData.MusicDef == null) {
NameID = ItemData.ItemDef.GetValue("name")?.ToString(); // Collectibles, Vanilla Knives
NameID = ItemData.ItemDef.GetValue("name"); // Collectibles, Vanilla Knives
} else {
NameID = String.Format("[{0}]{1}", (ItemData.MusicDef ?? ItemData.StickerKitDef ?? ItemData.PaintKitDef)?.GetValue("name")?.ToString(), ItemData.ItemDef.GetValue("name")?.ToString()); // Everything else
NameID = String.Format("[{0}]{1}", (ItemData.MusicDef ?? ItemData.StickerKitDef ?? ItemData.PaintKitDef)?.GetValue("name"), ItemData.ItemDef.GetValue("name")); // Everything else
}

{ // Determine what set, if any, this item belongs to
KVObject? setItemDef = GameData.ItemsGame["item_sets"]?.FirstOrDefault(x => x["items"][NameID] != null);
if (setItemDef != null) {
SetNameID = setItemDef.Name;
SetName = GameData.CsgoEnglish[setItemDef["name"]?.ToString()?.Substring(1)];
if (NameID != null) {
{ // Determine what set, if any, this item belongs to
KeyValue? setItemDef = GameData.ItemsGame["item_sets"]?.FirstOrDefault(x => x["items"][NameID] != KeyValue.Invalid);
if (setItemDef != null) {
SetNameID = setItemDef.Name;
SetName = GameData.CsgoEnglish[setItemDef["name"].Value?.Substring(1)];
}
}
}

{ // Determine what crate, if any, this item belongs to. Doesn't work for souvenir skins, knives, or gloves
string? lootListName = GameData.ItemsGame["client_loot_lists"]?.FirstOrDefault(x => x[NameID] != null)?.Name;
lootListName = lootListName == null ? null : GameData.ItemsGame["client_loot_lists"]?.FirstOrDefault(x => x[lootListName] != null)?.Name ?? lootListName; // Some lists in client_loot_lists are nested (1 or 2 layers), we want the top-most layer
string? lootListID = lootListName == null ? null : GameData.ItemsGame["revolving_loot_lists"]?.FirstOrDefault(x => x.Value.ToString() == lootListName)?.Name;
KVObject? crateItemDef = lootListID == null ? null : GameData.ItemsGame["items"]?.FirstOrDefault(x => x["attributes"]?["set supply crate series"]?["value"]?.ToString() == lootListID);
if (crateItemDef != null) {
CrateNameID = crateItemDef["name"]?.ToString();
CrateDefIndex = uint.Parse(crateItemDef.Name);
CrateSupplySeries = uint.Parse(lootListID!);
CrateName = GameData.CsgoEnglish[crateItemDef["item_name"]?.ToString()?.Substring(1)];
{ // Determine what crate, if any, this item belongs to. Doesn't work for souvenir skins, knives, or gloves
string? lootListName = GameData.ItemsGame["client_loot_lists"]?.FirstOrDefault(x => x[NameID] != KeyValue.Invalid)?.Name;
lootListName = lootListName == null ? null : GameData.ItemsGame["client_loot_lists"]?.FirstOrDefault(x => x[lootListName] != KeyValue.Invalid)?.Name ?? lootListName; // Some lists in client_loot_lists are nested (1 or 2 layers), we want the top-most layer
string? lootListID = lootListName == null ? null : GameData.ItemsGame["revolving_loot_lists"]?.FirstOrDefault(x => x.Value == lootListName)?.Name;
KeyValue? crateItemDef = lootListID == null ? null : GameData.ItemsGame["items"]?.FirstOrDefault(x => x["attributes"]["set supply crate series"]["value"].Value == lootListID);
if (crateItemDef != null && crateItemDef.Name != null) {
CrateNameID = crateItemDef["name"].Value;
CrateDefIndex = uint.Parse(crateItemDef.Name);
CrateSupplySeries = uint.Parse(lootListID!);
CrateName = GameData.CsgoEnglish[crateItemDef["item_name"].Value?.Substring(1)];
}
}
}

Expand Down
10 changes: 6 additions & 4 deletions CS2Interface/Data/ItemData.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using ArchiSteamFarm.Core;
using Newtonsoft.Json;
using ValveKeyValue;
using SteamKit2;

namespace CS2Interface {
internal class ItemData {
Expand Down Expand Up @@ -33,7 +33,7 @@ private ItemDef CreateItemDef(Item item) {
ItemDef itemDef = new(GameData.ItemsGame.GetDef("items", item.DefIndex.ToString()));

// Add prefab values
if (!MergePrefab(itemDef, itemDef.GetValue("prefab")?.ToString())) {
if (!MergePrefab(itemDef, itemDef.GetValue("prefab"))) {
throw new InvalidOperationException();
}

Expand All @@ -48,6 +48,8 @@ private bool MergePrefab(ItemDef itemDef, string? prefab) {
return true;
}

// STACK OVERFLOW HERE

// Some items have multiple prefabs separated by a space, but only one is valid (it has an entry in ItemsGame)
// Ex: "valve weapon_case_key": "valve" isn't valid, but "weapon_case_key" is
// Ex: "antwerp2022_sticker_capsule_prefab antwerp2022_sellable_item_with_payment_rules": "antwerp2022_sticker_capsule_prefab" is valid, but "antwerp2022_sellable_item_with_payment_rules" isn't
Expand All @@ -56,14 +58,14 @@ private bool MergePrefab(ItemDef itemDef, string? prefab) {
bool foundValid = false;

foreach (string prefabName in prefabNames) {
KVObject? prefabDef = GameData.ItemsGame.GetDef("prefabs", prefabName, suppressErrorLogs: true);
KeyValue? prefabDef = GameData.ItemsGame.GetDef("prefabs", prefabName, suppressErrorLogs: true);
if (prefabDef == null) {
continue;
}

foundValid = true;
itemDef.AddDef(prefabDef);
if (!MergePrefab(itemDef, prefabDef["prefab"]?.ToString())) {
if (!MergePrefab(itemDef, prefabDef["prefab"].Value)) {
return false;
};
}
Expand Down
Loading

0 comments on commit 855b4a4

Please sign in to comment.