CARLA
Loading...
Searching...
No Matches
ContentPackManifest.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
8#include "Carla.h"
9
10#include <util/ue-header-guard-begin.h>
11#include "Dom/JsonObject.h"
12#include "Misc/FileHelper.h"
13#include "Misc/Paths.h"
14#include "Serialization/JsonReader.h"
15#include "Serialization/JsonSerializer.h"
16#include <util/ue-header-guard-end.h>
17
19{
20 if (InName.IsEmpty())
21 {
22 return false;
23 }
24 for (const TCHAR C : InName)
25 {
26 if (!FChar::IsAlnum(C) && C != TEXT('_') && C != TEXT('-'))
27 {
28 return false;
29 }
30 }
31 return true;
32}
33
35 const FString &JsonText,
37 FString &OutError)
38{
39 TSharedPtr<FJsonObject> Root;
40 TSharedRef<TJsonReader<TCHAR>> Reader = TJsonReaderFactory<TCHAR>::Create(JsonText);
41 if (!FJsonSerializer::Deserialize(Reader, Root) || !Root.IsValid())
42 {
43 OutError = FString::Printf(TEXT("manifest is not valid JSON: %s"), *Reader->GetErrorMessage());
44 return false;
45 }
46
47 if (!Root->TryGetStringField(TEXT("name"), Out.Name) || Out.Name.IsEmpty())
48 {
49 OutError = TEXT("manifest has no \"name\"");
50 return false;
51 }
52 if (!IsValidPackName(Out.Name))
53 {
54 OutError = FString::Printf(
55 TEXT("manifest \"name\" '%s' is not a valid pack name (letters, digits, '_' and '-' only)"),
56 *Out.Name);
57 return false;
58 }
59
60 Root->TryGetStringField(TEXT("version"), Out.Version);
61 Root->TryGetStringField(TEXT("carla_version"), Out.CarlaVersion);
62 Root->TryGetStringField(TEXT("base_release"), Out.BaseRelease);
63 Root->TryGetStringField(TEXT("platform"), Out.Platform);
64
65 const TSharedPtr<FJsonObject> *Engine = nullptr;
66 if (Root->TryGetObjectField(TEXT("engine"), Engine) && Engine != nullptr && Engine->IsValid())
67 {
68 (*Engine)->TryGetStringField(TEXT("version"), Out.EngineVersion);
69 (*Engine)->TryGetStringField(TEXT("commit"), Out.EngineCommit);
70 }
71
72 Out.Maps.Reset();
73 const TArray<TSharedPtr<FJsonValue>> *MapsArray = nullptr;
74 if (Root->TryGetArrayField(TEXT("maps"), MapsArray) && MapsArray != nullptr)
75 {
76 for (const TSharedPtr<FJsonValue> &Value : *MapsArray)
77 {
78 const TSharedPtr<FJsonObject> MapObject = Value.IsValid() ? Value->AsObject() : nullptr;
79 if (!MapObject.IsValid())
80 {
81 OutError = TEXT("manifest \"maps\" entries must be objects");
82 return false;
83 }
85 MapObject->TryGetStringField(TEXT("name"), Map.Name);
86 MapObject->TryGetStringField(TEXT("package"), Map.Package);
87 MapObject->TryGetStringField(TEXT("xodr"), Map.Xodr);
88 MapObject->TryGetBoolField(TEXT("world_partition"), Map.bWorldPartition);
89 if (Map.Name.IsEmpty())
90 {
91 OutError = TEXT("manifest \"maps\" entry without \"name\"");
92 return false;
93 }
94 if (Map.Package.IsEmpty())
95 {
96 // Default to the canonical layout.
97 Map.Package = FString::Printf(TEXT("/%s/Maps/%s"), *Out.Name, *Map.Name);
98 }
99 // A pack map lives under the pack's own mount point, or it is a project map
100 // (/Game/...) the base release did not cook and the pack ships in place,
101 // e.g. /Game/Carla/Maps/Town12/Town12 (carla-pack add --map Town12).
102 const FString ExpectedRoot = FString::Printf(TEXT("/%s/"), *Out.Name);
103 if (!Map.Package.StartsWith(ExpectedRoot) && !Map.Package.StartsWith(TEXT("/Game/")))
104 {
105 OutError = FString::Printf(
106 TEXT("map '%s' package '%s' is neither under the pack mount point '%s' nor a project map (/Game/...)"),
107 *Map.Name, *Map.Package, *ExpectedRoot);
108 return false;
109 }
110 Out.Maps.Add(MoveTemp(Map));
111 }
112 }
113
114 Out.Catalogs.Reset();
115 const TArray<TSharedPtr<FJsonValue>> *CatalogsArray = nullptr;
116 if (Root->TryGetArrayField(TEXT("catalogs"), CatalogsArray) && CatalogsArray != nullptr)
117 {
118 for (const TSharedPtr<FJsonValue> &Value : *CatalogsArray)
119 {
120 FString Catalog;
121 if (Value.IsValid() && Value->TryGetString(Catalog) && !Catalog.IsEmpty())
122 {
123 Out.Catalogs.Add(Catalog);
124 }
125 }
126 }
127
128 Out.Files.Reset();
129 const TSharedPtr<FJsonObject> *FilesObject = nullptr;
130 if (Root->TryGetObjectField(TEXT("files"), FilesObject) && FilesObject != nullptr && FilesObject->IsValid())
131 {
132 for (const auto &Pair : (*FilesObject)->Values)
133 {
134 FString Digest;
135 if (Pair.Value.IsValid() && Pair.Value->TryGetString(Digest))
136 {
137 Out.Files.Add(FString(Pair.Key.ToView()), Digest);
138 }
139 }
140 }
141
142 return true;
143}
144
146 const FString &InManifestPath,
148 FString &OutError)
149{
150 const FString FullPath = FPaths::ConvertRelativePathToFull(InManifestPath);
151 FString JsonText;
152 if (!FFileHelper::LoadFileToString(JsonText, *FullPath))
153 {
154 OutError = FString::Printf(TEXT("cannot read manifest '%s'"), *FullPath);
155 return false;
156 }
157 if (!Parse(JsonText, Out, OutError))
158 {
159 OutError = FString::Printf(TEXT("%s: %s"), *FullPath, *OutError);
160 return false;
161 }
162 Out.ManifestPath = FullPath;
163 Out.PackDir = FPaths::GetPath(FullPath);
164 return true;
165}
166
168{
169 TArray<FString> Names;
170 Names.Reserve(Maps.Num());
171 for (const FCarlaContentPackMap &Map : Maps)
172 {
173 Names.Add(Map.Name);
174 }
175 return Names;
176}
Parsed carla-pack.json.
static bool LoadFromFile(const FString &InManifestPath, FCarlaContentPackManifest &OutManifest, FString &OutError)
Read and parse <ManifestPath>. Fills ManifestPath/PackDir.
FString ManifestPath
Absolute path of the manifest file this was parsed from.
static bool Parse(const FString &JsonText, FCarlaContentPackManifest &OutManifest, FString &OutError)
Parse a manifest from a JSON string. OutError explains a false return.
FString PackDir
Absolute path of the pack directory (parent of the manifest).
TArray< FCarlaContentPackMap > Maps
static bool IsValidPackName(const FString &InName)
Name must be usable as a plugin name and mount point ("/<Name>/").
TArray< FString > GetMapNames() const
TMap< FString, FString > Files
One map declared by a content pack manifest.
FString Package
Long package name of the level, e.g. "/NewPack/Maps/NewTown".
FString Xodr
OpenDRIVE file relative to the pack Content dir, e.g. "Maps/OpenDrive/NewTown.xodr".
FString Name
Map name as exposed by get_available_maps / load_world.