From 417672c83c48761fd87e08c5067b48d50e62f586 Mon Sep 17 00:00:00 2001 From: ArthurHeitmann <37270165+ArthurHeitmann@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:41:03 +0200 Subject: [PATCH] working wta/wtp/wtb export --- lib/fileTypeUtils/wta/wtaExtractor.dart | 9 +- lib/fileTypeUtils/wta/wtaReader.dart | 109 +++++++----------- .../hierarchy/types/DatHierarchyEntry.dart | 3 +- .../openFiles/types/FtbFileData.dart | 3 +- .../openFiles/types/McdFileData.dart | 2 +- .../openFiles/types/WtaWtpData.dart | 35 +++--- lib/widgets/filesView/types/wtaWtpEditor.dart | 4 +- 7 files changed, 76 insertions(+), 89 deletions(-) diff --git a/lib/fileTypeUtils/wta/wtaExtractor.dart b/lib/fileTypeUtils/wta/wtaExtractor.dart index 3ae782d3..632ccc49 100644 --- a/lib/fileTypeUtils/wta/wtaExtractor.dart +++ b/lib/fileTypeUtils/wta/wtaExtractor.dart @@ -13,7 +13,7 @@ Future> extractWta(String wtaPath, String? wtpPath, bool isWtb) asy var wtpFile = await File(isWtb ? wtaPath : wtpPath!).open(); try { for (int i = 0; i < wta.textureOffsets.length; i++) { - var texturePath = join(extractDir, "${i}_${wta.textureIdx[i].toRadixString(16).padLeft(8, "0")}.dds"); + var texturePath = getWtaTexturePath(wta, i, extractDir); texturePaths.add(texturePath); await wtpFile.setPosition(wta.textureOffsets[i]); var textureBytes = await wtpFile.read(wta.textureSizes[i]); @@ -24,3 +24,10 @@ Future> extractWta(String wtaPath, String? wtpPath, bool isWtb) asy } return texturePaths; } + +String getWtaTexturePath(WtaFile wta, int i, String extractDir) { + String idStr = ""; + if (wta.textureIdx != null) + idStr = "_${wta.textureIdx![i].toRadixString(16).padLeft(8, "0")}"; + return join(extractDir, "$i$idStr.dds"); +} diff --git a/lib/fileTypeUtils/wta/wtaReader.dart b/lib/fileTypeUtils/wta/wtaReader.dart index 8d4ea18f..5a414acf 100644 --- a/lib/fileTypeUtils/wta/wtaReader.dart +++ b/lib/fileTypeUtils/wta/wtaReader.dart @@ -1,9 +1,9 @@ -import '../../stateManagement/events/statusInfo.dart'; import '../../utils/utils.dart'; import '../utils/ByteDataWrapper.dart'; class WtaFileHeader { + static const size = 32; String id; int unknown; int numTex; @@ -11,7 +11,17 @@ class WtaFileHeader { int offsetTextureSizes; int offsetTextureFlags; int offsetTextureIdx; - int offsetTextureInfo; + int offsetEnd; + + WtaFileHeader.empty() : + id = "WTB\x00", + unknown = 1, + numTex = 0, + offsetTextureOffsets = 0, + offsetTextureSizes = 0, + offsetTextureFlags = 0, + offsetTextureIdx = 0, + offsetEnd = 0; WtaFileHeader.read(ByteDataWrapper bytes) : id = bytes.readString(4), @@ -21,7 +31,7 @@ class WtaFileHeader { offsetTextureSizes = bytes.readUint32(), offsetTextureFlags = bytes.readUint32(), offsetTextureIdx = bytes.readUint32(), - offsetTextureInfo = bytes.readUint32(); + offsetEnd = bytes.readUint32(); void write(ByteDataWrapper bytes) { bytes.writeString(id); @@ -31,54 +41,14 @@ class WtaFileHeader { bytes.writeUint32(offsetTextureSizes); bytes.writeUint32(offsetTextureFlags); bytes.writeUint32(offsetTextureIdx); - bytes.writeUint32(offsetTextureInfo); + bytes.writeUint32(offsetEnd); } -} - -class WtaFileTextureInfo { - late int format; - late List data; - WtaFileTextureInfo(this.format, this.data); - - WtaFileTextureInfo.read(ByteDataWrapper bytes) : - format = bytes.readUint32(), - data = bytes.readUint32List(4); - - static Future fromDds(String ddsPath) async { - var dds = await ByteDataWrapper.fromFile(ddsPath); - dds.position = 84; - var dxt = dds.readString(4); - dds.position = 112; - var cube = dds.readUint32(); - if (!const ["DXT1", "DXT3", "DXT5"].contains(dxt)) - messageLog.add("Warning: $ddsPath uses unknown DDS format $dxt. This may not work."); - var isCube = cube == 0xFE00; - - int format = 0; - List data = [3, isCube ? 4 : 0, 1, 0]; - switch (dxt) { - case "DXT1": - format = 71; - break; - case "DXT3": - format = 74; - break; - case "DXT5": - format = 77; - break; - default: - format = 87; - break; - } - - return WtaFileTextureInfo(format, data); - } - - void write(ByteDataWrapper bytes) { - bytes.writeUint32(format); - for (var d in data) - bytes.writeUint32(d); + int getFileEnd() { + if (offsetEnd != 0) + return offsetEnd; + else + return offsetTextureIdx + numTex * 4; } } @@ -87,11 +57,12 @@ class WtaFile { late List textureOffsets; late List textureSizes; late List textureFlags; - late List textureIdx; - late List textureInfo; + List? textureIdx; static const int albedoFlag = 0x26000020; static const int noAlbedoFlag = 0x22000020; + + WtaFile(this.header, this.textureOffsets, this.textureSizes, this.textureFlags, this.textureIdx); WtaFile.read(ByteDataWrapper bytes) { header = WtaFileHeader.read(bytes); @@ -101,13 +72,10 @@ class WtaFile { textureSizes = bytes.readUint32List(header.numTex); bytes.position = header.offsetTextureFlags; textureFlags = bytes.readUint32List(header.numTex); - bytes.position = header.offsetTextureIdx; - textureIdx = bytes.readUint32List(header.numTex); - bytes.position = header.offsetTextureInfo; - textureInfo = List.generate( - header.numTex, - (i) => WtaFileTextureInfo.read(bytes) - ); + if (header.offsetTextureIdx > 0) { + bytes.position = header.offsetTextureIdx; + textureIdx = bytes.readUint32List(header.numTex); + } } static Future readFromFile(String path) async { @@ -116,7 +84,8 @@ class WtaFile { } Future writeToFile(String path) async { - var fileSize = header.offsetTextureInfo + textureInfo.length * 0x14; + var fileSize = header.getFileEnd(); + fileSize = alignTo(fileSize, 32); var bytes = ByteDataWrapper.allocate(fileSize); header.write(bytes); @@ -132,14 +101,12 @@ class WtaFile { for (var i = 0; i < textureFlags.length; i++) bytes.writeUint32(textureFlags[i]); - bytes.position = header.offsetTextureIdx; - for (var i = 0; i < textureIdx.length; i++) - bytes.writeUint32(textureIdx[i]); + if (textureIdx != null) { + bytes.position = header.offsetTextureIdx; + for (var i = 0; i < textureIdx!.length; i++) + bytes.writeUint32(textureIdx![i]); + } - bytes.position = header.offsetTextureInfo; - for (var i = 0; i < textureInfo.length; i++) - textureInfo[i].write(bytes); - await bytes.save(path); } @@ -148,7 +115,13 @@ class WtaFile { header.offsetTextureOffsets = 0x20; header.offsetTextureSizes = alignTo(header.offsetTextureOffsets + textureOffsets.length * 4, 32); header.offsetTextureFlags = alignTo(header.offsetTextureSizes + textureSizes.length * 4, 32); - header.offsetTextureIdx = alignTo(header.offsetTextureFlags + textureFlags.length * 4, 32); - header.offsetTextureInfo = alignTo(header.offsetTextureIdx + textureIdx.length * 4, 32); + if (textureIdx != null) { + header.offsetTextureIdx = alignTo(header.offsetTextureFlags + textureFlags.length * 4, 32); + header.offsetEnd = 0; + } + else { + header.offsetTextureIdx = 0; + header.offsetEnd = alignTo(header.getFileEnd(), 32); + } } } diff --git a/lib/stateManagement/hierarchy/types/DatHierarchyEntry.dart b/lib/stateManagement/hierarchy/types/DatHierarchyEntry.dart index 9c7296bd..3c625738 100644 --- a/lib/stateManagement/hierarchy/types/DatHierarchyEntry.dart +++ b/lib/stateManagement/hierarchy/types/DatHierarchyEntry.dart @@ -46,9 +46,8 @@ class DatHierarchyEntry extends ExtractableHierarchyEntry { List> futures = []; datFilePaths ??= await getDatFileList(extractedPath); RubyScriptGroupHierarchyEntry? rubyScriptGroup; - const supportedFileEndings = { ".pak", "_scp.bin", ".tmd", ".smd", ".mcd", ".ftb", ".bnk", ".bxm", ".wta", ".wtb", ".est", ".sst", ...datExtensions }; + const supportedFileEndings = { ".pak", "_scp.bin", ".tmd", ".smd", ".mcd", ".ftb", ".bnk", ".wta", ".wtb", ".est", ".sst", ...bxmExtensions, ...datExtensions }; for (var file in datFilePaths) { - print(file); if (supportedFileEndings.every((ending) => !file.endsWith(ending))) continue; int existingChildI = existingChildren.indexWhere((entry) => (entry as FileHierarchyEntry).path == file); diff --git a/lib/stateManagement/openFiles/types/FtbFileData.dart b/lib/stateManagement/openFiles/types/FtbFileData.dart index 6ca5434e..449bf4ad 100644 --- a/lib/stateManagement/openFiles/types/FtbFileData.dart +++ b/lib/stateManagement/openFiles/types/FtbFileData.dart @@ -281,9 +281,8 @@ class FtbData extends ChangeNotifier { // add new idx, flag info entries for (int i = wtaFile.header.numTex; i < textureBatches.length; i++) { wtaFile.header.numTex++; - wtaFile.textureIdx.add(randomId()); + wtaFile.textureIdx!.add(randomId()); wtaFile.textureFlags.add(wtaFile.textureFlags.last); - wtaFile.textureInfo.add(wtaFile.textureInfo.last); } wtaFile.updateHeader(); await wtaFile.writeToFile(wtaPath); diff --git a/lib/stateManagement/openFiles/types/McdFileData.dart b/lib/stateManagement/openFiles/types/McdFileData.dart index 899a6018..4e545d01 100644 --- a/lib/stateManagement/openFiles/types/McdFileData.dart +++ b/lib/stateManagement/openFiles/types/McdFileData.dart @@ -655,7 +655,7 @@ class McdData extends _McdFilePart { // glyphs var wta = await WtaFile.readFromFile(textureWtaPath!.value); - var texId = wta.textureIdx.first; + var texId = wta.textureIdx!.first; var texSize = await getDdsFileSize(textureWtpPath!.value); var exportGlyphs = usedSymbols.map((usedSym) { var sym = usedFonts[usedSym.fontId]!.supportedSymbols[usedSym.code]!; diff --git a/lib/stateManagement/openFiles/types/WtaWtpData.dart b/lib/stateManagement/openFiles/types/WtaWtpData.dart index 670b17ab..454ea0e3 100644 --- a/lib/stateManagement/openFiles/types/WtaWtpData.dart +++ b/lib/stateManagement/openFiles/types/WtaWtpData.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:path/path.dart'; +import '../../../fileTypeUtils/wta/wtaExtractor.dart'; import '../../../fileTypeUtils/wta/wtaReader.dart'; import '../../../utils/Disposable.dart'; import '../../../utils/utils.dart'; @@ -109,13 +110,13 @@ class WtaWtpData extends OpenFileData { class WtaTextureEntry with HasUuid, Undoable implements Disposable { final OpenFileId file; - final HexProp id; + final HexProp? id; final StringProp path; final BoolProp? isAlbedo; final HexProp? flag; WtaTextureEntry(this.file, this.id, this.path, { this.isAlbedo, this.flag }) { - id.addListener(_onPropChange); + id?.addListener(_onPropChange); path.addListener(_onPropChange); isAlbedo?.addListener(_onPropChange); flag?.addListener(_onPropChange); @@ -127,7 +128,7 @@ class WtaTextureEntry with HasUuid, Undoable implements Disposable { @override void dispose() { - id.dispose(); + id?.dispose(); path.dispose(); isAlbedo?.dispose(); flag?.dispose(); @@ -141,7 +142,7 @@ class WtaTextureEntry with HasUuid, Undoable implements Disposable { Undoable takeSnapshot() { var snap = WtaTextureEntry( file, - id.takeSnapshot() as HexProp, + id?.takeSnapshot() as HexProp?, path.takeSnapshot() as StringProp, isAlbedo: isAlbedo?.takeSnapshot() as BoolProp?, flag: flag?.takeSnapshot() as HexProp?, @@ -153,7 +154,7 @@ class WtaTextureEntry with HasUuid, Undoable implements Disposable { @override void restoreWith(Undoable snapshot) { var snap = snapshot as WtaTextureEntry; - id.restoreWith(snap.id); + id?.restoreWith(snap.id!); path.restoreWith(snap.path); isAlbedo?.restoreWith(snap.isAlbedo!); flag?.restoreWith(snap.flag!); @@ -180,7 +181,8 @@ class WtaWtpTextures with HasUuid, Undoable implements Disposable { try { for (int i = 0; i < wta.textureOffsets.length; i++) { messageLog.add("Extracting texture ${i + 1}/${wta.textureOffsets.length}"); - var texturePath = join(extractDir, "${i}_${wta.textureIdx[i].toRadixString(16).padLeft(8, "0")}.dds"); + // var texturePath = join(extractDir, "${i}_${wta.textureIdx[i].toRadixString(16).padLeft(8, "0")}.dds"); + var texturePath = getWtaTexturePath(wta, i, extractDir); await wtpFile.setPosition(wta.textureOffsets[i]); var textureBytes = await wtpFile.read(wta.textureSizes[i]); await File(texturePath).writeAsBytes(textureBytes); @@ -192,7 +194,9 @@ class WtaWtpTextures with HasUuid, Undoable implements Disposable { flag = HexProp(wta.textureFlags[i], fileId: file); textures.add(WtaTextureEntry( file, - HexProp(wta.textureIdx[i], fileId: file), + wta.textureIdx != null + ? HexProp(wta.textureIdx![i], fileId: file) + : null, StringProp(texturePath, fileId: file), isAlbedo: isAlbedo, flag: flag, @@ -213,7 +217,15 @@ class WtaWtpTextures with HasUuid, Undoable implements Disposable { Future save() async { var wta = await WtaFile.readFromFile(wtaPath); - wta.textureIdx = List.generate(textures.length, (index) => textures[index].id.value); + if (wta.textureIdx != null) { + if (!textures.every((e) => e.id != null)) { + showToast("Mismatch: WTA has texture indices, but some textures are missing indices!"); + throw Exception("Mismatch: WTA has texture indices, but some textures are missing indices!"); + } + wta.textureIdx = List.generate(textures.length, (index) => textures[index].id!.value); + } + else + wta.textureIdx = null; wta.textureOffsets = List.filled(textures.length, -1); wta.textureFlags = List.generate( textures.length, @@ -225,15 +237,10 @@ class WtaWtpTextures with HasUuid, Undoable implements Disposable { return await File(e.path.value).length(); })); - // update texture infos - wta.textureInfo = await Future.wait(textures.map((e) async { - return await WtaFileTextureInfo.fromDds(e.path.value); - })); - wta.updateHeader(); // update offsets (4096 byte alignment) - int offset = isWtb ? alignTo(wta.header.offsetTextureInfo + wta.textureInfo.length * 20, 32) : 0; + int offset = isWtb ? alignTo(wta.header.getFileEnd(), 32) : 0; for (int i = 0; i < wta.textureOffsets.length; i++) { wta.textureOffsets[i] = offset; offset += wta.textureSizes[i]; diff --git a/lib/widgets/filesView/types/wtaWtpEditor.dart b/lib/widgets/filesView/types/wtaWtpEditor.dart index 2261d451..69ae2fa6 100644 --- a/lib/widgets/filesView/types/wtaWtpEditor.dart +++ b/lib/widgets/filesView/types/wtaWtpEditor.dart @@ -52,7 +52,9 @@ class _TexturesTableConfig with CustomTableConfig { return RowConfig( key: Key(textures[index].uuid), cells: [ - PropCellConfig(prop: textures[index].id), + textures[index].id != null + ? PropCellConfig(prop: textures[index].id!) + : CustomWidgetCellConfig(const SizedBox.shrink()), PropCellConfig(prop: textures[index].path), CustomWidgetCellConfig(IconButton( icon: const Icon(Icons.folder, size: 20),