501 lines
20 KiB
C++
501 lines
20 KiB
C++
// Lot MAPDL1 (MAPDL1-SPEC-03, P1). Voir LPImportLegacyStreetMapCommandlet.h.
|
|
|
|
#include "LPImportLegacyStreetMapCommandlet.h"
|
|
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "Misc/Paths.h"
|
|
#include "StreetMap.h"
|
|
#include "UObject/EnumProperty.h"
|
|
#include "UObject/Package.h"
|
|
#include "UObject/SavePackage.h"
|
|
#include "UObject/UnrealType.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogLPImportLegacyStreetMap, Log, All);
|
|
|
|
namespace LPLegacyStreetMap
|
|
{
|
|
/** Lecteur petit-boutiste borne : toute lecture hors du tampon pose bError et rend une valeur nulle. */
|
|
struct FReader
|
|
{
|
|
const TArray<uint8>& Data;
|
|
int64 Pos = 0;
|
|
bool bError = false;
|
|
|
|
FReader(const TArray<uint8>& InData, int64 InPos) : Data(InData), Pos(InPos) {}
|
|
|
|
bool Has(int64 Count)
|
|
{
|
|
if (bError || Count < 0 || Pos < 0 || Pos + Count > Data.Num())
|
|
{
|
|
bError = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
template <typename T> T Read()
|
|
{
|
|
T Value{};
|
|
if (Has(sizeof(T)))
|
|
{
|
|
FMemory::Memcpy(&Value, Data.GetData() + Pos, sizeof(T));
|
|
Pos += sizeof(T);
|
|
}
|
|
return Value;
|
|
}
|
|
void Skip(int64 Count) { if (Has(Count)) { Pos += Count; } }
|
|
/** FString UE4 : longueur positive = ANSI, negative = UTF-16, terminateur nul compris. */
|
|
FString Str()
|
|
{
|
|
const int32 Len = Read<int32>();
|
|
FString Out;
|
|
if (Len > 0 && Has(Len))
|
|
{
|
|
for (int32 Index = 0; Index < Len - 1; ++Index) { Out.AppendChar(TCHAR(Data[Pos + Index])); }
|
|
Pos += Len;
|
|
}
|
|
else if (Len < 0 && Has(int64(-Len) * 2))
|
|
{
|
|
for (int32 Index = 0; Index < -Len - 1; ++Index) { Out.AppendChar(TCHAR(Data[Pos + 2 * Index] | (Data[Pos + 2 * Index + 1] << 8))); }
|
|
Pos += int64(-Len) * 2;
|
|
}
|
|
return Out;
|
|
}
|
|
};
|
|
|
|
/** Etiquette de propriete UE4 (FPropertyTag, format 4.19-4.27). */
|
|
struct FTag
|
|
{
|
|
FString Name;
|
|
FString Type;
|
|
int32 Size = 0;
|
|
int32 ArrayIndex = 0;
|
|
FString StructName;
|
|
FString InnerType;
|
|
FString EnumName;
|
|
uint8 BoolVal = 0;
|
|
};
|
|
|
|
/** Etat de la lecture : table de noms et constats du rapport. */
|
|
struct FContext
|
|
{
|
|
TArray<FString> Names;
|
|
TMap<FString, int32> Unknown;
|
|
TMap<FString, int32> Mismatch;
|
|
FString Error;
|
|
|
|
FString Name(FReader& R) const
|
|
{
|
|
const int32 Index = R.Read<int32>();
|
|
const int32 Number = R.Read<int32>();
|
|
if (!Names.IsValidIndex(Index))
|
|
{
|
|
R.bError = true;
|
|
return FString();
|
|
}
|
|
return Number == 0 ? Names[Index] : FString::Printf(TEXT("%s_%d"), *Names[Index], Number - 1);
|
|
}
|
|
bool Fail(FReader& R, const FString& Message)
|
|
{
|
|
if (Error.IsEmpty()) { Error = FString::Printf(TEXT("%s (octet %lld)"), *Message, R.Pos); }
|
|
R.bError = true;
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/** Lit une etiquette ; faux pour "None" (fin de liste) ou sur erreur. */
|
|
bool ReadTag(FReader& R, FContext& Ctx, FTag& Out)
|
|
{
|
|
Out = FTag();
|
|
Out.Name = Ctx.Name(R);
|
|
if (R.bError || Out.Name == TEXT("None")) { return false; }
|
|
Out.Type = Ctx.Name(R);
|
|
Out.Size = R.Read<int32>();
|
|
Out.ArrayIndex = R.Read<int32>();
|
|
if (Out.Type == TEXT("StructProperty")) { Out.StructName = Ctx.Name(R); R.Skip(16); }
|
|
else if (Out.Type == TEXT("BoolProperty")) { Out.BoolVal = R.Read<uint8>(); }
|
|
else if (Out.Type == TEXT("ByteProperty") || Out.Type == TEXT("EnumProperty")) { Out.EnumName = Ctx.Name(R); }
|
|
else if (Out.Type == TEXT("ArrayProperty") || Out.Type == TEXT("SetProperty")) { Out.InnerType = Ctx.Name(R); }
|
|
else if (Out.Type == TEXT("MapProperty")) { Out.InnerType = Ctx.Name(R); Ctx.Name(R); }
|
|
if (R.Read<uint8>() != 0) { R.Skip(16); }
|
|
return !R.bError && Out.Size >= 0;
|
|
}
|
|
|
|
bool ReadProperties(FReader& R, int64 Limit, const UStruct* Struct, void* Container, FContext& Ctx, const FString& Where);
|
|
|
|
/** Valeur d'enumeration (nom court ou qualifie) -> valeur numerique. */
|
|
int64 EnumValue(const UEnum* Enum, const FString& Value)
|
|
{
|
|
if (!Enum) { return INDEX_NONE; }
|
|
int64 Result = Enum->GetValueByNameString(Value);
|
|
if (Result == INDEX_NONE && !Value.Contains(TEXT("::")))
|
|
{
|
|
Result = Enum->GetValueByNameString(Enum->GetName() + TEXT("::") + Value);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Lit une valeur de type Type et l'ecrit dans Prop (ValuePtr) si les types sont compatibles ; Prop nul ou incompatible :
|
|
* la valeur est consommee sans etre ecrite. Faux sur erreur de format.
|
|
*/
|
|
bool ReadValue(FReader& R, int64 Limit, const FTag& Tag, FProperty* Prop, void* ValuePtr, FContext& Ctx, const FString& Where)
|
|
{
|
|
auto Mismatch = [&]() { if (Prop) { Ctx.Mismatch.FindOrAdd(Where + TEXT(".") + Tag.Name + TEXT(" (") + Tag.Type + TEXT(")"))++; } };
|
|
const FString& Type = Tag.Type;
|
|
|
|
if (Type == TEXT("BoolProperty"))
|
|
{
|
|
if (FBoolProperty* P = CastField<FBoolProperty>(Prop)) { P->SetPropertyValue(ValuePtr, Tag.BoolVal != 0); } else { Mismatch(); }
|
|
return true;
|
|
}
|
|
if (Type == TEXT("IntProperty") || Type == TEXT("FloatProperty") || Type == TEXT("DoubleProperty"))
|
|
{
|
|
const bool bInt = Type == TEXT("IntProperty");
|
|
const double AsDouble = bInt ? double(R.Read<int32>()) : (Type == TEXT("FloatProperty") ? double(R.Read<float>()) : R.Read<double>());
|
|
FNumericProperty* P = CastField<FNumericProperty>(Prop);
|
|
if (P && P->IsFloatingPoint()) { P->SetFloatingPointPropertyValue(ValuePtr, AsDouble); }
|
|
else if (P && P->IsInteger() && bInt && !P->IsEnum()) { P->SetIntPropertyValue(ValuePtr, int64(AsDouble)); }
|
|
else { Mismatch(); }
|
|
return !R.bError;
|
|
}
|
|
if (Type == TEXT("StrProperty"))
|
|
{
|
|
const FString Value = R.Str();
|
|
if (FStrProperty* P = CastField<FStrProperty>(Prop)) { *static_cast<FString*>(ValuePtr) = Value; } else { Mismatch(); }
|
|
return !R.bError;
|
|
}
|
|
if (Type == TEXT("NameProperty"))
|
|
{
|
|
const FString Value = Ctx.Name(R);
|
|
if (FNameProperty* P = CastField<FNameProperty>(Prop)) { *static_cast<FName*>(ValuePtr) = FName(*Value); } else { Mismatch(); }
|
|
return !R.bError;
|
|
}
|
|
if (Type == TEXT("ByteProperty") || Type == TEXT("EnumProperty"))
|
|
{
|
|
const bool bNamed = Type == TEXT("EnumProperty") || (!Tag.EnumName.IsEmpty() && Tag.EnumName != TEXT("None"));
|
|
const FString Named = bNamed ? Ctx.Name(R) : FString();
|
|
const int64 Raw = bNamed ? INDEX_NONE : int64(R.Read<uint8>());
|
|
if (R.bError) { return false; }
|
|
if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
|
|
{
|
|
const int64 Value = bNamed ? EnumValue(ByteProp->Enum, Named) : Raw;
|
|
if (Value == INDEX_NONE) { Ctx.Mismatch.FindOrAdd(Where + TEXT(".") + Tag.Name + TEXT(" = ") + Named)++; }
|
|
else { ByteProp->SetPropertyValue(ValuePtr, uint8(Value)); }
|
|
}
|
|
else if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
|
|
{
|
|
const int64 Value = bNamed ? EnumValue(EnumProp->GetEnum(), Named) : Raw;
|
|
if (Value == INDEX_NONE) { Ctx.Mismatch.FindOrAdd(Where + TEXT(".") + Tag.Name + TEXT(" = ") + Named)++; }
|
|
else { EnumProp->GetUnderlyingProperty()->SetIntPropertyValue(ValuePtr, Value); }
|
|
}
|
|
else { Mismatch(); }
|
|
return true;
|
|
}
|
|
if (Type == TEXT("StructProperty"))
|
|
{
|
|
FStructProperty* P = CastField<FStructProperty>(Prop);
|
|
if (Tag.StructName == TEXT("Vector2D") || Tag.StructName == TEXT("Vector"))
|
|
{
|
|
// Structures natives UE4 : flottants simples, sans etiquettes.
|
|
const bool b2D = Tag.StructName == TEXT("Vector2D");
|
|
const float X = R.Read<float>(), Y = R.Read<float>(), Z = b2D ? 0.f : R.Read<float>();
|
|
if (P && b2D && P->Struct == TBaseStructure<FVector2D>::Get()) { *static_cast<FVector2D*>(ValuePtr) = FVector2D(X, Y); }
|
|
else if (P && !b2D && P->Struct == TBaseStructure<FVector>::Get()) { *static_cast<FVector*>(ValuePtr) = FVector(X, Y, Z); }
|
|
else { Mismatch(); }
|
|
return !R.bError;
|
|
}
|
|
if (P && P->Struct->GetName() != Tag.StructName) { Mismatch(); P = nullptr; }
|
|
return ReadProperties(R, Limit, P ? P->Struct : nullptr, P ? ValuePtr : nullptr, Ctx, Where + TEXT(".") + Tag.StructName);
|
|
}
|
|
if (Type == TEXT("ArrayProperty"))
|
|
{
|
|
const int32 Count = R.Read<int32>();
|
|
if (R.bError || Count < 0 || Count > Limit - R.Pos) { return Ctx.Fail(R, FString::Printf(TEXT("tableau %s.%s : nombre %d invalide"), *Where, *Tag.Name, Count)); }
|
|
FArrayProperty* P = CastField<FArrayProperty>(Prop);
|
|
if (Prop && !P) { Mismatch(); }
|
|
FTag Inner;
|
|
Inner.Name = Tag.Name;
|
|
Inner.Type = Tag.InnerType;
|
|
if (Tag.InnerType == TEXT("StructProperty"))
|
|
{
|
|
// Tableau de structures : une etiquette interieure, puis les elements.
|
|
FTag ElementTag;
|
|
if (!ReadTag(R, Ctx, ElementTag)) { return Ctx.Fail(R, FString::Printf(TEXT("tableau %s.%s : etiquette interieure illisible"), *Where, *Tag.Name)); }
|
|
Inner.StructName = ElementTag.StructName;
|
|
}
|
|
else if (Tag.InnerType == TEXT("BoolProperty"))
|
|
{
|
|
// Booleens d'un tableau : un octet par element.
|
|
Inner.Type = TEXT("ByteProperty");
|
|
}
|
|
if (P)
|
|
{
|
|
FScriptArrayHelper Helper(P, ValuePtr);
|
|
Helper.EmptyValues();
|
|
Helper.AddValues(Count);
|
|
for (int32 Index = 0; Index < Count; ++Index)
|
|
{
|
|
if (!ReadValue(R, Limit, Inner, P->Inner, Helper.GetRawPtr(Index), Ctx, Where + TEXT(".") + Tag.Name)) { return false; }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int32 Index = 0; Index < Count; ++Index)
|
|
{
|
|
if (!ReadValue(R, Limit, Inner, nullptr, nullptr, Ctx, Where + TEXT(".") + Tag.Name)) { return false; }
|
|
}
|
|
}
|
|
return !R.bError;
|
|
}
|
|
return Ctx.Fail(R, FString::Printf(TEXT("type %s non pris en charge (%s.%s) dans un tableau"), *Type, *Where, *Tag.Name));
|
|
}
|
|
|
|
/** Liste de proprietes etiquetees jusqu'a "None". Struct nul : tout est saute par la taille de l'etiquette. */
|
|
bool ReadProperties(FReader& R, int64 Limit, const UStruct* Struct, void* Container, FContext& Ctx, const FString& Where)
|
|
{
|
|
FTag Tag;
|
|
while (ReadTag(R, Ctx, Tag))
|
|
{
|
|
const int64 ValueStart = R.Pos;
|
|
const int64 ValueEnd = ValueStart + Tag.Size;
|
|
if (ValueEnd > Limit) { return Ctx.Fail(R, FString::Printf(TEXT("%s.%s : taille %d hors de l'export"), *Where, *Tag.Name, Tag.Size)); }
|
|
|
|
FProperty* Prop = Struct ? FindFProperty<FProperty>(Struct, FName(*Tag.Name)) : nullptr;
|
|
if (Struct && !Prop) { Ctx.Unknown.FindOrAdd(Where + TEXT(".") + Tag.Name)++; }
|
|
if (Prop && Tag.ArrayIndex >= Prop->ArrayDim) { Ctx.Mismatch.FindOrAdd(Where + TEXT(".") + Tag.Name + TEXT(" (indice)"))++; Prop = nullptr; }
|
|
|
|
if (Prop && Container)
|
|
{
|
|
if (!ReadValue(R, ValueEnd, Tag, Prop, Prop->ContainerPtrToValuePtr<void>(Container, Tag.ArrayIndex), Ctx, Where)) { return false; }
|
|
if (Tag.Type != TEXT("BoolProperty") && R.Pos != ValueEnd)
|
|
{
|
|
return Ctx.Fail(R, FString::Printf(TEXT("%s.%s : %lld octets lus pour %d annonces"), *Where, *Tag.Name, R.Pos - ValueStart, Tag.Size));
|
|
}
|
|
}
|
|
R.Pos = ValueEnd;
|
|
}
|
|
return !R.bError;
|
|
}
|
|
|
|
/** Entree de la table d'imports. */
|
|
struct FImport
|
|
{
|
|
FString ClassName;
|
|
FString ObjectName;
|
|
};
|
|
}
|
|
|
|
ULPImportLegacyStreetMapCommandlet::ULPImportLegacyStreetMapCommandlet()
|
|
{
|
|
IsClient = false;
|
|
IsServer = false;
|
|
IsEditor = true;
|
|
LogToConsole = true;
|
|
}
|
|
|
|
int32 ULPImportLegacyStreetMapCommandlet::Main(const FString& Params)
|
|
{
|
|
using namespace LPLegacyStreetMap;
|
|
|
|
TArray<FString> Tokens;
|
|
TArray<FString> Switches;
|
|
TMap<FString, FString> ParamsMap;
|
|
ParseCommandLine(*Params, Tokens, Switches, ParamsMap);
|
|
|
|
const FString UassetPath = ParamsMap.FindRef(TEXT("Uasset"));
|
|
FString UexpPath = ParamsMap.FindRef(TEXT("Uexp"));
|
|
const FString PackageName = ParamsMap.FindRef(TEXT("Package"));
|
|
const FString ReportPath = ParamsMap.FindRef(TEXT("Report"));
|
|
const bool bDryRun = Switches.Contains(TEXT("DryRun"));
|
|
|
|
TArray<FString> Report;
|
|
auto Line = [&Report](const FString& Text)
|
|
{
|
|
UE_LOG(LogLPImportLegacyStreetMap, Display, TEXT("%s"), *Text);
|
|
Report.Add(Text);
|
|
};
|
|
auto Finish = [&](int32 Code)
|
|
{
|
|
Line(FString::Printf(TEXT("CODE %d"), Code));
|
|
if (!ReportPath.IsEmpty())
|
|
{
|
|
FFileHelper::SaveStringArrayToFile(Report, *ReportPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM);
|
|
}
|
|
return Code;
|
|
};
|
|
|
|
if (UassetPath.IsEmpty() || (!bDryRun && PackageName.IsEmpty()))
|
|
{
|
|
Line(TEXT("usage : -Uasset=<ancien Ville.uasset> [-Uexp=<Ville.uexp>] (-DryRun | -Package=(/Game/Heavy/StreetMap/_Pilot/<Ville> | /lpm_city_<v>/StreetMap/<Ville>)) [-Report=<f.md>]"));
|
|
return Finish(2);
|
|
}
|
|
if (UexpPath.IsEmpty())
|
|
{
|
|
UexpPath = FPaths::ChangeExtension(UassetPath, TEXT("uexp"));
|
|
}
|
|
Line(FString::Printf(TEXT("# LPImportLegacyStreetMap %s <- %s + %s"), bDryRun ? TEXT("(DryRun)") : *PackageName, *UassetPath, *UexpPath));
|
|
|
|
// Gardes d'ecriture, avant toute lecture.
|
|
if (!bDryRun)
|
|
{
|
|
TArray<FString> PackageParts;
|
|
PackageName.ParseIntoArray(PackageParts, TEXT("/"));
|
|
const bool bPackMap = PackageParts.Num() == 3 && PackageParts[0].StartsWith(TEXT("lpm_city_")) && PackageParts[1] == TEXT("StreetMap");
|
|
if ((!PackageName.StartsWith(TEXT("/Game/Heavy/StreetMap/_Pilot/")) && !bPackMap) || !FPackageName::IsValidLongPackageName(PackageName))
|
|
{
|
|
Line(TEXT("ECHEC : le paquet doit etre sous /Game/Heavy/StreetMap/_Pilot/ (pilote) ou /lpm_city_<v>/StreetMap/<Ville> (pack) ; aucun asset ecrit"));
|
|
return Finish(1);
|
|
}
|
|
if (FPackageName::DoesPackageExist(PackageName))
|
|
{
|
|
Line(FString::Printf(TEXT("ECHEC : %s existe deja ; aucun asset ecrit"), *PackageName));
|
|
return Finish(1);
|
|
}
|
|
}
|
|
|
|
TArray<uint8> Header, Body;
|
|
if (!FFileHelper::LoadFileToArray(Header, *UassetPath) || !FFileHelper::LoadFileToArray(Body, *UexpPath))
|
|
{
|
|
Line(TEXT("ECHEC : fichier .uasset ou .uexp illisible"));
|
|
return Finish(1);
|
|
}
|
|
Line(FString::Printf(TEXT("sources : uasset %d octets, uexp %d octets"), Header.Num(), Body.Num()));
|
|
|
|
// Resume de paquet UE4 (LegacyFileVersion -6 / -7).
|
|
FContext Ctx;
|
|
FReader H(Header, 0);
|
|
const uint32 Tag = H.Read<uint32>();
|
|
const int32 Legacy = H.Read<int32>();
|
|
if (Tag != 0x9E2A83C1u || Legacy > -6 || Legacy < -7)
|
|
{
|
|
Line(FString::Printf(TEXT("ECHEC : en-tete %08X legacy=%d : pas un paquet UE4 4.x pris en charge"), Tag, Legacy));
|
|
return Finish(1);
|
|
}
|
|
H.Read<int32>(); // LegacyUE3Version
|
|
const int32 VersionUE4 = H.Read<int32>();
|
|
const int32 Licensee = H.Read<int32>();
|
|
const int32 CustomVersions = H.Read<int32>();
|
|
H.Skip(int64(FMath::Max(CustomVersions, 0)) * 20); // FGuid + int32 (format optimise)
|
|
const int32 TotalHeaderSize = H.Read<int32>();
|
|
const FString FolderName = H.Str();
|
|
const uint32 PackageFlags = H.Read<uint32>();
|
|
const int32 NameCount = H.Read<int32>();
|
|
const int32 NameOffset = H.Read<int32>();
|
|
H.Skip(8); // GatherableTextDataCount, GatherableTextDataOffset (paquet cuit : pas de LocalizationId)
|
|
const int32 ExportCount = H.Read<int32>();
|
|
const int32 ExportOffset = H.Read<int32>();
|
|
const int32 ImportCount = H.Read<int32>();
|
|
const int32 ImportOffset = H.Read<int32>();
|
|
const bool bUnversioned = VersionUE4 == 0 && Licensee == 0;
|
|
Line(FString::Printf(TEXT("en-tete : legacy=%d ue4=%d licensee=%d (%s) flags=%08X totalheader=%d noms=%d exports=%d imports=%d"),
|
|
Legacy, VersionUE4, Licensee, bUnversioned ? TEXT("non versionne") : TEXT("versionne"), PackageFlags, TotalHeaderSize, NameCount, ExportCount, ImportCount));
|
|
if (H.bError || (!bUnversioned && VersionUE4 < 511) || (PackageFlags & 0x2000u) != 0)
|
|
{
|
|
Line(TEXT("ECHEC : resume illisible, version UE4 < 511, ou proprietes non versionnees (PKG_UnversionedProperties) : non pris en charge"));
|
|
return Finish(1);
|
|
}
|
|
|
|
H.Pos = NameOffset;
|
|
for (int32 Index = 0; Index < NameCount && !H.bError; ++Index)
|
|
{
|
|
Ctx.Names.Add(H.Str());
|
|
H.Skip(4); // hachages (2 x uint16)
|
|
}
|
|
TArray<FImport> Imports;
|
|
H.Pos = ImportOffset;
|
|
for (int32 Index = 0; Index < ImportCount && !H.bError; ++Index)
|
|
{
|
|
FImport Import;
|
|
Ctx.Name(H); // ClassPackage
|
|
Import.ClassName = Ctx.Name(H);
|
|
H.Read<int32>(); // OuterIndex
|
|
Import.ObjectName = Ctx.Name(H);
|
|
Imports.Add(Import);
|
|
}
|
|
|
|
// Export dont la classe est StreetMap.
|
|
int64 SerialSize = -1, SerialOffset = -1;
|
|
FString ExportName;
|
|
H.Pos = ExportOffset;
|
|
for (int32 Index = 0; Index < ExportCount && !H.bError; ++Index)
|
|
{
|
|
const int32 ClassIndex = H.Read<int32>();
|
|
H.Skip(12); // SuperIndex, TemplateIndex, OuterIndex
|
|
const FString Name = Ctx.Name(H);
|
|
H.Skip(4); // ObjectFlags
|
|
const int64 Size = H.Read<int64>();
|
|
const int64 Offset = H.Read<int64>();
|
|
H.Skip(12 + 16 + 4 + 4 + 4 + 20); // bForcedExport, bNotForClient, bNotForServer, PackageGuid, PackageFlags, bNotAlwaysLoadedForEditorGame, bIsAsset, dependances
|
|
const FString ClassName = (ClassIndex < 0 && Imports.IsValidIndex(-ClassIndex - 1)) ? Imports[-ClassIndex - 1].ObjectName : FString();
|
|
if (ClassName == TEXT("StreetMap") && SerialOffset < 0)
|
|
{
|
|
SerialSize = Size;
|
|
SerialOffset = Offset;
|
|
ExportName = Name;
|
|
}
|
|
}
|
|
const int64 DataStart = SerialOffset - TotalHeaderSize;
|
|
if (H.bError || SerialOffset < 0 || DataStart < 0 || DataStart + SerialSize > Body.Num())
|
|
{
|
|
Line(TEXT("ECHEC : aucun export StreetMap lisible, ou export hors du .uexp"));
|
|
return Finish(1);
|
|
}
|
|
Line(FString::Printf(TEXT("export : %s, %lld octets a partir de l'octet %lld du .uexp"), *ExportName, SerialSize, DataStart));
|
|
|
|
// Carte 5.8 : objet transitoire en -DryRun, sinon paquet neuf.
|
|
UPackage* Package = bDryRun ? GetTransientPackage() : CreatePackage(*PackageName);
|
|
const FName AssetName = bDryRun ? NAME_None : FName(*FPackageName::GetLongPackageAssetName(PackageName));
|
|
UStreetMap* StreetMap = NewObject<UStreetMap>(Package, AssetName, bDryRun ? RF_Transient : (RF_Public | RF_Standalone));
|
|
|
|
FReader R(Body, DataStart);
|
|
const int64 DataEnd = DataStart + SerialSize;
|
|
if (!ReadProperties(R, DataEnd, UStreetMap::StaticClass(), StreetMap, Ctx, TEXT("StreetMap")))
|
|
{
|
|
Line(FString::Printf(TEXT("ECHEC : lecture des proprietes : %s ; aucun asset ecrit"), Ctx.Error.IsEmpty() ? TEXT("format inattendu") : *Ctx.Error));
|
|
return Finish(1);
|
|
}
|
|
Line(FString::Printf(TEXT("proprietes lues jusqu'a l'octet %lld ; reste %lld octets apres la fin de liste (attendu : 4)"), R.Pos, DataEnd - R.Pos));
|
|
for (const TPair<FString, int32>& Pair : Ctx.Unknown) { Line(FString::Printf(TEXT("propriete inconnue en 5.8 (sautee) : %s x%d"), *Pair.Key, Pair.Value)); }
|
|
for (const TPair<FString, int32>& Pair : Ctx.Mismatch) { Line(FString::Printf(TEXT("type ou valeur incompatible (saute) : %s x%d"), *Pair.Key, Pair.Value)); }
|
|
|
|
// Comptes, a comparer a Saved/MAPDL1/lire_streetmap_ue4.py --totaux.
|
|
int64 RoadPoints = 0, RoadRefs = 0, BuildingPoints = 0, ParkPoints = 0, OneWay = 0;
|
|
int32 Named = 0, WithAmenity = 0, WithHeight = 0;
|
|
for (const FStreetMapRoad& Road : StreetMap->GetRoads()) { RoadPoints += Road.RoadPoints.Num(); OneWay += Road.bIsOneWay ? 1 : 0; }
|
|
for (const FStreetMapNode& Node : StreetMap->GetNodes()) { RoadRefs += Node.RoadRefs.Num(); }
|
|
for (const FStreetMapBuilding& Building : StreetMap->GetBuildings())
|
|
{
|
|
BuildingPoints += Building.BuildingPoints.Num();
|
|
Named += Building.BuildingName.IsEmpty() ? 0 : 1;
|
|
WithAmenity += Building.Amenity.IsEmpty() ? 0 : 1;
|
|
WithHeight += Building.Height > 0.0 ? 1 : 0;
|
|
}
|
|
for (const FStreetMapPark& Park : StreetMap->GetParks()) { ParkPoints += Park.ParkPoints.Num(); }
|
|
const FVector2D Min = StreetMap->GetBoundsMin();
|
|
const FVector2D Max = StreetMap->GetBoundsMax();
|
|
Line(FString::Printf(TEXT("TOTAUX routes=%d points_routes=%lld sens_unique=%lld noeuds=%d refs_noeuds=%lld batiments=%d points_batiments=%lld nommes=%d amenites=%d hauteurs=%d parcs=%d points_parcs=%lld eaux=%d"),
|
|
StreetMap->GetRoads().Num(), RoadPoints, OneWay, StreetMap->GetNodes().Num(), RoadRefs, StreetMap->GetBuildings().Num(), BuildingPoints,
|
|
Named, WithAmenity, WithHeight, StreetMap->GetParks().Num(), ParkPoints, StreetMap->GetWaters().Num()));
|
|
Line(FString::Printf(TEXT("BORNES (%.1f, %.1f) a (%.1f, %.1f) cm ; soit %.2f x %.2f km"), Min.X, Min.Y, Max.X, Max.Y, (Max.X - Min.X) / 100000.0, (Max.Y - Min.Y) / 100000.0));
|
|
|
|
if (bDryRun)
|
|
{
|
|
Line(TEXT("DryRun : aucun asset ecrit"));
|
|
return Finish(0);
|
|
}
|
|
|
|
FAssetRegistryModule::AssetCreated(StreetMap);
|
|
FSavePackageArgs SaveArgs;
|
|
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
|
|
const FString FileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension());
|
|
if (!UPackage::SavePackage(Package, StreetMap, *FileName, SaveArgs))
|
|
{
|
|
Line(FString::Printf(TEXT("ECHEC : sauvegarde de %s"), *FileName));
|
|
return Finish(1);
|
|
}
|
|
Line(FString::Printf(TEXT("asset ecrit : %s (%s), %lld octets"), *PackageName, *FileName, IFileManager::Get().FileSize(*FileName)));
|
|
return Finish(0);
|
|
}
|