CFramework/CFramework/Cheat/SDK/CItem/CItem.cpp

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;
}