diff options
| -rw-r--r-- | README.md | 40 | ||||
| -rwxr-xr-x | defaults/assets/fgmod.sh | 5 | ||||
| -rw-r--r-- | defaults/assets/update-optiscaler-config.py | 110 | ||||
| -rw-r--r-- | main.py | 11 |
4 files changed, 165 insertions, 1 deletions
@@ -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 @@ -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(): |
