whiteoutlib 0.2.0

Read and write Blizzard game assets from Rust: models (MDX, M2, M3), textures (BLP, DDS, PNG, JPEG, BMP, TGA, TIFF, GIF) and archives (CASC, MPQ).
Documentation
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026 Fernando Sahmkow
//
// AUTO-GENERATED by scripts/gen_d3_sno_defs.py - do not edit manually.
//
// Source: Diablo III TypeDescriptor tables (data/d3_sno_reflection.json)
// SNO / GameBalance group enums: data/d3_sno_enums.json
// Field names come from data/d3_field_names.json; the retail binary has
// them stripped, so unnamed fields appear as field_0x<offset>.

#pragma once

#include "../sno_defs.h"

#include <cstddef>
#include <span>

namespace whiteout {
namespace sno {
namespace d3 {

/// One SNO group: what the game calls it, what it stores on disk, how big the
/// root struct is in the revision this binary registers.
struct D3SnoGroupInfo {
    u32 id;               ///< SNO group id (1..69; 16, 30 and 50 are unused)
    const char* name;     ///< "Actor", "Appearance", ...
    const char* extension; ///< ".acr", ".app", ... from g_SnoGroupExtensions
    u32 structSize;       ///< root struct size the handler registers
    bool singleton;       ///< only Hero (10) and Account (53)
};

/// One GameBalance group -- the enum a DT_GBID field's `group` refers to.
/// A GBID key is {group, id}; `rootOffset` locates that group's array inside the
/// GameBalance root struct and `stride` is its record size.
struct D3GameBalanceGroupInfo {
    u32 id;
    const char* name;       ///< nullptr if the build names no such group
    u32 stride;             ///< record size, 0 when there is no runtime array
    u32 rootOffset;         ///< offset inside the GameBalance root, 0 if none
    bool hasRuntimeTable;   ///< false for editor-only groups (e.g. Scenery)
};

/// Compiled type registry for Diablo III SNO formats.
///
/// Generated from the game's own reflection metadata, so struct sizes and
/// field offsets are byte-exact.  Shares the SnoTypeRegistry interface with
/// d4::SnoTypeRegistry.
class SnoTypeRegistry final : public sno::SnoTypeRegistry {
public:
    /// Get the singleton instance.
    static const SnoTypeRegistry& instance();

    const SnoTypeDef* findType(u32 typeHash) const override;
    u32 typeHashFromKey(u32 key) const override;
    const char* typeName(const SnoTypeDef& def) const override;
    std::span<const SnoFieldDef> fields(const SnoTypeDef& def) const override;
    const char* fieldName(const SnoFieldDef& def) const override;
    u32 typeHashFromName(const char* name) const override;

    /// Every type in the registry, sorted by hash.  Iterating this is the only
    /// way to reach the nested types (SubObject, BoneStructure, ...) -- the group
    /// map only names root types.
    std::span<const SnoTypeDef> types() const;

    /// Look up a SNO group by id.  Returns nullptr for an unused group.
    const D3SnoGroupInfo* snoGroup(u32 groupId) const;
    /// All SNO groups, sorted by id.
    std::span<const D3SnoGroupInfo> snoGroups() const;

    /// Look up a GameBalance group by id.  Returns nullptr if unknown -- which
    /// is the honest answer for the ids this build leaves unnamed.
    const D3GameBalanceGroupInfo* gameBalanceGroup(u32 groupId) const;
    /// All GameBalance groups, sorted by id.
    std::span<const D3GameBalanceGroupInfo> gameBalanceGroups() const;

private:
    SnoTypeRegistry();

    const SnoTypeDef* m_types = nullptr;
    size_t m_typeCount = 0;
    const SnoFieldDef* m_fields = nullptr;
    [[maybe_unused]] size_t m_fieldCount = 0;
    const char* const* m_typeNames = nullptr;
    const char* const* m_fieldNames = nullptr;
    size_t m_fieldNameCount = 0;
};

} // namespace d3
} // namespace sno
} // namespace whiteout