Imports System Imports System.IO Imports System.Collections.Generic Imports System.Diagnostics Module DdsExtractor ' DDS 文件头标识 Private ReadOnly DDS_HEADER As Byte() = {&H44, &H44, &H53, &H20} ' "DDS " Private ReadOnly POF_MARKER As String = "POF" Public Const Version As String = "v1.1.2" Dim currentPath As String = AppDomain.CurrentDomain.BaseDirectory Dim targetExePath As String = Path.Combine(currentPath, "DDSPatcher.exe") Sub Main() Console.ForegroundColor = ConsoleColor.DarkCyan Console.WriteLine($"DDS 文件提取工具 {Version} by ChilorXN.") Console.ForegroundColor = ConsoleColor.DarkYellow Console.WriteLine("请拖放要处理的 .afb 或 .svo 文件到窗口,或输入文件路径(支持多个文件)") Console.ForegroundColor = ConsoleColor.White Console.WriteLine("输入 'Patcher' 启动同目录下的DDS修补工具") Console.WriteLine("输入 'exit' 退出程序") ' 持续处理循环 While True Console.WriteLine() Console.ForegroundColor = ConsoleColor.Blue Console.Write("[Extractor]") Console.ForegroundColor = ConsoleColor.White Console.Write("> ") Dim input As String = Console.ReadLine() ' 检查特殊命令 Select Case input.Trim().ToLower() Case "patcher" Console.WriteLine($"当前路径:{currentPath}") If File.Exists(targetExePath) Then Console.ForegroundColor = ConsoleColor.Green Console.WriteLine("正在启动...") Console.ForegroundColor = ConsoleColor.White Try ' 使用Process启动程序(不等待退出) Dim processInfo As New ProcessStartInfo() With { .FileName = targetExePath, .UseShellExecute = True ' 使用Shell执行可以避免阻塞 } Process.Start(processInfo) Console.WriteLine("已尝试启动DDSPatcher") Catch ex As Exception Console.ForegroundColor = ConsoleColor.Red Console.WriteLine($"启动DDS修补工具时出错:{ex.Message}") Console.ForegroundColor = ConsoleColor.White End Try Else Console.ForegroundColor = ConsoleColor.Red Console.WriteLine("错误:在当前目录下未找到DDSPatcher.exe,请确认您是否已经将其放入DDSExtractor所在的文件夹内") Console.ForegroundColor = ConsoleColor.White End If Continue While Case "clear" Console.Clear() Continue While Case "help", "about", "version" Console.ForegroundColor = ConsoleColor.DarkCyan Console.WriteLine($"DDS 文件提取工具 {Version} by ChilorXN.") Console.ForegroundColor = ConsoleColor.DarkYellow Console.WriteLine("请拖放要处理的 .afb 或 .svo 文件到窗口,或输入文件路径(支持多个文件)") Console.ForegroundColor = ConsoleColor.White Console.WriteLine("输入 'Patcher' 启动同目录下的DDS修补工具") Console.WriteLine("输入 'clear' 清空屏幕") Console.WriteLine("输入 'help' 再次查看帮助") Console.WriteLine("输入 'exit' 退出程序") Continue While Case "exit", "quit" Exit While End Select ' 处理输入的文件 ProcessInputFiles(input) End While Console.WriteLine("程序已退出") End Sub Private Sub ProcessInputFiles(input As String) ' 处理拖放的文件路径(Windows终端会用引号包裹带空格的文件路径) Dim filePaths As New List(Of String)() Dim inQuotes As Boolean = False Dim currentPath As New System.Text.StringBuilder() For Each c As Char In input If c = """"c Then If inQuotes Then ' 结束引号包裹的路径 filePaths.Add(currentPath.ToString()) currentPath.Clear() inQuotes = False Else ' 开始引号包裹的路径 inQuotes = True End If ElseIf Not inQuotes AndAlso Char.IsWhiteSpace(c) Then ' 非引号包裹的空格分隔符 If currentPath.Length > 0 Then filePaths.Add(currentPath.ToString()) currentPath.Clear() End If Else ' 添加到当前路径 currentPath.Append(c) End If Next ' 添加最后一个路径 If currentPath.Length > 0 Then filePaths.Add(currentPath.ToString()) End If ' 处理每个文件 For Each filePath In filePaths If Not String.IsNullOrWhiteSpace(filePath) Then Try ProcessFile(filePath) Catch ex As Exception Console.ForegroundColor = ConsoleColor.Red Console.WriteLine($"处理文件 {filePath} 时出错: {ex.Message}") Console.ForegroundColor = ConsoleColor.White End Try End If Next End Sub Private Sub ProcessFile(filePath As String) If Not File.Exists(filePath) Then Console.ForegroundColor = ConsoleColor.Red Console.WriteLine($"文件不存在: {filePath}") Console.ForegroundColor = ConsoleColor.White Return End If Dim extension As String = Path.GetExtension(filePath).ToLower() If extension <> ".afb" AndAlso extension <> ".svo" Then Console.ForegroundColor = ConsoleColor.Red Console.WriteLine($"不支持的文件类型: {filePath} (仅支持 .afb 和 .svo)") Console.ForegroundColor = ConsoleColor.White Return End If Console.WriteLine($"正在处理文件: {filePath}") Dim fileData As Byte() = File.ReadAllBytes(filePath) Dim ddsList As List(Of Byte()) = ExtractDdsFiles(fileData, extension = ".afb") Console.WriteLine($"找到 {ddsList.Count} 个 DDS 文件") ' 保存提取的 DDS 文件 Dim baseName As String = Path.GetFileNameWithoutExtension(filePath) Dim outputDir As String = Path.Combine(Path.GetDirectoryName(filePath), $"{baseName}_extracted") Directory.CreateDirectory(outputDir) For i As Integer = 0 To ddsList.Count - 1 Dim outputPath As String = Path.Combine(outputDir, $"{baseName}_{i + 1}.dds") File.WriteAllBytes(outputPath, ddsList(i)) Console.ForegroundColor = ConsoleColor.Green Console.WriteLine($"已保存: {outputPath}") Console.ForegroundColor = ConsoleColor.White Next End Sub Private Function ExtractDdsFiles(fileData As Byte(), isAfbFile As Boolean) As List(Of Byte()) Dim ddsFiles As New List(Of Byte())() Dim position As Integer = 0 While position < fileData.Length - 4 ' 检查是否是 DDS 文件头 If fileData(position) = DDS_HEADER(0) AndAlso fileData(position + 1) = DDS_HEADER(1) AndAlso fileData(position + 2) = DDS_HEADER(2) AndAlso fileData(position + 3) = DDS_HEADER(3) Then ' 查找下一个 DDS 文件头或结束标记 Dim nextDdsPos As Integer = FindNextDdsHeader(fileData, position + 4) Dim endPos As Integer = If(nextDdsPos <> -1, nextDdsPos, fileData.Length) ' 对于 AFB 文件,检查是否有 POF 标记 If isAfbFile AndAlso nextDdsPos = -1 Then Dim pofPos As Integer = FindPofMarker(fileData, position + 4) If pofPos <> -1 Then endPos = pofPos End If End If ' 提取 DDS 数据 Dim ddsLength As Integer = endPos - position Dim ddsData(ddsLength - 1) As Byte Array.Copy(fileData, position, ddsData, 0, ddsLength) ddsFiles.Add(ddsData) position = endPos Else position += 1 End If End While Return ddsFiles End Function Private Function FindNextDdsHeader(data As Byte(), startPos As Integer) As Integer For i As Integer = startPos To data.Length - 4 If data(i) = DDS_HEADER(0) AndAlso data(i + 1) = DDS_HEADER(1) AndAlso data(i + 2) = DDS_HEADER(2) AndAlso data(i + 3) = DDS_HEADER(3) Then Return i End If Next Return -1 End Function Private Function FindPofMarker(data As Byte(), startPos As Integer) As Integer ' POF 标记是 ASCII 字符串 "POF" For i As Integer = startPos To data.Length - 3 If data(i) = AscW("P"c) AndAlso data(i + 1) = AscW("O"c) AndAlso data(i + 2) = AscW("F"c) Then Return i End If Next Return -1 End Function End Module