@@ -7,7 +7,6 @@ import 'package:file_picker/file_picker.dart';
77import 'package:flutter/services.dart' ;
88import 'package:flutter/foundation.dart' ;
99import 'package:flutter_storm/flutter_storm.dart' ;
10- import 'package:flutter_storm/flutter_storm_bindings.dart' ;
1110import 'package:flutter_storm/flutter_storm_defines.dart' ;
1211import 'package:image/image.dart' ;
1312import 'package:retro/models/texture_manifest_entry.dart' ;
@@ -27,16 +26,18 @@ class CreateReplaceTexturesViewModel extends ChangeNotifier {
2726 CreateReplacementTexturesStep currentStep =
2827 CreateReplacementTexturesStep .question;
2928 String ? selectedFolderPath;
30- String ? selectedOTRPath ;
29+ List < String > selectedOTRPaths = [] ;
3130 bool isProcessing = false ;
3231 HashMap <String , dynamic > processedFiles = HashMap ();
32+
3333 String fontData = "assets/FontData" ;
3434 String fontTLUT = "assets/FontTLUT[%d].png" ;
35+ String fontTextureName = "textures/font/sGfxPrintFontData" ;
3536
3637 reset () {
3738 currentStep = CreateReplacementTexturesStep .question;
3839 selectedFolderPath = null ;
39- selectedOTRPath = null ;
40+ selectedOTRPaths = [] ;
4041 isProcessing = false ;
4142 processedFiles = HashMap ();
4243 notifyListeners ();
@@ -58,19 +59,22 @@ class CreateReplaceTexturesViewModel extends ChangeNotifier {
5859 HashMap <String , ProcessedFilesInFolder >? processedFiles = await compute (processFolder, selectedFolderPath! );
5960 if (processedFiles == null ) {
6061 // TODO: Handle this error.
61- } else {
62- this .processedFiles = processedFiles;
63- }
62+ log ("Error processing folder: $selectedFolderPath " );
63+ } else {
64+ this .processedFiles = processedFiles;
65+ }
66+
6467 isProcessing = false ;
6568 notifyListeners ();
6669 }
6770 }
6871
6972 onSelectOTR () async {
70- FilePickerResult ? result = await FilePicker .platform.pickFiles (allowMultiple: false , type: FileType .custom, allowedExtensions: ['otr' ]);
73+ FilePickerResult ? result = await FilePicker .platform.pickFiles (allowMultiple: true , type: FileType .custom, allowedExtensions: ['otr' ]);
7174 if (result != null && result.files.isNotEmpty) {
72- selectedOTRPath = result.paths.first;
73- if (selectedOTRPath == null ) {
75+ // save paths filtering out nulls
76+ selectedOTRPaths = result.paths.whereType <String >().toList ();
77+ if (selectedOTRPaths.isEmpty) {
7478 // TODO: Handle this error
7579 return ;
7680 }
@@ -80,7 +84,7 @@ class CreateReplaceTexturesViewModel extends ChangeNotifier {
8084 }
8185
8286 onProcessOTR () async {
83- if (selectedOTRPath == null ) {
87+ if (selectedOTRPaths.isEmpty ) {
8488 // TODO: Handle this error
8589 return ;
8690 }
@@ -94,38 +98,52 @@ class CreateReplaceTexturesViewModel extends ChangeNotifier {
9498 isProcessing = true ;
9599 notifyListeners ();
96100
97- HashMap <String , TextureManifestEntry >? processedFiles = await compute (processOTR, Tuple2 (selectedOTRPath! , selectedDirectory));
98- if (processedFiles == null ) {
101+ // Process OTR
102+ HashMap <String , TextureManifestEntry >? otrFiles = await compute (processOTR, Tuple2 (selectedOTRPaths, selectedDirectory));
103+ if (otrFiles == null ) {
99104 // TODO: Handle this error.
100105 } else {
101- this . processedFiles = processedFiles ;
106+ processedFiles = otrFiles ;
102107 }
103108
104- String otrName = selectedOTRPath ! . split ( Platform .pathSeparator).last. split ( "." ).first;
105- await dumpFont (path. join ( selectedDirectory, otrName) , (TextureManifestEntry entry) {
106- this . processedFiles["textures/fonts" ] = entry;
109+ // Dump font
110+ await dumpFont (selectedDirectory, (TextureManifestEntry entry) {
111+ processedFiles[fontTextureName ] = entry;
107112 });
108113
114+ // Write out the processed files to disk
115+ String ? manifestOutputPath = "$selectedDirectory /manifest.json" ;
116+ File manifestFile = File (manifestOutputPath);
117+ await manifestFile.create (recursive: true );
118+ String dataToWrite = jsonEncode (processedFiles);
119+ await manifestFile.writeAsString (dataToWrite);
120+
109121 isProcessing = false ;
110- notifyListeners ();
122+ notifyListeners ();
111123 }
112124
113125 dumpFont (String outputPath, Function onProcessed) async {
126+ Image fontImage = Image (width: 16 * 4 , height: 256 , numChannels: 4 , withPalette: false );
127+
114128 Texture tex = Texture .empty ();
115- tex.open ((await rootBundle.load (fontData)).buffer.asUint8List ());
129+ final ByteData data = await rootBundle.load (fontData);
130+ tex.open (data.buffer.asUint8List ());
116131 for (int id = 0 ; id < 4 ; id++ ){
117132 Texture tlut = Texture .empty ();
118133 tex.tlut = tlut;
119134 tlut.textureType = TextureType .RGBA32bpp ;
120- tlut.fromPNGImage (decodePng ((await rootBundle.load (fontTLUT.replaceAll ('%d' , id.toString ()))).buffer.asUint8List ())! );
121-
122- Uint8List pngBytes = tex.toPNGBytes ();
123- File textureFile = File (path.join (outputPath, "textures/font/sGfxPrintFontData[$id ].png" ));
124- textureFile.createSync (recursive: true );
125- textureFile.writeAsBytesSync (pngBytes);
126- String hash = sha256.convert (textureFile.readAsBytesSync ()).toString ();
127- onProcessed (TextureManifestEntry (hash, tex.textureType, tex.width, tex.height));
135+ final ByteData data = await rootBundle.load (fontTLUT.replaceAll ('%d' , id.toString ()));
136+ final Image pngImage = decodePng (data.buffer.asUint8List ())! ;
137+ tlut.fromRawImage (pngImage);
138+ compositeImage (fontImage, decodePng (tex.toPNGBytes ())! , dstX: id * 16 , dstY: 0 );
128139 }
140+
141+ File textureFile = File (path.join (outputPath, "$fontTextureName .png" ));
142+ Uint8List pngBytes = encodePng (fontImage);
143+ await textureFile.create (recursive: true );
144+ await textureFile.writeAsBytes (pngBytes);
145+ String hash = sha256.convert (pngBytes).toString ();
146+ onProcessed (TextureManifestEntry (hash, tex.textureType, fontImage.width, fontImage.height));
129147 }
130148}
131149
@@ -142,11 +160,13 @@ Future<HashMap<String, ProcessedFilesInFolder>?> processFolder(String folderPath
142160 String manifestContents = await manifestFile.readAsString ();
143161 Map <String , dynamic > manifest = jsonDecode (manifestContents);
144162
145- // find all pngs in folder
163+ // find all images in folder
164+ List <String > supportedExtensions = ['.png' , '.jpeg' , '.jpg' ];
146165 List <FileSystemEntity > files = Directory (folderPath).listSync (recursive: true );
166+ List <FileSystemEntity > texFiles = files.where ((file) => supportedExtensions.contains (path.extension (file.path))).toList ();
147167
148- // for each png , check if it's in the manifest
149- for (FileSystemEntity rawFile in files ) {
168+ // for each tex image , check if it's in the manifest
169+ for (FileSystemEntity rawFile in texFiles ) {
150170 File texFile = File (p.normalize (rawFile.path));
151171 String texPathRelativeToFolder = p.normalize (texFile.path.split ("$folderPath /" ).last.split ('.' ).first);
152172 if (manifest.containsKey (texPathRelativeToFolder)) {
@@ -173,66 +193,62 @@ Future<HashMap<String, ProcessedFilesInFolder>?> processFolder(String folderPath
173193 return processedFiles;
174194}
175195
176- Future <HashMap <String , TextureManifestEntry >?> processOTR (Tuple2 <String , String > params) async {
196+ Future <HashMap <String , TextureManifestEntry >?> processOTR (Tuple2 <List < String > , String > params) async {
177197 try {
178198 bool fileFound = false ;
179199 HashMap <String , TextureManifestEntry > processedFiles = HashMap ();
180200
181- log ("Processing OTR: ${params .item1 }" );
182- MPQArchive ? mpqArchive = MPQArchive .open (params.item1, 0 , MPQ_OPEN_READ_ONLY );
183-
184- FileFindResource hFind = FileFindResource ();
185- mpqArchive.findFirstFile ("*" , hFind, null );
186-
187201 // if folder we'll export to exists, delete it
188- String otrName = params.item1.split (Platform .pathSeparator).last.split ("." ).first;
189- Directory dir = Directory ("${params .item2 }/$otrName " );
202+ Directory dir = Directory (params.item2);
190203 if (dir.existsSync ()) {
191- log ("Deleting existing folder: ${params .item2 }/$ otrName " );
204+ log ("Deleting existing folder: ${params .item2 }" );
192205 await dir.delete (recursive: true );
193206 }
194-
195- // process first file
196- String ? fileName = hFind.fileName ();
197- await processFile (fileName! , mpqArchive, "${params .item2 }/$otrName /$fileName .png" , (TextureManifestEntry entry) {
198- processedFiles[fileName] = entry;
199- });
200-
201- do {
202- try {
203- mpqArchive.findNextFile (hFind);
204- fileFound = true ;
205-
206- String ? fileName = hFind.fileName ();
207- if (fileName == null || fileName == "(signature)" || fileName == "(listfile)" || fileName == "(attributes)" ) {
208- continue ;
209- }
210-
211- log ("Processing file: $fileName " );
212- bool processed = await processFile (fileName, mpqArchive, "${params .item2 }/$otrName /$fileName .png" , (TextureManifestEntry entry) {
213- processedFiles[fileName] = entry;
214- });
215-
216- if (! processed) {
217- continue ;
207+
208+ for (String otrPath in params.item1) {
209+ log ("Processing OTR: $otrPath " );
210+ MPQArchive ? mpqArchive = MPQArchive .open (otrPath, 0 , MPQ_OPEN_READ_ONLY );
211+
212+ FileFindResource hFind = FileFindResource ();
213+ mpqArchive.findFirstFile ("*" , hFind, null );
214+
215+ // process first file
216+ String ? fileName = hFind.fileName ();
217+ await processFile (fileName! , mpqArchive, "${params .item2 }/$fileName " , (TextureManifestEntry entry) {
218+ processedFiles[fileName] = entry;
219+ });
220+
221+ do {
222+ try {
223+ mpqArchive.findNextFile (hFind);
224+ fileFound = true ;
225+
226+ String ? fileName = hFind.fileName ();
227+ if (fileName == null || fileName == SIGNATURE_NAME || fileName == LISTFILE_NAME || fileName == ATTRIBUTES_NAME ) {
228+ continue ;
229+ }
230+
231+ log ("Processing file: $fileName " );
232+ bool processed = await processFile (fileName, mpqArchive, "${params .item2 }/$fileName " , (TextureManifestEntry entry) {
233+ processedFiles[fileName] = entry;
234+ });
235+
236+ if (! processed) {
237+ continue ;
238+ }
239+ } on StormLibException catch (e) {
240+ log ("Got a StormLib error: ${e .message }" );
241+ fileFound = false ;
242+ } on Exception catch (e) {
243+ log ("Got an error: $e " );
244+ fileFound = false ;
218245 }
219- } on StormLibException catch (e) {
220- log ("Got a StormLib error: ${e .message }" );
221- fileFound = false ;
222- } on Exception catch (e) {
223- log ("Got an error: $e " );
224- fileFound = false ;
225- }
226- } while (fileFound);
246+ } while (fileFound);
227247
228- // Write out the processed files to disk
229- String ? manifestOutputPath = "${params .item2 }/$otrName /manifest.json" ;
230- File manifestFile = File (manifestOutputPath);
231- await manifestFile.create (recursive: true );
232- String dataToWrite = jsonEncode (processedFiles);
233- await manifestFile.writeAsString (dataToWrite);
234-
235- hFind.close ();
248+ hFind.close ();
249+ mpqArchive.close ();
250+ }
251+
236252 return processedFiles;
237253 } on StormLibException catch (e) {
238254 log ("Failed to find next file: ${e .message }" );
@@ -262,7 +278,7 @@ Future<bool> processFile(String fileName, MPQArchive mpqArchive, String outputPa
262278 texture.open (fileData);
263279
264280 Uint8List pngBytes = texture.toPNGBytes ();
265- File textureFile = File (outputPath);
281+ File textureFile = File ("$ outputPath .png" );
266282 await textureFile.create (recursive: true );
267283 await textureFile.writeAsBytes (pngBytes);
268284 Uint8List textureBytes = await textureFile.readAsBytes ();
0 commit comments