Asset groups localization issue with IOStore (UE-96742)

Hello !

Quick post to talk about issue we had in our project with the AssetGroups localization since we use -IOStore.
(Someone already opened a bug report ticket for this UE-96742)

The localization of our assets where made before our persistent user was created and the save loaded. So our AssetGroups tag as “Audio” were never localized correctly. And because we can have “two cultures” in the game (one is the CurrentCulture of FInternationalization that we use for text and subtitles, and the other one is the “Audio” asset group use for voice and other stuff from L10N folder), we encounter issue with non-localized assets.

Here is the stuff we changed in the engine to fix it.

In AsyncLoading2.cpp file, we created this function:

void LocalizeMountedContainer(TArrayView<const FIoDispatcherMountedContainer> Containers)
	{
		int32 ContainersToLoad = 0;

		for (const FIoDispatcherMountedContainer& Container : Containers)
		{
			if (Container.ContainerId.IsValid())
			{
				++ContainersToLoad;
			}
		}

		if (!ContainersToLoad)
		{
			return;
		}

		TAtomic<int32> Remaining(ContainersToLoad);
		FIoBatch IoBatch = IoDispatcher.NewBatch();
		FEvent* Event = FPlatformProcess::GetSynchEventFromPool();

		for (const FIoDispatcherMountedContainer& Container : Containers)
		{
			const FIoContainerId& ContainerId = Container.ContainerId;
			if (!ContainerId.IsValid())
			{
				continue;
			}

			TUniquePtr<FLoadedContainer>& LoadedContainerPtr = LoadedContainers.FindOrAdd(ContainerId);
			if (!LoadedContainerPtr)
			{
				continue;
			}

			FLoadedContainer& LoadedContainer = *LoadedContainerPtr;

			FIoChunkId HeaderChunkId = CreateIoChunkId(ContainerId.Value(), 0, EIoChunkType::ContainerHeader);
			IoBatch.ReadWithCallback(HeaderChunkId, FIoReadOptions(), IoDispatcherPriority_High, [this, &LoadedContainer, &Remaining, Event](TIoStatusOr<FIoBuffer> Result)
			{

				const EAsyncExecution ExecutionMethod = FPlatformProcess::SupportsMultithreading() ? EAsyncExecution::TaskGraph : EAsyncExecution::Thread;

				Async(ExecutionMethod, [this, Event, &Remaining, IoBuffer = Result.ConsumeValueOrDie(), &LoadedContainer]()
				{
					LLM_SCOPE(ELLMTag::AsyncLoading); // Might be other tag, but we kept this one

					FMemoryReaderView Ar(MakeArrayView(IoBuffer.Data(), IoBuffer.DataSize()));

					FContainerHeader ContainerHeader;
					Ar << ContainerHeader;

					{
						TRACE_CPUPROFILER_EVENT_SCOPE(LoadPackageStoreLocalization);
						const FSourceToLocalizedPackageIdMap* LocalizedPackages = nullptr;

// AssetGroupCultureName is a custom variable we added in the SetupCulture function. Equals to FInternationalization::Get().GetCurrentAssetGroupsCulture(TEXT("Audio"))

						LocalizedPackages = ContainerHeader.CulturePackageMap.Find(AssetGroupCultureName);

						if (LocalizedPackages)
						{
							RedirectsPackageMap.Reset();

							for (auto& Pair : *LocalizedPackages)
							{
								const FPackageId& SourceId = Pair.Key;
								const FPackageId& LocalizedId = Pair.Value;
								RedirectsPackageMap.Emplace(SourceId, LocalizedId);
							}
						}
					}

					{
						TRACE_CPUPROFILER_EVENT_SCOPE(LoadPackageStoreRedirects);
						for (const TPair<FPackageId, FPackageId>& Redirect : ContainerHeader.PackageRedirects)
						{
							RedirectsPackageMap.Emplace(Redirect.Key, Redirect.Value);
						}
					}

					if (--Remaining == 0)
					{
						Event->Trigger();
					}
				});
			});
		}

		IoBatch.Issue();
		Event->Wait();
		FPlatformProcess::ReturnSynchEventToPool(Event);

		ApplyRedirects(RedirectsPackageMap);
	}

(The function is a copy/paste (with minor changes) of the existing one LoadContainers)

We also added a function to check if we have to update the localization of our assets. (nothing fancy here)

The LocalizeMountedContainer is called from a custom function namedLocalizeIOContainers we added in IAsyncPackageLoader.

Then from the FSeamlessTravelHandler::StartLoadingDestination, we get the AsyncPackageLoader and then we call LocalizeIOContainers to update the containers localization.

The goal here is to check when we travel from one level to an other one, if the AssetGroup Culture has changed, and update them if it’s the case.

It fixed the issue on our side, and our Audio assets are now correctly localized.
Hope it will help you if you encounter the same issue with -IOStore in your project.

(Sorry if I made mistakes, English isn’t my main language)