
import lldb, signal, sys, time
import lldb.formatters.Logger
import codelldb.value

############################################################
#
# Store pointers to some important types and globals.
#
############################################################


_FNameEntryType = None
_FNameEntryStride = None
_FUObjectItemType = None
_GNameBlocksDebug = None
_GObjectArrayForDebugVisualizers = None

def _init_globals(target):
    global _FNameEntryType, _FNameEntryStride, _FUObjectItemType
    global _GNameBlocksDebug, _GObjectArrayForDebugVisualizers

    if _FNameEntryType is None:
        t = target.FindFirstType('FNameEntry')
        if t.IsValid():
            _FNameEntryType = t
            _FNameEntryStride = t.GetByteAlign()

    if _FUObjectItemType is None:
        t = target.FindFirstType('FUObjectItem')
        if t.IsValid():
            _FUObjectItemType = t

    if _GNameBlocksDebug is None:
        v = target.FindFirstGlobalVariable('GNameBlocksDebug')
        if v.IsValid():
            _GNameBlocksDebug = v

    if _GObjectArrayForDebugVisualizers is None:
        v = target.FindFirstGlobalVariable('GObjectArrayForDebugVisualizers')
        if v.IsValid():
            _GObjectArrayForDebugVisualizers = v

class _GlobalsInitStopHook:
    def __init__(self, target, extra_args, internal_dict):
        _init_globals(target)
    def handle_stop(self, exe_ctx, stream):
        _init_globals(exe_ctx.GetTarget())
        return True


############################################################
#
# A forwarding synth provider is a provider that
# forwards all child lookups to a different SBValue.
#
############################################################

def TryGetSyntheticValue(v):
    s = v.GetSyntheticValue()
    return s if s.IsValid() else v

class ForwardingSynthProvider:
    def __init__(self, valobj, dict):
        self.valobj = valobj
        self.update()

    def num_children(self):
        return self.forward_to.GetNumChildren()

    def get_child_index(self, name):
        return self.forward_to.GetIndexOfChildWithName(name)

    def get_child_at_index(self, index):
        return self.forward_to.GetChildAtIndex(index)

    def update(self):
        self.forward_to = lldb.SBValue()

    def has_children(self):
        return self.forward_to.IsValid() and self.forward_to.MightHaveChildren()

############################################################
#
# A Stored synth provider is an synth provider that
# just stores an array of children.
#
############################################################

class StoredSynthProvider:
    def __init__(self, valobj, dict):
        self.valobj = valobj
        self.update()

    def num_children(self):
        return len(self.children)

    def get_child_index(self, name):
        for i, child in enumerate(self.children):
            if child.GetName() == name:
                return i
        return -1

    def get_child_at_index(self, index):
        if 0 <= index < len(self.children):
            return self.children[index]
        return lldb.SBValue()

    def update(self):
        self.children = []

    def has_children(self):
        return len(self.children) > 0

############################################################
#
# An Indexed synth provider exposes N integer-indexed
# children [0]..[N-1].  Subclasses set self.count in update()
# and override get_child_at_index(index) to return the SBValue
# for a given index (with their own range check).
#
############################################################

MAX_ARRAY_CHILDREN = 50

class IndexedSynthProvider:
    def __init__(self, valobj, dict):
        self.valobj = valobj
        self.update()

    def num_children(self):
        return min(self.count, MAX_ARRAY_CHILDREN)

    def get_child_index(self, name):
        try:
            return int(name.lstrip('[').rstrip(']'))
        except:
            return -1

    def has_children(self):
        return self.count > 0

    def update(self):
        self.count = 0

############################################################
#
# Provider for FString
#
############################################################

def UEFStringSummaryProvider(valobj, dict):
    target = valobj.GetTarget()
    sbtype = valobj.GetType().GetUnqualifiedType()
    if sbtype.IsPointerType():
        if valobj.GetValueAsUnsigned(0) == 0:
            return 'nullptr'
        valobj = valobj.Dereference()
    elif sbtype.IsReferenceType():
        valobj = valobj.Dereference()
    tarray = valobj.GetChildMemberWithName('Data').GetNonSyntheticValue()
    array_num = tarray.GetChildMemberWithName('ArrayNum').GetValueAsSigned(0)
    if array_num < 0:  return 'corrupted string'
    if array_num == 0: return 'empty string'
    data_ptr = tarray.GetChildMemberWithName('AllocatorInstance').GetChildMemberWithName('Data')
    buffer_addr = data_ptr.GetValueAsUnsigned(0)
    if buffer_addr == 0: return 'corrupted string'
    tchar_type = tarray.GetType().GetTemplateArgumentType(0)
    array_type = tchar_type.GetArrayType(array_num)
    sbaddr = lldb.SBAddress(buffer_addr, target)
    summary = target.CreateValueFromAddress('string', sbaddr, array_type).GetSummary()
    if summary and summary.startswith('u"'): return summary[1:]
    return summary

############################################################
#
# Provider for FName
#
############################################################

def UEFNameSummaryProvider(valobj, dict):
    valobj = valobj.GetNonSyntheticValue()
    target = valobj.GetTarget()
    process = target.GetProcess()
    entry_id = valobj.GetChildMemberWithName('DisplayIndex')
    index = entry_id.GetChildMemberWithName('Value').GetValueAsUnsigned(0)
    number = valobj.GetChildMemberWithName('Number').GetValueAsUnsigned(0)

    block_idx = index >> 16
    entry_offset = (index & 0xFFFF) * _FNameEntryStride

    gname_ptr = _GNameBlocksDebug.GetValueAsUnsigned(0)

    error = lldb.SBError()
    block_addr = process.ReadPointerFromMemory(gname_ptr + block_idx * target.GetAddressByteSize(), error)
    if error.Fail(): return '<read error>'

    entry_addr = block_addr + entry_offset
    entry = target.CreateValueFromAddress('entry', lldb.SBAddress(entry_addr, target), _FNameEntryType)

    header = entry.GetChildMemberWithName('Header')
    length = header.GetChildMemberWithName('Len').GetValueAsUnsigned(0)
    is_wide = header.GetChildMemberWithName('bIsWide').GetValueAsUnsigned(0)

    if is_wide:
        name_addr = entry.GetChildMemberWithName('WideName').GetLoadAddress()
        raw = process.ReadMemory(name_addr, length * 2, error)
        if error.Fail(): return '<read error>'
        name = raw.decode('utf-16-le')
    else:
        name_addr = entry.GetChildMemberWithName('AnsiName').GetLoadAddress()
        raw = process.ReadMemory(name_addr, length, error)
        if error.Fail(): return '<read error>'
        name = raw.decode('utf-8', errors='replace')

    if number > 0: return '%s_%d' % (name, number - 1)
    return name

############################################################
#
# Providers for TObjectPtr
#
############################################################

class TObjectPtrSynthProvider(ForwardingSynthProvider):
    def update(self):
        debug_ptr = self.valobj.GetChildMemberWithName('DebugPtr')
        addr = debug_ptr.GetValueAsUnsigned(0)
        if addr == 0 or addr & 1:  # null or unresolved handle (odd address)
            self.forward_to = lldb.SBValue()
        else:
            self.forward_to = TryGetSyntheticValue(debug_ptr.Dereference())

def TObjectPtrSummaryProvider(valobj, dict):
    debug_ptr = valobj.GetNonSyntheticValue().GetChildMemberWithName('DebugPtr')
    addr = debug_ptr.GetValueAsUnsigned(0)
    if addr == 0: return 'nullptr'
    if addr & 1: return 'unresolved'
    return '{...}'

############################################################
#
# Providers for TStrongObjectPtr
#
############################################################

class TStrongObjectPtrSynthProvider(ForwardingSynthProvider):
    def update(self):
        obj = self.valobj.GetChildMemberWithName('Object')
        addr = obj.GetValueAsUnsigned(0)
        if addr == 0:
            self.forward_to = lldb.SBValue()
        else:
            self.forward_to = TryGetSyntheticValue(obj.Dereference())

def TStrongObjectPtrSummaryProvider(valobj, dict):
    obj = valobj.GetNonSyntheticValue().GetChildMemberWithName('Object')
    addr = obj.GetValueAsUnsigned(0)
    if addr == 0: return 'nullptr'
    return '{...}'

############################################################
#
# Providers for TWeakObjectPtr
#
############################################################

def _resolve_weak_object_ptr(valobj):
    obj_index  = valobj.GetChildMemberWithName('ObjectIndex').GetValueAsSigned(0)
    serial_num = valobj.GetChildMemberWithName('ObjectSerialNumber').GetValueAsSigned(0)
    if serial_num == 0 or obj_index < 0: return None
    chunk_idx = obj_index >> 16  # NumElementsPerChunk = 65536
    item_idx  = obj_index & 0xFFFF
    objects = _GObjectArrayForDebugVisualizers.Dereference().GetChildMemberWithName('Objects')  # FUObjectItem**
    chunk = objects.GetChildAtIndex(chunk_idx, lldb.eDynamicDontRunTarget, True)  # FUObjectItem*
    item = chunk.GetChildAtIndex(item_idx, lldb.eDynamicDontRunTarget, True)      # FUObjectItem
    actual_serial = item.GetChildMemberWithName('SerialNumber').GetValueAsSigned(0)
    if actual_serial != serial_num: return None
    return item.GetChildMemberWithName('Object')

class TWeakObjectPtrSynthProvider(ForwardingSynthProvider):
    def update(self):
        obj_ptr = _resolve_weak_object_ptr(self.valobj)
        if obj_ptr is None or obj_ptr.GetValueAsUnsigned(0) == 0:
            self.forward_to = lldb.SBValue()
        else:
            self.forward_to = TryGetSyntheticValue(obj_ptr.Dereference())

def TWeakObjectPtrSummaryProvider(valobj, dict):
    raw = valobj.GetNonSyntheticValue()
    serial_num = raw.GetChildMemberWithName('ObjectSerialNumber').GetValueAsSigned(0)
    if serial_num == 0: return 'nullptr'
    if _resolve_weak_object_ptr(raw) is None: return 'expired'
    return '{...}'

############################################################
#
# Providers for TSharedPtr
#
############################################################

class TSharedPtrSynthProvider(ForwardingSynthProvider):
    def update(self):
        obj = self.valobj.GetChildMemberWithName('Object')
        addr = obj.GetValueAsUnsigned(0)
        if addr == 0:
            self.forward_to = lldb.SBValue()
        else:
            self.forward_to = TryGetSyntheticValue(obj.Dereference())

def TSharedPtrSummaryProvider(valobj, dict):
    obj = valobj.GetNonSyntheticValue().GetChildMemberWithName('Object')
    addr = obj.GetValueAsUnsigned(0)
    if addr == 0: return 'nullptr'
    return '{...}'

############################################################
#
# Providers for TWeakPtr
#
############################################################

def _resolve_weak_ptr(valobj):
    """Returns (object SBValue, is_expired).
    valobj must be the raw (non-synthetic) TWeakPtr."""
    weak_ref = valobj.GetChildMemberWithName('WeakReferenceCount')
    ref_ctrl_ptr_val = weak_ref.GetChildMemberWithName('ReferenceController')
    if ref_ctrl_ptr_val.GetValueAsUnsigned(0) == 0: return None, False
    shared_count_field = ref_ctrl_ptr_val.Dereference().GetChildMemberWithName('SharedReferenceCount')
    if shared_count_field.GetValueAsSigned(0) <= 0: return None, True
    obj = valobj.GetChildMemberWithName('Object')
    return obj, False

class TWeakPtrSynthProvider(ForwardingSynthProvider):
    def update(self):
        obj, expired = _resolve_weak_ptr(self.valobj)
        if obj is None or obj.GetValueAsUnsigned(0) == 0:
            self.forward_to = lldb.SBValue()
        else:
            self.forward_to = TryGetSyntheticValue(obj.Dereference())

def TWeakPtrSummaryProvider(valobj, dict):
    obj, expired = _resolve_weak_ptr(valobj.GetNonSyntheticValue())
    if expired: return 'expired'
    if obj is None: return 'nullptr'
    return '{...}'

############################################################
#
# Providers for TUniquePtr
#
############################################################

class TUniquePtrSynthProvider(ForwardingSynthProvider):
    def update(self):
        ptr = self.valobj.GetChildMemberWithName('Ptr')
        addr = ptr.GetValueAsUnsigned(0)
        if addr == 0:
            self.forward_to = lldb.SBValue()
        else:
            self.forward_to = TryGetSyntheticValue(ptr.Dereference())

def TUniquePtrSummaryProvider(valobj, dict):
    ptr = valobj.GetNonSyntheticValue().GetChildMemberWithName('Ptr')
    addr = ptr.GetValueAsUnsigned(0)
    if addr == 0: return 'nullptr'
    return '{...}'

############################################################
#
# Providers for UObject
#
############################################################

class UObjectSynthProvider(StoredSynthProvider):
    def update(self):
        self.children = []
        raw = self.valobj
        target = raw.GetTarget()
        debug_ptr = raw.GetChildMemberWithName('ClassPrivate') \
                       .GetChildMemberWithName('ObjectPtr') \
                       .GetChildMemberWithName('DebugPtr')
        class_val = target.CreateValueFromAddress(
            'Class', lldb.SBAddress(debug_ptr.GetLoadAddress(), target), debug_ptr.GetType())
        self.children.append(class_val)
        name_field = raw.GetChildMemberWithName('NamePrivate')
        name_val = target.CreateValueFromAddress(
            'Name', lldb.SBAddress(name_field.GetLoadAddress(), target), name_field.GetType())
        self.children.append(name_val)

def UObjectSummaryProvider(valobj, dict):
    raw = valobj.GetNonSyntheticValue()
    class_debug_ptr = raw.GetChildMemberWithName('ClassPrivate') \
                         .GetChildMemberWithName('ObjectPtr') \
                         .GetChildMemberWithName('DebugPtr')
    class_addr = class_debug_ptr.GetValueAsUnsigned(0)
    if not class_addr:
        return 'null'
    class_name = UEFNameSummaryProvider(
        class_debug_ptr.Dereference().GetChildMemberWithName('NamePrivate'), dict)
    instance_name = UEFNameSummaryProvider(
        raw.GetChildMemberWithName('NamePrivate'), dict)
    return 'Class=%s Name=%s' % (class_name, instance_name)

############################################################
#
# Providers for TArray
#
############################################################

def _resolve_tarray_data_addr(allocator):
    """Returns the base address of the element data for any TArray allocator."""
    # Heap allocator (TSizedHeapAllocator): has a Data pointer directly
    data = allocator.GetChildMemberWithName('Data')
    if data.IsValid():
        return data.GetValueAsUnsigned(0)
    # Inline allocator (TSizedInlineAllocator): prefer SecondaryData.Data, fall back to InlineData
    secondary = allocator.GetChildMemberWithName('SecondaryData')
    if secondary.IsValid():
        sec_data = secondary.GetChildMemberWithName('Data')
        if sec_data.IsValid() and sec_data.GetValueAsUnsigned(0) != 0:
            return sec_data.GetValueAsUnsigned(0)
    # Fixed allocator or inline allocator using inline storage
    inline = allocator.GetChildMemberWithName('InlineData')
    if inline.IsValid():
        return inline.GetLoadAddress()
    return 0

def _resolve_tarray(tarray_val):
    """Given an SBValue of a TArray, return (array_num, data_sbvalue) where
    data_sbvalue is a typed array of the TArray's element type.  Returns
    (0, None) if empty or unresolvable."""
    tarray_val = tarray_val.GetNonSyntheticValue()
    arr_type = tarray_val.GetType().GetUnqualifiedType()
    if arr_type.IsReferenceType():
        arr_type = arr_type.GetDereferencedType()
    elif arr_type.IsPointerType():
        arr_type = arr_type.GetPointeeType()
    if arr_type.GetNumberOfTemplateArguments() == 0: return 0, None
    array_num = tarray_val.GetChildMemberWithName('ArrayNum').GetValueAsSigned(0)
    if array_num <= 0: return 0, None
    data_addr = _resolve_tarray_data_addr(tarray_val.GetChildMemberWithName('AllocatorInstance'))
    if data_addr == 0: return 0, None
    elem_type = arr_type.GetTemplateArgumentType(0)
    if elem_type.GetByteSize() == 0: return 0, None
    target = tarray_val.GetTarget()
    data = target.CreateValueFromAddress(
        'data', lldb.SBAddress(data_addr, target), elem_type.GetArrayType(array_num))
    return array_num, data

class TArraySynthProvider(IndexedSynthProvider):
    def get_child_at_index(self, index):
        if index < 0 or index >= self.count:
            return lldb.SBValue()
        return self.data_array.GetChildAtIndex(index)

    def update(self):
        self.count, self.data_array = _resolve_tarray(self.valobj)

def TArraySummaryProvider(valobj, dict):
    raw = valobj.GetNonSyntheticValue()
    array_num = raw.GetChildMemberWithName('ArrayNum').GetValueAsSigned(0)
    if array_num < 0: return 'corrupted'
    return 'size=%d' % array_num

############################################################
#
# Provider for TBitArray
#
############################################################

def _bitarray_words(bit_array_val):
    """Given an SBValue of a TBitArray, return its backing uint32[] as an SBValue
    (or None if the array is empty/unallocated)."""
    bit_array_val = bit_array_val.GetNonSyntheticValue()
    num_bits = bit_array_val.GetChildMemberWithName('NumBits').GetValueAsSigned(0)
    if num_bits <= 0: return None
    allocator = bit_array_val.GetChildMemberWithName('AllocatorInstance')
    data_addr = _resolve_tarray_data_addr(allocator)
    if data_addr == 0: return None
    target = bit_array_val.GetTarget()
    uint32_type = target.GetBasicType(lldb.eBasicTypeUnsignedInt)
    num_words = (num_bits + 31) // 32
    return target.CreateValueFromAddress(
        'words', lldb.SBAddress(data_addr, target),
        uint32_type.GetArrayType(num_words))

class TBitArraySynthProvider(IndexedSynthProvider):
    def get_child_at_index(self, index):
        if index < 0 or index >= self.count:
            return lldb.SBValue()
        word = self.words.GetChildAtIndex(index)
        target = self.valobj.GetTarget()
        child = target.CreateValueFromAddress(
            '[%d]' % index, lldb.SBAddress(word.GetLoadAddress(), target),
            target.GetBasicType(lldb.eBasicTypeUnsignedInt))
        child.SetFormat(lldb.eFormatBinary)
        return child

    def update(self):
        self.count = 0
        self.words = _bitarray_words(self.valobj)
        if self.words is None: return
        self.count = self.words.GetNumChildren()

def TBitArraySummaryProvider(valobj, dict):
    num_bits = valobj.GetNonSyntheticValue().GetChildMemberWithName('NumBits').GetValueAsSigned(0)
    return 'size=%d' % num_bits

############################################################
#
# Provider for TSparseArray
#
############################################################

class TSparseArraySynthProvider(IndexedSynthProvider):
    def get_child_at_index(self, index):
        if index < 0 or index >= self.count:
            return lldb.SBValue()
        raw_idx = self.live_indices[index]
        slot = self.data_array.GetChildAtIndex(raw_idx)
        target = self.valobj.GetTarget()
        return target.CreateValueFromAddress(
            '[%d]' % index,
            lldb.SBAddress(slot.GetLoadAddress(), target),
            self.element_type)

    def update(self):
        self.count = 0
        self.live_indices = []
        self.data_array = None
        self.element_type = None
        array_num, data = _resolve_tarray(self.valobj.GetChildMemberWithName('Data'))
        if data is None: return
        words = _bitarray_words(self.valobj.GetChildMemberWithName('AllocationFlags'))
        if words is None: return
        self.data_array = data
        self.element_type = self.valobj.GetType().GetTemplateArgumentType(0)
        for i in range(array_num):
            word = words.GetChildAtIndex(i // 32).GetValueAsUnsigned(0)
            if (word >> (i % 32)) & 1:
                self.live_indices.append(i)
        self.count = len(self.live_indices)

def TSparseArraySummaryProvider(valobj, dict):
    raw = valobj.GetNonSyntheticValue()
    array_num = raw.GetChildMemberWithName('Data').GetNonSyntheticValue().GetChildMemberWithName('ArrayNum').GetValueAsSigned(0)
    num_free = raw.GetChildMemberWithName('NumFreeIndices').GetValueAsSigned(0)
    return 'size=%d' % (array_num - num_free)

############################################################
#
# Provider for TSet
#
############################################################

class TSetSynthProvider(ForwardingSynthProvider):
    def update(self):
        self.forward_to = TryGetSyntheticValue(self.valobj.GetChildMemberWithName('Elements'))

def TSetSummaryProvider(valobj, dict):
    elements = valobj.GetNonSyntheticValue().GetChildMemberWithName('Elements')
    return TSparseArraySummaryProvider(elements, dict)

############################################################
#
# Provider for TMap / TMultiMap
#
############################################################

class TMapSynthProvider(ForwardingSynthProvider):
    def update(self):
        self.forward_to = TryGetSyntheticValue(self.valobj.GetChildMemberWithName('Pairs').GetNonSyntheticValue().GetChildMemberWithName('Elements'))

def TMapSummaryProvider(valobj, dict):
    pairs = valobj.GetNonSyntheticValue().GetChildMemberWithName('Pairs')
    elements = pairs.GetNonSyntheticValue().GetChildMemberWithName('Elements')
    return TSparseArraySummaryProvider(elements, dict)

############################################################
#
# Provider for TTuple
#
############################################################

def _tuple_child_name(index):
    if index == 0: return 'Key'
    if index == 1: return 'Value'
    return 'Value%d' % index

class TTupleSynthProvider(StoredSynthProvider):
    def update(self):
        self.children = []
        raw = self.valobj.GetNonSyntheticValue()
        target = raw.GetTarget()
        container = raw
        first = raw.GetChildAtIndex(0)
        if first.IsValid() and 'TTupleBase<' in (first.GetType().GetName() or ''):
            container = first
        n = container.GetNumChildren()
        for i in range(n):
            base = container.GetChildAtIndex(i)
            field = base.GetChildAtIndex(0)
            if not field.IsValid(): continue
            child = target.CreateValueFromAddress(
                _tuple_child_name(i),
                lldb.SBAddress(field.GetLoadAddress(), target),
                field.GetType())
            self.children.append(child)

############################################################
#
# Provider for TSetElement
#
############################################################

class TSetElementSynthProvider(ForwardingSynthProvider):
    def update(self):
        self.forward_to = TryGetSyntheticValue(self.valobj.GetChildMemberWithName('Value'))

##############################################################
#
# The getattr which is built in to codelldb.value.Value
# fails to fetch synthetic values.  Monkey-patch it.
#
##############################################################

def _value_getattr(self, name):
    sbv = self._Value__sbvalue
    if sbv.GetType().IsPointerType():
        sbv = sbv.Dereference()
    child = sbv.GetChildMemberWithName(name)
    if not child.IsValid():
        for i in range(sbv.GetNumChildren()):
            c = sbv.GetChildAtIndex(i)
            if c.GetName() == name:
                child = c
                break
    if not child.IsValid():
        raise AttributeError("Attribute '%s' is not defined" % name)
    return codelldb.value.Value(child)

codelldb.value.Value.__getattr__ = _value_getattr

##############################################################
#
# The standard repr for SBValue will recurse infinitely if
# you provide useful synth providers for smart pointers.
# Monkey-patch it.
#
##############################################################

def _sbvalue_repr(self):
    n = self.GetNumChildren()
    if n == 0: return _sbvalue_display(self)
    parts = []
    for i in range(n):
        child = self.GetChildAtIndex(i)
        name = child.GetName() or str(i)
        parts.append('%s=%s\n' % (name, _sbvalue_display(child)))
    return ''.join(parts)

def _sbvalue_display(v):
    s = v.GetSummary()
    if s: return s
    s = v.GetValue()
    if s: return s
    if v.GetNumChildren() > 0: return '{...}'
    return '?'

lldb.SBValue.__repr__ = _sbvalue_repr

##############################################################
#
# Module Initialization
#
#############################################################

def _register_provider(cat, pattern, summary_fn=None, synth_cls=None):
    """Register summary and/or synthetic providers for a type pattern."""
    spec = lldb.SBTypeNameSpecifier(pattern, lldb.eFormatterMatchRegex)
    if summary_fn:
        cat.AddTypeSummary(spec, lldb.SBTypeSummary.CreateWithFunctionName(
            __name__ + '.' + summary_fn, lldb.eTypeOptionCascade))
    if synth_cls:
        cat.AddTypeSynthetic(spec, lldb.SBTypeSynthetic.CreateWithClassName(
            __name__ + '.' + synth_cls, lldb.eTypeOptionCascade))

def __lldb_init_module(debugger, dict):
    print("Running lldb_init_module")
    debugger.HandleCommand('target stop-hook add -P ' + __name__ + '._GlobalsInitStopHook')
    debugger.HandleCommand('type category delete ' + __name__)
    target = debugger.GetSelectedTarget()
    if target.IsValid():
        _init_globals(target)
    cat = debugger.CreateCategory(__name__)

    _register_provider(cat, '^FString$',                  summary_fn='UEFStringSummaryProvider')
    _register_provider(cat, '^FName$',                    summary_fn='UEFNameSummaryProvider')
    _register_provider(cat, '^TObjectPtr<.+>$',           summary_fn='TObjectPtrSummaryProvider',       synth_cls='TObjectPtrSynthProvider')
    _register_provider(cat, '^TStrongObjectPtr<.+>$',     summary_fn='TStrongObjectPtrSummaryProvider', synth_cls='TStrongObjectPtrSynthProvider')
    _register_provider(cat, '^TSharedPtr<.+>$',           summary_fn='TSharedPtrSummaryProvider',       synth_cls='TSharedPtrSynthProvider')
    _register_provider(cat, '^TSharedRef<.+>$',           summary_fn='TSharedPtrSummaryProvider',       synth_cls='TSharedPtrSynthProvider')
    _register_provider(cat, '^TWeakPtr<.+>$',             summary_fn='TWeakPtrSummaryProvider',         synth_cls='TWeakPtrSynthProvider')
    _register_provider(cat, '^TWeakObjectPtr<.+>$',       summary_fn='TWeakObjectPtrSummaryProvider',   synth_cls='TWeakObjectPtrSynthProvider')
    _register_provider(cat, '^FWeakObjectPtr$',           summary_fn='TWeakObjectPtrSummaryProvider',   synth_cls='TWeakObjectPtrSynthProvider')
    _register_provider(cat, '^TArray<.+>$',               summary_fn='TArraySummaryProvider',            synth_cls='TArraySynthProvider')
    _register_provider(cat, '^TBitArray<.+>$',            summary_fn='TBitArraySummaryProvider',         synth_cls='TBitArraySynthProvider')
    _register_provider(cat, '^TSparseArray<.+>$',         summary_fn='TSparseArraySummaryProvider',      synth_cls='TSparseArraySynthProvider')
    _register_provider(cat, '^TSetElement<.+>$',           synth_cls='TSetElementSynthProvider')
    _register_provider(cat, '^TTuple<.+>$',              synth_cls='TTupleSynthProvider')
    _register_provider(cat, '^TSet<.+>$',                summary_fn='TSetSummaryProvider',               synth_cls='TSetSynthProvider')
    _register_provider(cat, '^TMap<.+>$',                summary_fn='TMapSummaryProvider',               synth_cls='TMapSynthProvider')
    _register_provider(cat, '^TMultiMap<.+>$',           summary_fn='TMapSummaryProvider',               synth_cls='TMapSynthProvider')
    _register_provider(cat, '^UObject$',                  summary_fn='UObjectSummaryProvider',          synth_cls='UObjectSynthProvider')

    for class_name in (
        'AActor', 'APawn', 'ACharacter', 'APlayerController', 'AGameModeBase',
        'UActorComponent', 'USceneComponent', 'UPrimitiveComponent',
        'UWidget', 'UUserWidget', 'UWorld', 'ULevel', 'UGameInstance'):
        _register_provider(cat, '^%s$' % class_name, summary_fn='UObjectSummaryProvider')
    
    cat.SetEnabled(True)
