From bc47389d599652e4218aea9dfa0f7360d0c69f6c Mon Sep 17 00:00:00 2001 From: Emd4600 Date: Thu, 14 Feb 2019 21:19:54 +0100 Subject: [PATCH] Bugfixes Many bugfixes related to project importing, also some for the model viewer. Also contains many things related to shaders and materials, not included in the released version. --- Styles/Default/syntax.css | 23 + reg_file.txt | 39 +- reg_type.txt | 2 +- src/sporemodder/EditorManager.java | 7 +- src/sporemodder/FileManager.java | 2 +- src/sporemodder/FormatManager.java | 2 + src/sporemodder/MainApp.java | 10 + src/sporemodder/ProjectManager.java | 11 + src/sporemodder/UIManager.java | 1 + src/sporemodder/UpdateManager.java | 2 +- .../file/dbpf/DBPFPackingTask.java | 19 +- .../file/effects/EffectDirectory.java | 28 +- src/sporemodder/file/effects/LightEffect.java | 8 +- .../file/effects/SkinpaintFloodEffect.java | 28 +- .../file/effects/SkinpaintParticleEffect.java | 42 +- .../file/effects/VisualEffectBlock.java | 18 +- src/sporemodder/file/pctp/PCTPUnit.java | 12 +- src/sporemodder/file/prop/XmlPropParser.java | 37 +- src/sporemodder/file/rw4/Direct3DEnums.java | 255 +++++----- .../file/rw4/MaterialStateCompiler.java | 454 ++++++++++++------ src/sporemodder/file/rw4/RWCompiledState.java | 2 +- src/sporemodder/file/rw4/RWVertexElement.java | 16 +- src/sporemodder/file/rw4/RenderWare.java | 5 - .../file/rw4/RenderWareConverter.java | 3 +- .../file/shaders/CompiledShader.java | 47 ++ .../file/shaders/CompiledShaders.java | 208 ++++++++ src/sporemodder/file/shaders/FXCompiler.java | 138 ++++++ .../file/shaders/MaterialShader.java | 80 +++ .../file/shaders/MaterialStateLink.java | 423 ++++++++++++++++ .../file/shaders/MaterialStateLinks.java | 161 +++++++ src/sporemodder/file/shaders/ShaderData.java | 250 ++++++++++ .../file/shaders/ShaderDataUniform.java | 30 ++ .../file/shaders/ShaderFragment.java | 431 +++++++++++++++++ .../file/shaders/ShaderFragmentSelector.java | 84 ++++ .../file/shaders/ShaderFragments.java | 82 ++++ .../file/shaders/ShaderSelector.java | 66 +++ .../file/shaders/SmtConverter.java | 129 +++++ .../file/shaders/StandardShader.java | 107 +++++ src/sporemodder/util/ColorRGBA.java | 7 + .../view/editors/ArgScriptEditor.java | 26 +- .../view/editors/RWModelViewer.java | 138 +++++- .../SmtTextEditor.java} | 20 +- .../SmtTextEditorFactory.java} | 33 +- src/sporemodder/view/editors/TextEditor.java | 86 +++- .../view/editors/TextEditorSkin.java | 2 +- .../view/syntax/ArgScriptSyntax2.java | 129 ----- src/sporemodder/view/syntax/HlslSyntax.java | 173 +++++++ .../view/syntax/SyntaxHighlighter.java | 1 + src/sporemodder/view/syntax/XmlSyntax.java | 14 +- 49 files changed, 3365 insertions(+), 526 deletions(-) create mode 100644 src/sporemodder/file/shaders/CompiledShader.java create mode 100644 src/sporemodder/file/shaders/CompiledShaders.java create mode 100644 src/sporemodder/file/shaders/FXCompiler.java create mode 100644 src/sporemodder/file/shaders/MaterialShader.java create mode 100644 src/sporemodder/file/shaders/MaterialStateLink.java create mode 100644 src/sporemodder/file/shaders/MaterialStateLinks.java create mode 100644 src/sporemodder/file/shaders/ShaderData.java create mode 100644 src/sporemodder/file/shaders/ShaderDataUniform.java create mode 100644 src/sporemodder/file/shaders/ShaderFragment.java create mode 100644 src/sporemodder/file/shaders/ShaderFragmentSelector.java create mode 100644 src/sporemodder/file/shaders/ShaderFragments.java create mode 100644 src/sporemodder/file/shaders/ShaderSelector.java create mode 100644 src/sporemodder/file/shaders/SmtConverter.java create mode 100644 src/sporemodder/file/shaders/StandardShader.java rename src/sporemodder/view/{syntax/ArgScriptSyntax.java => editors/SmtTextEditor.java} (70%) rename src/sporemodder/view/{syntax/StandardSyntaxFormat.java => editors/SmtTextEditorFactory.java} (59%) delete mode 100644 src/sporemodder/view/syntax/ArgScriptSyntax2.java create mode 100644 src/sporemodder/view/syntax/HlslSyntax.java diff --git a/Styles/Default/syntax.css b/Styles/Default/syntax.css index d794c10..ea23a80 100644 --- a/Styles/Default/syntax.css +++ b/Styles/Default/syntax.css @@ -57,4 +57,27 @@ .propeditor-property { -fx-font-weight: bold; -fx-fill: #CE81D6; +} + +/** -- HLSL -- */ + +.hlsl-comments { + -fx-fill: green; +} +.hlsl-enums { + -fx-fill: rgb(186, 85, 211); +} +.hlsl-numbers { + -fx-fill: #e5af25; +} +.hlsl-types { + -fx-fill: #008b8b; +} +.hlsl-keywords { + -fx-fill: rgb(0, 0, 255); + -fx-font-weight: bold; +} +.hlsl-functions { + -fx-fill: #e57402; + -fx-font-weight: bold; } \ No newline at end of file diff --git a/reg_file.txt b/reg_file.txt index f318752..171c10d 100644 --- a/reg_file.txt +++ b/reg_file.txt @@ -77581,4 +77581,41 @@ weaponCell weaponCharging weaponElectro weaponPoison -wing \ No newline at end of file +wing + +materials_compiled_states_link~ 0x40212000 +materials_compiled_states~ 0x40212001 +materials_uncompiled_shader_fragments~ 0x40212002 +materials_compiled_shaders~ 0x40212004 + +GenericStatic +Generic2D +Particles_Decal +Particles_DecalInvertDepth +Particles_DecalIgnoreDepth +Particles_DepthDecal +Particles_Additive +Particles_AdditiveInvertDepth +Particles_AdditiveIgnoreDepth +Particles_Modulate +Particles_NormalMap +Particles_DepthNormalMap +Particles_AlphaTestDissolve +ParticlesLightFog_Decal +ParticlesLightFog_DecalInvertDepth +ParticlesLightFog_DecalIgnoreDepth +ParticlesLightFog_DepthDecal +ScreenAdditive +ScreenBlend +ScreenTint +ScreenBrighten +ScreenSkyBox +ScreenBackground +ScreenAdditiveTexture +ScreenBlendTexture +ScreenTintTexture +ScreenBrightenTexture +BuildBasis_0_0 +BuildBasis_0_1 +BuildBasis_1_0 +BuildBasis_1_1 \ No newline at end of file diff --git a/reg_type.txt b/reg_type.txt index fe132e2..6dbe8a6 100644 --- a/reg_type.txt +++ b/reg_type.txt @@ -62,7 +62,7 @@ cfx 0x278CF8F2 dds 0x17952E6C snr 0x01a527db sns 0x01eef63a -cpp 0x0469A3F7 +smt 0x0469A3F7 eapd 0x022D2C83 package 0x06EFC6AA instrument 0x03055F61 diff --git a/src/sporemodder/EditorManager.java b/src/sporemodder/EditorManager.java index b155395..38d9403 100644 --- a/src/sporemodder/EditorManager.java +++ b/src/sporemodder/EditorManager.java @@ -28,9 +28,6 @@ import javafx.collections.ListChangeListener; import javafx.scene.Node; -import javafx.scene.control.ButtonType; -import javafx.scene.control.CheckBox; -import javafx.scene.control.Dialog; import javafx.scene.control.Tab; import javafx.scene.image.Image; import javafx.scene.image.ImageView; @@ -49,10 +46,12 @@ import sporemodder.view.editors.PropEditorFactory; import sporemodder.view.editors.RWModelViewer; import sporemodder.view.editors.SearchableEditor; +import sporemodder.view.editors.SmtTextEditorFactory; import sporemodder.view.editors.SpuiEditorFactory; import sporemodder.view.editors.TextEditorFactory; import sporemodder.view.editors.TlsaEditorFactory; import sporemodder.view.editors.XmlEditorFactory; +import sporemodder.view.syntax.HlslSyntax; import sporemodder.view.syntax.SyntaxFormatFactory; import sporemodder.view.syntax.XmlSyntax; @@ -114,6 +113,7 @@ public void initialize(Properties properties) { // Default editors // The default one goes first, as it will be the last one to be checked editorFactories.add(defaultEditorFactory); + editorFactories.add(new SmtTextEditorFactory()); editorFactories.add(new XmlEditorFactory()); editorFactories.add(new PctpEditorFactory()); editorFactories.add(new TlsaEditorFactory()); @@ -126,6 +126,7 @@ public void initialize(Properties properties) { // Default syntax highlighting //TODO add more here! syntaxHighlighters.add(new XmlSyntax()); + syntaxHighlighters.add(new HlslSyntax()); // Load the icons for the project tree diff --git a/src/sporemodder/FileManager.java b/src/sporemodder/FileManager.java index a2674f7..327bd2f 100644 --- a/src/sporemodder/FileManager.java +++ b/src/sporemodder/FileManager.java @@ -59,7 +59,7 @@ public static FileManager get() { @Override public void initialize(Properties properties) { - searchableExtensions.addAll(Arrays.asList("prop_t", "xml", "locale", "txt", "trigger", "tlsa_t", "pctp_t", "pfx")); + searchableExtensions.addAll(Arrays.asList("prop_t", "xml", "locale", "txt", "trigger", "tlsa_t", "pctp_t", "pfx", "smt_t", "hlsl")); protectedPackages.addAll(Arrays.asList("patchdata.package", "spore_audio1.package", "spore_audio2.package", "spore_content.package", "spore_game.package", "spore_graphics.package", "spore_pack_03.package", diff --git a/src/sporemodder/FormatManager.java b/src/sporemodder/FormatManager.java index a3bd967..c1647a4 100644 --- a/src/sporemodder/FormatManager.java +++ b/src/sporemodder/FormatManager.java @@ -31,6 +31,7 @@ import sporemodder.file.prop.PropConverter; import sporemodder.file.raster.RasterConverter; import sporemodder.file.rw4.RenderWareConverter; +import sporemodder.file.shaders.SmtConverter; import sporemodder.file.tlsa.TLSAConverter; public class FormatManager extends AbstractManager { @@ -39,6 +40,7 @@ public class FormatManager extends AbstractManager { @Override public void initialize(Properties properties) { + converters.add(new SmtConverter()); converters.add(new RasterConverter()); converters.add(new PCTPConverter()); converters.add(new TLSAConverter()); diff --git a/src/sporemodder/MainApp.java b/src/sporemodder/MainApp.java index d191fac..5f5de3c 100644 --- a/src/sporemodder/MainApp.java +++ b/src/sporemodder/MainApp.java @@ -30,6 +30,7 @@ import javafx.application.Application; import javafx.application.Platform; import javafx.stage.Stage; +import sporemodder.file.shaders.FXCompiler; /** * The main class of the program. @@ -48,6 +49,7 @@ public class MainApp extends Application { private FormatManager formatManager; private FileManager fileManager; private UpdateManager updateManager; + private FXCompiler fxCompiler; private Properties settings; @@ -135,6 +137,10 @@ public UpdateManager getUpdateManager() { return updateManager; } + public FXCompiler getFXCompiler() { + return fxCompiler; + } + /** * Returns the current instance of the MainApp class. */ @@ -160,6 +166,7 @@ private void init(boolean testInit) { formatManager = new FormatManager(); fileManager = new FileManager(); updateManager = new UpdateManager(); + fxCompiler = new FXCompiler(); // Initialize it first, otherwise we can't get the settings pathManager.initialize(null); @@ -185,6 +192,7 @@ private void init(boolean testInit) { projectManager.initialize(settings); if (!testInit) documentationManager.initialize(settings); formatManager.initialize(settings); + fxCompiler.initialize(settings); // The plugin must go last, as plugins will use the other managers pluginManager.initialize(settings); @@ -201,6 +209,7 @@ public void saveSettings() { documentationManager.saveSettings(settings); formatManager.saveSettings(settings); fileManager.saveSettings(settings); + fxCompiler.saveSettings(settings); try (OutputStream stream = new FileOutputStream(pathManager.getProgramFile("config.properties"))) { settings.store(stream, null); @@ -219,6 +228,7 @@ public void stop() { // The plugin must go first, as plugins will use the other managers pluginManager.dispose(); + fxCompiler.dispose(); documentationManager.dispose(); formatManager.dispose(); editorManager.dispose(); diff --git a/src/sporemodder/ProjectManager.java b/src/sporemodder/ProjectManager.java index 9d26d3b..0b8f27d 100644 --- a/src/sporemodder/ProjectManager.java +++ b/src/sporemodder/ProjectManager.java @@ -1750,6 +1750,7 @@ private void importFile(File sourceFile, File destFolder) throws IOException { MemoryStream stream = XmlPropParser.xmlToProp(in); stream.seek(0); + //stream.writeToFile(new File(destFolder, name + ".prop")); PropertyList list = new PropertyList(); list.read(stream); @@ -1919,6 +1920,7 @@ public boolean importOldProject() { ImportProjectTask task = new ImportProjectTask(project, sourceFolder, fileRegistry, propRegistry, typeRegistry); task.showProgressDialog(); + hasher.replaceRegistries(null, null, null); hasher.setUpdateProjectRegistry(false); saveNamesRegistry(project); hasher.getProjectRegistry().clear(); @@ -1978,4 +1980,13 @@ public boolean showSaveDialog() { return false; } } + + public static void main(String[] args) throws IOException { + MainApp.testInit(); + + File file = new File("C:\\Users\\Eric\\Downloads\\DI_ItemDrop.prop.xml"); + File destFolder = new File("C:\\Users\\Eric\\Desktop"); + + ProjectManager.get().importFile(file, destFolder); + } } diff --git a/src/sporemodder/UIManager.java b/src/sporemodder/UIManager.java index 4dc7989..9678658 100644 --- a/src/sporemodder/UIManager.java +++ b/src/sporemodder/UIManager.java @@ -543,6 +543,7 @@ public boolean tryAction(SimpleAction action, String errorText) { return true; } catch (Exception e) { + e.printStackTrace(); showErrorDialog(e, errorText, true); return false; } diff --git a/src/sporemodder/UpdateManager.java b/src/sporemodder/UpdateManager.java index ef4df3e..eaf0bff 100644 --- a/src/sporemodder/UpdateManager.java +++ b/src/sporemodder/UpdateManager.java @@ -68,7 +68,7 @@ public class UpdateManager { private static final String GITHUB_URL = "https://api.github.com"; - public final VersionInfo versionInfo = new VersionInfo(2, 0, 2, null); + public final VersionInfo versionInfo = new VersionInfo(2, 0, 3, null); public static UpdateManager get() { return MainApp.get().getUpdateManager(); diff --git a/src/sporemodder/file/dbpf/DBPFPackingTask.java b/src/sporemodder/file/dbpf/DBPFPackingTask.java index 0c2605e..edfb7ba 100644 --- a/src/sporemodder/file/dbpf/DBPFPackingTask.java +++ b/src/sporemodder/file/dbpf/DBPFPackingTask.java @@ -161,7 +161,7 @@ public boolean accept(File arg0) { boolean bUsesConverter = false; String name = file.getName(); - file = getNestedFile(file, name); + file = getNestedFile(file, name, converters); // Skip if there was a problem if (file == null) continue; @@ -285,20 +285,17 @@ public DBPFItem getTemporaryItem() { } //TODO consider changing how nested files work - private File getNestedFile(File file, String name) { + private File getNestedFile(File file, String name, List converters) { if (!file.isFile()) { - if (name.contains(".") && !name.endsWith(".effdir.unpacked")) { - File newFile = new File(file, name); - if (!newFile.exists()) { - failedFiles.put(file, new UnsupportedOperationException("Couldn't find file " + name + " inside subfolder " + name)); - return null; - } - file = newFile; + for (Converter converter : converters) { + if (converter.isEncoder(file)) return file; } - else if (!name.endsWith(".effdir.unpacked")) { - failedFiles.put(file, new UnsupportedOperationException("Nested subfolders are not supported. File: " + name)); + File newFile = new File(file, name); + if (!newFile.exists()) { + failedFiles.put(file, new UnsupportedOperationException("Couldn't find file " + name + " inside subfolder " + name)); return null; } + file = newFile; } return file; diff --git a/src/sporemodder/file/effects/EffectDirectory.java b/src/sporemodder/file/effects/EffectDirectory.java index c729d6d..365b24c 100644 --- a/src/sporemodder/file/effects/EffectDirectory.java +++ b/src/sporemodder/file/effects/EffectDirectory.java @@ -340,15 +340,18 @@ public void read(StreamReader stream) throws IOException { componentOffsets[type] = offset + 4; - List list = new ArrayList(count); - components.set(type, list); - - String keyword = factories[type].getKeyword(); - - for (int i = 0; i < count; ++i) { - EffectComponent component = factories[type].create(this, version); - component.setName(keyword + '-' + Integer.toString(i)); - list.add(component); + if (type < factories.length && factories[type] != null && + version >= factories[type].getMinVersion() && version <= factories[type].getMaxVersion()) { + List list = new ArrayList(count); + components.set(type, list); + + String keyword = factories[type].getKeyword(); + + for (int i = 0; i < count; ++i) { + EffectComponent component = factories[type].create(this, version); + component.setName(keyword + '-' + Integer.toString(i)); + list.add(component); + } } stream.seek(offset + size); @@ -762,8 +765,11 @@ public static void copyArray(T[] dest, T[] source) { public static void main(String[] args) throws IOException { MainApp.testInit(); - String path = "E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Effect Editor\\gameEffects_3~\\games.effdir"; - String outputPath = "E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Effect Editor\\gameEffects_3~\\games.effdir.unpacked"; +// String path = "E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Effect Editor\\gameEffects_3~\\editors.effdir"; +// String outputPath = "E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Effect Editor\\gameEffects_3~\\editors.effdir.unpacked"; + + String path = "E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Effects\\gameEffects_3~\\base.effdir"; + String outputPath = "E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Effects\\gameEffects_3~\\base.effdir.unpacked"; long time = System.currentTimeMillis(); diff --git a/src/sporemodder/file/effects/LightEffect.java b/src/sporemodder/file/effects/LightEffect.java index 48bd30c..2937083 100644 --- a/src/sporemodder/file/effects/LightEffect.java +++ b/src/sporemodder/file/effects/LightEffect.java @@ -81,6 +81,8 @@ public LightEffect(EffectDirectory effectDirectory, int version) { @Override public void read(StreamReader in) throws IOException { + System.out.println(in.getFilePointer()); + flags = in.readInt(); // & 0xF ? type = in.readByte(); @@ -110,8 +112,10 @@ public LightEffect(EffectDirectory effectDirectory, int version) { out.writeInt(flags); out.writeByte(type); - out.writeFloat(life); - out.writeShort(loop); + if (version > 1) { + out.writeFloat(life); + out.writeShort(loop); + } out.writeInt(color.size()); for (ColorRGB f : color) f.writeLE(out); diff --git a/src/sporemodder/file/effects/SkinpaintFloodEffect.java b/src/sporemodder/file/effects/SkinpaintFloodEffect.java index 3bf0532..1a3fa9e 100644 --- a/src/sporemodder/file/effects/SkinpaintFloodEffect.java +++ b/src/sporemodder/file/effects/SkinpaintFloodEffect.java @@ -61,6 +61,9 @@ public class SkinpaintFloodEffect extends EffectComponent { public static final List OPERATION_TYPES = Arrays.asList("set", "add", "mult", "div"); public static final List MODIFIER_FLAG = Arrays.asList(null, "open", "clamp", "wrap", "mirror", "clamp2", "wrap2", "mirror2"); + public static final int COMPARE_NORMAL_MODIFIER = -3; + public static final int COMPARE_POSITION_MODIFIER = -2; + public static final ArgScriptEnum ENUM_BLEND = new ArgScriptEnum(); static { ENUM_BLEND.add(1, "alpha"); @@ -169,6 +172,8 @@ protected SkinpaintFloodEffect createEffect(EffectDirectory effectDirectory) { effect.varModifiers.add(varMod); } + modifierIndex = -1; + return effect; } @@ -191,7 +196,7 @@ public void addParsers() { if (arg.startsWith("color")) { try { - effect.diffuseColorIndex = Byte.parseByte(arg.substring("color".length())); + effect.diffuseColorIndex = Byte.parseByte(arg.substring("color".length())) + 1; effect.diffuseColor[0] = 0.0f; effect.diffuseColor[1] = 0.0f; effect.diffuseColor[2] = 1.0f; @@ -632,12 +637,31 @@ public void toArgScript(ArgScriptWriter writer) { } } + VarModifier compareNormal = null; + VarModifier comparePosition = null; + // process the modifiers for (int i = 0; i < varModifiers.size(); ++i) { - if (varModifiers.get(i).valueCount == 19) { + VarModifier vm = varModifiers.get(i); + if (vm.valueCount == 19) { writeModifierArgScript(writer, i); writer.blankLine(); } + else if (vm.modifierType == COMPARE_NORMAL_MODIFIER) { + compareNormal = vm; + } + else if (vm.modifierType == COMPARE_POSITION_MODIFIER) { + comparePosition = vm; + } + } + + if (compareNormal != null) { + writer.command("compareNormal").vector(compareNormal.value_0, compareNormal.value_1, compareNormal.value_2); + writer.floats(variableValues.get(compareNormal.valueIndex), variableValues.get(compareNormal.valueIndex + 1)); + } + if (comparePosition != null) { + writer.command("comparePosition").vector(compareNormal.value_0, compareNormal.value_1, compareNormal.value_2); + writer.vector(variableValues.get(compareNormal.valueIndex), variableValues.get(compareNormal.valueIndex + 1), variableValues.get(compareNormal.valueIndex + 2)); } writer.endBlock().commandEND(); diff --git a/src/sporemodder/file/effects/SkinpaintParticleEffect.java b/src/sporemodder/file/effects/SkinpaintParticleEffect.java index 670278e..8e0a4ca 100644 --- a/src/sporemodder/file/effects/SkinpaintParticleEffect.java +++ b/src/sporemodder/file/effects/SkinpaintParticleEffect.java @@ -65,6 +65,9 @@ public class SkinpaintParticleEffect extends EffectComponent { public static final List OPERATION_TYPES = Arrays.asList("set", "add", "mult", "div"); public static final List MODIFIER_FLAG = Arrays.asList(null, "open", "clamp", "wrap", "mirror", "clamp2", "wrap2", "mirror2"); + public static final int COMPARE_NORMAL_MODIFIER = -3; + public static final int COMPARE_POSITION_MODIFIER = -2; + public static final ArgScriptEnum ENUM_BLEND = new ArgScriptEnum(); static { ENUM_BLEND.add(1, "alpha"); @@ -266,6 +269,8 @@ protected SkinpaintParticleEffect createEffect(EffectDirectory effectDirectory) effect.varModifiers.add(varMod); } + modifierIndex = -1; + return effect; } @@ -438,7 +443,7 @@ public void addParsers() { if (arg.startsWith("color")) { try { - effect.diffuseColorIndex = Byte.parseByte(arg.substring("color".length())); + effect.diffuseColorIndex = (byte) (Byte.parseByte(arg.substring("color".length())) - 1); effect.diffuseColor[0] = 0.0f; effect.diffuseColor[1] = 0.0f; effect.diffuseColor[2] = 1.0f; @@ -1063,12 +1068,31 @@ else if (attractFlag == ALIGN_ALONGBONE) { } } + VarModifier compareNormal = null; + VarModifier comparePosition = null; + // process the modifiers for (int i = 0; i < varModifiers.size(); ++i) { - if (varModifiers.get(i).valueCount == 19) { + VarModifier vm = varModifiers.get(i); + if (vm.valueCount == 19) { writeModifierArgScript(writer, i); writer.blankLine(); } + else if (vm.modifierType == COMPARE_NORMAL_MODIFIER) { + compareNormal = vm; + } + else if (vm.modifierType == COMPARE_POSITION_MODIFIER) { + comparePosition = vm; + } + } + + if (compareNormal != null) { + writer.command("compareNormal").vector(compareNormal.value_0, compareNormal.value_1, compareNormal.value_2); + writer.floats(variableValues.get(compareNormal.valueIndex), variableValues.get(compareNormal.valueIndex + 1)); + } + if (comparePosition != null) { + writer.command("comparePosition").vector(compareNormal.value_0, compareNormal.value_1, compareNormal.value_2); + writer.vector(variableValues.get(compareNormal.valueIndex), variableValues.get(compareNormal.valueIndex + 1), variableValues.get(compareNormal.valueIndex + 2)); } // 'chain' usually goes at the end @@ -1077,20 +1101,6 @@ else if (attractFlag == ALIGN_ALONGBONE) { if (variables.size() == 0) writer.blankLine(); writer.command("chain").arguments(chainParticles.getName()); } - - //TODO what's this? - if (varModifiers.size() >= 2) { - VarModifier varMod = varModifiers.get(0); - - if (varMod.value_0 != 0 || varMod.value_1 != 0 || varMod.value_2 != 0 || varMod.modifierType != -1 || varMod.valueCount != 0 || varMod.valueIndex != 0) { - writer.command("compareNormal"); - } - - varMod = varModifiers.get(1); - if (varMod.value_0 != 0 || varMod.value_1 != 0 || varMod.value_2 != 0 || varMod.modifierType != -1 || varMod.valueCount != 0 || varMod.valueIndex != 0) { - writer.command("comparePosition"); - } - } writer.endBlock().commandEND(); } diff --git a/src/sporemodder/file/effects/VisualEffectBlock.java b/src/sporemodder/file/effects/VisualEffectBlock.java index 715d179..1c54ae7 100644 --- a/src/sporemodder/file/effects/VisualEffectBlock.java +++ b/src/sporemodder/file/effects/VisualEffectBlock.java @@ -369,14 +369,18 @@ public void parse(ArgScriptLine line) { } public void toArgScript(ArgScriptWriter writer) { - if (component.getFactory() == null) { - // imports are used as effects - writer.command(VisualEffect.KEYWORD).arguments(component.name); - } - else if (component.getFactory().onlySupportsInline()) { - component.toArgScript(writer); + if (component != null) { + if (component.getFactory() == null) { + // imports are used as effects + writer.command(VisualEffect.KEYWORD).arguments(component.name); + } + else if (component.getFactory().onlySupportsInline()) { + component.toArgScript(writer); + } else { + writer.command(component.getFactory().getKeyword()).arguments(component.getName()); + } } else { - writer.command(component.getFactory().getKeyword()).arguments(component.getName()); + writer.command("UNSUPPORTED_COMPONENT"); } transform.toArgScriptNoDefault(writer, false); diff --git a/src/sporemodder/file/pctp/PCTPUnit.java b/src/sporemodder/file/pctp/PCTPUnit.java index a782aec..2938b7a 100644 --- a/src/sporemodder/file/pctp/PCTPUnit.java +++ b/src/sporemodder/file/pctp/PCTPUnit.java @@ -293,6 +293,11 @@ public ArgScriptStream generateStream() { object.name = args.get(0); object.identifier = args.get(1); stream.getData().capabilityNames.add(object); + + CapabilityMapping mapping = new CapabilityMapping(); + mapping.identifier = args.get(1); + mapping.index = stream.getData().capabilityNames.size() - 1; + stream.getData().capabilitiesMap.put(HashManager.get().getFileHash(object.name), mapping); } })); @@ -312,7 +317,12 @@ public ArgScriptStream generateStream() { if (line.getArguments(args, 2)) { int hash = stream.parseFileID(args, 0); - stream.getData().capabilitiesMap.put(hash, stream.getData().capabilitiesMap.get(HashManager.get().fnvHash(args.get(1)))); + CapabilityMapping object = stream.getData().capabilitiesMap.get(HashManager.get().fnvHash(args.get(1))); + if (object == null) { + stream.addError(line.createErrorForArgument(args.get(1) + " is not a defined capability", 1)); + } else { + stream.getData().capabilitiesMap.put(hash, object); + } } })); diff --git a/src/sporemodder/file/prop/XmlPropParser.java b/src/sporemodder/file/prop/XmlPropParser.java index 879eda2..a9a0510 100644 --- a/src/sporemodder/file/prop/XmlPropParser.java +++ b/src/sporemodder/file/prop/XmlPropParser.java @@ -65,6 +65,8 @@ public PropertyData(MemoryStream stream) { private MemoryStream stream; // temporary stream private final Map propertiesData; + + private final StringBuilder content = new StringBuilder(); private int nTotalSize = 4; @@ -111,6 +113,8 @@ public void startElement(String uri, String localName,String qName, Attributes a this.attributes = attributes; this.lastQName = qName; + content.setLength(0); + if (qName.equalsIgnoreCase("properties")) { bInsideProperties = true; } @@ -238,6 +242,23 @@ public void endElement(String uri, String localName,String qName) throws SAXExce return; } + // Apply characters now + if (nCurrentType != -1 && (requiresContent(nCurrentType) || (bIsComplexProperty && bIsInComplexComponent))) { + if (bIsComplexProperty) { + if (bIsInComplexComponent) { + parseComplexComponent(content.toString()); + } + else if (content.length() != 0) { + throw new SAXException("PROP File: Data in the " + lastQName + " property '" + lastPropertyName + "' must be contained inside component tags ( etc)."); + } + } + else { + if ((bIsArray && !lastQName.endsWith("s") && bIsInArrayData) || !bIsArray) { + convert(stream, attributes, content.toString()); + } + } + } + // We finish the property ONLY if the closed tag was actually a property, and not a component like 'x' if (!bIsInComplexComponent) { @@ -312,11 +333,11 @@ public void characters(char[] ch, int start, int length) throws SAXException { if (nCurrentType == -1) { // a comment if (text.startsWith("#")) return; - throw new SAXException("PROP File: Data must be inside a property."); + else if (!text.isEmpty()) throw new SAXException("PROP File: Data must be inside a property."); } else { if (bIsArray) { - if (!bIsInArrayData && !bIsInComplexComponent) { + if (!bIsInArrayData && !bIsInComplexComponent && !text.isEmpty()) { if (requiresContent(nCurrentType)) { throw new SAXException("PROP File: Data in the array property '" + lastPropertyName + "' must be contained inside properties." + "Are you missing the <" + lastQName + "> tags?"); @@ -327,17 +348,7 @@ public void characters(char[] ch, int start, int length) throws SAXException { } } - if (bIsComplexProperty) { - if (bIsInComplexComponent) { - parseComplexComponent(text); - } - else { - throw new SAXException("PROP File: Data in the " + lastQName + " property '" + lastPropertyName + "' must be contained inside component tags ( etc)."); - } - } - else { - convert(stream, attributes, text); - } + content.append(ch, start, length); } } diff --git a/src/sporemodder/file/rw4/Direct3DEnums.java b/src/sporemodder/file/rw4/Direct3DEnums.java index 2945256..d9aad44 100644 --- a/src/sporemodder/file/rw4/Direct3DEnums.java +++ b/src/sporemodder/file/rw4/Direct3DEnums.java @@ -21,14 +21,17 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import sporemodder.HashManager; +import sporemodder.util.ColorRGBA; + public class Direct3DEnums { public static interface D3DStateEnum { public int getId(); } - public static D3DStateEnum getStateEnumById(int id, Class> stateEnum) { + public static D3DStateEnum getStateEnumById(int id, Class stateEnum) { try { - if (stateEnum == null) return null; + if (stateEnum == null || !stateEnum.isEnum()) return null; Method valuesMethod = stateEnum.getDeclaredMethod("values"); D3DStateEnum[] states = (D3DStateEnum[]) valuesMethod.invoke(null); for (D3DStateEnum state : states) { @@ -44,6 +47,37 @@ public static D3DStateEnum getStateEnumById(int id, Class stateEnum) { + try { + if (stateEnum == null || !stateEnum.isEnum()) return null; + Method valuesMethod = stateEnum.getDeclaredMethod("values"); + D3DStateEnum[] states = (D3DStateEnum[]) valuesMethod.invoke(null); + for (D3DStateEnum state : states) { + if (state.toString().equals(text)) { + return state.getId(); + } + } + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + e.printStackTrace(); + } + + return null; + } + + public static String getValueToString(Class typeClass, int value) { + if (typeClass == int.class) return Integer.toString(value); + else if (typeClass == float.class) return HashManager.get().floatToString(Float.intBitsToFloat(value)); + else if (typeClass == ColorRGBA.class) return new ColorRGBA(value).toString(); + else { + D3DStateEnum stateEnum = getStateEnumById(value, typeClass); + if (stateEnum == null) { + return Integer.toString(value); + } else { + return stateEnum.toString(); + } + } + } + public static final int D3DUSAGE_RENDERTARGET = 0x00000001; public static final int D3DUSAGE_DEPTHSTENCIL = 0x00000002; public static final int D3DUSAGE_DYNAMIC = 0x00000200; @@ -81,26 +115,30 @@ public static enum D3DFORMAT implements D3DStateEnum { } public static enum D3DDECLTYPE implements D3DStateEnum { - D3DDECLTYPE_FLOAT1(0), - D3DDECLTYPE_FLOAT2(1), - D3DDECLTYPE_FLOAT3(2), - D3DDECLTYPE_FLOAT4(3), - D3DDECLTYPE_D3DCOLOR(4), - D3DDECLTYPE_UBYTE4(5), - D3DDECLTYPE_SHORT2(6), - D3DDECLTYPE_SHORT4(7), - D3DDECLTYPE_UBYTE4N(8), - D3DDECLTYPE_SHORT2N(9), - D3DDECLTYPE_SHORT4N(10), - D3DDECLTYPE_USHORT2N(11), - D3DDECLTYPE_USHORT4N(12), - D3DDECLTYPE_UDEC3(13), - D3DDECLTYPE_DEC3N(14), - D3DDECLTYPE_FLOAT16_2(15), - D3DDECLTYPE_FLOAT16_4(16), - D3DDECLTYPE_UNUSED(17); + D3DDECLTYPE_FLOAT1(0, 4*1), + D3DDECLTYPE_FLOAT2(1, 4*2), + D3DDECLTYPE_FLOAT3(2, 4*3), + D3DDECLTYPE_FLOAT4(3, 4*4), + D3DDECLTYPE_D3DCOLOR(4, 4), + D3DDECLTYPE_UBYTE4(5, 4), + D3DDECLTYPE_SHORT2(6, 4), + D3DDECLTYPE_SHORT4(7, 8), + D3DDECLTYPE_UBYTE4N(8, 4), + D3DDECLTYPE_SHORT2N(9, 4), + D3DDECLTYPE_SHORT4N(10, 8), + D3DDECLTYPE_USHORT2N(11, 4), + D3DDECLTYPE_USHORT4N(12, 8), + D3DDECLTYPE_UDEC3(13, 4), + D3DDECLTYPE_DEC3N(14, 4), + D3DDECLTYPE_FLOAT16_2(15, 4), + D3DDECLTYPE_FLOAT16_4(16, 8), + D3DDECLTYPE_UNUSED(17, 0); int id; - private D3DDECLTYPE(int id) { this.id = id; } + public int size; + private D3DDECLTYPE(int id, int size) { + this.id = id; + this.size = size; + } public static D3DDECLTYPE getById(int id) { for (D3DDECLTYPE state : values()) if (state.id == id) return state; return null; } public int getId() { return id; } } @@ -129,10 +167,22 @@ public static enum D3DDECLUSAGE implements D3DStateEnum { public static enum RWDECLUSAGE implements D3DStateEnum { POSITION(0), NORMAL(2), - TEXCOORD(6), - BONEINDICES(14), - BONEWEIGHTS(15), - TANGENT(19); + COLOR0(3), + COLOR1(5), + TEXCOORD0(6), + TEXCOORD1(7), + TEXCOORD2(8), + TEXCOORD3(9), + BLENDINDICES(14), + BLENDWEIGHTS(15), + POINTSIZE(16), + POSITION2(17), + NORMAL2(18), + TANGENT(19), + BINORMAL(20), + FOG(21), + BLENDINDICES2(22), + BLENDWEIGHTS2(23); int id; private RWDECLUSAGE(int id) { this.id = id; } public static RWDECLUSAGE getById(int id) { for (RWDECLUSAGE state : values()) if (state.id == id) return state; return null; } @@ -352,6 +402,7 @@ public static enum D3DDEGREETYPE implements D3DStateEnum { public static D3DDEGREETYPE getById(int id) { for (D3DDEGREETYPE state : values()) if (state.id == id) return state; return null; } public int getId() { return id; } } + public static enum D3DRenderStateType { D3DRS_ZENABLE (7, D3DZBUFFERTYPE.class), D3DRS_FILLMODE (8, D3DFILLMODE.class), @@ -363,27 +414,27 @@ public static enum D3DRenderStateType { D3DRS_DESTBLEND (20, D3DBLEND.class), D3DRS_CULLMODE (22, D3DCULL.class), D3DRS_ZFUNC (23, D3DCMPFUNC.class), - D3DRS_ALPHAREF (24), //int from 0 to 0xff + D3DRS_ALPHAREF (24, int.class), // from 0 to 0xff D3DRS_ALPHAFUNC (25, D3DCMPFUNC.class), D3DRS_DITHERENABLE (26, D3DBOOLEAN.class), D3DRS_ALPHABLENDENABLE (27, D3DBOOLEAN.class), D3DRS_FOGENABLE (28, D3DBOOLEAN.class), D3DRS_SPECULARENABLE (29, D3DBOOLEAN.class), - D3DRS_FOGCOLOR (34), //color + D3DRS_FOGCOLOR (34, ColorRGBA.class), D3DRS_FOGTABLEMODE (35, D3DFOGMODE.class), - D3DRS_FOGSTART (36), //float from 0 to 1 - D3DRS_FOGEND (37), //float from 0 to 1 - D3DRS_FOGDENSITY (38), //float from 0 to 1 + D3DRS_FOGSTART (36, float.class), // from 0 to 1 + D3DRS_FOGEND (37, float.class), // from 0 to 1 + D3DRS_FOGDENSITY (38, float.class), // from 0 to 1 D3DRS_RANGEFOGENABLE (48, D3DBOOLEAN.class), D3DRS_STENCILENABLE (52, D3DBOOLEAN.class), D3DRS_STENCILFAIL (53, D3DSTENCILOP.class), D3DRS_STENCILZFAIL (54, D3DSTENCILOP.class), D3DRS_STENCILPASS (55, D3DSTENCILOP.class), D3DRS_STENCILFUNC (56, D3DCMPFUNC.class), - D3DRS_STENCILREF (57), //int - D3DRS_STENCILMASK (58), //int - D3DRS_STENCILWRITEMASK (59), //int - D3DRS_TEXTUREFACTOR (60), //color + D3DRS_STENCILREF (57, int.class), + D3DRS_STENCILMASK (58, int.class), + D3DRS_STENCILWRITEMASK (59, int.class), + D3DRS_TEXTUREFACTOR (60, ColorRGBA.class), D3DRS_WRAP0 (128, D3DWRAPCOORD.class), D3DRS_WRAP1 (129, D3DWRAPCOORD.class), D3DRS_WRAP2 (130, D3DWRAPCOORD.class), @@ -394,7 +445,7 @@ public static enum D3DRenderStateType { D3DRS_WRAP7 (135, D3DWRAPCOORD.class), D3DRS_CLIPPING (136, D3DBOOLEAN.class), D3DRS_LIGHTING (137, D3DBOOLEAN.class), - D3DRS_AMBIENT (139), //color + D3DRS_AMBIENT (139, ColorRGBA.class), D3DRS_FOGVERTEXMODE (140, D3DFOGMODE.class), D3DRS_COLORVERTEX (141, D3DBOOLEAN.class), D3DRS_LOCALVIEWER (142, D3DBOOLEAN.class), @@ -404,46 +455,46 @@ public static enum D3DRenderStateType { D3DRS_AMBIENTMATERIALSOURCE (147, D3DMATERIALCOLORSOURCE.class), D3DRS_EMISSIVEMATERIALSOURCE (148, D3DMATERIALCOLORSOURCE.class), D3DRS_VERTEXBLEND (151, D3DVERTEXBLENDFLAGS.class), - D3DRS_CLIPPLANEENABLE (152), //int, bit mask - D3DRS_POINTSIZE (154), //float - D3DRS_POINTSIZE_MIN (155), //float + D3DRS_CLIPPLANEENABLE (152, int.class), // bit mask + D3DRS_POINTSIZE (154, float.class), + D3DRS_POINTSIZE_MIN (155, float.class), D3DRS_POINTSPRITEENABLE (156, D3DBOOLEAN.class), D3DRS_POINTSCALEENABLE (157, D3DBOOLEAN.class), - D3DRS_POINTSCALE_A (158), //float - D3DRS_POINTSCALE_B (159), //float - D3DRS_POINTSCALE_C (160), //float + D3DRS_POINTSCALE_A (158, float.class), + D3DRS_POINTSCALE_B (159, float.class), + D3DRS_POINTSCALE_C (160, float.class), D3DRS_MULTISAMPLEANTIALIAS (161, D3DBOOLEAN.class), - D3DRS_MULTISAMPLEMASK (162), //int + D3DRS_MULTISAMPLEMASK (162, int.class), D3DRS_PATCHEDGESTYLE (163, D3DPATCHEDGESTYLE.class), D3DRS_DEBUGMONITORTOKEN (165, D3DDEBUGMONITORTOKENS.class), - D3DRS_POINTSIZE_MAX (166), //float + D3DRS_POINTSIZE_MAX (166, float.class), D3DRS_INDEXEDVERTEXBLENDENABLE (167, D3DBOOLEAN.class), - D3DRS_COLORWRITEENABLE (168), //int - D3DRS_TWEENFACTOR (170), //float + D3DRS_COLORWRITEENABLE (168, int.class), + D3DRS_TWEENFACTOR (170, float.class), D3DRS_BLENDOP (171, D3DBLENDOP.class), D3DRS_POSITIONDEGREE (172, D3DDEGREETYPE.class), D3DRS_NORMALDEGREE (173, D3DDEGREETYPE.class), D3DRS_SCISSORTESTENABLE (174, D3DBOOLEAN.class), - D3DRS_SLOPESCALEDEPTHBIAS (175), //float + D3DRS_SLOPESCALEDEPTHBIAS (175, float.class), D3DRS_ANTIALIASEDLINEENABLE (176, D3DBOOLEAN.class), - D3DRS_MINTESSELLATIONLEVEL (178), //float - D3DRS_MAXTESSELLATIONLEVEL (179), //float - D3DRS_ADAPTIVETESS_X (180), //float - D3DRS_ADAPTIVETESS_Y (181), //float - D3DRS_ADAPTIVETESS_Z (182), //float - D3DRS_ADAPTIVETESS_W (183), //float + D3DRS_MINTESSELLATIONLEVEL (178, float.class), + D3DRS_MAXTESSELLATIONLEVEL (179, float.class), + D3DRS_ADAPTIVETESS_X (180, float.class), + D3DRS_ADAPTIVETESS_Y (181, float.class), + D3DRS_ADAPTIVETESS_Z (182, float.class), + D3DRS_ADAPTIVETESS_W (183, float.class), D3DRS_ENABLEADAPTIVETESSELLATION (184, D3DBOOLEAN.class), D3DRS_TWOSIDEDSTENCILMODE (185, D3DBOOLEAN.class), D3DRS_CCW_STENCILFAIL (186, D3DSTENCILOP.class), D3DRS_CCW_STENCILZFAIL (187, D3DSTENCILOP.class), D3DRS_CCW_STENCILPASS (188, D3DSTENCILOP.class), D3DRS_CCW_STENCILFUNC (189, D3DCMPFUNC.class), - D3DRS_COLORWRITEENABLE1 (190), //int - D3DRS_COLORWRITEENABLE2 (191), //int - D3DRS_COLORWRITEENABLE3 (192), //int - D3DRS_BLENDFACTOR (193), //color - D3DRS_SRGBWRITEENABLE (194), //int - D3DRS_DEPTHBIAS (195), //float + D3DRS_COLORWRITEENABLE1 (190, int.class), + D3DRS_COLORWRITEENABLE2 (191, int.class), + D3DRS_COLORWRITEENABLE3 (192, int.class), + D3DRS_BLENDFACTOR (193, ColorRGBA.class), + D3DRS_SRGBWRITEENABLE (194, int.class), + D3DRS_DEPTHBIAS (195, float.class), D3DRS_WRAP8 (198, D3DWRAPCOORD.class), D3DRS_WRAP9 (199, D3DWRAPCOORD.class), D3DRS_WRAP10 (200, D3DWRAPCOORD.class), @@ -456,24 +507,14 @@ public static enum D3DRenderStateType { D3DRS_SRCBLENDALPHA (207, D3DBLEND.class), D3DRS_DESTBLENDALPHA (208, D3DBLEND.class), D3DRS_BLENDOPALPHA (209, D3DBLENDOP.class), - D3DRS_FORCE_DWORD (0x7fffffff); + D3DRS_FORCE_DWORD (0x7fffffff, int.class); public int id; - Class> enumClass; - private D3DRenderStateType(int id) { - this.id = id; - } - private D3DRenderStateType(int id, Class> enumClass) { + public Class typeClass; + + private D3DRenderStateType(int id, Class typeClass) { this.id = id; - this.enumClass = enumClass; - } - public String getValueToString(int value) { - D3DStateEnum stateEnum = getStateEnumById(value, enumClass); - if (stateEnum == null) { - return Integer.toString(value); - } else { - return stateEnum.toString(); - } + this.typeClass = typeClass; } public static D3DRenderStateType getById(int id) { for (D3DRenderStateType state : values()) { @@ -571,36 +612,26 @@ public static enum D3DTextureStageStateType { D3DTSS_ALPHAOP (4, D3DTEXTUREOP.class), D3DTSS_ALPHAARG1 (5, D3DTA.class), D3DTSS_ALPHAARG2 (6, D3DTA.class), - D3DTSS_BUMPENVMAT00 (7), //float - D3DTSS_BUMPENVMAT01 (8), //float - D3DTSS_BUMPENVMAT10 (9), //float - D3DTSS_BUMPENVMAT11 (10), //float - D3DTSS_TEXCOORDINDEX (11), //int - D3DTSS_BUMPENVLSCALE (22), //float - D3DTSS_BUMPENVLOFFSET (23), //float + D3DTSS_BUMPENVMAT00 (7, float.class), //float + D3DTSS_BUMPENVMAT01 (8, float.class), //float + D3DTSS_BUMPENVMAT10 (9, float.class), //float + D3DTSS_BUMPENVMAT11 (10, float.class), //float + D3DTSS_TEXCOORDINDEX (11, int.class), //int + D3DTSS_BUMPENVLSCALE (22, float.class), //float + D3DTSS_BUMPENVLOFFSET (23, float.class), //float D3DTSS_TEXTURETRANSFORMFLAGS (24, D3DTEXTURETRANSFORMFLAGS.class), D3DTSS_COLORARG0 (26, D3DTA.class), D3DTSS_ALPHAARG0 (27, D3DTA.class), D3DTSS_RESULTARG (28, D3DTA.class), D3DTSS_CONSTANT (32, D3DTA.class), - D3DTSS_FORCE_DWORD (0x7fffffff); + D3DTSS_FORCE_DWORD (0x7fffffff, int.class); public int id; - Class> enumClass; - private D3DTextureStageStateType(int id) { - this.id = id; - } - private D3DTextureStageStateType(int id, Class> enumClass) { + public Class typeClass; + + private D3DTextureStageStateType(int id, Class typeClass) { this.id = id; - this.enumClass = enumClass; - } - public String getValueToString(int value) { - D3DStateEnum stateEnum = getStateEnumById(value, enumClass); - if (stateEnum == null) { - return Integer.toString(value); - } else { - return stateEnum.toString(); - } + this.typeClass = typeClass; } public static D3DTextureStageStateType getById(int id) { for (D3DTextureStageStateType state : values()) { @@ -646,34 +677,24 @@ public static enum D3DSamplerStateType { D3DSAMP_ADDRESSU (1, D3DTEXTUREADDRESS.class), D3DSAMP_ADDRESSV (2, D3DTEXTUREADDRESS.class), D3DSAMP_ADDRESSW (3, D3DTEXTUREADDRESS.class), - D3DSAMP_BORDERCOLOR (4), //color + D3DSAMP_BORDERCOLOR (4, ColorRGBA.class), //color D3DSAMP_MAGFILTER (5, D3DTEXTUREFILTERTYPE.class), D3DSAMP_MINFILTER (6, D3DTEXTUREFILTERTYPE.class), D3DSAMP_MIPFILTER (7, D3DTEXTUREFILTERTYPE.class), - D3DSAMP_MIPMAPLODBIAS (8), //int - D3DSAMP_MAXMIPLEVEL (9), //int - D3DSAMP_MAXANISOTROPY (10), //int - D3DSAMP_SRGBTEXTURE (11), //int - D3DSAMP_ELEMENTINDEX (12), //int - D3DSAMP_DMAPOFFSET (13), //int - D3DSAMP_FORCE_DWORD (0x7fffffff); + D3DSAMP_MIPMAPLODBIAS (8, int.class), //int + D3DSAMP_MAXMIPLEVEL (9, int.class), //int + D3DSAMP_MAXANISOTROPY (10, int.class), //int + D3DSAMP_SRGBTEXTURE (11, int.class), //int + D3DSAMP_ELEMENTINDEX (12, int.class), //int + D3DSAMP_DMAPOFFSET (13, int.class), //int + D3DSAMP_FORCE_DWORD (0x7fffffff, int.class); public int id; - Class> enumClass; - private D3DSamplerStateType(int id) { - this.id = id; - } - private D3DSamplerStateType(int id, Class> enumClass) { + public Class typeClass; + + private D3DSamplerStateType(int id, Class typeClass) { this.id = id; - this.enumClass = enumClass; - } - public String getValueToString(int value) { - D3DStateEnum stateEnum = getStateEnumById(value, enumClass); - if (stateEnum == null) { - return Integer.toString(value); - } else { - return stateEnum.toString(); - } + this.typeClass = typeClass; } public static D3DSamplerStateType getById(int id) { for (D3DSamplerStateType state : values()) { diff --git a/src/sporemodder/file/rw4/MaterialStateCompiler.java b/src/sporemodder/file/rw4/MaterialStateCompiler.java index 2858b71..8d2a826 100644 --- a/src/sporemodder/file/rw4/MaterialStateCompiler.java +++ b/src/sporemodder/file/rw4/MaterialStateCompiler.java @@ -20,40 +20,48 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import emord.filestructures.FileStream; import emord.filestructures.MemoryStream; +import sporemodder.HashManager; +import sporemodder.file.argscript.ArgScriptWriter; import sporemodder.file.rw4.Direct3DEnums.D3DPRIMITIVETYPE; import sporemodder.file.rw4.Direct3DEnums.D3DRenderStateType; import sporemodder.file.rw4.Direct3DEnums.D3DSamplerStateType; import sporemodder.file.rw4.Direct3DEnums.D3DTextureStageStateType; +import sporemodder.file.shaders.ShaderData; import sporemodder.util.ColorRGB; import sporemodder.util.ColorRGBA; public class MaterialStateCompiler { - public static class ShaderConstant { + public static class ShaderDataEntry { public short index; public short offset; - public byte[] data; + public int[] data; } public static class TextureSlot { + public final LinkedHashMap textureStageStates = new LinkedHashMap(); public final LinkedHashMap samplerStates = new LinkedHashMap(); // Usually a RWRaster, but might be a RWCompiledState too public RWObject raster; - public int unkStageStates = 0x3F; - public int unkSamplerStates = 0x73; + public int stageStatesMask; + public int samplerStatesMask; public int samplerIndex; } + public static final String KEYWORD = "compiledState"; + /** Used in the first flags value, whether shader constants are defined. */ - private static final int FLAG_SHADER_CONSTANTS = 0x8; + private static final int FLAG_SHADER_DATA = 0x8; /** Used in the first flags value, whether the material color is defined. */ private static final int FLAG_MATERIAL_COLOR = 0x10; /** Used in the first flags value, whether the ambient color is defined. */ @@ -73,15 +81,19 @@ public static class TextureSlot { private static final int FLAG3_PALETTE_ENTRIES = 0x100000; /** Used in the third flags value, which texture slots are used. */ // Spore checks for 0xDFFFF, using this should be enough - private static final int FLAG3_TEXTURE_SLOTS = 0xDFFFF; + private static final int FLAG3_TEXTURE_SLOTS = 0x1FFFF; + + // 0x10803B + private static final int FLAG_MASK = FLAG_SHADER_DATA | FLAG_MATERIAL_COLOR | FLAG_AMBIENT_COLOR | + FLAG_VERTEX_DESCRIPTION | FLAG_USE_BOOLEANS | FLAG_MODELTOWORLD | FLAG_MODELTOWORLD_OBJECT; + + private static final int FLAG3_MASK = FLAG3_RENDER_STATES | FLAG3_PALETTE_ENTRIES | FLAG3_TEXTURE_SLOTS; // Should only be used inside this package byte[] data; private RenderWare renderWare; private boolean isDecompiled; - - private boolean automaticFlags; public ColorRGBA materialColor; public ColorRGB ambientColor; @@ -89,59 +101,95 @@ public static class TextureSlot { public int rendererID; public D3DPRIMITIVETYPE primitiveType = D3DPRIMITIVETYPE.D3DPT_TRIANGLELIST; public final List textureSlots = new ArrayList(); - public final List shaderConstants = new ArrayList(); + public final List shaderData = new ArrayList(); - public final LinkedHashMap renderStates = new LinkedHashMap(); + public final Map> renderStates = new LinkedHashMap<>(); /** Either a RWObject or a float[4][4], representing the 4x4 modelToWorld matrix. */ public Object modelToWorld; - private int flags1; - private int flags2; - private int flags3; + public int extraFlags1; + public int extraFlags2; + public int extraFlags3; public int field_14; /** Only read if flags1 & 0x3FC0; each bit tells whether data is available for that * position or not. */ // it's baked lighting data? - private Float[] unkData2; + public Float[] unkData2; /** Only read if flags1 & FLAG_USE_BOOLEANS; it's 17 booleans. */ - private boolean[] unkData3; + public boolean[] unkData3; /** Only read if flags1 & 0x10000. */ - private Integer unkData4; + public Integer unkData4; /** Only read if flags1 & 0x20000; 3 floats. */ - private float[] unkData5; + public float[] unkData5; /** Only read if flags1 & 0x40000. */ - private Float unkData6; + public Float unkData6; /** Only read if flags1 & 0x80000. */ - private Float unkData7; + public Float unkData7; /** Only read if field_14 & 0x20000; 7 integers, apparently flags. */ - private int[] unkData8; + public int[] unkData8; /** Only read if field_14 & 0x40000; 11 integers. */ - private int[] unkData9; + public int[] unkData9; /** Only read if field_14 & 0x80000; 11 integers. */ - private int[] unkData10; + public int[] unkData10; /** Only read if flags3 & 0x100000. This object must contain 256 palette entries, * with each palette entry being a 4-byte color. */ - private RWObject paletteEntries; + public RWObject paletteEntries; public MaterialStateCompiler(RenderWare renderWare) { super(); this.renderWare = renderWare; } + + public void reset() { + isDecompiled = false; + materialColor = null; + ambientColor = null; + vertexDescription = null; + primitiveType = D3DPRIMITIVETYPE.D3DPT_TRIANGLELIST; + rendererID = 0; + textureSlots.clear(); + shaderData.clear(); + renderStates.clear(); + modelToWorld = null; + extraFlags1 = 0; + extraFlags2 = 0; + extraFlags3 = 0; + field_14 = 0; + unkData2 = null; + unkData3 = null; + unkData4 = null; + unkData5 = null; + unkData6 = null; + unkData7 = null; + unkData8 = null; + unkData9 = null; + unkData10 = null; + paletteEntries = null; + } + + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } public void decompile() throws IOException { try (MemoryStream stream = new MemoryStream(data)) { + reset(); // Size stream.skip(4); primitiveType = D3DPRIMITIVETYPE.getById(stream.readLEInt()); - flags1 = stream.readLEInt(); - flags2 = stream.readLEInt(); - flags3 = stream.readLEInt(); + int flags1 = stream.readLEInt(); + int flags2 = stream.readLEInt(); + int flags3 = stream.readLEInt(); field_14 = stream.readLEInt(); rendererID = stream.readLEInt(); @@ -165,17 +213,23 @@ public void decompile() throws IOException { vertexDescription.read(stream); } - if ((flags1 & FLAG_SHADER_CONSTANTS) != 0) { + if ((flags1 & FLAG_SHADER_DATA) != 0) { short index = stream.readLEShort(); while (index != 0) { if (index > 0) { - ShaderConstant sc = new ShaderConstant(); + ShaderDataEntry sc = new ShaderDataEntry(); + shaderData.add(sc); sc.index = index; sc.offset = stream.readLEShort(); - sc.data = new byte[stream.readLEInt()]; + int length = stream.readLEInt(); + if (length % 4 != 0) throw new IOException("Unexpected ShaderData length " + length); + sc.data = new int[length / 4]; stream.skip(sc.offset); // ? - stream.read(sc.data); + stream.readLEInts(sc.data); + + // Spore uses it as a render ware index + if (sc.data.length == 0) stream.skip(4); } index = stream.readLEShort(); @@ -240,17 +294,19 @@ public void decompile() throws IOException { } if ((flags3 & FLAG3_RENDER_STATES) != 0) { - stream.skip(4); + int group = stream.readLEInt(); - while (true) { - int state = stream.readLEInt(); - int value = stream.readLEInt(); + while (group != -1) { + Map states = new LinkedHashMap<>(); - if (state == -1 && value == -1) { - break; + int state = stream.readLEInt(); + while (state != -1) { + states.put(D3DRenderStateType.getById(state), stream.readLEInt()); + state = stream.readLEInt(); } - renderStates.put(D3DRenderStateType.getById(state), value); + renderStates.put(group, states); + group = stream.readLEInt(); } } @@ -272,8 +328,8 @@ public void decompile() throws IOException { slot.samplerIndex = samplerIndex; slot.raster = renderWare.get(stream.readLEInt()); - slot.unkStageStates = stream.readLEInt(); - if (slot.unkStageStates != 0) { + slot.stageStatesMask = stream.readLEInt(); + if (slot.stageStatesMask != 0) { int state; while ((state = stream.readLEInt()) != -1) { @@ -281,8 +337,8 @@ public void decompile() throws IOException { } } - slot.unkSamplerStates = stream.readLEInt(); - if (slot.unkSamplerStates != 0) { + slot.samplerStatesMask = stream.readLEInt(); + if (slot.samplerStatesMask != 0) { int state; while ((state = stream.readLEInt()) != -1) { @@ -292,20 +348,27 @@ public void decompile() throws IOException { } } + extraFlags1 = flags1 & ~FLAG_MASK; + extraFlags2 = ~flags1 & flags2; + extraFlags3 = flags3 & ~FLAG3_MASK; + isDecompiled = true; } } public void compile() throws IOException { - if (automaticFlags) { - flags1 = 0; - flags2 = 0; - flags3 = 0; - - // Always used? Don't really know what it does - flags1 |= 4; - flags2 |= 0x8000; - } + + int flags1 = 0; + int flags2 = 0; + int flags3 = 0; + +// // Always used? Don't really know what it does +// flags1 |= 4; +// flags2 |= 0x8000; + + flags1 |= extraFlags1; + flags2 |= extraFlags2; + flags3 |= extraFlags3; if (materialColor != null) flags1 |= FLAG_MATERIAL_COLOR; else flags1 &= ~FLAG_MATERIAL_COLOR; @@ -313,32 +376,32 @@ public void compile() throws IOException { if (ambientColor != null) flags1 |= FLAG_AMBIENT_COLOR; else flags1 &= ~FLAG_AMBIENT_COLOR; - if (unkData3 != null) flags1 |= FLAG_USE_BOOLEANS; - else flags1 &= ~FLAG_USE_BOOLEANS; + flags1 |= FLAG_USE_BOOLEANS; +// if (unkData3 != null) flags1 |= FLAG_USE_BOOLEANS; +// else flags1 &= ~FLAG_USE_BOOLEANS; - if (!shaderConstants.isEmpty()) { - flags1 |= FLAG_SHADER_CONSTANTS; - flags2 |= FLAG_SHADER_CONSTANTS; + if (!shaderData.isEmpty()) { + flags1 |= FLAG_SHADER_DATA; } else { - flags2 &= ~FLAG_SHADER_CONSTANTS; - flags2 &= ~FLAG_SHADER_CONSTANTS; + flags2 &= ~FLAG_SHADER_DATA; } if (vertexDescription != null) { flags1 |= FLAG_VERTEX_DESCRIPTION; - flags2 |= FLAG_VERTEX_DESCRIPTION; } else { flags2 &= ~FLAG_VERTEX_DESCRIPTION; - flags2 &= ~FLAG_VERTEX_DESCRIPTION; } if (!renderStates.isEmpty()) flags3 |= FLAG3_RENDER_STATES; else flags3 &= ~FLAG3_RENDER_STATES; - if (!textureSlots.isEmpty()) flags3 |= FLAG3_TEXTURE_SLOTS; - else flags3 &= ~FLAG3_TEXTURE_SLOTS; + for (TextureSlot slot : textureSlots) { + flags3 |= 1 << slot.samplerIndex; + } +// if (!textureSlots.isEmpty()) flags3 |= FLAG3_TEXTURE_SLOTS; +// else flags3 &= ~FLAG3_TEXTURE_SLOTS; if (paletteEntries != null) flags3 |= FLAG3_PALETTE_ENTRIES; else flags3 &= ~FLAG3_PALETTE_ENTRIES; @@ -386,6 +449,9 @@ public void compile() throws IOException { if (unkData10 != null) field_14 |= 0x80000; else field_14 &= ~0x80000; + System.out.println("0x" + Integer.toHexString(flags2)); + flags2 |= (0xFFFF & flags1); + try (MemoryStream stream = new MemoryStream()) { @@ -413,15 +479,19 @@ public void compile() throws IOException { vertexDescription.write(stream); } - if (!shaderConstants.isEmpty()) { - for (ShaderConstant sc : shaderConstants) { + if (!shaderData.isEmpty()) { + for (ShaderDataEntry sc : shaderData) { stream.writeLEShort(sc.index); stream.writeLEShort(sc.offset); - stream.writeLEInt(sc.data.length); + stream.writeLEInt(sc.data.length * 4); if (sc.offset > 0) stream.writePadding(sc.offset); - stream.write(sc.data); + stream.writeLEInts(sc.data); + + if (sc.data.length == 0) stream.writeInt(0); } + + stream.writePadding(8); } if (materialColor != null) { @@ -437,9 +507,14 @@ public void compile() throws IOException { } } - if (unkData3 != null) { - stream.writeBooleans(unkData3); + if (unkData3 == null) { + unkData3 = new boolean[17]; + for (TextureSlot slot : textureSlots) { + unkData3[slot.samplerIndex] = true; + } } + // This is not an option anymore. Setting all to false has the same effect as not having them? + stream.writeBooleans(unkData3); if (unkData4 != null) stream.writeLEInt(unkData4); if (unkData5 != null) stream.writeLEFloats(unkData5); @@ -450,14 +525,14 @@ public void compile() throws IOException { if (unkData10 != null) stream.writeLEInts(unkData10); if (!renderStates.isEmpty()) { - stream.writeLEInt(0); - - for (Map.Entry entry : renderStates.entrySet()) { - stream.writeLEInt(entry.getKey().id); - stream.writeLEInt(entry.getValue()); + for (int group : renderStates.keySet()) { + stream.writeLEInt(group); + for (Map.Entry entry : renderStates.get(group).entrySet()) { + stream.writeLEInt(entry.getKey().id); + stream.writeLEInt(entry.getValue()); + } + stream.writeLEInt(-1); } - - stream.writeLEInt(-1); stream.writeLEInt(-1); } @@ -470,10 +545,10 @@ public void compile() throws IOException { for (TextureSlot slot : textureSlots) { stream.writeLEInt(slot.samplerIndex); - stream.writeLEInt(renderWare.indexOf(slot.raster)); + stream.writeLEInt(slot.raster == null ? -1 : renderWare.indexOf(slot.raster)); - stream.writeLEInt(slot.unkStageStates); - if (slot.unkStageStates != 0) { + stream.writeLEInt(slot.stageStatesMask); + if (slot.stageStatesMask != 0) { for (Map.Entry entry : slot.textureStageStates.entrySet()) { stream.writeLEInt(entry.getKey().id); stream.writeLEInt(entry.getValue()); @@ -481,9 +556,9 @@ public void compile() throws IOException { stream.writeLEInt(-1); } - stream.writeLEInt(slot.unkStageStates); - if (slot.unkStageStates != 0) { - for (Map.Entry entry : slot.textureStageStates.entrySet()) { + stream.writeLEInt(slot.samplerStatesMask); + if (slot.samplerStatesMask != 0) { + for (Map.Entry entry : slot.samplerStates.entrySet()) { stream.writeLEInt(entry.getKey().id); stream.writeLEInt(entry.getValue()); } @@ -496,20 +571,18 @@ public void compile() throws IOException { stream.seek(0); stream.writeLEUInt(stream.length()); + + data = stream.toByteArray(); } } - public boolean isAutomaticFlags() { - return automaticFlags; - } - - /** - * If true, the flags value will be reseted and will be set depending on the existing - * parameters. - * @param automaticFlags - */ - public void setAutomaticFlags(boolean automaticFlags) { - this.automaticFlags = automaticFlags; + private boolean compareUnkData3() { + for (int i = 0; i < 17; ++i) { + if (i < textureSlots.size()) { + if (!unkData3[i]) return true; + } else if (unkData3[i]) return false; + } + return true; } /** @@ -520,13 +593,165 @@ public void setAutomaticFlags(boolean automaticFlags) { public boolean isDecompiled() { return isDecompiled; } + + public static int calculateStageStateMask(TextureSlot slot) { + int mask = 0; + for (D3DTextureStageStateType key : slot.textureStageStates.keySet()) { + mask |= 1 << (key.id-1); + } + return mask; + } + + public static int calculateSamplerStateMask(TextureSlot slot) { + int mask = 0; + for (D3DSamplerStateType key : slot.samplerStates.keySet()) { + mask |= 1 << (key.id-1); + } + return mask; + } + + public String toArgScript(String name) { + ArgScriptWriter writer = new ArgScriptWriter(); + toArgScript(name, writer); + return writer.toString(); + } + + public void toArgScript(String name, ArgScriptWriter writer) { + writer.command(KEYWORD).arguments(name).startBlock(); + + writer.command("shaderID").arguments(HashManager.get().getFileName(rendererID)); + if (primitiveType != D3DPRIMITIVETYPE.D3DPT_TRIANGLELIST) { + writer.command("primitiveType").arguments(primitiveType.toString()); + } + + if (materialColor != null) writer.command("materialColor").colorsRGBA(materialColor); + if (ambientColor != null) writer.command("ambientColor").color(ambientColor); + + if (extraFlags1 != 0 || extraFlags3 != 0) { + writer.blankLine(); + if (extraFlags1 != 0) writer.command("flags1").arguments("0x" + Integer.toHexString(extraFlags1)); + if (extraFlags3 != 0) writer.command("flags3").arguments("0x" + Integer.toHexString(extraFlags3)); + } + + if (!shaderData.isEmpty()) { + writer.blankLine(); + for (ShaderDataEntry sd : shaderData) { + writer.command("shaderData").arguments(ShaderData.getName(sd.index)); + for (int i : sd.data) { + writer.arguments(HashManager.get().formatInt32(i)); + } + if (sd.offset != 0) writer.option("offset").ints(sd.offset); + } + } + + if (unkData3 != null) { + if (!compareUnkData3()) { + writer.blankLine(); + writer.command("unkData3"); + for (int i = 0; i < unkData3.length; ++i) { + writer.arguments(unkData3[i]); + } + } + } else { + writer.blankLine(); + // No unkData3 command means assigning it automatically, so this is to disable it + writer.command("unkData3"); + for (int i = 0; i < 17; ++i) writer.ints(0); + } + + if (vertexDescription != null) { + writer.blankLine(); + writer.command("vertexDescription"); + + if (vertexDescription.field_0 != 0) writer.option("field_0").arguments("0x" + Integer.toHexString(vertexDescription.field_0)); + if (vertexDescription.field_4 != 0) writer.option("field_4").arguments("0x" + Integer.toHexString(vertexDescription.field_4)); + if (vertexDescription.field_0E != 0) writer.option("field_0E").arguments("0x" + Integer.toHexString(vertexDescription.field_0E)); + if (vertexDescription.field_10 != 0) writer.option("field_10").arguments("0x" + Integer.toHexString(vertexDescription.field_10)); + if (vertexDescription.field_14 != 0) writer.option("field_14").arguments("0x" + Integer.toHexString(vertexDescription.field_14)); + + writer.startBlock(); + for (RWVertexElement element : vertexDescription.elements) { + element.toArgScript(writer); + } + writer.endBlock().commandEND(); + } + + if (!renderStates.isEmpty()) { + writer.blankLine(); + Set groups = renderStates.keySet(); + if (groups.size() == 1 && groups.contains(0)) { + for (Map.Entry entry : renderStates.get(0).entrySet()) { + writer.command("renderState").arguments(entry.getKey(), Direct3DEnums.getValueToString(entry.getKey().typeClass, entry.getValue())); + } + } + else { + for (int group : renderStates.keySet()) { + writer.command("statesGroup").ints(group).startBlock(); + for (Map.Entry entry : renderStates.get(group).entrySet()) { + writer.command("renderState").arguments(entry.getKey(), Direct3DEnums.getValueToString(entry.getKey().typeClass, entry.getValue())); + } + writer.endBlock().commandEND(); + } + } + } + + for (TextureSlot slot : textureSlots) { + writer.blankLine(); + writer.command("textureSlot").ints(slot.samplerIndex).startBlock(); + + boolean first = true; + if (slot.raster != null) { + first = false; + writer.command("raster").ints(renderWare.indexOf(slot.raster)); + } + + if (!slot.textureStageStates.isEmpty()) { + if (!first) writer.blankLine(); + first = false; + for (Map.Entry entry : slot.textureStageStates.entrySet()) { + writer.command("stageState").arguments(entry.getKey(), Direct3DEnums.getValueToString(entry.getKey().typeClass, entry.getValue())); + } + } + + if (!slot.samplerStates.isEmpty()) { + if (!first) writer.blankLine(); + first = false; + for (Map.Entry entry : slot.samplerStates.entrySet()) { + writer.command("samplerState").arguments(entry.getKey(), Direct3DEnums.getValueToString(entry.getKey().typeClass, entry.getValue())); + } + } + + writer.endBlock().commandEND(); + } + + writer.endBlock().commandEND(); + } public static void main(String[] args) throws IOException { + //File file = new File("E:\\Eric\\SporeModder\\Projects\\Spore_Graphics.package.unpacked\\#40212001\\#00000003.rw4"); + File file = new File("C:\\Users\\Eric\\Desktop\\test.rw4"); + + MaterialStateCompiler state = new MaterialStateCompiler(new RenderWare()); + state.data = Files.readAllBytes(file.toPath()); + state.decompile(); + + try (FileStream stream = new FileStream(file, "r")) { +// RenderWare renderWare = new RenderWare(); +// renderWare.read(stream); +// List compiledStates = renderWare.getObjects(RWCompiledState.class); +// for (RWCompiledState state : compiledStates) { +// state.data.decompile(); +// +// } + } + catch (Exception e) { + e.printStackTrace(); + } + // File folder = new File("E:\\Eric\\SporeModder\\Projects\\Spore_Graphics.package.unpacked\\animations~"); // for (File file : folder.listFiles()) { // if (file.getName().endsWith(".rw4")) { -// // try (FileStream stream = new FileStream(file, "r")) { // RenderWare renderWare = new RenderWare(); // renderWare.read(stream); @@ -534,53 +759,10 @@ public static void main(String[] args) throws IOException { // for (RWCompiledState state : compiledStates) { // state.data.decompile(); // -// if (state.data.unkData1 != null) { -// System.out.println("1: " + file.getName()); -// } -// -// if (state.data.unkData2 != null) { -// System.out.println("2: " + file.getName()); -// } -// -// if (state.data.unkData4 != null) System.out.println("4: " + file.getName()); -// if (state.data.unkData5 != null) System.out.println("5: " + file.getName()); -// if (state.data.unkData6 != null) System.out.println("6: " + file.getName()); -// if (state.data.unkData7 != null) System.out.println("7: " + file.getName()); -// if (state.data.unkData8 != null) System.out.println("8: " + file.getName()); -// if (state.data.unkData9 != null) System.out.println("9: " + file.getName()); -// if (state.data.unkData10 != null) System.out.println("10: " + file.getName()); -// if (state.data.paletteEntries != null) System.out.println("paletteEntries: " + file.getName()); +// //if (state.data.extraFlags != null && !state.data.compareUnkData3()) System.out.println("Wrong data 3"); // } // } -// catch (Exception e) { -// e.printStackTrace(); -// continue; -// } // } // } - - File file = new File("E:\\Eric\\SporeModder\\Projects\\Spore_Graphics.package.unpacked\\#40212001\\#00000003.rw4"); - - try (FileStream stream = new FileStream(file, "r")) { - RenderWare renderWare = new RenderWare(); - renderWare.read(stream); - List compiledStates = renderWare.getObjects(RWCompiledState.class); - for (RWCompiledState state : compiledStates) { - state.data.decompile(); - - if (state.data.unkData2 != null) System.out.println("2: " + state.sectionInfo.pData); - if (state.data.unkData4 != null) System.out.println("4: " + state.sectionInfo.pData); - if (state.data.unkData5 != null) System.out.println("5: " + state.sectionInfo.pData); - if (state.data.unkData6 != null) System.out.println("6: " + state.sectionInfo.pData); - if (state.data.unkData7 != null) System.out.println("7: " + state.sectionInfo.pData); - if (state.data.unkData8 != null) System.out.println("8: " + state.sectionInfo.pData); - if (state.data.unkData9 != null) System.out.println("9: " + state.sectionInfo.pData); - if (state.data.unkData10 != null) System.out.println("10: " + state.sectionInfo.pData); - if (state.data.paletteEntries != null) System.out.println("paletteEntries: " + state.sectionInfo.pData); - } - } - catch (Exception e) { - e.printStackTrace(); - } } } diff --git a/src/sporemodder/file/rw4/RWCompiledState.java b/src/sporemodder/file/rw4/RWCompiledState.java index e47bdea..5dbe587 100644 --- a/src/sporemodder/file/rw4/RWCompiledState.java +++ b/src/sporemodder/file/rw4/RWCompiledState.java @@ -28,7 +28,7 @@ public class RWCompiledState extends RWObject { public static final int TYPE_CODE = 0x2000b; public static final int ALIGNMENT = 16; - public final MaterialStateCompiler data; + public MaterialStateCompiler data; public RWCompiledState(RenderWare renderWare) { super(renderWare); diff --git a/src/sporemodder/file/rw4/RWVertexElement.java b/src/sporemodder/file/rw4/RWVertexElement.java index 3587273..2ac6d9f 100644 --- a/src/sporemodder/file/rw4/RWVertexElement.java +++ b/src/sporemodder/file/rw4/RWVertexElement.java @@ -22,6 +22,7 @@ import emord.filestructures.StreamReader; import emord.filestructures.StreamWriter; +import sporemodder.file.argscript.ArgScriptWriter; import sporemodder.file.rw4.Direct3DEnums.D3DDECLMETHOD; import sporemodder.file.rw4.Direct3DEnums.D3DDECLTYPE; import sporemodder.file.rw4.Direct3DEnums.D3DDECLUSAGE; @@ -29,6 +30,8 @@ public class RWVertexElement { + public static final String KEYWORD = "element"; + public int stream; /** The offset position of this element inside the vertex data. */ public int offset; @@ -36,7 +39,7 @@ public class RWVertexElement { public D3DDECLMETHOD method; public D3DDECLUSAGE usage; public byte usageIndex; - public RWDECLUSAGE typeCode; + public int typeCode; public void read(StreamReader stream) throws IOException { this.stream = stream.readLEShort(); @@ -45,7 +48,7 @@ public void read(StreamReader stream) throws IOException { method = D3DDECLMETHOD.getById(stream.readByte()); usage = D3DDECLUSAGE.getById(stream.readByte()); usageIndex = stream.readByte(); - typeCode = RWDECLUSAGE.getById(stream.readLEInt()); + typeCode = stream.readLEInt(); } public void write(StreamWriter stream) throws IOException { @@ -55,6 +58,13 @@ public void write(StreamWriter stream) throws IOException { stream.writeByte(method.id); stream.writeByte(usage.id); stream.writeByte(usageIndex); - stream.writeLEInt(typeCode.id); + stream.writeLEInt(typeCode); + } + + public void toArgScript(ArgScriptWriter writer) { + writer.command(KEYWORD).arguments(type, usage, RWDECLUSAGE.getById(typeCode) == null ? typeCode : RWDECLUSAGE.getById(typeCode)); + if (stream != 0) writer.option("stream").ints(stream); + if (usageIndex != 0) writer.option("usageIndex").ints(usageIndex); + if (method != D3DDECLMETHOD.D3DDECLMETHOD_DEFAULT) writer.option("method").arguments(method); } } diff --git a/src/sporemodder/file/rw4/RenderWare.java b/src/sporemodder/file/rw4/RenderWare.java index f200e3c..d8b2366 100644 --- a/src/sporemodder/file/rw4/RenderWare.java +++ b/src/sporemodder/file/rw4/RenderWare.java @@ -61,15 +61,10 @@ public void read(StreamReader stream) throws IOException { else { System.err.println("Unrecognised RW section type: 0x" + Integer.toHexString(info.typeCode)); } - - System.out.println("0x" + Integer.toHexString(info.typeCode) + "\t" + info.pData); } // Now that all objects have been created, read the sub references header.sectionManifest.subReferences.readReferences(stream); - for (SubReference r : header.sectionManifest.subReferences.references) { - System.out.println("SubReference: " + r.offset); - } // Read the objects for (RWObject object : objects) { diff --git a/src/sporemodder/file/rw4/RenderWareConverter.java b/src/sporemodder/file/rw4/RenderWareConverter.java index 1adf9a6..7daecca 100644 --- a/src/sporemodder/file/rw4/RenderWareConverter.java +++ b/src/sporemodder/file/rw4/RenderWareConverter.java @@ -39,6 +39,7 @@ public class RenderWareConverter implements Converter { + public static final int TYPE_ID = 0x2F4E681B; private static String extension = null; private boolean decode(StreamReader stream, File outputFile) throws IOException { @@ -99,7 +100,7 @@ public boolean encode(File input, DBPFPackingTask packer, int groupID) throws Ex DBPFItem item = packer.getTemporaryItem(); item.name.setGroupID(groupID); item.name.setInstanceID(input.getName().split("\\.", 2)[0]); - item.name.setTypeID(0x2F4E681B); // rw4 + item.name.setTypeID(TYPE_ID); // rw4 packer.writeFileData(item, outputStream.getRawData(), (int) outputStream.length()); packer.addFile(item); diff --git a/src/sporemodder/file/shaders/CompiledShader.java b/src/sporemodder/file/shaders/CompiledShader.java new file mode 100644 index 0000000..78de8b5 --- /dev/null +++ b/src/sporemodder/file/shaders/CompiledShader.java @@ -0,0 +1,47 @@ +package sporemodder.file.shaders; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import emord.filestructures.StreamReader; + +public class CompiledShader { + + public final int[] fragmentIndices = new int[32]; + public byte[] data; + public final List dataUniforms = new ArrayList<>(); + public final List startRegisters = new ArrayList<>(); + public int flags; // field_12C + + public String getSignatureString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < fragmentIndices.length; ++i) { + if (fragmentIndices[i] == 0) break; + String text = Integer.toHexString(fragmentIndices[i]); + if (text.length() == 1) sb.append('0'); + sb.append(text); + } + return sb.toString(); + } + + public void read(StreamReader in) throws IOException { + in.readUBytes(fragmentIndices); + + data = new byte[in.readInt()]; + in.read(data); + + int count = in.readInt(); + for (int i = 0; i < count; i++) { + ShaderDataUniform uniform = new ShaderDataUniform(); + uniform.readCompiled(in); + dataUniforms.add(uniform); + } + + for (int i = 0; i < count; ++i) { + startRegisters.add(in.readInt()); + } + + flags = in.readInt(); + } +} diff --git a/src/sporemodder/file/shaders/CompiledShaders.java b/src/sporemodder/file/shaders/CompiledShaders.java new file mode 100644 index 0000000..70c112f --- /dev/null +++ b/src/sporemodder/file/shaders/CompiledShaders.java @@ -0,0 +1,208 @@ +package sporemodder.file.shaders; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import emord.filestructures.FileStream; +import emord.filestructures.Stream.StringEncoding; +import emord.filestructures.StreamReader; +import emord.filestructures.StreamWriter; +import sporemodder.FileManager; +import sporemodder.HashManager; +import sporemodder.MainApp; +import sporemodder.file.shaders.StandardShader.StandardShaderEntry; + +public class CompiledShaders { + + public final List shaders = new ArrayList<>(); + public final List shaderSelectors = new ArrayList<>(); + public final List vertexShaders = new ArrayList<>(); + public final List pixelShaders = new ArrayList<>(); + public int version = 7; + public String name; + + public void read(StreamReader in) throws IOException { + version = in.readInt(); + + int count = in.readInt(); + for (int i = 0; i < count; i++) { + StandardShader shader = new StandardShader(); + shader.read(in, version); + shaders.add(shader); + } + + count = in.readInt(); + for (int i = 0; i < count; i++) { + ShaderSelector shader = new ShaderSelector(); + shader.read(in, version); + shaderSelectors.add(shader); + } + + in.readInt(); // unused + count = in.readInt(); + for (int i = 0; i < count; ++i) { + CompiledShader shader = new CompiledShader(); + shader.read(in); + vertexShaders.add(shader); + } + + in.readInt(); // unused + count = in.readInt(); + for (int i = 0; i < count; ++i) { + CompiledShader shader = new CompiledShader(); + shader.read(in); + pixelShaders.add(shader); + } + + name = in.readString(StringEncoding.ASCII, in.readInt()); + } + + public void write(StreamWriter out) throws IOException { + out.writeInt(version); + + out.writeInt(shaders.size()); + for (StandardShader shader : shaders) shader.write(out, version); + + out.writeInt(shaderSelectors.size()); + for (ShaderSelector shader : shaderSelectors) shader.write(out, version); + + //TODO remove + out.writeInts(0, 0, 0, 0); + +// out.writeInt(0); +// out.writeInt(shaders.size()); +// for (CompiledShader shader : vertexShaders) shader.write(out, version); +// +// out.writeInt(0); +// out.writeInt(shaders.size()); +// for (CompiledShader shader : pixelShaders) shader.write(out, version); + + String name = this.name == null ? "" : this.name; + out.writeInt(name.length()); + out.writeString(name, StringEncoding.ASCII); + } + + public void unpack(File outputFolder) throws IOException { + FileManager.get().deleteDirectory(outputFolder); + outputFolder.mkdir(); + + for (StandardShader shader : shaders) { + String fileName = HashManager.get().hexToString(shader.id) + '(' + shader.name + ").shader"; + + try (StreamWriter stream = new FileStream(new File(outputFolder, fileName), "rw")) { + shader.write(stream, version); + } + } + } + + private static String removeExtension(String name) { + int indexOf = name.indexOf("."); + return indexOf == -1 ? name : name.substring(0, indexOf); + } + + public void pack(File sourceFolder) throws IOException, InterruptedException { + File[] files = sourceFolder.listFiles(); + + Set standardShaderNames = new HashSet<>(); + Map sourceVSHFiles = new HashMap<>(); + Map sourcePSHFiles = new HashMap<>(); + + for (File file : files) { + String fileName = file.getName(); + + if (fileName.endsWith(".vshader.hlsl")) { + String name = removeExtension(fileName); + standardShaderNames.add(name); + sourceVSHFiles.put(name, file); + } + else if (fileName.endsWith(".pshader.hlsl")) { + String name = removeExtension(fileName); + standardShaderNames.add(name); + sourcePSHFiles.put(name, file); + } + //TODO other cases + } + + for (String stdName : standardShaderNames) { + StandardShader shader = new StandardShader(); + shaders.add(shader); + + shader.processName(stdName); + //TODO support multiple entries + StandardShaderEntry entry = new StandardShaderEntry(); + shader.entries.put(0, entry); + + File sourceVertexFile = sourceVSHFiles.get(stdName); + File sourcePixelFile = sourcePSHFiles.get(stdName); + + if (sourceVertexFile == null) { + throw new IOException("Missing vertex shader for '" + stdName + "'"); + } + if (sourcePixelFile == null) { + throw new IOException("Missing pixel shader for '" + stdName + "'"); + } + + shader.compile(entry, sourceVertexFile, sourcePixelFile, sourceFolder); + } + } + + public static void main(String[] args) throws IOException { + MainApp.testInit(); + + File fragmentsFile = new File("E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Materials\\materials_uncompiled_shader_fragments~\\0x00000003.smt"); + File file = new File("E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Materials\\materials_compiled_shaders~\\0x00000003.smt"); + File outputFolder = new File("E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Materials\\materials_compiled_shaders~\\0x00000003.smt.unpacked\\"); + + try (StreamReader stream = new FileStream(file, "r")) { + CompiledShaders shaders = new CompiledShaders(); + shaders.read(stream); + //shaders.unpack(outputFolder); + + ShaderFragments fragments = ShaderFragments.readShaderFragments(fragmentsFile); + for (int i = 0; i < shaders.vertexShaders.size(); ++i) { + CompiledShader entry = shaders.vertexShaders.get(i); + + for (int index : entry.fragmentIndices) { + if (index == 0) break; + System.out.print(fragments.getFragment(index).getName() + " "); + } + System.out.println(); + } + +// for (int i = 0; i < shaders.vertexShaders.size(); ++i) { +// CompiledShader entry = shaders.vertexShaders.get(i); +// System.out.println(entry.getSignatureString()); +// +//// for (ShaderDataUniform data : entry.dataUniforms) { +////// if (data.flags != 0 && ShaderData.getFlags(data.dataIndex) != data.flags) { +////// System.out.println("0x" + Integer.toHexString(data.dataIndex) + "\tflags=0x" + Integer.toHexString(data.flags)); +////// } +//// if (!ShaderData.hasName(data.dataIndex)) { +//// System.out.println("0x" + Integer.toHexString(data.dataIndex) + " in shader " + i); +//// } +//// } +// } + +// //Set indices = new HashSet<>(); +// +// System.out.println(stream.getFilePointer()); +// +// for (ShaderSelector selector : shaders.shaderSelectors) { +// +// } + } + +// File file = new File("E:\\Eric\\SporeModder\\Projects\\CustomMaterials\\materials_compiled_shaders~\\CustomShaderPack.cpp"); +// +// try (StreamReader stream = new FileStream(file, "r")) { +// CompiledShaders shaders = new CompiledShaders(); +// shaders.read(stream); +// } + } +} diff --git a/src/sporemodder/file/shaders/FXCompiler.java b/src/sporemodder/file/shaders/FXCompiler.java new file mode 100644 index 0000000..5585595 --- /dev/null +++ b/src/sporemodder/file/shaders/FXCompiler.java @@ -0,0 +1,138 @@ +package sporemodder.file.shaders; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.ProcessBuilder.Redirect; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Properties; + +import emord.filestructures.MemoryStream; +import emord.filestructures.Stream.StringEncoding; +import sporemodder.AbstractManager; +import sporemodder.MainApp; +import sporemodder.util.WinRegistry; + +public class FXCompiler extends AbstractManager { + + public static FXCompiler get() { + return MainApp.get().getFXCompiler(); + } + + private static final String PROPERTY_fxcFile = "fxcFile"; + + private File fxcFile; + private boolean isAutoPath; + + @Override public void initialize(Properties properties) { + + String path = properties.getProperty(PROPERTY_fxcFile, ""); + if (!path.isEmpty() && !path.equals("AUTO")) { + fxcFile = new File(path); + } else { + autoDetectPath(); + } + } + + @Override public void saveSettings(Properties properties) { + properties.put(PROPERTY_fxcFile, (isAutoPath || fxcFile == null) ? "AUTO" : fxcFile.getAbsolutePath()); + } + + public File getFXCFile() { + return fxcFile; + } + + public boolean autoDetectPath() { + try { + String path = WinRegistry.valueForKey(WinRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\WOW6432Node\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot10"); + + File versionFolder = new File(path, "bin"); + for (File folder : versionFolder.listFiles()) { + File file = new File(folder, "x86\\fxc.exe"); + if (file.exists()) { + fxcFile = file; + isAutoPath = true; + return true; + } + } + + return false; + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException | IOException e) { + e.printStackTrace(); + return false; + } + } + + public File compile(String targetProfile, File sourceHLSL, File includePath, File outputFile) throws IOException, InterruptedException { + + String command = String.format("\"%s\" /T %s /Fo \"%s\" /I \"%s\" \"%s\"", + fxcFile.getAbsolutePath(), targetProfile, outputFile.getAbsolutePath(), includePath.getAbsolutePath(), sourceHLSL.getAbsolutePath()); + +// System.out.println(command); +// Process p = Runtime.getRuntime().exec(command); +// System.out.println(p.waitFor()); +// +// ProcessBuilder builder = new ProcessBuilder(command); +// builder.redirectOutput(Redirect.INHERIT); +// builder.redirectError(Redirect.INHERIT); +// +// int result = builder.start().waitFor(); + + String line; + Process p = Runtime.getRuntime().exec(command); + int result = p.waitFor(); + + if (result != 0) { + StringBuilder sb = new StringBuilder(); + BufferedReader input = new BufferedReader(new InputStreamReader(p.getErrorStream())); + while ((line = input.readLine()) != null) { + sb.append(line); + sb.append('\n'); + } + input.close(); + throw new IOException("Cannot compile " + sourceHLSL.getName() + ": " + sb.toString()); + } + + return outputFile; + } + + public File compile(String targetProfile, File sourceHLSL, File includePath) throws IOException, InterruptedException { + return compile(targetProfile, sourceHLSL, includePath, File.createTempFile("SporeModderFX-compiled-shader", ".fxc.tmp")); + } + + public void getUniformData(byte[] data, List dst) throws IOException { + + try (MemoryStream stream = new MemoryStream(data)) { + stream.seek(8); + // CTAB + if (stream.readInt() != 0x43544142) return; + stream.skip(12); // Size, Creator, Version + int constCount = stream.readLEInt(); + int constOffset = stream.readLEInt(); + + for (int i = 0; i < constCount; ++i) { + stream.seek(constOffset + 12 + i*20); + + ShaderDataUniform uniform = new ShaderDataUniform(); + dst.add(uniform); + + int nameOffset = stream.readLEInt(); + stream.readShort(); // D3DXREGISTER_SET + uniform.register = stream.readShort(); + uniform.registerSize = stream.readShort(); + + stream.seek(12 + nameOffset); + String name = stream.readCString(StringEncoding.ASCII); + if (!ShaderData.hasIndex(name)) { + throw new IOException(name + " is not a recognized shader data uniform."); + } + + uniform.dataIndex = ShaderData.getIndex(name); + uniform.field_2 = uniform.dataIndex; //TODO not always like this! + uniform.flags = ShaderData.getFlags(uniform.dataIndex); + } + } + } +} diff --git a/src/sporemodder/file/shaders/MaterialShader.java b/src/sporemodder/file/shaders/MaterialShader.java new file mode 100644 index 0000000..ba62e28 --- /dev/null +++ b/src/sporemodder/file/shaders/MaterialShader.java @@ -0,0 +1,80 @@ +package sporemodder.file.shaders; + +import emord.filestructures.StreamReader; +import emord.filestructures.StreamWriter; + +import java.io.IOException; + +import emord.filestructures.Stream.StringEncoding; +import sporemodder.HashManager; + +public class MaterialShader { + + public static final int FLAG_NAME = 0x10; + + public static final int VERTEX_SHADER = 0xFFFE0000; + public static final int PIXEL_SHADER = 0xFFFF0000; + + public int id; + public int var_28; + public int var_24; + public int var_2C; + public int vertexShaderVersion = VERTEX_SHADER | 0x00000300; + public int pixelShaderVersion = PIXEL_SHADER | 0x00000300; + public int flags = 4; + + public String name; + + public void read(StreamReader in, int version) throws IOException { + id = in.readInt(); + + var_28 = in.readInt(); + var_24 = in.readInt(); + var_2C = in.readInt(); + vertexShaderVersion = in.readInt(); + pixelShaderVersion = in.readInt(); + flags = in.readInt(); + + if ((flags & FLAG_NAME) != 0) { + name = in.readString(StringEncoding.ASCII, in.readInt()); + } + } + + public void write(StreamWriter out, int version) throws IOException { + out.writeInt(id); + out.writeInt(var_28); + out.writeInt(var_24); + out.writeInt(var_2C); + out.writeInt(vertexShaderVersion); + out.writeInt(pixelShaderVersion); + out.writeInt(flags); + if ((flags & FLAG_NAME) != 0) { + out.writeInt(name.length()); + out.writeString(name, StringEncoding.ASCII); + } + } + + /** + * Process the file name to get the shader ID and name. The given name must not have any extensions. The default format is + * ShaderID(ShaderName); if the name does not have parenthesis the shader ID is calculated from the shader name. + * @param name + */ + public void processName(String fileName) { + int indexOf = fileName.indexOf("("); + if (indexOf != -1) { + id = HashManager.get().int32(fileName.substring(0, indexOf)); + name = fileName.substring(indexOf + 1, fileName.length() - 1); + flags |= FLAG_NAME; + } + else if (fileName.startsWith("0x")) { + id = HashManager.get().int32(name); + name = fileName; + flags |= FLAG_NAME; + } + else { + name = fileName; + id = HashManager.get().getFileHash(name); + flags |= FLAG_NAME; + } + } +} diff --git a/src/sporemodder/file/shaders/MaterialStateLink.java b/src/sporemodder/file/shaders/MaterialStateLink.java new file mode 100644 index 0000000..cefb9ce --- /dev/null +++ b/src/sporemodder/file/shaders/MaterialStateLink.java @@ -0,0 +1,423 @@ +package sporemodder.file.shaders; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import sporemodder.HashManager; +import sporemodder.file.argscript.ArgScriptArguments; +import sporemodder.file.argscript.ArgScriptBlock; +import sporemodder.file.argscript.ArgScriptLine; +import sporemodder.file.argscript.ArgScriptParser; +import sporemodder.file.argscript.ArgScriptStream; +import sporemodder.file.argscript.ArgScriptWriter; +import sporemodder.file.effects.ResourceID; +import sporemodder.file.rw4.Direct3DEnums; +import sporemodder.file.rw4.Direct3DEnums.D3DDECLMETHOD; +import sporemodder.file.rw4.Direct3DEnums.D3DDECLTYPE; +import sporemodder.file.rw4.Direct3DEnums.D3DDECLUSAGE; +import sporemodder.file.rw4.Direct3DEnums.D3DPRIMITIVETYPE; +import sporemodder.file.rw4.Direct3DEnums.D3DRenderStateType; +import sporemodder.file.rw4.Direct3DEnums.D3DSamplerStateType; +import sporemodder.file.rw4.Direct3DEnums.D3DTextureStageStateType; +import sporemodder.file.rw4.Direct3DEnums.RWDECLUSAGE; +import sporemodder.file.rw4.MaterialStateCompiler; +import sporemodder.file.rw4.MaterialStateCompiler.ShaderDataEntry; +import sporemodder.file.rw4.MaterialStateCompiler.TextureSlot; +import sporemodder.file.rw4.RWObject; +import sporemodder.file.rw4.RWRaster; +import sporemodder.file.rw4.RWVertexDescription; +import sporemodder.file.rw4.RWVertexElement; +import sporemodder.file.rw4.RenderWare; +import sporemodder.util.ColorRGB; +import sporemodder.util.ColorRGBA; + +public class MaterialStateLink { + public RenderWare renderWare; + + public int materialID; + public final List states = new ArrayList<>(); + public final List textures = new ArrayList<>(); + + // Only on Darkspore (version == 1) + public final List textureUnks1 = new ArrayList<>(); + public final List textureUnks2 = new ArrayList<>(); + + private final Map nameToState = new HashMap<>(); + private final Map stateToName = new HashMap<>(); + + private static > T parseEnum(Class enumeration, ArgScriptStream stream, ArgScriptLine line, ArgScriptArguments args, int index) { + try { + return Enum.valueOf(enumeration, args.get(index)); + } catch (IllegalArgumentException e) { + stream.addError(line.createErrorForArgument(args.get(index) + " is not a member of the " + enumeration.getSimpleName() + " enum.", index)); + return null; + } + } + + private static int parseStateValue(Class typeClass, ArgScriptStream stream, ArgScriptLine line, ArgScriptArguments args, int index) { + if (typeClass == int.class) return Optional.of(stream.parseInt(args, index)).orElse(0); + else if (typeClass == float.class) return Float.floatToRawIntBits(Optional.of(stream.parseFloat(args, index)).orElse(0.0f)); + else if (typeClass == ColorRGBA.class) { + ColorRGBA color = new ColorRGBA(); + stream.parseColorRGBA(args, index, color); + return ((int)(color.getA()*255) << 24) | ((int)(color.getR()*255) << 16) | ((int)(color.getG()*255) << 8) | ((int)(color.getB()*255) << 0); + } + else { + Integer value = Direct3DEnums.getStateValue(args.get(index), typeClass); + if (value == null) { + stream.addError(line.createErrorForArgument(args.get(index) + " is not a member of the " + typeClass.getSimpleName() + " enum.", index)); + return -1; + } else { + return value; + } + } + } + + public void reset() { + materialID = 0; + states.clear(); + textures.clear(); + textureUnks1.clear(); + textureUnks2.clear(); + nameToState.clear(); + stateToName.clear(); + } + + public void setName(MaterialStateCompiler state, String name) { + stateToName.put(state, name); + nameToState.put(name, state); + } + + public void toArgScript(ArgScriptWriter writer) throws IOException { + for (MaterialStateCompiler state : states) { + if (!state.isDecompiled()) state.decompile(); + state.toArgScript(stateToName.get(state), writer); + writer.blankLine(); + } + writer.command("material").arguments(HashManager.get().getFileName(materialID)); + if (!states.isEmpty()) { + writer.option("states"); + for (MaterialStateCompiler state : states) { + writer.arguments(stateToName.get(state)); + } + } + if (!textures.isEmpty()) { + writer.option("textures").arguments(textures); + } + if (!textureUnks1.isEmpty()) { + writer.option("unk1").arguments(textureUnks1); + } + if (!textureUnks2.isEmpty()) { + writer.option("unk2").arguments(textureUnks2); + } + } + + public ArgScriptStream generateStream(boolean singleState) { + + ArgScriptStream stream = new ArgScriptStream<>(); + stream.setData(this); + stream.addDefaultParsers(); + + stream.addParser(MaterialStateCompiler.KEYWORD, new ArgScriptBlock() { + MaterialStateCompiler currentState; + + @Override public void parse(ArgScriptLine line) { + if (singleState && !states.isEmpty()) { + stream.addError(line.createError("Already declared one compiled state.")); + } + else { + currentState = new MaterialStateCompiler(renderWare); + + final ArgScriptArguments args = new ArgScriptArguments(); + if (line.getArguments(args, 1)) { + nameToState.put(args.get(0), currentState); + } + } + + stream.startBlock(this); + } + + @Override public void setData(ArgScriptStream stream, MaterialStateLink data) { + super.setData(stream, data); + + final ArgScriptArguments args = new ArgScriptArguments(); + + addParser("shaderID", ArgScriptParser.create((parser, line) -> { + Number value; + if (line.getArguments(args, 1) && ((value = stream.parseFileID(args, 0)) != null)) currentState.rendererID = value.intValue(); + })); + + addParser("primitiveType", ArgScriptParser.create((parser, line) -> { + if (line.getArguments(args, 1)) { + currentState.primitiveType = parseEnum(D3DPRIMITIVETYPE.class, stream, line, args, 0); + } + })); + + addParser("materialColor", ArgScriptParser.create((parser, line) -> { + if (line.getArguments(args, 1)) { + currentState.materialColor = new ColorRGBA(); + stream.parseColorRGBA(args, 0, currentState.materialColor); + } + })); + + addParser("ambientColor", ArgScriptParser.create((parser, line) -> { + if (line.getArguments(args, 1)) { + currentState.ambientColor = new ColorRGB(); + stream.parseColorRGB(args, 0, currentState.ambientColor); + } + })); + + addParser("shaderData", ArgScriptParser.create((parser, line) -> { + if (line.getArguments(args, 1, Integer.MAX_VALUE)) { + ShaderDataEntry entry = new ShaderDataEntry(); + + try { + entry.index = (short) ShaderData.getIndex(args.get(0)); + } catch (Exception e) { + stream.addError(line.createErrorForArgument(e.getMessage(), 0)); + } + + entry.data = new int[args.size() - 1]; + + for (int i = 0; i < entry.data.length; ++i) { + Integer value = stream.parseInt(args, i + 1); + if (value != null) { + entry.data[i] = value; + } + } + + currentState.shaderData.add(entry); + } + })); + + + addParser("unkData3", ArgScriptParser.create((parser, line) -> { + if (line.getArguments(args, 17)) { + currentState.unkData3 = new boolean[17]; + for (int i = 0; i < 17; ++i) { + Boolean value = stream.parseBoolean(args, i); + if (value != null) currentState.unkData3[i] = value; + } + } + })); + + addParser("renderState", ArgScriptParser.create((parser, line) -> { + if (line.getArguments(args, 2)) { + D3DRenderStateType state = parseEnum(D3DRenderStateType.class, stream, line, args, 0); + + if (!currentState.renderStates.containsKey(0)) { + currentState.renderStates.put(0, new LinkedHashMap()); + } + currentState.renderStates.get(0).put(state, parseStateValue(state.typeClass, stream, line, args, 1)); + } + })); + + addParser("statesGroup", new ArgScriptBlock() { + private Integer group; + + @Override public void parse(ArgScriptLine line) { + if (line.getArguments(args, 1)) { + group = stream.parseInt(args, 0); + if (group != null) { + currentState.renderStates.put(group, new LinkedHashMap()); + } + } + stream.startBlock(this); + } + + @Override public void setData(ArgScriptStream stream, MaterialStateLink data) { + super.setData(stream, data); + + addParser("renderState", ArgScriptParser.create((parser, line) -> { + if (group != null && line.getArguments(args, 2)) { + D3DRenderStateType state = parseEnum(D3DRenderStateType.class, stream, line, args, 0); + + currentState.renderStates.get(group).put(state, parseStateValue(state.typeClass, stream, line, args, 1)); + } + })); + } + }); + + addParser("vertexDescription", new ArgScriptBlock() { + @Override public void parse(ArgScriptLine line) { + if (currentState.vertexDescription != null) { + stream.addError(line.createError("Can only have one vertex description per state.")); + } + currentState.vertexDescription = new RWVertexDescription(renderWare); + + line.getArguments(args, 0); + + Integer value = null; + if (line.getOptionArguments(args, "field_0", 1) && (value = stream.parseInt(args, 0)) != null) { + currentState.vertexDescription.field_0 = value; + } + if (line.getOptionArguments(args, "field_4", 1) && (value = stream.parseInt(args, 0)) != null) { + currentState.vertexDescription.field_4 = value; + } + if (line.getOptionArguments(args, "field_0E", 1) && (value = stream.parseUByte(args, 0)) != null) { + currentState.vertexDescription.field_0E = value.byteValue(); + } + if (line.getOptionArguments(args, "field_10", 1) && (value = stream.parseInt(args, 0)) != null) { + currentState.vertexDescription.field_10 = value; + } + if (line.getOptionArguments(args, "field_14", 1) && (value = stream.parseInt(args, 0)) != null) { + currentState.vertexDescription.field_14 = value; + } + stream.startBlock(this); + } + + @Override public void setData(ArgScriptStream stream, MaterialStateLink data) { + super.setData(stream, data); + + addParser("element", ArgScriptParser.create((parser, line) -> { + RWVertexElement element = new RWVertexElement(); + currentState.vertexDescription.elements.add(element); + + Number value = null; + + if (line.getArguments(args, 3)) { + element.type = parseEnum(D3DDECLTYPE.class, stream, line, args, 0); + element.usage = parseEnum(D3DDECLUSAGE.class, stream, line, args, 1); + + try { + element.typeCode = RWDECLUSAGE.valueOf(args.get(2)).getId(); + } catch (Exception e) { + value = stream.parseInt(args, 2); + if (value != null) element.typeCode = value.intValue(); + } + } + + if (line.getOptionArguments(args, "usageIndex", 1) && (value = stream.parseByte(args, 0)) != null) { + element.usageIndex = value.byteValue(); + } + if (line.getOptionArguments(args, "stream", 1) && (value = stream.parseInt(args, 0)) != null) { + element.stream = value.intValue(); + } + if (line.getOptionArguments(args, "method", 1)) { + element.method = parseEnum(D3DDECLMETHOD.class, stream, line, args, 2); + } + })); + } + + @Override public void onBlockEnd() { + super.onBlockEnd(); + + int offset = 0; + for (RWVertexElement element : currentState.vertexDescription.elements) { + element.offset = offset; + offset += element.type.size; + } + currentState.vertexDescription.vertexSize = (byte) offset; + } + }); + + addParser("flags1", ArgScriptParser.create((parser, line) -> { + Integer value = null; + if (line.getArguments(args, 1) && (value = stream.parseInt(args, 0)) != null) { + currentState.extraFlags1 = value; + } + })); + + addParser("flags3", ArgScriptParser.create((parser, line) -> { + Integer value = null; + if (line.getArguments(args, 1) && (value = stream.parseInt(args, 0)) != null) { + currentState.extraFlags3 = value; + } + })); + + addParser("textureSlot", new ArgScriptBlock() { + TextureSlot currentSlot; + + @Override public void parse(ArgScriptLine line) { + currentSlot = new TextureSlot(); + + Integer value; + if (line.getArguments(args, 1) && (value = stream.parseInt(args, 0)) != null) { + currentSlot.samplerIndex = value; + } + currentState.textureSlots.add(currentSlot); + + stream.startBlock(this); + } + + @Override public void setData(ArgScriptStream stream, MaterialStateLink data) { + super.setData(stream, data); + + if (renderWare != null) addParser("raster", ArgScriptParser.create((parser, line) -> { + Integer value = null; + if (line.getArguments(args, 1) && (value = stream.parseInt(args, 0)) != null) { + RWObject object = renderWare.get(value); + if (object.getTypeCode() == RWRaster.TYPE_CODE) { + currentSlot.raster = (RWRaster) object; + } else { + stream.addError(line.createErrorForArgument("RWObject at index " + value + " is not a RWRaster.", 0)); + } + } + })); + + addParser("samplerState", ArgScriptParser.create((parser, line) -> { + if (line.getArguments(args, 2)) { + D3DSamplerStateType state = parseEnum(D3DSamplerStateType.class, stream, line, args, 0); + + currentSlot.samplerStates.put(state, parseStateValue(state.typeClass, stream, line, args, 1)); + + currentSlot.samplerStatesMask |= 1 << (state.id - 1); + } + })); + + addParser("stageState", ArgScriptParser.create((parser, line) -> { + if (line.getArguments(args, 2)) { + D3DTextureStageStateType state = parseEnum(D3DTextureStageStateType.class, stream, line, args, 0); + + currentSlot.textureStageStates.put(state, parseStateValue(state.typeClass, stream, line, args, 1)); + + currentSlot.stageStatesMask |= 1 << (state.id - 1); + } + })); + } + }); + } + }); + + if (!singleState) { + stream.addParser("material", ArgScriptParser.create((parser, line) -> { + final ArgScriptArguments args = new ArgScriptArguments(); + if (materialID != 0) { + stream.addError(line.createError("Only one material can be exported per file.")); + return; + } + + Integer value = null; + if (line.getArguments(args, 1) && (value = stream.parseFileID(args, 0)) != null) { + materialID = value; + } + + if (line.getOptionArguments(args, "states", 1, Integer.MAX_VALUE)) { + for (int i = 0; i < args.size(); ++i) { + MaterialStateCompiler state = nameToState.get(args.get(i)); + if (state != null) { + states.add(state); + } else { + stream.addError(line.createErrorForOptionArgument("states", args.get(i) + " is not a defined state.", i+1)); + } + } + } + + if (line.getOptionArguments(args, "textures", 1, Integer.MAX_VALUE)) { + for (int i = 0; i < args.size(); ++i) { + ResourceID id = new ResourceID(); + id.parse(args, 0); + textures.add(id); + } + } + })); + } + + return stream; + } +} diff --git a/src/sporemodder/file/shaders/MaterialStateLinks.java b/src/sporemodder/file/shaders/MaterialStateLinks.java new file mode 100644 index 0000000..659ec7f --- /dev/null +++ b/src/sporemodder/file/shaders/MaterialStateLinks.java @@ -0,0 +1,161 @@ +package sporemodder.file.shaders; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import emord.filestructures.FileStream; +import emord.filestructures.StreamReader; +import emord.filestructures.StreamWriter; +import sporemodder.HashManager; +import sporemodder.MainApp; +import sporemodder.file.argscript.ArgScriptWriter; +import sporemodder.file.effects.ResourceID; +import sporemodder.file.rw4.MaterialStateCompiler; +import sporemodder.file.rw4.RWCompiledState; +import sporemodder.file.rw4.RWHeader.RenderWareType; +import sporemodder.file.rw4.RenderWare; + +public class MaterialStateLinks { + + public final List materials = new ArrayList<>(); + public int version; + + public void read(StreamReader in, List compiledStates) throws IOException { + + version = in.readInt(); + if (version > 1) { + throw new IOException("Unsupported material link version " + version); + } + + int nameID = -1; + int index = 0; + + while ((nameID = in.readInt()) != -1) { + + MaterialStateLink material = new MaterialStateLink(); + materials.add(material); + + material.materialID = nameID; + int numCompiledStates = 0; + + if (version == 0) { + numCompiledStates = in.readByte(); + int textureCount = in.readUByte(); + + for (int i = 0; i < textureCount; i++) { + ResourceID resource = new ResourceID(); + resource.setInstanceID(in.readInt()); + resource.setGroupID(in.readInt()); + material.textures.add(resource); + } + } + else if (version == 1) + { + int textureCount = in.readShort(); + for (int i = 0; i < textureCount; i++) { + ResourceID resource = new ResourceID(); + resource.setInstanceID(in.readInt()); + resource.setGroupID(in.readInt()); + material.textures.add(resource); + + material.textureUnks1.add(in.readUShort()); + material.textureUnks2.add(in.readUShort()); + } + + for (int i = 0; i < 16; i++) { + in.readByte(); + in.readByte(); + } + + for (int i = 0; i < 16; i++) { + byte b = in.readByte(); + if (b != 0) { + numCompiledStates++; + } + } + } + + for (int i = 0; i < numCompiledStates; ++i) { + MaterialStateCompiler state = compiledStates.get(index + i).data; + material.setName(state, "state-" + (i + index)); + material.states.add(state); + } + + index += numCompiledStates; + } + } + + public void write(StreamWriter stream) throws IOException { + stream.writeInt(0); // only this version is supported + for (MaterialStateLink material : materials) { + stream.writeInt(material.materialID); + stream.writeByte(material.states.size()); + stream.writeByte(material.textures.size()); + + for (ResourceID texture : material.textures) { + stream.writeInt(texture.getInstanceID()); + stream.writeInt(texture.getGroupID()); + } + } + stream.writeInt(-1); + } + + public void writeRenderWare(StreamWriter stream) throws IOException { + RenderWare renderWare = new RenderWare(); + renderWare.setType(RenderWareType.SPECIAL); + for (MaterialStateLink material : materials) { + for (MaterialStateCompiler state : material.states) { + RWCompiledState rw = new RWCompiledState(renderWare); + rw.data = state; + rw.data.compile(); + renderWare.add(rw); + } + } + renderWare.write(stream); + } + + public void toArgScript(File outputFolder) throws IOException { + outputFolder.mkdir(); + for (MaterialStateLink material : materials) { + File file = new File(outputFolder, HashManager.get().getFileName(material.materialID) + ".smt_t"); + + ArgScriptWriter writer = new ArgScriptWriter(); + material.toArgScript(writer); + writer.write(file); + } + } + + public static MaterialStateLinks read(File renderWareFile, File linkFile) throws IOException { + try (StreamReader linkStream = new FileStream(linkFile, "r")) { + + List compiledStates = RenderWare.fromFile(renderWareFile).getObjects(RWCompiledState.class); + + MaterialStateLinks links = new MaterialStateLinks(); + links.read(linkStream, compiledStates); + + return links; + } + } + + public void loadFolder(File folder) throws IOException { + for (File file : folder.listFiles()) { + if (file.getName().endsWith(".smt_t")) { + MaterialStateLink link = new MaterialStateLink(); + link.generateStream(false).process(file); + materials.add(link); + } + } + } + + public static void main(String[] args) throws IOException { + MainApp.testInit(); + + File linkFile = new File("E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Materials\\materials_compiled_states_link~\\0x00000003.smt"); + File renderWareFile = new File("E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Materials\\materials_compiled_states~\\0x00000003.rw4"); + File outputFolder = new File("E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Materials\\materials_compiled_states_link~\\0x00000003.smt.unpacked\\"); + + read(renderWareFile, linkFile).toArgScript(outputFolder); + } +} diff --git a/src/sporemodder/file/shaders/ShaderData.java b/src/sporemodder/file/shaders/ShaderData.java new file mode 100644 index 0000000..a88f86d --- /dev/null +++ b/src/sporemodder/file/shaders/ShaderData.java @@ -0,0 +1,250 @@ +package sporemodder.file.shaders; + +import java.util.HashMap; +import java.util.Map; + +import sporemodder.HashManager; + +public class ShaderData { + + private static final Map indexToName = new HashMap<>(); + private static final Map nameToIndex = new HashMap<>(); + + private static final Map flags = new HashMap<>(); + + private static final int FLAG_MODEL_SHADER_DATA = 8; + private static final int FLAG_MODEL_TRANSFORM = 1; + private static final int FLAG_WORLD_TRANSFORM = 0x4000; + private static final int FLAG_MATERIAL_COLOR = 0x10; + private static final int FLAG_AMBIENT_COLOR = 0x20; + private static final int FLAG_LIGHT_DATA = 0x3FC0; + + static + { + flags.put(0x3, FLAG_MODEL_SHADER_DATA); + flags.put(0x4, FLAG_MODEL_SHADER_DATA); + flags.put(0x202, FLAG_MODEL_SHADER_DATA); + flags.put(0x203, FLAG_MODEL_SHADER_DATA); + flags.put(0x204, FLAG_MODEL_SHADER_DATA); + flags.put(0x205, FLAG_MODEL_SHADER_DATA); + flags.put(0x206, FLAG_MODEL_SHADER_DATA); + flags.put(0x20A, FLAG_MODEL_SHADER_DATA); + flags.put(0x20B, FLAG_MODEL_SHADER_DATA); + flags.put(0x20C, FLAG_MODEL_SHADER_DATA); + flags.put(0x20F, FLAG_MODEL_SHADER_DATA); + flags.put(0x210, FLAG_MODEL_SHADER_DATA); + flags.put(0x211, FLAG_MODEL_SHADER_DATA); + flags.put(0x212, FLAG_MODEL_SHADER_DATA); + flags.put(0x213, FLAG_MODEL_SHADER_DATA); + flags.put(0x219, FLAG_MODEL_SHADER_DATA); + flags.put(0x21A, FLAG_MODEL_SHADER_DATA); + flags.put(0x21E, FLAG_MODEL_SHADER_DATA); + flags.put(0x220, FLAG_MODEL_SHADER_DATA); + flags.put(0x223, FLAG_MODEL_SHADER_DATA); + flags.put(0x22E, FLAG_MODEL_SHADER_DATA); + flags.put(0x22F, FLAG_MODEL_SHADER_DATA); + flags.put(0x230, FLAG_MODEL_SHADER_DATA); + flags.put(0x231, FLAG_MODEL_SHADER_DATA); + flags.put(0x233, FLAG_MODEL_SHADER_DATA); + flags.put(0x234, FLAG_MODEL_SHADER_DATA); + flags.put(0x235, FLAG_MODEL_SHADER_DATA); + flags.put(0x236, FLAG_MODEL_SHADER_DATA); + flags.put(0x23C, FLAG_MODEL_SHADER_DATA); + flags.put(0x23E, FLAG_MODEL_SHADER_DATA); + flags.put(0x23D, FLAG_MODEL_SHADER_DATA); + flags.put(0x241, FLAG_MODEL_SHADER_DATA); + flags.put(0x242, FLAG_MODEL_SHADER_DATA); + flags.put(0x248, FLAG_MODEL_SHADER_DATA); + flags.put(0x24A, FLAG_MODEL_SHADER_DATA); + flags.put(0x24B, FLAG_MODEL_SHADER_DATA); + flags.put(0x24C, FLAG_MODEL_SHADER_DATA); + flags.put(0x24D, FLAG_MODEL_SHADER_DATA); + flags.put(0x250, FLAG_MODEL_SHADER_DATA); + flags.put(0x251, FLAG_MODEL_SHADER_DATA); + flags.put(0x252, FLAG_MODEL_SHADER_DATA); + flags.put(0x255, FLAG_MODEL_SHADER_DATA); + flags.put(0x256, FLAG_MODEL_SHADER_DATA); + flags.put(0x301, FLAG_MODEL_SHADER_DATA); + flags.put(0x304, FLAG_MODEL_SHADER_DATA); + flags.put(0x305, FLAG_MODEL_SHADER_DATA); + + flags.put(0x22, FLAG_MATERIAL_COLOR); + flags.put(0x23, FLAG_AMBIENT_COLOR); + + flags.put(0x21b, FLAG_MODEL_TRANSFORM | FLAG_MODEL_SHADER_DATA); + flags.put(0x254, FLAG_MODEL_TRANSFORM | FLAG_MODEL_SHADER_DATA); + flags.put(0x225, FLAG_WORLD_TRANSFORM | FLAG_MODEL_TRANSFORM | FLAG_MODEL_SHADER_DATA); + flags.put(0x006, FLAG_WORLD_TRANSFORM | FLAG_MODEL_TRANSFORM); + flags.put(0x007, FLAG_WORLD_TRANSFORM | FLAG_MODEL_TRANSFORM); + flags.put(0x228, FLAG_WORLD_TRANSFORM | FLAG_MODEL_TRANSFORM); + flags.put(0x229, FLAG_WORLD_TRANSFORM | FLAG_MODEL_TRANSFORM); + flags.put(0x008, FLAG_MODEL_TRANSFORM); + flags.put(0x02A, FLAG_MODEL_TRANSFORM); + flags.put(0x00C, FLAG_WORLD_TRANSFORM); + flags.put(0x00D, FLAG_WORLD_TRANSFORM); + flags.put(0x00E, FLAG_WORLD_TRANSFORM); + flags.put(0x00F, FLAG_WORLD_TRANSFORM); + flags.put(0x010, FLAG_WORLD_TRANSFORM); + flags.put(0x011, FLAG_WORLD_TRANSFORM); + flags.put(0x012, FLAG_WORLD_TRANSFORM); + flags.put(0x01F, FLAG_WORLD_TRANSFORM); + flags.put(0x021, FLAG_WORLD_TRANSFORM); + flags.put(0x222, FLAG_WORLD_TRANSFORM); + + flags.put(0x014, FLAG_LIGHT_DATA); + flags.put(0x019, FLAG_LIGHT_DATA); + flags.put(0x214, FLAG_LIGHT_DATA); + flags.put(0x215, FLAG_LIGHT_DATA); + + flags.put(0x27, 0xffffd); + flags.put(0x28, 0xffffd); + flags.put(0x21f, 0xffffd); + flags.put(0x246, 0xffffd); + + flags.put(0x25, 0x10000 | 0x40000); + flags.put(0x24, 0x20000); + } + + static + { + nameToIndex.put("skinWeights", 0x003); + nameToIndex.put("skinBones", 0x004); + + nameToIndex.put("modelToClip", 0x006); + nameToIndex.put("modelToCamera", 0x007); + nameToIndex.put("modelToWorld", 0x008); + + nameToIndex.put("worldToClip", 0x00C); + nameToIndex.put("cameraToWorld", 0x00D); + nameToIndex.put("worldToCamera", 0x00E); + nameToIndex.put("worldToClipTranspose", 0x00F); + nameToIndex.put("cameraToWorldTranspose", 0x010); + nameToIndex.put("worldToCameraTranspose", 0x011); + nameToIndex.put("cameraToClip", 0x012); + + nameToIndex.put("lightPosModel", 0x014); + + nameToIndex.put("lightDirCamera", 0x019); // float3 + + nameToIndex.put("worldCameraPosition", 0x01F); + + nameToIndex.put("worldCameraDirection", 0x021); + nameToIndex.put("materialColor", 0x022); + nameToIndex.put("ambient", 0x023); + + nameToIndex.put("time", 0x027); + nameToIndex.put("pulse", 0x028); + + nameToIndex.put("worldToModel", 0x02A); + + // 0x201 shaderRenderType + nameToIndex.put("objectTypeColor", 0x202); + nameToIndex.put("frameInfo", 0x203); + nameToIndex.put("screenInfo", 0x204); + nameToIndex.put("mNoiseScale", 0x205); // struct { float4 mFrequencyMag; float mTime; } mNoiseScale; + nameToIndex.put("customParams", 0x206); + + nameToIndex.put("geomToRTT", 0x20A); + nameToIndex.put("geomToRTTViewTrans", 0x20B); + nameToIndex.put("tintParams", 0x20C); // float4 + + nameToIndex.put("region", 0x20F); // float4 + nameToIndex.put("materialParams", 0x210); // if 5, applyMaterialAlpha + nameToIndex.put("uvTweak", 0x211); + nameToIndex.put("editorColors[]", 0x212); // float4 editorColors[2]; + nameToIndex.put("editorColors", 0x213); // struct { float mColor1; float mColor2; float mParams } editorColors; + nameToIndex.put("dirLightsWorld", 0x214); + nameToIndex.put("dirLightsModel", 0x215); + + nameToIndex.put("sunDirAndCelStrength", 0x219); // 0x219 to display buildings and creatures? + nameToIndex.put("shCoeffs", 0x21A); + nameToIndex.put("cameraDistance", 0x21B); // float + + + nameToIndex.put("uvSubRect", 0x21E); + nameToIndex.put("mousePosition", 0x21F); + nameToIndex.put("expandAmount", 0x220); + + nameToIndex.put("cameraParams", 0x222); // cameraParams[1] + nameToIndex.put("shadowMapInfo", 0x223); + // 224 related to per-vertex fogging + nameToIndex.put("foggingCPU", 0x225); + nameToIndex.put("patchLocation", 0x226); + + nameToIndex.put("clipToWorld", 0x228); // float4x4 + nameToIndex.put("clipToCamera", 0x229); // float4x4 + + nameToIndex.put("identityColor", 0x22e); + nameToIndex.put("pcaTexture", 0x22f); + nameToIndex.put("rolloverRegion", 0x230); + nameToIndex.put("renderDepth", 0x231); + + nameToIndex.put("terrainTint", 0x233); + nameToIndex.put("utfWin", 0x234); // struct { float4 mColorAlpha; row_major float4x4 mTransform; float4 mClipRect; } utfWin[16]; + nameToIndex.put("deadTerrainTint", 0x235); + nameToIndex.put("cellStage", 0x236); + // 238 also related with cells + + nameToIndex.put("terrainBrushFilterKernel", 0x23C); // float4 terrainBrushFilterKernel[3]; + nameToIndex.put("terraformValues", 0x23D); + nameToIndex.put("worldToPatch", 0x23E); + + nameToIndex.put("terrainBrushCubeMatRot", 0x241); + nameToIndex.put("terrainSynthParams", 0x242); + + nameToIndex.put("debugPSColor", 0x246); + + nameToIndex.put("gameInfo", 0x248); + + nameToIndex.put("ramp", 0x24A); // struct { float4 envelope; float4 values2; float4 values3; } + nameToIndex.put("sunDir", 0x24B); + nameToIndex.put("tramp", 0x24C); // tramp[8] + // struct { float4 vSunDir; float4 nightLightTint; float4 nightLightColor; float4 duskLightColor; + // float4 dayLightColor; float4 nightShadowColor; float4 duskShadowColor; float4 dayShadowColor; float4 duskDawnStartEnd; } + nameToIndex.put("terrainLighting", 0x24D); + + nameToIndex.put("beachColor", 0x250); + nameToIndex.put("cliffColor", 0x251); + nameToIndex.put("viewTransform", 0x252); + + // 0x254 float4 shCoeffs[4] ? + nameToIndex.put("minWater", 0x255); // minWater[2] + nameToIndex.put("worldCameraNormal", 0x256); + + nameToIndex.put("terrainTransform", 0x301); + + nameToIndex.put("decalState", 0x304); + nameToIndex.put("terrainState", 0x305); + + nameToIndex.put("ModAPIShader", 0x3FF); + + for (Map.Entry entry : nameToIndex.entrySet()) { + indexToName.put(entry.getValue(), entry.getKey()); + } + } + + public static boolean hasName(int index) { + return indexToName.containsKey(index); + } + + public static boolean hasIndex(String name) { + return nameToIndex.containsKey(name); + } + + public static String getName(int index) { + String name = indexToName.get(index); + if (name == null) name = "0x" + Integer.toHexString(index); + return name; + } + + public static int getIndex(String name) { + Integer result = nameToIndex.get(name); + if (result == null) return HashManager.get().int32(name); + else return result; + } + + public static int getFlags(int dataIndex) { + Integer value = flags.get(dataIndex); + return value == null ? 0 : value; + } +} diff --git a/src/sporemodder/file/shaders/ShaderDataUniform.java b/src/sporemodder/file/shaders/ShaderDataUniform.java new file mode 100644 index 0000000..9f7981b --- /dev/null +++ b/src/sporemodder/file/shaders/ShaderDataUniform.java @@ -0,0 +1,30 @@ +package sporemodder.file.shaders; + +import java.io.IOException; + +import emord.filestructures.StreamReader; +import emord.filestructures.StreamWriter; + +public class ShaderDataUniform { + public int dataIndex; // short; + public int field_2; // short; + public int registerSize; // short; register size? + public int register; // short; + public int flags; + + public void readCompiled(StreamReader in) throws IOException { + dataIndex = in.readShort(); + field_2 = in.readShort(); + registerSize = in.readShort(); + register = in.readShort(); + flags = in.readInt(); + } + + public void write(StreamWriter out) throws IOException { + out.writeShort(dataIndex); + out.writeShort(field_2); + out.writeShort(registerSize); + out.writeShort(register); + out.writeInt(flags); + } +} diff --git a/src/sporemodder/file/shaders/ShaderFragment.java b/src/sporemodder/file/shaders/ShaderFragment.java new file mode 100644 index 0000000..0aa6b85 --- /dev/null +++ b/src/sporemodder/file/shaders/ShaderFragment.java @@ -0,0 +1,431 @@ +package sporemodder.file.shaders; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import emord.filestructures.Stream.StringEncoding; +import emord.filestructures.StreamReader; + +public class ShaderFragment { + public static class ShaderVariable { + public String name; + /* 10h */ public short dataIndex; + /* 12h */ public short field_12; + /* 14h */ public short registerSize; + /* 16h */ public short field_16; // always 0 + /* 18h */ public int flags; + + public void read(StreamReader in) throws IOException { + name = in.readString(StringEncoding.ASCII, in.readInt()); + dataIndex = in.readShort(); // dataIndex again + field_12 = in.readShort(); + registerSize = in.readShort(); + field_16 = in.readShort(); + flags = in.readInt(); + } + + public void writeHLSL(BufferedWriter out, int startRegister) throws IOException { + out.write("extern uniform " + name + " : register(c" + startRegister + ");"); + /* Only for testing */ + out.write(" // 0x" + Integer.toHexString(dataIndex) + " 0x" + Integer.toHexString(field_12) + " 0x" + Integer.toHexString(flags)); + } + } + + public static final int FLAG_DEFINED = 1; + + // 1 << RWDECLUSAGE + public static final int VS_INPUT_NORMAL = 0x4; + public static final int VS_INPUT_COLOR = 0x8; + public static final int VS_INPUT_COLOR1 = 0x20; + public static final int VS_INPUT_INDICES = 0x4000; + public static final int VS_INPUT_WEIGHTS = 0x8000; + public static final int VS_INPUT_POINTSIZE = 0x10000; + public static final int VS_INPUT_POSITION2 = 0x20000; + public static final int VS_INPUT_NORMAL2 = 0x40000; + public static final int VS_INPUT_TANGENT = 0x80000; + public static final int VS_INPUT_BINORMAL = 0x100000; + public static final int VS_INPUT_FOG = 0x200000; + public static final int VS_INPUT_INDICES2 = 0x400000; + public static final int VS_INPUT_WEIGHTS2 = 0x800000; + public static final int VS_INPUT_SHL = 6; + + public static final int VS_OUTPUT_COLOR = 0x8; + public static final int VS_OUTPUT_COLOR1 = 0x20; + public static final int VS_OUTPUT_FOG = 0x200000; + public static final int VS_OUTPUT_POINTSIZE = 0x10000; + + // ??? + public static final int PS_INPUT_POSITION = 0x2; + public static final int PS_INPUT_NORMAL = 0x4; + public static final int PS_INPUT_TANGENT = 0x8; + public static final int PS_INPUT_BINORMAL = 0x10; + public static final int PS_INPUT_COLOR = 0x20; + public static final int PS_INPUT_COLOR1 = 0x140; + public static final int PS_INPUT_INDICES = 0x80; + + public static final int PS_OUTPUT_COLOR = 0x2; + public static final int PS_OUTPUT_COLOR1 = 0x4; + public static final int PS_OUTPUT_COLOR2 = 0x8; + public static final int PS_OUTPUT_COLOR3 = 0x10; + public static final int PS_OUTPUT_DEPTH = 0x20; + + public String mainCode; + public String declareCode; + public final List variables = new ArrayList<>(); + + public int input; + public int output; + public byte numRegisters; + public byte numOutputTexcoords; // only for vertex shader + public byte type; // only for vertex shader, 4, sometimes 3? + public int flags; // -18h + + public String shaderName; + + public void read(StreamReader in) throws IOException { + + input = in.readInt(); + output = in.readInt(); + numOutputTexcoords = in.readByte(); + type = in.readByte(); + numRegisters = in.readByte(); + flags = in.readInt(); + + mainCode = in.readString(StringEncoding.ASCII, in.readInt()); + declareCode = in.readString(StringEncoding.ASCII, in.readInt()); + + int variableCount = in.readInt(); + + for (int i = 0; i < variableCount; i++) { + ShaderVariable variable = new ShaderVariable(); + variable.read(in); + variables.add(variable); + } + + if ((flags & 0x2) != 0) { + shaderName = in.readString(StringEncoding.ASCII, in.readInt()); + } + } + + public void writeHLSL(BufferedWriter out) throws IOException { + + if (type == 0) { +// out.write("// input: 0x" + Integer.toHexString(input)); out.newLine(); +// out.write("// output: 0x" + Integer.toHexString(output)); out.newLine(); +// out.write("// numRegisters: 0x" + Integer.toHexString(numRegisters)); out.newLine(); +// out.write("// numOutputTexcoords: 0x" + Integer.toHexString(numOutputTexcoords)); out.newLine(); + //writePSInputStruct(out); //TODO + out.newLine(); + writePSOutputStruct(out, "cFragCurrent"); + out.newLine(); + writePSOutputStruct(out, "cFragOut"); + out.newLine(); + } else { + writeVSInputStruct(out); + out.newLine(); + writeVSOutputStruct(out); + out.newLine(); + } + + int startRegister = 0; + for (ShaderVariable var : variables) { + var.writeHLSL(out, startRegister); + out.newLine(); + + startRegister += var.registerSize; + } + + out.newLine(); + + if (declareCode != null && declareCode.length() > 0) { + out.write(declareCode); + out.newLine(); + out.newLine(); + } + + if (type == 0) { + out.write("cFragOut main( cFragIn In )"); + out.newLine(); + out.write("{"); + out.newLine(); + out.write("cFragCurrent Current;"); + out.newLine(); + out.write("cFragOut Out;"); + out.newLine(); + out.newLine(); + //writePSInput(out); + } else { + out.write("cVertOut main( cVertIn In )"); + out.newLine(); + out.write("{"); + out.newLine(); + out.write("cVertCurrent Current;"); + out.newLine(); + out.write("cVertOut Out;"); + out.newLine(); + out.newLine(); + writeVSInput(out); + } + + out.newLine(); + + out.write(mainCode); + + out.newLine(); + if (type == 0) { + writePSOutput(out); + } else { + writeVSOutput(out); + } + out.write("return Out;"); + out.newLine(); + out.write("}"); + out.newLine(); + } + + private void writeVSInputStruct(BufferedWriter out) throws IOException { + out.write("struct cVertCurrent"); + out.newLine(); + out.write("{"); + out.newLine(); + out.write("float4 position;"); + out.newLine(); + if ((input & VS_INPUT_NORMAL) != 0) { + out.write("float4 normal;"); + out.newLine(); + } + if ((input & (10|VS_INPUT_COLOR)) != 0) { + out.write("float4 color;"); + out.newLine(); + } + for (int i = 0; i < 8; ++i) { + if ((input & (1 << (VS_INPUT_SHL+i))) != 0) { + out.write("float2 texcoord" + i + ";"); + out.newLine(); + } + } + if ((input & VS_INPUT_INDICES) != 0) { + out.write("int4 indices;"); + out.newLine(); + } + if ((input & VS_INPUT_WEIGHTS) != 0) { + out.write("float4 weights;"); + out.newLine(); + } + if ((input & VS_INPUT_POSITION2) != 0) { + out.write("float4 position2;"); + out.newLine(); + } + if ((input & VS_INPUT_NORMAL2) != 0) { + out.write("float4 normal2;"); + out.newLine(); + } + if ((input & VS_INPUT_INDICES2) != 0) { + out.write("int4 indices2;"); + out.newLine(); + } + if ((input & VS_INPUT_WEIGHTS2) != 0) { + out.write("float4 weights2;"); + out.newLine(); + } + if ((input & VS_INPUT_TANGENT) != 0) { + out.write("float4 tangent;"); + out.newLine(); + } + if ((input & VS_INPUT_BINORMAL) != 0) { + out.write("float4 binormal;"); + out.newLine(); + } + if ((input & VS_INPUT_COLOR1) != 0) { + out.write("float4 color1;"); + out.newLine(); + } + if ((input & VS_INPUT_FOG) != 0) { + out.write("float fog;"); + out.newLine(); + } + if ((input & VS_INPUT_POINTSIZE) != 0) { + out.write("float pointSize;"); + out.newLine(); + } + out.write("};"); + out.newLine(); + } + + private void writeVSInput(BufferedWriter out) throws IOException { + out.write("Current.position = In.position;"); + out.newLine(); + if ((input & VS_INPUT_NORMAL) != 0) { + out.write("Current.normal = In.normal;"); + out.newLine(); + } + if ((input & (10|VS_INPUT_COLOR)) != 0) { + out.write("Current.color = In.color;"); + out.newLine(); + } + for (int i = 0; i < 8; ++i) { + if ((input & (1 << (VS_INPUT_SHL+i))) != 0) { + out.write("Current.texcoord" + i + " = In.texcoord" + i + ";"); + out.newLine(); + } + } + if ((input & VS_INPUT_INDICES) != 0) { + out.write("Current.indices = In.indices;"); + out.newLine(); + } + if ((input & VS_INPUT_WEIGHTS) != 0) { + out.write("Current.weights = In.weights;"); + out.newLine(); + } + if ((input & VS_INPUT_POSITION2) != 0) { + out.write("Current.position2 = In.position2;"); + out.newLine(); + } + if ((input & VS_INPUT_NORMAL2) != 0) { + out.write("Current.normal2 = In.normal2;"); + out.newLine(); + } + if ((input & VS_INPUT_INDICES2) != 0) { + out.write("Current.indices2 = In.indices2;"); + out.newLine(); + } + if ((input & VS_INPUT_WEIGHTS2) != 0) { + out.write("Current.weights2 = In.weights2;"); + out.newLine(); + } + if ((input & VS_INPUT_TANGENT) != 0) { + out.write("Current.tangent = In.tangent;"); + out.newLine(); + } + if ((input & VS_INPUT_BINORMAL) != 0) { + out.write("Current.binormal = In.binormal;"); + out.newLine(); + } + if ((input & VS_INPUT_COLOR1) != 0) { + out.write("Current.color1 = In.color1;"); + out.newLine(); + } + if ((input & VS_INPUT_FOG) != 0) { + out.write("Current.fog = In.fog;"); + out.newLine(); + } + if ((input & VS_INPUT_POINTSIZE) != 0) { + out.write("Current.pointSize = In.pointSize;"); + out.newLine(); + } + } + + private void writeVSOutputStruct(BufferedWriter out) throws IOException { + out.write("struct cVertOut"); + out.newLine(); + out.write("{"); + out.newLine(); + out.write("float4 position : POSITION;"); + out.newLine(); + if ((output & (10|VS_OUTPUT_COLOR)) != 0) { + out.write("float4 diffuse : COLOR0;"); + out.newLine(); + } + for (int i = 0; i < numOutputTexcoords; ++i) { + out.write("float2 texcoord" + i + " : TEXCOORD" + i + ";"); + out.newLine(); + } + if ((output & VS_OUTPUT_COLOR1) != 0) { + out.write("float4 color1 : COLOR1;"); + out.newLine(); + } + if ((output & VS_OUTPUT_FOG) != 0) { + out.write("float fog : FOG;"); + out.newLine(); + } + if ((output & VS_OUTPUT_POINTSIZE) != 0) { + out.write("float pointSize : PSIZE;"); + out.newLine(); + } + out.write("};"); + out.newLine(); + } + + private void writeVSOutput(BufferedWriter out) throws IOException { + out.write("Out.position = Current.position;"); + out.newLine(); + if ((output & (10|VS_OUTPUT_COLOR)) != 0) { + out.write("Out.diffuse = Current.color;"); + out.newLine(); + } + for (int i = 0; i < numOutputTexcoords; ++i) { + out.write("Out.texcoord" + i + " = Current.texcoord" + i + ";"); + out.newLine(); + } + if ((output & VS_OUTPUT_COLOR1) != 0) { + out.write("Out.color1 = Current.color1;"); + out.newLine(); + } + if ((output & VS_OUTPUT_FOG) != 0) { + out.write("Out.fog = Current.fog;"); + out.newLine(); + } + if ((output & VS_OUTPUT_POINTSIZE) != 0) { + out.write("Out.pointSize = Current.pointSize;"); + out.newLine(); + } + } + + private void writePSOutputStruct(BufferedWriter out, String name) throws IOException { + out.write("struct " + name); + out.newLine(); + out.write("{"); + out.newLine(); + if ((output & PS_OUTPUT_COLOR) != 0) { + out.write("float4 color : COLOR0;"); + out.newLine(); + } + if ((output & PS_OUTPUT_COLOR1) != 0) { + out.write("float4 color1 : COLOR1;"); + out.newLine(); + } + if ((output & PS_OUTPUT_COLOR2) != 0) { + out.write("float4 color2 : COLOR2;"); + out.newLine(); + } + if ((output & PS_OUTPUT_COLOR3) != 0) { + out.write("float4 color3 : COLOR3;"); + out.newLine(); + } + if ((output & PS_OUTPUT_DEPTH) != 0) { + out.write("float depth : DEPTH;"); + out.newLine(); + } + out.write("};"); + out.newLine(); + } + + private void writePSOutput(BufferedWriter out) throws IOException { + if ((output & PS_OUTPUT_COLOR) != 0) { + out.write("Out.color = Current.color;"); + out.newLine(); + } + if ((output & PS_OUTPUT_COLOR1) != 0) { + out.write("Out.color1 = Current.color1;"); + out.newLine(); + } + if ((output & PS_OUTPUT_COLOR2) != 0) { + out.write("Out.color2 = Current.color2;"); + out.newLine(); + } + if ((output & PS_OUTPUT_COLOR3) != 0) { + out.write("Out.color3 = Current.color3;"); + out.newLine(); + } + if ((output & PS_OUTPUT_DEPTH) != 0) { + out.write("Out.depth = Current.depth;"); + out.newLine(); + } + } + + public String getName() { + return shaderName; + } +} diff --git a/src/sporemodder/file/shaders/ShaderFragmentSelector.java b/src/sporemodder/file/shaders/ShaderFragmentSelector.java new file mode 100644 index 0000000..5b03dc3 --- /dev/null +++ b/src/sporemodder/file/shaders/ShaderFragmentSelector.java @@ -0,0 +1,84 @@ +package sporemodder.file.shaders; + +import java.io.IOException; + +import emord.filestructures.StreamReader; +import emord.filestructures.StreamWriter; + +public class ShaderFragmentSelector { + // Writes the byte if shaderData[field_2] != nullptr + public static final int CHECK_DATA = 1; + + // Writes the byte if objectTypeColor != field_2 + public static final int CHECK_OBJECT_TYPE_COLOR = 2; + + // Writes the byte + shaderData[field_2] if shaderData[field_2] != nullptr + public static final int CHECK_ADD_DATA = 4; + + // Writes the byte if shaderData[field_2] != nullptr and shaderData[field_4] != nullptr + public static final int CHECK_DATA_2 = 5; + + // Writes the byte if shaderData[field_2] != nullptr and shaderData[field_4] != nullptr and shaderData[field_6] != nullptr + public static final int CHECK_DATA_3 = 6; + + // Writes the byte if shaderData[field_2] != nullptr and *(byte*)shaderData[field_2] == field_2 + public static final int CHECK_DATA_EQUAL = 7; + + // Writes the byte + shaderData[field_2] if shaderData[field_2] != nullptr and shaderData[field_4] != nullptr + public static final int CHECK_ADD_DATA_2 = 9; + + // Writes the byte if samplers[field_2] != nullptr + public static final int CHECK_SAMPLER = 10; + + public int fragmentIndex; + public int checkType; + public int field_2; + public int field_4; + public int field_6; + public int vertexUsageFlags; + // If any of these flags are missing, does not apply fragment + public int requiredFlags; + // If the current flags aren't exactly these, does not apply fragment + public int exactFlags; + public int flags; + public int field_18 = -1; // compared with shader data 23A, always if it's not -1 + + public void read(StreamReader in, int version) throws IOException { + checkType = in.readByte(); + + if (version <= 6) { + in.readByte(); + } + + field_2 = in.readShort(); + field_4 = in.readShort(); + field_6 = in.readShort(); + + if (version <= 6) { + in.readShort(); + in.readShort(); + in.readShort(); + } + + vertexUsageFlags = in.readInt(); + requiredFlags = in.readInt(); + exactFlags = in.readInt(); + flags = in.readInt(); + field_18 = in.readByte(); + + fragmentIndex = in.readUByte(); + } + + public void write(StreamWriter stream) throws IOException { + stream.writeByte(checkType); + stream.writeShort(field_2); + stream.writeShort(field_4); + stream.writeShort(field_6); + stream.writeInt(vertexUsageFlags); + stream.writeInt(requiredFlags); + stream.writeInt(exactFlags); + stream.writeInt(flags); + stream.writeByte(field_18); + stream.writeByte(fragmentIndex); + } +} diff --git a/src/sporemodder/file/shaders/ShaderFragments.java b/src/sporemodder/file/shaders/ShaderFragments.java new file mode 100644 index 0000000..3207b89 --- /dev/null +++ b/src/sporemodder/file/shaders/ShaderFragments.java @@ -0,0 +1,82 @@ +package sporemodder.file.shaders; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import emord.filestructures.FileStream; +import emord.filestructures.StreamReader; + +public class ShaderFragments { +private static final int SHADER_COUNT = 255; // 255 ? + + public final List shaders = new ArrayList<>(); + public int version = 1; + + public ShaderFragment getFragment(int index) { + return shaders.get(index - 1); + } + + public void read(StreamReader in) throws IOException { + version = in.readInt(); + + for (int i = 0; i < SHADER_COUNT; i++) { + + ShaderFragment shader = new ShaderFragment(); + shader.read(in); + if ((shader.flags & ShaderFragment.FLAG_DEFINED) != 0) { + shaders.add(shader); + } + } + } + + public void writeHLSL(File outputFolder) throws IOException { + outputFolder.mkdir(); + for (ShaderFragment shader : shaders) { + if (shader.getName() == null || shader.getName().isEmpty()) return; + File file = new File(outputFolder, shader.getName() + ".hlsl"); + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + shader.writeHLSL(writer); + } + } + } + + public static ShaderFragments readShaderFragments(File file) throws IOException { + try (FileStream stream = new FileStream(file, "r")) { + ShaderFragments shaders = new ShaderFragments(); + shaders.read(stream); + return shaders; + } + } + + public static void main(String[] args) throws IOException { + File file = new File("E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Materials\\materials_uncompiled_shader_fragments~\\0x00000003.smt"); + File output = new File("E:\\Eric\\Eclipse Projects\\SporeModder FX\\Projects\\Materials\\materials_uncompiled_shader_fragments~\\0x00000003.smt.unpacked"); + + try (FileStream stream = new FileStream(file, "r")) { + ShaderFragments shaders = new ShaderFragments(); + shaders.read(stream); +// shaders.writeHLSL(output); + +// for (UncompiledShader shader : shaders.shaders) { +// if (shader.flags != 3 && shader.shaderName != null) { +// System.out.println(shader.shaderName + " 0x" + Integer.toHexString(shader.flags)); +// } +// } + + for (int i = 0; i < shaders.shaders.size(); ++i) { + ShaderFragment shader = shaders.shaders.get(i); + if (shader.shaderName != null && (shader.flags & 1) != 0) { + System.out.println((i+1) + ":\t" + shader.shaderName); + } + else { + break; + } + } + } + } +} diff --git a/src/sporemodder/file/shaders/ShaderSelector.java b/src/sporemodder/file/shaders/ShaderSelector.java new file mode 100644 index 0000000..4970e38 --- /dev/null +++ b/src/sporemodder/file/shaders/ShaderSelector.java @@ -0,0 +1,66 @@ +package sporemodder.file.shaders; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import emord.filestructures.StreamReader; +import emord.filestructures.StreamWriter; + +public class ShaderSelector extends MaterialShader { + + public static class ShaderSelectorEntry { + public final List vertexShaderSelectors = new ArrayList<>(); + public final List pixelShaderSelectors = new ArrayList<>(); + } + + public final Map entries = new TreeMap<>(); + + @Override public void read(StreamReader in, int version) throws IOException { + super.read(in, version); + + int index = 0; + + while ((index = in.readUByte()) != 0xFF) { + + ShaderSelectorEntry entry = new ShaderSelectorEntry(); + entries.put(index, entry); + + int count = in.readInt(); + for (int i = 0; i < count; i++) { + ShaderFragmentSelector filter = new ShaderFragmentSelector(); + filter.read(in, version); + entry.vertexShaderSelectors.add(filter); + } + + count = in.readInt(); + for (int i = 0; i < count; i++) { + ShaderFragmentSelector filter = new ShaderFragmentSelector(); + filter.read(in, version); + entry.pixelShaderSelectors.add(filter); + } + } + } + + @Override public void write(StreamWriter stream, int version) throws IOException { + super.write(stream, version); + + for (Map.Entry entry : entries.entrySet()) { + stream.writeUByte(entry.getKey()); + + stream.writeInt(entry.getValue().vertexShaderSelectors.size()); + for (ShaderFragmentSelector object : entry.getValue().vertexShaderSelectors) { + object.write(stream); + } + + stream.writeInt(entry.getValue().pixelShaderSelectors.size()); + for (ShaderFragmentSelector object : entry.getValue().pixelShaderSelectors) { + object.write(stream); + } + } + + stream.writeByte(-1); + } +} diff --git a/src/sporemodder/file/shaders/SmtConverter.java b/src/sporemodder/file/shaders/SmtConverter.java new file mode 100644 index 0000000..7d75c9e --- /dev/null +++ b/src/sporemodder/file/shaders/SmtConverter.java @@ -0,0 +1,129 @@ +package sporemodder.file.shaders; + +import java.io.File; + +import emord.filestructures.MemoryStream; +import emord.filestructures.StreamReader; +import emord.filestructures.StreamWriter; +import javafx.scene.control.ContextMenu; +import sporemodder.HashManager; +import sporemodder.file.Converter; +import sporemodder.file.ResourceKey; +import sporemodder.file.dbpf.DBPFItem; +import sporemodder.file.dbpf.DBPFPackingTask; +import sporemodder.file.rw4.RenderWareConverter; +import sporemodder.util.ProjectItem; + +public class SmtConverter implements Converter { + + public static final int GROUP_STATES_LINK = 0x40212000; + public static final int GROUP_COMPILED_STATES = 0x40212001; + public static final int GROUP_UNCOMPILED = 0x40212002; + public static final int GROUP_COMPILED = 0x40212004; + private static final int TYPE_ID = 0x0469A3F7; + private static String EXTENSION = null; + + private void checkExtensions() { + if (EXTENSION == null) { + EXTENSION = HashManager.get().getTypeName(TYPE_ID); + } + } + + @Override + public boolean decode(StreamReader stream, File outputFolder, ResourceKey key) throws Exception { + // TODO Auto-generated method stub + return false; + } + + @Override public boolean encode(File input, StreamWriter output) throws Exception { + // TODO Auto-generated method stub + return false; + } + + @Override public boolean encode(File input, DBPFPackingTask packer, int groupID) throws Exception { + if (groupID == GROUP_COMPILED) { + String[] splits = input.getName().split("\\.", 2); + + CompiledShaders shaders = new CompiledShaders(); + shaders.name = splits[0]; + shaders.pack(input); + + try (MemoryStream stream = new MemoryStream()) { + shaders.write(stream); + + DBPFItem item = packer.getTemporaryItem(); + item.name.setInstanceID(HashManager.get().getFileHash(splits[0])); + item.name.setGroupID(groupID); + item.name.setTypeID(TYPE_ID); + packer.writeFileData(item, stream.getRawData(), (int) stream.length()); + packer.addFile(item); + } + + return true; + } + else if (groupID == GROUP_STATES_LINK) { + String[] splits = input.getName().split("\\.", 2); + + MaterialStateLinks materials = new MaterialStateLinks(); + materials.loadFolder(input); + + try (MemoryStream rwStream = new MemoryStream(); + MemoryStream linkStream = new MemoryStream()) { + + materials.write(linkStream); + materials.writeRenderWare(rwStream); + + DBPFItem item = packer.getTemporaryItem(); + + item.name.setInstanceID(HashManager.get().getFileHash(splits[0])); + item.name.setGroupID(GROUP_COMPILED_STATES); + item.name.setTypeID(RenderWareConverter.TYPE_ID); + packer.writeFileData(item, rwStream.getRawData(), (int) rwStream.length()); + packer.addFile(item); + + item.name.setInstanceID(HashManager.get().getFileHash(splits[0])); + item.name.setGroupID(GROUP_STATES_LINK); + item.name.setTypeID(TYPE_ID); + packer.writeFileData(item, linkStream.getRawData(), (int) linkStream.length()); + packer.addFile(item); + } + + return true; + } + return false; + } + + @Override public boolean isDecoder(ResourceKey key) { + // TODO Auto-generated method stub + return false; + } + + @Override public boolean isEncoder(File file) { + checkExtensions(); + boolean valid = file.isDirectory() && file.getName().endsWith("." + EXTENSION + ".unpacked"); + if (valid) { + int groupID = HashManager.get().getFileHash(file.getParentFile().getName()); + valid = groupID == GROUP_COMPILED || groupID == GROUP_STATES_LINK; + } + return valid; + } + + @Override public String getName() { + return "Spore Materials (." + HashManager.get().getTypeName(TYPE_ID) + ")"; + } + + @Override public boolean isEnabledByDefault() { + return false; + } + + @Override public int getOriginalTypeID(String extension) { + return TYPE_ID; + } + @Override + public void generateContextMenu(ContextMenu contextMenu, ProjectItem item) { + // TODO Auto-generated method stub + + } + + +} diff --git a/src/sporemodder/file/shaders/StandardShader.java b/src/sporemodder/file/shaders/StandardShader.java new file mode 100644 index 0000000..4218631 --- /dev/null +++ b/src/sporemodder/file/shaders/StandardShader.java @@ -0,0 +1,107 @@ +package sporemodder.file.shaders; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import emord.filestructures.StreamReader; +import emord.filestructures.StreamWriter; + +public class StandardShader extends MaterialShader { + public static class StandardShaderEntry { + public byte[] vertexShader; + public byte[] pixelShader; + + public final List vertexShaderData = new ArrayList<>(); + public final List pixelShaderData = new ArrayList<>(); + } + + // Selected depending on the value of shader data 0x201 + public final Map entries = new TreeMap<>(); + + public StandardShader() { + super(); + flags |= 4; // ? + } + + @Override public void read(StreamReader in, int version) throws IOException { + + super.read(in, version); + + int renderType = in.readUByte(); + + while (renderType != 0xFF) { + + StandardShaderEntry entry = new StandardShaderEntry(); + entries.put(renderType, entry); + + entry.vertexShader = new byte[in.readInt()]; + in.read(entry.vertexShader); + + entry.pixelShader = new byte[in.readInt()]; + in.read(entry.pixelShader); + + int count = in.readInt(); + for (int i = 0; i < count; i++) { + ShaderDataUniform uniform = new ShaderDataUniform(); + uniform.readCompiled(in); + entry.vertexShaderData.add(uniform); + } + + count = in.readInt(); + for (int i = 0; i < count; i++) { + ShaderDataUniform uniform = new ShaderDataUniform(); + uniform.readCompiled(in); + entry.pixelShaderData.add(uniform); + } + + renderType = in.readUByte(); + } + } + + @Override public void write(StreamWriter out, int version) throws IOException { + super.write(out, version); + + for (Map.Entry entry : entries.entrySet()) { + out.writeUByte(entry.getKey()); + + StandardShaderEntry shader = entry.getValue(); + + out.writeInt(shader.vertexShader.length); + out.write(shader.vertexShader); + + out.writeInt(shader.pixelShader.length); + out.write(shader.pixelShader); + + out.writeInt(shader.vertexShaderData.size()); + for (ShaderDataUniform uniform : shader.vertexShaderData) { + uniform.write(out); + } + + out.writeInt(shader.pixelShaderData.size()); + for (ShaderDataUniform uniform : shader.pixelShaderData) { + uniform.write(out); + } + } + out.writeUByte(0xFF); + } + + public void compile(StandardShaderEntry entry, File sourceVertexFile, File sourcePixelFile, File includeFolder) throws IOException, InterruptedException { + FXCompiler fxc = FXCompiler.get(); + File compiledVShader = fxc.compile("vs_3_0", sourceVertexFile, includeFolder); + File compiledPShader = fxc.compile("ps_3_0", sourcePixelFile, includeFolder); + + entry.vertexShader = Files.readAllBytes(compiledVShader.toPath()); + entry.pixelShader = Files.readAllBytes(compiledPShader.toPath()); + + fxc.getUniformData(entry.vertexShader, entry.vertexShaderData); + fxc.getUniformData(entry.pixelShader, entry.pixelShaderData); + + compiledVShader.delete(); + compiledPShader.delete(); + } +} diff --git a/src/sporemodder/util/ColorRGBA.java b/src/sporemodder/util/ColorRGBA.java index b740a76..847e386 100644 --- a/src/sporemodder/util/ColorRGBA.java +++ b/src/sporemodder/util/ColorRGBA.java @@ -52,6 +52,13 @@ public ColorRGBA(Color color) { this.a = (float) color.getOpacity(); } + public ColorRGBA(int code) { + a = ((code & 0xFF000000) >> 24) / 255.0f; + r = ((code & 0x00FF0000) >> 16) / 255.0f; + g = ((code & 0x0000FF00) >> 8) / 255.0f; + b = ((code & 0x000000FF) >> 0) / 255.0f; + } + public ColorRGBA(ColorRGBA color) { copy(color); } diff --git a/src/sporemodder/view/editors/ArgScriptEditor.java b/src/sporemodder/view/editors/ArgScriptEditor.java index cad03f8..f3fa212 100644 --- a/src/sporemodder/view/editors/ArgScriptEditor.java +++ b/src/sporemodder/view/editors/ArgScriptEditor.java @@ -92,6 +92,8 @@ public ArgScriptEditor() { syntax.addExtras(streamSyntax, false); setErrorInfo(streamSyntax); + + afterStreamParse(); } }); @@ -268,15 +270,17 @@ public int compare(ErrorInfo obj1, ErrorInfo obj2) { } }); - if (documentErrors.isEmpty()) { - UIManager.get().getUserInterface().setStatusInfo(null); - UIManager.get().getUserInterface().getStatusBar().setStatus(Status.DEFAULT); - } else { - Label label = new Label("The file contains " + documentErrors.size() + " error" + (documentErrors.size() == 1 ? "" : "s") + ", cannot be compiled."); - label.setGraphic(UIManager.get().getAlertIcon(AlertType.WARNING, 16, 16)); - - UIManager.get().getUserInterface().setStatusInfo(label); - UIManager.get().getUserInterface().getStatusBar().setStatus(Status.ERROR); + if (item != null) { + if (documentErrors.isEmpty()) { + UIManager.get().getUserInterface().setStatusInfo(null); + UIManager.get().getUserInterface().getStatusBar().setStatus(Status.DEFAULT); + } else { + Label label = new Label("The file contains " + documentErrors.size() + " error" + (documentErrors.size() == 1 ? "" : "s") + ", cannot be compiled."); + label.setGraphic(UIManager.get().getAlertIcon(AlertType.WARNING, 16, 16)); + + UIManager.get().getUserInterface().setStatusInfo(label); + UIManager.get().getUserInterface().getStatusBar().setStatus(Status.ERROR); + } } } @@ -284,6 +288,10 @@ protected void onStreamParse() { } + protected void afterStreamParse() { + + } + /** * Replaces a split word of an ArgScriptLine. The line is fully contained in the specified DocumentFragment. * The document structure will be adapted accordingly. diff --git a/src/sporemodder/view/editors/RWModelViewer.java b/src/sporemodder/view/editors/RWModelViewer.java index 493c6db..dc369d2 100644 --- a/src/sporemodder/view/editors/RWModelViewer.java +++ b/src/sporemodder/view/editors/RWModelViewer.java @@ -22,7 +22,10 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -46,6 +49,8 @@ import javafx.scene.SnapshotParameters; import javafx.scene.SubScene; import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; import javafx.scene.control.ScrollPane; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; @@ -74,9 +79,12 @@ import javafx.scene.transform.Translate; import javafx.stage.FileChooser; import sporemodder.FileManager; +import sporemodder.HashManager; +import sporemodder.ProjectManager; import sporemodder.UIManager; import sporemodder.file.BoundingBox; import sporemodder.file.dds.DDSTexture; +import sporemodder.file.effects.EffectUnit; import sporemodder.file.rw4.Direct3DEnums.RWDECLUSAGE; import sporemodder.file.rw4.MaterialStateCompiler; import sporemodder.file.rw4.RWBBox; @@ -91,6 +99,7 @@ import sporemodder.file.rw4.RWVertexBuffer; import sporemodder.file.rw4.RWVertexElement; import sporemodder.file.rw4.RenderWare; +import sporemodder.file.shaders.MaterialStateLink; import sporemodder.util.ProjectItem; /** @@ -212,6 +221,7 @@ public Node getIcon(ProjectItem item) { private TreeView treeView = new TreeView<>(); private TreeItem tiTextures = new TreeItem("Textures"); + private TreeItem tiCompiledStates = new TreeItem("Compiled States"); private final Map nameMap = new HashMap<>(); private final Map> itemsMap = new HashMap<>(); @@ -238,7 +248,9 @@ private RWModelViewer() { treeView.setMaxHeight(TREE_VIEW_HEIGHT); rootItem.getChildren().add(tiTextures); + //rootItem.getChildren().add(tiCompiledStates); tiTextures.setExpanded(true); + tiCompiledStates.setExpanded(true); treeView.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> { if (newValue != null) { @@ -267,13 +279,13 @@ private TriangleMesh readMesh(RWMesh mesh) throws IOException { RWVertexElement normalElement = null; for (RWVertexElement element : buffer.vertexDescription.elements) { - if (element.typeCode == RWDECLUSAGE.POSITION) { + if (element.typeCode == RWDECLUSAGE.POSITION.getId()) { positionElement = element; } - else if (element.typeCode == RWDECLUSAGE.TEXCOORD) { + else if (element.typeCode == RWDECLUSAGE.TEXCOORD0.getId()) { texcoordElement = element; } - else if (element.typeCode == RWDECLUSAGE.NORMAL) { + else if (element.typeCode == RWDECLUSAGE.NORMAL.getId()) { normalElement = element; } } @@ -448,6 +460,7 @@ private void loadImages() throws IOException { } private void loadMaterial(RWCompiledState compiledState, MeshView meshView) throws IOException { + MaterialStateCompiler state = compiledState.data; state.decompile(); @@ -637,10 +650,21 @@ private void fillTreeView() { for (RWRaster raster : rasters) { String name = renderWare.getName(raster); nameMap.put(name, raster); + TreeItem item = new TreeItem(name); itemsMap.put(name, item); tiTextures.getChildren().add(item); } + + List compiledStates = renderWare.getObjects(RWCompiledState.class); + for (RWCompiledState compiledState : compiledStates) { + String name = renderWare.getName(compiledState); + nameMap.put(name, compiledState); + + TreeItem item = new TreeItem(name); + itemsMap.put(name, item); + tiCompiledStates.getChildren().add(item); + } } } @@ -811,7 +835,7 @@ public void loadFile(ProjectItem item) throws IOException { private void showInspector(boolean show) { if (show) { - UIManager.get().getUserInterface().getInspectorPane().setTitle("Effects Editor"); + UIManager.get().getUserInterface().getInspectorPane().setTitle("Model Viewer"); UIManager.get().getUserInterface().setInspectorContent(inspectorPane); } else { UIManager.get().getUserInterface().getInspectorPane().setTitle(null); @@ -872,15 +896,19 @@ public boolean supportsEditHistory() { private void fillPropertiesPane(String selectedName) { RWObject object = nameMap.get(selectedName); - if (object == null) { - propertiesContainer.setContent(null); - } - else if (object instanceof RWRaster) { + + if (object instanceof RWRaster) { fillTexturePane(selectedName, (RWRaster) object); + return; } - else { - propertiesContainer.setContent(null); + else if (object instanceof RWCompiledState) { + UIManager.get().tryAction(() -> { + showCompiledStateEditor(selectedName); + }, "Error with compiled state."); + // We don't return because we want to empty the properties container } + + propertiesContainer.setContent(null); } private void fillTexturePane(String name, RWRaster raster) { @@ -985,7 +1013,7 @@ private String getSelectedName() { } } - if (undoRedoIndex == 1 && editHistory.get(0) == ORIGINAL_ACTION) { + if (undoRedoIndex == 0 && editHistory.get(0) == ORIGINAL_ACTION) { setIsSaved(true); } else { setIsSaved(false); @@ -1066,4 +1094,92 @@ protected void saveData() throws Exception { protected void restoreContents() throws Exception { loadModel(getItem()); } + + public void showCompiledStateEditor(String name) throws IOException { + HashManager.get().setUpdateProjectRegistry(true); + + RWCompiledState state = (RWCompiledState) nameMap.get(name); + + Dialog dialog = new Dialog(); + CompiledStateEditor editor = new CompiledStateEditor(dialog); + if (!state.data.isDecompiled()) { + state.data.decompile(); + } + editor.loadContents(state.data.toArgScript(name)); + + dialog.getDialogPane().getButtonTypes().setAll(ButtonType.CANCEL, ButtonType.APPLY); + dialog.getDialogPane().setContent(editor); + dialog.setTitle(name); + + editor.setPrefWidth(800); + editor.setPrefHeight(600); + + dialog.setResizable(true); + + if (UIManager.get().showDialog(dialog).orElse(ButtonType.CANCEL) != ButtonType.CANCEL) { + HashManager.get().setUpdateProjectRegistry(false); + ProjectManager.get().saveNamesRegistry(); + + byte[] oldData = state.data.getData(); + + MaterialStateCompiler compiler = editor.stream.getData().states.get(0); + compiler.vertexDescription = state.data.vertexDescription; + compiler.compile(); + byte[] newData = compiler.getData(); + + if (!Arrays.equals(oldData, newData)) { + state.data = compiler; + + addEditAction(new RWUndoableAction(name) { + + @Override public void undo() { + state.data.setData(oldData); + try { + state.data.decompile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override public void redo() { + state.data.setData(newData); + try { + state.data.decompile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + } + } + + private class CompiledStateEditor extends ArgScriptEditor { + private final MaterialStateLink stateLink = new MaterialStateLink(); + private final Dialog dialog; + + public CompiledStateEditor(Dialog dialog) { + super(); + this.dialog = dialog; + + stateLink.renderWare = renderWare; + stream = stateLink.generateStream(false); + } + + @Override protected void onStreamParse() { + super.onStreamParse(); + + stateLink.reset(); + } + + @Override protected void afterStreamParse() { + super.afterStreamParse(); + + Node button = dialog.getDialogPane().lookupButton(ButtonType.APPLY); + if (button != null && stream != null) { + boolean disable = !stream.getErrors().isEmpty() || stream.getData().states.isEmpty(); + button.setDisable(disable); + } + } + } } diff --git a/src/sporemodder/view/syntax/ArgScriptSyntax.java b/src/sporemodder/view/editors/SmtTextEditor.java similarity index 70% rename from src/sporemodder/view/syntax/ArgScriptSyntax.java rename to src/sporemodder/view/editors/SmtTextEditor.java index 73a9f49..24fbdda 100644 --- a/src/sporemodder/view/syntax/ArgScriptSyntax.java +++ b/src/sporemodder/view/editors/SmtTextEditor.java @@ -16,14 +16,20 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . ****************************************************************************/ -package sporemodder.view.syntax; +package sporemodder.view.editors; -public abstract class ArgScriptSyntax extends StandardSyntaxFormat { +import sporemodder.file.shaders.MaterialStateLink; - @Override - public String getStylesheetPath() { - //TODO first try to get from 'SporeModder\Syntax\', otherwise get a failsafe from inside the .jar - return XmlSyntax.class.getResource("/sporemodder/resources/styles/ArgScriptSyntax.css").toExternalForm(); +public class SmtTextEditor extends ArgScriptEditor { + + public SmtTextEditor() { + super(); + + MaterialStateLink unit = new MaterialStateLink(); + stream = unit.generateStream(false); + } + + @Override protected void onStreamParse() { + stream.getData().reset(); } - } diff --git a/src/sporemodder/view/syntax/StandardSyntaxFormat.java b/src/sporemodder/view/editors/SmtTextEditorFactory.java similarity index 59% rename from src/sporemodder/view/syntax/StandardSyntaxFormat.java rename to src/sporemodder/view/editors/SmtTextEditorFactory.java index b80d742..8edb4ad 100644 --- a/src/sporemodder/view/syntax/StandardSyntaxFormat.java +++ b/src/sporemodder/view/editors/SmtTextEditorFactory.java @@ -16,32 +16,25 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . ****************************************************************************/ -package sporemodder.view.syntax; +package sporemodder.view.editors; -import java.util.Collection; +import javafx.scene.Node; +import sporemodder.util.ProjectItem; -import org.fxmisc.richtext.model.StyleSpans; +public class SmtTextEditorFactory implements EditorFactory { -public abstract class StandardSyntaxFormat implements SyntaxFormatFactory { - - private SyntaxHighlighter highlighter; - - public SyntaxHighlighter getHighlighter() { - return highlighter; + @Override + public ItemEditor createInstance() { + return new SmtTextEditor(); } - public void setHighlighter(SyntaxHighlighter highlighter) { - this.highlighter = highlighter; + @Override + public boolean isSupportedFile(ProjectItem item) { + return !item.isFolder() && item.getName().endsWith(".smt_t"); } - + @Override - public StyleSpans> generateStyle(String text) { - if (highlighter != null) { - highlighter.setText(text, null); - return highlighter.generateStyleSpans(); - } - else { - return null; - } + public Node getIcon(ProjectItem item) { + return null; } } diff --git a/src/sporemodder/view/editors/TextEditor.java b/src/sporemodder/view/editors/TextEditor.java index a1ddc59..b9ff79c 100644 --- a/src/sporemodder/view/editors/TextEditor.java +++ b/src/sporemodder/view/editors/TextEditor.java @@ -34,18 +34,24 @@ import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.LineNumberFactory; import org.fxmisc.richtext.event.MouseOverTextEvent; +import org.fxmisc.wellbehaved.event.EventPattern; +import org.fxmisc.wellbehaved.event.InputMap; +import org.fxmisc.wellbehaved.event.Nodes; import javafx.geometry.Point2D; import javafx.scene.Node; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Label; import javafx.scene.control.Skin; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCombination; import javafx.stage.Popup; import sporemodder.EditorManager; import sporemodder.ProjectManager; import sporemodder.UIManager; import sporemodder.file.DocumentFragment; import sporemodder.file.DocumentStructure; +import sporemodder.file.TextUtils; import sporemodder.util.ProjectItem; import sporemodder.view.syntax.SyntaxFormat; import sporemodder.view.syntax.SyntaxHighlighter; @@ -107,7 +113,22 @@ public TextEditor() { codeArea.requestFollowCaret(); } }); - + + Nodes.addInputMap(codeArea, InputMap.consume(EventPattern.keyPressed(KeyCode.TAB), event -> { + if (codeArea.getSelection().getLength() != 0) { + tabulateSelection(); + } else { + codeArea.replaceSelection("\t"); + } + })); + + Nodes.addInputMap(codeArea, InputMap.consume(EventPattern.keyPressed(KeyCode.TAB, KeyCombination.SHIFT_DOWN), event -> { + if (codeArea.getSelection().getLength() != 0) { + untabulateSelection(); + } else { + codeArea.replaceSelection("\t"); + } + })); // -- Tooltips -- // popupMsg.getStyleClass().add("tooltip"); @@ -147,6 +168,47 @@ public TextEditor() { }); } + private void tabulateSelection() { + int selectionStart = codeArea.getSelection().getStart(); + int selectionEnd = codeArea.getSelection().getEnd(); + String text = codeArea.getText(); + + List indices = new ArrayList<>(); + int lineStart = TextUtils.scanLineStart(text, selectionStart); + while (lineStart < selectionEnd) { + indices.add(lineStart); + lineStart = TextUtils.scanLineStart(text, TextUtils.scanLineEnd(text, lineStart) + 1); + } + + for (int i = 0; i < indices.size(); ++i) { + // Add i because there are i \t characters now + codeArea.insertText(indices.get(i) + i, "\t"); + } + } + + private void untabulateSelection() { + int selectionStart = codeArea.getSelection().getStart(); + int selectionEnd = codeArea.getSelection().getEnd(); + String text = codeArea.getText(); + + List indices = new ArrayList<>(); + int lineStart = TextUtils.scanLineStart(text, selectionStart); + while (lineStart < selectionEnd) { + indices.add(lineStart); + lineStart = TextUtils.scanLineStart(text, TextUtils.scanLineEnd(text, lineStart) + 1); + } + + int subtract = 0; + for (int i = 0; i < indices.size(); ++i) { + int index = indices.get(i) - subtract; + if (text.charAt(index) == '\t') { + codeArea.replaceText(index, index + 1, ""); + text = codeArea.getText(); + ++subtract; + } + } + } + public void loadFile(ProjectItem item) throws IOException { if (item != null) { this.item = item; @@ -189,16 +251,10 @@ public void load(File file) throws IOException { // Set the text byte[] bytes = Files.readAllBytes(file.toPath()); - originalContents = new String(bytes); - codeArea.replaceText(originalContents); - - codeArea.moveTo(0); - codeArea.scrollToPixel(0, 0); // Always returns null, does not work fine // String type = Files.probeContentType(file.toPath()); // isValidText = "text/plain".equals(type); - try { Charset.availableCharsets().get("UTF-8").newDecoder() .decode(ByteBuffer.wrap(bytes)); @@ -207,6 +263,16 @@ public void load(File file) throws IOException { isValidText = false; } + loadContents(new String(bytes)); + } + + public void loadContents(String contents) throws IOException { + originalContents = contents; + codeArea.replaceText(originalContents); + + codeArea.moveTo(0); + codeArea.scrollToPixel(0, 0); + if (item != null && item.isMod() && ProjectManager.get().getActive().isReadOnly()) { codeArea.setEditable(false); } else { @@ -216,6 +282,8 @@ public void load(File file) throws IOException { setIsSaved(true); updateSyntaxHighlighting(); + + codeArea.getUndoManager().forgetHistory(); } @@ -295,7 +363,7 @@ public void updateSyntaxHighlighting() { try { codeArea.setStyleSpans(0, syntax.generateStyleSpans()); } catch (Exception e) { - + e.printStackTrace(); } } @@ -366,7 +434,7 @@ else if (c == '\t') { public void setActive(boolean isActive) { super.setActive(isActive); - if (isActive && !isValidText) { + if (isActive && !isValidText && item != null) { Label label = new Label("This format is not supported by SporeModder and it cannot be read as text, therefore it cannot be edited."); label.setGraphic(UIManager.get().getAlertIcon(AlertType.WARNING, 16, 16)); diff --git a/src/sporemodder/view/editors/TextEditorSkin.java b/src/sporemodder/view/editors/TextEditorSkin.java index cd041c3..004f846 100644 --- a/src/sporemodder/view/editors/TextEditorSkin.java +++ b/src/sporemodder/view/editors/TextEditorSkin.java @@ -23,7 +23,7 @@ import javafx.scene.control.SkinBase; -public class TextEditorSkin extends SkinBase { +class TextEditorSkin extends SkinBase { private VirtualizedScrollPane scrollPane; diff --git a/src/sporemodder/view/syntax/ArgScriptSyntax2.java b/src/sporemodder/view/syntax/ArgScriptSyntax2.java deleted file mode 100644 index 12b520e..0000000 --- a/src/sporemodder/view/syntax/ArgScriptSyntax2.java +++ /dev/null @@ -1,129 +0,0 @@ -/**************************************************************************** -* Copyright (C) 2019 Eric Mor -* -* This file is part of SporeModder FX. -* -* SporeModder FX is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -****************************************************************************/ -package sporemodder.view.syntax; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.fxmisc.richtext.model.StyleSpans; -import org.fxmisc.richtext.model.StyleSpansBuilder; - -import sporemodder.file.argscript.ArgScriptSyntaxHighlighting; - -public abstract class ArgScriptSyntax2 implements SyntaxFormatFactory { - - /* - * (? - the capturing group Comment - * ( - start block comment group - * #< - the characters #< - * .* - any characters - * #> - the characters #> - * ) - end block comment group - * | - or - * ( - start line comment group - * # - the character # - * .* - any characters - * $ - the end of the line - * ) - end line comment group - * ) - end Attribute - */ - private static final String TAG_COMMENT = "(?(#<.*#>)|(#(?![><]).*$))"; - //private static final String TAG_COMMENT = "(?(#<.*))"; - - private final Pattern pattern; - - public ArgScriptSyntax2(ArgScriptSyntaxHighlighting syntax) { - - StringBuilder sb = new StringBuilder(); - sb.append("(?m)(?s)"); - - sb.append('('); - sb.append(TAG_COMMENT); - sb.append(')'); - sb.append('!'); - - sb.append('('); - - sb.append("(?"); - List blocks = syntax.getBlocks(); - - sb.append("asd"); - -// for (int i = 0; i < blocks.size(); i++) { -// sb.append('('); -// sb.append("^\\s*"); -// sb.append(blocks.get(i)); -// sb.append("\\s+"); -// sb.append(')'); -// -// if (i + 1 != blocks.size()) { -// sb.append('!'); -// } -// } - - sb.append(')'); - - sb.append(')'); - - System.out.println(sb.toString()); - - pattern = Pattern.compile(sb.toString()); - } - - @Override - public String getStylesheetPath() { - //TODO first try to get from 'SporeModder\Syntax\', otherwise get a failsafe from inside the .jar - return XmlSyntax.class.getResource("/sporemodder/resources/styles/ArgScriptSyntax.css").toExternalForm(); - } - - @Override - public StyleSpans> generateStyle(String text) { - - Matcher matcher = pattern.matcher(text); - int lastEnd = 0; - - StyleSpansBuilder> builder = new StyleSpansBuilder>(); - - while (matcher.find()) { - - String styleClass = null; - - if (matcher.group("Comment") != null) { - styleClass = "argscript-comment"; - } - else if (matcher.group("Block") != null) { - styleClass = "argscript-block"; - } - - - builder.add(Collections.emptyList(), matcher.start() - lastEnd); - builder.add(Collections.singleton(styleClass), matcher.end() - matcher.start()); - - lastEnd = matcher.end(); - } - - // Add the remaining text - builder.add(Collections.emptyList(), text.length() - lastEnd); - - return builder.create(); - } -} diff --git a/src/sporemodder/view/syntax/HlslSyntax.java b/src/sporemodder/view/syntax/HlslSyntax.java new file mode 100644 index 0000000..a70220a --- /dev/null +++ b/src/sporemodder/view/syntax/HlslSyntax.java @@ -0,0 +1,173 @@ +package sporemodder.view.syntax; + +import java.io.File; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class HlslSyntax implements SyntaxFormatFactory { + + private static final String[] TAGS_ENUMS = new String[] { + // Vertex Shader input semantics + "BINORMAL", "BINORMAL0", "BINORMAL1", "BINORMAL2", "BINORMAL3", "BINORMAL4", + "BLENDINDICES", "BLENDINDICES0", "BLENDINDICES1", "BLENDINDICES2", "BLENDINDICES3", "BLENDINDICES4", + "BLENDWEIGHT", "BLENDWEIGHT0", "BLENDWEIGHT1", "BLENDWEIGHT2", "BLENDWEIGHT3", "BLENDWEIGHT4", + "COLOR", "COLOR0", "COLOR1", "COLOR2", "COLOR3", "COLOR4", + "NORMAL", "NORMAL0", "NORMAL1", "NORMAL2", "NORMAL3", "NORMAL4", + "POSITION", "POSITION0", "POSITION1", "POSITION2", "POSITION3", "POSITION4", "POSITIONT", + "PSIZE", "PSIZE0", "PSIZE1", "PSIZE2", "PSIZE3", "PSIZE4", + "TANGENT", "TANGENT0", "TANGENT1", "TANGENT2", "TANGENT3", "TANGENT4", + "TESSFACTOR", "TESSFACTOR0", "TESSFACTOR1", "TESSFACTOR2", "TESSFACTOR3", "TESSFACTOR4", + "TEXCOORD", "TEXCOORD0", "TEXCOORD1", "TEXCOORD2", "TEXCOORD3", "TEXCOORD4", "TEXCOORD5", "TEXCOORD6", "TEXCOORD7", "TEXCOORD8", + // Vertex Shader output Symantics + "FOG", "PSIZE", + // Pixel Shader input Symantics + "VFACE", "VPOS", + // Pixel Shader output Symantics + "DEPTH", "DEPTH0", "DEPTH1", "DEPTH2", "DEPTH3", "DEPTH4", "DEPTH5" + }; + + private static final String[] TAGS_TYPES = new String[] { + // Basic types + "BOOL", "bool", "int", "half", "float", "double", "sampler", "string", + "sampler1D", "sampler2D", "sampler3D", "samplerCUBE", + // Vector and matrix types + "bool1", "bool2", "bool3", "bool4", + "BOOL1", "BOOL2", "BOOL3", "BOOL4", + "int1", "int2", "int3", "int4", + "half1", "half2", "half3", "half4", + "float1", "float2", "float3", "float4", + "double1", "double2", "double3", "double4", + "vector", + "bool1x1", "bool1x2", "bool1x3", "bool1x4", + "bool2x1", "bool2x2", "bool2x3", "bool2x4", + "bool3x1", "bool3x2", "bool3x3", "bool3x4", + "bool4x1", "bool4x2", "bool4x3", "bool4x4", + "BOOL1x1", "BOOL1x2", "BOOL1x3", "BOOL1x4", + "BOOL2x1", "BOOL2x2", "BOOL2x3", "BOOL2x4", + "BOOL3x1", "BOOL3x2", "BOOL3x3", "BOOL3x4", + "BOOL4x1", "BOOL4x2", "BOOL4x3", "BOOL4x4", + "half1x1", "half1x2", "half1x3", "half1x4", + "half2x1", "half2x2", "half2x3", "half2x4", + "half3x1", "half3x2", "half3x3", "half3x4", + "half4x1", "half4x2", "half4x3", "half4x4", + "int1x1", "int1x2", "int1x3", "int1x4", + "int2x1", "int2x2", "int2x3", "int2x4", + "int3x1", "int3x2", "int3x3", "int3x4", + "int4x1", "int4x2", "int4x3", "int4x4", + "float1x1", "float1x2", "float1x3", "float1x4", + "float2x1", "float2x2", "float2x3", "float2x4", + "float3x1", "float3x2", "float3x3", "float3x4", + "float4x1", "float4x2", "float4x3", "float4x4", + "double1x1", "double1x2", "double1x3", "double1x4", + "double2x1", "double2x2", "double2x3", "double2x4", + "double3x1", "double3x2", "double3x3", "double3x4", + "double4x1", "double4x2", "double4x3", "double4x4", + "matrix", "vertexshader", "pixelshader" + }; + + private static final String[] TAGS_KEYWORDS = new String[] { + "struct", "extern", "shared", "static", "uniform", "volatile", "const", "row_major", "column_major", + "pack_matrix", "warning", "def", "once", "default", "disable", "error", + "vs", "vs_1_1", "vs_2_0", "vs_2_a", "ps", "ps_1_1", "ps_1_2", "ps_1_3", "ps_1_4", "ps_2_0", "ps_2_a", + "__FILE__", "__LINE__", "asm", "asm_fragment", "compile", "compile_fragment", "discard", "decl", + "do", "else", "false", "for", "if", "in", "inline", "inout", "out", + "pass", "pixelfragment", "return", "register", "sampler_state", "shared", "stateblock", "stateblock_state", + "technique", "true", "typedef", "uniform", "vertexfragment", "void", "volatile", "while", + // Methods used by ModAPI's custom shaders + "VS_main", "PS_main" + }; + + private static final String[] TAGS_FUNCTIONS = new String[] { + "abs", "acos", "all", "any", "asin", "atan", "atan2", "ceil", "clamp", "clip", "cos", "cosh", "cross", + "D3DCOLORtoUBYTE4", "ddx", "ddy", "degrees", "determinant", "distance", "dot", "exp", "exp2", "faceforward", + "floor", "fmod", "frac", "frexp", "fwidth", "isfinite", "isinf", "isnan", "ldexp", "length", "lerp", "lit", + "log", "log10", "log2", "max", "min", "modf", "mul", "noise", "normalize", "pow", "radians", "reflect", + "refract", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step", + "tan", "tanh", "tex1D", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", + "tex2D", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj", "tex3D", "tex3D", + "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", + "texCUBE", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose" + }; + + private static final String HLSL_COMMENTS = "hlsl-comments"; + + private static Map patterns = new LinkedHashMap<>(); + + static { + // NOTE: the order is important! + + patterns.put(Pattern.compile("\\W+(\\d*\\.?\\d*)\\W"), "hlsl-numbers"); + + for (String s : TAGS_ENUMS) { + patterns.put(Pattern.compile("\\W+(" + s + ")\\W"), "hlsl-enums"); + } + for (String s : TAGS_TYPES) { + patterns.put(Pattern.compile("^(" + s + ")\\W"), "hlsl-types"); + patterns.put(Pattern.compile("\\W(" + s + ")\\W"), "hlsl-types"); + } + for (String s : TAGS_KEYWORDS) { + patterns.put(Pattern.compile("^(" + s + ")\\W"), "hlsl-keywords"); + patterns.put(Pattern.compile("\\W+(" + s + ")\\W"), "hlsl-keywords"); + } + for (String s : TAGS_FUNCTIONS) { + patterns.put(Pattern.compile("\\W(" + s + ")\\("), "hlsl-functions"); + } + } + + @Override public void generateStyle(String text, SyntaxHighlighter syntax) { + + SyntaxHighlighter commentsSyntax = new SyntaxHighlighter(); + + // Process block comments + int indexOf = text.indexOf("/*"); + while (indexOf != -1) { + int endIndex = text.indexOf("*/", indexOf + 2); + + // It's an error, but we comment all the following text + if (endIndex == -1) { + commentsSyntax.add(indexOf, text.length() - indexOf, Collections.singleton(HLSL_COMMENTS)); + break; + } else { + // + 2 because we also include the ending */ + commentsSyntax.add(indexOf, endIndex - indexOf + 2, Collections.singleton(HLSL_COMMENTS)); + } + + indexOf = text.indexOf("/*", endIndex + 2); + } + + // Process line comments + indexOf = text.indexOf("//"); + while (indexOf != -1) { + int endIndex = text.indexOf("\n", indexOf + 2); + // End of the file + if (endIndex == -1) { + commentsSyntax.add(indexOf, text.length() - indexOf, Collections.singleton(HLSL_COMMENTS)); + break; + } else { + commentsSyntax.add(indexOf, endIndex - indexOf, Collections.singleton(HLSL_COMMENTS)); + } + indexOf = text.indexOf("//", endIndex + 1); + } + + for (Map.Entry entry : patterns.entrySet()) { + Matcher matcher = entry.getKey().matcher(text); + + while (matcher.find()) { + int start = matcher.start(1); + int end = matcher.end(1); + + if (start != end) syntax.add(start, end - start, Collections.singleton(entry.getValue())); + } + } + + // We remove the existing syntax if it collides with comments + syntax.addExtras(commentsSyntax, true); + } + + @Override public boolean isSupportedFile(File file) { + return file.isFile() && file.getName().endsWith(".hlsl"); + } +} diff --git a/src/sporemodder/view/syntax/SyntaxHighlighter.java b/src/sporemodder/view/syntax/SyntaxHighlighter.java index ea8c76b..7690619 100644 --- a/src/sporemodder/view/syntax/SyntaxHighlighter.java +++ b/src/sporemodder/view/syntax/SyntaxHighlighter.java @@ -130,6 +130,7 @@ private void addExtraRemoving(int start, int size, Collection styles) { // Invariant: ceilEntry.start >= start while (ceilEntry != null && ceilEntry.getEnd() <= end) { entries.remove(ceilEntry.start); + ceilEntry = higherEntry(ceilEntry.start); } // Now that the boundaries are cleared, just add the new style diff --git a/src/sporemodder/view/syntax/XmlSyntax.java b/src/sporemodder/view/syntax/XmlSyntax.java index 857967c..516c14b 100644 --- a/src/sporemodder/view/syntax/XmlSyntax.java +++ b/src/sporemodder/view/syntax/XmlSyntax.java @@ -78,13 +78,13 @@ public class XmlSyntax implements SyntaxFormatFactory { private static final String TAG_CDATA = "(?<\\!\\[CDATA\\[.*\\]\\]>)"; - private static final String TAG_PATTERN_END = "(>)?"; - private static final String TAG_CLOSING_PATTERN = "(/>)"; - private static final String TAG_ATTRIBUTE_PATTERN = "\\s(\\w*)\\="; - //private static final String TAG_ATTRIBUTE_VALUE = "[a-z-]*\\=(\"[^\"]*\")"; // [a-z-] - //private static final String TAG_COMMENT = "()"; - private static final String TAG_CDATA_START = "(\\)"; +// private static final String TAG_PATTERN_END = "(>)?"; +// private static final String TAG_CLOSING_PATTERN = "(/>)"; +// private static final String TAG_ATTRIBUTE_PATTERN = "\\s(\\w*)\\="; +// //private static final String TAG_ATTRIBUTE_VALUE = "[a-z-]*\\=(\"[^\"]*\")"; // [a-z-] +// //private static final String TAG_COMMENT = "()"; +// private static final String TAG_CDATA_START = "(\\)"; private static final Pattern PATTERN = Pattern.compile( "(" + TAG_PATTERN + ")" +