207 lines
7.1 KiB
PowerShell
207 lines
7.1 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$ServerInstance,
|
|
[Parameter(Mandatory = $true)][string]$GlobalBackup,
|
|
[Parameter(Mandatory = $true)][string]$ObjectBackup,
|
|
[Parameter(Mandatory = $true)][string]$OutputFile
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$globalDatabase = "HealerMan_RW_ROM_Global_Forensic"
|
|
$objectDatabase = "HealerMan_RW_ObjectData_Forensic"
|
|
|
|
function Quote-SqlLiteral([string]$Value) {
|
|
return "N'" + $Value.Replace("'", "''") + "'"
|
|
}
|
|
|
|
function Quote-SqlIdentifier([string]$Value) {
|
|
return "[" + $Value.Replace("]", "]]") + "]"
|
|
}
|
|
|
|
function Invoke-SqlTable(
|
|
[System.Data.SqlClient.SqlConnection]$Connection,
|
|
[string]$CommandText
|
|
) {
|
|
$command = $Connection.CreateCommand()
|
|
$command.CommandTimeout = 300
|
|
$command.CommandText = $CommandText
|
|
$table = [System.Data.DataTable]::new()
|
|
$reader = $command.ExecuteReader()
|
|
try {
|
|
$table.Load($reader)
|
|
}
|
|
finally {
|
|
$reader.Dispose()
|
|
$command.Dispose()
|
|
}
|
|
return ,$table
|
|
}
|
|
|
|
function Invoke-SqlNonQuery(
|
|
[System.Data.SqlClient.SqlConnection]$Connection,
|
|
[string]$CommandText
|
|
) {
|
|
$command = $Connection.CreateCommand()
|
|
$command.CommandTimeout = 600
|
|
$command.CommandText = $CommandText
|
|
try {
|
|
[void]$command.ExecuteNonQuery()
|
|
}
|
|
finally {
|
|
$command.Dispose()
|
|
}
|
|
}
|
|
|
|
function Remove-ForensicDatabase(
|
|
[System.Data.SqlClient.SqlConnection]$Connection,
|
|
[string]$DatabaseName
|
|
) {
|
|
$identifier = Quote-SqlIdentifier $DatabaseName
|
|
Invoke-SqlNonQuery $Connection @"
|
|
IF DB_ID($(Quote-SqlLiteral $DatabaseName)) IS NOT NULL
|
|
BEGIN
|
|
ALTER DATABASE $identifier SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
|
|
DROP DATABASE $identifier;
|
|
END
|
|
"@
|
|
}
|
|
|
|
function Restore-ForensicDatabase(
|
|
[System.Data.SqlClient.SqlConnection]$Connection,
|
|
[string]$DatabaseName,
|
|
[string]$BackupFile,
|
|
[string]$DataDirectory,
|
|
[string]$LogDirectory
|
|
) {
|
|
if (-not (Test-Path -LiteralPath $BackupFile -PathType Leaf)) {
|
|
throw "Backup not found: $BackupFile"
|
|
}
|
|
$files = Invoke-SqlTable $Connection (
|
|
"RESTORE FILELISTONLY FROM DISK = " + (Quote-SqlLiteral $BackupFile)
|
|
)
|
|
if ($files.Rows.Count -eq 0) {
|
|
throw "Backup contains no logical files: $BackupFile"
|
|
}
|
|
$moves = [System.Collections.Generic.List[string]]::new()
|
|
$dataIndex = 0
|
|
$logIndex = 0
|
|
foreach ($file in $files.Rows) {
|
|
$isLog = [string]$file.Type -eq "L"
|
|
if ($isLog) {
|
|
$physical = Join-Path $LogDirectory ($DatabaseName + "_" + $logIndex + ".ldf")
|
|
$logIndex += 1
|
|
}
|
|
else {
|
|
$extension = if ($dataIndex -eq 0) { ".mdf" } else { ".ndf" }
|
|
$physical = Join-Path $DataDirectory ($DatabaseName + "_" + $dataIndex + $extension)
|
|
$dataIndex += 1
|
|
}
|
|
$moves.Add("MOVE " + (Quote-SqlLiteral ([string]$file.LogicalName)) + " TO " + (Quote-SqlLiteral $physical))
|
|
}
|
|
Remove-ForensicDatabase $Connection $DatabaseName
|
|
$newline = [Environment]::NewLine
|
|
$restoreSql = @(
|
|
"RESTORE DATABASE " + (Quote-SqlIdentifier $DatabaseName),
|
|
"FROM DISK = " + (Quote-SqlLiteral $BackupFile),
|
|
"WITH REPLACE, RECOVERY, STATS = 10,",
|
|
($moves -join (',' + $newline))
|
|
) -join $newline
|
|
Invoke-SqlNonQuery $Connection $restoreSql
|
|
Invoke-SqlNonQuery $Connection ("ALTER DATABASE " + (Quote-SqlIdentifier $DatabaseName) + " SET READ_ONLY WITH ROLLBACK IMMEDIATE")
|
|
}
|
|
|
|
$connectionString = "Server=" + $ServerInstance + ";Database=master;Integrated Security=SSPI;Encrypt=False;TrustServerCertificate=True;Connect Timeout=15;Application Name=HealerMan RuneWaker Forensics"
|
|
$connection = [System.Data.SqlClient.SqlConnection]::new($connectionString)
|
|
$globalRestored = $false
|
|
$objectRestored = $false
|
|
try {
|
|
$connection.Open()
|
|
$paths = Invoke-SqlTable $connection @"
|
|
SELECT
|
|
CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS nvarchar(4000)) AS DataPath,
|
|
CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS nvarchar(4000)) AS LogPath,
|
|
(SELECT TOP (1) physical_name FROM sys.master_files WHERE database_id = 1 AND file_id = 1) AS MasterFile;
|
|
"@
|
|
$dataDirectory = [string]$paths.Rows[0].DataPath
|
|
$logDirectory = [string]$paths.Rows[0].LogPath
|
|
if ([string]::IsNullOrWhiteSpace($dataDirectory)) {
|
|
$dataDirectory = Split-Path -Parent ([string]$paths.Rows[0].MasterFile)
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($logDirectory)) {
|
|
$logDirectory = $dataDirectory
|
|
}
|
|
|
|
Restore-ForensicDatabase $connection $globalDatabase $GlobalBackup $dataDirectory $logDirectory
|
|
$globalRestored = $true
|
|
Restore-ForensicDatabase $connection $objectDatabase $ObjectBackup $dataDirectory $logDirectory
|
|
$objectRestored = $true
|
|
|
|
$globalId = Quote-SqlIdentifier $globalDatabase
|
|
$objectId = Quote-SqlIdentifier $objectDatabase
|
|
$table = Invoke-SqlTable $connection @"
|
|
SELECT
|
|
d.DBID AS spawn_id,
|
|
d.OrgObjID AS template_id,
|
|
d.ZoneID AS zone_id,
|
|
d.RoomID AS room_id,
|
|
CONVERT(float, d.X) AS source_x,
|
|
CONVERT(float, d.Y) AS source_y,
|
|
CONVERT(float, d.Z) AS source_z,
|
|
CONVERT(float, d.Dir) AS direction,
|
|
ISNULL(d.RoleName, '') AS role_name,
|
|
ISNULL(d.AutoPlot, '') AS auto_plot,
|
|
ISNULL(d.PlotClassName, '') AS plot_class_name,
|
|
ISNULL(n.sex, 0) AS sex,
|
|
ISNULL(n.level, 0) AS native_level,
|
|
ISNULL(s.Content, '') AS localized_name
|
|
FROM $globalId.dbo.NPCData d
|
|
LEFT JOIN $objectId.dbo.NPCObjectDB n ON n.guid = d.OrgObjID
|
|
LEFT JOIN $objectId.dbo.StringDB s
|
|
ON s.KeyStr = 'Sys' + CAST(d.OrgObjID AS varchar) + '_name'
|
|
WHERE d.ZoneID = 102
|
|
AND d.IsDelflag = 0
|
|
ORDER BY d.DBID;
|
|
"@
|
|
$rows = foreach ($row in $table.Rows) {
|
|
[ordered]@{
|
|
spawnId = [int64]$row.spawn_id
|
|
templateId = [int]$row.template_id
|
|
zoneId = [int]$row.zone_id
|
|
roomId = [int]$row.room_id
|
|
sourcePosition = @(
|
|
[Convert]::ToDouble($row.source_x),
|
|
[Convert]::ToDouble($row.source_y),
|
|
[Convert]::ToDouble($row.source_z)
|
|
)
|
|
direction = [Convert]::ToDouble($row.direction)
|
|
roleName = [string]$row.role_name
|
|
autoPlot = [string]$row.auto_plot
|
|
plotClassName = [string]$row.plot_class_name
|
|
sex = [int]$row.sex
|
|
nativeLevel = [int]$row.native_level
|
|
localizedName = [string]$row.localized_name
|
|
}
|
|
}
|
|
$payload = [ordered]@{
|
|
schemaVersion = 1
|
|
queryVersion = 1
|
|
zoneId = 102
|
|
rowCount = @($rows).Count
|
|
rows = @($rows)
|
|
}
|
|
$outputDirectory = Split-Path -Parent $OutputFile
|
|
if ($outputDirectory) {
|
|
[void](New-Item -ItemType Directory -Path $outputDirectory -Force)
|
|
}
|
|
$payload | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $OutputFile -Encoding UTF8
|
|
Write-Host ("Exported {0} active Zone 102 rows." -f @($rows).Count)
|
|
}
|
|
finally {
|
|
if ($connection.State -eq [System.Data.ConnectionState]::Open) {
|
|
if ($objectRestored) { Remove-ForensicDatabase $connection $objectDatabase }
|
|
if ($globalRestored) { Remove-ForensicDatabase $connection $globalDatabase }
|
|
$connection.Close()
|
|
}
|
|
$connection.Dispose()
|
|
}
|