|
3 | 3 | import subprocess |
4 | 4 | import json |
5 | 5 | import shutil |
| 6 | +import re |
6 | 7 | from pathlib import Path |
7 | 8 |
|
8 | 9 | class Plugin: |
@@ -478,3 +479,360 @@ async def set_optiscaler_fgtype(self, fgtype: str) -> dict: |
478 | 479 | except Exception as e: |
479 | 480 | decky.logger.error(f"Error setting FGType: {e}") |
480 | 481 | return {"status": "error", "message": str(e)} |
| 482 | + |
| 483 | + async def get_optiscaler_settings(self, section: str = None) -> dict: |
| 484 | + """ |
| 485 | + Get current settings from the OptiScaler.ini file. |
| 486 | + |
| 487 | + Args: |
| 488 | + section: Optional section name to filter settings |
| 489 | + |
| 490 | + Returns: |
| 491 | + Dictionary with settings |
| 492 | + """ |
| 493 | + try: |
| 494 | + ini_path = Path(decky.HOME) / "opti" / "OptiScaler.ini" |
| 495 | + |
| 496 | + if not ini_path.exists(): |
| 497 | + return { |
| 498 | + "status": "error", |
| 499 | + "message": "OptiScaler.ini not found", |
| 500 | + "settings": {} |
| 501 | + } |
| 502 | + |
| 503 | + settings = {} |
| 504 | + current_section = None |
| 505 | + |
| 506 | + with open(ini_path, 'r') as f: |
| 507 | + for line in f: |
| 508 | + line = line.strip() |
| 509 | + |
| 510 | + # Skip comments and empty lines |
| 511 | + if not line or line.startswith(';'): |
| 512 | + continue |
| 513 | + |
| 514 | + # Check for section headers |
| 515 | + if line.startswith('[') and line.endswith(']'): |
| 516 | + current_section = line[1:-1] |
| 517 | + if current_section not in settings: |
| 518 | + settings[current_section] = {} |
| 519 | + continue |
| 520 | + |
| 521 | + # Process key-value pairs |
| 522 | + if '=' in line and current_section: |
| 523 | + key, value = line.split('=', 1) |
| 524 | + settings[current_section][key.strip()] = value.strip() |
| 525 | + |
| 526 | + # If a specific section was requested |
| 527 | + if section: |
| 528 | + if section in settings: |
| 529 | + return { |
| 530 | + "status": "success", |
| 531 | + "settings": settings[section] |
| 532 | + } |
| 533 | + else: |
| 534 | + return { |
| 535 | + "status": "error", |
| 536 | + "message": f"Section {section} not found", |
| 537 | + "settings": {} |
| 538 | + } |
| 539 | + |
| 540 | + return { |
| 541 | + "status": "success", |
| 542 | + "settings": settings |
| 543 | + } |
| 544 | + |
| 545 | + except Exception as e: |
| 546 | + decky.logger.error(f"Error getting OptiScaler settings: {str(e)}") |
| 547 | + return { |
| 548 | + "status": "error", |
| 549 | + "message": str(e), |
| 550 | + "settings": {} |
| 551 | + } |
| 552 | + |
| 553 | + async def set_optiscaler_setting(self, section: str, key: str, value: str) -> dict: |
| 554 | + """ |
| 555 | + Set a specific setting in the OptiScaler.ini file. |
| 556 | + |
| 557 | + Args: |
| 558 | + section: Section name in the INI file |
| 559 | + key: Setting key |
| 560 | + value: New value for the setting |
| 561 | + |
| 562 | + Returns: |
| 563 | + Dictionary with status and message |
| 564 | + """ |
| 565 | + try: |
| 566 | + ini_path = Path(decky.HOME) / "opti" / "OptiScaler.ini" |
| 567 | + |
| 568 | + if not ini_path.exists(): |
| 569 | + return { |
| 570 | + "status": "error", |
| 571 | + "message": "OptiScaler.ini not found" |
| 572 | + } |
| 573 | + |
| 574 | + # Read the entire file |
| 575 | + with open(ini_path, 'r') as f: |
| 576 | + content = f.read() |
| 577 | + |
| 578 | + # Create a pattern that looks for the key in the specific section |
| 579 | + section_pattern = f"\\[{re.escape(section)}\\](.*?)(?=\\[|$)" |
| 580 | + section_match = re.search(section_pattern, content, re.DOTALL) |
| 581 | + |
| 582 | + if not section_match: |
| 583 | + return { |
| 584 | + "status": "error", |
| 585 | + "message": f"Section [{section}] not found" |
| 586 | + } |
| 587 | + |
| 588 | + section_content = section_match.group(1) |
| 589 | + key_pattern = f"^{re.escape(key)}\\s*=.*$" |
| 590 | + |
| 591 | + # Check if the key exists in the section |
| 592 | + key_match = re.search(key_pattern, section_content, re.MULTILINE) |
| 593 | + |
| 594 | + if not key_match: |
| 595 | + return { |
| 596 | + "status": "error", |
| 597 | + "message": f"Key {key} not found in section [{section}]" |
| 598 | + } |
| 599 | + |
| 600 | + # Replace the key's value |
| 601 | + new_line = f"{key}={value}" |
| 602 | + updated_section = re.sub(key_pattern, new_line, section_content, flags=re.MULTILINE) |
| 603 | + |
| 604 | + # Replace the old section with the updated one |
| 605 | + updated_content = content.replace(section_content, updated_section) |
| 606 | + |
| 607 | + # Write the updated content back to the file |
| 608 | + with open(ini_path, 'w') as f: |
| 609 | + f.write(updated_content) |
| 610 | + |
| 611 | + decky.logger.info(f"Updated OptiScaler.ini: [{section}] {key}={value}") |
| 612 | + return { |
| 613 | + "status": "success", |
| 614 | + "message": f"Setting updated: [{section}] {key}={value}" |
| 615 | + } |
| 616 | + |
| 617 | + except Exception as e: |
| 618 | + decky.logger.error(f"Error setting OptiScaler setting: {str(e)}") |
| 619 | + return { |
| 620 | + "status": "error", |
| 621 | + "message": str(e) |
| 622 | + } |
| 623 | + |
| 624 | + async def set_upscaler_type(self, upscaler_type: str, value: str) -> dict: |
| 625 | + """ |
| 626 | + Set the upscaler type for a specific API. |
| 627 | + |
| 628 | + Args: |
| 629 | + upscaler_type: 'Dx11Upscaler', 'Dx12Upscaler', or 'VulkanUpscaler' |
| 630 | + value: The upscaler value to set (e.g., 'auto', 'fsr22', 'xess', etc.) |
| 631 | + |
| 632 | + Returns: |
| 633 | + Dictionary with status and message |
| 634 | + """ |
| 635 | + valid_types = ["Dx11Upscaler", "Dx12Upscaler", "VulkanUpscaler"] |
| 636 | + if upscaler_type not in valid_types: |
| 637 | + return { |
| 638 | + "status": "error", |
| 639 | + "message": f"Invalid upscaler type: {upscaler_type}" |
| 640 | + } |
| 641 | + |
| 642 | + return await self.set_optiscaler_setting("Upscalers", upscaler_type, value) |
| 643 | + |
| 644 | + async def set_optifg_settings(self, settings: dict) -> dict: |
| 645 | + """ |
| 646 | + Update multiple OptiFG settings at once. |
| 647 | + |
| 648 | + Args: |
| 649 | + settings: Dictionary of settings to update in the [OptiFG] section |
| 650 | + |
| 651 | + Returns: |
| 652 | + Dictionary with status and message |
| 653 | + """ |
| 654 | + try: |
| 655 | + results = [] |
| 656 | + section = "OptiFG" |
| 657 | + |
| 658 | + for key, value in settings.items(): |
| 659 | + result = await self.set_optiscaler_setting(section, key, str(value)) |
| 660 | + results.append(result) |
| 661 | + |
| 662 | + # Check if any errors occurred |
| 663 | + errors = [r for r in results if r["status"] == "error"] |
| 664 | + if errors: |
| 665 | + return { |
| 666 | + "status": "partial_success", |
| 667 | + "message": f"Updated {len(results) - len(errors)} settings, {len(errors)} failed", |
| 668 | + "details": errors |
| 669 | + } |
| 670 | + |
| 671 | + return { |
| 672 | + "status": "success", |
| 673 | + "message": f"Updated {len(results)} OptiFG settings" |
| 674 | + } |
| 675 | + |
| 676 | + except Exception as e: |
| 677 | + decky.logger.error(f"Error setting OptiFG settings: {str(e)}") |
| 678 | + return { |
| 679 | + "status": "error", |
| 680 | + "message": str(e) |
| 681 | + } |
| 682 | + |
| 683 | + async def set_framerate_limit(self, limit: str) -> dict: |
| 684 | + """ |
| 685 | + Set the framerate limit in the OptiScaler.ini. |
| 686 | + |
| 687 | + Args: |
| 688 | + limit: The framerate limit value (e.g., 'auto', '60.0', etc.) |
| 689 | + |
| 690 | + Returns: |
| 691 | + Dictionary with status and message |
| 692 | + """ |
| 693 | + return await self.set_optiscaler_setting("Framerate", "FramerateLimit", limit) |
| 694 | + |
| 695 | + async def set_quality_ratio_override(self, enabled: bool, ratios: dict = None) -> dict: |
| 696 | + """ |
| 697 | + Set the quality ratio override settings. |
| 698 | + |
| 699 | + Args: |
| 700 | + enabled: Whether to enable quality ratio overrides |
| 701 | + ratios: Optional dictionary with quality mode ratios |
| 702 | + |
| 703 | + Returns: |
| 704 | + Dictionary with status and message |
| 705 | + """ |
| 706 | + try: |
| 707 | + results = [] |
| 708 | + |
| 709 | + # First, enable/disable the override |
| 710 | + enabled_result = await self.set_optiscaler_setting( |
| 711 | + "QualityOverrides", |
| 712 | + "QualityRatioOverrideEnabled", |
| 713 | + "true" if enabled else "false" |
| 714 | + ) |
| 715 | + results.append(enabled_result) |
| 716 | + |
| 717 | + # If ratios are provided and enabled is True, update each ratio |
| 718 | + if enabled and ratios: |
| 719 | + for mode, ratio in ratios.items(): |
| 720 | + key_name = f"QualityRatio{mode}" |
| 721 | + ratio_result = await self.set_optiscaler_setting( |
| 722 | + "QualityOverrides", |
| 723 | + key_name, |
| 724 | + str(ratio) |
| 725 | + ) |
| 726 | + results.append(ratio_result) |
| 727 | + |
| 728 | + # Check if any errors occurred |
| 729 | + errors = [r for r in results if r["status"] == "error"] |
| 730 | + if errors: |
| 731 | + return { |
| 732 | + "status": "partial_success", |
| 733 | + "message": f"Updated {len(results) - len(errors)} settings, {len(errors)} failed", |
| 734 | + "details": errors |
| 735 | + } |
| 736 | + |
| 737 | + return { |
| 738 | + "status": "success", |
| 739 | + "message": f"Updated quality ratio override settings" |
| 740 | + } |
| 741 | + |
| 742 | + except Exception as e: |
| 743 | + decky.logger.error(f"Error setting quality ratio override: {str(e)}") |
| 744 | + return { |
| 745 | + "status": "error", |
| 746 | + "message": str(e) |
| 747 | + } |
| 748 | + |
| 749 | + async def set_menu_settings(self, settings: dict) -> dict: |
| 750 | + """ |
| 751 | + Update multiple Menu settings at once. |
| 752 | + |
| 753 | + Args: |
| 754 | + settings: Dictionary of settings to update in the [Menu] section |
| 755 | + |
| 756 | + Returns: |
| 757 | + Dictionary with status and message |
| 758 | + """ |
| 759 | + try: |
| 760 | + results = [] |
| 761 | + section = "Menu" |
| 762 | + |
| 763 | + for key, value in settings.items(): |
| 764 | + result = await self.set_optiscaler_setting(section, key, str(value)) |
| 765 | + results.append(result) |
| 766 | + |
| 767 | + # Check if any errors occurred |
| 768 | + errors = [r for r in results if r["status"] == "error"] |
| 769 | + if errors: |
| 770 | + return { |
| 771 | + "status": "partial_success", |
| 772 | + "message": f"Updated {len(results) - len(errors)} settings, {len(errors)} failed", |
| 773 | + "details": errors |
| 774 | + } |
| 775 | + |
| 776 | + return { |
| 777 | + "status": "success", |
| 778 | + "message": f"Updated {len(results)} Menu settings" |
| 779 | + } |
| 780 | + |
| 781 | + except Exception as e: |
| 782 | + decky.logger.error(f"Error setting Menu settings: {str(e)}") |
| 783 | + return { |
| 784 | + "status": "error", |
| 785 | + "message": str(e) |
| 786 | + } |
| 787 | + |
| 788 | + async def set_upscale_ratio_override(self, enabled: bool, value: str = None) -> dict: |
| 789 | + """ |
| 790 | + Set the upscale ratio override settings. |
| 791 | + |
| 792 | + Args: |
| 793 | + enabled: Whether to enable upscale ratio override |
| 794 | + value: Optional value for the override |
| 795 | + |
| 796 | + Returns: |
| 797 | + Dictionary with status and message |
| 798 | + """ |
| 799 | + try: |
| 800 | + results = [] |
| 801 | + |
| 802 | + # First, enable/disable the override |
| 803 | + enabled_result = await self.set_optiscaler_setting( |
| 804 | + "UpscaleRatio", |
| 805 | + "UpscaleRatioOverrideEnabled", |
| 806 | + "true" if enabled else "false" |
| 807 | + ) |
| 808 | + results.append(enabled_result) |
| 809 | + |
| 810 | + # If value is provided and enabled is True, update the value |
| 811 | + if enabled and value: |
| 812 | + value_result = await self.set_optiscaler_setting( |
| 813 | + "UpscaleRatio", |
| 814 | + "UpscaleRatioOverrideValue", |
| 815 | + value |
| 816 | + ) |
| 817 | + results.append(value_result) |
| 818 | + |
| 819 | + # Check if any errors occurred |
| 820 | + errors = [r for r in results if r["status"] == "error"] |
| 821 | + if errors: |
| 822 | + return { |
| 823 | + "status": "partial_success", |
| 824 | + "message": f"Updated {len(results) - len(errors)} settings, {len(errors)} failed", |
| 825 | + "details": errors |
| 826 | + } |
| 827 | + |
| 828 | + return { |
| 829 | + "status": "success", |
| 830 | + "message": f"Updated upscale ratio override settings" |
| 831 | + } |
| 832 | + |
| 833 | + except Exception as e: |
| 834 | + decky.logger.error(f"Error setting upscale ratio override: {str(e)}") |
| 835 | + return { |
| 836 | + "status": "error", |
| 837 | + "message": str(e) |
| 838 | + } |
0 commit comments