Merge branch 'json-schema'

This commit is contained in:
Ottermandias 2025-01-20 15:13:20 +01:00
commit 7d75c7d7a5
27 changed files with 1533 additions and 145 deletions

@ -1 +1 @@
Subproject commit 78ce195c171d7bce4ad9df105f1f95cce9bf1150 Subproject commit c525072299d5febd2bb638ab229060b0073ba6a6

View file

@ -24,6 +24,34 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.CrashHandler", "Penumbra.CrashHandler\Penumbra.CrashHandler.csproj", "{EE834491-A98F-4395-BE0D-6861AE5AD953}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.CrashHandler", "Penumbra.CrashHandler\Penumbra.CrashHandler.csproj", "{EE834491-A98F-4395-BE0D-6861AE5AD953}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -58,6 +86,10 @@ Global
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection 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 GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF} SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
EndGlobalSection EndGlobalSection

View file

@ -27,6 +27,9 @@ public enum ModDataChangeType : ushort
public class ModDataEditor(SaveService saveService, CommunicatorService communicatorService) : IService public class ModDataEditor(SaveService saveService, CommunicatorService communicatorService) : IService
{ {
public SaveService SaveService
=> saveService;
/// <summary> Create the file containing the meta information about a mod from scratch. </summary> /// <summary> Create the file containing the meta information about a mod from scratch. </summary>
public void CreateMeta(DirectoryInfo directory, string? name, string? author, string? description, string? version, public void CreateMeta(DirectoryInfo directory, string? name, string? author, string? description, string? version,
string? website) string? website)
@ -40,148 +43,6 @@ public class ModDataEditor(SaveService saveService, CommunicatorService communic
saveService.ImmediateSaveSync(new ModMeta(mod)); saveService.ImmediateSaveSync(new ModMeta(mod));
} }
public ModDataChangeType LoadLocalData(Mod mod)
{
var dataFile = saveService.FileNames.LocalDataFile(mod);
var importDate = 0L;
var localTags = Enumerable.Empty<string>();
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<long>() ?? importDate;
favorite = json[nameof(Mod.Favorite)]?.Value<bool>() ?? favorite;
note = json[nameof(Mod.Note)]?.Value<string>() ?? note;
localTags = (json[nameof(Mod.LocalTags)] as JArray)?.Values<string>().OfType<string>() ?? 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>() ?? string.Empty;
var newAuthor = json[nameof(Mod.Author)]?.Value<string>() ?? string.Empty;
var newDescription = json[nameof(Mod.Description)]?.Value<string>() ?? string.Empty;
var newImage = json[nameof(Mod.Image)]?.Value<string>() ?? string.Empty;
var newVersion = json[nameof(Mod.Version)]?.Value<string>() ?? string.Empty;
var newWebsite = json[nameof(Mod.Website)]?.Value<string>() ?? string.Empty;
var newFileVersion = json[nameof(ModMeta.FileVersion)]?.Value<uint>() ?? 0;
var importDate = json[nameof(Mod.ImportDate)]?.Value<long>();
var modTags = (json[nameof(Mod.ModTags)] as JArray)?.Values<string>().OfType<string>();
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) public void ChangeModName(Mod mod, string newName)
{ {
if (mod.Name.Text == newName) if (mod.Name.Text == newName)

View file

@ -72,11 +72,11 @@ public partial class ModCreator(
if (!Directory.Exists(mod.ModPath.FullName)) if (!Directory.Exists(mod.ModPath.FullName))
return false; return false;
modDataChange = dataEditor.LoadMeta(this, mod); modDataChange = ModMeta.Load(dataEditor, this, mod);
if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0) if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0)
return false; return false;
modDataChange |= dataEditor.LoadLocalData(mod); modDataChange |= ModLocalData.Load(dataEditor, mod);
LoadDefaultOption(mod); LoadDefaultOption(mod);
LoadAllGroups(mod); LoadAllGroups(mod);
if (incorporateMetaChanges) if (incorporateMetaChanges)

View file

@ -27,6 +27,63 @@ public readonly struct ModLocalData(Mod mod) : ISavable
jObject.WriteTo(jWriter); 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<string>();
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<long>() ?? importDate;
favorite = json[nameof(Mod.Favorite)]?.Value<bool>() ?? favorite;
note = json[nameof(Mod.Note)]?.Value<string>() ?? note;
localTags = (json[nameof(Mod.LocalTags)] as JArray)?.Values<string>().OfType<string>() ?? 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<string>? newModTags, IEnumerable<string>? newLocalTags) internal static ModDataChangeType UpdateTags(Mod mod, IEnumerable<string>? newModTags, IEnumerable<string>? newLocalTags)
{ {
if (newModTags == null && newLocalTags == null) if (newModTags == null && newLocalTags == null)

View file

@ -1,5 +1,7 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Manager;
using Penumbra.Services; using Penumbra.Services;
namespace Penumbra.Mods; namespace Penumbra.Mods;
@ -28,4 +30,85 @@ public readonly struct ModMeta(Mod mod) : ISavable
jWriter.Formatting = Formatting.Indented; jWriter.Formatting = Formatting.Indented;
jObject.WriteTo(jWriter); 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<uint>() ?? 0;
// Empty name gets checked after loading and is not allowed.
var newName = json[nameof(Mod.Name)]?.Value<string>() ?? string.Empty;
var newAuthor = json[nameof(Mod.Author)]?.Value<string>() ?? string.Empty;
var newDescription = json[nameof(Mod.Description)]?.Value<string>() ?? string.Empty;
var newImage = json[nameof(Mod.Image)]?.Value<string>() ?? string.Empty;
var newVersion = json[nameof(Mod.Version)]?.Value<string>() ?? string.Empty;
var newWebsite = json[nameof(Mod.Website)]?.Value<string>() ?? string.Empty;
var modTags = (json[nameof(Mod.ModTags)] as JArray)?.Values<string>().OfType<string>();
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;
}
}
} }

19
schemas/default_mod.json Normal file
View file

@ -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"
}
]
}

57
schemas/group.json Normal file
View file

@ -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"
}
]
}

View file

@ -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" ]
}

52
schemas/mod_meta-v3.json Normal file
View file

@ -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"
]
}

499
schemas/shpk_devkit.json Normal file
View file

@ -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<string>"
},
"Samplers": {
"type": "object",
"patternProperties": {
"^\\d+$": {
"$ref": "#/$defs/MayVary<Sampler>"
}
},
"additionalProperties": false
},
"Constants": {
"type": "object",
"patternProperties": {
"^\\d+$": {
"$ref": "#/$defs/MayVary<Constant[]>"
}
},
"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<any>": {
"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<T> is defined by constraining this array's items to T"
}
},
"required": [
"Vary",
"Selectors",
"Items"
],
"additionalProperties": false
},
"MayVary<string>": {
"oneOf": [
{
"type": ["string", "null"]
}, {
"allOf": [
{
"$ref": "#/$defs/Varying<any>"
}, {
"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<Sampler>": {
"oneOf": [
{
"$ref": "#/$defs/Sampler"
}, {
"allOf": [
{
"$ref": "#/$defs/Varying<any>"
}, {
"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<Constant[]>": {
"oneOf": [
{
"type": ["array", "null"],
"items": {
"$ref": "#/$defs/Constant"
}
}, {
"allOf": [
{
"$ref": "#/$defs/Varying<any>"
}, {
"type": "object",
"properties": {
"Items": {
"type": "array",
"items": {
"type": ["array", "null"],
"items": {
"$ref": "#/$defs/Constant"
}
}
}
}
}
]
}
]
},
"LaxInteger": {
"oneOf": [
{
"type": "integer"
}, {
"type": "string",
"pattern": "^\\d+$"
}
]
}
}
}

View file

@ -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"
}
}
}
}

View file

@ -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" ]
}
}
}
]
}
}
}
}

View file

@ -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"
]
}
]
}
}
}
}

View file

@ -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"
}
}
}
]
}
}
}
}

View file

@ -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"
}
]
}
}
}
}

View file

@ -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"
}
}
}
]
}

View file

@ -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"
]
}

View file

@ -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])$"
}
]
}
}
}

View file

@ -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"
]
}

View file

@ -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"
]
}

View file

@ -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"
]
}

View file

@ -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": {}
}
}
]
}

View file

@ -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"
]
}

View file

@ -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"
]
}
}
}

View file

@ -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"
]
}

View file

@ -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" ]
}