Save/load package

Hello,

Here is the context :
I am currently working on a plugin to make some custom commands (in blueprints) to ease deployment or post builds processes (something that seems a little like the build xml file that make its apparition in UE 4.13).

Well, my issue is that I want to save/load some custom datas. I don’t want theses datas to be saved in the Content directory, to hide them from the user and not pollute their project with asset that will not be packaged.
So, I want to save these datas in Plugin/MyPlugin/Datas.
My datas are stocked in a UObject*.

With that in mind, I thought custom asset was not I wanted since the factory (FactoryNew), perhaps, will force me to make my asset in the Content directory.

Here is the problem :
So I am using CreatePackage/SavePackage/LoadPackage. But I am encountering some issues.

For datas creation, I do :

package = CreatePackage(nullptr, *absoluteFilePath); // Create the package where I want
ProfileDatas = NewObject<UCCProfileDatas>(package, UCCProfileDatas::StaticClass(), CCProfileDatasSaveDataName); // Create a new object of my datas that will be in the package
package->MarkPackageDirty(); // Mark package dirty to be able to save it

FStringOutputDevice outputDevice; // Will get error message
if (UPackage::SavePackage(package, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *absoluteFilePath, &outputDevice)) // Save the package where I want

Then, to load the datas, I do :

package = LoadPackage(nullptr, *outPackagePath, LOAD_None);

This works. Until I add some datas in my object. I fill a TArray (as a UPROPERTY).
Then I save it, but when I load it again. It crashes here :

LinkerLoad.cpp : line 3586

UObject* Template = UObject::GetArchetypeFromRequiredInfo(LoadClass, ThisParent, Export.ObjectName, Export.ObjectFlags);

check(Template);

With ‘Export.ObjectName = 0x00000017aeada028 “Default__Object”’. I do not know what is this Default__Object.

Any idea ? What I am doing wrong ? Should I use some custom asset to make a correct serialization ?

Well, there is some advancement. Now I can save and load a package like this :
ProfileDatas = NewObject(package, UCCProfileDatas::StaticClass(), CCProfileDatasSaveDataName, RF_Public | RF_Standalone);

And after the load package, I can have my object by doing :

ProfileDatas = FindObject<UCCProfileDatas>(package, *CCProfileDatasSaveDataName.ToString());

It works almost like a charm. Almost.
There is an issue when my main object (UCCProfileDatas) contains a UObject* that have been instantiated like this :

ProfileDatas->Hello = NewObject<UCCDatas>(package, UCCDatas::StaticClass(), TEXT("Hello"));

I tried several things, like settings the ProfileDatas as the Outer, w/ or w/o a name. However, I still get an dependency issue.

It crashes during the SavePackage step.

SavePackage.cpp : line 3860
// If we still didn’t find index, maybe it was a duplicate export which got removed.
// Check if we have a redirect to original.
if (DependencyIndex.IsNull())
{
DependencyIndex = ExportToIndexMap.FindRef(DuplicateRedirects[DependentObject]);
}

I am not sure what I am suppose to do to not get this dependency issue…

Well, the dependency had no link with the real issue… I just misunderstood how the packaging system worked.

For anybody who is in my situation, here is the resolution.

The issue was concerning the paths between package, objects etc.
In my situation, I wanted to create a package in a special path (not something like /Game, /Engine etc.).
For the example, we will want to create our asset here : “MyProjectPath/Plugins/Datas/CustomAsset.uasset”

To start, we have to register a new mount point. You can do it in the initialization of your module.

FPackageName::RegisterMountPoint("/MyMountPoint/",  "Absolute/Path/To/MyProjectPath/Plugins")

Or

FPackageName::RegisterMountPoint("/MyMountPoint/", FPaths::GamePluginsDir())

Now, we create the package with CreatePackage by giving the name with the mount point (not absolute path). Does not require the “.uasset”.

package = CreatePackage(nullptr, TEXT("/MyMountPoint/Datas/CustomAsset"));

Afterwards, you can create the object and set it into the package like this :

MyDatas = NewObject<UMyDataClass>(package, UMyDataClass::StaticClass(), TEXT("MyObjectName"), RF_Public | RF_Standalone);

NB : If you need to add some UObject* into MyDatas, you just have to do that, giving the package as outer :

MyDatas->MySubUObject = NewObject<UMyClass>(package, UMyClass::StaticClass(), TEXT("MySubObject"));

Then, to save your package, you need the absolute path + .uasset extension :

UPackage::SavePackage(package, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *(TEXT("Absolute/Path/To/File") + FPackageName::GetAssetPackageExtension());

Your object is saved ! Now, the loading.
You have to use LoadPackage, using the path with the mount point (the one you used for CreatePackage).

package = LoadPackage(nullptr, TEXT("/MyMountPoint/Datas/CustomAsset"), LOAD_None);

Once your package is loaded, you have to search your object saved in it. For that, you have to use FindObject (with the name used during the NewObject) :

MyDatas = FindObject<UMyClass>(package, TEXT("MyObjectName"));

And that’s all ! All your datas should be loaded correctly now.

I tried doing this, but I’m getting a duplicate at the root of Plugin’s content directory, as well as, a uasset in the correct location.

I am sorry, I have no idea where in the process it can create a duplicate…
If you have Unreal symbols, you might try to dig into the SavePackage function and see if there is something that try to save in your Plugins directory.

Using this method, everything works fine in unpackaged builds. However, in packaged builds, when I call LoadPackage(), I get a lot of “Couldn’t find file for [-] requested by async”. I can’t find any solutions for this.

Okay, NONE of the solutions I found where helpful for automatically saving the package (without user prompt)… The relative path that needs to be used for Package saving is quite confusing, but I found a good way of saving assets through their packages with the following snippet:

Creating the Asset should be well covered by the previous answers. Just make sure to mark the Package with FullyLoad() if the asset is ready to be saved, otherwise Unreal will throw an error because the file isn’t loaded, thus cannot be saved, etc.