summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md40
-rwxr-xr-xdefaults/assets/fgmod.sh5
-rw-r--r--defaults/assets/update-optiscaler-config.py110
-rw-r--r--main.py11
4 files changed, 165 insertions, 1 deletions
diff --git a/README.md b/README.md
index 70b8131..d188cd3 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,46 @@ This plugin uses OptiScaler to replace DLSS calls with FSR3/FSR3.1, giving you:
- Click "Copy Unpatch Command" and replace the launch options with: `~/fgmod/fgmod-uninstaller.sh %command%`
- Run the game at least once to make the uninstaller script run. After you can leave the launch option or remove it
+### Configuring OptiScaler via Environment Variables
+Starting v0.14.0, you can update OptiScaler settings before a game launches by adding environment variables.
+This is useful if you plan to use the same settings across multiple games so they are pre-configured by the time you launch them.
+
+For example, considering the following sample from the OptiScaler.ini config file:
+```
+[Upscalers]
+Dx11Upscaler=auto
+Dx12Upscaler=auto
+VulkanUpscaler=auto
+
+[FrameGen]
+Enabled=auto
+FGInput=auto
+FGOutput=auto
+DebugView=auto
+DrawUIOverFG=auto
+```
+We can decide to set `Dx12Upscaler=fsr31` to enable FSR4 in DX12 games by default. This works because the option name `Dx12Upscaler` is unique throughout the file but for options that appear multiple times like `Enabled`, you can prefix the option name with the section name like `FrameGen_Enabled=true`.
+You can provide section names for all options if you want to be explicit. You can also prefix `Section_Option` with `OptiScaler` to ensure no conflict with other commands.
+
+Here's the breakdown of supported formats:
+- `OptiScaler_Section_Option=value` - Full format (foolproof)
+- `Section_Option=value` - Short format (recommended)
+- `Option=value` - Minimal format (only works if the option name appears once in OptiScaler.ini)
+
+**Example:**
+```bash
+# Enable frame generation with XeFG output
+FrameGen_Enabled=true FGInput=fsrfg FGOutput=xefg ~/fgmod/fgmod %command%
+
+# Set DX12 upscaler to FSR 3.1 (Upgrades to FSR4)
+Dx12Upscaler=fsr31 ~/fgmod/fgmod %command%
+```
+
+**Notes:**
+- Environment variables override the OptiScaler.ini file on each game launch
+- Hyphenated section names like `[V-Sync]` can be accessed like `VSync_Option=value`
+- If an option name appears in multiple sections of the OptiScaler.ini file, use the `Section_Option` or `OptiScaler_Section_Option` format
+
## Technical Details
### What's Included
diff --git a/defaults/assets/fgmod.sh b/defaults/assets/fgmod.sh
index d48856d..30d74ac 100755
--- a/defaults/assets/fgmod.sh
+++ b/defaults/assets/fgmod.sh
@@ -128,6 +128,11 @@ else
logger -t fgmod "📄 OptiScaler.ini installed to $exe_folder_path"
fi
+# === OptiScaler env variables Handling ===
+if [[ -f "$fgmod_path/update-optiscaler-config.py" ]]; then
+ python "$fgmod_path/update-optiscaler-config.py" "$exe_folder_path/OptiScaler.ini"
+fi
+
# === ASI Plugins Directory ===
if [[ -d "$fgmod_path/plugins" ]]; then
echo "🔌 Installing ASI plugins directory"
diff --git a/defaults/assets/update-optiscaler-config.py b/defaults/assets/update-optiscaler-config.py
new file mode 100644
index 0000000..f4a65de
--- /dev/null
+++ b/defaults/assets/update-optiscaler-config.py
@@ -0,0 +1,110 @@
+import os
+import sys
+import re
+from configparser import ConfigParser
+
+def update_optiscaler_config(file_path):
+ if not os.path.exists(file_path):
+ print(f"Error: File '{file_path}' not found.")
+ return
+
+ with open(file_path, 'r') as f:
+ lines = f.readlines()
+
+ config = ConfigParser()
+ config.optionxform = str # Preserve case for keys (otherwise PATH could match Path)
+ config.read(file_path)
+
+ # Because we want to support unprefixed env variables, we need to count key occurrences across all sections of the ini file
+ # Keys that appear multiple times should be prefixed like Section_Key by the user for them to be targeted properly
+
+ # Normalize section names: strip - and . so V-Sync becomes VSync
+ # This allows env vars like VSync_Key to match INI section [V-Sync]
+ def normalize_section(section_name):
+ return section_name.replace('-', '').replace('.', '')
+
+ key_occurrences = {}
+ key_to_sections = {}
+ section_normalized_to_actual = {} # Maps normalized section name to actual section name
+
+ for section in config.sections():
+ normalized = normalize_section(section)
+ section_normalized_to_actual[normalized] = section
+
+ for key in config.options(section):
+ key_occurrences[key] = key_occurrences.get(key, 0) + 1
+ if key not in key_to_sections:
+ key_to_sections[key] = []
+ key_to_sections[key].append(section)
+
+ env_updates = []
+
+ # Handle OptiScaler_Section_Key format
+ optiscaler_vars = {k: v for k, v in os.environ.items() if k.startswith("OptiScaler_")}
+ for env_name, env_value in optiscaler_vars.items():
+ parts = env_name.split('_', 2)
+ if len(parts) >= 3:
+ env_updates.append(('optiscaler', parts[1], parts[2], env_value, env_name))
+
+ # Handle Section_Key and Key formats
+ other_vars = {k: v for k, v in os.environ.items() if not k.startswith("OptiScaler_")}
+ for env_name, env_value in other_vars.items():
+ # Try Section_Key format
+ if '_' in env_name:
+ parts = env_name.split('_', 1)
+ section_from_env = parts[0]
+ key = parts[1]
+
+ # Try exact section match first
+ if config.has_section(section_from_env) and config.has_option(section_from_env, key):
+ env_updates.append(('section_key', section_from_env, key, env_value, env_name))
+ continue
+
+ # Try section match with normalized section names
+ if section_from_env in section_normalized_to_actual:
+ actual_section = section_normalized_to_actual[section_from_env]
+ if config.has_option(actual_section, key):
+ env_updates.append(('section_key', actual_section, key, env_value, env_name))
+ continue
+
+ # Try Key format (only if key appears exactly once across all sections)
+ if env_name in key_occurrences and key_occurrences[env_name] == 1:
+ section = key_to_sections[env_name][0]
+ env_updates.append(('key', section, env_name, env_value, env_name))
+
+ print(f"Found {len(env_updates)} updates to apply")
+ for entry in env_updates:
+ print(f"> {entry}")
+
+ for update_type, section_target, key_target, env_value, env_name in env_updates:
+ found_section = False
+
+ # Regex to match [Section] and Key=Value (case-sensitive)
+ section_pattern = re.compile(rf'^\s*\[{re.escape(section_target)}]\s*')
+ key_pattern = re.compile(rf'^(\s*{re.escape(key_target)}\s*)=.*')
+
+ for i, line in enumerate(lines):
+ # Track if we are inside the correct section
+ if section_pattern.match(line):
+ found_section = True
+ continue
+
+ # If we hit a new section before finding the key, the key doesn't exist in the target section
+ if found_section and line.strip().startswith('[') and not section_pattern.match(line):
+ break
+
+ # Replace the value if the key is found within the correct section
+ if found_section and key_pattern.match(line):
+ lines[i] = key_pattern.sub(r'\1=' + env_value, line)
+ print(f"Updated: [{section_target}] {key_target} = {env_value} (from {env_name})")
+ break
+
+ # Write the modified content back
+ with open(file_path, 'w') as f:
+ f.writelines(lines)
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print("Usage: python update-optiscaler-config.py <path_to_ini>")
+ else:
+ update_optiscaler_config(sys.argv[1]) \ No newline at end of file
diff --git a/main.py b/main.py
index 5b6b8e8..1e45149 100644
--- a/main.py
+++ b/main.py
@@ -114,6 +114,14 @@ class Plugin:
shutil.copy2(uninstaller_src, uninstaller_dest)
uninstaller_dest.chmod(0o755)
decky.logger.info(f"Copied uninstaller script to {uninstaller_dest}")
+
+ # Copy optiscaler config updater script
+ optiscaler_config_updater_src = assets_dir / "update-optiscaler-config.py"
+ optiscaler_config_updater_dest = extract_path / "update-optiscaler-config.py"
+ if optiscaler_config_updater_src.exists():
+ shutil.copy2(optiscaler_config_updater_src, optiscaler_config_updater_dest)
+ optiscaler_config_updater_dest.chmod(0o755)
+ decky.logger.info(f"Copied update-optiscaler-config.py script to {optiscaler_config_updater_dest}")
return True
except Exception as e:
@@ -412,7 +420,8 @@ class Plugin:
"libxess_fg.dll", # New in v0.9.0-pre4
"libxell.dll", # New in v0.9.0-pre4
"fgmod",
- "fgmod-uninstaller.sh"
+ "fgmod-uninstaller.sh",
+ "update-optiscaler-config.py"
]
if path.exists():