diff --git a/Winch.Examples/ExampleItems/DefaultConfig.json b/Winch.Examples/ExampleItems/DefaultConfig.json
new file mode 100644
index 00000000..83f50daa
--- /dev/null
+++ b/Winch.Examples/ExampleItems/DefaultConfig.json
@@ -0,0 +1,45 @@
+{
+ "Toggle": {
+ "type": "toggle",
+ "title": "exampleconfig.toggle.title",
+ "tooltip": "exampleconfig.toggle.tooltip",
+ "value": true
+ },
+ "Text": {
+ "type": "text",
+ "title": "exampleconfig.text.title",
+ "tooltip": "exampleconfig.text.tooltip",
+ "value": "Hello"
+ },
+ "Integer": {
+ "type": "integer",
+ "title": "exampleconfig.integer.title",
+ "tooltip": "exampleconfig.integer.tooltip",
+ "value": 22
+ },
+ "Decimal": {
+ "type": "decimal",
+ "title": "exampleconfig.decimal.title",
+ "tooltip": "exampleconfig.decimal.tooltip",
+ "value": 1.01
+ },
+ "Slider": {
+ "type": "slider",
+ "title": "exampleconfig.slider.title",
+ "tooltip": "exampleconfig.slider.tooltip",
+ "min": 10,
+ "max": 20,
+ "value": 15
+ },
+ "Dropdown": {
+ "type": "dropdown",
+ "title": "exampleconfig.dropdown.title",
+ "tooltip": "exampleconfig.dropdown.tooltip",
+ "options": [
+ "exampleconfig.dropdown.option.one",
+ "exampleconfig.dropdown.option.two",
+ "exampleconfig.dropdown.option.three"
+ ],
+ "value": "exampleconfig.dropdown.option.two"
+ }
+}
diff --git a/Winch.Examples/ExampleItems/ExampleItems.csproj b/Winch.Examples/ExampleItems/ExampleItems.csproj
index 9b745d83..f6b841bb 100644
--- a/Winch.Examples/ExampleItems/ExampleItems.csproj
+++ b/Winch.Examples/ExampleItems/ExampleItems.csproj
@@ -44,6 +44,9 @@
Always
+
+ Always
+
Always
diff --git a/Winch/Assets/Localization/en.json b/Winch/Assets/Localization/en.json
new file mode 100644
index 00000000..afe5e9a4
--- /dev/null
+++ b/Winch/Assets/Localization/en.json
@@ -0,0 +1,24 @@
+{
+ "menu.mods": "Mods",
+ "popup.confirm-restore-mod-default-settings": "Are you sure you want to restore this mod's settings back to default values?\n\nThis action cannot be undone.",
+ "settings.tab.mods": "Mods",
+ "settings.mods.footer.list": "List",
+ "settings.mods.footer.options": "Options",
+ "winch.name": "Winch",
+ "winch.writelogstofile.title": "Write logs to file",
+ "winch.writelogstofile.tooltip": "Whether to write logs to a file.\n\nREQUIRES RESTART TO TAKE EFFECT.",
+ "winch.writelogstoconsole.title": "Write logs to console",
+ "winch.writelogstoconsole.tooltip": "Whether to open the console program.\n\nREQUIRES RESTART TO TAKE EFFECT.",
+ "winch.loglevel.title": "Minimum Log Level",
+ "winch.loglevel.tooltip": "The minimum log level that can pushed to file and console.",
+ "winch.logsfolder.title": "Logs Folder",
+ "winch.logsfolder.tooltip": "The path to the logs folder. Can be relative to the game folder or absolute.\n\nREQUIRES RESTART TO TAKE EFFECT.",
+ "winch.detailedlogsources.title": "Detailed Log Sources",
+ "winch.detailedlogsources.tooltip": "Whether to show the class and method the log came from instead of just the assembly name.",
+ "winch.enabledeveloperconsole.title": "Enable Developer Console",
+ "winch.enabledeveloperconsole.tooltip": "Whether to enable the terminal that can opened with the ` key.",
+ "winch.maxlogfiles.title": "Max Log Files",
+ "winch.maxlogfiles.tooltip": "The maximum amount of logs that can be in the log folder.\n\nREQUIRES RESTART TO TAKE EFFECT.",
+ "winch.exportyarnprogram.title": "Export Yarn Program",
+ "winch.exportyarnprogram.tooltip": "Whether to extract the game's Yarn dialogue program to 'YarnProgramVanilla.txt' and 'YarnProgramModded.txt' in the game folder.\n\nREQUIRES RESTART TO TAKE EFFECT."
+}
\ No newline at end of file
diff --git a/Winch/Components/ColorDropdownInput.cs b/Winch/Components/ColorDropdownInput.cs
new file mode 100644
index 00000000..e9c6b948
--- /dev/null
+++ b/Winch/Components/ColorDropdownInput.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using TMPro;
+using UnityEngine;
+using UnityEngine.Localization;
+using UnityEngine.Localization.Settings;
+using UnityEngine.UI;
+using Winch.Util;
+
+namespace Winch.Components
+{
+ public class ColorDropdownInput : DropdownInput
+ {
+ [SerializeField]
+ internal TextMeshProUGUI textField;
+
+ [SerializeField]
+ private LocalizedString labelLocalizedString = LocalizationUtil.CreateReference("Strings", "settings.dropdown.color");
+
+ [SerializeField]
+ internal int columns;
+
+ protected override void Awake()
+ {
+ populateOptions = true;
+ retrieveSelectedIndex = true;
+ scrollToSelectedItem = false;
+ optionStrings = GameManager.Instance.GameConfigData.Colors.Select(color => labelLocalizedString).ToList();
+ base.Awake();
+ initialized = true;
+ }
+
+ protected override void OnLocaleChanged(Locale l)
+ {
+ RefreshDropdown();
+ }
+
+ protected override void RefreshDropdown()
+ {
+ base.RefreshDropdown();
+ RefreshLabelsColor();
+ }
+
+ private void RefreshLabelsColor()
+ {
+ textField.color = GameManager.Instance.GameConfigData.Colors[CurrentIndex];
+ }
+
+ protected override void RetrieveSelectedIndex()
+ {
+ base.RetrieveSelectedIndex();
+ }
+
+ protected override void OnValueChanged(int value)
+ {
+ base.OnValueChanged(value);
+ RefreshLabelsColor();
+ }
+
+ protected override void OnComponentSubmitted()
+ {
+ List items = transform.GetComponentsInChildren().ToList();
+ for (int i = 0; i < items.Count; i++)
+ {
+ var item = items[i];
+ item.background.color = GameManager.Instance.GameConfigData.Colors[i];
+ Selectable toggle = item.toggle;
+ Navigation navigation = toggle.navigation;
+ var floatedColumns = (float)columns;
+ if (i <= Mathf.Ceil((items.Count / floatedColumns) * floatedColumns) && i + columns < items.Count)
+ {
+ Selectable toggle2 = items[i + columns].toggle;
+ navigation.selectOnDown = toggle2;
+ }
+ if (i >= columns && i - columns >= 0)
+ {
+ Selectable toggle3 = items[i - columns].toggle;
+ navigation.selectOnUp = toggle3;
+ }
+ toggle.navigation = navigation;
+ }
+ }
+
+ protected override void ChangeValue(int index)
+ {
+ SetConfigValue(GameManager.Instance.GameConfigData.Colors[index]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Winch/Components/DecimalFieldInput.cs b/Winch/Components/DecimalFieldInput.cs
new file mode 100644
index 00000000..ca0d35ea
--- /dev/null
+++ b/Winch/Components/DecimalFieldInput.cs
@@ -0,0 +1,32 @@
+
+namespace Winch.Components
+{
+ public class DecimalFieldInput : FieldInput
+ {
+ protected override void Awake()
+ {
+ base.Awake();
+ //inputField.characterValidation = TMPro.TMP_InputField.CharacterValidation.Decimal;
+ }
+
+ protected override bool ValidateChar(char addedChar)
+ {
+ if (IsNRT(addedChar))
+ {
+ return false;
+ }
+ return char.IsDigit(addedChar) || addedChar == '.';
+ }
+
+ protected override void ChangeValue(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ SetConfigValue(GetDefaultConfigValue());
+ return;
+ }
+
+ SetConfigValue(double.Parse(value));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Winch/Components/DropdownInput.cs b/Winch/Components/DropdownInput.cs
new file mode 100644
index 00000000..08c5970e
--- /dev/null
+++ b/Winch/Components/DropdownInput.cs
@@ -0,0 +1,176 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using TMPro;
+using UnityEngine;
+using UnityEngine.Events;
+using UnityEngine.Localization;
+using UnityEngine.Localization.Components;
+using UnityEngine.Localization.Settings;
+using Winch.Core;
+using Winch.Data.Item;
+using Winch.Util;
+
+namespace Winch.Components
+{
+ public class DropdownInput : Input
+ {
+ [SerializeField]
+ public bool populateOptions = true;
+
+ [SerializeField]
+ public bool retrieveSelectedIndex = true;
+
+ [SerializeField]
+ public bool scrollToSelectedItem = true;
+
+ [SerializeField]
+ protected internal TextMeshProUGUI selectedValueTextField;
+
+ [SerializeField]
+ protected internal TMP_Dropdown dropdown;
+
+ [SerializeField]
+ protected internal SettingsUIComponentEventNotifier dropdownEventNotifier;
+
+ [SerializeField]
+ protected List optionStrings = new List() { LocalizationUtil.Unknown };
+
+ [SerializeField]
+ protected List options = new List() { "Unknown" };
+
+ protected bool initialized = false;
+
+ public int CurrentIndex
+ {
+ get => dropdown.value;
+ set => dropdown.value = value;
+ }
+
+ public LocalizedString CurrentLocalizedOption
+ {
+ get => optionStrings[CurrentIndex];
+ }
+
+ public string CurrentOption
+ {
+ get => options[CurrentIndex];
+ }
+
+ protected virtual void Awake()
+ {
+ dropdown.onValueChanged.AddListener(OnValueChanged);
+ }
+
+ protected virtual void OnComponentSubmitted()
+ {
+ if (scrollToSelectedItem)
+ {
+ var selectedItem = dropdown.GetComponentsInChildren().ElementAtOrDefault(CurrentIndex);
+ if (selectedItem != null)
+ {
+ selectedItem.ScrollToThis();
+ }
+ }
+ }
+
+ protected virtual void OnEnable()
+ {
+ dropdownEventNotifier.OnComponentSubmitted += OnComponentSubmitted;
+ ApplicationEvents.Instance.OnLocaleChanged += OnLocaleChanged;
+ RefreshDropdown();
+ }
+
+ protected virtual void OnDisable()
+ {
+ dropdownEventNotifier.OnComponentSubmitted -= OnComponentSubmitted;
+ ApplicationEvents.Instance.OnLocaleChanged -= OnLocaleChanged;
+ }
+
+ protected virtual void OnLocaleChanged(Locale l)
+ {
+ RefreshDropdownStrings();
+ }
+
+ protected void AddOptionString(LocalizedString str)
+ {
+ TMP_Dropdown.OptionData optionData = new TMP_Dropdown.OptionData();
+ optionData.text = LocalizationSettings.StringDatabase.GetLocalizedString(str.TableEntryReference, null, FallbackBehavior.UseProjectSettings, Array.Empty