2026-10-01 17:24:20 +00:00
|
|
|
// Portage UE 5.5 - voir LPTestStringCompatCommandlet.h
|
|
|
|
|
|
|
|
|
|
#include "LPTestStringCompatCommandlet.h"
|
|
|
|
|
|
|
|
|
|
#include "Kismet/KismetStringLibrary.h"
|
|
|
|
|
#include "LPStringCompat.h"
|
|
|
|
|
#include <wchar.h>
|
|
|
|
|
|
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogLPTestStringCompat, Log, All);
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
2026-10-19 20:57:42 +00:00
|
|
|
/** TCString<TCHAR>::Stristr, UE4 (CString.h l. 622-650), transcrit a l'identique. */
|
2026-10-01 17:24:20 +00:00
|
|
|
const TCHAR* Stristr427(const TCHAR* Str, const TCHAR* Find)
|
|
|
|
|
{
|
|
|
|
|
if (Find == nullptr || Str == nullptr)
|
|
|
|
|
{
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
TCHAR FindInitial = FChar::ToUpper(*Find);
|
|
|
|
|
int32 Length = FCString::Strlen(Find++) - 1;
|
|
|
|
|
TCHAR StrChar = *Str++;
|
|
|
|
|
while (StrChar)
|
|
|
|
|
{
|
|
|
|
|
StrChar = FChar::ToUpper(StrChar);
|
|
|
|
|
if (StrChar == FindInitial && !FCString::Strnicmp(Str, Find, Length))
|
|
|
|
|
{
|
|
|
|
|
return Str - 1;
|
|
|
|
|
}
|
|
|
|
|
StrChar = *Str++;
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-19 20:57:42 +00:00
|
|
|
/** FString::Find, UE4 (String.cpp l. 180-227), transcrit a l'identique. */
|
2026-10-01 17:24:20 +00:00
|
|
|
int32 Find427(const FString& This, const TCHAR* SubStr, ESearchCase::Type SearchCase, ESearchDir::Type SearchDir, int32 StartPosition)
|
|
|
|
|
{
|
|
|
|
|
if (SubStr == nullptr)
|
|
|
|
|
{
|
|
|
|
|
return INDEX_NONE;
|
|
|
|
|
}
|
|
|
|
|
if (SearchDir == ESearchDir::FromStart)
|
|
|
|
|
{
|
|
|
|
|
const TCHAR* Start = *This;
|
|
|
|
|
if (StartPosition != INDEX_NONE && This.Len() > 0)
|
|
|
|
|
{
|
|
|
|
|
Start += FMath::Clamp(StartPosition, 0, This.Len() - 1);
|
|
|
|
|
}
|
2026-10-19 20:57:42 +00:00
|
|
|
// FCString::Strstr UE4 sous Windows : FWindowsPlatformString::Strstr = _tcsstr (wcsstr).
|
2026-10-01 17:24:20 +00:00
|
|
|
const TCHAR* Tmp = SearchCase == ESearchCase::IgnoreCase
|
|
|
|
|
? Stristr427(Start, SubStr)
|
|
|
|
|
: (const TCHAR*)wcsstr((const wchar_t*)Start, (const wchar_t*)SubStr);
|
|
|
|
|
return Tmp ? UE_PTRDIFF_TO_INT32(Tmp - *This) : INDEX_NONE;
|
|
|
|
|
}
|
|
|
|
|
if (SearchCase == ESearchCase::IgnoreCase)
|
|
|
|
|
{
|
|
|
|
|
return Find427(This.ToUpper(), *FString(SubStr).ToUpper(), ESearchCase::CaseSensitive, SearchDir, StartPosition);
|
|
|
|
|
}
|
|
|
|
|
const int32 SearchStringLength = FMath::Max(1, FCString::Strlen(SubStr));
|
|
|
|
|
if (StartPosition == INDEX_NONE || StartPosition >= This.Len())
|
|
|
|
|
{
|
|
|
|
|
StartPosition = This.Len();
|
|
|
|
|
}
|
2026-10-19 20:57:42 +00:00
|
|
|
// En UE4, (*this)[i+j] pouvait lire le '\0' final sans controle : on lit le tampon brut.
|
2026-10-01 17:24:20 +00:00
|
|
|
const TCHAR* Data = *This;
|
|
|
|
|
for (int32 i = StartPosition - SearchStringLength; i >= 0; i--)
|
|
|
|
|
{
|
|
|
|
|
int32 j;
|
|
|
|
|
for (j = 0; SubStr[j]; j++)
|
|
|
|
|
{
|
|
|
|
|
if (Data[i + j] != SubStr[j])
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!SubStr[j])
|
|
|
|
|
{
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return INDEX_NONE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ULPTestStringCompatCommandlet::ULPTestStringCompatCommandlet()
|
|
|
|
|
{
|
|
|
|
|
IsClient = false;
|
|
|
|
|
IsServer = false;
|
|
|
|
|
IsEditor = true;
|
|
|
|
|
LogToConsole = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 ULPTestStringCompatCommandlet::Main(const FString& Params)
|
|
|
|
|
{
|
|
|
|
|
const TArray<FString> Strings = { TEXT(""), TEXT("a"), TEXT("A"), TEXT("abc"), TEXT("ABC"), TEXT("aXbXc"), TEXT("round, oval"), TEXT(" "), TEXT("teen"), TEXT("Daz") };
|
|
|
|
|
const TArray<FString> Subs = { TEXT(""), TEXT("a"), TEXT("A"), TEXT("b"), TEXT("X"), TEXT("bc"), TEXT("abc"), TEXT("abcd"), TEXT(", "), TEXT(" "), TEXT("oval"), TEXT("TEEN"), TEXT("daz") };
|
|
|
|
|
const TArray<int32> Starts = { -1, 0, 1, 2, 5, 50 };
|
|
|
|
|
|
|
|
|
|
int32 Cases = 0, CompatMismatch = 0, EngineMismatch = 0;
|
|
|
|
|
for (const FString& S : Strings)
|
|
|
|
|
{
|
|
|
|
|
for (const FString& Sub : Subs)
|
|
|
|
|
{
|
|
|
|
|
for (int32 CaseIdx = 0; CaseIdx < 2; ++CaseIdx)
|
|
|
|
|
{
|
|
|
|
|
for (int32 DirIdx = 0; DirIdx < 2; ++DirIdx)
|
|
|
|
|
{
|
|
|
|
|
for (const int32 Start : Starts)
|
|
|
|
|
{
|
|
|
|
|
const ESearchCase::Type Case = CaseIdx ? ESearchCase::CaseSensitive : ESearchCase::IgnoreCase;
|
|
|
|
|
const ESearchDir::Type Dir = DirIdx ? ESearchDir::FromEnd : ESearchDir::FromStart;
|
|
|
|
|
++Cases;
|
|
|
|
|
|
|
|
|
|
const int32 Ref = Find427(S, *Sub, Case, Dir, Start);
|
|
|
|
|
const int32 Compat = ULPStringCompat::FindUE4(S, Sub, Case, Dir, Start);
|
|
|
|
|
const int32 Engine = S.Find(Sub, Case, Dir, Start);
|
|
|
|
|
|
|
|
|
|
if (Compat != Ref)
|
|
|
|
|
{
|
|
|
|
|
++CompatMismatch;
|
2026-10-19 20:57:42 +00:00
|
|
|
UE_LOG(LogLPTestStringCompat, Error, TEXT("COMPAT Find(\"%s\", \"%s\", %s, %s, %d) : herite(UE4)=%d compat=%d"),
|
2026-10-01 17:24:20 +00:00
|
|
|
*S, *Sub, CaseIdx ? TEXT("Case") : TEXT("Ignore"), DirIdx ? TEXT("FromEnd") : TEXT("FromStart"), Start, Ref, Compat);
|
|
|
|
|
}
|
|
|
|
|
if (Engine != Ref)
|
|
|
|
|
{
|
|
|
|
|
++EngineMismatch;
|
2026-10-19 20:57:42 +00:00
|
|
|
UE_LOG(LogLPTestStringCompat, Display, TEXT("moteur Find(\"%s\", \"%s\", %s, %s, %d) : herite(UE4)=%d moteur=%d"),
|
2026-10-01 17:24:20 +00:00
|
|
|
*S, *Sub, CaseIdx ? TEXT("Case") : TEXT("Ignore"), DirIdx ? TEXT("FromEnd") : TEXT("FromStart"), Start, Ref, Engine);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fonctions Blueprint : uniquement avec la position de depart par defaut.
|
|
|
|
|
if (Start == -1)
|
|
|
|
|
{
|
|
|
|
|
const bool bUseCase = CaseIdx == 1, bFromEnd = DirIdx == 1;
|
|
|
|
|
if (ULPStringCompat::Contains(S, Sub, bUseCase, bFromEnd) != (Ref != INDEX_NONE)
|
|
|
|
|
|| ULPStringCompat::FindSubstring(S, Sub, bUseCase, bFromEnd) != Ref)
|
|
|
|
|
{
|
|
|
|
|
++CompatMismatch;
|
|
|
|
|
UE_LOG(LogLPTestStringCompat, Error, TEXT("COMPAT Contains/FindSubstring(\"%s\", \"%s\") ecart"), *S, *Sub);
|
|
|
|
|
}
|
|
|
|
|
FString L1, R1, L2, R2;
|
|
|
|
|
const bool B1 = ULPStringCompat::Split(S, Sub, L1, R1, Case, Dir);
|
|
|
|
|
const bool B2 = Ref >= 0;
|
|
|
|
|
if (B1 != B2 || (B2 && (L1 != S.Left(Ref) || R1 != S.Mid(Ref + Sub.Len()))))
|
|
|
|
|
{
|
|
|
|
|
++CompatMismatch;
|
|
|
|
|
UE_LOG(LogLPTestStringCompat, Error, TEXT("COMPAT Split(\"%s\", \"%s\") ecart"), *S, *Sub);
|
|
|
|
|
}
|
2026-10-19 20:57:42 +00:00
|
|
|
// Cas Blueprint par defaut, moteur courant contre comportement herite(UE4) : documente l'ecart vise.
|
2026-10-01 17:24:20 +00:00
|
|
|
if (!bUseCase && !bFromEnd && UKismetStringLibrary::Contains(S, Sub) != (Ref != INDEX_NONE))
|
|
|
|
|
{
|
2026-10-19 20:57:42 +00:00
|
|
|
UE_LOG(LogLPTestStringCompat, Display, TEXT("moteur Contains(\"%s\", \"%s\") : herite(UE4)=%d moteur=%d"),
|
2026-10-01 17:24:20 +00:00
|
|
|
*S, *Sub, Ref != INDEX_NONE, UKismetStringLibrary::Contains(S, Sub));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-19 20:57:42 +00:00
|
|
|
UE_LOG(LogLPTestStringCompat, Warning, TEXT("=== %d cas : compat != herite(UE4) : %d ; moteur != herite(UE4) : %d ==="), Cases, CompatMismatch, EngineMismatch);
|
2026-10-01 17:24:20 +00:00
|
|
|
return CompatMismatch > 0 ? 1 : 0;
|
|
|
|
|
}
|