Skip to content

Instantly share code, notes, and snippets.

@jarrodnorwell
Created April 18, 2025 17:18
Show Gist options
  • Save jarrodnorwell/fb1159d82daadcf6f5ce32dccc9c910a to your computer and use it in GitHub Desktop.
Save jarrodnorwell/fb1159d82daadcf6f5ce32dccc9c910a to your computer and use it in GitHub Desktop.
enum CytrusSettingsHeaders : String, CaseIterable {
case core = "Core"
case debugging = "Debugging"
case system = "System"
case systemSaveGame = "System Save Game"
case renderer = "Renderer"
case defaultLayout = "Default Layout"
case customLayout = "Custom Layout"
case audio = "Audio"
case miscellaneous = "Miscellaneous"
var header: SettingHeader {
switch self {
case .core,
.debugging,
.system,
.systemSaveGame,
.renderer,
.defaultLayout,
.audio,
.miscellaneous: .init(text: rawValue)
case .customLayout: .init(text: rawValue,
secondaryText: "Set Default Layout to Custom Layout")
}
}
static var allHeaders: [SettingHeader] { allCases.map { $0.header } }
}
enum CytrusSettingsItems : String, CaseIterable {
// Core
case cpuJIT = "cytrus.cpuJIT"
case cpuClockPercentage = "cytrus.cpuClockPercentage"
case new3DS = "cytrus.new3DS"
case lleApplets = "cytrus.lleApplets"
case deterministicAsyncOperations = "cytrus.deterministicAsyncOperations"
case enableRequiredOnlineLLEModules = "cytrus.enableRequiredOnlineLLEModules"
// System
case regionValue = "cytrus.regionValue"
case pluginLoader = "cytrus.pluginLoader"
case allowPluginLoader = "cytrus.allowPluginLoader"
case stepsPerHour = "cytrus.stepsPerHour"
// Renderer
case spirvShaderGeneration = "cytrus.spirvShaderGeneration"
case useAsyncShaderCompilation = "cytrus.useAsyncShaderCompilation"
case useAsyncPresentation = "cytrus.useAsyncPresentation"
case useHardwareShaders = "cytrus.useHardwareShaders"
case useDiskShaderCache = "cytrus.useDiskShaderCache"
case useShadersAccurateMul = "cytrus.useShadersAccurateMul"
case useNewVSync = "cytrus.useNewVSync"
case useShaderJIT = "cytrus.useShaderJIT"
case resolutionFactor = "cytrus.resolutionFactor"
case textureFilter = "cytrus.textureFilter"
case textureSampling = "cytrus.textureSampling"
case delayGameRenderThreadUS = "cytrus.delayGameRenderThreadUS"
case layoutOption = "cytrus.layoutOption"
case customTopX = "cytrus.customTopX"
case customTopY = "cytrus.customTopY"
case customTopWidth = "cytrus.customTopWidth"
case customTopHeight = "cytrus.customTopHeight"
case customBottomX = "cytrus.customBottomX"
case customBottomY = "cytrus.customBottomY"
case customBottomWidth = "cytrus.customBottomWidth"
case customBottomHeight = "cytrus.customBottomHeight"
case customSecondLayerOpacity = "cytrus.customSecondLayerOpacity"
case render3D = "cytrus.render3D"
case factor3D = "cytrus.factor3D"
case monoRender = "cytrus.monoRender"
case filterMode = "cytrus.filterMode"
case ppShaderName = "cytrus.ppShaderName"
case anaglyphShaderName = "cytrus.anaglyphShaderName"
case dumpTextures = "cytrus.dumpTextures"
case customTextures = "cytrus.customTextures"
case preloadTextures = "cytrus.preloadTextures"
case asyncCustomLoading = "cytrus.asyncCustomLoading"
case disableRightEyeRender = "cytrus.disableRightEyeRender"
// Audio
case audioMuted = "cytrus.audioMuted"
case audioEmulation = "cytrus.audioEmulation"
case audioStretching = "cytrus.audioStretching"
case realtimeAudio = "cytrus.realtimeAudio"
case volume = "cytrus.volume"
case outputType = "cytrus.outputType"
case inputType = "cytrus.inputType"
// Miscellaneous
case logLevel = "cytrus.logLevel"
case webAPIURL = "cytrus.webAPIURL"
case systemLanguage = "cytrus.systemLanguage"
case username = "cytrus.username"
var title: String {
switch self {
case .cpuJIT: "CPU JIT"
case .cpuClockPercentage: "CPU Clock Percentage"
case .new3DS: "New 3DS"
case .lleApplets: "LLE Applets"
case .deterministicAsyncOperations: "Deterministic Async Operations"
case .enableRequiredOnlineLLEModules: "Required Online LLE Modules"
case .regionValue: "Region Value"
case .pluginLoader: "Plugin Loader"
case .allowPluginLoader: "Allow Plugin Loader"
case .stepsPerHour: "Steps Per Hour"
case .spirvShaderGeneration: "SPIR-V Shader Generation"
case .useAsyncShaderCompilation: "Async Shader Compilation"
case .useAsyncPresentation: "Async Presentation"
case .useHardwareShaders: "Hardware Shaders"
case .useDiskShaderCache: "Disk Shader Cache"
case .useShadersAccurateMul: "Shaders Accurate Mul"
case .useNewVSync: "New VSync"
case .useShaderJIT: "Shader JIT"
case .resolutionFactor: "Resolution Factor"
case .textureFilter: "Texture Filter"
case .textureSampling: "Texture Sampling"
case .delayGameRenderThreadUS: "Delay Game Render Thread US"
case .layoutOption: "Layout Option"
case .customTopX: "Custom Top X"
case .customTopY: "Custom Top Y"
case .customTopWidth: "Custom Top Width"
case .customTopHeight: "Custom Top Height"
case .customBottomX: "Custom Bottom X"
case .customBottomY: "Custom Bottom Y"
case .customBottomWidth: "Custom Bottom Width"
case .customBottomHeight: "Custom Bottom Height"
case .customSecondLayerOpacity: "Custom Second Layer Opacity"
case .render3D: "Render 3D"
case .factor3D: "Factor 3D"
case .monoRender: "Mono Render"
case .filterMode: "Filter Mode"
case .ppShaderName: "PP Shader Name"
case .anaglyphShaderName: "Anaglyph Shader Name"
case .dumpTextures: "Dump Textures"
case .customTextures: "Custom Textures"
case .preloadTextures: "Preload Textures"
case .asyncCustomLoading: "Async Custom Loading"
case .disableRightEyeRender: "Disable Right Eye Render"
case .audioMuted: "Audio Muted"
case .audioEmulation: "Audio Emulation"
case .audioStretching: "Audio Stretching"
case .realtimeAudio: "Realtime Audio"
case .volume: "Volume"
case .outputType: "Output Type"
case .inputType: "Input Type"
case .logLevel: "Log Level"
case .webAPIURL: "Web API URL"
case .systemLanguage: "System Language"
case .username: "Username"
}
}
func setting(_ delegate: SettingDelegate? = nil) -> BaseSetting {
switch self {
case .cpuJIT,
.new3DS,
.lleApplets,
.deterministicAsyncOperations,
.enableRequiredOnlineLLEModules,
.pluginLoader,
.allowPluginLoader,
.spirvShaderGeneration,
.useAsyncShaderCompilation,
.useAsyncPresentation,
.useHardwareShaders,
.useDiskShaderCache,
.useShadersAccurateMul,
.useNewVSync,
.useShaderJIT,
.filterMode,
.dumpTextures,
.customTextures,
.preloadTextures,
.asyncCustomLoading,
.disableRightEyeRender,
.audioMuted,
.audioStretching,
.realtimeAudio:
BoolSetting(key: rawValue,
title: title,
details: nil,
value: UserDefaults.standard.bool(forKey: rawValue),
delegate: delegate)
case .cpuClockPercentage:
InputNumberSetting(key: rawValue,
title: title,
details: nil,
min: 5, max: 400,
value: UserDefaults.standard.double(forKey: rawValue),
delegate: delegate)
case .regionValue:
SelectionSetting(key: rawValue,
title: title,
details: nil,
values: [
"Automatic" : -1,
"Japan" : 0,
"USA" : 1,
"Europe" : 2,
"Australia" : 3,
"China" : 4,
"Korea" : 5,
"Taiwan" : 6
],
selectedValue: UserDefaults.standard.value(forKey: rawValue),
action: {},
delegate: delegate)
case .stepsPerHour:
InputNumberSetting(key: rawValue,
title: title,
details: nil,
min: 0, max: 9999,
value: UserDefaults.standard.double(forKey: rawValue),
delegate: delegate)
case .resolutionFactor:
StepperSetting(key: rawValue,
title: title,
details: nil,
min: 0,
max: 10,
value: UserDefaults.standard.double(forKey: rawValue),
delegate: delegate)
case .textureFilter:
SelectionSetting(key: rawValue,
title: title,
details: nil,
values: [
"None" : 0,
"Anime4K" : 1,
"Bicubic" : 2,
"ScaleForce" : 3,
"xBRZ" : 4,
"MMPX" : 5
],
selectedValue: UserDefaults.standard.value(forKey: rawValue),
action: {},
delegate: delegate)
case .textureSampling:
SelectionSetting(key: rawValue,
title: title,
values: [
"Game Controlled" : 0,
"Nearest Neighbor" : 1,
"Linear" : 2
],
selectedValue: UserDefaults.standard.value(forKey: rawValue),
action: {},
delegate: delegate)
case .delayGameRenderThreadUS:
InputNumberSetting(key: rawValue,
title: title,
details: nil,
min: 0, max: 16000,
value: UserDefaults.standard.double(forKey: rawValue),
delegate: delegate)
case .layoutOption:
SelectionSetting(key: rawValue,
title: title,
details: nil,
values: [
"Default" : 0,
"Single Screen" : 1,
"Large Screen" : 2,
"Side Screen" : 3,
"Hybrid Screen" : 5,
"Custom Layout" : 6
],
selectedValue: UserDefaults.standard.value(forKey: rawValue),
action: {},
delegate: delegate)
case .customTopX,
.customTopY,
.customTopWidth,
.customTopHeight,
.customBottomX,
.customBottomY,
.customBottomWidth,
.customBottomHeight:
InputNumberSetting(key: rawValue,
title: title,
details: nil,
min: 0, max: 9999,
value: UserDefaults.standard.double(forKey: rawValue),
delegate: delegate)
case .customSecondLayerOpacity:
InputNumberSetting(key: rawValue,
title: title,
details: nil,
min: 0, max: 100,
value: UserDefaults.standard.double(forKey: rawValue),
delegate: delegate)
case .render3D:
SelectionSetting(key: rawValue,
title: title,
details: nil,
values: [
"Off" : 0,
"Side by Side" : 1,
"Anaglyph" : 2,
"Interlaced" : 3,
"ReverseInterlaced" : 4,
"CardboardVR" : 5
],
selectedValue: UserDefaults.standard.value(forKey: rawValue),
action: {},
delegate: delegate)
case .factor3D:
StepperSetting(key: rawValue,
title: title,
details: nil,
min: 0,
max: 100,
value: UserDefaults.standard.double(forKey: rawValue),
delegate: delegate)
case .monoRender:
SelectionSetting(key: rawValue,
title: title,
details: nil,
values: [
"Left Eye" : 0,
"Right Eye" : 1
],
selectedValue: UserDefaults.standard.value(forKey: rawValue),
action: {},
delegate: delegate)
case .ppShaderName:
InputStringSetting(key: rawValue,
title: title,
placeholder: "none (builtin)",
value: UserDefaults.standard.string(forKey: rawValue),
action: {},
delegate: delegate)
case .anaglyphShaderName:
InputStringSetting(key: rawValue,
title: title,
placeholder: "dubois (builtin)",
value: UserDefaults.standard.string(forKey: rawValue),
action: {},
delegate: delegate)
case .audioEmulation:
SelectionSetting(key: rawValue,
title: title,
values: [
"HLE" : 0,
"LLE" : 1,
"LLE (Multithreaded)" : 2
],
selectedValue: UserDefaults.standard.value(forKey: rawValue),
action: {},
delegate: delegate)
case .volume:
StepperSetting(key: rawValue,
title: title,
details: nil,
min: 0,
max: 1,
value: UserDefaults.standard.double(forKey: rawValue),
delegate: delegate)
case .outputType:
SelectionSetting(key: rawValue,
title: title,
values: [
"Automatic" : 0,
"None" : 1,
"CoreAudio" : 2,
"OpenAL" : 3,
"SDL3" : 4
],
selectedValue: UserDefaults.standard.value(forKey: rawValue),
action: {},
delegate: delegate)
case .inputType:
SelectionSetting(key: rawValue,
title: title,
values: [
"Automatic" : 0,
"None" : 1,
"Static" : 2,
"OpenAL" : 3
],
selectedValue: UserDefaults.standard.value(forKey: rawValue),
action: {},
delegate: delegate)
case .logLevel:
SelectionSetting(key: rawValue,
title: title,
details: nil,
values: [
"Trace" : 0,
"Debug" : 1,
"Info" : 2,
"Warning" : 3,
"Error" : 4,
"Critical" : 5
],
selectedValue: UserDefaults.standard.value(forKey: rawValue),
action: {},
delegate: delegate)
case .webAPIURL:
InputStringSetting(key: rawValue,
title: title,
details: nil,
placeholder: "http(s)://address:port",
value: UserDefaults.standard.string(forKey: rawValue),
action: {
MultiplayerManager.shared().updateWebAPIURL()
},
delegate: delegate)
case .systemLanguage:
SelectionSetting(key: rawValue,
title: title,
values: [
"Japanese" : 0,
"English" : 1,
"French" : 2,
"German" : 3,
"Italian" : 4,
"Spanish" : 5,
"Simplified Chinese" : 6,
"Korean" : 7,
"Dutch" : 8,
"Portuguese" : 9,
"Russian" : 10,
"Traditional Chinese" : 11
],
selectedValue: UserDefaults.standard.value(forKey: rawValue),
action: {
guard let systemLanguage = UserDefaults.standard.value(forKey: rawValue) as? Int else { return }
SystemSaveGame.shared.set(systemLanguage)
},
delegate: delegate)
case .username:
InputStringSetting(key: rawValue,
title: title,
placeholder: "Cytrus",
value: UserDefaults.standard.string(forKey: rawValue),
action: {
guard let username = UserDefaults.standard.string(forKey: rawValue) else { return }
SystemSaveGame.shared.set(username)
},
delegate: delegate)
}
}
static func settings(_ header: CytrusSettingsHeaders) -> [CytrusSettingsItems] {
var debugging: [CytrusSettingsItems] = [.logLevel]
if AppStoreCheck.shared.debugging {
debugging.insert(.cpuJIT, at: 0)
}
return switch header {
case .core:
[
.cpuClockPercentage,
.new3DS,
.lleApplets,
.deterministicAsyncOperations,
.enableRequiredOnlineLLEModules
]
case .debugging:
debugging
case .system:
[
.regionValue,
.pluginLoader,
.allowPluginLoader,
.stepsPerHour
]
case .systemSaveGame:
[
.systemLanguage,
.username
]
case .renderer:
[
.spirvShaderGeneration,
.useAsyncShaderCompilation,
.useAsyncPresentation,
.useHardwareShaders,
.useDiskShaderCache,
.useShadersAccurateMul,
.useNewVSync,
.useShaderJIT,
.resolutionFactor,
.textureFilter,
.textureSampling,
.delayGameRenderThreadUS,
.render3D,
.factor3D,
.monoRender,
.filterMode,
.ppShaderName,
.anaglyphShaderName,
.dumpTextures,
.customTextures,
.preloadTextures,
.asyncCustomLoading,
.disableRightEyeRender
]
case .defaultLayout:
[
.layoutOption
]
case .customLayout:
[
.customTopX,
.customTopY,
.customTopWidth,
.customTopHeight,
.customBottomX,
.customBottomY,
.customBottomWidth,
.customBottomHeight,
.customSecondLayerOpacity
]
case .audio:
[
.audioMuted,
.audioEmulation,
.audioStretching,
.realtimeAudio,
.volume,
.outputType,
.inputType
]
case .miscellaneous:
[
.webAPIURL
]
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment