1
General Discussion / Re: [FF7] Command Line Save Converter or Metadata.xml Generator
« on: 2016-07-07 21:05:09 »
Once more with some bigger improvements:
* Profile ID extrapolated from FF7 Save Directory automatically
* Can change the save slot being synchronized, seems to work in my tests -- can't guarantee other save files in your .mcd will survive
* md5salt.bin Automatically generated at runtime from Profile ID
* local/remote.txt/.cmd generated at runtime (effectively making the script only require ff7save.exe + the icon dat and the PSFTP module to succeed), and only as needed
* Lots more here and there cleanup, a couple of minor bug fixes.
Again, who knows if this will be useful to anyone else, but it'd be a shame to have done all this work and not post it in case someone else has a similar need.
Known issues:
* Still doesn't set the modified date on the uploaded FTP file, looks like that's a limitation of the PSFTP module. I'm investigating other ways to fix that.
* Profile ID extrapolated from FF7 Save Directory automatically
* Can change the save slot being synchronized, seems to work in my tests -- can't guarantee other save files in your .mcd will survive
* md5salt.bin Automatically generated at runtime from Profile ID
* local/remote.txt/.cmd generated at runtime (effectively making the script only require ff7save.exe + the icon dat and the PSFTP module to succeed), and only as needed
* Lots more here and there cleanup, a couple of minor bug fixes.
Again, who knows if this will be useful to anyone else, but it'd be a shame to have done all this work and not post it in case someone else has a similar need.
Known issues:
* Still doesn't set the modified date on the uploaded FTP file, looks like that's a limitation of the PSFTP module. I'm investigating other ways to fix that.
Code: [Select]
<#
.SYNOPSIS
FF7 PC to Gaming Device Synchronizer
.DESCRIPTION
Connects via FTP to a compatible gaming device that utilizes a common format for memory cards, compares the age of the
targeted save file versus the age of the save file in the local save path and then synchronizes the two save files,
including the conversion between file types and the modification of the metadata.xml file.
.NOTES
Author: n1ckn4m3 - [email protected]
.DEPENDENCIES
PSFTP Module by Michal Gajda: https://gallery.technet.microsoft.com/scriptcenter/PowerShell-FTP-Client-db6fe0cb
FF7Save Converter: http://www.zophar.net/utilities/genutil/ff7-savegame-converter.html
.USAGE
* Download and Import the PSFTP Module and ensure that Import-Module PSFTP successfully completes
* Create a working directory for the script and copy the contents of the FF7Save.zip to that directory, as well as this script
** Make sure that the user account running the script has write access to the working directory you've chosen, else you'll need to elevate privileges for the script to work
* Update the script variables to be accurate for your environment. Key ones include $WorkingDir, $SaveSlot, $AndroidSave, $AndroidFTP, $AndroidDir, $UserName, and $Password
* MAKE BACKUP COPIES OF YOUR SAVE FILES FIRST. The script does this, but considering it replaces files, better safe than sorry. I accept no liability if you overwrite
saves or nuke a 400 hour perfect game, or if your HDD turns into a megalomanical robot trying to take over the world. In short, use at your own risk. Provided free with
no warranty of fitness for any purpose, implied or otherwise.
#>
# I tried doing this without this module and it was hacky, ugly, and stupid. Thanks, guy who wrote this module.
Import-Module PSFTP
# Working Directory. Change this to whatever you'd like, just make sure it has the FF7 Save converter in it, that it exists, and that it's writable by your user account.
$WorkingDir = ([environment]::getfolderpath(“mydocuments”)) + "\Square Enix\FF7 Save Sync\"
# General Script Variables
$SaveSlot = "00" # Use the filename's save slot ID, e.g., for save00.ff7, use 00 - NOT 1 - the script relies on this
$SaveDirs = Get-ChildItem ([environment]::getfolderpath(“mydocuments”))"Square Enix\Final Fantasy VII Steam\"
$LocalSave = $SaveDirs[1].FullName + "\save" + $SaveSlot + ".ff7"
$LocalBackup = $LocalSave + ".backup"
$WorkingLocalSave = $WorkingDir + "save" + $SaveSlot + ".ff7"
$LocalSaveFile = "save"+$SaveSlot+".ff7"
# Metadata.XML Specific Variables
$MD5Salt = $WorkingDir + "md5salt.bin"
$MetadataXML = $SaveDirs[1].FullName + "\Metadata.xml"
$MetadataBackup = $MetadataXML + ".backup"
$WorkingMetadataXML = $WorkingDir + "Metadata.xml"
$WorkingLocalMD5 = $WorkingDir + "save" + $SaveSlot + ".ff7.md5"
# Metadata.XML Lookup - Save Slot to XML Line ID
If ($SaveSlot -eq "00"){ $XMLLine = 18 ; $SaveID = 1}
If ($SaveSlot -eq "01"){ $XMLLine = 36 ; $SaveID = 2}
If ($SaveSlot -eq "02"){ $XMLLine = 54 ; $SaveID = 3}
If ($SaveSlot -eq "03"){ $XMLLine = 72 ; $SaveID = 4}
If ($SaveSlot -eq "04"){ $XMLLine = 90 ; $SaveID = 5}
If ($SaveSlot -eq "05"){ $XMLLine = 108 ; $SaveID = 6}
If ($SaveSlot -eq "06"){ $XMLLine = 126 ; $SaveID = 7}
If ($SaveSlot -eq "07"){ $XMLLine = 144 ; $SaveID = 8}
If ($SaveSlot -eq "08"){ $XMLLine = 162 ; $SaveID = 9}
If ($SaveSlot -eq "09"){ $XMLLine = 180 ; $SaveID = 10}
# Android .MCD file locations and names, update to reflect yours. Backup should be in the same directory to streamline but I suppose it would be fine anywhere.
$AndroidDir = "/mnt/external_sd/MEMCARDS/"
$AndroidSaveFile = "ff7_pc.mcd"
$AndroidSave = $AndroidDir + $AndroidSaveFile
$AndroidBackup = $AndroidDir + $AndroidSaveFile + ".backup"
$WorkingAndroidSave = $WorkingDir + $AndroidSaveFile
$WorkingAndroidBackup = $WorkingDir + $AndroidSaveFile + ".backup"
#FTP Support Variables, update to reflect your specifics.
$AndroidFTP="ftp://gpdxd:31337"
$UserName = "<username>"
$Password = "android" | ConvertTo-SecureString -asPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($UserName,$Password)
# Set Save Sync Directory
Set-Location $WorkingDir
function Cleanup () {
If (Test-Path $WorkingLocalSave){Remove-Item $WorkingLocalSave}
If (Test-Path $WorkingAndroidSave){Remove-Item $WorkingAndroidSave}
If (Test-Path $WorkingAndroidBackup){Remove-Item $WorkingAndroidBackup}
If (Test-Path $WorkingMetadataXML){Remove-Item $WorkingMetadataXML}
If (Test-Path $WorkingDir\local.txt){Remove-Item $WorkingDir\local.txt}
If (Test-Path $WorkingDir\remote.txt){Remove-Item $WorkingDir\remote.txt}
If (Test-Path $WorkingDir\local.cmd){Remove-Item $WorkingDir\local.cmd}
If (Test-Path $WorkingDir\remote.cmd){Remove-Item $WorkingDir\remote.cmd}
}
# Delete working copies of saves if they exist due to a script failure or bad cleanup
Cleanup
# Copy Local Save to Save Sync Directory
Copy-Item $LocalSave $WorkingLocalSave -ErrorAction SilentlyContinue
#Initiate Connection to Android Device, break script if we can't connect.
Try {
Set-FTPConnection -Credentials $Credential -Server $AndroidFTP -Session AndroidFF7SyncSession -UsePassive -ErrorAction Stop | Out-Null
}
Catch {
Write-Host "No response from FTP server, check variables and network connectivity. Aborting synchronization."
# Clean up after ourselves, even if stuff goes south pretty early.
Cleanup
Break
}
# Since we know we can connect now, let's finish setting up the FTP session.
$FTPSession = Get-FTPConnection -Session AndroidFF7SyncSession
# Download Remote Save to Save Sync Directory and create working copy of backup
Get-FTPItem -Session $FTPSession -Path $AndroidSave -LocalPath $WorkingAndroidSave | Out-Null
Copy-Item $WorkingAndroidSave $WorkingAndroidBackup
# Check Timestamp of Remote Save vs. Local Save
$RemoteFullFile = $AndroidDir + $AndroidSaveFile
$RemoteTimestamp = [datetime](Get-FTPChildItem -Session $FTPSession -Path $RemoteFullFile).ModifiedDate
$LocalTimestamp = [datetime](Get-ItemProperty -Path $WorkingLocalSave -Name LastWriteTime).lastwritetime
# If Remote Save is newer, back up local save and replace
If ($RemoteTimestamp -gt $LocalTimestamp) {
# Create remote.txt and .cmd to launch and control FF7 Converter
$RemoteTXT="1",$AndroidSaveFile,$SaveID,"4",$LocalSaveFile,$SaveID,"1"
$RemoteTXT | Set-Content -Path $WorkingDir\remote.txt
$RemoteCMD = "ff7save < remote.txt"
$RemoteCMD | Set-Content -Path $WorkingDir\remote.cmd
# Remove the local backup, then convert the new save and copy it.
If (Test-Path $LocalBackup){Remove-Item $LocalBackup}
Copy-Item $WorkingLocalSave $LocalBackup -ErrorAction SilentlyContinue
CMD /C "remote.cmd" | Out-Null # Convert .MCD format FF7 save to .FF7 save format save
Remove-Item $LocalSave -ErrorAction SilentlyContinue
Copy-Item $WorkingLocalSave $LocalSave
Write-Host "Remote save was newer. If one existed, local save has been backed up and replaced."
# Generate MD5 "salt" (not really a salt, but whatever) for Metadata.XML generation later
$ProfileID = $SaveDirs[1].Name
$ProfileID = $ProfileID.Trim("user_")
$ProfileIDArray = $ProfileID.ToCharArray();
$Bytes = New-Object Byte[] 7
$HEXArray=[int[]][char[]]$ProfileIDArray
$Loop=0
ForEach ($HEXLine in $HEXArray){
$Bytes[$Loop]=$HEXLine
$Loop = $Loop + 1
}
[IO.File]::WriteAllBytes($MD5Salt,$Bytes)
# Metadata.XML support code
$COPYCMD = "copy /b save" + $SaveSlot + ".ff7+md5salt.bin save" + $SaveSlot + ".ff7.md5"
CMD /C $COPYCMD | Out-Null # Concatenate existing save file w/ md5 "salt" to allow signature generation
$HashPath = "save" + $SaveSlot + ".ff7.md5"
$MD5Hash = (Get-FileHash -Path $HashPath -Algorithm md5).Hash
$PreppedHash = " <signature>"+$MD5Hash.ToLower()+"</signature>"
$MetadataContent = Get-Content -Path $MetadataXML
$MetadataContent[$XMLLine] = $PreppedHash
Copy-Item $MetadataXML $MetadataBackup
$MetadataContent | Set-Content -Path $MetadataXML
# Re-set proper modified time, as downloading it changed it to 'right now'.
$ModifiedSave = Get-Item $LocalSave
$ModifiedSave.LastWriteTime = $RemoteTimestamp
}
# If Local save is newer, back up remote save and replace
If ($LocalTimestamp -gt $RemoteTimestamp) {
# Create local.txt and .cmd to launch and control FF7 Converter
$LocalTXT="4",$LocalSaveFile,$SaveID,"1",$AndroidSaveFile,$SaveID,"1"
$LocalTXT | Set-Content -Path $WorkingDir\local.txt
$LocalCMD = "ff7save < local.txt"
$LocalCMD | Set-Content -Path $WorkingDir\local.cmd
# Create/replace backup save and convert new save to proper format, then upload
Add-FTPItem -Session $FTPSession -Path $AndroidDir -LocalPath $WorkingAndroidBackup -Overwrite | Out-Null
CMD /C "local.cmd" | Out-Null # Convert .ff7 format FF7 save to .MCD save format save
Add-FTPItem -Session $FTPSession -Path $AndroidDir -LocalPath $WorkingAndroidSave -Overwrite | Out-Null
Write-Host "Local save was newer. Remote save has been backed up and replaced."
}
If ($LocalTimestamp -eq $RemoteTimestamp) {
Write-Host "Timestamps the same, sync complete."
}
# Clean up after ourselves.
Cleanup