From cbce6eb4319cdff5ca7f7b39e12b7e1e98aaefdd Mon Sep 17 00:00:00 2001 From: Rovetown <110688035+Rovetown@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:11:10 +0200 Subject: [PATCH] feat: :sparkles: Add "Asset Toolkit: Unreal Asset Copy Tool" for automated asset copying Adds automated asset copy tool guidance Introduces an IMPORTANT note linking to a guided PowerShell tool that automates copying required assets for the starter project, reducing manual effort and errors. Retains manual instructions for advanced use and cleans up minor formatting and whitespace. https://github.com/satisfactorymodding/Documentation/issues/348 --- .../AssetToolkitCopyTool.ps1 | 256 ++++++++++++++++++ .../CommunityResources/AssetToolkit.adoc | 17 +- 2 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 modules/ROOT/attachments/CommunityResources/AssetToolkitCopyTool.ps1 diff --git a/modules/ROOT/attachments/CommunityResources/AssetToolkitCopyTool.ps1 b/modules/ROOT/attachments/CommunityResources/AssetToolkitCopyTool.ps1 new file mode 100644 index 00000000..3be5746b --- /dev/null +++ b/modules/ROOT/attachments/CommunityResources/AssetToolkitCopyTool.ps1 @@ -0,0 +1,256 @@ +<# +.SYNOPSIS + Asset Toolkit: Unreal Asset Copy Tool + +.DESCRIPTION + Copies custom assets from a (backup) Starter Project into your generated project. + Skips invalid or missing paths and optionally copies StringTables. + Logs all actions. + Designed for saving time and better overview over what assets are copied. + +.NOTES + Author: Rovetown + GitHub: https://github.com/Rovetown + Discord: https://discord.gg/NdVyd7bef9 + Date: 22nd September 2025 +#> + +param() + +# ------------------------- +# CONFIG +# ------------------------- +$CustomAssetListUrl = "https://raw.githubusercontent.com/satisfactorymodding/UnrealProjectUpdater/master/CustomAssets.txt" +$LogFile = Join-Path -Path $PSScriptRoot -ChildPath ("AssetCopyLog_{0}.log" -f (Get-Date -Format "ddMMyyyy_HHmmss")) + +$SourceFolder = "" # If left empty, defaults to "$PSScriptRoot\Content" +$TargetFolder = "" # Can be set to a default path if needed, otherwise User will be prompted + +# ------------------------- +# FUNCTIONS +# ------------------------- +function Write-Log { + param( + [string]$Message, + [string]$Level = "INFO", + [switch]$Silent # Add a flag to skip console output + ) + + $timestamp = Get-Date -Format "dd-MM-yyyy HH:mm:ss" + $entry = "$timestamp [$Level] $Message" + + # Always write to log file + Add-Content -Path $LogFile -Value $entry + + # Only display on console if not silent + if (-not $Silent) { + switch ($Level.ToUpper()) { + "INFO" { Write-Host $entry -ForegroundColor Green } + "WARN" { Write-Host $entry -ForegroundColor Yellow } + "ERROR" { Write-Host $entry -ForegroundColor Red } + "COPIED" { Write-Host $entry -ForegroundColor Cyan } + "INVALIDPATH" { Write-Host $entry -ForegroundColor Magenta } + default { Write-Host $entry } + } + + } +} + +function Write-Header { + param([string]$Text) + Write-Host "=== $Text ===" -ForegroundColor Magenta +} + +function Write-Divider { + Write-Host ("-" * 50) -ForegroundColor DarkGray +} + +function Get-CustomAssets { + $response = Invoke-WebRequest -Uri $CustomAssetListUrl -UseBasicParsing + $lines = $response.Content -split "`n" + + $assets = @() + # Define all substrings that indicate texture-like assets + $skipKeywords = @('TX_', 'T_', 'TX2D_', '_Alb', '_Rough', '_Nor', '_Mask', '_Normal', '_Noise', 'Noise', 'DecalColor_', 'Decal_', 'NormalPack', 'Mask_Factory_02') + + foreach ($line in $lines) { + $clean = ($line -replace "`r", "").Trim() + if (-not $clean -or $clean -match '^\d{4}-\d{2}-\d{2}') { continue } + + # Normalize slashes + $clean = $clean -replace '^[\\/]+', '' + $clean = $clean -replace '/', '\' + + $baseName = [System.IO.Path]::GetFileName($clean) + + $skip = $false + foreach ($kw in $skipKeywords) { + if ($baseName -cmatch [regex]::Escape($kw)) { + $skip = $true + break + } + } + + if ($skip) { + Write-Log "SKIPPED: $baseName (filtered by pattern)" "WARN" + continue + } + + $assets += $clean + } + return $assets +} + + +function Copy-Asset { + param($Source, $Target) + + try { + if (Test-Path $Source) { + if (Test-Path $Source -PathType Container) { + Copy-Item -Path $Source -Destination $Target -Recurse -Force + Write-Log "Copied folder: $Source -> $Target" + } + else { + Copy-Item -Path $Source -Destination $Target -Force + Write-Log "Copied file: $Source -> $Target" + } + } + else { + Write-Log "Asset not found, skipping: $Source" "WARN" + } + } + catch { + Write-Log "Error copying $Source. Exception: $_" "WARN" + } +} + +# ------------------------- +# MAIN +# ------------------------- +Clear-Host +Write-Header "Unreal Asset Copy Tool" +Write-Host "This tool copies custom assets from a (backup) Starter Project into your generated project." +Write-Host "The Script should be placed in the root folder of your backup Starter Project, but its not mandatory." +Write-Divider +Write-Host "Press ENTER to continue..." +[void][System.Console]::ReadKey($true) + +Clear-Host +Write-Header "'Asset Backup' and 'Dumped Content' Folders" +Write-Host "Please enter the full paths to the following folders:" +Write-Host "1. 'Asset Backup' folder: This is the Content folder from your (backup) Starter Project (where your original assets are stored)." +Write-Host "2. 'Dumped Content' folder: This is the Content folder of your generated or dumped project (where assets will be copied to)." +Write-Divider +Write-Host "ATTENTION: If you leave the 'Asset Backup' folder empty, it will default to your current folder `n'$PSScriptRoot\Content', `nwhich should be the correct path if you put the Script in the root folder of your (backup) Starter Project." -ForegroundColor Red +Write-Divider + +$SourceFolder = Read-Host "Enter the path to your 'Asset Backup' folder [ex. $PSScriptRoot\Content]" +if (-not $SourceFolder) { $SourceFolder = Join-Path $PSScriptRoot "Content" } + +# $SourceFolder = Read-Host "Enter/paste the path to your 'Asset Backup Content' folder (where you saved your Starter Project content)" +$TargetFolder = Read-Host "Enter the path to your 'Dumped Content' folder" + +if (-not (Test-Path $SourceFolder)) { + Write-Log "'Asset Backup' folder does not exist: $SourceFolder" "ERROR" + exit +} +if (-not (Test-Path $TargetFolder)) { + Write-Log "'Dumped Content' folder does not exist: $TargetFolder" "ERROR" + exit +} + +Write-Header "Starting copy process" +Write-Host "Copies the assets listed in the CustomAssets.txt from the 'Asset Backup' to the 'Dumped Content' folder." +Write-Divider +Write-Log "Asset Backup: $SourceFolder" +Write-Log "Dumped Content: $TargetFolder" +Write-Divider + +# Get assets +$assets = Get-CustomAssets + +# ------------------------- +# Copy Assets with Bottom-Right Counter +# ------------------------- + +$width = [console]::WindowWidth +$height = [console]::WindowHeight + +for ($index = 0; $index -lt $assets.Count; $index++) { + $asset = $assets[$index] + + if ([string]::IsNullOrWhiteSpace($asset)) { continue } + + try { + $srcPath = [System.IO.Path]::Combine($SourceFolder.TrimEnd('\'), $asset.Trim()) + $dstPath = [System.IO.Path]::Combine($TargetFolder.TrimEnd('\'), $asset.Trim()) + + if ([string]::IsNullOrWhiteSpace($srcPath)) { continue } + + if (Test-Path $srcPath) { + # Copy file/folder + Copy-Item -Path $srcPath -Destination $dstPath -Recurse -Force + + # Log success + Write-Log "COPIED: $asset ($srcPath -> $dstPath)" "COPIED" + } + else { + # Log missing file + Write-Log "MISSING: $asset ($srcPath) not found" "WARN" + } + } + catch { + Write-Log "INVALID PATH: $asset ($srcPath). Exception: $_" "WARN" + continue + } + + # ------------------------- + # Bottom-Right Progress Counter + # ------------------------- + $counterText = "Processed: {0}/{1}" -f ($index + 1), $assets.Count + [console]::SetCursorPosition([Math]::Max(0, $width - $counterText.Length), $height - 1) + Write-Host $counterText -NoNewline -ForegroundColor Cyan +} + + +# Optional: copy StringTables +Write-Host "`n" +Write-Header "Copy Optional StringTables" +Write-Host "This tool can also copy StringTables from the backup folder." +Write-Divider +$includeStringTables = Read-Host "Do you also want to copy the StringTables folder? (y/n)" +if ($includeStringTables -match "^[Yy]") { + $StringTablesSrc = Join-Path $SourceFolder "Localization/StringTables" + $StringTablesDst = Join-Path $TargetFolder "Localization/StringTables" + Copy-Asset -Source $StringTablesSrc -Target $StringTablesDst +} +else { + Write-Log "Skipped copying StringTables" +} + +# Build compact lists without touching log lines +$copiedFiles = $assets | Where-Object { Test-Path ([System.IO.Path]::Combine($SourceFolder, $_)) } | +ForEach-Object { [System.IO.Path]::GetFileName($_) } + +$missingFiles = $assets | Where-Object { -not (Test-Path ([System.IO.Path]::Combine($SourceFolder, $_))) } | +ForEach-Object { [System.IO.Path]::GetFileName($_) } + + +Clear-Host +Write-Log "Process complete" -Silent +Write-Header "Copy Process Completed" +Write-Divider + +Write-Host "Assets successfully copied ($($copiedFiles.Count)):`n" -ForegroundColor Cyan +$copiedFiles | ForEach-Object { Write-Host "- $_" -ForegroundColor Green } + +Write-Host "`nAssets missing/skipped ($($missingFiles.Count)):`n" -ForegroundColor Yellow +$missingFiles | ForEach-Object { Write-Host "- $_" -ForegroundColor Yellow } + +Write-Host "`nA log of this run has been saved to: $LogFile" +Write-Host "If you encounter issues, contact the author on the Satisfactory Modding Discord: rovetown." +Write-Divider + +Write-Host "Press any key to exit..." +[void][System.Console]::ReadKey($true) diff --git a/modules/ROOT/pages/CommunityResources/AssetToolkit.adoc b/modules/ROOT/pages/CommunityResources/AssetToolkit.adoc index 000c7d7d..8fd577a8 100644 --- a/modules/ROOT/pages/CommunityResources/AssetToolkit.adoc +++ b/modules/ROOT/pages/CommunityResources/AssetToolkit.adoc @@ -146,7 +146,7 @@ Excluding the `-log` argument could result in a shorter runtime, but will make t The Powershell command should immediately complete, leaving you with the option to run another command. -Shortly after, you should see the Satisfactory splash screen and a console window +Shortly after, you should see the Satisfactory splash screen and a console window which will provide info regarding the dumping process (since `-log -NewConsole` was used). You can safely close the Powershell window once you see the console log window, @@ -178,7 +178,7 @@ inside your game install directory. [NOTE] ==== -Asset dumping is also used to create the stater project stubs when the game updates. +Asset dumping is also used to create the starter project stubs when the game updates. If you're interested in how this works, check out the https://github.com/satisfactorymodding/UnrealProjectUpdater/blob/master/.github/workflows/updateSML.yml#L209[CI scripts], or contact us on the discord for more info. @@ -349,9 +349,16 @@ and bring some of them in to replace the ones in your freshly generated copy. Make sure your editor is closed before you move the files in! +[IMPORTANT] +==== +Thanks to community member Rovetown we now have an almost fully automated way of doing this. +You can use the link:{attachmentsdir}/CommunityResources/AssetToolkitCopyTool.ps1[Asset Toolkit: Unreal Asset Copy Tool] to help with this process. Simply download the script, place it in the **root** of your (backup) **Starter Project** folder and run it. It will copy all the necessary files for you. It is guided, interactive and easy to use. It comes with logs in case something goes wrong. +==== + +In case you want to do it manually, here is how to do it: + The list of files to move is -https://github.com/satisfactorymodding/UnrealProjectUpdater/blob/master/CustomAssets.txt[in the CustomAssets.txt file of the UnrealProjectUpdater repository], -except skip any texture assets (usually those starting with `TX_`) mentioned in the list, +https://github.com/satisfactorymodding/UnrealProjectUpdater/blob/master/CustomAssets.txt[in the CustomAssets.txt file of the UnrealProjectUpdater repository], except skip any texture assets (usually those starting with `TX_`) mentioned in the list, because the generator's copy of textures is more accurate. You should also bring over the `Content/Localization/StringTables` folder because they are stored raw in the pak file (not the utoc/ucas) and the automation does not currently extract them. @@ -406,7 +413,7 @@ because it adds a significant amount of time to game startup. If you'd like to learn more about the Asset Toolkit, you can continue reading below. -=== Asset Generator Commandlet Documentation +=== Asset Generator Commandlet Documentation Here is an explanation of what the various commandlet options do, written by Archengius: