반응형

언리얼 치트의 경우는 컴파일러가 직접 호출하는게 아니라 문자열을 런타임에 파싱해서 파라미터를 조립하는 방식

 

치트매니저에 함수들은 UFUNCTION(exec) 라는 매크로가 붙게되는데 이 경우 UHT 가 ProcessConsoleExec 함수를 통해 exec 함수를 탐색한다.

그리고 CallFunctionByNameWithArguments 함수에서 파라미터를 파싱하게 된다.

또한 내부에서는 FindFunction 이라는 함수를 통해 함수포인터를 실행하는 방식

 

UClass 의 이런 멤버변수가 있다.

TMap<FName, TObjectPtr<UFunction>> FuncMap;

여기에 <함수이름, 함수 포인터> 를 저장하게 되는데 콘솔 명령어를 입력하면 해당 함수포인터를 실행하는 방식이다.

 

 

컴파일러 직접 호출

  • 함수 이름, 파라미터 타입을 컴파일 타임에 알아야 함
  • 즉 "Slomo" 라는 문자열을 받아서 호출하는 게 불가능

 

리플렉션 호출

  • 런타임에 문자열만 있으면 함수 찾아서 호출 가능
  • 콘솔에서 문자열 입력받아서 실행하는 치트가 가능해짐

 

에디터에서 매개변수가 기본값으로 지정되어있을때 잘 실행되었던게 패키징에서는 매개변수 생략하면 동작하지않거나 아예 이상한 값으로 가는 문제가 있었다.

UFUNCTION(exec)
void SpawnTrainingBot(int32 bHero = 1, int32 InLevel = 1, uint8 TeamID = 100, float Distance = 500.0f);

SpawnTrainingBot 1 -1 → 안불림
SpawnTrainingBot 1 -1 100 → 불림
SpawnTrainingBot 1 -1 100 500 → 불림



UFUNCTION(Exec)
void ApplyAbnormalGameplayEffectToSelfByKey(FName Key, float duration = 0.f, float value = 0.f );

ApplyAbnormalGameplayEffectToSelfByKey Stun → 불림



UFUNCTION(Exec)
void SetSkillLevel(int32 SkillNumber, int32 Level = 1);

SetSkillLevel 5 → 안불림

 

ScriptCore.cpp

CallFunctionByNameWithArguments 함수 내부이다.

Strcmp 함수 = 두 문자열이 같은지 판별하는 함수

 

ArgStr (치트 함수 파라미터) 가 비어있으면 기본 값 메타데이터 (CPP_Default_Level) 을 사용하는데 이 경로가 Editor 에서만 동작하는것

 

그리고 패키징에선 아래 if문 블록에서 ImportText_InContainer 가 들어가게 되고 타입마다 다른 구조체의 함수를 타게된다.

const TCHAR* FNumericProperty::ImportText_Internal(
    const TCHAR* Buffer,
    void* ContainerOrPropertyPtr,
    EPropertyPointerType PropertyPointerType,
    UObject* Parent,
    int32 PortFlags,
    FOutputDevice* ErrorText) const
{
    if (Buffer != NULL)
    {
        const TCHAR* Start = Buffer;

        if (IsInteger())
        {
            if (FChar::IsAlpha(*Buffer))
            {
                int64 EnumValue = UEnum::ParseEnum(Buffer);
                if (EnumValue != INDEX_NONE)
                {
                    if (PropertyPointerType == EPropertyPointerType::Container && HasSetter())
                    {
                        SetValue_InContainer(ContainerOrPropertyPtr, &EnumValue);
                    }
                    else
                    {
                        SetIntPropertyValue(PointerToValuePtr(ContainerOrPropertyPtr, PropertyPointerType), EnumValue);
                    }
                    return Buffer;
                }
                else
                {
                    return NULL;
                }
            }
            else
            {
                if (!FCString::Strnicmp(Start, TEXT("0x"), 2))
                {
                    Buffer += 2;
                    while (Buffer && (FParse::HexDigit(*Buffer) != 0 || *Buffer == TCHAR('0')))
                    {
                        Buffer++;
                    }
                }
                else
                {
                    while (Buffer && (*Buffer == TCHAR('-') || *Buffer == TCHAR('+')))
                    {
                        Buffer++;
                    }
                    while (Buffer && FChar::IsDigit(*Buffer))
                    {
                        Buffer++;
                    }
                }

                if (Start == Buffer)
                {
                    // import failure
                    return NULL;
                }
            }
        }
        else
        {
            check(IsFloatingPoint());

            // floating point
            while (*Buffer == TCHAR('+') || *Buffer == TCHAR('-') || *Buffer == TCHAR('.')
                || (*Buffer >= TCHAR('0') && *Buffer <= TCHAR('9')))
            {
                Buffer++;
            }
            if (*Buffer == TCHAR('f') || *Buffer == TCHAR('F'))
            {
                Buffer++;
            }
        }

        if (PropertyPointerType == EPropertyPointerType::Container && HasSetter())
        {
            SetNumericPropertyValueFromString_InContainer(ContainerOrPropertyPtr, Start);
        }
        else
        {
            SetNumericPropertyValueFromString(PointerToValuePtr(ContainerOrPropertyPtr, PropertyPointerType), Start);
        }
    }
    return Buffer;
}

 

ImportText_InContainer -> ImportText_Internal -> FNumericProperty::ImportText_Internal 내부에서 정수와 소수의 분기점을 타게되는데

 

정수의 경우 Null 을 반환하고

소수의 경우 0 을 반환하기때문에

 

치트의 정수 매개변수를 생략하였을땐은 함수 자체를 파싱못하는것이고, 소수의 매개변수를 생략하였을때는 0 으로 들어가게된다.

 

패키징에서 안되면 에디터에서도 안되게끔 하는게 맞지않나 싶었다.

반응형

'언리얼' 카테고리의 다른 글

SetViewTarget  (0) 2026.03.12
Zen Storage  (0) 2025.12.31
DerivedDataBackendGraph  (0) 2025.12.24
Deproject 위젯 -> 월드  (1) 2025.11.18
World Widget Depth Test  (0) 2025.09.24

+ Recent posts