diff --git a/Penumbra.GameData b/Penumbra.GameData
index 78ce195c..c5250722 160000
--- a/Penumbra.GameData
+++ b/Penumbra.GameData
@@ -1 +1 @@
-Subproject commit 78ce195c171d7bce4ad9df105f1f95cce9bf1150
+Subproject commit c525072299d5febd2bb638ab229060b0073ba6a6
diff --git a/Penumbra.sln b/Penumbra.sln
index 94a04ef3..e864fbee 100644
--- a/Penumbra.sln
+++ b/Penumbra.sln
@@ -24,6 +24,34 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.CrashHandler", "Penumbra.CrashHandler\Penumbra.CrashHandler.csproj", "{EE834491-A98F-4395-BE0D-6861AE5AD953}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Schemas", "Schemas", "{BFEA7504-1210-4F79-A7FE-BF03B6567E33}"
+ ProjectSection(SolutionItems) = preProject
+ schemas\default_mod.json = schemas\default_mod.json
+ schemas\group.json = schemas\group.json
+ schemas\local_mod_data-v3.json = schemas\local_mod_data-v3.json
+ schemas\mod_meta-v3.json = schemas\mod_meta-v3.json
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "structs", "structs", "{B03F276A-0572-4F62-AF86-EF62F6B80463}"
+ ProjectSection(SolutionItems) = preProject
+ schemas\structs\container.json = schemas\structs\container.json
+ schemas\structs\group_combining.json = schemas\structs\group_combining.json
+ schemas\structs\group_imc.json = schemas\structs\group_imc.json
+ schemas\structs\group_multi.json = schemas\structs\group_multi.json
+ schemas\structs\group_single.json = schemas\structs\group_single.json
+ schemas\structs\manipulation.json = schemas\structs\manipulation.json
+ schemas\structs\meta_atch.json = schemas\structs\meta_atch.json
+ schemas\structs\meta_enums.json = schemas\structs\meta_enums.json
+ schemas\structs\meta_eqdp.json = schemas\structs\meta_eqdp.json
+ schemas\structs\meta_eqp.json = schemas\structs\meta_eqp.json
+ schemas\structs\meta_est.json = schemas\structs\meta_est.json
+ schemas\structs\meta_geqp.json = schemas\structs\meta_geqp.json
+ schemas\structs\meta_gmp.json = schemas\structs\meta_gmp.json
+ schemas\structs\meta_imc.json = schemas\structs\meta_imc.json
+ schemas\structs\meta_rsp.json = schemas\structs\meta_rsp.json
+ schemas\structs\option.json = schemas\structs\option.json
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -58,6 +86,10 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {BFEA7504-1210-4F79-A7FE-BF03B6567E33} = {F89C9EAE-25C8-43BE-8108-5921E5A93502}
+ {B03F276A-0572-4F62-AF86-EF62F6B80463} = {BFEA7504-1210-4F79-A7FE-BF03B6567E33}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
EndGlobalSection
diff --git a/Penumbra/Mods/Manager/ModDataEditor.cs b/Penumbra/Mods/Manager/ModDataEditor.cs
index 162f823d..7c48205a 100644
--- a/Penumbra/Mods/Manager/ModDataEditor.cs
+++ b/Penumbra/Mods/Manager/ModDataEditor.cs
@@ -27,6 +27,9 @@ public enum ModDataChangeType : ushort
public class ModDataEditor(SaveService saveService, CommunicatorService communicatorService) : IService
{
+ public SaveService SaveService
+ => saveService;
+
/// Create the file containing the meta information about a mod from scratch.
public void CreateMeta(DirectoryInfo directory, string? name, string? author, string? description, string? version,
string? website)
@@ -40,148 +43,6 @@ public class ModDataEditor(SaveService saveService, CommunicatorService communic
saveService.ImmediateSaveSync(new ModMeta(mod));
}
- public ModDataChangeType LoadLocalData(Mod mod)
- {
- var dataFile = saveService.FileNames.LocalDataFile(mod);
-
- var importDate = 0L;
- var localTags = Enumerable.Empty();
- var favorite = false;
- var note = string.Empty;
-
- var save = true;
- if (File.Exists(dataFile))
- try
- {
- var text = File.ReadAllText(dataFile);
- var json = JObject.Parse(text);
-
- importDate = json[nameof(Mod.ImportDate)]?.Value() ?? importDate;
- favorite = json[nameof(Mod.Favorite)]?.Value() ?? favorite;
- note = json[nameof(Mod.Note)]?.Value() ?? note;
- localTags = (json[nameof(Mod.LocalTags)] as JArray)?.Values().OfType() ?? localTags;
- save = false;
- }
- catch (Exception e)
- {
- Penumbra.Log.Error($"Could not load local mod data:\n{e}");
- }
-
- if (importDate == 0)
- importDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-
- ModDataChangeType changes = 0;
- if (mod.ImportDate != importDate)
- {
- mod.ImportDate = importDate;
- changes |= ModDataChangeType.ImportDate;
- }
-
- changes |= ModLocalData.UpdateTags(mod, null, localTags);
-
- if (mod.Favorite != favorite)
- {
- mod.Favorite = favorite;
- changes |= ModDataChangeType.Favorite;
- }
-
- if (mod.Note != note)
- {
- mod.Note = note;
- changes |= ModDataChangeType.Note;
- }
-
- if (save)
- saveService.QueueSave(new ModLocalData(mod));
-
- return changes;
- }
-
- public ModDataChangeType LoadMeta(ModCreator creator, Mod mod)
- {
- var metaFile = saveService.FileNames.ModMetaPath(mod);
- if (!File.Exists(metaFile))
- {
- Penumbra.Log.Debug($"No mod meta found for {mod.ModPath.Name}.");
- return ModDataChangeType.Deletion;
- }
-
- try
- {
- var text = File.ReadAllText(metaFile);
- var json = JObject.Parse(text);
-
- var newName = json[nameof(Mod.Name)]?.Value() ?? string.Empty;
- var newAuthor = json[nameof(Mod.Author)]?.Value() ?? string.Empty;
- var newDescription = json[nameof(Mod.Description)]?.Value() ?? string.Empty;
- var newImage = json[nameof(Mod.Image)]?.Value() ?? string.Empty;
- var newVersion = json[nameof(Mod.Version)]?.Value() ?? string.Empty;
- var newWebsite = json[nameof(Mod.Website)]?.Value() ?? string.Empty;
- var newFileVersion = json[nameof(ModMeta.FileVersion)]?.Value() ?? 0;
- var importDate = json[nameof(Mod.ImportDate)]?.Value();
- var modTags = (json[nameof(Mod.ModTags)] as JArray)?.Values().OfType();
-
- ModDataChangeType changes = 0;
- if (mod.Name != newName)
- {
- changes |= ModDataChangeType.Name;
- mod.Name = newName;
- }
-
- if (mod.Author != newAuthor)
- {
- changes |= ModDataChangeType.Author;
- mod.Author = newAuthor;
- }
-
- if (mod.Description != newDescription)
- {
- changes |= ModDataChangeType.Description;
- mod.Description = newDescription;
- }
-
- if (mod.Image != newImage)
- {
- changes |= ModDataChangeType.Image;
- mod.Image = newImage;
- }
-
- if (mod.Version != newVersion)
- {
- changes |= ModDataChangeType.Version;
- mod.Version = newVersion;
- }
-
- if (mod.Website != newWebsite)
- {
- changes |= ModDataChangeType.Website;
- mod.Website = newWebsite;
- }
-
- if (newFileVersion != ModMeta.FileVersion)
- if (ModMigration.Migrate(creator, saveService, mod, json, ref newFileVersion))
- {
- changes |= ModDataChangeType.Migration;
- saveService.ImmediateSave(new ModMeta(mod));
- }
-
- if (importDate != null && mod.ImportDate != importDate.Value)
- {
- mod.ImportDate = importDate.Value;
- changes |= ModDataChangeType.ImportDate;
- }
-
- changes |= ModLocalData.UpdateTags(mod, modTags, null);
-
- return changes;
- }
- catch (Exception e)
- {
- Penumbra.Log.Error($"Could not load mod meta for {metaFile}:\n{e}");
- return ModDataChangeType.Deletion;
- }
- }
-
public void ChangeModName(Mod mod, string newName)
{
if (mod.Name.Text == newName)
diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs
index 18d2bc09..0db83ef9 100644
--- a/Penumbra/Mods/ModCreator.cs
+++ b/Penumbra/Mods/ModCreator.cs
@@ -72,11 +72,11 @@ public partial class ModCreator(
if (!Directory.Exists(mod.ModPath.FullName))
return false;
- modDataChange = dataEditor.LoadMeta(this, mod);
+ modDataChange = ModMeta.Load(dataEditor, this, mod);
if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0)
return false;
- modDataChange |= dataEditor.LoadLocalData(mod);
+ modDataChange |= ModLocalData.Load(dataEditor, mod);
LoadDefaultOption(mod);
LoadAllGroups(mod);
if (incorporateMetaChanges)
diff --git a/Penumbra/Mods/ModLocalData.cs b/Penumbra/Mods/ModLocalData.cs
index beda0dc7..d3534391 100644
--- a/Penumbra/Mods/ModLocalData.cs
+++ b/Penumbra/Mods/ModLocalData.cs
@@ -27,6 +27,63 @@ public readonly struct ModLocalData(Mod mod) : ISavable
jObject.WriteTo(jWriter);
}
+ public static ModDataChangeType Load(ModDataEditor editor, Mod mod)
+ {
+ var dataFile = editor.SaveService.FileNames.LocalDataFile(mod);
+
+ var importDate = 0L;
+ var localTags = Enumerable.Empty();
+ var favorite = false;
+ var note = string.Empty;
+
+ var save = true;
+ if (File.Exists(dataFile))
+ try
+ {
+ var text = File.ReadAllText(dataFile);
+ var json = JObject.Parse(text);
+
+ importDate = json[nameof(Mod.ImportDate)]?.Value() ?? importDate;
+ favorite = json[nameof(Mod.Favorite)]?.Value() ?? favorite;
+ note = json[nameof(Mod.Note)]?.Value() ?? note;
+ localTags = (json[nameof(Mod.LocalTags)] as JArray)?.Values().OfType() ?? localTags;
+ save = false;
+ }
+ catch (Exception e)
+ {
+ Penumbra.Log.Error($"Could not load local mod data:\n{e}");
+ }
+
+ if (importDate == 0)
+ importDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+
+ ModDataChangeType changes = 0;
+ if (mod.ImportDate != importDate)
+ {
+ mod.ImportDate = importDate;
+ changes |= ModDataChangeType.ImportDate;
+ }
+
+ changes |= ModLocalData.UpdateTags(mod, null, localTags);
+
+ if (mod.Favorite != favorite)
+ {
+ mod.Favorite = favorite;
+ changes |= ModDataChangeType.Favorite;
+ }
+
+ if (mod.Note != note)
+ {
+ mod.Note = note;
+ changes |= ModDataChangeType.Note;
+ }
+
+ if (save)
+ editor.SaveService.QueueSave(new ModLocalData(mod));
+
+ return changes;
+ }
+
internal static ModDataChangeType UpdateTags(Mod mod, IEnumerable? newModTags, IEnumerable? newLocalTags)
{
if (newModTags == null && newLocalTags == null)
diff --git a/Penumbra/Mods/ModMeta.cs b/Penumbra/Mods/ModMeta.cs
index 39dd20e4..0cebcf81 100644
--- a/Penumbra/Mods/ModMeta.cs
+++ b/Penumbra/Mods/ModMeta.cs
@@ -1,5 +1,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using Penumbra.Mods.Editor;
+using Penumbra.Mods.Manager;
using Penumbra.Services;
namespace Penumbra.Mods;
@@ -28,4 +30,85 @@ public readonly struct ModMeta(Mod mod) : ISavable
jWriter.Formatting = Formatting.Indented;
jObject.WriteTo(jWriter);
}
+
+ public static ModDataChangeType Load(ModDataEditor editor, ModCreator creator, Mod mod)
+ {
+ var metaFile = editor.SaveService.FileNames.ModMetaPath(mod);
+ if (!File.Exists(metaFile))
+ {
+ Penumbra.Log.Debug($"No mod meta found for {mod.ModPath.Name}.");
+ return ModDataChangeType.Deletion;
+ }
+
+ try
+ {
+ var text = File.ReadAllText(metaFile);
+ var json = JObject.Parse(text);
+
+ var newFileVersion = json[nameof(FileVersion)]?.Value() ?? 0;
+
+ // Empty name gets checked after loading and is not allowed.
+ var newName = json[nameof(Mod.Name)]?.Value() ?? string.Empty;
+
+ var newAuthor = json[nameof(Mod.Author)]?.Value() ?? string.Empty;
+ var newDescription = json[nameof(Mod.Description)]?.Value() ?? string.Empty;
+ var newImage = json[nameof(Mod.Image)]?.Value() ?? string.Empty;
+ var newVersion = json[nameof(Mod.Version)]?.Value() ?? string.Empty;
+ var newWebsite = json[nameof(Mod.Website)]?.Value() ?? string.Empty;
+ var modTags = (json[nameof(Mod.ModTags)] as JArray)?.Values().OfType();
+
+ ModDataChangeType changes = 0;
+ if (mod.Name != newName)
+ {
+ changes |= ModDataChangeType.Name;
+ mod.Name = newName;
+ }
+
+ if (mod.Author != newAuthor)
+ {
+ changes |= ModDataChangeType.Author;
+ mod.Author = newAuthor;
+ }
+
+ if (mod.Description != newDescription)
+ {
+ changes |= ModDataChangeType.Description;
+ mod.Description = newDescription;
+ }
+
+ if (mod.Image != newImage)
+ {
+ changes |= ModDataChangeType.Image;
+ mod.Image = newImage;
+ }
+
+ if (mod.Version != newVersion)
+ {
+ changes |= ModDataChangeType.Version;
+ mod.Version = newVersion;
+ }
+
+ if (mod.Website != newWebsite)
+ {
+ changes |= ModDataChangeType.Website;
+ mod.Website = newWebsite;
+ }
+
+ if (newFileVersion != FileVersion)
+ if (ModMigration.Migrate(creator, editor.SaveService, mod, json, ref newFileVersion))
+ {
+ changes |= ModDataChangeType.Migration;
+ editor.SaveService.ImmediateSave(new ModMeta(mod));
+ }
+
+ changes |= ModLocalData.UpdateTags(mod, modTags, null);
+
+ return changes;
+ }
+ catch (Exception e)
+ {
+ Penumbra.Log.Error($"Could not load mod meta for {metaFile}:\n{e}");
+ return ModDataChangeType.Deletion;
+ }
+ }
}
diff --git a/schemas/default_mod.json b/schemas/default_mod.json
new file mode 100644
index 00000000..8f50c5db
--- /dev/null
+++ b/schemas/default_mod.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "allOf": [
+ {
+ "type": "object",
+ "properties": {
+ "Version": {
+ "description": "Mod Container version, currently unused.",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 0
+ }
+ }
+ },
+ {
+ "$ref": "structs/container.json"
+ }
+ ]
+}
diff --git a/schemas/group.json b/schemas/group.json
new file mode 100644
index 00000000..4c37b631
--- /dev/null
+++ b/schemas/group.json
@@ -0,0 +1,57 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Version": {
+ "description": "Mod Container version, currently unused.",
+ "type": "integer"
+ },
+ "Name": {
+ "description": "Name of the group.",
+ "type": "string",
+ "minLength": 1
+ },
+ "Description": {
+ "description": "Description of the group.",
+ "type": [ "string", "null" ]
+ },
+ "Image": {
+ "description": "Relative path to a preview image for the group. Unused by Penumbra, present for round-trip import/export of TexTools-generated mods.",
+ "type": ["string", "null" ]
+ },
+ "Page": {
+ "description": "TexTools page of the group. Unused by Penumbra, present for round-trip import/export of TexTools-generated mods.",
+ "type": "integer"
+ },
+ "Priority": {
+ "description": "Priority of the group. If several groups define conflicting files or manipulations, the highest priority wins.",
+ "type": "integer"
+ },
+ "Type": {
+ "description": "Group type. Single groups have one and only one of their options active at any point. Multi groups can have zero, one or many of their options active. Combining groups have n options, 2^n containers, and will have one and only one container active depending on the selected options.",
+ "enum": [ "Single", "Multi", "Imc", "Combining" ]
+ },
+ "DefaultSettings": {
+ "description": "Default configuration for the group.",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "Name",
+ "Type"
+ ],
+ "oneOf": [
+ {
+ "$ref": "structs/group_combining.json"
+ },
+ {
+ "$ref": "structs/group_imc.json"
+ },
+ {
+ "$ref": "structs/group_multi.json"
+ },
+ {
+ "$ref": "structs/group_single.json"
+ }
+ ]
+}
diff --git a/schemas/local_mod_data-v3.json b/schemas/local_mod_data-v3.json
new file mode 100644
index 00000000..bf5d1311
--- /dev/null
+++ b/schemas/local_mod_data-v3.json
@@ -0,0 +1,32 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "title": "Local Penumbra Mod Data",
+ "description": "The locally stored data for an installed mod in Penumbra",
+ "type": "object",
+ "properties": {
+ "FileVersion": {
+ "description": "Major version of the local data schema used.",
+ "type": "integer",
+ "minimum": 3,
+ "maximum": 3
+ },
+ "ImportDate": {
+ "description": "The date and time of the installation of the mod as a Unix Epoch millisecond timestamp.",
+ "type": "integer"
+ },
+ "LocalTags": {
+ "description": "User-defined local tags for the mod.",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ },
+ "uniqueItems": true
+ },
+ "Favorite": {
+ "description": "Whether the mod is favourited by the user.",
+ "type": "boolean"
+ }
+ },
+ "required": [ "FileVersion" ]
+}
diff --git a/schemas/mod_meta-v3.json b/schemas/mod_meta-v3.json
new file mode 100644
index 00000000..a926b49e
--- /dev/null
+++ b/schemas/mod_meta-v3.json
@@ -0,0 +1,52 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "title": "Penumbra Mod Metadata",
+ "description": "Metadata of a Penumbra mod.",
+ "type": "object",
+ "properties": {
+ "FileVersion": {
+ "description": "Major version of the metadata schema used.",
+ "type": "integer",
+ "minimum": 3,
+ "maximum": 3
+ },
+ "Name": {
+ "description": "Name of the mod.",
+ "type": "string",
+ "minLength": 1
+ },
+ "Author": {
+ "description": "Author of the mod.",
+ "type": [ "string", "null" ]
+ },
+ "Description": {
+ "description": "Description of the mod. Can span multiple paragraphs.",
+ "type": [ "string", "null" ]
+ },
+ "Image": {
+ "description": "Relative path to a preview image for the mod. Unused by Penumbra, present for round-trip import/export of TexTools-generated mods.",
+ "type": [ "string", "null" ]
+ },
+ "Version": {
+ "description": "Version of the mod. Can be an arbitrary string.",
+ "type": [ "string", "null" ]
+ },
+ "Website": {
+ "description": "URL of the web page of the mod.",
+ "type": [ "string", "null" ]
+ },
+ "ModTags": {
+ "description": "Author-defined tags for the mod.",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ },
+ "uniqueItems": true
+ }
+ },
+ "required": [
+ "FileVersion",
+ "Name"
+ ]
+}
diff --git a/schemas/shpk_devkit.json b/schemas/shpk_devkit.json
new file mode 100644
index 00000000..f03fbb05
--- /dev/null
+++ b/schemas/shpk_devkit.json
@@ -0,0 +1,499 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "ShaderKeys": {
+ "type": "object",
+ "patternProperties": {
+ "^\\d+$": {
+ "$ref": "#/$defs/ShaderKey"
+ }
+ },
+ "additionalProperties": false
+ },
+ "Comment": {
+ "$ref": "#/$defs/MayVary"
+ },
+ "Samplers": {
+ "type": "object",
+ "patternProperties": {
+ "^\\d+$": {
+ "$ref": "#/$defs/MayVary"
+ }
+ },
+ "additionalProperties": false
+ },
+ "Constants": {
+ "type": "object",
+ "patternProperties": {
+ "^\\d+$": {
+ "$ref": "#/$defs/MayVary"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false,
+ "$defs": {
+ "ShaderKeyValue": {
+ "type": "object",
+ "properties": {
+ "Label": {
+ "type": "string"
+ },
+ "Description": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "ShaderKey": {
+ "type": "object",
+ "properties": {
+ "Label": {
+ "type": "string"
+ },
+ "Description": {
+ "type": "string"
+ },
+ "Values": {
+ "type": "object",
+ "patternProperties": {
+ "^\\d+$": {
+ "$ref": "#/$defs/ShaderKeyValue"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+ },
+ "Varying": {
+ "type": "object",
+ "properties": {
+ "Vary": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/LaxInteger"
+ }
+ },
+ "Selectors": {
+ "description": "Keys are Σ 31^i shaderKey(Vary[i]).",
+ "type": "object",
+ "patternProperties": {
+ "^\\d+$": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false
+ },
+ "Items": {
+ "type": "array",
+ "$comment": "Varying is defined by constraining this array's items to T"
+ }
+ },
+ "required": [
+ "Vary",
+ "Selectors",
+ "Items"
+ ],
+ "additionalProperties": false
+ },
+ "MayVary": {
+ "oneOf": [
+ {
+ "type": ["string", "null"]
+ }, {
+ "allOf": [
+ {
+ "$ref": "#/$defs/Varying"
+ }, {
+ "type": "object",
+ "properties": {
+ "Items": {
+ "type": "array",
+ "items": {
+ "type": ["string", "null"]
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "Sampler": {
+ "type": ["object", "null"],
+ "properties": {
+ "Label": {
+ "type": "string"
+ },
+ "Description": {
+ "type": "string"
+ },
+ "DefaultTexture": {
+ "type": "string",
+ "pattern": "^[^/\\\\][^\\\\]*$"
+ }
+ },
+ "additionalProperties": false
+ },
+ "MayVary": {
+ "oneOf": [
+ {
+ "$ref": "#/$defs/Sampler"
+ }, {
+ "allOf": [
+ {
+ "$ref": "#/$defs/Varying"
+ }, {
+ "type": "object",
+ "properties": {
+ "Items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/Sampler"
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "ConstantBase": {
+ "type": "object",
+ "properties": {
+ "Offset": {
+ "description": "Defaults to 0. Mutually exclusive with ByteOffset.",
+ "type": "integer",
+ "minimum": 0
+ },
+ "Length": {
+ "description": "Defaults to up to the end. Mutually exclusive with ByteLength.",
+ "type": "integer",
+ "minimum": 0
+ },
+ "ByteOffset": {
+ "description": "Defaults to 0. Mutually exclusive with Offset.",
+ "type": "integer",
+ "minimum": 0
+ },
+ "ByteLength": {
+ "description": "Defaults to up to the end. Mutually exclusive with Length.",
+ "type": "integer",
+ "minimum": 0
+ },
+ "Group": {
+ "description": "Defaults to \"Further Constants\".",
+ "type": "string"
+ },
+ "Label": {
+ "type": "string"
+ },
+ "Description": {
+ "description": "Defaults to empty.",
+ "type": "string"
+ },
+ "Type": {
+ "description": "Defaults to Float.",
+ "enum": ["Hidden", "Float", "Integer", "Color", "Enum", "Int32", "Int32Enum", "Int8", "Int8Enum", "Int16", "Int16Enum", "Int64", "Int64Enum", "Half", "Double", "TileIndex", "SphereMapIndex"]
+ }
+ },
+ "not": {
+ "anyOf": [
+ {
+ "required": ["Offset", "ByteOffset"]
+ }, {
+ "required": ["Length", "ByteLenngth"]
+ }
+ ]
+ }
+ },
+ "HiddenConstant": {
+ "type": "object",
+ "properties": {
+ "Type": {
+ "const": "Hidden"
+ }
+ },
+ "required": [
+ "Type"
+ ],
+ "allOf": [
+ {
+ "$ref": "#/$defs/ConstantBase"
+ }
+ ],
+ "unevaluatedProperties": false
+ },
+ "FloatConstant": {
+ "type": "object",
+ "properties": {
+ "Type": {
+ "enum": ["Float", "Half", "Double"]
+ },
+ "Minimum": {
+ "description": "Defaults to -∞.",
+ "type": "number"
+ },
+ "Maximum": {
+ "description": "Defaults to ∞.",
+ "type": "number"
+ },
+ "Speed": {
+ "description": "Defaults to 0.1.",
+ "type": "number",
+ "minimum": 0
+ },
+ "RelativeSpeed": {
+ "description": "Defaults to 0.",
+ "type": "number",
+ "minimum": 0
+ },
+ "Exponent": {
+ "description": "Defaults to 1. Uses an odd pseudo-power function, f(x) = sgn(x) |x|^n.",
+ "type": "number"
+ },
+ "Factor": {
+ "description": "Defaults to 1.",
+ "type": "number"
+ },
+ "Bias": {
+ "description": "Defaults to 0.",
+ "type": "number"
+ },
+ "Precision": {
+ "description": "Defaults to 3.",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 9
+ },
+ "Slider": {
+ "description": "Defaults to true. Drag has priority over this.",
+ "type": "boolean"
+ },
+ "Drag": {
+ "description": "Defaults to true. Has priority over Slider.",
+ "type": "boolean"
+ },
+ "Unit": {
+ "description": "Defaults to no unit.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "Label"
+ ],
+ "allOf": [
+ {
+ "$ref": "#/$defs/ConstantBase"
+ }
+ ],
+ "unevaluatedProperties": false
+ },
+ "IntConstant": {
+ "type": "object",
+ "properties": {
+ "Type": {
+ "enum": ["Integer", "Int32", "Int8", "Int16", "Int64"]
+ },
+ "Minimum": {
+ "description": "Defaults to -2^N, N being the explicit integer width specified in the type, or 32 for Int.",
+ "type": "number"
+ },
+ "Maximum": {
+ "description": "Defaults to 2^N - 1, N being the explicit integer width specified in the type, or 32 for Int.",
+ "type": "number"
+ },
+ "Speed": {
+ "description": "Defaults to 0.25.",
+ "type": "number",
+ "minimum": 0
+ },
+ "RelativeSpeed": {
+ "description": "Defaults to 0.",
+ "type": "number",
+ "minimum": 0
+ },
+ "Factor": {
+ "description": "Defaults to 1.",
+ "type": "number"
+ },
+ "Bias": {
+ "description": "Defaults to 0.",
+ "type": "number"
+ },
+ "Hex": {
+ "description": "Defaults to false. Has priority over Slider and Drag.",
+ "type": "boolean"
+ },
+ "Slider": {
+ "description": "Defaults to true. Hex and Drag have priority over this.",
+ "type": "boolean"
+ },
+ "Drag": {
+ "description": "Defaults to true. Has priority over Slider, but Hex has priority over this.",
+ "type": "boolean"
+ },
+ "Unit": {
+ "description": "Defaults to no unit.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "Label",
+ "Type"
+ ],
+ "allOf": [
+ {
+ "$ref": "#/$defs/ConstantBase"
+ }
+ ],
+ "unevaluatedProperties": false
+ },
+ "ColorConstant": {
+ "type": "object",
+ "properties": {
+ "Type": {
+ "const": "Color"
+ },
+ "SquaredRgb": {
+ "description": "Defaults to false. Uses an odd pseudo-square function, f(x) = sgn(x) |x|².",
+ "type": "boolean"
+ },
+ "Clamped": {
+ "description": "Defaults to false.",
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "Label",
+ "Type"
+ ],
+ "allOf": [
+ {
+ "$ref": "#/$defs/ConstantBase"
+ }
+ ],
+ "unevaluatedProperties": false
+ },
+ "EnumValue": {
+ "type": "object",
+ "properties": {
+ "Label": {
+ "type": "string"
+ },
+ "Description": {
+ "type": "string"
+ },
+ "Value": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "Label",
+ "Value"
+ ],
+ "additionalProperties": false
+ },
+ "EnumConstant": {
+ "type": "object",
+ "properties": {
+ "Type": {
+ "enum": ["Enum", "Int32Enum", "Int8Enum", "Int16Enum", "Int64Enum"]
+ },
+ "Values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/EnumValue"
+ }
+ }
+ },
+ "required": [
+ "Label",
+ "Type"
+ ],
+ "allOf": [
+ {
+ "$ref": "#/$defs/ConstantBase"
+ }
+ ],
+ "unevaluatedProperties": false
+ },
+ "SpecialConstant": {
+ "type": "object",
+ "properties": {
+ "Type": {
+ "enum": ["TileIndex", "SphereMapIndex"]
+ }
+ },
+ "required": [
+ "Label",
+ "Type"
+ ],
+ "allOf": [
+ {
+ "$ref": "#/$defs/ConstantBase"
+ }
+ ],
+ "unevaluatedProperties": false
+ },
+ "Constant": {
+ "oneOf": [
+ {
+ "$ref": "#/$defs/HiddenConstant"
+ }, {
+ "$ref": "#/$defs/FloatConstant"
+ }, {
+ "$ref": "#/$defs/IntConstant"
+ }, {
+ "$ref": "#/$defs/ColorConstant"
+ }, {
+ "$ref": "#/$defs/EnumConstant"
+ }, {
+ "$ref": "#/$defs/SpecialConstant"
+ }
+ ]
+ },
+ "MayVary": {
+ "oneOf": [
+ {
+ "type": ["array", "null"],
+ "items": {
+ "$ref": "#/$defs/Constant"
+ }
+ }, {
+ "allOf": [
+ {
+ "$ref": "#/$defs/Varying"
+ }, {
+ "type": "object",
+ "properties": {
+ "Items": {
+ "type": "array",
+ "items": {
+ "type": ["array", "null"],
+ "items": {
+ "$ref": "#/$defs/Constant"
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "LaxInteger": {
+ "oneOf": [
+ {
+ "type": "integer"
+ }, {
+ "type": "string",
+ "pattern": "^\\d+$"
+ }
+ ]
+ }
+ }
+}
diff --git a/schemas/structs/container.json b/schemas/structs/container.json
new file mode 100644
index 00000000..74db4a23
--- /dev/null
+++ b/schemas/structs/container.json
@@ -0,0 +1,34 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Files": {
+ "description": "File redirections in this container. Keys are game paths, values are relative file paths.",
+ "type": [ "object", "null" ],
+ "patternProperties": {
+ "^[^/\\\\.:?][^\\\\?:]+$": {
+ "type": "string",
+ "pattern": "^[^/\\\\.:?][^?:]+$"
+ }
+ },
+ "additionalProperties": false
+ },
+ "FileSwaps": {
+ "description": "File swaps in this container. Keys are original game paths, values are actual game paths.",
+ "type": [ "object", "null" ],
+ "patternProperties": {
+ "^[^/\\\\.?:][^\\\\?:]+$": {
+ "type": "string",
+ "pattern": "^[^/\\\\.:?][^?:]+$"
+ }
+ },
+ "additionalProperties": false
+ },
+ "Manipulations": {
+ "type": [ "array", "null" ],
+ "items": {
+ "$ref": "manipulation.json"
+ }
+ }
+ }
+}
diff --git a/schemas/structs/group_combining.json b/schemas/structs/group_combining.json
new file mode 100644
index 00000000..e42edcb8
--- /dev/null
+++ b/schemas/structs/group_combining.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "properties": {
+ "Type": {
+ "const": "Combining"
+ },
+ "Options": {
+ "type": "array",
+ "items": {
+ "$ref": "option.json"
+ }
+ },
+ "Containers": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "$ref": "container.json"
+ },
+ {
+ "properties": {
+ "Name": {
+ "type": [ "string", "null" ]
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/schemas/structs/group_imc.json b/schemas/structs/group_imc.json
new file mode 100644
index 00000000..48a04bd9
--- /dev/null
+++ b/schemas/structs/group_imc.json
@@ -0,0 +1,50 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "properties": {
+ "Type": {
+ "const": "Imc"
+ },
+ "AllVariants": {
+ "type": "boolean"
+ },
+ "OnlyAttributes": {
+ "type": "boolean"
+ },
+ "Identifier": {
+ "$ref": "meta_imc.json#ImcIdentifier"
+ },
+ "DefaultEntry": {
+ "$ref": "meta_imc.json#ImcEntry"
+ },
+ "Options": {
+ "type": "array",
+ "items": {
+ "$ref": "option.json",
+ "oneOf": [
+ {
+ "properties": {
+ "AttributeMask": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 1023
+ }
+ },
+ "required": [
+ "AttributeMask"
+ ]
+ },
+ {
+ "properties": {
+ "IsDisableSubMod": {
+ "const": true
+ }
+ },
+ "required": [
+ "IsDisableSubMod"
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/schemas/structs/group_multi.json b/schemas/structs/group_multi.json
new file mode 100644
index 00000000..ca7d4dfa
--- /dev/null
+++ b/schemas/structs/group_multi.json
@@ -0,0 +1,32 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Type": {
+ "const": "Multi"
+ },
+ "Options": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "$ref": "option.json"
+ },
+ {
+ "$ref": "container.json"
+ },
+ {
+ "properties": {
+ "Priority": {
+ "type": "integer"
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}
+
+
+
diff --git a/schemas/structs/group_single.json b/schemas/structs/group_single.json
new file mode 100644
index 00000000..24cda88d
--- /dev/null
+++ b/schemas/structs/group_single.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Type": {
+ "const": "Single"
+ },
+ "Options": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "$ref": "option.json"
+ },
+ {
+ "$ref": "container.json"
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/schemas/structs/manipulation.json b/schemas/structs/manipulation.json
new file mode 100644
index 00000000..4a41dbe2
--- /dev/null
+++ b/schemas/structs/manipulation.json
@@ -0,0 +1,95 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Type": {
+ "enum": [ "Unknown", "Imc", "Eqdp", "Eqp", "Est", "Gmp", "Rsp", "GlobalEqp", "Atch" ]
+ },
+ "Manipulation": {
+ "type": "object"
+ }
+ },
+ "required": [ "Type", "Manipulation" ],
+ "oneOf": [
+ {
+ "properties": {
+ "Type": {
+ "const": "Imc"
+ },
+ "Manipulation": {
+ "$ref": "meta_imc.json"
+ }
+ }
+ },
+ {
+ "properties": {
+ "Type": {
+ "const": "Eqdp"
+ },
+ "Manipulation": {
+ "$ref": "meta_eqdp.json"
+ }
+ }
+ },
+ {
+ "properties": {
+ "Type": {
+ "const": "Eqp"
+ },
+ "Manipulation": {
+ "$ref": "meta_eqp.json"
+ }
+ }
+ },
+ {
+ "properties": {
+ "Type": {
+ "const": "Est"
+ },
+ "Manipulation": {
+ "$ref": "meta_est.json"
+ }
+ }
+ },
+ {
+ "properties": {
+ "Type": {
+ "const": "Gmp"
+ },
+ "Manipulation": {
+ "$ref": "meta_gmp.json"
+ }
+ }
+ },
+ {
+ "properties": {
+ "Type": {
+ "const": "Rsp"
+ },
+ "Manipulation": {
+ "$ref": "meta_rsp.json"
+ }
+ }
+ },
+ {
+ "properties": {
+ "Type": {
+ "const": "GlobalEqp"
+ },
+ "Manipulation": {
+ "$ref": "meta_geqp.json"
+ }
+ }
+ },
+ {
+ "properties": {
+ "Type": {
+ "const": "Atch"
+ },
+ "Manipulation": {
+ "$ref": "meta_atch.json"
+ }
+ }
+ }
+ ]
+}
diff --git a/schemas/structs/meta_atch.json b/schemas/structs/meta_atch.json
new file mode 100644
index 00000000..3c9cbef5
--- /dev/null
+++ b/schemas/structs/meta_atch.json
@@ -0,0 +1,67 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Entry": {
+ "type": "object",
+ "properties": {
+ "Bone": {
+ "type": "string",
+ "maxLength": 34
+ },
+ "Scale": {
+ "type": "number"
+ },
+ "OffsetX": {
+ "type": "number"
+ },
+ "OffsetY": {
+ "type": "number"
+ },
+ "OffsetZ": {
+ "type": "number"
+ },
+ "RotationX": {
+ "type": "number"
+ },
+ "RotationY": {
+ "type": "number"
+ },
+ "RotationZ": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "Bone",
+ "Scale",
+ "OffsetX",
+ "OffsetY",
+ "OffsetZ",
+ "RotationX",
+ "RotationY",
+ "RotationZ"
+ ]
+ },
+ "Gender": {
+ "$ref": "meta_enums.json#Gender"
+ },
+ "Race": {
+ "$ref": "meta_enums.json#ModelRace"
+ },
+ "Type": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 4
+ },
+ "Index": {
+ "$ref": "meta_enums.json#U16"
+ }
+ },
+ "required": [
+ "Entry",
+ "Gender",
+ "Race",
+ "Type",
+ "Index"
+ ]
+}
diff --git a/schemas/structs/meta_enums.json b/schemas/structs/meta_enums.json
new file mode 100644
index 00000000..747da849
--- /dev/null
+++ b/schemas/structs/meta_enums.json
@@ -0,0 +1,57 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$defs": {
+ "EquipSlot": {
+ "$anchor": "EquipSlot",
+ "enum": [ "Unknown", "MainHand", "OffHand", "Head", "Body", "Hands", "Belt", "Legs", "Feet", "Ears", "Neck", "Wrists", "RFinger", "BothHand", "LFinger", "HeadBody", "BodyHandsLegsFeet", "SoulCrystal", "LegsFeet", "FullBody", "BodyHands", "BodyLegsFeet", "ChestHands", "Nothing", "All" ]
+ },
+ "Gender": {
+ "$anchor": "Gender",
+ "enum": [ "Unknown", "Male", "Female", "MaleNpc", "FemaleNpc" ]
+ },
+ "ModelRace": {
+ "$anchor": "ModelRace",
+ "enum": [ "Unknown", "Midlander", "Highlander", "Elezen", "Lalafell", "Miqote", "Roegadyn", "AuRa", "Hrothgar", "Viera" ]
+ },
+ "ObjectType": {
+ "$anchor": "ObjectType",
+ "enum": [ "Unknown", "Vfx", "DemiHuman", "Accessory", "World", "Housing", "Monster", "Icon", "LoadingScreen", "Map", "Interface", "Equipment", "Character", "Weapon", "Font" ]
+ },
+ "BodySlot": {
+ "$anchor": "BodySlot",
+ "enum": [ "Unknown", "Hair", "Face", "Tail", "Body", "Zear" ]
+ },
+ "SubRace": {
+ "$anchor": "SubRace",
+ "enum": [ "Unknown", "Midlander", "Highlander", "Wildwood", "Duskwight", "Plainsfolk", "Dunesfolk", "SeekerOfTheSun", "KeeperOfTheMoon", "Seawolf", "Hellsguard", "Raen", "Xaela", "Helion", "Lost", "Rava", "Veena" ]
+ },
+ "U8": {
+ "$anchor": "U8",
+ "oneOf": [
+ {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
+ {
+ "type": "string",
+ "pattern": "^0*(1[0-9][0-9]|[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$"
+ }
+ ]
+ },
+ "U16": {
+ "$anchor": "U16",
+ "oneOf": [
+ {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 65535
+ },
+ {
+ "type": "string",
+ "pattern": "^0*([1-5][0-9]{4}|[0-9]{0,4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$"
+ }
+ ]
+ }
+ }
+}
diff --git a/schemas/structs/meta_eqdp.json b/schemas/structs/meta_eqdp.json
new file mode 100644
index 00000000..f27606b9
--- /dev/null
+++ b/schemas/structs/meta_eqdp.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Entry": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 1023
+ },
+ "Gender": {
+ "$ref": "meta_enums.json#Gender"
+ },
+ "Race": {
+ "$ref": "meta_enums.json#ModelRace"
+ },
+ "SetId": {
+ "$ref": "meta_enums.json#U16"
+ },
+ "Slot": {
+ "$ref": "meta_enums.json#EquipSlot"
+ }
+ },
+ "required": [
+ "Entry",
+ "Gender",
+ "Race",
+ "SetId",
+ "Slot"
+ ]
+}
diff --git a/schemas/structs/meta_eqp.json b/schemas/structs/meta_eqp.json
new file mode 100644
index 00000000..c829d7a7
--- /dev/null
+++ b/schemas/structs/meta_eqp.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Entry": {
+ "type": "integer"
+ },
+ "SetId": {
+ "$ref": "meta_enums.json#U16"
+ },
+ "Slot": {
+ "$ref": "meta_enums.json#EquipSlot"
+ }
+ },
+ "required": [
+ "Entry",
+ "SetId",
+ "Slot"
+ ]
+}
diff --git a/schemas/structs/meta_est.json b/schemas/structs/meta_est.json
new file mode 100644
index 00000000..22bce12b
--- /dev/null
+++ b/schemas/structs/meta_est.json
@@ -0,0 +1,28 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Entry": {
+ "$ref": "meta_enums.json#U16"
+ },
+ "Gender": {
+ "$ref": "meta_enums.json#Gender"
+ },
+ "Race": {
+ "$ref": "meta_enums.json#ModelRace"
+ },
+ "SetId": {
+ "$ref": "meta_enums.json#U16"
+ },
+ "Slot": {
+ "enum": [ "Hair", "Face", "Body", "Head" ]
+ }
+ },
+ "required": [
+ "Entry",
+ "Gender",
+ "Race",
+ "SetId",
+ "Slot"
+ ]
+}
diff --git a/schemas/structs/meta_geqp.json b/schemas/structs/meta_geqp.json
new file mode 100644
index 00000000..3d4908f9
--- /dev/null
+++ b/schemas/structs/meta_geqp.json
@@ -0,0 +1,40 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Condition": {
+ "$ref": "meta_enums.json#U16"
+ },
+ "Type": {
+ "enum": [ "DoNotHideEarrings", "DoNotHideNecklace", "DoNotHideBracelets", "DoNotHideRingR", "DoNotHideRingL", "DoNotHideHrothgarHats", "DoNotHideVieraHats" ]
+ }
+ },
+ "required": [ "Type" ],
+ "oneOf": [
+ {
+ "properties": {
+ "Type": {
+ "const": [ "DoNotHideHrothgarHats", "DoNotHideVieraHats" ]
+ },
+ "Condition": {
+ "const": 0
+ }
+ }
+ },
+ {
+ "properties": {
+ "Type": {
+ "const": [ "DoNotHideHrothgarHats", "DoNotHideVieraHats" ]
+ }
+ }
+ },
+ {
+ "properties": {
+ "Type": {
+ "const": [ "DoNotHideEarrings", "DoNotHideNecklace", "DoNotHideBracelets", "DoNotHideRingR", "DoNotHideRingL" ]
+ },
+ "Condition": {}
+ }
+ }
+ ]
+}
diff --git a/schemas/structs/meta_gmp.json b/schemas/structs/meta_gmp.json
new file mode 100644
index 00000000..bf1fb1df
--- /dev/null
+++ b/schemas/structs/meta_gmp.json
@@ -0,0 +1,59 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Entry": {
+ "type": "object",
+ "properties": {
+ "Enabled": {
+ "type": "boolean"
+ },
+ "Animated": {
+ "type": "boolean"
+ },
+ "RotationA": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 1023
+ },
+ "RotationB": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 1023
+ },
+ "RotationC": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 1023
+ },
+ "UnknownA": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 15
+ },
+ "UnknownB": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 15
+ }
+ },
+ "required": [
+ "Enabled",
+ "Animated",
+ "RotationA",
+ "RotationB",
+ "RotationC",
+ "UnknownA",
+ "UnknownB"
+ ],
+ "additionalProperties": false
+ },
+ "SetId": {
+ "$ref": "meta_enums.json#U16"
+ }
+ },
+ "required": [
+ "Entry",
+ "SetId"
+ ]
+}
diff --git a/schemas/structs/meta_imc.json b/schemas/structs/meta_imc.json
new file mode 100644
index 00000000..aa9a4fca
--- /dev/null
+++ b/schemas/structs/meta_imc.json
@@ -0,0 +1,87 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Entry": {
+ "$ref": "#ImcEntry"
+ }
+ },
+ "required": [
+ "Entry"
+ ],
+ "allOf": [
+ {
+ "$ref": "#ImcIdentifier"
+ }
+ ],
+ "$defs": {
+ "ImcIdentifier": {
+ "type": "object",
+ "properties": {
+ "PrimaryId": {
+ "$ref": "meta_enums.json#U16"
+ },
+ "SecondaryId": {
+ "$ref": "meta_enums.json#U16"
+ },
+ "Variant": {
+ "$ref": "meta_enums.json#U8"
+ },
+ "ObjectType": {
+ "$ref": "meta_enums.json#ObjectType"
+ },
+ "EquipSlot": {
+ "$ref": "meta_enums.json#EquipSlot"
+ },
+ "BodySlot": {
+ "$ref": "meta_enums.json#BodySlot"
+ }
+ },
+ "$anchor": "ImcIdentifier",
+ "required": [
+ "PrimaryId",
+ "SecondaryId",
+ "Variant",
+ "ObjectType",
+ "EquipSlot",
+ "BodySlot"
+ ]
+ },
+ "ImcEntry": {
+ "type": "object",
+ "properties": {
+ "MaterialId": {
+ "$ref": "meta_enums.json#U8"
+ },
+ "DecalId": {
+ "$ref": "meta_enums.json#U8"
+ },
+ "VfxId": {
+ "$ref": "meta_enums.json#U8"
+ },
+ "MaterialAnimationId": {
+ "$ref": "meta_enums.json#U8"
+ },
+ "AttributeMask": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 1023
+ },
+ "SoundId": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 63
+ }
+ },
+ "$anchor": "ImcEntry",
+ "required": [
+ "MaterialId",
+ "DecalId",
+ "VfxId",
+ "MaterialAnimationId",
+ "AttributeMask",
+ "SoundId"
+ ]
+ }
+ }
+}
diff --git a/schemas/structs/meta_rsp.json b/schemas/structs/meta_rsp.json
new file mode 100644
index 00000000..3354281b
--- /dev/null
+++ b/schemas/structs/meta_rsp.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Entry": {
+ "type": "number"
+ },
+ "SubRace": {
+ "$ref": "meta_enums.json#SubRace"
+ },
+ "Attribute": {
+ "enum": [ "MaleMinSize", "MaleMaxSize", "MaleMinTail", "MaleMaxTail", "FemaleMinSize", "FemaleMaxSize", "FemaleMinTail", "FemaleMaxTail", "BustMinX", "BustMinY", "BustMinZ", "BustMaxX", "BustMaxY", "BustMaxZ" ]
+ }
+ },
+ "required": [
+ "Entry",
+ "SubRace",
+ "Attribute"
+ ]
+}
diff --git a/schemas/structs/option.json b/schemas/structs/option.json
new file mode 100644
index 00000000..c45ccfdb
--- /dev/null
+++ b/schemas/structs/option.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "Name": {
+ "description": "Name of the option.",
+ "type": "string",
+ "minLength": 1
+ },
+ "Description": {
+ "description": "Description of the option.",
+ "type": [ "string", "null" ]
+ },
+ "Priority": {
+ "description": "Priority of the option. If several enabled options within the group define conflicting files or manipulations, the highest priority wins.",
+ "type": "integer"
+ },
+ "Image": {
+ "description": "Unused by Penumbra.",
+ "type": [ "string", "null" ]
+ }
+ },
+ "required": [ "Name" ]
+}