Skip to content

Commit

Permalink
added support for SMD files
Browse files Browse the repository at this point in the history
  • Loading branch information
ArthurHeitmann committed Oct 24, 2022
1 parent c277354 commit c33024c
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 40 deletions.
30 changes: 30 additions & 0 deletions lib/fileTypeUtils/smd/smdReader.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

import 'dart:io';

import '../utils/ByteDataWrapper.dart';

class SmdEntry {
final String id;
final int indexX10;
final String text;

const SmdEntry(this.id, this.indexX10, this.text);
}

Future<List<SmdEntry>> readSmdFile(String path) async {
var file = File(path);
var bytes = await file.readAsBytes();
var reader = ByteDataWrapper(bytes.buffer);
var entries = <SmdEntry>[];
int count = reader.readUint32();
for (int i = 0; i < count; i++) {
String id = reader.readString(0x80, encoding: StringEncoding.utf16);
int indexX10 = reader.readUint64();
String text = reader.readString(0x800, encoding: StringEncoding.utf16);
var zerosRemover = RegExp("\x00+\$");
id = id.replaceAll(zerosRemover, "");
text = text.replaceAll(zerosRemover, "");
entries.add(SmdEntry(id, indexX10, text));
}
return entries;
}
22 changes: 22 additions & 0 deletions lib/fileTypeUtils/smd/smdWriter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

import 'dart:io';
import 'dart:typed_data';

import '../utils/ByteDataWrapper.dart';
import 'smdReader.dart';

Future<void> saveSmd(List<SmdEntry> entries, String path) async {
var totalSize = 4 + entries.length * 0x888;
var bytes = ByteDataWrapper(ByteData(totalSize).buffer);
bytes.writeUint32(entries.length);
for (var entry in entries) {
var id = entry.id.padRight(0x40, '\x00');
var text = entry.text.padRight(0x400, '\x00');
bytes.writeString(id, StringEncoding.utf16);
bytes.writeUint64(entry.indexX10);
bytes.writeString(text, StringEncoding.utf16);
}

var file = File(path);
await file.writeAsBytes(bytes.buffer.asUint8List());
}
61 changes: 23 additions & 38 deletions lib/stateManagement/FileHierarchy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,11 @@ class TmdHierarchyEntry extends FileHierarchyEntry {
: super(name, path, false, true);
}

class SmdHierarchyEntry extends FileHierarchyEntry {
SmdHierarchyEntry(StringProp name, String path)
: super(name, path, false, true);
}

class OpenHierarchyManager extends NestedNotifier<HierarchyEntry> with Undoable {
HierarchyEntry? _selectedEntry;

Expand Down Expand Up @@ -451,7 +456,7 @@ class OpenHierarchyManager extends NestedNotifier<HierarchyEntry> with Undoable
}
else if (filePath.endsWith(".xml")) {
if (await File(filePath).exists())
entry = openXmlScript(filePath, parent: parent);
entry = openGenericFile<XmlScriptHierarchyEntry>(filePath, parent, (n, p) => XmlScriptHierarchyEntry(n, p));
else
throw FileSystemException("File not found: $filePath");
}
Expand All @@ -463,7 +468,7 @@ class OpenHierarchyManager extends NestedNotifier<HierarchyEntry> with Undoable
}
else if (filePath.endsWith(".rb")) {
if (await File(filePath).exists())
entry = openRbScript(filePath, parent: parent);
entry = openGenericFile<RubyScriptHierarchyEntry>(filePath, parent, ((n, p) => RubyScriptHierarchyEntry(n, p)));
else
throw FileSystemException("File not found: $filePath");
}
Expand All @@ -475,7 +480,13 @@ class OpenHierarchyManager extends NestedNotifier<HierarchyEntry> with Undoable
}
else if (filePath.endsWith(".tmd")) {
if (await File(filePath).exists())
entry = openTmdFile(filePath, parent: parent);
entry = openGenericFile<TmdHierarchyEntry>(filePath, parent, (n, p) => TmdHierarchyEntry(n, p),);
else
throw FileSystemException("File not found: $filePath");
}
else if (filePath.endsWith(".smd")) {
if (await File(filePath).exists())
entry = openGenericFile<SmdHierarchyEntry>(filePath, parent, (n ,p) => SmdHierarchyEntry(n, p));
else
throw FileSystemException("File not found: $filePath");
}
Expand Down Expand Up @@ -527,7 +538,7 @@ class OpenHierarchyManager extends NestedNotifier<HierarchyEntry> with Undoable
// process DAT files
List<Future<void>> futures = [];
datFilePaths ??= await getDatFileList(datExtractDir);
const supportedFileEndings = { ".pak", "_scp.bin", ".tmd" };
const supportedFileEndings = { ".pak", "_scp.bin", ".tmd", ".smd" };
for (var file in datFilePaths) {
if (supportedFileEndings.every((ending) => !file.endsWith(ending)))
continue;
Expand All @@ -541,7 +552,9 @@ class OpenHierarchyManager extends NestedNotifier<HierarchyEntry> with Undoable
else if (file.endsWith("_scp.bin"))
futures.add(openBinMrbScript(file, parent: datEntry));
else if (file.endsWith(".tmd"))
openTmdFile(file, parent: datEntry);
openGenericFile<TmdHierarchyEntry>(file, parent, (n, p) => TmdHierarchyEntry(n, p));
else if (file.endsWith(".smd"))
openGenericFile<SmdHierarchyEntry>(file, parent, (n ,p) => SmdHierarchyEntry(n, p));
else
throw FileSystemException("Unsupported file type: $file");
}
Expand Down Expand Up @@ -628,28 +641,14 @@ class OpenHierarchyManager extends NestedNotifier<HierarchyEntry> with Undoable
if (!await File(xmlFilePath).exists()) {
await yaxFileToXmlFile(yaxFilePath);
}
return openXmlScript(xmlFilePath, parent: parent);
return openGenericFile<XmlScriptHierarchyEntry>(xmlFilePath,parent, (n, p) => XmlScriptHierarchyEntry(n, p));
}

HierarchyEntry openXmlScript(String xmlFilePath, { HierarchyEntry? parent }) {
var existing = findRecWhere((entry) => entry is XmlScriptHierarchyEntry && entry.path == xmlFilePath);
HierarchyEntry openGenericFile<T extends FileHierarchyEntry>(String filePath, HierarchyEntry? parent, HierarchyEntry Function(StringProp n, String p) make) {
var existing = findRecWhere((entry) => entry is T && entry.path == filePath);
if (existing != null)
return existing;
var entry = XmlScriptHierarchyEntry(StringProp(path.basename(xmlFilePath)), xmlFilePath);
if (parent != null)
parent.add(entry);
else
add(entry);

return entry;
}

HierarchyEntry openRbScript(String rbFilePath, { HierarchyEntry? parent }) {
var existing = findRecWhere((entry) => entry is RubyScriptHierarchyEntry && entry.path == rbFilePath);
if (existing != null)
return existing;

var entry = RubyScriptHierarchyEntry(StringProp(path.basename(rbFilePath)), rbFilePath);
var entry = make(StringProp(path.basename(filePath)), filePath);
if (parent != null)
parent.add(entry);
else
Expand All @@ -669,21 +668,7 @@ class OpenHierarchyManager extends NestedNotifier<HierarchyEntry> with Undoable
await binFileToRuby(binFilePath);
}

return openRbScript(rbPath, parent: parent);
}

HierarchyEntry openTmdFile(String tmdFilePath, { HierarchyEntry? parent }) {
var existing = findRecWhere((entry) => entry is TmdHierarchyEntry && entry.path == tmdFilePath);
if (existing != null)
return existing;

var entry = TmdHierarchyEntry(StringProp(path.basename(tmdFilePath)), tmdFilePath);
if (parent != null)
parent.add(entry);
else
add(entry);

return entry;
return openGenericFile<RubyScriptHierarchyEntry>(rbPath, parent, (n, p) => RubyScriptHierarchyEntry(n, p));
}

@override
Expand Down
44 changes: 44 additions & 0 deletions lib/stateManagement/openFileTypes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:xml/xml.dart';

import '../fileTypeUtils/smd/smdReader.dart';
import '../fileTypeUtils/smd/smdWriter.dart';
import '../fileTypeUtils/tmd/tmdReader.dart';
import '../fileTypeUtils/tmd/tmdWriter.dart';
import '../utils.dart';
Expand All @@ -14,6 +16,7 @@ import 'Property.dart';
import 'changesExporter.dart';
import 'hasUuid.dart';
import 'miscValues.dart';
import 'otherFileTypes/SmdFileData.dart';
import 'otherFileTypes/TmdFileData.dart';
import 'undoable.dart';
import 'xmlProps/xmlProp.dart';
Expand Down Expand Up @@ -47,6 +50,8 @@ class OpenFileData extends ChangeNotifier with Undoable, HasUuid {
return RubyFileData(name, path, secondaryName: secondaryName);
else if (path.endsWith(".tmd"))
return TmdFileData(name, path, secondaryName: secondaryName);
else if (path.endsWith(".smd"))
return SmdFileData(name, path, secondaryName: secondaryName);
else
return TextFileData(name, path, secondaryName: secondaryName);
}
Expand All @@ -58,6 +63,8 @@ class OpenFileData extends ChangeNotifier with Undoable, HasUuid {
return FileType.preferences;
else if (path.endsWith(".tmd"))
return FileType.tmd;
else if (path.endsWith(".smd"))
return FileType.smd;
else
return FileType.text;
}
Expand Down Expand Up @@ -315,3 +322,40 @@ class TmdFileData extends OpenFileData {
super.dispose();
}
}

class SmdFileData extends OpenFileData {
SmdData? smdData;

SmdFileData(super.name, super.path, { super.secondaryName });

@override
Future<void> load() async {
if (_loadingState != LoadingState.notLoaded)
return;
_loadingState = LoadingState.loading;

var smdEntries = await readSmdFile(path);
smdData = SmdData.from(smdEntries, basenameWithoutExtension(path));
smdData!.fileChangeNotifier.addListener(() {
hasUnsavedChanges = true;
});

await super.load();
}

@override
Future<void> save() async {
await saveSmd(smdData!.toEntries(), path);

var datDir = dirname(path);
changedDatFiles.add(datDir);

await super.save();
}

@override
void dispose() {
smdData?.dispose();
super.dispose();
}
}
98 changes: 98 additions & 0 deletions lib/stateManagement/otherFileTypes/SmdFileData.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member

import 'package:flutter/material.dart';

import '../../fileTypeUtils/smd/smdReader.dart';
import '../../widgets/propEditors/customXmlProps/tableEditor.dart';
import '../Property.dart';
import '../hasUuid.dart';
import '../nestedNotifier.dart';
import '../undoable.dart';

class SmdEntryData with HasUuid {
final StringProp id;
final StringProp text;
final ChangeNotifier _anyChangeNotifier;

SmdEntryData({ required this.id, required this.text, required ChangeNotifier anyChangeNotifier })
: _anyChangeNotifier = anyChangeNotifier {
id.addListener(_anyChangeNotifier.notifyListeners);
text.addListener(_anyChangeNotifier.notifyListeners);
}
}

class SmdData extends NestedNotifier<SmdEntryData> with CustomTableConfig, Undoable {
final ChangeNotifier fileChangeNotifier;

SmdData(List<SmdEntryData> entries, String fileName, this.fileChangeNotifier)
: super(entries) {
name = fileName;
columnNames = ["ID", "Text"];
rowCount = NumberProp(entries.length, true);
}

SmdData.from(List<SmdEntry> rawEntries, String fileName)
: fileChangeNotifier = ChangeNotifier(),
super([]) {
addAll(rawEntries.map((e) {
var idProp = StringProp(e.id);
var textProp = StringProp(e.text);
textProp.transform = (str) => str;
return SmdEntryData(
id: idProp,
text: textProp,
anyChangeNotifier: fileChangeNotifier,
);
}));
name = fileName;
columnNames = ["ID", "Text"];
rowCount = NumberProp(rawEntries.length, true);
}

List<SmdEntry> toEntries()
=> List.generate(length, (i) => SmdEntry(this[i].id.value, i * 10, this[i].text.value));

@override
void onRowAdd() {
var idProp = StringProp("ID");
var textProp = StringProp("Text");
textProp.transform = (str) => str;
add(SmdEntryData(
id: idProp,
text: textProp,
anyChangeNotifier: fileChangeNotifier,
));
rowCount.value++;
fileChangeNotifier.notifyListeners();
}

@override
void onRowRemove(int index) {
removeAt(index);
rowCount.value--;
fileChangeNotifier.notifyListeners();
}

@override
RowConfig rowPropsGenerator(int index) {
var entry = this[index];
return RowConfig(
key: Key(entry.uuid),
cells: [
CellConfig(prop: entry.id),
CellConfig(prop: entry.text, allowMultiline: true),
],
);
}

@override
void restoreWith(Undoable snapshot) {
// TODO: implement restoreWith
}

@override
Undoable takeSnapshot() {
// TODO: implement takeSnapshot
throw UnimplementedError();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class _HierarchyEntryState extends ChangeNotifierState<HierarchyEntryWidget> {
return Icon(Icons.workspaces, color: iconColor, size: 15);
else if (widget.entry is XmlScriptHierarchyEntry || widget.entry is RubyScriptHierarchyEntry)
return Icon(Icons.description, color: iconColor, size: 15);
else if (widget.entry is TmdHierarchyEntry)
else if (widget.entry is TmdHierarchyEntry || widget.entry is SmdHierarchyEntry)
return Icon(Icons.subtitles, color: iconColor, size: 15);

return null;
Expand Down
3 changes: 3 additions & 0 deletions lib/widgets/filesView/FileType.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ enum FileType {
xml,
preferences,
tmd,
smd,
}

Widget makeFileEditor(OpenFileData content) {
Expand All @@ -22,6 +23,8 @@ Widget makeFileEditor(OpenFileData content) {
return PreferencesEditor(prefs: content as PreferencesData);
case FileType.tmd:
return TableFileEditor(file: content, getTableConfig: () => (content as TmdFileData).tmdData!);
case FileType.smd:
return TableFileEditor(file: content, getTableConfig: () => (content as SmdFileData).smdData!);
default:
return TextFileEditor(fileContent: content as TextFileData);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/widgets/theme/customTheme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ class NierThemeExtension extends ThemeExtension<NierThemeExtension> {
}

Color colorOfFiletype(HierarchyEntry entry) {
if (entry is XmlScriptHierarchyEntry || entry is RubyScriptHierarchyEntry || entry is TmdHierarchyEntry)
if (entry is XmlScriptHierarchyEntry || entry is RubyScriptHierarchyEntry || entry is TmdHierarchyEntry || entry is TmdHierarchyEntry)
return filetypeXmlColor!;
if (entry is PakHierarchyEntry)
return filetypePakColor!;
Expand Down

0 comments on commit c33024c

Please sign in to comment.