• Я зарабатываю 100 000 RUB / месяц на этом сайте!

    А знаешь как? Я всего-лишь публикую (создаю темы), а админ мне платит. Трачу деньги на мороженое, робуксы и сервера в Minecraft. А ещё на паль из Китая. 

    Хочешь так же? Пиши и узнавай условия: https://t.me/alex_redact
    Реклама: https://t.me/yougame_official

Гайд Spoon-feed SVG icons parser. External / Internal

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
3 Фев 2020
Сообщения
36
Реакции
8
Всем привет. Это гайд, как реализовать полноценный SVG icons parser двумя методами.
// credits ( no ad ):
//
Пожалуйста, авторизуйтесь для просмотра ссылки.

//
Пожалуйста, авторизуйтесь для просмотра ссылки.

// nano svg

Метод 1. External / Internal

Для начала, нам необходимо загрузить в память .vpk файл, в котором хранятся ресурсы игры (например иконки). Для этого воспользуемся небольшой библиотекой

c_vpk.h:
Expand Collapse Copy
#pragma once

#include <fstream>
#include <string>
#include <optional>
#include <unordered_map>

#include "../../include/creep/creep_str.hpp"

#pragma pack(push,1)
struct vpk_header_t
{
    uint32_t signature;
    uint32_t version;
    uint32_t tree_size;
    uint32_t file_data_section_size;
    uint32_t archive_md5_section_size;
    uint32_t other_md5_section_size;
    uint32_t signature_section_size;
};

struct dir_entry_t
{
    uint32_t crc;
    uint16_t preload_bytes;
    uint16_t archive_index;
    uint32_t entry_offset;
    uint32_t entry_length;
    uint16_t terminator;
};
#pragma pack(pop)

class c_vpk_entry
{
public:
    c_vpk_entry() = default;
    c_vpk_entry(const std::string& base_file, const std::string& path, const dir_entry_t& entry) :_base_file(base_file), _path(path), _entry(entry) {}

    std::optional<std::vector<uint8_t>> get_data()
    {
        // Didn't add error handling here, as for my usage there is no need to.
        size_t [I]dir_pos = _base_file.find(creep[/I]("dir"));

        std::string old_number_string = std::to_string(_entry.archive_index);
        std::string vpk_file = _base_file.replace(_base_file.begin() + _dir_pos, _base_file.begin() + _dir_pos + 3, std::string(3 - old_number_string.length(), '0') + old_number_string);
        std::ifstream stream(vpk_file, std::ios::binary);
        if (!stream.is_open())
            return {};

        uint8_t* data = new uint8_t[_entry.entry_length];
        stream.seekg(_entry.entry_offset, stream.beg);
        stream.read((char*)data, _entry.entry_length);
        stream.close();

        std::vector<uint8_t> data_vector(data, data + _entry.entry_length);
        delete[] data;

        return data_vector;
    }

    std::vector<uint8_t> preload_bytes = {};
private:
    std::string _base_file = {};
    std::string _path = {};
    dir_entry_t _entry = {};
};

class c_vpk_archive
{
public:
    bool load(const std::string& name)
    {
        std::ifstream stream(name, std::ios::binary);
        if (!stream.is_open())
            return false;

        vpk_header_t file_header = {};
        stream.read((char*)&file_header, sizeof(file_header));
        if (file_header.signature != 0x55aa1234)
        {
            stream.close();
            return false;
        }

        if (file_header.version != 2)
        {
            stream.close();
            return false;
        }

        while (true)
        {
            std::string extension;

            // read from stream until it finds the end of the string "\x00"
            std::getline(stream, extension, '\x00');
            if (extension.empty())
                break;

            while (true)
            {
                std::string directory;
                std::getline(stream, directory, '\x00');
                if (directory.empty())
                    break;

                while (true)
                {
                    std::string file_name;
                    std::getline(stream, file_name, '\x00');
                    if (file_name.empty())
                        break;

                    std::string path = directory + '/' + file_name + '.' + extension;

                    dir_entry_t dir_entry;
                    stream.read((char*)&dir_entry, sizeof(dir_entry));

                    c_vpk_entry vpk_entry(name, path, dir_entry);
                    if (dir_entry.preload_bytes)
                    {
                        // I don't have a use for preload bytes, so you could skip over them
                        // by uncommenting the line below, and commenting the other lines inside of this if statement.
                        //stream.seekg(dir_entry.preload_bytes, stream.cur);

                        // If you do find a use for them however, here's how to read them.
                        uint8_t* bytes = new uint8_t[dir_entry.preload_bytes];
                        stream.read((char*)bytes, dir_entry.preload_bytes);
                        vpk_entry.preload_bytes = std::vector<uint8_t>(bytes, bytes + dir_entry.preload_bytes);
                        delete[] bytes;
                    }

                    if (!files.count(path))
                        files[path] = vpk_entry;
                }
            }
        }

        stream.close();

        return true;
    }

    std::optional<c_vpk_entry> get_file(const std::string& name)
    {
        if (files.count(name))
            return files[name];

        return {};
    }
private:
    std::unordered_map<std::string, c_vpk_entry> files = {};
};

Для иконок нас интересует pak01_dir.vpk, лежащий по пути Counter-Strike Global Offensive\game\csgo\pak01_dir.vpk

Один раз в инициализации вашего чита:

C++:
Expand Collapse Copy
inline c_vpk_archive   m_archive  {};

auto svg_parser::initialize() noexcept -> bool
{
    m_base_game_path = utils::get_csgo_game_path( true );

    if ( ! m_archive.load( m_base_game_path + creep_( "pak01_dir.vpk" ) ) )
    {
        std::cout << creep_("Failed to load VPK file!\n");
        return false;
    }

    return true;
}

Теперь мы имеем загруженный в память .vpk архив. Все иконки внутри этого архива располагаются по пути panorama/images/icons/equipment/ . Примерный полный путь до иконки panorama/images/icons/equipment/some_weapon.vsvg_c . Для получения имени оружия (some_weapon) мы можем взять m_szName из CCSWeaponBaseVData и подогнать под наш формат ( m_szName имеет вид weapon_some_weapon )

C++:
Expand Collapse Copy
auto make_svg_path = [](std::string& in)
{
    const std::string prefix    = creep_("weapon_");
    const std::string base_path = creep_("panorama/images/icons/equipment/");
    const std::string extension = creep_(".vsvg_c");

    if ( in.rfind(prefix, 0) == 0 )
        in.erase( 0, prefix.size( ) );
    in = base_path + in + extension;
};

Далее подгружает конкретную иконку в валвовском формате .vsvg_c, используя метод get_file c_vpk_archive

Код:
Expand Collapse Copy
std::string svg_icon_path{ base_name };

make_svg_path(svg_icon_path);

auto entry_opt = m_archive.get_file( svg_icon_path );

if ( ! entry_opt.has_value() )
{
    std::cout << creep_("Failed to load svg: ") << svg_icon_path << creep_("\n");
    return std::string{};
}

auto data_opt = entry_opt->get_data( );

if ( ! data_opt.has_value( ) )
{
    std::cout << creep_("Failed to read svg data: ") << svg_icon_path << creep_("\n");
    return std::string{};
}

Теперь нам нужно спарсить из .vsvg_c, .svg иконку, используя этот метод

C++:
Expand Collapse Copy
inline std::string parse(const std::vector<uint8_t>& file_bytes)
{
    const auto header = reinterpret_cast<const valve_svg_header_t*>(file_bytes.data());
    const auto data = reinterpret_cast<const valve_svg_data_t*>(file_bytes.data() + header->file_offset_to_svg_data);

    const auto total_header_size = header->file_offset_to_svg_data + offsetof(valve_svg_data_t, svg_string);

    auto str = std::string(file_bytes.begin(), file_bytes.end());
    str = str.substr(total_header_size);

    return str;
}

C++:
Expand Collapse Copy
auto parsed_data = svg::parse( * data_opt );

Далее парсим svg через библиотеку nano svg и растеризуем иконку в rgba формат

C++:
Expand Collapse Copy
auto svg_parser::make_texture_external(ID3D11Device* device, ID3D11DeviceContext* context, const std::string& complited_name, float desired_height) noexcept -> texture_data
{
#ifdef SVG_ICONS_EXTERNAL

    NSVGimage* image = nsvgParse( const_cast<char*>( complited_name.c_str( ) ), creep_("px"), 96.0f );

    if ( ! image )
    {
        std::cout << creep_("Failed to make svg image!\n");
        return {};
    }

    float scale  = desired_height / image->height;
    int   img_w  = int(image->width * scale);
    int   img_h  = int(image->height * scale);

    std::vector<unsigned char> rgba(img_w * img_h * 4);

    NSVGrasterizer* rast = nsvgCreateRasterizer();

    nsvgRasterize(rast, image, 0, 0, scale, rgba.data(), img_w, img_h, img_w * 4);
    nsvgDeleteRasterizer(rast);
    nsvgDelete(image);

    texture_data m_data{};

    if ( ! create_svg_texture( device, rgba.data(), img_w, img_h, m_data ) )
    {
        std::cout << creep_("Failed to create svg texture!\n");
        return {};
    }

    return m_data;

#endif

    return {};
}

И в конце создаем dx11 текстуру для рендера

C++:
Expand Collapse Copy
auto svg_parser::create_svg_texture(ID3D11Device* device, const unsigned char* rgba, int width, int height, texture_data& ret_data) -> bool
{
    D3D11_TEXTURE2D_DESC desc = {};
    desc.Width = width;
    desc.Height = height;
    desc.MipLevels = 1;
    desc.ArraySize = 1;
    desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    desc.SampleDesc.Count = 1;
    desc.Usage = D3D11_USAGE_DYNAMIC;
    desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

    D3D11_SUBRESOURCE_DATA initData = {};
    initData.pSysMem = rgba;
    initData.SysMemPitch = width * 4;
    initData.SysMemSlicePitch = 0;

    D3D11_SHADER_RESOURCE_VIEW_DESC r{};
    r.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    r.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    r.Texture2D.MipLevels = 1;
    r.Texture2D.MostDetailedMip = 0;

    ID3D11Texture2D* svg_texture_ret{ };
    ID3D11ShaderResourceView* svg_shader_resource_ret{ };

    HRESULT hr = device->CreateTexture2D( &desc, &initData, &svg_texture_ret );
    if ( FAILED( hr ) )
    {
        ret_data = { };
        return false;
    }

    hr = device->CreateShaderResourceView( svg_texture_ret, &r, &svg_shader_resource_ret );
    if ( FAILED( hr ) )
    {
        ret_data = { };
        return false;
    }

    ret_data = { svg_texture_ret, svg_shader_resource_ret, static_cast<int>(width), static_cast<int>(height) };
}

Готово! Теперь мы можем рендерить нашу иконку

C++:
Expand Collapse Copy
m_base.m_background_draw_list->AddImage(result.m_shader, vec3_t { pos.x - (result.width / 2.f), pos.y }.im(), vec3_t{pos.x + (result.width / 2.f), pos.y + result.height}.im(), color.im());

Метод 2. Internal only

В этом методе мы будем использовать функцию из panorama.dll для растеризации .svg иконки в rgba формат:

Парсите иконку аналогично из первого метода, получая готовый svg файл. После чего растеризуете:

C++:
Expand Collapse Copy
auto complited_name = parse_weapon_icon(base_name);

if ( complited_name.empty( ) )
    return false;

std::vector<uint8_t> texture ( 0xFFFFFF );
svg::image_data_t    img     ( texture );

uint32_t w{}, h{};

if ( ! img.load_svg( reinterpret_cast< uint8_t * >( complited_name.data( ) ), complited_name.size( ), &w, &h ) )
    return false;

const auto width_to_height = static_cast<float>( w ) / static_cast<float>( h );

texture_data m_data{};

create_svg_texture( device, reinterpret_cast<uint8_t*>( complited_name.data( ) ), complited_name.size( ), desired_height ? static_cast<uint32_t>(width_to_height * desired_height) : 0, desired_height, m_data );

m_textures.emplace( fnv1a::hash_64(base_name) + static_cast<uint64_t>(desired_height), m_data );

return true;

C++:
Expand Collapse Copy
struct image_data_t
{
    image_data_t(std::vector<uint8_t>& buf)
    {
        reinterpret_cast<void* (__fastcall*)(void*, int, int, int)>(g_dumper->dump()[FNV32("utl_buffer_init")].as<void*>())(pad, 0, 0, 0);
        *reinterpret_cast<uintptr_t*>(pad) = reinterpret_cast<uintptr_t>(buf.data());
        *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(pad) + 8) = buf.size();
    }

    bool load_svg(uint8_t* img, size_t s, uint32_t* w, uint32_t* h)
    {
        uint16_t magic[] = { 0x33, 0x34, 0x35, 0x5c, 0x37, 0x33, 0x30, 0x5c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5c, 0x70, 0x61, 0x6e, 0x6f, 0x72, 0x61, 0x6d, 0x61, 0x00 };
        return reinterpret_cast<bool(__fastcall*)(void*, uint32_t, void*, uint32_t*, uint32_t*, float, void*)>(g_dumper->dump()[FNV32("load_svg")].as<void*>())(img, s, pad, w, h, 1.f, magic);
    }

    char pad[0x3C];
};

И получается dx11 текстуру

C++:
Expand Collapse Copy
auto svg_parser::create_svg_texture(ID3D11Device* device, uint8_t* rgba, size_t sz, uint32_t width, uint32_t height, texture_data& ret_data) -> bool
{
    std::vector<uint8_t> texture( 0xFFFFFF );
    svg::image_data_t img( texture );

    if ( img.load_svg(rgba, sz, &width, &height) && width && height )
    {
        D3D11_TEXTURE2D_DESC desc = {};
        desc.Width = width;
        desc.Height = height;
        desc.MipLevels = 1;
        desc.ArraySize = 1;
        desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        desc.SampleDesc.Count = 1;
        desc.Usage = D3D11_USAGE_DYNAMIC;
        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

        D3D11_SUBRESOURCE_DATA initData = {};
        initData.pSysMem = texture.data();
        initData.SysMemPitch = width * 4;
        initData.SysMemSlicePitch = 0;

        D3D11_SHADER_RESOURCE_VIEW_DESC r{};
        r.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        r.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
        r.Texture2D.MipLevels = 1;
        r.Texture2D.MostDetailedMip = 0;

        ID3D11Texture2D* svg_texture_ret{ };
        ID3D11ShaderResourceView* svg_shader_resource_ret{ };

        HRESULT hr = device->CreateTexture2D(&desc, &initData, &svg_texture_ret);
        if ( FAILED( hr ) )
        {
            ret_data = { };
            return false;
        }

        hr = device->CreateShaderResourceView(svg_texture_ret, &r, &svg_shader_resource_ret);
        if ( FAILED( hr ) )
        {
            ret_data = { };
            return false;
        }

        ret_data = { svg_texture_ret, svg_shader_resource_ret, (int)width, (int)height };
    }

    return true;
}

И также рендерите:

C++:
Expand Collapse Copy
    m_base.m_background_draw_list->AddImage(result.m_shader, vec3_t { pos.x - (result.width / 2.f), pos.y }.im(), vec3_t{pos.x + (result.width / 2.f), pos.y + result.height}.im(), color.im());

P.S

C++:
Expand Collapse Copy
m_dump.emplace(FNV32("load_svg"), address_t{ creep_("panorama.dll"), creep_("48 8B C4 48 89 58 08 48 89 70 10 48 89 78 18 55 41 54 41 55 41 56 41 57 48 8D 6C 24 90") });
m_dump.emplace(FNV32("utl_buffer_init"), address_t{ shadow::dll_export("??0CUtlBuffer@@QEAA@HHH@Z", "tier0.dll").address().as<uintptr_t>() });

Важно отметить, что этап с ручным парсингом vpk файла и конвертацией валвовской vsvg_c в svg, можно заменить вызовом внутриигровой функцией из client.dll, однако найти ее у меня не получилось.

Спасибо за внимание!

image_2025-05-19_22-46-46.png
 
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
а зачем
 
можно куда проще на самом деле ...
 
Прикольно у бота керамбит) [Yanni]
 
Назад
Сверху Снизу