Files
test/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs
T
2025-09-16 10:58:02 +07:00

270 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BrewMonster;
using ModelRenderer.Scripts.Common;
using ModelRenderer.Scripts.GameData;
using UnityEngine;
namespace PerfectWorld.Scripts.Managers
{
public class InventoryItemData
{
public byte Package;
public int Slot;
public int TemplateId;
public int ExpireDate;
public int State;
public int Count;
public ushort Crc;
public byte[] Content; // variable-length item-specific payload (can be null)
}
public static class EC_IvtrItem
{
private static readonly Dictionary<int, string> _tidNameCache = new Dictionary<int, string>();
private const int MaxContentHexToLog = 64;
public static string ResolveItemName(int templateId)
{
if (templateId <= 0) return "";
if (_tidNameCache.TryGetValue(templateId, out var cached)) return cached;
try
{
var edm = ElementDataManProvider.GetElementDataMan();
if (edm == null) return CacheAndReturn(templateId, "");
uint id = unchecked((uint)templateId);
object data = edm.get_data_ptr(id, ID_SPACE.ID_SPACE_ESSENCE);
string name = ExtractNameFromElement(data);
if (string.IsNullOrEmpty(name))
{
name = TryFindNameByScanningArrays(edm, id);
}
return CacheAndReturn(templateId, name ?? "");
}
catch (Exception ex)
{
Debug.LogWarning($"[Inventory] ResolveItemName error for tid={templateId}: {ex.Message}");
return CacheAndReturn(templateId, "");
}
}
private static string CacheAndReturn(int tid, string name)
{
_tidNameCache[tid] = name ?? "";
return name ?? "";
}
private static string ExtractNameFromElement(object data)
{
if (data == null) return "";
var t = data.GetType();
// Debug: Log all available fields and properties
Debug.Log($"[Inventory] Data type: {t.Name}");
var fields = t.GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (var f in fields)
{
Debug.Log($"[Inventory] Field: {f.Name} ({f.FieldType.Name})");
}
var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var p in props)
{
Debug.Log($"[Inventory] Property: {p.Name} ({p.PropertyType.Name})");
}
var methods = t.GetMethods(BindingFlags.Public | BindingFlags.Instance);
foreach (var m in methods)
{
if (m.Name.ToLower().Contains("name") || m.Name.ToLower().Contains("getname"))
{
Debug.Log($"[Inventory] Method: {m.Name} ({m.ReturnType.Name})");
}
}
// Prefer decoding the raw fields first to control encoding (Unicode for Vietnamese),
// then fall back to any string properties if needed.
var fieldName = t.GetField("name", BindingFlags.Public | BindingFlags.Instance);
if (fieldName != null && fieldName.FieldType == typeof(ushort[]))
{
var arr = fieldName.GetValue(data) as ushort[];
// Debug: Log the raw ushort array data
if (arr != null && arr.Length > 0)
{
var rawData = string.Join(",", arr.Take(Math.Min(10, arr.Length)));
Debug.Log($"[Inventory] Raw ushort array data (first 10): [{rawData}]");
}
// Vietnamese names are stored as wide chars; decode as Unicode first.
var s = ByteToStringUtils.UshortArrayToUnicodeString(arr);
// Debug log to see what we're getting
Debug.Log($"[Inventory] Unicode decode result: '{s}' (length: {s?.Length ?? 0})");
if (!string.IsNullOrEmpty(s) && !string.IsNullOrWhiteSpace(s)) return s;
// Fallback to legacy CP936 if Unicode was empty
s = ByteToStringUtils.UshortArrayToCP936String(arr);
Debug.Log($"[Inventory] CP936 fallback result: '{s}' (length: {s?.Length ?? 0})");
if (!string.IsNullOrEmpty(s)) return s;
}
// Try calling GetName method if it exists (similar to C++ pIt->GetName())
var getNameMethod = t.GetMethod("GetName", BindingFlags.Public | BindingFlags.Instance);
if (getNameMethod != null && getNameMethod.ReturnType == typeof(string))
{
try
{
var val = getNameMethod.Invoke(data, null) as string;
Debug.Log($"[Inventory] GetName method result: '{val}' (length: {val?.Length ?? 0})");
if (!string.IsNullOrEmpty(val) && !string.IsNullOrWhiteSpace(val)) return val;
}
catch (Exception ex)
{
Debug.LogWarning($"[Inventory] Error calling GetName method: {ex.Message}");
}
}
return "";
}
private static string TryFindNameByScanningArrays(object edm, uint id)
{
try
{
var fields = edm.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (var f in fields)
{
if (!f.FieldType.IsArray) continue;
var arr = f.GetValue(edm) as Array;
if (arr == null || arr.Length == 0) continue;
var elemType = f.FieldType.GetElementType();
var idField = elemType.GetField("id", BindingFlags.Public | BindingFlags.Instance);
if (idField == null || idField.FieldType != typeof(uint)) continue;
for (int i = 0; i < arr.Length; i++)
{
var element = arr.GetValue(i);
if (element == null) continue;
var value = (uint)idField.GetValue(element);
if (value == id)
{
var name = ExtractNameFromElement(element);
if (!string.IsNullOrEmpty(name)) return name;
var toStr = element.ToString();
if (!string.IsNullOrEmpty(toStr)) return toStr;
}
}
}
}
catch { }
return "";
}
public static bool TryParseInventoryDetail(byte[] buffer, out byte byPackage, out byte ivtrSize, out List<InventoryItemData> items)
{
byPackage = 0;
ivtrSize = 0;
items = null;
if (buffer == null || buffer.Length < 6)
{
return false;
}
int index = 0;
byPackage = buffer[index++];
ivtrSize = buffer[index++];
uint contentLength = BitConverter.ToUInt32(buffer, index); index += 4;
int remaining = buffer.Length - index;
int contentBytes = remaining;
if (contentLength < (uint)remaining)
{
contentBytes = (int)contentLength;
}
if (contentBytes <= 0)
{
items = new List<InventoryItemData>();
return true;
}
byte[] content = new byte[contentBytes];
Buffer.BlockCopy(buffer, index, content, 0, contentBytes);
// Parse S2C::cmd_own_ivtr_detail_info.content
int ci = 0;
if (contentBytes < 4)
{
return false;
}
int numItems = BitConverter.ToInt32(content, ci); ci += 4;
items = new List<InventoryItemData>(numItems > 0 ? numItems : 0);
for (int i = 0; i < numItems; i++)
{
// Ensure enough bytes for fixed part: index, tid, expire, state, amount, crc(2), len(2) => 4*5 + 2 + 2 = 24 bytes
if (ci + 24 > contentBytes)
{
return false;
}
int slotIndex = BitConverter.ToInt32(content, ci); ci += 4;
if (slotIndex < 0)
{
// Skip invalid slot but continue parsing to keep stream in sync
// Still need to consume fields even if slot is negative
int skipTid = BitConverter.ToInt32(content, ci); ci += 4;
int skipExpire = BitConverter.ToInt32(content, ci); ci += 4;
int skipState = BitConverter.ToInt32(content, ci); ci += 4;
int skipAmt = BitConverter.ToInt32(content, ci); ci += 4;
ushort skipCrc = BitConverter.ToUInt16(content, ci); ci += 2;
ushort skipLen = BitConverter.ToUInt16(content, ci); ci += 2;
if (skipLen > 0)
{
if (ci + skipLen > contentBytes) return false;
ci += skipLen;
}
continue;
}
int tid = BitConverter.ToInt32(content, ci); ci += 4;
int expireDate = BitConverter.ToInt32(content, ci); ci += 4;
int state = BitConverter.ToInt32(content, ci); ci += 4;
int amount = BitConverter.ToInt32(content, ci); ci += 4;
ushort crc = BitConverter.ToUInt16(content, ci); ci += 2;
ushort extraLen = BitConverter.ToUInt16(content, ci); ci += 2;
byte[] extra = null;
if (extraLen > 0)
{
if (ci + extraLen > contentBytes)
{
return false;
}
extra = new byte[extraLen];
Buffer.BlockCopy(content, ci, extra, 0, extraLen);
ci += extraLen;
}
var item = new InventoryItemData
{
Package = byPackage,
Slot = slotIndex,
TemplateId = tid,
ExpireDate = expireDate,
State = state,
Count = amount,
Crc = crc,
Content = extra
};
items.Add(item);
}
return true;
}
public static string BytesToHex(byte[] bytes, int max)
{
if (bytes == null || bytes.Length == 0) return "";
int len = Math.Min(bytes.Length, max);
string hex = BitConverter.ToString(bytes, 0, len);
if (bytes.Length > len) hex += "-...";
return hex;
}
}
}