203 lines
6.3 KiB
C++
203 lines
6.3 KiB
C++
#include "CItem.h"
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
constexpr int kMaxManagedStringLength = 128;
|
|
|
|
void AppendUtf8(std::string& output, uint32_t codePoint)
|
|
{
|
|
if (codePoint <= 0x7F)
|
|
{
|
|
output.push_back(static_cast<char>(codePoint));
|
|
}
|
|
else if (codePoint <= 0x7FF)
|
|
{
|
|
output.push_back(static_cast<char>(0xC0 | (codePoint >> 6)));
|
|
output.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));
|
|
}
|
|
else if (codePoint <= 0xFFFF)
|
|
{
|
|
output.push_back(static_cast<char>(0xE0 | (codePoint >> 12)));
|
|
output.push_back(static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F)));
|
|
output.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));
|
|
}
|
|
else
|
|
{
|
|
output.push_back(static_cast<char>(0xF0 | (codePoint >> 18)));
|
|
output.push_back(static_cast<char>(0x80 | ((codePoint >> 12) & 0x3F)));
|
|
output.push_back(static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F)));
|
|
output.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));
|
|
}
|
|
}
|
|
|
|
std::string ReadManagedUtf16String(uintptr_t stringObject)
|
|
{
|
|
if (!Memory::IsUserAddress(stringObject))
|
|
return {};
|
|
|
|
int32_t length = 0;
|
|
if (!m.TryRead(stringObject + offset::ManagedString::Length, length) ||
|
|
length <= 0 || length > kMaxManagedStringLength)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
std::vector<char16_t> chars;
|
|
if (!m.TryReadVector<char16_t>(stringObject + offset::ManagedString::Data,
|
|
static_cast<std::size_t>(length), chars, kMaxManagedStringLength))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
std::string output;
|
|
output.reserve(chars.size());
|
|
for (std::size_t index = 0; index < chars.size(); ++index)
|
|
{
|
|
const uint32_t first = chars[index];
|
|
if (first == 0)
|
|
break;
|
|
|
|
uint32_t codePoint = first;
|
|
if (first >= 0xD800 && first <= 0xDBFF)
|
|
{
|
|
if (index + 1 < chars.size() && chars[index + 1] >= 0xDC00 && chars[index + 1] <= 0xDFFF)
|
|
{
|
|
codePoint = 0x10000u + ((first - 0xD800u) << 10) + (chars[++index] - 0xDC00u);
|
|
}
|
|
else
|
|
{
|
|
codePoint = 0xFFFDu;
|
|
}
|
|
}
|
|
else if (first >= 0xDC00 && first <= 0xDFFF)
|
|
{
|
|
codePoint = 0xFFFDu;
|
|
}
|
|
|
|
if (codePoint < 0x20u && codePoint != '\t')
|
|
codePoint = 0xFFFDu;
|
|
AppendUtf8(output, codePoint);
|
|
}
|
|
return output;
|
|
}
|
|
}
|
|
|
|
bool ReadInventoryItemMetadata(const uintptr_t itemAddress, LootItemMetadata& out)
|
|
{
|
|
out = {};
|
|
if (!Memory::IsUserAddress(itemAddress))
|
|
return false;
|
|
|
|
uintptr_t itemIdString = 0;
|
|
if (m.TryRead(itemAddress + offset::InventoryItem::Id, itemIdString) &&
|
|
Memory::IsUserAddress(itemIdString))
|
|
{
|
|
out.itemId = ReadManagedUtf16String(itemIdString);
|
|
}
|
|
|
|
uintptr_t templateAddress = 0;
|
|
if (!m.TryRead(itemAddress + offset::InventoryItem::Template, templateAddress) ||
|
|
!Memory::IsUserAddress(templateAddress))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
out.valid = true;
|
|
|
|
uintptr_t nameString = 0;
|
|
if (m.TryRead(templateAddress + offset::ItemTemplate::Name, nameString) &&
|
|
Memory::IsUserAddress(nameString))
|
|
{
|
|
out.name = ReadManagedUtf16String(nameString);
|
|
}
|
|
|
|
uintptr_t shortNameString = 0;
|
|
if (m.TryRead(templateAddress + offset::ItemTemplate::ShortName, shortNameString) &&
|
|
Memory::IsUserAddress(shortNameString))
|
|
{
|
|
out.shortName = ReadManagedUtf16String(shortNameString);
|
|
}
|
|
|
|
uint8_t questItem = 0;
|
|
if (m.TryRead(templateAddress + offset::ItemTemplate::QuestItem, questItem))
|
|
out.isQuestItem = questItem != 0;
|
|
|
|
int32_t rarity = 0;
|
|
if (m.TryRead(templateAddress + offset::ItemTemplate::Rarity, rarity))
|
|
out.rarity = rarity;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CItem::GetAddress(uintptr_t& ptr)
|
|
{
|
|
address = ptr;
|
|
return Memory::IsUserAddress(address);
|
|
}
|
|
|
|
bool CItem::Update()
|
|
{
|
|
m_bIsCorpse = false;
|
|
m_bMetadataValid = false;
|
|
m_bIsQuestItem = false;
|
|
m_iRarity = 0;
|
|
m_iMarketPrice = 0;
|
|
m_CName.clear();
|
|
m_CItemId.clear();
|
|
m_vecOrigin = {};
|
|
|
|
if (!Memory::IsUserAddress(address))
|
|
return false;
|
|
|
|
const uintptr_t transform = m.ReadChain(address,
|
|
{ offset::UnityComponent::Step0, offset::UnityComponent::Step1, offset::UnityComponent::Step2,
|
|
offset::UnityComponent::Step3, offset::UnityComponent::Step4, offset::UnityComponent::Transform });
|
|
m_vecOrigin = GetTransformPosition(transform);
|
|
if (Vec3_Empty(m_vecOrigin))
|
|
return false;
|
|
|
|
uintptr_t inventoryItem = 0;
|
|
LootItemMetadata metadata{};
|
|
if (m.TryRead(address + offset::LootItem::Item, inventoryItem) &&
|
|
Memory::IsUserAddress(inventoryItem))
|
|
{
|
|
m_bMetadataValid = ReadInventoryItemMetadata(inventoryItem, metadata);
|
|
if (m_bMetadataValid)
|
|
{
|
|
m_bIsQuestItem = metadata.isQuestItem;
|
|
m_iRarity = metadata.rarity;
|
|
m_CItemId = metadata.itemId;
|
|
m_CName = !metadata.shortName.empty() ? metadata.shortName : metadata.name;
|
|
}
|
|
}
|
|
|
|
uintptr_t outerItemId = 0;
|
|
if (m_CItemId.empty() && m.TryRead(address + offset::LootItem::ItemId, outerItemId) &&
|
|
Memory::IsUserAddress(outerItemId))
|
|
{
|
|
m_CItemId = ReadManagedUtf16String(outerItemId);
|
|
}
|
|
|
|
if (m_CName.empty())
|
|
{
|
|
uintptr_t outerName = 0;
|
|
if (m.TryRead(address + offset::LootItem::Name, outerName) && Memory::IsUserAddress(outerName))
|
|
m_CName = ReadManagedUtf16String(outerName);
|
|
}
|
|
|
|
// Keep a stable label even when a partially initialized loot object has no template metadata.
|
|
if (m_CName.empty())
|
|
m_CName = "物品";
|
|
|
|
// Keep the legacy corpse check, but validate the chain through ReadChain's read-only checks.
|
|
const uintptr_t nameAddr = m.ReadChain(address, { 0x0, 0x0, 0x48 });
|
|
const std::string className = m.ReadString(nameAddr, 64);
|
|
if (className == "ObservedCorpse" || className == "Corpse")
|
|
m_bIsCorpse = true;
|
|
|
|
return true;
|
|
}
|