[
  {
    "path": ".editorconfig",
    "content": "# Remove the line below if you want to inherit .editorconfig settings from higher directories\nroot = true\n\n# C# files\n[*.cs]\n\n#### Core EditorConfig Options ####\n\n# Indentation and spacing\nindent_size = 4\nindent_style = space\ntab_width = 4\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n# charset\ncharset = utf-8\nend_of_line = lf\n\n#### .NET Coding Conventions ####\n\n# this. and Me. preferences\ndotnet_style_qualification_for_method = true\n\n#### Diagnostic configuration ####\n\n# CA1000: Do not declare static members on generic types\ndotnet_diagnostic.CA1000.severity = warning\n"
  },
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: ItsLogic\r\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Report\ndescription: File a bug report\ntitle: \"[Bug]: \"\nlabels: [\"bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Make sure you fill in every section correctly and with as much detail as possible.\n  - type: input\n    id: XAU-Ver\n    attributes:\n      label: What version of the Xbox Achievement Unlocker were you using (you can see this in the info page)?\n      placeholder: 2.x\n    validations:\n      required: true\n  - type: dropdown\n    id: Tool-Section\n    attributes:\n      label: Tool Section\n      description: What area of the tool did the issue occur?\n      options:\n        - Home\n        - Games\n        - Achievements\n        - Misc- Spoofer\n        - Misc- Search\n        - Settings\n        - Other- Please put more information in the box below\n    validations:\n      required: true\n  - type: textarea\n    id: issue-description\n    attributes:\n      label: What is the bug?\n      description: What did you expect to happen and what actually happened? If the error dialog popped up paste the error message into this box (include images/video where applicable)\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature/Improvement request\ndescription: Make a request for a new feature or an improvement to an existing feature\ntitle: \"[Enhancement]: \"\nlabels: [\"enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Make sure you fill in every section correctly and with as much detail as possible.\n  - type: dropdown\n    id: RequestSelection\n    attributes:\n      label: Request Selection\n      description: Are you asking for an improvement or a new feature?\n      options:\n        - New Feature\n        - Improvement\n    validations:\n      required: true\n  - type: textarea\n    id: RequestDescription\n    attributes:\n      label: What is your request?\n      description: What do you want? Where do you want it? What do you want it to do?\n    validations:\n      required: true"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/game_request.yml",
    "content": "name: Game Request\ndescription: Request support for an event-based game\ntitle: \"[Game]: \"\nlabels: [\"Game\"]\nbody:\n  - type: markdown\n    attributes:\n      value: \"Please fill in each section accurately.\"\n  - type: checkboxes\n    id: duplicate\n    attributes:\n      label: Confirmation\n      description: Confirm before submitting\n      options:\n        - label: I have verified that this is not a duplicate request.\n          required: true\n  - type: input\n    id: GameLink\n    attributes:\n      label: Game Link\n      description: Provide a True Achievements or Xbox store link for the game you want supported.\n      placeholder: e.g., https://www.trueachievements.com/game/Forza-Horizon-3/achievements\n    validations:\n      required: true\n  - type: dropdown\n    id: Tool-Section\n    attributes:\n      label: Game Platform\n      description: Specify the platforms on which the Xbox version of this game is available.\n      options:\n        - Console only\n        - Console and PC\n        - PC only\n    validations:\n      required: true\n  - type: textarea\n    id: issue-description\n    attributes:\n      label: Additional Game Information\n      description: Provide any additional information about the game or specific achievements you want supported.\n    validations:\n      required: true"
  },
  {
    "path": ".github/workflows/Build-Events-Zip.yml",
    "content": "name: Update Events Data\n\non:\n  push:\n    paths:\n      - 'Events/**'\n      - 'Events'\n    branches:\n      - Main\n\njobs:\n  zip-and-push:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n        with:\n          submodules: recursive\n          fetch-depth: 0\n\n      - name: Ensure submodules are initialized and at the recorded commit\n        run: |\n          # sync submodule URLs, init and update to the recorded gitlinks\n          git submodule sync --recursive\n          git submodule update --init --recursive --force\n\n      - name: Install zip\n        run: sudo apt-get install zip\n\n      - name: Zip Events folder\n        run: zip -r Events.zip Events\n\n      - name: Create meta.json\n        run: |\n            echo '{\n              \"Timestamp\": '$(date +%s)',\n              \"DataVersion\": \"1.0\"\n            }' > meta.json\n\n      - name: Setup Git\n        run: |\n            git config --global user.email \"38233332+ItsLogic@users.noreply.github.com\"\n            git config --global user.name \"Events Data Action\"\n\n      - name: Create and switch to new branch\n        run: |\n            git checkout --orphan Events-Data\n            git rm --cached -r .\n\n      - name: Add files to new branch\n        run: |\n            git add Events.zip meta.json\n            git commit -m \"Update events data: $(date -u +'%Y-%m-%d %H:%M:%S')\"\n\n      - name: Force Push to new branch\n        run: git push --force origin Events-Data"
  },
  {
    "path": ".github/workflows/Build-Pre-Release.yml",
    "content": "name: .NET 8 Build\n\non:\n  push:\n    branches: [ Main ]\n    paths-ignore:\n      - '**.md'\n      - '**/ISSUE_TEMPLATE/**'\n      - '**.json'\n      - '.gitignore'\n      - '.gitattributes'\n      \n\njobs:\n  build:\n    runs-on: windows-latest\n\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: Setup .NET\n      uses: actions/setup-dotnet@v1\n      with:\n        dotnet-version: '9.0.x'\n\n    - name: Get latest commit hash\n      id: get-hash\n      run: echo \"hash=$(git rev-parse HEAD)\" >> $env:GITHUB_OUTPUT\n\n    - name: Replace ToolVersion with commit hash\n      run: |\n        $content = Get-Content -Path \"${{github.workspace}}/XAU/ViewModels/Pages/HomeViewModel.cs\" -Raw\n        $content = $content -replace 'public static string ToolVersion = \"EmptyDevToolVersion\";', 'public static string ToolVersion = \"DEV-${{ steps.get-hash.outputs.hash }}\";'\n        $content = $content -replace 'Tool Version: {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}', 'Tool Version: DEV-${{ steps.get-hash.outputs.hash }}'\n        Set-Content -Path \"${{github.workspace}}/XAU/ViewModels/Pages/HomeViewModel.cs\" -Value $content\n\n    - name: Restore dependencies\n      run: dotnet restore /p:EnableWindowsTargeting=true\n\n    - name: Build\n      run: dotnet publish XAU.sln -c Release -r win-x64 --self-contained false /p:PublishSingleFile=true /p:PublishReadyToRun=false /p:IncludeAllContentForSelfExtract=true /p:EnableWindowsTargeting=true -o ./publish\n\n    - name: Copy .exe file to temp\n      run: |\n        New-Item -ItemType Directory -Force -Path \"${{github.workspace}}/../temp\"\n        Copy-Item -Path \"./publish/XAU.exe\" -Destination \"${{github.workspace}}/../temp/\"\n\n    - name: Copy .exe file to workspace\n      run: |\n        Remove-Item -Path \"${{github.workspace}}/XAU.exe\" -ErrorAction Ignore\n        Move-Item -Path \"${{github.workspace}}/../temp/XAU.exe\" -Destination \"${{github.workspace}}/XAU.exe\"\n\n    - name: Write download URL and version to JSON file\n      run: |\n        echo \"{ `\"DownloadURL`\": `\"https://github.com/${{ github.repository }}/raw/Pre-Release/XAU.exe`\", `\"LatestBuildVersion`\": `\"${{ steps.get-hash.outputs.hash }}`\" }\" | Out-File -FilePath info.json\n\n    - name: Create and switch to new branch\n      run: |\n          git checkout --orphan Pre-Release\n          git rm -f --cached -r .\n\n    - name: Setup Git\n      run: |\n          git config --global user.email \"38233332+ItsLogic@users.noreply.github.com\"\n          git config --global user.name \"Pre Release Action\"    \n\n    - name: Add files to new branch\n      run: |\n          git add XAU.exe info.json\n          git commit -m \"Update pre-release version: $(date -u +'%Y-%m-%d %H:%M:%S')\"\n      \n    - name: Force Push to new branch\n      run: git push --force origin Pre-Release\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Ww][Ii][Nn]32/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Oo]ut/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# ASP.NET Scaffolding\nScaffoldingReadMe.txt\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*.json\ncoverage*.xml\ncoverage*.info\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n\n# Fody - auto-generated XML schema\nFodyWeavers.xsd"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"Events\"]\n\tpath = Events\n\turl = https://github.com/Fumo-Unlockers/XAU-Events\n"
  },
  {
    "path": ".idea/.idea.XAU/.idea/.gitignore",
    "content": "﻿# Default ignored files\n/shelf/\n/workspace.xml\n# Rider ignored files\n/projectSettingsUpdater.xml\n/.idea.XAU.iml\n/modules.xml\n/contentModel.xml\n# Editor-based HTTP Client requests\n/httpRequests/\n# Datasource local storage ignored files\n/dataSources/\n/dataSources.local.xml\n"
  },
  {
    "path": ".idea/.idea.XAU/.idea/encodings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\" addBOMForNewFiles=\"with BOM under Windows, with no BOM otherwise\" />\n</project>"
  },
  {
    "path": ".idea/.idea.XAU/.idea/indexLayout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"UserContentModel\">\n    <attachedFolders />\n    <explicitIncludes />\n    <explicitExcludes />\n  </component>\n</project>"
  },
  {
    "path": ".idea/.idea.XAU/.idea/riderPublish.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"LocationsWithSilentDeleteHolder\">\n    <option name=\"locations\">\n      <list>\n        <option value=\"$PROJECT_DIR$/XAU/bin/Release/net8.0-windows/win-x64/publish\" />\n      </list>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/.idea.XAU/.idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": ".run/Debug.run.xml",
    "content": "﻿<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"Debug\" type=\"DotNetProject\" factoryName=\".NET Project\">\n    <option name=\"EXE_PATH\" value=\"$PROJECT_DIR$/XAU/bin/x64/Debug/net8.0-windows/XAU.exe\" />\n    <option name=\"PROGRAM_PARAMETERS\" value=\"\" />\n    <option name=\"WORKING_DIRECTORY\" value=\"$PROJECT_DIR$/XAU/bin/x64/Debug/net8.0-windows\" />\n    <option name=\"PASS_PARENT_ENVS\" value=\"1\" />\n    <option name=\"USE_EXTERNAL_CONSOLE\" value=\"0\" />\n    <option name=\"USE_MONO\" value=\"0\" />\n    <option name=\"RUNTIME_ARGUMENTS\" value=\"\" />\n    <option name=\"PROJECT_PATH\" value=\"$PROJECT_DIR$/XAU/XAU.csproj\" />\n    <option name=\"PROJECT_EXE_PATH_TRACKING\" value=\"1\" />\n    <option name=\"PROJECT_ARGUMENTS_TRACKING\" value=\"1\" />\n    <option name=\"PROJECT_WORKING_DIRECTORY_TRACKING\" value=\"1\" />\n    <option name=\"PROJECT_KIND\" value=\"DotNetCore\" />\n    <option name=\"PROJECT_TFM\" value=\"net8.0-windows\" />\n    <method v=\"2\">\n      <option name=\"Build\" default=\"false\" projectName=\"XAU\" projectPath=\"$PROJECT_DIR$/XAU/XAU.csproj\" />\n    </method>\n  </configuration>\n</component>"
  },
  {
    "path": ".run/Publish Debug.run.xml",
    "content": "﻿<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"Publish Debug\" type=\"DotNetFolderPublish\" factoryName=\"Publish to folder\">\n    <riderPublish configuration=\"Debug\" delete_existing_files=\"true\" include_native_libs_for_self_extract=\"true\" platform=\"x64\" produce_single_file=\"true\" runtime=\"win-x64\" target_folder=\"$PROJECT_DIR$/XAU/bin/Debug/net8.0-windows/win-x64/publish\" target_framework=\"net8.0-windows\" uuid_high=\"3467633360020586717\" uuid_low=\"-5402852396017586348\" />\n    <method v=\"2\" />\n  </configuration>\n</component>"
  },
  {
    "path": ".run/Publish Release.run.xml",
    "content": "﻿<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"Publish Release\" type=\"DotNetFolderPublish\" factoryName=\"Publish to folder\">\n    <riderPublish configuration=\"Release\" delete_existing_files=\"true\" include_native_libs_for_self_extract=\"true\" platform=\"x64\" produce_single_file=\"true\" runtime=\"win-x64\" target_folder=\"$PROJECT_DIR$/XAU/bin/Release/net8.0-windows/win-x64/publish\" target_framework=\"net8.0-windows\" uuid_high=\"3467633360020586717\" uuid_low=\"-5402852396017586348\" />\n    <method v=\"2\" />\n  </configuration>\n</component>"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\n## 1. Acceptance\n\nWe believe in the inherent dignity of all individuals. We stand for the acceptance of all people, regardless of who they are, where they come from, what they believe, or how they choose to express themselves. \n\n## 2. No Hate\n\nWe strictly prohibit any form of hate speech, discrimination, or prejudice. We encourage open-mindedness and respect for all members of our community. \n\n## 3. Respect\n\nWe expect all members of our community to treat each other with respect and kindness. Disagreements should be handled with maturity and understanding. \n\n## 4. Violations\n\nViolations of this code of conduct will not be tolerated and may result in expulsion from the community. \n\nBy participating in this community, you agree to abide by this code of conduct."
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guidelines\n\nWe love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:\n\n- Reporting a bug\n- Discussing the current state of the code\n- Submitting a fix\n- Proposing new features\n\n## We Develop with Github\n\nWe use github to host code, to track issues and feature requests, as well as accept pull requests.\n\nPull Requests are the best way to propose changes to the codebase. We actively welcome your pull requests if:\n\n1. They are functional\n2. They improve the tool and/or repository\n3. They are easy to understand and check\n\n## Any contributions you make will be under the GNU GPL License\n\nIn short, when you submit code changes, your submissions are understood to be under the same [MIT License](https://choosealicense.com/licenses/gpl-3.0/) that covers the project. Feel free to contact the maintainers if that's a concern.\n\n## Report bugs using Github's issues\n\nWe use GitHub issues to track bugs. Report a bug by [opening a new issue](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/issues/new); it's that easy!\n\n## Write bug reports with detail and background\n\nGreat Bug Reports tend to have:\n\n- A quick summary and/or background\n- Steps to reproduce\n- Be specific!\n- What you expected would happen\n- What actually happens\n"
  },
  {
    "path": "Clown-Behaviour.md",
    "content": "# This a fun list of people acting like clowns\n\n## 1. S1MXN (Simon)\nThis user goes by S1MXN on [youtube](https://www.youtube.com/@S1MXN) and s1mxnyt (285913424716627970) on discord.\n\nHe made a [video](https://www.youtube.com/watch?v=aQk5OGRqzQ8) on August 1st and claimed the tool is his within this video. He then goes on to double down in the comments that the tool is his.\n\nIn this example he even claims to want to take down other peoples videos for copying his work:\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/c051cb5a-2244-4c6b-8c86-d9e509d80da1)\n\nHere are a few other funny images of him claiming that he is fixing the tool (as this version of the tool stopped working after an xbox app update)\n\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/dca8ed48-7d6b-4514-8ec4-4d9505404af4)\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/3a633cc9-3b7c-43a5-b500-04a016e17708)\n\nHe finally spoke to me on discord which resulted in this great interaction where he claims that I actually stole his work and even that I hacked him to upload the code before his video.\n\nI first pretty innocently ask about the tool as a concerned user as it has stopped working\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/4b5566f6-f773-43fe-8f52-67aa02737552)\n\nI then link this github repo and ask if the code was stolen from him\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/5004e06e-9556-42f7-b90a-1329684161db)\n\nHe responds to my surprise with yes\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/3994e933-ee00-4970-8e30-e7a95be18e51)\n\nI then ask him how the code managed to get there before his video and if his PC was hacked and he once again responds to my surprise with yes\n\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/7383c987-873a-4c67-bda1-7a03490a9395)\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/7280b2ee-ef78-42ce-a204-bf2fcedf58e3)\n\n---\n## 2. relights\nThis user goes by relights/em on discord (761088676675321856)\n\nThis user first linked the S1MXN video claiming it was the \"only legit tool\"\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/dbee43ad-0853-460b-b7db-6d06566a6e6b)\n\nThey then claim to have made the tool themselves\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/378edb83-648b-48b2-b92b-000afe78a39e)\n\nWhen called out they double down\n\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/48770120-7d0b-4f23-b2e4-8c75139e99b3)\n\nI then replied to this message with the file hashes proving they did not edit anything and its just a direct rip\n\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/a33e5ed1-1ef3-4678-9cb4-2d9f9f6c8e08)\n\nThe interaction ends with them repeating the same stuff\n![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/f712b07c-c547-445a-b31e-c0ffb5d8bf62)\n"
  },
  {
    "path": "Doc/Events.md",
    "content": "# Events Guide\n\nThis guide provides information on how to work with events, set up your system to see events, and how to use events in the tool.\n\n## Video Guide\n\nThese guides will serve as video guides to getting the prerequisites setup. Please reference the text guide below for details.\n\n### asutermo/Math/Shodan/etc's Guide\n\n[![asutermo/Math/Shodan/etc's Guide](https://img.youtube.com/vi/1UrEZi2p6w4/hqdefault.jpg)](https://youtu.be/1UrEZi2p6w4)\n\n### Cracked Fisk's Guide\n\n[![Cracked Fisk's Guide](https://img.youtube.com/vi/9-oAuq6zFVg/hqdefault.jpg)](https://www.youtube.com/watch?v=9-oAuq6zFVg)\n\n## Table of Contents\n\n- [Events Guide](#events-guide)\n  - [Video Guide](#video-guide)\n    - [asutermo/Math/Shodan/etc's Guide](#asutermomathshodanetcs-guide)\n    - [Cracked Fisk's Guide](#cracked-fisks-guide)\n  - [Table of Contents](#table-of-contents)\n  - [Information](#information)\n    - [Events Overview](#events-overview)\n      - [Anonymising Events](#anonymising-events)\n        - [Raw Event](#raw-event)\n        - [Anonymised Event](#anonymised-event)\n    - [Setup Guide](#setup-guide)\n      - [Prerequisites](#prerequisites)\n        - [Frida](#frida)\n        - [Wireshark](#wireshark)\n        - [Windows Settings](#windows-settings)\n      - [Viewing Events](#viewing-events)\n      - [Sending Events](#sending-events)\n        - [Headers](#headers)\n        - [Body](#body)\n        - [Errors](#errors)\n  - [Using Events In The Tool](#using-events-in-the-tool)\n    - [Support Overview](#support-overview)\n      - [Obtaining Your Events Token](#obtaining-your-events-token)\n    - [Adding Your Own Game](#adding-your-own-game)\n      - [Good Luck Achievements](#good-luck-achievements)\n      - [Bad Luck Achievements](#bad-luck-achievements)\n      - [Preparing a Template](#preparing-a-template)\n      - [Preparing the Criteria](#preparing-the-criteria)\n        - [Standard Replace](#standard-replace)\n        - [Range Replace (Int)](#range-replace-int)\n        - [Range Replace (Float)](#range-replace-float)\n        - [Special Replacements](#special-replacements)\n        - [Ordering Criteria](#ordering-criteria)\n      - [Examples](#examples)\n        - [Good Luck Achievement](#good-luck-achievement)\n          - [Step 1. Monitor an event of an achievement unlocking and anonymise that event data](#step-1-monitor-an-event-of-an-achievement-unlocking-and-anonymise-that-event-data)\n          - [Step 2. Figure out the important values within the data section](#step-2-figure-out-the-important-values-within-the-data-section)\n          - [Step 3. Create the template](#step-3-create-the-template)\n          - [Step 4. Create the data](#step-4-create-the-data)\n          - [Step 5. Add the achievement criteria](#step-5-add-the-achievement-criteria)\n        - [Bad Luck Achievement](#bad-luck-achievement)\n          - [Step 1. Monitor an event of an achievement unlocking and anonymise that event data](#step-1-monitor-an-event-of-an-achievement-unlocking-and-anonymise-that-event-data-1)\n          - [Step 2. Figure out the important values within the data section](#step-2-figure-out-the-important-values-within-the-data-section-1)\n          - [Step 3. Create the template](#step-3-create-the-template-1)\n          - [Step 4. Create the data](#step-4-create-the-data-1)\n          - [Step 5. Add the achievement criteria](#step-5-add-the-achievement-criteria-1)\n    - [Stats Editor](#stats-editor)\n\n## Information\n\n> [!WARNING]  \n> This document is techincal and is not intended to be user friendly. This is not intended for normal users.\n\n### Events Overview\n\nEach game has its own set of events that can modify stats. An event from Forza Horizon 3, activated when you purchase a car, as an example. The event is called `Microsoft.XboxLive.T1289871275.CarAddedToGarage`. The `T1289871275` part is the letter T followed by the game's TitleID. Most data outside of the Data section is not required to be valid, as you can see from the Anonymising Events section. Events require additional authentication, which is explained in the \"Sending Events\" section.\n\n#### Anonymising Events\n\nEvents contain lots of identifying information you should remove before sharing them on the github repo. I will provide an example of an event straight from wireshark and one prepared to be put into the [events database github repository](https://github.com/Fumo-Unlockers/Events-Database)\n\n##### Raw Event\n\n```json\n{\n  \"ver\": \"4.0\",\n  \"name\": \"Microsoft.XboxLive.T1289871275.CarAddedToGarage\",\n  \"time\": \"2024-04-07T01:11:53.6748551Z\",\n  \"iKey\": \"o:0890af88a9ed4cc886a14f5e174a2827\",\n  \"ext\": {\n    \"utc\": {\n      \"shellId\": 281572416699236352,\n      \"eventFlags\": 514,\n      \"pgName\": \"XBOX\",\n      \"flags\": 880804513,\n      \"epoch\": \"903317\",\n      \"seq\": 807\n    },\n    \"privacy\": {\n      \"isRequired\": false\n    },\n    \"metadata\": {\n      \"f\": {\n        \"baseData\": {\n          \"f\": {\n            \"properties\": {\n              \"f\": {\n                \"BuildNum\": 4,\n                \"CarId\": 4,\n                \"IsHorizonEdition\": 4,\n                \"NumCarsInGarage\": 4,\n                \"Track\": 4\n              }\n            }\n          }\n        }\n      },\n      \"policies\": 0\n    },\n    \"os\": {\n      \"bootId\": 284,\n      \"name\": \"Windows\",\n      \"ver\": \"10.0.22621.3296.amd64fre.ni_release.220506-1250\",\n      \"expId\": \"RS:1B54D,MD:283BAEF,ME:28279A6,MD:2A69053,MD:255521A\"\n    },\n    \"app\": {\n      \"id\": \"U:Microsoft.OpusPG_1.0.125.2_x64__8wekyb3d8bbwe!OpusReleaseFinal\",\n      \"ver\": \"1.0.125.2_x64_!2018/06/05:22:52:33!60EF05C!forza_x64_release_final.exe\",\n      \"is1P\": 1,\n      \"asId\": 898\n    },\n    \"device\": {\n      \"localId\": \"s:0039DA16-8F86-463F-9EBE-CB0619831735\",\n      \"deviceClass\": \"Windows.Desktop\"\n    },\n    \"protocol\": {\n      \"devMake\": \"Micro-Star International Co., Ltd.\",\n      \"devModel\": \"MS-7E06\",\n      \"ticketKeys\": [\"21171495\"]\n    },\n    \"user\": {\n      \"localId\": \"w:EFEB6F6A-3B57-C3DA-5A6B-B5908C25B28C\"\n    },\n    \"loc\": {\n      \"tz\": \"+01:00\"\n    }\n  },\n  \"data\": {\n    \"baseType\": \"Microsoft.XboxLive.InGame\",\n    \"baseData\": {\n      \"name\": \"CarAddedToGarage\",\n      \"serviceConfigId\": \"19020100-9575-4c2b-9916-3d664ce1dfab\",\n      \"playerSessionId\": \"DBF9D623-02F2-4CAB-866E-EDF6908C9491\",\n      \"titleId\": \"1289871275\",\n      \"userId\": \"1234567890123456\",\n      \"ver\": 1,\n      \"properties\": {\n        \"BuildNum\": 156858,\n        \"CarId\": 302,\n        \"IsHorizonEdition\": 0,\n        \"NumCarsInGarage\": 20,\n        \"Track\": 552\n      }\n    }\n  }\n}\n```\n\n##### Anonymised Event\n\n```json\n{\n  \"ver\": \"4.0\",\n  \"name\": \"Microsoft.XboxLive.T1289871275.CarAddedToGarage\",\n  \"time\": \"REPLACETIME\",\n  \"iKey\": \"o:0890af88a9ed4cc886a14f5e174a2827\",\n  \"ext\": {\n    \"utc\": {\n      \"shellId\": 1,\n      \"eventFlags\": 1,\n      \"pgName\": \"XBOX\",\n      \"flags\": 1,\n      \"epoch\": \"1\",\n      \"seq\": REPLACESEQ\n    },\n    \"privacy\": {\n      \"isRequired\": false\n    },\n    \"metadata\": {\n      \"f\": {\n        \"baseData\": {\n          \"f\": {\n            \"properties\": {\n              \"f\": {\n                \"BuildNum\": 1,\n                \"CarId\": 1,\n                \"IsHorizonEdition\": 1,\n                \"NumCarsInGarage\": 1,\n                \"Track\": 1\n              }\n            }\n          }\n        }\n      },\n      \"policies\": 0\n    },\n    \"os\": {\n      \"bootId\": 1,\n      \"name\": \"1\",\n      \"ver\": \"1\",\n      \"expId\": \"1\"\n    },\n    \"app\": {\n      \"id\": \"U:Microsoft.OpusPG_1.0.125.2_x64__8wekyb3d8bbwe!OpusReleaseFinal\",\n      \"ver\": \"1.0.125.2_x64_!2018/06/05:22:52:33!60EF05C!forza_x64_release_final.exe\",\n      \"is1P\": 1,\n      \"asId\": 1\n    },\n    \"device\": {\n      \"localId\": \"s:11111111-1111-1111-1111-111111111111\",\n      \"deviceClass\": \"Windows.Desktop\"\n    },\n    \"protocol\": {\n      \"devMake\": \"1\",\n      \"devModel\": \"1\",\n      \"ticketKeys\": [\n        \"1\"\n      ]\n    },\n    \"user\": {\n      \"localId\": \"w:11111111-1111-1111-1111-111111111111\"\n    },\n    \"loc\": {\n      \"tz\": \"+01:00\"\n    }\n  },\n  \"data\": {\n    \"baseType\": \"Microsoft.XboxLive.InGame\",\n    \"baseData\": {\n      \"name\": \"CarAddedToGarage\",\n      \"serviceConfigId\": \"19020100-9575-4c2b-9916-3d664ce1dfab\",\n      \"playerSessionId\": \"11111111-1111-1111-1111-111111111111\",\n      \"titleId\": \"1289871275\",\n      \"userId\": \"REPLACEXUID\",\n      \"ver\": 1,\n      \"properties\": {\n        \"BuildNum\": 156858,\n        \"CarId\": 302,\n        \"IsHorizonEdition\": 0,\n        \"NumCarsInGarage\": 20,\n        \"Track\": 552\n      }\n    }\n  }\n}\n```\n\n### Setup Guide\n\nThis section guides you through the process of setting up your system to view events.\n\n#### Prerequisites\n\nTo monitor events and use the event-based unlocker in the tool, you need to install Wireshark and Frida and configure them.\n\n##### Frida\n\nThe easiest way to install frida is using python pip. You can install frida by running the command\n\n```\npip install frida-tools\n```\n\nYou can view all install methods on the github repository: https://github.com/frida/frida\n\n> [!WARNING]  \n> You must do the following steps if you are using Windows 11\n>\n> 1. Open Windows Security\n> 2. App & Browser Control>Exploit Protection>Programme Settings\n> 3. Press the plus, Select program by name and enter `lsass.exe`\n> 4. Scroll down until you see `Hardware Enforced Stack Protection`\n> 5. Copy these settings:\n>\n> ![Settings](Settings.png)\n\nYou also need a script to extract data from lsass.\nDownload the following [script](https://raw.githubusercontent.com/ngo/win-frida-scripts/master/lsasslkeylog-easy/keylog.js) and save it somewhere you will remember for the Viewing Events section\n\n##### Wireshark\n\n1. Install wireshark using the installer on the [website](https://www.wireshark.org/download.html)\n2. Make an empty file on the root of your C: drive `C:\\keylog.log`\n3. Open wireshark and go to Edit>Preferences\n4. Click on Name Resolution and make sure your settings are set like this:\n\n   ![Wireshark 1](Wireshark1.png)\n\n5. Go to Protocols>TLS and then select `C:keylog.log` as (Pre)-Master-Secret log filename\n\n##### Windows Settings\n\n1. Press Win+R and type in `services.msc`\n2. Open this and scroll until you see `Connected User Experiences and Telemetry`\n3. Double click this and change the `Startup Type` to `Automatic`\n4. Restart (maybe?)\n\n#### Viewing Events\n\nThis section explains how to use Wireshark and Frida to view events from an event-based game.\n\n1. Open an administrator terminal\n2. Run frida using the following command `frida lsass.exe -l \\Path\\To\\keylog.js `\n3. You should get output like this:\n\n   ![Frida 1](Frida1.png)\n\n4. Open wireshark and select your network interface\n5. Put the following string into your filter box `tls && http2 && ip.dst_host contains \"cloudapp.azure.com\"`\n6. Open your game of choice and start doing things\n7. Look back at wireshark and you will (hopefully) see requests to a few cloudapp.azure.com domains\n\n   ![Wireshark 2](Wireshark2.png)\n\n8. Now click on \"DATA\" and then navigate to `Uncompressed entity body`\n9. You can now read events (yay)\n\n#### Sending Events\n\nThis section will be relatively bare as I assume anyone who wants to test and play around with events like this is smart enough to figure stuff out with minimal handholding. I will be using the visual studio code extension `Thunder Client` but any HTTP client will do\nTo start off you will need an event to work with. Get one from the Viewing Events section\n\n##### Headers\n\nNow not all headers are required. You only need the following headers:\n\n![Thunder1](Thunder1.png)\n\nAll headers other than `tickets` and `authxtoken` are static.\n\n##### Body\n\nThe json body **_cannot_** be formatted. The endpoint will throw back a parsing error if it is. Use a json minifier before sending the data away if you want to edit it while it is formatted nicely.\nYou must also increment the `seq` value every time after sending an event. This increment does not need to be by 1 so if you lose your place you can just add 100 or 1000 to the value\n\n##### Errors\n\nThe endpoint is nice about formatting errors and it tells you when they exist but other than that it is pretty quiet about errors. If any of your auth tokens are expired it will silently fail while giving a 200 response and the same acc 1 json it would on a success.\nThe same goes for not incrementing seq\n\n## Using Events In The Tool\n\n### Support Overview\n\nThe tool supports manually defined achievement criteria and has a stats editor in progress.\n\n#### Obtaining Your Events Token\n\nTo use the event-based functionality in the tool, you need to input your own events token.\n\n1. Follow the guide for Viewing Events\n2. Instead of clicking on `DATA` click on `HEADERS`\n3. You want to scroll until you see a tickets header that starts with something like `\"2145125\"=\"x:XBL3.0 x=\"` (The number will be different)\n4. Copy from the start of `x:XBL3.0 x=` and stop before the last `\"`. You should completely skip the number and anything before the first x\n5. Your copied token should look something like this `x:XBL3.0 x=somenumbers;a very large amount of letters`\n6. You now have a usable Events Token to input into the tool\n\n### Adding Your Own Game\n\nThis section explains how to add support for a game, find the correct events and stats, and prepare a template and criteria.\n\n#### Good Luck Achievements\n\nThese achievements work similarly to title-based games and only require an ID input into a generic \"achievement unlocked\" event.\nAs an example here is the data section of a Gears of War 4 event called `Microsoft.XboxLive.T552499398.UnlockAchievement`\n\n```json\n\"data\": {\n    \"baseType\": \"Microsoft.XboxLive.InGame\",\n    \"baseData\": {\n      \"name\": \"UnlockAchievement\",\n      \"serviceConfigId\": \"780f0100-3c66-41ff-b8cc-964f20ee78c6\",\n      \"playerSessionId\": \"11111111-1111-1111-1111-111111111111\",\n      \"titleId\": \"552499398\",\n      \"userId\": \"1234567890123456\",\n      \"ver\": 1,\n      \"properties\": {\n        \"AchievementID\": 1,\n        \"MatchId\": \"Front End\",\n        \"MatchJoinId\": 0\n      },\n      \"measurements\": {\n        \"ProgressPercent\": 1\n      }\n    }\n  }\n```\n\nThese achievements are very easy to unlock and don't require much work to support as you can just try every AchivementID and then take note of which achievements unlock to then write the criteria.\n\n#### Bad Luck Achievements\n\nThese achievements require specific stats to be unlocked. These achievements are much harder to work with and take more work to support (usually due to multiple requirements per achievement). As an example I will use the data section of a Halo MCC event called `Microsoft.XboxLive.T1144039928.MissionCompleted` This is a generic Mission Complete event which means it will handle most if not all mission completion achievements for this game.\n\n```json\n\"data\": {\n    \"baseType\": \"Microsoft.XboxLive.InGame\",\n    \"baseData\": {\n      \"name\": \"MissionCompleted\",\n      \"serviceConfigId\": \"77290100-225e-4768-9373-98164430a9f8\",\n      \"titleId\": \"1144039928\",\n      \"userId\": \"1234567890123456\",\n      \"properties\": {\n        \"Coop\": 0,\n        \"DatePlayedUTC\": 133577376536650000,\n        \"DifficultyId\": 1,\n        \"GameCategoryId\": 18,\n        \"HaloTitleId\": \"HaloReach\",\n        \"Kills\": 358,\n        \"MapId\": 179,\n        \"MissionScore64\": 235767,\n        \"NumPlayers\": 1,\n        \"Penalties\": 0,\n        \"PlayerSectionStats\": \"{\\\\\\\"scores\\\\\\\":[0.0,0.0,1553.0,2669.0,3398.0],\\\\\\\"interpolatedScores\\\\\\\":[0.0,0.0,4659.0,7995.43408203125,7169.02197265625],\\\\\\\"times\\\\\\\":[0,1,2,3,4],\\\\\\\"sectionIDs\\\\\\\":[652215696,534971578,329253778,976357054,976684729],\\\\\\\"deaths\\\\\\\":0,\\\\\\\"numPlayers\\\\\\\":1,\\\\\\\"skullMask\\\\\\\":0,\\\\\\\"skullCount\\\\\\\":0}\",\n        \"SkullUsedFlags\": 0,\n        \"TimePlayedMS\": 92356,\n        \"TotalCoopMissionsComplete\": 0,\n        \"TotalSoloMissionsComplete\": 2\n      },\n      \"measurements\": {\n        \"Multiplier\": 2.35,\n        \"SkullMultiplier\": 1\n      }\n    }\n  }\n```\n\nAs you can see there is a significant amount of data in this data section. For example, to meet criteria for a mission complete the only thing needed is the correct `MapId` however if you wanted to unlock the achievement for par score you would also need a `MissionScore64` value above the par score criteria, the same goes for par time and `TimePlayedMS` although you would want it under the criteria in that case. Guessing from other data available this event could also be used to get the achievements for LASO runs and possibly even some kill related achievements.\n\n#### Preparing a Template\n\nEach game needs a template, I will be using Halo MCC as an example for this section. I will show criteria for both a Good Luck and a Bad Luck achievement.\nYou first need to anonymise the event using the [Anonymising Events](#anonymising-events) section\nNow you need to figure out which criteria you need to create and where you would need to place these criteria\n\n#### Preparing the Criteria\n\nThe criteria replace certain strings within the template.\n\n##### Standard Replace\n\nThis is the most common type of replacement for criteria and the most simple.\nExample from Achievement 900 from halo MCC\n\n```json\n\"Replacement1\": {\n          \"ReplacementType\": \"Replace\",\n          \"Target\": \"REPLACEMAPID\",\n          \"Replacement\": 179\n        },\n```\n\nThis replaces the string `REPLACEMAPID` with the number `179`\n\n##### Range Replace (Int)\n\nThis type of replacement will be required when data is on leaderboards like halo MCC where I would not like all users to have exactly the same statistics as well as for other achievements relating to clearing something within a certain time.\nExample from Achievement 900 from halo MCC:\n\n```json\n\"Replacement3\": {\n          \"ReplacementType\": \"RangeInt\",\n          \"Target\": \"REPLACEMISSIONSCORE\",\n          \"Min\": 12750,\n          \"Max\": 14999\n        },\n```\n\nThis replaces the string `REPLACEMISSIONSCORE` with a number between `12750` and `14999`\n\n##### Range Replace (Float)\n\nThis type of replacement is the same as the above but for Float numbers instead of Integers\n\nExample from Achievement 900 from halo MCC:\n\n```json\n\"Replacement5\": {\n          \"ReplacementType\": \"RangeFloat\",\n          \"Target\": \"REPLACEMULTIPLIER\",\n          \"Min\": 1,\n          \"Max\": 2\n        },\n```\n\nThis replaces the string `REPLACEMULTIPLIER` with a float between `1` and `2`\n\n##### Special Replacements\n\nThese replacements are either required by the tool or are created as exceptions to fill out data otherwise impossible.\n\nThey currently consist of:\n\n- REPLACEXUID (Required)\n- REPLACETIME (Required)\n- REPLACESEQ (Required)\n- LDAP timestamp (Exception: Created for Halo MCC mission completion timestamp)\n\n##### Ordering Criteria\n\nThe order of the criteria within the data.json file is important as the tool starts from the top and works its way down. This allows you to replace parts of data you have previously placed.\n\n#### Examples\n\n##### Good Luck Achievement\n\nIn this example, I will be using Gears Of War 4\n\n###### Step 1. Monitor an event of an achievement unlocking and anonymise that event data\n\n```json\n{\n    \"ver\": \"4.0\",\n    \"name\": \"Microsoft.XboxLive.T552499398.UnlockAchievement\",\n    \"time\": \"REPLACETIME\",\n    \"iKey\": \"o:0890af88a9ed4cc886a14f5e174a2827\",\n    \"ext\": {\n        \"utc\": {\n            \"shellId\": 1,\n            \"eventFlags\": 1,\n            \"pgName\": \"XBOX\",\n            \"flags\": 1,\n            \"epoch\": \"1\",\n            \"seq\": REPLACESEQ\n        },\n        \"privacy\": {\n            \"isRequired\": false\n        },\n        \"metadata\": {\n            \"f\": {\n                \"baseData\": {\n                    \"f\": {\n                        \"properties\": {\n                            \"f\": {\n                                \"AchievementID\": 1,\n                                \"MatchJoinId\": 1\n                            }\n                        },\n                        \"measurements\": {\n                            \"f\": {\n                                \"ProgressPercent\": 1\n                            }\n                        }\n                    }\n                }\n            },\n            \"policies\": 0\n        },\n        \"os\": {\n            \"bootId\": 1,\n            \"name\": \"1\",\n            \"ver\": \"1\",\n            \"expId\": \"1\"\n        },\n        \"app\": {\n            \"id\": \"U:Microsoft.SpartaUWP_14.4.0.2_x64__8wekyb3d8bbwe!GearGameShippingPublic\",\n            \"ver\": \"14.4.0.2_x64_!2019/06/27:23:38:26!9474C5E!geargame.exe\",\n            \"is1P\": 1,\n            \"asId\": 1\n        },\n        \"device\": {\n            \"localId\": \"s:11111111-1111-1111-1111-111111111111\",\n            \"deviceClass\": \"Windows.Desktop\"\n        },\n        \"protocol\": {\n            \"devMake\": \"1\",\n            \"devModel\": \"1\",\n            \"ticketKeys\": [\n                \"1\"\n            ]\n        },\n        \"user\": {\n            \"localId\": \"s:11111111-1111-1111-1111-111111111111\"\n        },\n        \"loc\": {\n            \"tz\": \"00:00\"\n        }\n    },\n    \"data\": {\n        \"baseType\": \"Microsoft.XboxLive.InGame\",\n        \"baseData\": {\n            \"name\": \"UnlockAchievement\",\n            \"serviceConfigId\": \"780f0100-3c66-41ff-b8cc-964f20ee78c6\",\n            \"playerSessionId\": \"11111111-1111-1111-1111-111111111111\",\n            \"titleId\": \"552499398\",\n            \"userId\": \"REPLACEXUID\",\n            \"ver\": 1,\n            \"properties\": {\n                \"AchievementID\": 4,\n                \"MatchId\": \"11111111-1111-1111-1111-111111111111\",\n                \"MatchJoinId\": 0\n            },\n            \"measurements\": {\n                \"ProgressPercent\": 1\n            }\n        }\n    }\n}\n```\n\n###### Step 2. Figure out the important values within the data section\n\nIn this case, it would be the AchievementID which is 4. We can check our recently unlocked achievements to see that this ID matches up with the Triple Play achievement which is ID 70 on the API\n\n###### Step 3. Create the template\n\nKnowing that AchievementId is our only important value we can set up the template like this:\n\n```json\n{\n    \"ver\": \"4.0\",\n    \"name\": \"Microsoft.XboxLive.T552499398.UnlockAchievement\",\n    \"time\": \"REPLACETIME\",\n    \"iKey\": \"o:0890af88a9ed4cc886a14f5e174a2827\",\n    \"ext\": {\n        \"utc\": {\n            \"shellId\": 1,\n            \"eventFlags\": 1,\n            \"pgName\": \"XBOX\",\n            \"flags\": 1,\n            \"epoch\": \"1\",\n            \"seq\": REPLACESEQ\n        },\n        \"privacy\": {\n            \"isRequired\": false\n        },\n        \"metadata\": {\n            \"f\": {\n                \"baseData\": {\n                    \"f\": {\n                        \"properties\": {\n                            \"f\": {\n                                \"AchievementID\": 1,\n                                \"MatchJoinId\": 1\n                            }\n                        },\n                        \"measurements\": {\n                            \"f\": {\n                                \"ProgressPercent\": 1\n                            }\n                        }\n                    }\n                }\n            },\n            \"policies\": 0\n        },\n        \"os\": {\n            \"bootId\": 1,\n            \"name\": \"1\",\n            \"ver\": \"1\",\n            \"expId\": \"1\"\n        },\n        \"app\": {\n            \"id\": \"U:Microsoft.SpartaUWP_14.4.0.2_x64__8wekyb3d8bbwe!GearGameShippingPublic\",\n            \"ver\": \"14.4.0.2_x64_!2019/06/27:23:38:26!9474C5E!geargame.exe\",\n            \"is1P\": 1,\n            \"asId\": 1\n        },\n        \"device\": {\n            \"localId\": \"s:11111111-1111-1111-1111-111111111111\",\n            \"deviceClass\": \"Windows.Desktop\"\n        },\n        \"protocol\": {\n            \"devMake\": \"1\",\n            \"devModel\": \"1\",\n            \"ticketKeys\": [\n                \"1\"\n            ]\n        },\n        \"user\": {\n            \"localId\": \"s:11111111-1111-1111-1111-111111111111\"\n        },\n        \"loc\": {\n            \"tz\": \"00:00\"\n        }\n    },\n    \"data\": {\n        \"baseType\": \"Microsoft.XboxLive.InGame\",\n        \"baseData\": {\n            \"name\": \"UnlockAchievement\",\n            \"serviceConfigId\": \"780f0100-3c66-41ff-b8cc-964f20ee78c6\",\n            \"playerSessionId\": \"11111111-1111-1111-1111-111111111111\",\n            \"titleId\": \"552499398\",\n            \"userId\": \"REPLACEXUID\",\n            \"ver\": 1,\n            \"properties\": {\n                \"AchievementID\": REPLACECRITERIA1,\n                \"MatchId\": \"11111111-1111-1111-1111-111111111111\",\n                \"MatchJoinId\": 0\n            },\n            \"measurements\": {\n                \"ProgressPercent\": 1\n            }\n        }\n    }\n}\n```\n\n`REPLACECRITERIA1` is the only thing we need to change to unlock these Good Luck achievements.\nThis file will be saved as the TitleID `552499398.json`\n\n###### Step 4. Create the data\n\nOpen `Data.json` and check to see if `552499398` is already in the `SupportedTitleIDs` list. It is not, meaning we need to add a new game entry to this file. The only supported game is currently Halo MCC\n![Data.json1](Data.json.png)\n\nFirst, add the title ID to `SupportedTitleIDs`\n\n```json\n\"SupportedTitleIDs\": [\n    1144039928,\n    552499398\n  ]\n```\n\nSecondly, create a new section within the json file\n\n```json\n\"552499398\": {\n    \"FullySupported\": false,\n    \"Achievements\": {\n    }\n  }\n```\n\nThe json file should now look like this:\n\n![Data.json2](Data.json2.png)\n\n###### Step 5. Add the achievement criteria\n\nWe know that 4 matches up with achievement id 70 so we will add this\n\n```json\n\"552499398\": {\n    \"FullySupported\": false,\n    \"Achievements\": {\n      \"70\": {\n        \"CriteriaReplacement\": {\n          \"ReplacementType\": \"Replace\",\n          \"Target\": \"REPLACECRITERIA1\",\n          \"Replacement\": \"4\"\n        }\n      }\n    }\n  },\n```\n\n##### Bad Luck Achievement\n\nThe majority of this guide is the same as Good Luck achievement so I will only go over the changes\n\n###### Step 1. Monitor an event of an achievement unlocking and anonymise that event data\n\n```json\n{\n    \"ver\": \"4.0\",\n    \"name\": \"Microsoft.XboxLive.T1144039928.MissionCompleted\",\n    \"time\": \"REPLACETIME\",\n    \"iKey\": \"o:0890af88a9ed4cc886a14f5e174a2827\",\n    \"ext\": {\n        \"utc\": {\n            \"shellId\": 1,\n            \"eventFlags\": 1,\n            \"pgName\": \"XBOX\",\n            \"flags\": 1,\n            \"epoch\": \"1\",\n            \"seq\": REPLACESEQ\n        },\n        \"privacy\": {\n            \"dataType\": 1,\n            \"isRequired\": false\n        },\n        \"metadata\": {\n            \"f\": {\n                \"baseData\": {\n                    \"f\": {\n                        \"properties\": {\n                            \"f\": {\n                                \"AchievementId\": 1\n                            }\n                        },\n                        \"ver\": 1\n                    }\n                }\n            },\n            \"privTags\": 1,\n            \"policies\": 0\n        },\n        \"os\": {\n            \"bootId\": 1,\n            \"name\": \"1\",\n            \"ver\": \"1\",\n            \"expId\": \"1\"\n        },\n        \"app\": {\n            \"id\": \"U:Microsoft.Chelan_1.3385.0.0_x64__8wekyb3d8bbwe!HaloMCCShipping\",\n            \"ver\": \"1.3385.0.0_x64_!2024/01/03:17:50:38!3E58AE2!mccwinstore-win64-shipping.exe\",\n            \"is1P\": 1,\n            \"asId\": 1\n        },\n        \"device\": {\n            \"localId\": \"s:11111111-1111-1111-1111-111111111111\",\n            \"deviceClass\": \"Windows.Desktop\"\n        },\n        \"protocol\": {\n            \"devMake\": \"1\",\n            \"devModel\": \"1\",\n            \"ticketKeys\": [\n                \"1\"\n            ]\n        },\n        \"user\": {\n            \"localId\": \"m:11111111111111111\"\n        },\n        \"loc\": {\n            \"tz\": \"00:00\"\n        }\n    },\n    \"data\": {\n        \"baseType\": \"Microsoft.XboxLive.InGame\",\n        \"baseData\": {\n            \"name\": \"MissionCompleted\",\n            \"serviceConfigId\": \"77290100-225e-4768-9373-98164430a9f8\",\n            \"titleId\": \"1144039928\",\n            \"userId\": \"REPLACEXUID\",\n            \"properties\": {\n                \"Coop\": 0,\n                \"DatePlayedUTC\": 133583987328905914,\n                \"DifficultyId\": 1,\n                \"GameCategoryId\": 18,\n                \"HaloTitleId\": \"HaloReach\",\n                \"Kills\": 257,\n                \"MapId\": 179,\n                \"MissionScore64\": 13327,\n                \"NumPlayers\": 1,\n                \"Penalties\": 0,\n                \"PlayerSectionStats\": \"{\\\\\\\"scores\\\\\\\":[0.0,1.0,2.0,3.0,4.0],\\\\\\\"interpolatedScores\\\\\\\":[0.0,1.0,2.0,3.0,4.0],\\\\\\\"times\\\\\\\":[0,1,2,3,4],\\\\\\\"sectionIDs\\\\\\\":[1,2,3,4,5],\\\\\\\"deaths\\\\\\\":0,\\\\\\\"numPlayers\\\\\\\":1,\\\\\\\"skullMask\\\\\\\":0,\\\\\\\"skullCount\\\\\\\":0}\",\n                \"SkullUsedFlags\": 0,\n                \"TimePlayedMS\": 1014169,\n                \"TotalCoopMissionsComplete\": 0,\n                \"TotalSoloMissionsComplete\": 1\n            },\n            \"measurements\": {\n                \"Multiplier\": 1.3916192,\n                \"SkullMultiplier\": 1\n            }\n        }\n    }\n}\n```\n\n###### Step 2. Figure out the important values within the data section\n\nThere are lots of important values in this section however we are only focusing on Par Time, Par score and normal completion achievements. I know this event is for the level winter contingency which I will assume is `MapId` 179. I can assume I will need a `MissionScore64` of >15000 for the Par score achievement and a `TimePlayedMS` of <900000 for the Par time achievement. `Kills` and `DatePlayedUTC` are also important values but have no bearing on the achievement criteria for the achievements we are currently working on\n\n###### Step 3. Create the template\n\nStep not required as MCC is already supported\n\n###### Step 4. Create the data\n\nStep not required as MCC is already supported\n\n###### Step 5. Add the achievement criteria\n\nI will be adding three achievements here. Due to MCC having both bad luck and good luck achievements I need to replace the event name and data section for every achievement. These three achievements will be: `We're Just Getting Started (900)`, `Winter Urgency(915)` `Ice In Your Veins(925)`\n\n```json\n\"900\": {\n        \"EventReplacement\": {\n          \"ReplacementType\": \"Replace\",\n          \"Target\": \"REPLACEEVENT\",\n          \"Replacement\": \"Microsoft.XboxLive.T1144039928.MissionCompleted\"\n        },\n        \"DataReplacement\": {\n          \"ReplacementType\": \"Replace\",\n          \"Target\": \"REPLACEDATA\",\n          \"Replacement\": \"{\\\"baseType\\\":\\\"Microsoft.XboxLive.InGame\\\",\\\"baseData\\\":{\\\"name\\\":\\\"MissionCompleted\\\",\\\"serviceConfigId\\\":\\\"77290100-225e-4768-9373-98164430a9f8\\\",\\\"titleId\\\":\\\"1144039928\\\",\\\"userId\\\":\\\"REPLACEXUID\\\",\\\"properties\\\":{\\\"Coop\\\":0,\\\"DatePlayedUTC\\\":REPLACEDATEPLAYED,\\\"DifficultyId\\\":1,\\\"GameCategoryId\\\":18,\\\"HaloTitleId\\\":\\\"HaloReach\\\",\\\"Kills\\\":REPLACEKILLS,\\\"MapId\\\":REPLACEMAPID,\\\"MissionScore64\\\":REPLACEMISSIONSCORE,\\\"NumPlayers\\\":1,\\\"Penalties\\\":0,\\\"PlayerSectionStats\\\":\\\"{\\\\\\\\\\\\\\\"scores\\\\\\\\\\\\\\\":[0.0,1.0,2.0,3.0,4.0],\\\\\\\\\\\\\\\"interpolatedScores\\\\\\\\\\\\\\\":[0.0,1.0,2.0,3.0,4.0],\\\\\\\\\\\\\\\"times\\\\\\\\\\\\\\\":[0,1,2,3,4],\\\\\\\\\\\\\\\"sectionIDs\\\\\\\\\\\\\\\":[1,2,3,4,5],\\\\\\\\\\\\\\\"deaths\\\\\\\\\\\\\\\":0,\\\\\\\\\\\\\\\"numPlayers\\\\\\\\\\\\\\\":1,\\\\\\\\\\\\\\\"skullMask\\\\\\\\\\\\\\\":0,\\\\\\\\\\\\\\\"skullCount\\\\\\\\\\\\\\\":0}\\\",\\\"SkullUsedFlags\\\":0,\\\"TimePlayedMS\\\":REPLACETIMEPLAYED,\\\"TotalCoopMissionsComplete\\\":0,\\\"TotalSoloMissionsComplete\\\":1},\\\"measurements\\\":{\\\"Multiplier\\\":REPLACEMULTIPLIER,\\\"SkullMultiplier\\\":1}}}\"\n        },\n        \"Replacement1\": {\n          \"ReplacementType\": \"Replace\",\n          \"Target\": \"REPLACEMAPID\",\n          \"Replacement\": 179\n        },\n        \"Replacement2\": {\n          \"ReplacementType\": \"RangeInt\",\n          \"Target\": \"REPLACEKILLS\",\n          \"Min\": 200,\n          \"Max\": 300\n        },\n        \"Replacement3\": {\n          \"ReplacementType\": \"RangeInt\",\n          \"Target\": \"REPLACEMISSIONSCORE\",\n          \"Min\": 12750,\n          \"Max\": 14999\n        },\n        \"Replacement4\": {\n          \"ReplacementType\": \"RangeInt\",\n          \"Target\": \"REPLACETIMEPLAYED\",\n          \"Min\": 900001,\n          \"Max\": 1035000\n        },\n        \"Replacement5\": {\n          \"ReplacementType\": \"RangeFloat\",\n          \"Target\": \"REPLACEMULTIPLIER\",\n          \"Min\": 1,\n          \"Max\": 2\n        },\n        \"Replacement6\": {\n          \"ReplacementType\": \"StupidFuckingLDAPTimestamp\",\n          \"Target\": \"REPLACEDATEPLAYED\"\n        }\n      },\n      \"915\": {\n        \"EventReplacement\": {\n          \"ReplacementType\": \"Replace\",\n          \"Target\": \"REPLACEEVENT\",\n          \"Replacement\": \"Microsoft.XboxLive.T1144039928.MissionCompleted\"\n        },\n        \"DataReplacement\": {\n          \"ReplacementType\": \"Replace\",\n          \"Target\": \"REPLACEDATA\",\n          \"Replacement\": \"{\\\"baseType\\\":\\\"Microsoft.XboxLive.InGame\\\",\\\"baseData\\\":{\\\"name\\\":\\\"MissionCompleted\\\",\\\"serviceConfigId\\\":\\\"77290100-225e-4768-9373-98164430a9f8\\\",\\\"titleId\\\":\\\"1144039928\\\",\\\"userId\\\":\\\"REPLACEXUID\\\",\\\"properties\\\":{\\\"Coop\\\":0,\\\"DatePlayedUTC\\\":REPLACEDATEPLAYED,\\\"DifficultyId\\\":1,\\\"GameCategoryId\\\":18,\\\"HaloTitleId\\\":\\\"HaloReach\\\",\\\"Kills\\\":REPLACEKILLS,\\\"MapId\\\":REPLACEMAPID,\\\"MissionScore64\\\":REPLACEMISSIONSCORE,\\\"NumPlayers\\\":1,\\\"Penalties\\\":0,\\\"PlayerSectionStats\\\":\\\"{\\\\\\\\\\\\\\\"scores\\\\\\\\\\\\\\\":[0.0,1.0,2.0,3.0,4.0],\\\\\\\\\\\\\\\"interpolatedScores\\\\\\\\\\\\\\\":[0.0,1.0,2.0,3.0,4.0],\\\\\\\\\\\\\\\"times\\\\\\\\\\\\\\\":[0,1,2,3,4],\\\\\\\\\\\\\\\"sectionIDs\\\\\\\\\\\\\\\":[1,2,3,4,5],\\\\\\\\\\\\\\\"deaths\\\\\\\\\\\\\\\":0,\\\\\\\\\\\\\\\"numPlayers\\\\\\\\\\\\\\\":1,\\\\\\\\\\\\\\\"skullMask\\\\\\\\\\\\\\\":0,\\\\\\\\\\\\\\\"skullCount\\\\\\\\\\\\\\\":0}\\\",\\\"SkullUsedFlags\\\":0,\\\"TimePlayedMS\\\":REPLACETIMEPLAYED,\\\"TotalCoopMissionsComplete\\\":0,\\\"TotalSoloMissionsComplete\\\":1},\\\"measurements\\\":{\\\"Multiplier\\\":REPLACEMULTIPLIER,\\\"SkullMultiplier\\\":1}}}\"\n        },\n        \"Replacement1\": {\n          \"ReplacementType\": \"Replace\",\n          \"Target\": \"REPLACEMAPID\",\n          \"Replacement\": 179\n        },\n        \"Replacement2\": {\n          \"ReplacementType\": \"RangeInt\",\n          \"Target\": \"REPLACEKILLS\",\n          \"Min\": 200,\n          \"Max\": 300\n        },\n        \"Replacement3\": {\n          \"ReplacementType\": \"RangeInt\",\n          \"Target\": \"REPLACEMISSIONSCORE\",\n          \"Min\": 12750,\n          \"Max\": 14999\n        },\n        \"Replacement4\": {\n          \"ReplacementType\": \"RangeInt\",\n          \"Target\": \"REPLACETIMEPLAYED\",\n          \"Min\": 765000,\n          \"Max\": 899999\n        },\n        \"Replacement5\": {\n          \"ReplacementType\": \"RangeFloat\",\n          \"Target\": \"REPLACEMULTIPLIER\",\n          \"Min\": 1,\n          \"Max\": 2\n        },\n        \"Replacement6\": {\n          \"ReplacementType\": \"StupidFuckingLDAPTimestamp\",\n          \"Target\": \"REPLACEDATEPLAYED\"\n        }\n      },\n      \"925\": {\n        \"EventReplacement\": {\n          \"ReplacementType\": \"Replace\",\n          \"Target\": \"REPLACEEVENT\",\n          \"Replacement\": \"Microsoft.XboxLive.T1144039928.MissionCompleted\"\n        },\n        \"DataReplacement\": {\n          \"ReplacementType\": \"Replace\",\n          \"Target\": \"REPLACEDATA\",\n          \"Replacement\": \"{\\\"baseType\\\":\\\"Microsoft.XboxLive.InGame\\\",\\\"baseData\\\":{\\\"name\\\":\\\"MissionCompleted\\\",\\\"serviceConfigId\\\":\\\"77290100-225e-4768-9373-98164430a9f8\\\",\\\"titleId\\\":\\\"1144039928\\\",\\\"userId\\\":\\\"REPLACEXUID\\\",\\\"properties\\\":{\\\"Coop\\\":0,\\\"DatePlayedUTC\\\":REPLACEDATEPLAYED,\\\"DifficultyId\\\":1,\\\"GameCategoryId\\\":18,\\\"HaloTitleId\\\":\\\"HaloReach\\\",\\\"Kills\\\":REPLACEKILLS,\\\"MapId\\\":REPLACEMAPID,\\\"MissionScore64\\\":REPLACEMISSIONSCORE,\\\"NumPlayers\\\":1,\\\"Penalties\\\":0,\\\"PlayerSectionStats\\\":\\\"{\\\\\\\\\\\\\\\"scores\\\\\\\\\\\\\\\":[0.0,1.0,2.0,3.0,4.0],\\\\\\\\\\\\\\\"interpolatedScores\\\\\\\\\\\\\\\":[0.0,1.0,2.0,3.0,4.0],\\\\\\\\\\\\\\\"times\\\\\\\\\\\\\\\":[0,1,2,3,4],\\\\\\\\\\\\\\\"sectionIDs\\\\\\\\\\\\\\\":[1,2,3,4,5],\\\\\\\\\\\\\\\"deaths\\\\\\\\\\\\\\\":0,\\\\\\\\\\\\\\\"numPlayers\\\\\\\\\\\\\\\":1,\\\\\\\\\\\\\\\"skullMask\\\\\\\\\\\\\\\":0,\\\\\\\\\\\\\\\"skullCount\\\\\\\\\\\\\\\":0}\\\",\\\"SkullUsedFlags\\\":0,\\\"TimePlayedMS\\\":REPLACETIMEPLAYED,\\\"TotalCoopMissionsComplete\\\":0,\\\"TotalSoloMissionsComplete\\\":1},\\\"measurements\\\":{\\\"Multiplier\\\":REPLACEMULTIPLIER,\\\"SkullMultiplier\\\":1}}}\"\n        },\n        \"Replacement1\": {\n          \"ReplacementType\": \"Replace\",\n          \"Target\": \"REPLACEMAPID\",\n          \"Replacement\": 179\n        },\n        \"Replacement2\": {\n          \"ReplacementType\": \"RangeInt\",\n          \"Target\": \"REPLACEKILLS\",\n          \"Min\": 200,\n          \"Max\": 300\n        },\n        \"Replacement3\": {\n          \"ReplacementType\": \"RangeInt\",\n          \"Target\": \"REPLACEMISSIONSCORE\",\n          \"Min\": 15001,\n          \"Max\": 17250\n        },\n        \"Replacement4\": {\n          \"ReplacementType\": \"RangeInt\",\n          \"Target\": \"REPLACETIMEPLAYED\",\n          \"Min\": 900001,\n          \"Max\": 1035000\n        },\n        \"Replacement5\": {\n          \"ReplacementType\": \"RangeFloat\",\n          \"Target\": \"REPLACEMULTIPLIER\",\n          \"Min\": 1,\n          \"Max\": 2\n        },\n        \"Replacement6\": {\n          \"ReplacementType\": \"StupidFuckingLDAPTimestamp\",\n          \"Target\": \"REPLACEDATEPLAYED\"\n        }\n      }\n    }\n```\n\n### Stats Editor\n\nThis section is under construction.\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "LICENSE.MIT",
    "content": "MIT License\n\nCopyright (c) 2021-2023 Leszek Pomianowski and WPF UI Contributors. https://dev.lepo.co/\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Xbox Achievement Unlocker\n\n![GitHub contributors][contributors-badge]\n![GitHub forks][forks-badge]\n![GitHub stars][stars-badge]\n![GitHub issues][issues-badge]\n![GitHub release][release-badge]\n\n[Join our Discord][discord-invite]\n\nUnlock achievements on Microsoft/Xbox games with ease. This tool is inspired by the functionality of Steam Achievements Manager and is completely free to use.\n\n## Table of Contents\n\n- [Xbox Achievement Unlocker](#xbox-achievement-unlocker)\n  - [Table of Contents](#table-of-contents)\n  - [About Xbox Achievement Unlocker](#about-xbox-achievement-unlocker)\n  - [How It Works](#how-it-works)\n  - [Requirements](#requirements)\n  - [Features](#features)\n  - [Screenshots](#screenshots)\n  - [Events Guide](#events-guide)\n  - [Usage Guide](#usage-guide)\n  - [Future Improvements](#future-improvements)\n  - [Join Our Discord Server](#join-our-discord-server)\n  - [License](#license)\n  - [Sponsors](#sponsors)\n    - [ziqnr](#ziqnr)\n\n## About Xbox Achievement Unlocker\n\nThere are numerous paid services offering tools or services to unlock a full game's achievements on your account. Xbox Achievement Unlocker is a free alternative that doesn't randomly add gamerscore from arbitrary games or charge you to unlock a game's achievements.\n\n## How It Works\n\nXbox Achievement Unlocker uses code from memory.dll to extract the user's XAuth token from one of the Xbox app processes. This token is then used to make web requests to Xbox servers, pulling information on achievements and informing the server which of these achievements have been unlocked.\n\n## Requirements\n\n- [dotnet 8](https://download.visualstudio.microsoft.com/download/pr/77284554-b8df-4697-9a9e-4c70a8b35f29/6763c16069d1ab8fa2bc506ef0767366/dotnet-runtime-8.0.5-win-x64.exe)\n- [New Xbox app](https://apps.microsoft.com/store/detail/xbox/9MV0B5HZVK9Z)\n\n## Features\n\n- Extract XAuth from Xbox app or use OAuth to login\n- Obtain a list of games from the user or any selected XUID\n- Unlock Achievements for any Title Managed game\n- Spoof time in any game\n- Automatic updates\n- Automatically spoof time in the currently viewed game\n- Search TA and use the Xbox API to get the titleID for any game with a store page\n\n## Screenshots\n\nComing soon.\n\n## Events Guide\n\nSee [Events](./Doc/Events.md) for details.\n\n## Usage Guide\n\nSee [Discord](https://discord.com/channels/1013602813093359657/1233193528553640017) for comprehensive guides by Xolara.\n\n## Future Improvements\n\n- Stats editor\n- Support for Event based stats\n\n## Join Our Discord Server\n\nFeel free to join our [Discord server][discord-invite] for updates and discussions.\n\n## License\n\nThe UI for this program was built on top of the WPF-UI Fluent template as of [this commit](https://github.com/lepoco/wpfui/tree/c8cd75f6f82414a52a94d2a55fe2a21dd5db83d7) which is MIT licensed. Any and all modifications and/or additions to this template are GNU GPL licensed. You can find a copy of the licenses [here][LICENSE] and [here][MIT-LICENSE].\nThis tool uses the XboxAuthNet library which is also MIT licensed\n\n## Sponsors\n\nThanks very much to all of my sponsors. Below are messages included as one of the sponsorship rewards\n\n### ziqnr\n\n\"I have brain damage\" - [ziqnr](https://github.com/ziqnr) 2024\n\n\n[contributors-badge]: https://img.shields.io/github/contributors/ItsLogic/Xbox-Achievement-Unlocker?style=for-the-badge\n[contributors-url]: https://github.com/ItsLogic/Xbox-Achievement-Unlocker/graphs/contributors\n[forks-badge]: https://img.shields.io/github/forks/ItsLogic/Xbox-Achievement-Unlocker?style=for-the-badge\n[forks-url]: https://github.com/ItsLogic/Xbox-Achievement-Unlocker/network/members\n[stars-badge]: https://img.shields.io/github/stars/ItsLogic/Xbox-Achievement-Unlocker?style=for-the-badge\n[stars-url]: https://github.com/ItsLogic/Xbox-Achievement-Unlocker/stargazers\n[issues-badge]: https://img.shields.io/github/issues/ItsLogic/Xbox-Achievement-Unlocker?style=for-the-badge\n[issues-url]: https://github.com/ItsLogic/Xbox-Achievement-Unlocker/issues\n[release-badge]: https://img.shields.io/github/v/release/ItsLogic/Xbox-Achievement-Unlocker?style=for-the-badge\n[release-url]: https://github.com/ItsLogic/Xbox-Achievement-Unlocker/releases\n[discord-id]: https://img.shields.io/discord/1013602813093359657?logo=discord&style=for-the-badge\n[discord-invite]: https://discord.gg/ugDvSw7cns\n[WPF-Commit]: https://github.com/lepoco/wpfui/tree/c8cd75f6f82414a52a94d2a55fe2a21dd5db83d7\n[LICENSE]:LICENSE\n[MIT-LICENSE]:LICENSE.MIT\n"
  },
  {
    "path": "XAU/App.xaml",
    "content": "﻿<Application x:Class=\"XAU.App\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n             Exit=\"OnExit\"\n             Startup=\"OnStartup\">\n    <Application.Resources>\n        <ResourceDictionary>\n            <ResourceDictionary.MergedDictionaries>\n                <ui:ThemesDictionary Theme=\"Dark\" />\n                <ui:ControlsDictionary />\n                <ResourceDictionary Source=\"/Theme/ThemeConstants.xaml\"/>\n            </ResourceDictionary.MergedDictionaries>\n            <BooleanToVisibilityConverter x:Key=\"BooleanToVisibilityConverter\" />\n        </ResourceDictionary>\n    </Application.Resources>\n</Application>\n"
  },
  {
    "path": "XAU/App.xaml.cs",
    "content": "using Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Services;\nusing XAU.Services;\nusing XAU.ViewModels.Pages;\nusing XAU.ViewModels.Windows;\nusing XAU.Views.Pages;\nusing XAU.Views.Windows;\n\nnamespace XAU;\n\npublic partial class App\n{\n    private static readonly IHost Host = Microsoft.Extensions.Hosting.Host\n        .CreateDefaultBuilder()\n        .ConfigureServices((_, services) =>\n        {\n            services.AddHostedService<ApplicationHostService>();\n\n            services.AddSingleton<MainWindow>();\n            services.AddSingleton<MainWindowViewModel>();\n            services.AddSingleton<INavigationService, NavigationService>();\n            services.AddSingleton<ISnackbarService, SnackbarService>();\n            services.AddSingleton<IContentDialogService, ContentDialogService>();\n\n            services.AddSingleton<HomePage>();\n            services.AddSingleton<HomeViewModel>();\n            services.AddSingleton<SettingsPage>();\n            services.AddSingleton<SettingsViewModel>();\n            services.AddSingleton<GamesPage>();\n            services.AddSingleton<GamesViewModel>();\n            services.AddSingleton<AchievementsPage>();\n            services.AddSingleton<AchievementsViewModel>();\n            services.AddSingleton<PlaceholderPage>();\n            services.AddSingleton<StatsPage>();\n            services.AddSingleton<StatsViewModel>();\n            services.AddSingleton<MiscPage>();\n            services.AddSingleton<MiscViewModel>();\n            services.AddSingleton<InfoPage>();\n            services.AddSingleton<InfoViewModel>();\n            services.AddSingleton<DebugPage>();\n            services.AddSingleton<DebugViewModel>();\n        }).Build();\n\n    private static T? GetService<T>() where T : class\n    {\n        return Host.Services.GetService(typeof(T)) as T;\n    }\n\n    private void OnStartup(object sender, StartupEventArgs e)\n    {\n        Host.Start();\n        SetupExceptionHandling();\n    }\n\n    private async void OnExit(object sender, ExitEventArgs e)\n    {\n        await Host.StopAsync();\n        Host.Dispose();\n    }\n\n    private void SetupExceptionHandling()\n    {\n        AppDomain.CurrentDomain.UnhandledException += (_, e) =>\n        {\n            ReportException((Exception)e.ExceptionObject);\n        };\n\n        DispatcherUnhandledException += (_, e) =>\n        {\n            ReportException(e.Exception);\n            e.Handled = true;\n        };\n\n        TaskScheduler.UnobservedTaskException += (_, e) =>\n        {\n            ReportException(e.Exception);\n            e.SetObserved();\n        };\n    }\n    private static void ReportException(Exception exception)\n    {\n        var mainWindowViewModel = GetService<MainWindowViewModel>();\n        mainWindowViewModel?.ShowErrorDialog(exception);\n    }\n}\n"
  },
  {
    "path": "XAU/AssemblyInfo.cs",
    "content": "[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]\n"
  },
  {
    "path": "XAU/Models/GitHubResponse.cs",
    "content": "using Newtonsoft.Json;\n\npublic class EventsUpdateResponse\n{\n    public int Timestamp { get; set; }\n    public string? DataVersion { get; set; }\n}\n\npublic class VersionResponse\n{\n    public string? DownloadURL { get; set; }\n    public string? LatestBuildVersion { get; set; }\n}\n\n\npublic class GitHubFile\n{\n    [JsonProperty(\"name\")]\n    public string Name { get; set; }\n\n    [JsonProperty(\"path\")]\n    public string Path { get; set; }\n\n    [JsonProperty(\"sha\")]\n    public string Sha { get; set; }\n\n    [JsonProperty(\"size\")]\n    public long Size { get; set; }\n\n    [JsonProperty(\"download_url\")]\n    public string DownloadUrl { get; set; }\n}\n"
  },
  {
    "path": "XAU/Models/MiscItems.cs",
    "content": "\n    public class GameItem\n    {\n        public string Title { get; set; }\n        public string TitleId { get; set; }\n        public bool IsTitleBased { get; set; }\n    }\n"
  },
  {
    "path": "XAU/Models/XAUSettings.cs",
    "content": "public class XAUSettings\n{\n    public string? SettingsVersion { get; set; }\n    public string? ToolVersion { get; set; }\n    public bool UnlockAllEnabled { get; set; }\n    public bool AutoSpooferEnabled { get; set; }\n    public bool AutoLaunchXboxAppEnabled { get; set; }\n    public bool LaunchHidden { get; set; }\n    public bool FakeSignatureEnabled { get; set; }\n    public bool RegionOverride { get; set; }\n    public bool UseAcrylic { get; set; }\n    public bool PrivacyMode { get; set; }\n    public bool OAuthLogin { get; set; }\n    public bool AutoGrabEventsToken { get; set; }\n    public string? CachedEventsToken { get; set; }\n    public DateTime? EventsTokenObtainedAt { get; set; }\n    public string? EventsUserHash { get; set; }\n}\n"
  },
  {
    "path": "XAU/Models/XboxApiRequest.cs",
    "content": "// TODO: Clean up, set names, default fields, minor renames, etc.\n\npublic class GameTitleRequest\n{\n    public string? Pfns { get; set; }\n    public List<string> TitleIds { get; set; } = new List<string>();\n}\n\npublic class AchievementsArrayEntry\n{\n    public string? id { get; set; }\n    public string percentComplete { get; set; } = \"100\";\n}\n\npublic class UnlockTitleBasedAchievementRequest\n{\n    public string action { get; set; } = @\"progressUpdate\";\n    public string serviceConfigId { get; set; } = StringConstants.ZeroUid;\n    public string? titleId { get; set; }\n    public string? userId { get; set; }\n    public List<AchievementsArrayEntry> achievements { get; set; } = new List<AchievementsArrayEntry>();\n}\n\npublic class GameStat\n{\n    public string Name { get; set; } = \"MinutesPlayed\";\n    public string? TitleId { get; set; }\n}\n\npublic class GameStatsRequest\n{\n    public string ArrangeByField { get; set; } = \"xuid\";\n    public List<string> Xuids { get; set; } = new List<string>();\n    public List<GameStat> Stats { get; set; } = new List<GameStat>();\n}\n\n\npublic class HeartbeatRequest\n{\n    public List<TitleRequest> titles { get; set; } = new List<TitleRequest>();\n}\n\npublic class TitleRequest\n{\n    public int expiration { get; set; } = 600;\n    public string? id { get; set; }\n    public string state { get; set; } = \"active\";\n    public string sandbox { get; set; } = \"RETAIL\";\n}\n\npublic class GamepassProductsRequest\n{\n    public List<string> Products { get; set; } = new List<string>();\n}\n"
  },
  {
    "path": "XAU/Models/XboxApiResponse.cs",
    "content": "// OpenAPI or Swagger doesn't exist. But let's try and type the things we need to make parsing more readable\n// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/atoc-xboxlivews-reference\n\n// TODO: Clean up, set names, default fields, minor renames, etc.\n\npublic class PersonResponse\n{\n    public string? Xuid { get; set; }\n\n    public bool IsFavorite { get; set; }\n    public bool IsFollowingCaller { get; set; }\n    public bool IsFollowedByCaller { get; set; }\n    public bool IsIdentityShared { get; set; }\n    public DateTime? AddedDateTimeUtc { get; set; }\n    public string? DisplayName { get; set; }\n\n    public string? RealName { get; set; }\n\n    public string? DisplayPicRaw { get; set; }\n\n    public string? ShowUserAsAvatar { get; set; }\n\n    public string? Gamertag { get; set; }\n\n    public string? GamerScore { get; set; }\n\n    public string? ModernGamertag { get; set; }\n\n    public string? ModernGamertagSuffix { get; set; }\n\n    public string? UniqueModernGamertag { get; set; }\n\n    public string? XboxOneRep { get; set; }\n\n    public string? PresenceState { get; set; }\n\n    public string? PresenceText { get; set; }\n\n    public object? PresenceDevices { get; set; }\n\n    public bool IsBroadcasting { get; set; }\n    public bool? IsCloaked { get; set; }\n    public bool IsQuarantined { get; set; }\n    public bool IsXbox360Gamerpic { get; set; }\n    public DateTime? LastSeenDateTimeUtc { get; set; }\n    public object? Suggestion { get; set; }\n\n    public object? Recommendation { get; set; }\n\n    public object? Search { get; set; }\n\n    public object? TitleHistory { get; set; }\n\n    public MultiplayerSummary? MultiplayerSummary { get; set; }\n    public object? RecentPlayer { get; set; }\n\n    public object? Follower { get; set; }\n\n    public PreferredColor? PreferredColor { get; set; }\n    public List<PresenceDetail> PresenceDetails { get; set; } = new List<PresenceDetail>();\n    public object? TitlePresence { get; set; }\n\n    public object? TitleSummaries { get; set; }\n\n    public object? PresenceTitleIds { get; set; }\n\n    public Detail? Detail { get; set; }\n    public object? CommunityManagerTitles { get; set; }\n\n    public object? SocialManager { get; set; }\n\n    public object? Broadcast { get; set; }\n\n    public object? Avatar { get; set; }\n\n    public List<LinkedAccount> LinkedAccounts { get; set; } = new List<LinkedAccount>();\n    public string? ColorTheme { get; set; }\n\n    public string? PreferredFlag { get; set; }\n\n    public List<object> PreferredPlatforms { get; set; } = new List<object>();\n}\n\npublic class MultiplayerSummary\n{\n    public List<object> JoinableActivities { get; set; } = new List<object>();\n    public List<object> PartyDetails { get; set; } = new List<object>();\n    public int InParty { get; set; }\n}\n\npublic class PreferredColor\n{\n    public string? PrimaryColor { get; set; }\n\n    public string? SecondaryColor { get; set; }\n\n    public string? TertiaryColor { get; set; }\n\n}\n\npublic class PresenceDetail\n{\n    public bool IsBroadcasting { get; set; }\n    public string? Device { get; set; }\n\n    public object? DeviceSubType { get; set; }\n\n    public object? GameplayType { get; set; }\n\n    public string? PresenceText { get; set; }\n\n    public string? State { get; set; }\n\n    public string? TitleId { get; set; }\n\n    public object? TitleType { get; set; }\n\n    public bool IsPrimary { get; set; }\n    public bool IsGame { get; set; }\n    public object? RichPresenceText { get; set; }\n\n}\n\npublic class Detail\n{\n    public string? AccountTier { get; set; }\n\n    public string? Bio { get; set; }\n\n    public bool IsVerified { get; set; }\n    public string? Location { get; set; }\n\n    public string? Tenure { get; set; }\n\n    public List<string> Watermarks { get; set; } = new List<string>();\n    public bool Blocked { get; set; }\n    public bool Mute { get; set; }\n    public int FollowerCount { get; set; }\n    public int FollowingCount { get; set; }\n    public bool HasGamePass { get; set; }\n    public List<string>? Genres { get; set; }\n}\n\npublic class LinkedAccount\n{\n    public string? NetworkName { get; set; }\n\n    public string? DisplayName { get; set; }\n\n    public bool ShowOnProfile { get; set; }\n    public bool IsFamilyFriendly { get; set; }\n    public string? Deeplink { get; set; }\n\n}\n\npublic class Profile\n{\n    public List<PersonResponse> People { get; set; } = new List<PersonResponse>();\n    public object? RecommendationSummary { get; set; }\n\n    public object? FriendFinderState { get; set; }\n\n    public object? AccountLinkDetails { get; set; }\n\n}\n\n\npublic class XboxTitle\n{\n    public string? TitleId { get; set; }\n\n    public string? Pfn { get; set; }\n\n    public string? BingId { get; set; }\n\n    public string? WindowsPhoneProductId { get; set; }\n\n    public required string Name { get; set; }\n\n    public string? Type { get; set; }\n\n    public List<string> Devices { get; set; } = new List<string>();\n    public string? DisplayImage { get; set; }\n\n    public string? MediaItemType { get; set; }\n\n    public string? ModernTitleId { get; set; }\n\n    public bool IsBundle { get; set; }\n    public BasicAchievementDetails? Achievement { get; set; }\n    public Stats? Stats { get; set; }\n    public GamePass? GamePass { get; set; }\n    public object? Images { get; set; }\n\n    public object? TitleHistory { get; set; }\n\n    public object? TitleRecord { get; set; }\n\n    public object? Detail { get; set; }\n\n    public object? FriendsWhoPlayed { get; set; }\n\n    public object? AlternateTitleIds { get; set; }\n\n    public object? ContentBoards { get; set; }\n\n    public string? XboxLiveTier { get; set; }\n\n}\n\npublic class BasicAchievementDetails\n{\n    public int CurrentAchievements { get; set; }\n    public int TotalAchievements { get; set; }\n    public int CurrentGamerscore { get; set; }\n    public int TotalGamerscore { get; set; }\n    public double ProgressPercentage { get; set; }\n    public int SourceVersion { get; set; }\n}\n\npublic class Stats\n{\n    public int SourceVersion { get; set; }\n}\n\npublic class GamePass\n{\n    public bool IsGamePass { get; set; }\n}\n\npublic class GameTitle\n{\n    public string? Xuid { get; set; }\n\n    public List<XboxTitle> Titles { get; set; } = new List<XboxTitle>();\n}\n\npublic class GamepassData\n{\n    public string? GamepassMembership { get; set; }\n\n}\n\npublic class Gamepass\n{\n    // This json response is actually massive, but we don't care\n    // TOOD: maybe we want to look at points/stuff later\n    public string? GamepassMembership { get; set; }\n\n    public GamepassData? Data { get; set; }\n}\n\npublic class TitleHistory\n{\n    public DateTime LastTimePlayed { get; set; }\n    public bool Visible { get; set; }\n    public bool CanHide { get; set; }\n}\n\npublic class Title\n{\n    public string? TitleId { get; set; }\n\n    public string? Pfn { get; set; }\n\n    public string? BingId { get; set; }\n\n    public string? ServiceConfigId { get; set; }\n\n    public string? WindowsPhoneProductId { get; set; }\n\n    public string? Name { get; set; }\n\n    public string? Type { get; set; }\n\n    public List<string> Devices { get; set; } = new List<string>();\n    public string? DisplayImage { get; set; }\n\n    public string? MediaItemType { get; set; }\n\n    public string? ModernTitleId { get; set; }\n\n    public bool IsBundle { get; set; }\n    public BasicAchievementDetails? Achievement { get; set; }\n    public object? Stats { get; set; }\n\n    public object? GamePass { get; set; }\n\n    public object? Images { get; set; }\n\n    public TitleHistory? TitleHistory { get; set; }\n    public object? TitleRecord { get; set; }\n\n    public Detail? Detail { get; set; }\n\n    public object? FriendsWhoPlayed { get; set; }\n\n    public object? AlternateTitleIds { get; set; }\n\n    public object? ContentBoards { get; set; }\n\n    public string? XboxLiveTier { get; set; }\n\n}\n\npublic class TitlesList\n{\n    public string? Xuid { get; set; }\n\n    public List<Title> Titles { get; set; } = new List<Title>();\n}\n\npublic class ProfileSettings\n{\n    public string? Id { get; set; }\n\n    public string? Value { get; set; }\n\n}\n\npublic class ProfileUser\n{\n    public string? Id { get; set; }\n\n    public string? HostId { get; set; }\n\n    public List<ProfileSettings> Settings { get; set; } = new List<ProfileSettings>();\n    public string? IsSponsoredUser { get; set; }\n\n}\n\npublic class BasicProfile\n{\n    public List<ProfileUser> ProfileUsers { get; set; } = new List<ProfileUser>();\n}\n\n\npublic class Stat\n{\n    public Dictionary<string, object> GroupProperties { get; set; } = new Dictionary<string, object>();\n    public string? Xuid { get; set; }\n\n    public string? Scid { get; set; }\n\n    public string? TitleId { get; set; }\n\n    public string? Name { get; set; }\n\n    public string? Type { get; set; }\n\n    public string? Value { get; set; }\n\n    public Dictionary<string, object> Properties { get; set; } = new Dictionary<string, object>();\n}\n\npublic class StatListCollection\n{\n    public string? ArrangeByField { get; set; }\n\n    public string? ArrangeByFieldId { get; set; }\n\n    public List<Stat> Stats { get; set; } = new List<Stat>();\n}\n\npublic class GameStatsResponse\n{\n    public List<object> Groups { get; set; } = new List<object>();\n    public List<StatListCollection> StatListsCollection { get; set; } = new List<StatListCollection>();\n}\n\npublic class AchievementRewards\n{\n    public string? name { get; set; }\n\n    public string? description { get; set; }\n\n    public string? value { get; set; }\n\n    public string? type { get; set; }\n\n    public MediaAsset? mediaAsset { get; set; }\n    public string? valueType { get; set; }\n\n}\n\npublic class Rarity\n{\n    public string? currentCategory { get; set; }\n\n    public string? currentPercentage { get; set; }\n\n}\n\n\npublic class AchievementRequirements\n{\n    public string? id { get; set; }\n\n    public string? current { get; set; }\n\n    public string? target { get; set; }\n\n    public string? operationType { get; set; }\n\n    public string? valueType { get; set; }\n\n    public string? ruleParticipationType { get; set; }\n\n}\n\npublic class AchievementProgression\n{\n    public List<AchievementRequirements> requirements { get; set; } = new List<AchievementRequirements>();\n    public string? timeUnlocked { get; set; }\n\n}\n\npublic class TitleAssociation\n{\n    public string? name { get; set; }\n\n    public string? id { get; set; }\n\n}\n\npublic class MediaAsset\n{\n    public string? name { get; set; }\n\n    public string? type { get; set; }\n\n    public string? url { get; set; }\n\n}\n\n\n\npublic class OneCoreAchievementResponse // Xbox One Achievements\n{\n    public Rarity? rarity { get; set; }\n    public object? gamerscore { get; set; }\n    public required string id { get; set; }\n    public string serviceConfigId { get; set; } = StringConstants.ZeroUid;\n    public required string name { get; set; }\n    public List<TitleAssociation> titleAssociations { get; set; } = new List<TitleAssociation>();\n    public string progressState { get; set; } = \"Null\";\n    public AchievementProgression? progression { get; set; }\n    public List<MediaAsset> mediaAssets { get; set; } = new List<MediaAsset>();\n    public List<string> platforms { get; set; } = new List<string>();\n    public bool isSecret { get; set; }\n    public string? description { get; set; }\n    public string? lockedDescription { get; set; }\n    public string? productId { get; set; }\n    public string? achievementType { get; set; }\n    public string? participationType { get; set; }\n    public TimeWindow? timeWindow { get; set; }\n    public List<AchievementRewards> rewards { get; set; } = new List<AchievementRewards>();\n    public string? estimatedTime { get; set; }\n    public string? deeplink { get; set; }\n    public string? isRevoked { get; set; }\n    public string? raritycurrentCategory { get; set; }\n    public string? raritycurrentPercentage { get; set; }\n}\n\npublic class Xbox360AchievementEntry\n{\n    public int id {get; set;}\n    public long titleId {get; set;}\n    public string name {get; set;}\n    public int gamerscore {get; set;}\n    public string description {get; set;}\n    public string lockedDescription {get; set;}\n    public string timeUnlocked {get; set;}\n    public bool isSecret {get; set;}\n    public Rarity? rarity {get; set;}\n\n}\n\npublic class Xbox360AchievementResponse\n{\n    public List<Xbox360AchievementEntry> achievements {get;set; } = new List<Xbox360AchievementEntry>();\n}\n\npublic class AchievementsResponse\n{\n    public List<OneCoreAchievementResponse> achievements { get; set; } = new List<OneCoreAchievementResponse>();\n}\n\npublic class TimeWindow\n{\n    public required string startDate { get; set; }\n    public required string endDate { get; set; }\n\n}\n\npublic class Image\n{\n    public string? URI { get; set; }\n    public int Height { get; set; }\n    public int Width { get; set; }\n    public string? Caption { get; set; }\n}\n\npublic class Product\n{\n    public bool PCPlatformPreinstallable { get; set; }\n    public string? ProductTitle { get; set; }\n    public string? ProductDescription { get; set; }\n    public string? ProductDescriptionShort { get; set; }\n    public string? PackageFamilyName { get; set; }\n    public string? ProductType { get; set; }\n    public object? ChildPackageFamilyNames { get; set; }\n    public string? XboxTitleId { get; set; }\n    public object? ChildXboxTitleIds { get; set; }\n    public int MinimumUserAge { get; set; }\n    public List<object> Children { get; set; } = new List<object>();\n    public List<string> Categories { get; set; } = new List<string>();\n    public List<object> Attributes { get; set; } = new List<object>();\n    public object? HeroTrailer { get; set; }\n    public Image? ImageBoxArt { get; set; }\n    public Image? ImageHero { get; set; }\n    public object? ImageTitledHero { get; set; }\n    public Image? ImagePoster { get; set; }\n    public object? ImageTile { get; set; }\n    public object? PCComingSoonDate { get; set; }\n    public object? PCExitDate { get; set; }\n    public object? UltimateComingSoonDate { get; set; }\n    public object? UltimateExitDate { get; set; }\n    public bool IsEAPlay { get; set; }\n    public List<object> XCloudSupportedInputs { get; set; } = new List<object>();\n    public object? GameCatalogExtensionId { get; set; }\n    public string? StoreId { get; set; }\n}\n\npublic class GamePassProducts\n{\n    public Dictionary<string, Product> Products { get; set; } = new Dictionary<string, Product>();\n    public List<object> InvalidIds { get; set; } = new List<object>();\n}\n"
  },
  {
    "path": "XAU/Networking/DBoxRestAPI.cs",
    "content": "using System.Net;\nusing System.Net.Http;\nusing HtmlAgilityPack;\nusing Newtonsoft.Json.Linq;\n\npublic class DBoxRestApi\n{\n    private readonly HttpClient _httpClient;\n\n    public DBoxRestApi()\n    {\n        // This is a placeholder for the Xbox REST API\n        var handler = new HttpClientHandler()\n        {\n            AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate\n        };\n        _httpClient = new HttpClient(handler);\n    }\n\n    private void SetDefaultHeaders()\n    {\n        _httpClient.DefaultRequestHeaders.Clear();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, HeaderValues.AcceptEncoding);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, HeaderValues.Accept);\n    }\n\n    public async Task<JObject> SearchAsync(string searchText)\n    {\n        _httpClient.DefaultRequestHeaders.Clear();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, \"application/json\");\n\n        searchText = Uri.EscapeDataString(searchText);\n\n        var response = await _httpClient.GetAsync($\"https://dbox.tools/api/title_ids/?name={searchText}&limit=100&offset=0\");\n        var jsonString = await response.Content.ReadAsStringAsync();\n        if (response.IsSuccessStatusCode)\n        {\n            return JObject.Parse(jsonString);\n        }\n        else\n        {\n            throw new HttpRequestException($\"Error fetching data: {response.StatusCode} - {jsonString}\");\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Networking/GithubRestApi.cs",
    "content": "using System.Net;\nusing System.Net.Http;\nusing Newtonsoft.Json;\nusing Newtonsoft.Json.Linq;\n\npublic class GithubRestApi\n{\n    private readonly HttpClient _httpClient;\n\n    // User specifics\n    public GithubRestApi()\n    {\n        var handler = new HttpClientHandler()\n        {\n            AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate\n        };\n        _httpClient = new HttpClient(handler);\n    }\n\n    private void SetDefaultHeaders()\n    {\n        _httpClient.DefaultRequestHeaders.Clear();\n        _httpClient.DefaultRequestHeaders.Add(\"User-Agent\",\n            \"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0\");\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, \"gzip, deflate, br\");\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept,\n            \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\");\n    }\n\n    public async Task<VersionResponse?> GetDevToolVersionAsync()\n    {\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.GitHubRaw);\n        var responseString =\n            await _httpClient.GetStringAsync(\"https://raw.githubusercontent.com/Fumo-Unlockers/Xbox-Achievement-Unlocker/Pre-Release/info.json\");\n        return JsonConvert.DeserializeObject<VersionResponse>(responseString);\n    }\n\n    public async Task<dynamic> GetReleaseVersionAsync()\n    {\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.GitHubApi);\n        var responseString =\n            await _httpClient.GetStringAsync(\"https://api.github.com/repos/Fumo-Unlockers/Xbox-Achievement-unlocker/releases\");\n        var jsonResponse = (dynamic)JArray.Parse(responseString);\n        return jsonResponse;\n    }\n\n    public async Task<EventsUpdateResponse?> CheckForEventUpdatesAsync()\n    {\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.GitHubRaw);\n        var responseString = await _httpClient.GetStringAsync(\"https://raw.githubusercontent.com/Fumo-Unlockers/Xbox-Achievement-Unlocker/Events-Data/meta.json\");\n        return JsonConvert.DeserializeObject<EventsUpdateResponse>(responseString);\n    }\n\n\n    public async Task<GitHubFile?> GetXboxGamesDatabaseInfoAsync()\n    {\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.GitHubApi);\n        var responseString = await _httpClient.GetStringAsync(\"https://api.github.com/repos/Fumo-Unlockers/XboxGames/contents\");\n        var files = JsonConvert.DeserializeObject<List<GitHubFile>>(responseString);\n        return files?.FirstOrDefault(f => f.Name.Equals(\"xbox_games.db\", StringComparison.OrdinalIgnoreCase));\n    }\n\n}\n"
  },
  {
    "path": "XAU/Networking/XboxRestApi.cs",
    "content": "using System.Net;\nusing System.Net.Http;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing Newtonsoft.Json;\nusing Newtonsoft.Json.Linq;\nusing XAU.ViewModels.Pages;\nusing XAU.ViewModels.Windows;\n\npublic class XboxRestAPI\n{\n    private readonly HttpClient _httpClient;\n\n    private readonly HttpClient _eventBasedClient; // Dumb, but needed for events for now\n\n    private readonly HttpClient _spooferClient;\n\n    // User specifics\n    private readonly string _xauth;\n    private readonly string _requestedResponseLanguage;\n\n    public XboxRestAPI(string xauth)\n    {\n        _xauth = xauth;\n        _requestedResponseLanguage = HomeViewModel.Settings.RegionOverride ? \"en-GB\" : System.Globalization.CultureInfo.CurrentCulture.Name;\n        var handler = new HttpClientHandler()\n        {\n            AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate\n        };\n        _httpClient = new HttpClient(handler);\n        _spooferClient = new HttpClient(handler);\n\n        var insecureEventsHandler = new HttpClientHandler()\n        {\n            AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate,\n            //This is an absolutely terrible idea but the stupid fucking events API just cries about SSL errors\n            ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator\n        };\n        _eventBasedClient = new HttpClient(insecureEventsHandler);\n    }\n\n    private void SetDefaultHeaders()\n    {\n        _httpClient.DefaultRequestHeaders.Clear();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Authorization, _xauth);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.AcceptLanguage, _requestedResponseLanguage);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, HeaderValues.AcceptEncoding);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, HeaderValues.Accept);\n\n\n#if DEBUG\n        Console.WriteLine(\"Headers in _httpClient:\");\n        foreach (var header in _httpClient.DefaultRequestHeaders)\n        {\n            if (header.Key == \"Authorization\") continue;\n            Console.WriteLine($\"{header.Key}: {string.Join(\", \", header.Value)}\");\n        }\n#endif\n    }\n\n    private void SetDefaultSpooferHeaders()\n    {\n        _spooferClient.DefaultRequestHeaders.Clear();\n        _spooferClient.DefaultRequestHeaders.Add(HeaderNames.Authorization, _xauth);\n        _spooferClient.DefaultRequestHeaders.Add(HeaderNames.AcceptLanguage, _requestedResponseLanguage);\n        _spooferClient.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, HeaderValues.AcceptEncoding);\n        _spooferClient.DefaultRequestHeaders.Add(HeaderNames.Accept, HeaderValues.Accept);\n\n#if DEBUG\n        Console.WriteLine(\"Headers in _spooferClient:\");\n        foreach (var header in _spooferClient.DefaultRequestHeaders)\n        {\n            if (header.Key == \"Authorization\") continue;\n            Console.WriteLine($\"{header.Key}: {string.Join(\", \", header.Value)}\");\n        }\n#endif\n    }\n\n    private void SetDefaultEventBasedHeaders()\n    {\n        _eventBasedClient.DefaultRequestHeaders.Clear();\n        _eventBasedClient.DefaultRequestHeaders.Add(\"user-agent\", \"MSDW\");\n        _eventBasedClient.DefaultRequestHeaders.Add(\"cache-control\", \"no-cache\");\n        _eventBasedClient.DefaultRequestHeaders.Add(HeaderNames.Accept, HeaderValues.Accept);\n        _eventBasedClient.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, HeaderValues.AcceptEncoding);\n        _eventBasedClient.DefaultRequestHeaders.Add(\"reliability-mode\", \"standard\");\n        _eventBasedClient.DefaultRequestHeaders.Add(\"client-version\", \"EUTC-Windows-C++-no-10.0.22621.3296.amd64fre.ni_release.220506-1250-no\");\n        _eventBasedClient.DefaultRequestHeaders.Add(\"apikey\", \"0890af88a9ed4cc886a14f5e174a2827-9de66c5e-f867-43a8-a7b8-e0ddd481cca4-7548,95c1f21d6cb047a09e7b423c1cb2222e-9965f07b-54fa-498e-9727-9e8d24dec39e-7027\");\n        _eventBasedClient.DefaultRequestHeaders.Add(\"Client-Id\", \"NO_AUTH\");\n        _eventBasedClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Telemetry);\n        _eventBasedClient.DefaultRequestHeaders.Add(HeaderNames.Connection, \"close\");\n        ;\n        var authxtoken = Regex.Replace(_xauth, @\"XBL3\\.0 x=\\d+;\", \"XBL3.0 x=-;\");\n        _eventBasedClient.DefaultRequestHeaders.Add(\"authxtoken\", authxtoken);\n\n#if DEBUG\n        Console.WriteLine(\"Headers in _eventBasedClient:\");\n        foreach (var header in _eventBasedClient.DefaultRequestHeaders)\n        {\n            if (header.Key == \"authxtoken\") continue;\n            Console.WriteLine($\"{header.Key}: {string.Join(\", \", header.Value)}\");\n        }\n#endif\n    }\n\n    public async Task<BasicProfile?> GetBasicProfileAsync()\n    {\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Profile);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive);\n        var response = await _httpClient.GetStringAsync(BasicXboxAPIUris.GamertagUrl);\n        return JsonConvert.DeserializeObject<BasicProfile>(response);\n    }\n\n    public async Task<Profile?> GetProfileAsync(string xuid)\n    {\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion5);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.PeopleHub);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive);\n        var responseString = await _httpClient.GetStringAsync(string.Format(InterpolatedXboxAPIUrls.ProfileUrl, xuid));\n        return JsonConvert.DeserializeObject<Profile>(responseString);\n    }\n\n    public async Task<GameTitle?> GetGameTitleAsync(string xuid, string titleId)\n    {\n        if (string.IsNullOrWhiteSpace(xuid) || string.IsNullOrWhiteSpace(titleId))\n        {\n            // Don't send a request if we don't have the details\n            return null;\n        }\n\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2);\n        var gameTitleRequest = new GameTitleRequest()\n        {\n            Pfns = null,\n            TitleIds = new List<string>() { titleId }\n        };\n\n        var gameTitleHttpResponse = await _httpClient.PostAsync(string.Format(InterpolatedXboxAPIUrls.TitleUrl, xuid), new StringContent(JsonConvert.SerializeObject(gameTitleRequest), Encoding.UTF8, HeaderValues.Accept));\n        var gameTitleResponse = await gameTitleHttpResponse.Content.ReadAsStringAsync();\n        return JsonConvert.DeserializeObject<GameTitle>(gameTitleResponse);\n    }\n\n    public async Task<Gamepass?> GetGamepassMembershipAsync(string xuid)\n    {\n        if (string.IsNullOrWhiteSpace(xuid))\n        {\n            // Don't send a request if we don't have the details\n            return null;\n        }\n\n        SetDefaultHeaders();\n        var gpuHttpResponse = await _httpClient.GetAsync(string.Format(InterpolatedXboxAPIUrls.GamepassMembershipUrl, xuid));\n        var gpuResponse = await gpuHttpResponse.Content.ReadAsStringAsync();\n        return JsonConvert.DeserializeObject<Gamepass>(gpuResponse);\n    }\n\n    public async Task<TitlesList?> GetGamesListAsync(string xuid)\n    {\n        if (string.IsNullOrWhiteSpace(xuid))\n        {\n            // Don't send a request if we don't have the details\n            return null;\n        }\n\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.TitleHub);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive);\n        var responseString = await _httpClient.GetStringAsync(string.Format(InterpolatedXboxAPIUrls.TitlesUrl, xuid));\n        return JsonConvert.DeserializeObject<TitlesList>(responseString);\n    }\n\n    public async Task<JObject?> GetGamertagProfileAsync(string gamertag)\n    {\n        if (string.IsNullOrWhiteSpace(gamertag))\n        {\n            // Don't send a request if we don't have the details\n            return null;\n        }\n\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Profile);\n\n        string url = string.Format(InterpolatedXboxAPIUrls.GamertagSearch, gamertag);\n        var response = await _httpClient.GetAsync(url);\n        response.EnsureSuccessStatusCode();\n        var jsonResponse = await response.Content.ReadAsStringAsync();\n        return JObject.Parse(jsonResponse);\n    }\n\n    public async Task<GameStatsResponse?> GetGameStatsAsync(string xuid, string titleId)\n    {\n        if (string.IsNullOrWhiteSpace(xuid) || string.IsNullOrWhiteSpace(titleId))\n        {\n            // Don't send a request if we don't have the details\n            return null;\n        }\n\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2);\n\n        var stat = new GameStat()\n        {\n            TitleId = titleId\n        };\n        var gameStatsRequest = new GameStatsRequest()\n        {\n            Xuids = new List<string>() { xuid },\n            Stats = new List<GameStat>() { stat }\n        };\n        var httpResponse = await _httpClient\n                .PostAsync(BasicXboxAPIUris.UserStatsUrl, new StringContent(JsonConvert.SerializeObject(gameStatsRequest), Encoding.UTF8, HeaderValues.Accept));\n        var response = await httpResponse.Content.ReadAsStringAsync();\n        return JsonConvert.DeserializeObject<GameStatsResponse>(response);\n    }\n\n    public async Task SendHeartbeatAsync(string xuid, string spoofedTitleId)\n    {\n        if (string.IsNullOrWhiteSpace(xuid) || string.IsNullOrWhiteSpace(spoofedTitleId))\n        {\n            // Don't send a request if we don't have the details\n            return;\n        }\n\n        SetDefaultSpooferHeaders();\n        _spooferClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion3);\n        var heartbeatRequest = new HeartbeatRequest()\n        {\n            titles = new List<TitleRequest>()\n            {\n                new TitleRequest()\n                {\n                    id = spoofedTitleId\n                }\n            }\n        };\n        await _spooferClient.PostAsync(\n        string.Format(InterpolatedXboxAPIUrls.HeartbeatUrl, xuid),\n        new StringContent(JsonConvert.SerializeObject(heartbeatRequest), Encoding.UTF8, HeaderValues.Accept));\n    }\n\n    public async Task StopHeartbeatAsync(string xuid)\n    {\n        if (string.IsNullOrWhiteSpace(xuid))\n        {\n            // Don't send a request if we don't have the details\n            return;\n        }\n\n        SetDefaultSpooferHeaders();\n        _spooferClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion3);\n        await _spooferClient.DeleteAsync(string.Format(InterpolatedXboxAPIUrls.HeartbeatUrl, xuid));\n    }\n\n    public async Task<AchievementsResponse?> GetAchievementsForTitleAsync(string xuid, string titleId)\n    {\n        if (string.IsNullOrWhiteSpace(xuid) || string.IsNullOrWhiteSpace(titleId))\n        {\n            // Don't send a request if we don't have the details\n            return null;\n        }\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion4);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Achievements);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive);\n\n        var httpResponse = await _httpClient.GetAsync(string.Format(InterpolatedXboxAPIUrls.QueryAchievementsUrl, xuid, titleId));\n        var response = await httpResponse.Content.ReadAsStringAsync();\n        var achievements = JsonConvert.DeserializeObject<AchievementsResponse>(response);\n        return achievements;\n    }\n\n    public async Task<Xbox360AchievementResponse?> GetAchievementsFor360TitleAsync(string xuid, string titleId)\n    {\n        if (string.IsNullOrWhiteSpace(xuid) || string.IsNullOrWhiteSpace(titleId))\n        {\n            // Don't send a request if we don't have the details\n            return null;\n        }\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion3);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Achievements);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive);\n        var httpResponse = await _httpClient.GetAsync(string.Format(InterpolatedXboxAPIUrls.QueryAchievements360Url, xuid, titleId));\n        var response = await httpResponse.Content.ReadAsStringAsync();\n        var achievements = JsonConvert.DeserializeObject<Xbox360AchievementResponse>(response);\n        return achievements;\n    }\n\n    public async Task UnlockTitleBasedAchievementAsync(string serviceConfigId, string titleId, string xuid, string achievementId, bool useFakeSignature = false)\n    {\n        // only unlock the specified achievement\n        await UnlockTitleBasedAchievementsAsync(serviceConfigId, titleId, xuid, new List<string>() { achievementId }, useFakeSignature);\n    }\n\n    public async Task UnlockTitleBasedAchievementsAsync(string serviceConfigId, string titleId, string xuid, List<string> achievementIds, bool useFakeSignature = false)\n    {\n        if (string.IsNullOrWhiteSpace(serviceConfigId) || string.IsNullOrWhiteSpace(titleId) || string.IsNullOrWhiteSpace(xuid) || achievementIds.Count == 0)\n        {\n            // Don't send a request if we don't have the details\n            return;\n        }\n\n        SetDefaultHeaders();\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Achievements);\n        _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive);\n        _httpClient.DefaultRequestHeaders.Add(\"User-Agent\", \"XboxServicesAPI/2021.10.20211005.0 c\");\n\n        if (useFakeSignature)\n        {\n            _httpClient.DefaultRequestHeaders.Add(HeaderNames.Signature, HeaderValues.Signature);\n        }\n\n        // Split the requests into 50 achievements each. Anything over 100 seems to BadRequest. TODO: look into\n        // headers and see if we can send long data or w/e\n        const int chunkSize = 50;\n        for (int i = 0; i < achievementIds.Count; i += chunkSize)\n        {\n            var chunk = achievementIds.Skip(i).Take(chunkSize).ToList();\n\n            var unlockRequest = new UnlockTitleBasedAchievementRequest\n            {\n                titleId = titleId,\n                serviceConfigId = serviceConfigId,\n                userId = xuid,\n                achievements = chunk.Select(id => new AchievementsArrayEntry { id = id, percentComplete = \"100\" }).ToList()\n            };\n\n            var unlockBodyStr = JsonConvert.SerializeObject(unlockRequest);\n            var bodyconverted = new StringContent(unlockBodyStr, Encoding.UTF8, HeaderValues.Accept);\n\n            var response = await _httpClient.PostAsync(\n                string.Format(InterpolatedXboxAPIUrls.UpdateAchievementsUrl, xuid, serviceConfigId), bodyconverted);\n            if (response.StatusCode != HttpStatusCode.OK)\n            {\n                throw new HttpRequestException($\"Failed to unlock achievement(s) for title {titleId} with status code {response.StatusCode}\");\n            }\n        }\n    }\n\n    // TODO: see if we can handle the actual request body building\n    public async Task UnlockEventBasedAchievement(string eventsToken, StringContent requestBody)\n    {\n        if (string.IsNullOrWhiteSpace(eventsToken))\n        {\n            // Don't send a request if we don't have the details\n            return;\n        }\n\n        SetDefaultEventBasedHeaders();\n        _eventBasedClient.DefaultRequestHeaders.Add(\"tickets\", $\"\\\"1\\\"=\\\"{eventsToken}\\\"\");\n        var response = await _eventBasedClient.PostAsync(BasicXboxAPIUris.TelemetryUrl, requestBody);\n        var responseBody = await response.Content.ReadAsStringAsync();\n        HomeViewModel.EventsLog($\"POST {BasicXboxAPIUris.TelemetryUrl} => {(int)response.StatusCode} {response.StatusCode}\");\n        HomeViewModel.EventsLog($\"Response: {responseBody}\");\n        if (!response.IsSuccessStatusCode)\n        {\n            HomeViewModel.EventsLog(\"Response headers:\");\n            foreach (var header in response.Headers)\n                HomeViewModel.EventsLog($\"  {header.Key}: {string.Join(\", \", header.Value)}\");\n        }\n    }\n\n    public async Task<GamePassProducts?> GetTitleIdsFromGamePass(string prodId)\n    {\n        if (string.IsNullOrWhiteSpace(prodId))\n        {\n            // Don't send a request if we don't have the details\n            return null;\n        }\n\n        SetDefaultHeaders();\n        GamepassProductsRequest gamepassProducts = new GamepassProductsRequest()\n        {\n            Products = new List<string>() { prodId }\n        };\n        var titleIDsHttpResponse = await _httpClient.PostAsync(\n                    BasicXboxAPIUris.GamepassCatalogUrl,\n                    new StringContent(JsonConvert.SerializeObject(gamepassProducts)));\n        var titleIDsResponse = await titleIDsHttpResponse.Content.ReadAsStringAsync();\n        return JsonConvert.DeserializeObject<GamePassProducts>(titleIDsResponse);\n    }\n}\n"
  },
  {
    "path": "XAU/Services/ApplicationHostService.cs",
    "content": "using Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing XAU.Views.Pages;\nusing XAU.Views.Windows;\nusing Application = System.Windows.Application;\nnamespace XAU.Services\n{\n    /// <summary>\n    /// Managed host of the application.\n    /// </summary>\n    public class ApplicationHostService : IHostedService\n    {\n        private readonly IServiceProvider _serviceProvider;\n\n        public ApplicationHostService(IServiceProvider serviceProvider)\n        {\n            _serviceProvider = serviceProvider;\n        }\n\n        /// <summary>\n        /// Triggered when the application host is ready to start the service.\n        /// </summary>\n        /// <param name=\"cancellationToken\">Indicates that the start process has been aborted.</param>\n        public async Task StartAsync(CancellationToken cancellationToken)\n        {\n            await HandleActivationAsync();\n        }\n\n        /// <summary>\n        /// Triggered when the application host is performing a graceful shutdown.\n        /// </summary>\n        /// <param name=\"cancellationToken\">Indicates that the shutdown process should no longer be graceful.</param>\n        public async Task StopAsync(CancellationToken cancellationToken)\n        {\n            await Task.CompletedTask;\n        }\n\n        /// <summary>\n        /// Creates main window during activation.\n        /// </summary>\n        private async Task HandleActivationAsync()\n        {\n            await Task.CompletedTask;\n\n            if (!Application.Current.Windows.OfType<MainWindow>().Any())\n            {\n                var navigationWindow = _serviceProvider.GetRequiredService<MainWindow>();\n                navigationWindow.Loaded += OnNavigationWindowLoaded;\n                navigationWindow.Show();\n            }\n        }\n\n        private void OnNavigationWindowLoaded(object sender, RoutedEventArgs e)\n        {\n            if (sender is not MainWindow navigationWindow)\n            {\n                return;\n            }\n\n            navigationWindow.NavigationView.Navigate(typeof(HomePage));\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Services/HttpServer/AchievementRoutes.cs",
    "content": "using Newtonsoft.Json;\nusing System.IO;\nusing System.Net;\nusing System.Net.Http;\nusing System.Text;\n\npublic static class AchievementRoutes\n{\n    private static readonly Dictionary<string, string> ServiceConfigCache = new();\n    public static Dictionary<string, Func<HttpListenerContext, Task>> GetRoutes(Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\n    {\n        return new Dictionary<string, Func<HttpListenerContext, Task>>\n        {\n            { \"/api/achievements/unlockall\", async context => await UnlockAllAchievementsRequest(context, getXboxRestAPI, getXUIDOnly) },\n            { \"/api/achievements/unlock\", async context => await UnlockAchievementRequest(context, getXboxRestAPI, getXUIDOnly) },\n            { \"/api/achievements/\", async context => await AchievementsTitleRequest(context, getXboxRestAPI, getXUIDOnly) }\n\n        };\n    }\n\n    private static async Task AchievementsTitleRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\n    {\n        var request = context.Request;\n        var response = context.Response;\n\n        try\n        {\n            if (request.Url.Segments.Length < 3)\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No Title ID provided in URL\" });\n                return;\n            }\n\n            string titleId = request.Url.Segments.Last().TrimEnd('/');\n\n            string xuid = getXUIDOnly?.Invoke();\n            if (string.IsNullOrWhiteSpace(xuid))\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No XUID found\" });\n                return;\n            }\n\n            var xboxRestAPI = getXboxRestAPI();\n\n            var achievements = await xboxRestAPI.GetAchievementsForTitleAsync(xuid, titleId);\n\n            if (achievements == null || achievements.achievements == null || !achievements.achievements.Any())\n            {\n                var xbox360Achievements = await xboxRestAPI.GetAchievementsFor360TitleAsync(xuid, titleId);\n\n                if (xbox360Achievements != null)\n                {\n                    await SendJsonResponse(response, xbox360Achievements);\n                    return;\n                }\n            }\n\n            if (achievements == null)\n            {\n                response.StatusCode = 404;\n                await SendJsonResponse(response, new { error = \"Achievements not found\" });\n                return;\n            }\n\n            await SendJsonResponse(response, achievements);\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new\n            {\n                error = ex.Message,\n                innerError = ex.InnerException?.Message\n            });\n        }\n    }\n    private static async Task UnlockAchievementRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\n    {\n        var request = context.Request;\n        var response = context.Response;\n\n        try\n        {\n            if (request.HttpMethod != \"POST\")\n            {\n                response.StatusCode = 405;\n                await SendJsonResponse(response, new { error = \"Method not allowed. Use POST.\" });\n                return;\n            }\n\n            using var reader = new StreamReader(request.InputStream);\n            var body = await reader.ReadToEndAsync();\n            var unlockRequest = JsonConvert.DeserializeObject<UnlockAchievementRequest>(body);\n\n            if (unlockRequest == null || string.IsNullOrWhiteSpace(unlockRequest.TitleId) || string.IsNullOrWhiteSpace(unlockRequest.AchievementId))\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"Invalid request. Provide TitleId and AchievementId in your body.\" });\n                return;\n            }\n\n            string xuid = getXUIDOnly?.Invoke();\n            if (string.IsNullOrWhiteSpace(xuid))\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No XUID found\" });\n                return;\n            }\n\n            string serviceConfigId = await GetServiceConfigId(getXboxRestAPI, getXUIDOnly, unlockRequest.TitleId);\n\n            var xboxRestAPI = getXboxRestAPI();\n            await xboxRestAPI.UnlockTitleBasedAchievementAsync(\n                serviceConfigId,\n                unlockRequest.TitleId,\n                xuid,\n                unlockRequest.AchievementId\n            );\n\n            await SendJsonResponse(response, new\n            {\n                message = \"Achievement unlocked successfully\",\n                achievementId = unlockRequest.AchievementId,\n                titleId = unlockRequest.TitleId\n            });\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new\n            {\n                error = ex.Message,\n                innerError = ex.InnerException?.Message\n            });\n        }\n    }\n\n    private static async Task UnlockAllAchievementsRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\n    {\n        var request = context.Request;\n        var response = context.Response;\n\n        try\n        {\n            if (request.HttpMethod != \"POST\")\n            {\n                response.StatusCode = 405;\n                await SendJsonResponse(response, new { error = \"Method not allowed. Use POST.\" });\n                return;\n            }\n\n            using var reader = new StreamReader(request.InputStream);\n            var body = await reader.ReadToEndAsync();\n            var unlockRequest = JsonConvert.DeserializeObject<UnlockAllAchievementsRequest>(body);\n\n            if (unlockRequest == null || string.IsNullOrWhiteSpace(unlockRequest.TitleId))\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"Invalid request. Provide TitleId in your body.\" });\n                return;\n            }\n\n            string serviceConfigId = await GetServiceConfigId(getXboxRestAPI, getXUIDOnly, unlockRequest.TitleId);\n\n            string xuid = getXUIDOnly?.Invoke();\n            if (string.IsNullOrWhiteSpace(xuid))\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No XUID found\" });\n                return;\n            }\n\n            var xboxRestAPI = getXboxRestAPI();\n            var achievements = await xboxRestAPI.GetAchievementsForTitleAsync(xuid, unlockRequest.TitleId);\n\n            var achievementIds = achievements.achievements.Select(a => a.id).ToList();\n            await xboxRestAPI.UnlockTitleBasedAchievementsAsync(\n                serviceConfigId,\n                unlockRequest.TitleId,\n                xuid,\n                achievementIds\n            );\n\n            await SendJsonResponse(response, new\n            {\n                message = \"All achievements unlocked successfully\",\n                titleId = unlockRequest.TitleId,\n                totalAchievements = achievementIds.Count\n            });\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new\n            {\n                error = ex.Message,\n                innerError = ex.InnerException?.Message\n            });\n        }\n    }\n\n    private static async Task<string> GetServiceConfigId(Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly, string titleId)\n    {\n        if (ServiceConfigCache.TryGetValue(titleId, out var cachedServiceConfigId))\n        {\n            return cachedServiceConfigId;\n        }\n\n        var xuid = getXUIDOnly?.Invoke();\n        if (string.IsNullOrWhiteSpace(xuid)) throw new Exception(\"No XUID found.\");\n\n        var xboxRestAPI = getXboxRestAPI();\n        var achievements = await xboxRestAPI.GetAchievementsForTitleAsync(xuid, titleId);\n\n        if (achievements == null || achievements.achievements == null || !achievements.achievements.Any())\n        {\n            throw new Exception(\"Achievements not found for the title.\");\n        }\n\n        var serviceConfigId = achievements.achievements.FirstOrDefault()?.serviceConfigId;\n        if (string.IsNullOrWhiteSpace(serviceConfigId))\n        {\n            throw new Exception(\"ServiceConfigId not found for the title.\");\n        }\n\n        ServiceConfigCache[titleId] = serviceConfigId;\n\n        return serviceConfigId;\n    }\n\n    private static async Task SendJsonResponse(HttpListenerResponse response, object data)\n    {\n        var json = JsonConvert.SerializeObject(data);\n        var buffer = Encoding.UTF8.GetBytes(json);\n\n        response.ContentLength64 = buffer.Length;\n        response.ContentType = \"application/json\";\n\n        await response.OutputStream.WriteAsync(buffer);\n        response.Close();\n    }\n}\n\npublic class UnlockAchievementRequest\n{\n    public string TitleId { get; set; }\n    public string AchievementId { get; set; }\n}\n\npublic class UnlockAllAchievementsRequest\n{\n    public string TitleId { get; set; }\n}\n"
  },
  {
    "path": "XAU/Services/HttpServer/EndpointRoutes.cs",
    "content": "using Newtonsoft.Json;\nusing System.Net;\nusing System.Text;\n\npublic static class EndpointRoutes\n{\n    public static Dictionary<string, Func<HttpListenerContext, Task>> GetRoutes()\n    {\n        return new Dictionary<string, Func<HttpListenerContext, Task>>\n        {\n            { \"/api/\", async context => await IndexPageRequest(context) },\n            { \"/\", async context => await IndexPageRequest(context) }\n        };\n    }\n\n    private static async Task IndexPageRequest(HttpListenerContext context)\n    {\n        var response = context.Response;\n        response.ContentType = \"application/json\";\n\n        string htmlContent = @\"\n<!-- meow meow :) -->\n<!DOCTYPE html>\n<html lang=\"\"en\"\">\n<head>\n    <meta charset=\"\"UTF-8\"\">\n    <meta name=\"\"viewport\"\" content=\"\"width=device-width, initial-scale=1.0\"\">\n    <title>XAU API Endpoints</title>\n    <link rel=\"\"icon\"\" type=\"\"image/x-icon\"\" href=\"\"https://raw.githubusercontent.com/Fumo-Unlockers/Xbox-Achievement-Unlocker/refs/heads/Main/XAU/cirno.ico\"\">\n    <link href=\"\"https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css\"\" rel=\"\"stylesheet\"\">\n    <script>\n        function openEndpoint(baseUrl, inputId) {\n            const input = document.getElementById(inputId);\n            const value = input.value.trim();\n            if (value) {\n                window.open(baseUrl + encodeURIComponent(value), '_blank');\n            } else {\n                alert('Oopsie Woopsy Fucky Wucky... Enter a value in the text box.');\n            }\n        }\n        function openEndpointWithTwoInputs(baseUrl, inputId1, middlePath, inputId2) {\n            const input1 = document.getElementById(inputId1);\n            const input2 = document.getElementById(inputId2);\n            const value1 = input1.value.trim();\n            const value2 = input2.value.trim();\n            if (value1 && value2) {\n                window.open(baseUrl + encodeURIComponent(value1) + middlePath + encodeURIComponent(value2), '_blank');\n            } else {\n                alert('Oopsie Woopsy Fucky Wucky... Enter a value in the text box.');\n            }\n        }\n    </script>\n    <style>\n        .placeholder {\n            color: #ef4444;\n            font-weight: bold;\n        }\n    </style>\n</head>\n<body class=\"\"bg-gray-100 min-h-screen p-8\"\">\n    <div class=\"\"container mx-auto\"\">\n        <h1 class=\"\"text-4xl font-bold text-center mb-2 text-gray-800\"\">XAU API Endpoints</h1>\n        <p class=\"\"text-center text-red-600 font-medium text-sm mb-6\"\">\n            Warning: These endpoints are still in beta and have not been extensively tested. Use at your own risk!\n        </p>\n        <div class=\"\"grid md:grid-cols-2 lg:grid-cols-3 gap-6\"\">\n<!-- START ENDPOINTS -->\n<!-- Profile Endpoints -->\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded text-sm\"\">GET</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/profile/me</h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Get the current user's profile information</p>\n                <a href=\"\"/api/profile/me\"\" target=\"\"_blank\"\" class=\"\"btn bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\">Open Endpoint</a>\n            </div>\n\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded text-sm\"\">GET</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/profile/xuid/<span class=\"\"placeholder\"\">{xuid}</span></h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Get profile by XUID</p>\n                <div class=\"\"flex items-center space-x-2\"\">\n                    <input type=\"\"text\"\" id=\"\"xuidInput\"\" placeholder=\"\"Enter XUID\"\" class=\"\"flex-grow px-3 py-2 border rounded\"\">\n                    <button onclick=\"\"openEndpoint('/api/profile/xuid/', 'xuidInput')\"\" class=\"\"bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600\"\">Go</button>\n                </div>\n            </div>\n\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded text-sm\"\">GET</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/profile/gt/<span class=\"\"placeholder\"\">{gamertag}</span></h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Get profile by Gamertag</p>\n                <div class=\"\"flex items-center space-x-2\"\">\n                    <input type=\"\"text\"\" id=\"\"gamertagInput\"\" placeholder=\"\"Enter Gamertag\"\" class=\"\"flex-grow px-3 py-2 border rounded\"\">\n                    <button onclick=\"\"openEndpoint('/api/profile/gt/', 'gamertagInput')\"\" class=\"\"bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600\"\">Go</button>\n                </div>\n            </div>\n\n<!-- Games Endpoints -->\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded text-sm\"\">GET</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/games/me</h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Get the current user's games list</p>\n                <a href=\"\"/api/games/me\"\" target=\"\"_blank\"\" class=\"\"btn bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\">Open Endpoint</a>\n            </div>\n\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded text-sm\"\">GET</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/games/xuid/<span class=\"\"placeholder\"\">{xuid}</span></h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Get games list by XUID</p>\n                <div class=\"\"flex items-center space-x-2\"\">\n                    <input type=\"\"text\"\" id=\"\"gamesXuidInput\"\" placeholder=\"\"Enter XUID\"\" class=\"\"flex-grow px-3 py-2 border rounded\"\">\n                    <button onclick=\"\"openEndpoint('/api/games/xuid/', 'gamesXuidInput')\"\" class=\"\"bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600\"\">Go</button>\n                </div>\n            </div>\n\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded text-sm\"\">GET</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/games/gt/<span class=\"\"placeholder\"\">{gamertag}</span></h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Get games list by Gamertag</p>\n                <div class=\"\"flex items-center space-x-2\"\">\n                    <input type=\"\"text\"\" id=\"\"gamesGamertagInput\"\" placeholder=\"\"Enter Gamertag\"\" class=\"\"flex-grow px-3 py-2 border rounded\"\">\n                    <button onclick=\"\"openEndpoint('/api/games/gt/', 'gamesGamertagInput')\"\" class=\"\"bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600\"\">Go</button>\n                </div>\n            </div>\n\n<!-- XAuth Endpoint -->\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded text-sm\"\">GET</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/xauth</h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Get XAuth token</p>\n                <a href=\"\"/api/xauth\"\" target=\"\"_blank\"\" class=\"\"btn bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\">Plain Text Format</a>\n                <a href=\"\"/api/xauth?format=json\"\" target=\"\"_blank\"\" class=\"\"btn bg-yellow-500 text-white px-4 py-2 rounded hover:bg-yellow-600 ml-2\"\">JSON Format</a>\n            </div>\n\n<!-- Spoofing Endpoint -->\n\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded text-sm\"\">GET</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/spoof/<span class=\"\"placeholder\"\">{titleid}</span></h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Spoofs the specified title for 5 minutes. Make an API request every 5 minutes to keep the spoofer working.</p>\n                <div class=\"\"flex items-center space-x-2\"\">\n                    <input type=\"\"text\"\" id=\"\"spoofIdInput\"\" placeholder=\"\"Enter Title ID\"\" class=\"\"flex-grow px-3 py-2 border rounded\"\">\n                    <button onclick=\"\"openEndpoint('/api/spoof/', 'spoofIdInput')\"\" class=\"\"bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600\"\">Go</button>\n                </div>\n            </div>\n\n<!-- Achievements Endpoint -->\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded text-sm\"\">GET</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/achievements/<span class=\"\"placeholder\"\">{titleid}</span></h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Get achievements for a specific title</p>\n                <div class=\"\"flex items-center space-x-2\"\">\n                    <input type=\"\"text\"\" id=\"\"titleIdInput\"\" placeholder=\"\"Enter Title ID\"\" class=\"\"flex-grow px-3 py-2 border rounded\"\">\n                    <button onclick=\"\"openEndpoint('/api/achievements/', 'titleIdInput')\"\" class=\"\"bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600\"\">Go</button>\n                </div>\n            </div>\n\n\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-green-500 text-white px-2 py-1 rounded text-sm\"\">POST</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/achievements/unlock</h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Unlock achievements of title-based game by ID.</p>\n                <div class=\"\"flex space-x-4\"\">\n                    <pre class=\"\"bg-gray-800 text-white p-4 rounded-lg overflow-x-auto w-full whitespace-nowrap\"\">\n                        <code class=\"\"block\"\">{\"\"titleId\"\": \"\"xxxxx\"\",\"\"achievementId\"\": \"\"xxx\"\"}</code>\n                    </pre>\n                </div>\n            </div>\n\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-green-500 text-white px-2 py-1 rounded text-sm\"\">POST</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/achievements/unlockall</h2>\n                 <p class=\"\"text-gray-600 mb-4\"\">⚠️ BEWARE: Unlocks <u><strong>ALL</strong></u> achievements of a game ⚠️</p>\n                <div class=\"\"flex space-x-4\"\">\n                    <pre class=\"\"bg-gray-800 text-white p-4 rounded-lg overflow-x-auto w-full whitespace-nowrap\"\">\n                        <code class=\"\"block\"\"> {\"\"TitleId\"\": \"\"xxxxx\"\"} </code>\n                    </pre>\n                </div>\n            </div>\n<!-- Game Search Endpoints -->\n\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded text-sm\"\">GET</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/games/search/titleid/<span class=\"\"placeholder\"\">{titleid}</span></h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Retrieve game details for a specific Title ID.</p>\n                <div class=\"\"flex items-center space-x-2\"\">\n                    <input type=\"\"text\"\" id=\"\"searchTitleIdInput\"\" placeholder=\"\"Enter Title ID\"\" class=\"\"flex-grow px-3 py-2 border rounded\"\">\n                    <button onclick=\"\"openEndpoint('/api/games/search/titleid/', 'searchTitleIdInput')\"\" class=\"\"bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600\"\">GO</button>\n                </div>\n            </div>\n\n            <div class=\"\"bg-white rounded-xl shadow-lg p-6 relative\"\">\n                <span class=\"\"absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded text-sm\"\">GET</span>\n                <h2 class=\"\"text-xl font-semibold mb-4 text-gray-700\"\">/api/games/search/productid/<span class=\"\"placeholder\"\">{productid}</span></h2>\n                <p class=\"\"text-gray-600 mb-4\"\">Retrieve game details for a specific product ID.</p>\n                <div class=\"\"flex items-center space-x-2\"\">\n                    <input type=\"\"text\"\" id=\"\"searchProductIdInput\"\" placeholder=\"\"Enter Product ID\"\" class=\"\"flex-grow px-3 py-2 border rounded\"\">\n                    <button onclick=\"\"openEndpoint('/api/games/search/productid/', 'searchProductIdInput')\"\" class=\"\"bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600\"\">GO</button>\n           </div>\n\n<!-- END ENDPOINTS -->\n        </div>\n    </div>\n</body>\n</html>\n\n\n\";\n        var buffer = Encoding.UTF8.GetBytes(htmlContent);\n        response.ContentLength64 = buffer.Length;\n        response.ContentType = \"text/html\";\n\n        await response.OutputStream.WriteAsync(buffer);\n        response.Close();\n    }\n}\n"
  },
  {
    "path": "XAU/Services/HttpServer/GameRoutes.cs",
    "content": "using Newtonsoft.Json;\nusing System.Net;\nusing System.Text;\n\npublic static class GameRoutes\n{\n    public static Dictionary<string, Func<HttpListenerContext, Task>> GetRoutes(Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\n    {\n        return new Dictionary<string, Func<HttpListenerContext, Task>>\n    {\n        { \"/api/games/me\", async context => await GamesMeRequest(context, getXboxRestAPI, getXUIDOnly) },\n        { \"/api/games/xuid/\", async context => await GamesXuidRequest(context, getXboxRestAPI) },\n        { \"/api/games/gt/\", async context => await GamesGamertagRequest(context, getXboxRestAPI) },\n        { \"/api/games/search/titleid/\", async context => await GameTitleIDSearchRequest(context, getXboxRestAPI, getXUIDOnly) },\n        { \"/api/games/search/productid/\", async context => await GameProductIDSearchRequest(context, getXboxRestAPI) }\n\n\n    };\n    }\n\n    private static async Task GamesMeRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\n    {\n        var response = context.Response;\n\n        try\n        {\n            string xuid = getXUIDOnly?.Invoke();\n            if (string.IsNullOrWhiteSpace(xuid))\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No XUID found\" });\n                return;\n            }\n\n            var xboxRestAPI = getXboxRestAPI();\n            var gamesList = await xboxRestAPI.GetGamesListAsync(xuid);\n\n            if (gamesList == null)\n            {\n                response.StatusCode = 404;\n                await SendJsonResponse(response, new { error = \"Games list not found\" });\n                return;\n            }\n\n            await SendJsonResponse(response, gamesList);\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message });\n        }\n    }\n\n    private static async Task GamesXuidRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI)\n    {\n        var request = context.Request;\n        var response = context.Response;\n\n        try\n        {\n            if (request.Url.Segments.Length < 3)\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No XUID provided in URL\" });\n                return;\n            }\n\n            string xuid = request.Url.Segments.Last().TrimEnd('/');\n            var xboxRestAPI = getXboxRestAPI();\n\n            var gamesList = await xboxRestAPI.GetGamesListAsync(xuid);\n\n            if (gamesList == null)\n            {\n                response.StatusCode = 404;\n                await SendJsonResponse(response, new { error = \"Games list not found\" });\n                return;\n            }\n\n            await SendJsonResponse(response, gamesList);\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message });\n        }\n    }\n\n    private static async Task GamesGamertagRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI)\n    {\n        var request = context.Request;\n        var response = context.Response;\n\n        try\n        {\n            if (request.Url.Segments.Length < 3)\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No Gamertag provided in URL\" });\n                return;\n            }\n\n            string gamertag = request.Url.Segments.Last().TrimEnd('/');\n            var xboxRestAPI = getXboxRestAPI();\n\n            var gamertagProfile = await xboxRestAPI.GetGamertagProfileAsync(gamertag);\n            if (gamertagProfile == null || !gamertagProfile.ContainsKey(\"profileUsers\"))\n            {\n                response.StatusCode = 404;\n                await SendJsonResponse(response, new { error = \"Gamertag not found\" });\n                return;\n            }\n\n            string xuid = gamertagProfile[\"profileUsers\"][0][\"id\"].ToString();\n            var gamesList = await xboxRestAPI.GetGamesListAsync(xuid);\n\n            if (gamesList == null)\n            {\n                response.StatusCode = 404;\n                await SendJsonResponse(response, new { error = \"Games list not found\" });\n                return;\n            }\n\n            await SendJsonResponse(response, gamesList);\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message });\n        }\n    }\n\n    private static async Task GameTitleIDSearchRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\n    {\n        var request = context.Request;\n        var response = context.Response;\n\n        try\n        {\n            if (request.Url.Segments.Length < 4)\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No Title ID provided in URL\" });\n                return;\n            }\n\n            string titleId = request.Url.Segments.Last().TrimEnd('/');\n\n            string xuid = getXUIDOnly?.Invoke();\n            if (string.IsNullOrWhiteSpace(xuid))\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No XUID found\" });\n                return;\n            }\n\n            var xboxRestAPI = getXboxRestAPI();\n            var gameTitle = await xboxRestAPI.GetGameTitleAsync(xuid, titleId);\n\n            if (gameTitle == null)\n            {\n                response.StatusCode = 404;\n                await SendJsonResponse(response, new { error = \"No Games found\" });\n                return;\n            }\n\n            await SendJsonResponse(response, gameTitle);\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message });\n        }\n    }\n\n    private static async Task GameProductIDSearchRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI)\n    {\n        var request = context.Request;\n        var response = context.Response;\n\n        try\n        {\n            if (request.Url.Segments.Length < 4)\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No Product ID provided in URL\" });\n                return;\n            }\n\n            string productId = request.Url.Segments.Last().TrimEnd('/');\n\n            var xboxRestAPI = getXboxRestAPI();\n            var gamePassProducts = await xboxRestAPI.GetTitleIdsFromGamePass(productId);\n\n            if (gamePassProducts == null)\n            {\n                response.StatusCode = 404;\n                await SendJsonResponse(response, new { error = \"No Games found\" });\n                return;\n            }\n\n            await SendJsonResponse(response, gamePassProducts);\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message });\n        }\n    }\n\n    private static async Task SendJsonResponse(HttpListenerResponse response, object data)\n    {\n        var json = JsonConvert.SerializeObject(data);\n        var buffer = Encoding.UTF8.GetBytes(json);\n\n        response.ContentLength64 = buffer.Length;\n        response.ContentType = \"application/json\";\n\n        await response.OutputStream.WriteAsync(buffer);\n        response.Close();\n    }\n}\n"
  },
  {
    "path": "XAU/Services/HttpServer/HttpServer.cs",
    "content": "using System.Diagnostics;\nusing System.Net;\nusing System.Security.Principal;\nusing System.Runtime.Versioning;\nusing System.Net.NetworkInformation;\nusing System.Net.Sockets;\n\nnamespace XAU.Services.HttpServer\n{\n    public sealed class HttpServer : IDisposable\n    {\n        private readonly HttpListener _listener;\n        private readonly Dictionary<string, Func<HttpListenerContext, Task>> _routes;\n        private string _port;\n        private bool _isRunning;\n        private bool _disposed;\n        private const string FirewallRuleName = \"XAU API Server\";\n\n        public HttpServer(string port, Dictionary<string, Func<HttpListenerContext, Task>> routes)\n        {\n            _port = port;\n            _routes = routes;\n            _listener = new HttpListener();\n            UpdateListenerPrefixes();\n        }\n\n        private static bool IsAdministrator()\n        {\n            using var identity = WindowsIdentity.GetCurrent();\n            var principal = new WindowsPrincipal(identity);\n            return principal.IsInRole(WindowsBuiltInRole.Administrator);\n        }\n\n        [SupportedOSPlatform(\"windows\")]\n        public void RestartAsAdmin()\n        {\n            var startInfo = new ProcessStartInfo\n            {\n                UseShellExecute = true,\n                WorkingDirectory = Environment.CurrentDirectory,\n                FileName = Process.GetCurrentProcess().MainModule?.FileName,\n                Verb = \"runas\"\n            };\n\n            Process.Start(startInfo);\n            Environment.Exit(0);\n        }\n\n        private void UpdateListenerPrefixes()\n        {\n            _listener.Prefixes.Clear();\n            _listener.Prefixes.Add($\"http://localhost:{_port}/\");\n\n            if (IsAdministrator())\n            {\n                // Admin required for other PCs on the network to access API (ex: XAU Mobile)\n                _listener.Prefixes.Add($\"http://*:{_port}/\");\n            }\n        }\n\n        public static void AddFirewallRule(string port)\n        {\n            if (RuleExists(port)) return;\n\n            var process = new Process\n            {\n                StartInfo = new ProcessStartInfo\n                {\n                    FileName = \"netsh\",\n                    Arguments = $\"advfirewall firewall add rule name=\\\"{FirewallRuleName}\\\" dir=in action=allow protocol=TCP localport={port}\",\n                    Verb = \"runas\",\n                    UseShellExecute = true,\n                    CreateNoWindow = true\n                }\n            };\n\n            process.Start();\n            process.WaitForExit();\n        }\n\n        private static bool RuleExists(string port)\n        {\n            var process = new Process\n            {\n                StartInfo = new ProcessStartInfo\n                {\n                    FileName = \"netsh\",\n                    Arguments = $\"advfirewall firewall show rule name=\\\"{FirewallRuleName}\\\"\",\n                    UseShellExecute = false,\n                    RedirectStandardOutput = true,\n                    CreateNoWindow = true\n                }\n            };\n\n            process.Start();\n            var output = process.StandardOutput.ReadToEnd();\n            process.WaitForExit();\n\n            return output.Contains(port);\n        }\n\n        public string GetListeningAddress()\n        {\n            if (_listener.Prefixes.Count == 1 && _listener.Prefixes.First().Contains(\"localhost\"))\n            {\n                return $\"http://localhost:{_port}\";\n            }\n\n            return $\"http://{GetLocalIPAddress()}:{_port}\";\n        }\n\n        public static string GetLocalIPAddress()\n        {\n            try\n            {\n                var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces()\n                    .Where(n => n.OperationalStatus == OperationalStatus.Up\n                                && n.NetworkInterfaceType != NetworkInterfaceType.Loopback);\n\n                foreach (var network in networkInterfaces)\n                {\n                    var properties = network.GetIPProperties();\n\n                    var ipv4 = properties.UnicastAddresses\n                        .FirstOrDefault(ua => ua.Address.AddressFamily == AddressFamily.InterNetwork);\n\n                    if (ipv4 != null)\n                    {\n                        return ipv4.Address.ToString();\n                    }\n                }\n\n                return \"127.0.0.1\";\n            }\n            catch\n            {\n                return \"127.0.0.1\";\n            }\n        }\n\n        public void Start()\n        {\n            if (_isRunning) return;\n\n            try\n            {\n                if (!IsAdministrator())\n                {\n                    _listener.Start();\n                    _isRunning = true;\n                    Task.Run(HandleRequests);\n                    return;\n                }\n\n                AddFirewallRule(_port);\n                _listener.Start();\n                _isRunning = true;\n                Task.Run(HandleRequests);\n            }\n            catch (HttpListenerException ex)\n            {\n                if (IsAdministrator())\n                {\n                    RestartAsAdmin();\n                }\n                else\n                {\n                    Debug.WriteLine($\"Failed to start HTTP server: {ex.Message}\");\n                }\n            }\n        }\n\n        public void Stop()\n        {\n            if (!_isRunning) return;\n            _listener.Stop();\n            _isRunning = false;\n        }\n\n        public void UpdatePort(string newPort)\n        {\n            if (_port == newPort) return;\n\n            bool wasRunning = _isRunning;\n            if (wasRunning) Stop();\n\n            _port = newPort;\n            UpdateListenerPrefixes();\n\n            if (wasRunning) Start();\n        }\n\n        private async Task HandleRequests()\n        {\n            while (_isRunning)\n            {\n                try\n                {\n                    var context = await _listener.GetContextAsync();\n                    var path = context.Request.Url?.LocalPath ?? string.Empty;\n\n                    var handler = _routes\n                        .Where(r => path.StartsWith(r.Key))\n                        .OrderByDescending(r => r.Key.Length)\n                        .Select(r => r.Value)\n                        .FirstOrDefault();\n\n                    if (handler != null)\n                    {\n                        await handler.Invoke(context);\n                    }\n                    else\n                    {\n                        context.Response.StatusCode = 404;\n                        context.Response.Close();\n                    }\n                }\n                catch (Exception ex)\n                {\n                    Debug.WriteLine($\"Error handling request: {ex.Message}\");\n                    _isRunning = false;\n                }\n            }\n        }\n\n        public bool IsRunning => _isRunning;\n\n        public void Dispose()\n        {\n            if (_disposed) return;\n\n            if (_isRunning) Stop();\n            _listener.Close();\n            _disposed = true;\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Services/HttpServer/ProfileRoutes.cs",
    "content": "using Newtonsoft.Json;\nusing System.Net;\nusing System.Text;\n\npublic static class ProfileRoutes\n{\n    public static Dictionary<string, Func<HttpListenerContext, Task>> GetRoutes(Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\n    {\n        return new Dictionary<string, Func<HttpListenerContext, Task>>\n        {\n            { \"/api/profile/me\", async context => await ProfileMeRequest(context, getXboxRestAPI, getXUIDOnly) },\n            { \"/api/profile/xuid/\", async context => await ProfileXuidRequest(context, getXboxRestAPI) },\n            { \"/api/profile/gt/\", async context => await ProfileGamertagRequest(context, getXboxRestAPI) }\n        };\n    }\n\n    private static async Task ProfileMeRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\n    {\n        var response = context.Response;\n\n        try\n        {\n            string xuid = getXUIDOnly?.Invoke();\n            if (string.IsNullOrWhiteSpace(xuid))\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No XUID found\" });\n                return;\n            }\n\n            var xboxRestAPI = getXboxRestAPI();\n\n            var profile = await xboxRestAPI.GetProfileAsync(xuid);\n\n            if (profile == null)\n            {\n                response.StatusCode = 404;\n                await SendJsonResponse(response, new { error = \"Profile not found\" });\n                return;\n            }\n\n            await SendJsonResponse(response, profile);\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new\n            {\n                error = ex.Message,\n                innerError = ex.InnerException?.Message\n            });\n        }\n    }\n\n    private static async Task ProfileXuidRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI)\n    {\n        var request = context.Request;\n        var response = context.Response;\n\n        try\n        {\n            if (request.Url.Segments.Length < 4)\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No XUID provided in URL\" });\n                return;\n            }\n\n            string xuid = request.Url.Segments.Last().TrimEnd('/');\n\n            if (string.IsNullOrWhiteSpace(xuid))\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"Invalid XUID in URL\" });\n                return;\n            }\n\n            var xboxRestAPI = getXboxRestAPI();\n            var profile = await xboxRestAPI.GetProfileAsync(xuid);\n\n            if (profile == null)\n            {\n                response.StatusCode = 404;\n                await SendJsonResponse(response, new { error = \"Profile not found\" });\n                return;\n            }\n\n            await SendJsonResponse(response, profile);\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new\n            {\n                error = ex.Message,\n                innerError = ex.InnerException?.Message\n            });\n        }\n    }\n\n    private static async Task ProfileGamertagRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI)\n    {\n        var request = context.Request;\n        var response = context.Response;\n\n        try\n        {\n            if (request.Url.Segments.Length < 4)\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No Gamertag provided in URL\" });\n                return;\n            }\n\n            string gamertag = request.Url.Segments.Last().TrimEnd('/');\n\n            var xboxRestAPI = getXboxRestAPI();\n\n            // First, get the profile to extract XUID\n            var gamertagProfile = await xboxRestAPI.GetGamertagProfileAsync(gamertag);\n\n            if (gamertagProfile == null ||\n                gamertagProfile[\"profileUsers\"] == null ||\n                !gamertagProfile[\"profileUsers\"].Any())\n            {\n                response.StatusCode = 404;\n                await SendJsonResponse(response, new { error = \"Gamertag not found\" });\n                return;\n            }\n\n            string xuid = gamertagProfile[\"profileUsers\"][0][\"id\"].ToString();\n\n            var fullProfile = await xboxRestAPI.GetProfileAsync(xuid);\n\n            if (fullProfile == null)\n            {\n                response.StatusCode = 404;\n                await SendJsonResponse(response, new { error = \"Full profile not found\" });\n                return;\n            }\n\n            await SendJsonResponse(response, fullProfile);\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new\n            {\n                error = ex.Message,\n                innerError = ex.InnerException?.Message\n            });\n        }\n    }\n\n    private static async Task SendJsonResponse(HttpListenerResponse response, object data)\n    {\n        var json = JsonConvert.SerializeObject(data);\n        var buffer = Encoding.UTF8.GetBytes(json);\n\n        response.ContentLength64 = buffer.Length;\n        response.ContentType = \"application/json\";\n\n        await response.OutputStream.WriteAsync(buffer);\n        response.Close();\n    }\n}\n"
  },
  {
    "path": "XAU/Services/HttpServer/Routes.cs",
    "content": "using Newtonsoft.Json;\nusing System.Net;\nusing System.Text;\r\n\r\npublic static class Routes\r\n{\r\n    public static Dictionary<string, Func<HttpListenerContext, Task>> GetRoutes(Func<string> getXauthToken, Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\r\n    {\r\n        var routes = new Dictionary<string, Func<HttpListenerContext, Task>>\r\n        {\r\n            { \"/api/xauth\", async context => await ApiXauthRequest(context, getXauthToken) }\r\n        };\r\n\r\n        var profileRoutes = ProfileRoutes.GetRoutes(getXboxRestAPI, getXUIDOnly);\r\n        foreach (var route in profileRoutes)\r\n        {\r\n            routes[route.Key] = route.Value;\r\n        }\n\n        var gameRoutes = GameRoutes.GetRoutes(getXboxRestAPI, getXUIDOnly);\r\n        foreach (var route in gameRoutes)\r\n        {\r\n            routes[route.Key] = route.Value;\r\n        }\n\n        var achievementRoutes = AchievementRoutes.GetRoutes(getXboxRestAPI, getXUIDOnly);\n        foreach (var route in achievementRoutes)\n        {\n            routes[route.Key] = route.Value;\n        }\n\n        var spoofingRoutes = SpoofingRoutes.GetRoutes(getXboxRestAPI, getXUIDOnly);\n        foreach (var route in spoofingRoutes)\n        {\n            routes[route.Key] = route.Value;\n        }\n\n        var endpointRoutes = EndpointRoutes.GetRoutes();\n        foreach (var route in endpointRoutes)\n        {\n            routes[route.Key] = route.Value;\n        }\r\n\r\n        return routes;\r\n    }\r\n\r\n    private static async Task ApiXauthRequest(HttpListenerContext context, Func<string> getXauthToken)\n    {\r\n        var response = context.Response;\n        string query = context.Request.Url?.Query ?? string.Empty;\n\n        bool isJson = query.Contains(\"?format=json\", StringComparison.OrdinalIgnoreCase);\r\n\n        var xauth = getXauthToken();\n\n        if (isJson)\n        {\n            var jsonResponse = new { token = xauth };\n            var json = JsonConvert.SerializeObject(jsonResponse);\n            var buffer = Encoding.UTF8.GetBytes(json);\n\n            response.ContentLength64 = buffer.Length;\n            response.ContentType = \"application/json\";\n\n            await response.OutputStream.WriteAsync(buffer);\n        }\n        else\n        {\n            var buffer = Encoding.UTF8.GetBytes(xauth);\n\n            response.ContentLength64 = buffer.Length;\n            response.ContentType = \"text/plain\";\n\n            await response.OutputStream.WriteAsync(buffer);\n        }\n\n        response.Close();\n    }\r\n}\n"
  },
  {
    "path": "XAU/Services/HttpServer/SpoofingRoutes.cs",
    "content": "using Newtonsoft.Json;\nusing System.IO;\nusing System.Net;\nusing System.Text;\n\npublic static class SpoofingRoutes\n{\n    public static Dictionary<string, Func<HttpListenerContext, Task>> GetRoutes(Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\n    {\n        return new Dictionary<string, Func<HttpListenerContext, Task>>\n        {\n            { \"/api/spoof\", async context => await StartSpoofingRequest(context, getXboxRestAPI, getXUIDOnly) },\r\n        };\n    }\n\n    private static async Task StartSpoofingRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly)\n    {\n        var request = context.Request;\n        var response = context.Response;\n\n        try\n        {\n            if (request.HttpMethod != \"GET\")\n            {\n                response.StatusCode = 405;\n                await SendJsonResponse(response, new { error = \"Method not allowed. Use GET.\" });\n                return;\n            }\n\n            if (request.Url.Segments.Length < 3)\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No Title ID provided in URL.\" });\n                return;\n            }\n\n            string titleId = request.Url.Segments.Last().TrimEnd('/');\n            if (string.IsNullOrWhiteSpace(titleId))\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"Invalid Title ID.\" });\n                return;\n            }\n\n            string xuid = getXUIDOnly?.Invoke();\n            if (string.IsNullOrWhiteSpace(xuid))\n            {\n                response.StatusCode = 400;\n                await SendJsonResponse(response, new { error = \"No XUID found.\" });\n                return;\n            }\n\n            var xboxRestAPI = getXboxRestAPI();\n            await xboxRestAPI.SendHeartbeatAsync(xuid, titleId);\n\n            response.StatusCode = 200;\n            await SendJsonResponse(response, new { message = \"Spoofing started successfully.\", titleId });\n        }\n        catch (Exception ex)\n        {\n            response.StatusCode = 500;\n            await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message });\n        }\n    }\r\n\r\n\r\n\r\n    private static async Task SendJsonResponse(HttpListenerResponse response, object data)\n    {\n        var json = JsonConvert.SerializeObject(data);\n        var buffer = Encoding.UTF8.GetBytes(json);\n\n        response.ContentLength64 = buffer.Length;\n        response.ContentType = \"application/json\";\n\n        await response.OutputStream.WriteAsync(buffer);\n        response.Close();\n    }\n}\n\npublic class SpoofingRequest\n{\n    public string TitleId { get; set; }\n}\n"
  },
  {
    "path": "XAU/Theme/ThemeConstants.xaml",
    "content": "<ResourceDictionary xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n                    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\">\n    <Thickness x:Key=\"PageMargin\">15</Thickness>\n</ResourceDictionary>"
  },
  {
    "path": "XAU/Usings.cs",
    "content": "global using CommunityToolkit.Mvvm.ComponentModel;\nglobal using CommunityToolkit.Mvvm.Input;\nglobal using System;\nglobal using System.Windows;\nglobal using Wpf.Ui;\n//fix these to override winforms imports\nglobal using Clipboard = System.Windows.Clipboard;\nglobal using Brush = System.Windows.Media.Brush;\nglobal using HtmlDocument = HtmlAgilityPack.HtmlDocument;\nglobal using KeyEventArgs = System.Windows.Input.KeyEventArgs;\nglobal using ButtonBase = System.Windows.Controls.Primitives.ButtonBase;\nglobal using ListBox = System.Windows.Controls.ListBox;\n"
  },
  {
    "path": "XAU/Util/Constants/Constants.cs",
    "content": "// Minimize total number of string allocations if .NET runtime is opting to not intern them\nstruct StringConstants\n{\n    public const string Gamerscore = @\"Gamerscore\";\n    public const string Achieved = @\"Achieved\";\n    public const string ZeroUid = @\"00000000-0000-0000-0000-000000000000\";\n}\n\nstruct HeaderNames\n{\n    public const string ContractVersion = @\"x-xbl-contract-version\";\n    public const string AcceptEncoding = @\"Accept-Encoding\";\n    public const string Accept = @\"accept\";\n    public const string Authorization = @\"Authorization\";\n    public const string AcceptLanguage = @\"accept-language\";\n    public const string Host = @\"Host\";\n    public const string Connection = @\"Connection\";\n    public const string Signature = @\"Signature\";\n\n}\n\nstruct HeaderValues\n{\n    public const string ContractVersion2 = @\"2\";\n    public const string ContractVersion3 = @\"3\";\n    public const string ContractVersion4 = @\"4\";\n    public const string ContractVersion5 = @\"5\";\n    public const string AcceptEncoding = @\"gzip, deflate\";\n    public const string Accept = @\"application/json\";\n    public const string KeepAlive = @\"Keep-Alive\";\n    public const string Signature = @\"RGFtbklHb3R0YU1ha2VUaGlzU3RyaW5nU3VwZXJMb25nSHVoLkRvbnRFdmVuS25vd1doYXRTaG91bGRCZUhlcmVEcmFmZlN0cmluZw==\";\n\n}\n\nstruct OpenableLinks\n{\n    // Hardcoded links to socials\n    public const string Discord = @\"https://discord.gg/fCqM7287jG\";\n    public const string GitHubUserUrl = @\"https://github.com/ItsLogic\";\n    public const string EventsDocumentationUrl = @\"https://github.com/Fumo-Unlockers/Xbox-Achievement-Unlocker/blob/Main/Doc/Events.md\";\n}\n\nstruct Hosts\n{\n    // Xbox Live Header Host Values\n    public const string Achievements = @\"achievements.xboxlive.com\";\n    public const string Profile = @\"profile.xboxlive.com\";\n    public const string PeopleHub = @\"peoplehub.xboxlive.com\";\n    public const string TitleHub = @\"titlehub.xboxlive.com\";\n    public const string Telemetry = @\"v20.events.data.microsoft.com\";\n    public const string GitHubApi = @\"api.github.com\";\n    public const string GitHubRaw = @\"raw.githubusercontent.com\";\n\n}\n\npublic struct BasicXboxAPIUris\n{\n    public const string GamertagUrl = @\"https://profile.xboxlive.com/users/me/profile/settings?settings=Gamertag\";\n    public const string WatermarksUrl = @\"https://dlassets-ssl.xboxlive.com/public/content/ppl/watermarks/\";\n    public const string GamepassCatalogUrl = @\"https://catalog.gamepass.com/products?market=GB&language=en-GB&hydration=PCHome\";\n    public const string TelemetryUrl = @\"https://v20.events.data.microsoft.com/OneCollector/1.0/\";\n    public const string UserStatsUrl = @\"https://userstats.xboxlive.com/batch\";\n\n}\n\npublic struct InterpolatedXboxAPIUrls\n{\n    // TODO: could uri build things\n    public const string GamepassMembershipUrl = \"https://xgrant.xboxlive.com/users/xuid({0})/programInfo?filter=profile,activities,catalog\";\n    public const string ProfileUrl = \"https://peoplehub.xboxlive.com/users/me/people/xuids({0})/decoration/detail,preferredColor,presenceDetail,multiplayerSummary\";\n    public const string TitleUrl = \"https://titlehub.xboxlive.com/users/xuid({0})/titles/batch/decoration/GamePass,Achievement,Stats\";\n    public const string TitlesUrl = \"https://titlehub.xboxlive.com/users/xuid({0})/titles/titleHistory/decoration/Achievement,detail,scid?maxItems=10000\";\n    public const string QueryAchievementsUrl = \"https://achievements.xboxlive.com/users/xuid({0})/achievements?titleId={1}&maxItems=1000\";\n    public const string QueryAchievements360Url = \"https://achievements.xboxlive.com/users/xuid({0})/titleachievements?titleId={1}&maxItems=1000\";\n    public const string UpdateAchievementsUrl = \"https://achievements.xboxlive.com/users/xuid({0})/achievements/{1}/update\";\n    public const string HeartbeatUrl = \"https://presence-heartbeat.xboxlive.com/users/xuid({0})/devices/current/\";\n    public const string GamertagSearch = \"https://profile.xboxlive.com/users/gt({0})/profile/settings?settings=GameDisplayPicRaw,Gamerscore,Gamertag\";\n}\n\npublic struct ProcessNames\n{\n    public const string XboxPcApp = @\"XboxPcApp\";\n    public const string Solitaire = @\"Solitaire\";\n}\n\npublic struct AppLaunchUris\n{\n    public const string Solitaire = @\"shell:appsFolder\\Microsoft.MicrosoftSolitaireCollection_8wekyb3d8bbwe!App\";\n}\n\npublic struct EventsUrls\n{\n    public const string Zip = @\"https://github.com/Fumo-Unlockers/Xbox-Achievement-Unlocker/raw/Events-Data/Events.zip\";\n    public const string MetaUrl = @\"https://raw.githubusercontent.com/Fumo-Unlockers/Xbox-Achievement-Unlocker/Events-Data/meta.json\";\n}\n"
  },
  {
    "path": "XAU/Util/Etw/EtwTokenCapture.cs",
    "content": "using System.Diagnostics;\nusing System.IO;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing XAU.ViewModels.Pages;\n\nnamespace XAU.Util.Etw\n{\n    static class EtwTokenCapture\n    {\n        private static readonly string EtwSessionName = \"XAU_EventsTokenCapture\";\n        private static readonly string EtwTempDir = Path.Combine(Path.GetTempPath(), \"XAU_ETW\");\n        private static readonly string EtwEtlPath = Path.Combine(EtwTempDir, \"capture.etl\");\n\n        private static readonly Regex TicketHeaderRegex = new Regex(\n            @\"\"\"(\\d{5,12})\"\"\\s*=\\s*\"\"(x:XBL3\\.0 x=[^\"\"]{100,})\"\"\",\n            RegexOptions.Compiled);\n        private static readonly Regex BareTokenRegex = new Regex(\n            @\"x:XBL3\\.0 x=[\\w;+/=\\-\\.]{100,}\",\n            RegexOptions.Compiled);\n        private static readonly Regex OneCollectorUrlRegex = new Regex(\n            @\"v20\\.events\\.data\\.microsoft\\.com|OneCollector\",\n            RegexOptions.Compiled);\n\n        // Events RP x5t — used to distinguish events tokens from XAUTH tokens.\n        // This is the certificate thumbprint for events.xboxlive.com; it appears\n        // in the decoded JWE header JSON as \"x5t\":\"9wLGzMJDNz...\"\n        private const string EventsRpX5t = \"9wLGzMJDNz\";\n\n        /// <summary>\n        /// Checks whether a token was encrypted for the events RP by decoding\n        /// the JWE header and verifying the x5t certificate thumbprint.\n        /// Token format: \"x:XBL3.0 x={hash};{JWE}\" or \"XBL3.0 x={hash};{JWE}\"\n        /// </summary>\n        public static bool IsEventsRpToken(string token)\n        {\n            try\n            {\n                int semiIdx = token.IndexOf(';');\n                if (semiIdx < 0) return false;\n\n                string jwe = token.Substring(semiIdx + 1);\n                int dotIdx = jwe.IndexOf('.');\n                if (dotIdx <= 0) return false;\n\n                string headerB64 = jwe.Substring(0, dotIdx);\n                // Base64url → standard Base64\n                string padded = headerB64.Replace('-', '+').Replace('_', '/');\n                switch (padded.Length % 4)\n                {\n                    case 2: padded += \"==\"; break;\n                    case 3: padded += \"=\"; break;\n                }\n\n                string headerJson = Encoding.UTF8.GetString(Convert.FromBase64String(padded));\n                return headerJson.Contains(EventsRpX5t);\n            }\n            catch\n            {\n                return false;\n            }\n        }\n\n        /// <summary>\n        /// One-shot: start trace, wait, stop, extract, cleanup. Returns token or null.\n        /// </summary>\n        public static string Capture(int captureSeconds)\n        {\n            HomeViewModel.EventsLog($\"Starting ETW capture for {captureSeconds}s...\");\n\n            Cleanup();\n\n            string method = Start();\n            if (method == null)\n            {\n                HomeViewModel.EventsLog(\"Failed to start ETW trace (not running as admin?)\");\n                return null;\n            }\n            HomeViewModel.EventsLog($\"ETW started via {method}\");\n\n            Thread.Sleep(captureSeconds * 1000);\n\n            Stop(method);\n\n            // Give it a moment to flush\n            Thread.Sleep(2000);\n\n            string token = ExtractTokens();\n\n            CleanupFiles();\n\n            return token;\n        }\n\n        public static void Cleanup()\n        {\n            try { RunShellCommand(\"netsh\", \"trace stop\", 15000); } catch { }\n            try { RunShellCommand(\"logman\", $\"stop {EtwSessionName} -ets\", 10000); } catch { }\n        }\n\n        public static string Start()\n        {\n            Directory.CreateDirectory(EtwTempDir);\n\n            // Try netsh trace (captures most HTTP traffic including WinHTTP)\n            var (code, stdout, stderr) = RunShellCommand(\"netsh\",\n                $\"trace start scenario=InternetClient_dbg capture=no tracefile=\\\"{EtwEtlPath}\\\" maxsize=256 overwrite=yes report=disabled\",\n                15000);\n            HomeViewModel.EventsLog($\"netsh trace start: exit={code}, stdout={stdout.Trim()}, stderr={stderr.Trim()}\");\n            if (code == 0)\n                return \"netsh\";\n\n            // Try logman with WinHttp provider\n            (code, stdout, stderr) = RunShellCommand(\"logman\",\n                $\"start {EtwSessionName} -p Microsoft-Windows-WinHttp 0xFFFFFFFF 0xFF -o \\\"{EtwEtlPath}\\\" -ets\",\n                10000);\n            HomeViewModel.EventsLog($\"logman WinHttp: exit={code}, stdout={stdout.Trim()}, stderr={stderr.Trim()}\");\n            if (code == 0)\n                return \"logman-winhttp\";\n\n            // Try logman with WinINet provider\n            (code, stdout, stderr) = RunShellCommand(\"logman\",\n                $\"start {EtwSessionName} -p Microsoft-Windows-WinINet 0xFFFFFFFF 0xFF -o \\\"{EtwEtlPath}\\\" -ets\",\n                10000);\n            HomeViewModel.EventsLog($\"logman WinINet: exit={code}, stdout={stdout.Trim()}, stderr={stderr.Trim()}\");\n            if (code == 0)\n                return \"logman-wininet\";\n\n            HomeViewModel.EventsLog(\"All ETW start methods failed\");\n            return null;\n        }\n\n        public static void Stop(string method)\n        {\n            if (method == \"netsh\")\n            {\n                var (code, _, _) = RunShellCommand(\"netsh\", \"trace stop\", 30000);\n                HomeViewModel.EventsLog($\"netsh trace stop: exit={code}\");\n            }\n            else if (method != null)\n            {\n                var (code, _, _) = RunShellCommand(\"logman\", $\"stop {EtwSessionName} -ets\", 15000);\n                HomeViewModel.EventsLog($\"logman stop: exit={code}\");\n            }\n        }\n\n        public static string ExtractTokens()\n        {\n            if (!File.Exists(EtwEtlPath))\n            {\n                HomeViewModel.EventsLog(\"ETL file not found\");\n                return null;\n            }\n\n            var fileSize = new FileInfo(EtwEtlPath).Length;\n            HomeViewModel.EventsLog($\"ETL file size: {fileSize / 1024}KB\");\n\n            const int chunkSize = 64 * 1024 * 1024; // 64MB\n            const int overlap = 8 * 1024; // 8KB overlap\n            var candidates = new List<(string token, int score)>();\n\n            int totalXblHits = 0;\n\n            using (var fs = new FileStream(EtwEtlPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))\n            {\n                byte[] buffer = new byte[chunkSize + overlap];\n                long position = 0;\n                int chunkNum = 0;\n\n                while (position < fs.Length)\n                {\n                    fs.Position = position;\n                    int bytesRead = fs.Read(buffer, 0, buffer.Length);\n                    if (bytesRead == 0) break;\n\n                    // Work directly from buffer (avoid extra copy)\n                    string ascii = Encoding.ASCII.GetString(buffer, 0, bytesRead);\n\n                    // Diagnostic: count raw \"XBL3.0\" occurrences\n                    int xblCount = 0;\n                    int searchIdx = 0;\n                    while ((searchIdx = ascii.IndexOf(\"XBL3.0\", searchIdx, StringComparison.Ordinal)) >= 0)\n                    {\n                        xblCount++;\n                        searchIdx += 6;\n                    }\n                    totalXblHits += xblCount;\n                    HomeViewModel.EventsLog($\"Chunk {chunkNum}: {bytesRead / 1024}KB, XBL3.0 hits={xblCount}, regex searching...\");\n\n                    SearchForTokens(ascii, candidates);\n\n                    // UTF-16 → strip null bytes to get ASCII\n                    string stripped = StripNullBytes(buffer, bytesRead);\n                    SearchForTokens(stripped, candidates);\n\n                    HomeViewModel.EventsLog($\"Chunk {chunkNum}: candidates so far={candidates.Count}\");\n\n                    position += chunkSize;\n                    chunkNum++;\n                }\n            }\n\n            HomeViewModel.EventsLog($\"Total XBL3.0 hits across all chunks: {totalXblHits}\");\n\n            if (candidates.Count == 0)\n            {\n                HomeViewModel.EventsLog($\"No regex candidates found (XBL3.0 hits={totalXblHits}). Trying IndexOf fallback...\");\n\n                // Fallback: re-read and use IndexOf to extract tokens directly\n                if (totalXblHits > 0)\n                {\n                    using var fs2 = new FileStream(EtwEtlPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);\n                    byte[] allBytes = new byte[fs2.Length];\n                    fs2.Read(allBytes, 0, allBytes.Length);\n                    string stripped = StripNullBytes(allBytes, allBytes.Length);\n\n                    string marker = \"x:XBL3.0 x=\";\n                    int idx = 0;\n                    int found = 0;\n                    while ((idx = stripped.IndexOf(marker, idx, StringComparison.Ordinal)) >= 0)\n                    {\n                        // Extract up to 4000 chars from this position\n                        int maxLen = Math.Min(4000, stripped.Length - idx);\n                        string raw = stripped.Substring(idx, maxLen);\n                        string token = CleanToken(raw);\n                        if (token != null)\n                        {\n                            int score = ScoreCandidate(stripped, idx, token, null);\n                            candidates.Add((token, score));\n                            found++;\n                            HomeViewModel.EventsLog($\"IndexOf fallback found token: len={token.Length}, score={score}\");\n                        }\n                        else\n                        {\n                            // Log why CleanToken rejected it\n                            int endSnip = Math.Min(80, raw.Length);\n                            HomeViewModel.EventsLog($\"IndexOf hit rejected by CleanToken at pos={idx}, start: {raw.Substring(0, endSnip)}\");\n                        }\n                        idx += marker.Length;\n                    }\n                    HomeViewModel.EventsLog($\"IndexOf fallback: {found} tokens from {totalXblHits} XBL3.0 hits\");\n                }\n\n                if (candidates.Count == 0)\n                {\n                    HomeViewModel.EventsLog(\"No token candidates found in ETL\");\n                    return null;\n                }\n            }\n\n            // Sort by score descending\n            candidates.Sort((a, b) => b.score.CompareTo(a.score));\n\n            HomeViewModel.EventsLog($\"Found {candidates.Count} candidate(s):\");\n            foreach (var (token, score) in candidates.Take(5))\n            {\n                int semi = token.IndexOf(';');\n                string hash = semi > 0 ? token.Substring(token.IndexOf(\"x=\") + 2, semi - token.IndexOf(\"x=\") - 2) : \"?\";\n                HomeViewModel.EventsLog($\"  score={score}, len={token.Length}, hash={hash}\");\n            }\n\n            // Validate the best candidates\n            foreach (var (token, score) in candidates)\n            {\n                if (IsEventsRpToken(token))\n                {\n                    HomeViewModel.EventsLog($\"Candidate validated (x5t check passed), score={score}, len={token.Length}\");\n                    return token;\n                }\n            }\n\n            HomeViewModel.EventsLog(\"No candidate passed x5t validation\");\n            return null;\n        }\n\n        public static void CleanupFiles()\n        {\n            try\n            {\n                if (Directory.Exists(EtwTempDir))\n                {\n                    foreach (var file in Directory.GetFiles(EtwTempDir))\n                    {\n                        try { File.Delete(file); } catch { }\n                    }\n                    try { Directory.Delete(EtwTempDir, true); } catch { }\n                }\n            }\n            catch (Exception ex)\n            {\n                HomeViewModel.EventsLog($\"Cleanup error: {ex.Message}\");\n            }\n        }\n\n        private static (int exitCode, string stdout, string stderr) RunShellCommand(string fileName, string args, int timeoutMs = 30000)\n        {\n            try\n            {\n                var psi = new ProcessStartInfo\n                {\n                    FileName = fileName,\n                    Arguments = args,\n                    UseShellExecute = false,\n                    CreateNoWindow = true,\n                    RedirectStandardOutput = true,\n                    RedirectStandardError = true,\n                };\n                using var proc = Process.Start(psi);\n                string stdout = proc.StandardOutput.ReadToEnd();\n                string stderr = proc.StandardError.ReadToEnd();\n                proc.WaitForExit(timeoutMs);\n                return (proc.ExitCode, stdout, stderr);\n            }\n            catch (Exception ex)\n            {\n                return (-1, \"\", ex.Message);\n            }\n        }\n\n        private static void SearchForTokens(string text, List<(string token, int score)> candidates)\n        {\n            // Search with ticket header pattern (has title ID context)\n            foreach (Match match in TicketHeaderRegex.Matches(text))\n            {\n                string titleId = match.Groups[1].Value;\n                string token = CleanToken(match.Groups[2].Value);\n                if (token == null) continue;\n\n                int score = ScoreCandidate(text, match.Index, token, titleId);\n                candidates.Add((token, score));\n            }\n\n            // Search with bare token pattern\n            foreach (Match match in BareTokenRegex.Matches(text))\n            {\n                string token = CleanToken(match.Value);\n                if (token == null) continue;\n\n                // Skip if already found via ticket header\n                if (candidates.Any(c => c.token == token)) continue;\n\n                int score = ScoreCandidate(text, match.Index, token, null);\n                candidates.Add((token, score));\n            }\n        }\n\n        private static string CleanToken(string raw)\n        {\n            if (string.IsNullOrEmpty(raw)) return null;\n\n            // Trim at first non-token character\n            int end = raw.Length;\n            for (int i = 0; i < raw.Length; i++)\n            {\n                char c = raw[i];\n                if (c < 0x20 || c > 0x7E || c == '\"' || c == '\\'' || c == '<' || c == '>' || c == '{' || c == '}')\n                {\n                    end = i;\n                    break;\n                }\n            }\n\n            string token = raw.Substring(0, end).TrimEnd();\n            if (!token.StartsWith(\"x:XBL3.0 x=\")) return null;\n            if (token.Length < 100) return null;\n            if (!token.Contains(';')) return null;\n\n            return token;\n        }\n\n        private static int ScoreCandidate(string text, int matchIndex, string token, string titleId)\n        {\n            int score = 0;\n\n            // OneCollector URL proximity (+5000)\n            int searchStart = Math.Max(0, matchIndex - 2000);\n            int searchLen = Math.Min(4000, text.Length - searchStart);\n            string vicinity = text.Substring(searchStart, searchLen);\n            if (OneCollectorUrlRegex.IsMatch(vicinity))\n                score += 5000;\n\n            // Has ticket ID context (+2000)\n            if (!string.IsNullOrEmpty(titleId))\n                score += 2000;\n\n            // Proper x:XBL3.0 prefix (+1000)\n            if (token.StartsWith(\"x:XBL3.0 x=\"))\n                score += 1000;\n\n            // Length bonus (longer tokens are more likely complete)\n            score += token.Length / 10;\n\n            return score;\n        }\n\n        private static string StripNullBytes(byte[] data, int length)\n        {\n            var sb = new StringBuilder(length / 2);\n            for (int i = 0; i < length; i++)\n            {\n                if (data[i] != 0 && data[i] >= 0x20 && data[i] <= 0x7E)\n                    sb.Append((char)data[i]);\n            }\n            return sb.ToString();\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/Files/FileDownloader.cs",
    "content": "using System.ComponentModel;\nusing System.IO;\nusing System.Net.Http;\n\npublic class FileDownloader : IDisposable\n{\n    private readonly HttpClient httpClient;\n\n    public FileDownloader()\n    {\n        httpClient = new HttpClient();\n    }\n\n    public void Dispose()\n    {\n        httpClient.Dispose();\n    }\n\n    public async Task DownloadFileAsync(string url, string destinationFilePath, Action<object, AsyncCompletedEventArgs>? updateToolCallback = null)\n    {\n        try\n        {\n            HttpResponseMessage response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);\n\n            using (Stream contentStream = await response.Content.ReadAsStreamAsync())\n            {\n                using (FileStream fileStream = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))\n                {\n                    await contentStream.CopyToAsync(fileStream);\n                }\n            }\n\n            // Invoke the provided callback method upon successful download\n            updateToolCallback?.Invoke(this, new AsyncCompletedEventArgs(null, false, null));\n        }\n        catch (Exception ex)\n        {\n            // Handle exceptions\n            Console.WriteLine($\"Error downloading file: {ex.Message}\");\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/Memory/Methods/AoB.cs",
    "content": "﻿using System;\nusing System.Collections.Concurrent;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Threading.Tasks;\nusing static Memory.Imps;\n\nnamespace Memory\n{\n    public partial class Mem\n    {\n        /// <summary>\n        /// Array of byte scan.\n        /// </summary>\n        /// <param name=\"search\">array of bytes to search for, OR your ini code label.</param>\n        /// <param name=\"writable\">Include writable addresses in scan</param>\n        /// <param name=\"executable\">Include executable addresses in scan</param>\n        /// <param name=\"file\">ini file (OPTIONAL)</param>\n        /// <returns>IEnumerable of all addresses found.</returns>\n        public Task<IEnumerable<long>> AoBScan(string search, bool writable = false, bool executable = true, string file = \"\")\n        {\n            return AoBScan(0, long.MaxValue, search, writable, executable, false, file);\n        }\n\n\n        /// <summary>\n        /// Array of Byte scan.\n        /// </summary>\n        /// <param name=\"start\">Your starting address.</param>\n        /// <param name=\"end\">ending address</param>\n        /// <param name=\"search\">array of bytes to search for, OR your ini code label.</param>\n        /// <param name=\"file\">ini file (OPTIONAL)</param>\n        /// <param name=\"writable\">Include writable addresses in scan</param>\n        /// <param name=\"executable\">Include executable addresses in scan</param>\n        /// <param name=\"mapped\">Include mapped addresses in scan</param>\n        /// <returns>IEnumerable of all addresses found.</returns>\n        public Task<IEnumerable<long>> AoBScan(long start, long end, string search, bool writable = false, bool executable = true, bool mapped = false, string file = \"\")\n        {\n            // Not including read only memory was scan behavior prior.\n            return AoBScan(start, end, search, false, writable, executable, mapped, file);\n        }\n\n        /// <summary>\n        /// Array of Byte scan.\n        /// </summary>\n        /// <param name=\"start\">Your starting address.</param>\n        /// <param name=\"end\">ending address</param>\n        /// <param name=\"search\">array of bytes to search for, OR your ini code label.</param>\n        /// <param name=\"file\">ini file (OPTIONAL)</param>\n        /// <param name=\"readable\">Include readable addresses in scan</param>\n        /// <param name=\"writable\">Include writable addresses in scan</param>\n        /// <param name=\"executable\">Include executable addresses in scan</param>\n        /// <param name=\"mapped\">Include mapped addresses in scan</param>\n        /// <returns>IEnumerable of all addresses found.</returns>\n        public Task<IEnumerable<long>> AoBScan(long start, long end, string search, bool readable, bool writable, bool executable, bool mapped, string file = \"\")\n        {\n            return Task.Run(() =>\n            {\n                var memRegionList = new List<MemoryRegionResult>();\n\n                string memCode = LoadCode(search, file);\n\n                string[] stringByteArray = memCode.Split(' ');\n\n                byte[] aobPattern = new byte[stringByteArray.Length];\n                byte[] mask = new byte[stringByteArray.Length];\n\n                for (var i = 0; i < stringByteArray.Length; i++)\n                {\n                    string ba = stringByteArray[i];\n\n                    if (ba == \"??\" || (ba.Length == 1 && ba == \"?\"))\n                    {\n                        mask[i] = 0x00;\n                        stringByteArray[i] = \"0x00\";\n                    }\n                    else if (Char.IsLetterOrDigit(ba[0]) && ba[1] == '?')\n                    {\n                        mask[i] = 0xF0;\n                        stringByteArray[i] = ba[0] + \"0\";\n                    }\n                    else if (Char.IsLetterOrDigit(ba[1]) && ba[0] == '?')\n                    {\n                        mask[i] = 0x0F;\n                        stringByteArray[i] = \"0\" + ba[1];\n                    }\n                    else\n                        mask[i] = 0xFF;\n                }\n\n\n                for (int i = 0; i < stringByteArray.Length; i++)\n                    aobPattern[i] = (byte)(Convert.ToByte(stringByteArray[i], 16) & mask[i]);\n\n                SYSTEM_INFO sys_info = new SYSTEM_INFO();\n                GetSystemInfo(out sys_info);\n\n                UIntPtr proc_min_address = sys_info.minimumApplicationAddress;\n                UIntPtr proc_max_address = sys_info.maximumApplicationAddress;\n\n                if (start < (long)proc_min_address.ToUInt64())\n                    start = (long)proc_min_address.ToUInt64();\n\n                if (end > (long)proc_max_address.ToUInt64())\n                    end = (long)proc_max_address.ToUInt64();\n\n                Debug.WriteLine(\"[DEBUG] memory scan starting... (start:0x\" + start.ToString(MSize()) + \" end:0x\" + end.ToString(MSize()) + \" time:\" + DateTime.Now.ToString(\"h:mm:ss tt\") + \")\");\n                UIntPtr currentBaseAddress = new UIntPtr((ulong)start);\n\n                MEMORY_BASIC_INFORMATION memInfo = new MEMORY_BASIC_INFORMATION();\n\n            //Debug.WriteLine(\"[DEBUG] start:0x\" + start.ToString(\"X8\") + \" curBase:0x\" + currentBaseAddress.ToUInt64().ToString(\"X8\") + \" end:0x\" + end.ToString(\"X8\") + \" size:0x\" + memInfo.RegionSize.ToString(\"X8\") + \" vAloc:\" + VirtualQueryEx(mProc.Handle, currentBaseAddress, out memInfo).ToUInt64().ToString());\n\n            while (VirtualQueryEx(mProc.Handle, currentBaseAddress, out memInfo).ToUInt64() != 0 &&\n                       currentBaseAddress.ToUInt64() < (ulong)end &&\n                       currentBaseAddress.ToUInt64() + (ulong)memInfo.RegionSize >\n                       currentBaseAddress.ToUInt64())\n                {\n                    bool isValid = memInfo.State == MEM_COMMIT;\n                    isValid &= memInfo.BaseAddress.ToUInt64() < (ulong)proc_max_address.ToUInt64();\n                    isValid &= ((memInfo.Protect & PAGE_GUARD) == 0);\n                    isValid &= ((memInfo.Protect & PAGE_NOACCESS) == 0);\n                    isValid &= (memInfo.Type == MEM_PRIVATE) || (memInfo.Type == MEM_IMAGE);\n                    if (mapped)\n                        isValid &= (memInfo.Type == MEM_MAPPED);\n\n                    if (isValid)\n                    {\n                        bool isReadable = (memInfo.Protect & PAGE_READONLY) > 0;\n\n                        bool isWritable = ((memInfo.Protect & PAGE_READWRITE) > 0) ||\n                                          ((memInfo.Protect & PAGE_WRITECOPY) > 0) ||\n                                          ((memInfo.Protect & PAGE_EXECUTE_READWRITE) > 0) ||\n                                          ((memInfo.Protect & PAGE_EXECUTE_WRITECOPY) > 0);\n\n                        bool isExecutable = ((memInfo.Protect & PAGE_EXECUTE) > 0) ||\n                                            ((memInfo.Protect & PAGE_EXECUTE_READ) > 0) ||\n                                            ((memInfo.Protect & PAGE_EXECUTE_READWRITE) > 0) ||\n                                            ((memInfo.Protect & PAGE_EXECUTE_WRITECOPY) > 0);\n\n                        isReadable &= readable;\n                        isWritable &= writable;\n                        isExecutable &= executable;\n\n                        isValid &= isReadable || isWritable || isExecutable;\n                    }\n\n                    if (!isValid)\n                    {\n                        currentBaseAddress = new UIntPtr(memInfo.BaseAddress.ToUInt64() + (ulong)memInfo.RegionSize);\n                        continue;\n                    }\n\n                    MemoryRegionResult memRegion = new MemoryRegionResult\n                    {\n                        CurrentBaseAddress = currentBaseAddress,\n                        RegionSize = memInfo.RegionSize,\n                        RegionBase = memInfo.BaseAddress\n                    };\n\n                    currentBaseAddress = new UIntPtr(memInfo.BaseAddress.ToUInt64() + (ulong)memInfo.RegionSize);\n\n                //Console.WriteLine(\"SCAN start:\" + memRegion.RegionBase.ToString() + \" end:\" + currentBaseAddress.ToString());\n\n                if (memRegionList.Count > 0)\n                    {\n                        var previousRegion = memRegionList[memRegionList.Count - 1];\n\n                        if ((long)previousRegion.RegionBase + previousRegion.RegionSize == (long)memInfo.BaseAddress)\n                        {\n                            memRegionList[memRegionList.Count - 1] = new MemoryRegionResult\n                            {\n                                CurrentBaseAddress = previousRegion.CurrentBaseAddress,\n                                RegionBase = previousRegion.RegionBase,\n                                RegionSize = previousRegion.RegionSize + memInfo.RegionSize\n                            };\n\n                            continue;\n                        }\n                    }\n\n                    memRegionList.Add(memRegion);\n                }\n\n                ConcurrentBag<long> bagResult = new ConcurrentBag<long>();\n\n                Parallel.ForEach(memRegionList,\n                                 (item, parallelLoopState, index) =>\n                                 {\n                                     long[] compareResults = CompareScan(item, aobPattern, mask);\n\n                                     foreach (long result in compareResults)\n                                         bagResult.Add(result);\n                                 });\n\n                Debug.WriteLine(\"[DEBUG] memory scan completed. (time:\" + DateTime.Now.ToString(\"h:mm:ss tt\") + \")\");\n\n                return bagResult.ToList().OrderBy(c => c).AsEnumerable();\n            });\n        }\n\n        private long[] CompareScan(MemoryRegionResult item, byte[] aobPattern, byte[] mask)\n        {\n            if (mask.Length != aobPattern.Length)\n                throw new ArgumentException($\"{nameof(aobPattern)}.Length != {nameof(mask)}.Length\");\n\n            IntPtr buffer = Marshal.AllocHGlobal((int)item.RegionSize);\n\n            ReadProcessMemory(mProc.Handle, item.CurrentBaseAddress, buffer, (UIntPtr)item.RegionSize, out ulong bytesRead);\n\n            int result = 0 - aobPattern.Length;\n            List<long> ret = new List<long>();\n            unsafe\n            {\n                do\n                {\n\n                    result = FindPattern((byte*)buffer.ToPointer(), (int)bytesRead, aobPattern, mask, result + aobPattern.Length);\n\n                    if (result >= 0)\n                        ret.Add((long)item.CurrentBaseAddress + result);\n\n                } while (result != -1);\n            }\n\n            Marshal.FreeHGlobal(buffer);\n\n            return ret.ToArray();\n        }\n\n        private unsafe int FindPattern(byte* body, int bodyLength, byte[] pattern, byte[] masks, int start = 0)\n        {\n            int foundIndex = -1;\n\n            if (bodyLength <= 0 || pattern.Length <= 0 || start > bodyLength - pattern.Length ||\n                pattern.Length > bodyLength) return foundIndex;\n\n            for (int index = start; index <= bodyLength - pattern.Length; index++)\n            {\n                if (((body[index] & masks[0]) == (pattern[0] & masks[0])))\n                {\n                    var match = true;\n                    for (int index2 = pattern.Length - 1; index2 >= 1; index2--)\n                    {\n                        if ((body[index + index2] & masks[index2]) == (pattern[index2] & masks[index2])) continue;\n                        match = false;\n                        break;\n\n                    }\n\n                    if (!match) continue;\n\n                    foundIndex = index;\n                    break;\n                }\n            }\n\n            return foundIndex;\n        }\n    }\n}"
  },
  {
    "path": "XAU/Util/Memory/Methods/Read.cs",
    "content": "﻿using System;\nusing System.Collections.Concurrent;\nusing System.Diagnostics;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing static Memory.Imps;\n\nnamespace Memory\n{\n    public partial class Mem\n    {\n        /// <summary>\n        /// Read a string value from an address.\n        /// </summary>\n        /// <param name=\"code\">address, module + pointer + offset, module + offset OR label in .ini file.</param>\n        /// <param name=\"file\">path and name of ini file. (OPTIONAL)</param>\n        /// <param name=\"length\">length of bytes to read (OPTIONAL)</param>\n        /// <param name=\"zeroTerminated\">terminate string at null char</param>\n        /// <param name=\"stringEncoding\">System.Text.Encoding.UTF8 (DEFAULT). Other options: ascii, unicode, utf32, utf7</param>\n        /// <returns></returns>\n        public string ReadString(string code, string file = \"\", int length = 32, bool zeroTerminated = true, System.Text.Encoding stringEncoding = null)\n        {\n            if (stringEncoding == null)\n                stringEncoding = System.Text.Encoding.UTF8;\n\n            byte[] memoryNormal = new byte[length];\n            UIntPtr theCode = GetCode(code, file);\n            if (theCode == null || theCode == UIntPtr.Zero || theCode.ToUInt64() < 0x10000)\n                return \"\";\n\n            if (ReadProcessMemory(mProc.Handle, theCode, memoryNormal, (UIntPtr)length, IntPtr.Zero))\n                return (zeroTerminated) ? stringEncoding.GetString(memoryNormal).Split('\\0')[0] : stringEncoding.GetString(memoryNormal);\n            else\n                return \"\";\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/Memory/Structures/Imports.cs",
    "content": "﻿using System;\nusing System.Runtime.InteropServices;\nusing System.Text;\n\nnamespace Memory\n{\n    public class Imps\n    {\n        [DllImport(\"kernel32.dll\")]\n        public static extern IntPtr OpenProcess(\n            UInt32 dwDesiredAccess,\n            bool bInheritHandle,\n            Int32 dwProcessId\n            );\n\n#if WINXP\n#else\n        [DllImport(\"kernel32.dll\", EntryPoint = \"VirtualQueryEx\")]\n        public static extern UIntPtr Native_VirtualQueryEx(IntPtr hProcess, UIntPtr lpAddress,\n            out MEMORY_BASIC_INFORMATION32 lpBuffer, UIntPtr dwLength);\n\n        [DllImport(\"kernel32.dll\", EntryPoint = \"VirtualQueryEx\")]\n        public static extern UIntPtr Native_VirtualQueryEx(IntPtr hProcess, UIntPtr lpAddress,\n            out MEMORY_BASIC_INFORMATION64 lpBuffer, UIntPtr dwLength);\n\n        [DllImport(\"kernel32.dll\")]\n        public static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);\n#endif\n\n\n        [DllImport(\"kernel32.dll\", CharSet = CharSet.Unicode)]\n        public static extern uint GetPrivateProfileString(\n           string lpAppName,\n           string lpKeyName,\n           string lpDefault,\n           StringBuilder lpReturnedString,\n           uint nSize,\n           string lpFileName);\n\n        [DllImport(\"kernel32.dll\")]\n        public static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr lpBaseAddress, [Out] byte[] lpBuffer, UIntPtr nSize, IntPtr lpNumberOfBytesRead);\n\n        [DllImport(\"kernel32.dll\")]\n        public static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr lpBaseAddress, [Out] IntPtr lpBuffer, UIntPtr nSize, out ulong lpNumberOfBytesRead);\n\n        [DllImport(\"kernel32\")]\n        public static extern bool IsWow64Process(IntPtr hProcess, out bool lpSystemInfo);\n\n        // used for memory allocation\n        public const uint MEM_FREE = 0x10000;\n        public const uint MEM_COMMIT = 0x00001000;\n        public const uint MEM_RESERVE = 0x00002000;\n\n        public const uint PAGE_READONLY = 0x02;\n        public const uint PAGE_READWRITE = 0x04;\n        public const uint PAGE_WRITECOPY = 0x08;\n        public const uint PAGE_EXECUTE_READWRITE = 0x40;\n        public const uint PAGE_EXECUTE_WRITECOPY = 0x80;\n        public const uint PAGE_EXECUTE = 0x10;\n        public const uint PAGE_EXECUTE_READ = 0x20;\n\n        public const uint PAGE_GUARD = 0x100;\n        public const uint PAGE_NOACCESS = 0x01;\n\n        public const uint MEM_PRIVATE = 0x20000;\n        public const uint MEM_IMAGE = 0x1000000;\n        public const uint MEM_MAPPED = 0x40000;\n\n\n        public struct SYSTEM_INFO\n        {\n            public ushort processorArchitecture;\n            ushort reserved;\n            public uint pageSize;\n            public UIntPtr minimumApplicationAddress;\n            public UIntPtr maximumApplicationAddress;\n            public IntPtr activeProcessorMask;\n            public uint numberOfProcessors;\n            public uint processorType;\n            public uint allocationGranularity;\n            public ushort processorLevel;\n            public ushort processorRevision;\n        }\n\n        public struct MEMORY_BASIC_INFORMATION32\n        {\n            public UIntPtr BaseAddress;\n            public UIntPtr AllocationBase;\n            public uint AllocationProtect;\n            public uint RegionSize;\n            public uint State;\n            public uint Protect;\n            public uint Type;\n        }\n\n        public struct MEMORY_BASIC_INFORMATION64\n        {\n            public UIntPtr BaseAddress;\n            public UIntPtr AllocationBase;\n            public uint AllocationProtect;\n            public uint __alignment1;\n            public ulong RegionSize;\n            public uint State;\n            public uint Protect;\n            public uint Type;\n            public uint __alignment2;\n        }\n\n        public struct MEMORY_BASIC_INFORMATION\n        {\n            public UIntPtr BaseAddress;\n            public UIntPtr AllocationBase;\n            public uint AllocationProtect;\n            public long RegionSize;\n            public uint State;\n            public uint Protect;\n            public uint Type;\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/Memory/Structures/MemoryRegionResult.cs",
    "content": "﻿using System;\n\nnamespace Memory\n{\n    /// <summary>\n    /// AoB scan information.\n    /// </summary>\n    struct MemoryRegionResult\n    {\n        public UIntPtr CurrentBaseAddress { get; set; }\n        public long RegionSize { get; set; }\n        public UIntPtr RegionBase { get; set; }\n\n    }\n}\n"
  },
  {
    "path": "XAU/Util/Memory/Structures/Process.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\n\nnamespace Memory\n{\n    /// <summary>\n    /// Information about the opened process.\n    /// </summary>\n    public class Proc\n    {\n        public Process Process { get; set; }\n        public IntPtr Handle { get; set; }\n        public bool Is64Bit { get; set; }\n        public ProcessModule MainModule { get; set; }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/Memory/memory.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.IO.Pipes;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Runtime.InteropServices;\nusing System.Diagnostics;\nusing System.Globalization;\nusing System.Threading.Tasks;\nusing System.ComponentModel;\nusing static Memory.Imps;\n\nnamespace Memory\n{\n    /// <summary>\n    /// Memory.dll class. Full documentation at https://github.com/erfg12/memory.dll/wiki\n    /// </summary>\n    public partial class Mem\n    {\n        public Proc mProc = new Proc();\n\n        public UIntPtr VirtualQueryEx(IntPtr hProcess, UIntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer)\n        {\n            UIntPtr retVal;\n\n            // TODO: Need to change this to only check once.\n            if (mProc.Is64Bit || IntPtr.Size == 8)\n            {\n                // 64 bit\n                MEMORY_BASIC_INFORMATION64 tmp64 = new MEMORY_BASIC_INFORMATION64();\n                retVal = Native_VirtualQueryEx(hProcess, lpAddress, out tmp64, new UIntPtr((uint)Marshal.SizeOf(tmp64)));\n\n                lpBuffer.BaseAddress = tmp64.BaseAddress;\n                lpBuffer.AllocationBase = tmp64.AllocationBase;\n                lpBuffer.AllocationProtect = tmp64.AllocationProtect;\n                lpBuffer.RegionSize = (long)tmp64.RegionSize;\n                lpBuffer.State = tmp64.State;\n                lpBuffer.Protect = tmp64.Protect;\n                lpBuffer.Type = tmp64.Type;\n\n                return retVal;\n            }\n            MEMORY_BASIC_INFORMATION32 tmp32 = new MEMORY_BASIC_INFORMATION32();\n\n            retVal = Native_VirtualQueryEx(hProcess, lpAddress, out tmp32, new UIntPtr((uint)Marshal.SizeOf(tmp32)));\n\n            lpBuffer.BaseAddress = tmp32.BaseAddress;\n            lpBuffer.AllocationBase = tmp32.AllocationBase;\n            lpBuffer.AllocationProtect = tmp32.AllocationProtect;\n            lpBuffer.RegionSize = tmp32.RegionSize;\n            lpBuffer.State = tmp32.State;\n            lpBuffer.Protect = tmp32.Protect;\n            lpBuffer.Type = tmp32.Type;\n\n            return retVal;\n        }\n\n        /// <summary>\n        /// Open the PC game process with all security and access rights.\n        /// </summary>\n        /// <param name=\"pid\">Use process name or process ID here.</param>\n        /// <returns>Process opened successfully or failed.</returns>\n        /// <param name=\"FailReason\">Show reason open process fails</param>\n        public bool OpenProcess(int pid, out string FailReason)\n        {\n            /*if (!IsAdmin())\n            {\n                Debug.WriteLine(\"WARNING: This program may not be running with raised privileges! Visit https://github.com/erfg12/memory.dll/wiki/Administrative-Privileges\");\n            }*/\n\n            if (pid <= 0)\n            {\n                FailReason = \"OpenProcess given proc ID 0.\";\n                Debug.WriteLine(\"ERROR: OpenProcess given proc ID 0.\");\n                return false;\n            }\n\n\n            if (mProc.Process != null && mProc.Process.Id == pid)\n            {\n                FailReason = \"mProc.Process is null\";\n                return true;\n            }\n\n            try\n            {\n                mProc.Process = Process.GetProcessById(pid);\n\n                if (mProc.Process != null && !mProc.Process.Responding)\n                {\n                    Debug.WriteLine(\"ERROR: OpenProcess: Process is not responding or null.\");\n                    FailReason = \"Process is not responding or null.\";\n                    return false;\n                }\n\n                mProc.Handle = Imps.OpenProcess(0x1F0FFF, true, pid);\n\n                if (mProc.Handle == IntPtr.Zero)\n                {\n                    var eCode = Marshal.GetLastWin32Error();\n                    Debug.WriteLine(\"ERROR: OpenProcess has failed opening a handle to the target process (GetLastWin32ErrorCode: \" + eCode + \")\");\n                    Process.LeaveDebugMode();\n                    mProc = null;\n                    FailReason = \"failed opening a handle to the target process(GetLastWin32ErrorCode: \" + eCode + \")\";\n                    return false;\n                }\n\n                // Lets set the process to 64bit or not here (cuts down on api calls)\n                mProc.Is64Bit = Environment.Is64BitOperatingSystem && (IsWow64Process(mProc.Handle, out bool retVal) && !retVal);\n\n                mProc.MainModule = mProc.Process.MainModule;\n\n                //GetModules();\n\n                Debug.WriteLine(\"Process #\" + mProc.Process + \" is now open.\");\n                FailReason = \"\";\n                return true;\n            }\n            catch (Exception ex)\n            {\n                Debug.WriteLine(\"ERROR: OpenProcess has crashed. \" + ex);\n                FailReason = \"OpenProcess has crashed. \" + ex;\n                return false;\n            }\n        }\n\n\n        /// <summary>\n        /// Open the PC game process with all security and access rights.\n        /// </summary>\n        /// <param name=\"proc\">Use process name or process ID here.</param>\n        /// <returns></returns>\n        public bool OpenProcess(string proc)\n        {\n            return OpenProcess(GetProcIdFromName(proc), out string FailReason);\n        }\n\n        /// <summary>\n        /// Get the process ID number by process name.\n        /// </summary>\n        /// <param name=\"name\">Example: \"eqgame\". Use task manager to find the name. Do not include .exe</param>\n        /// <returns></returns>\n        public int GetProcIdFromName(string name) //new 1.0.2 function\n        {\n            Process[] processlist = Process.GetProcesses();\n\n            if (name.ToLower().Contains(\".exe\"))\n                name = name.Replace(\".exe\", \"\");\n            if (name.ToLower().Contains(\".bin\")) // test\n                name = name.Replace(\".bin\", \"\");\n\n            foreach (System.Diagnostics.Process theprocess in processlist)\n            {\n                if (theprocess.ProcessName.Equals(name, StringComparison.CurrentCultureIgnoreCase)) //find (name).exe in the process list (use task manager to find the name)\n                    return theprocess.Id;\n            }\n\n            return 0; //if we fail to find it\n        }\n\n\n\n        /// <summary>\n        /// Get code. If just the ini file name is given with no path, it will assume the file is next to the executable.\n        /// </summary>\n        /// <param name=\"name\">label for address or code</param>\n        /// <param name=\"iniFile\">path and name of ini file</param>\n        /// <returns></returns>\n        public string LoadCode(string name, string iniFile)\n        {\n            StringBuilder returnCode = new StringBuilder(1024);\n            uint read_ini_result;\n\n            if (!String.IsNullOrEmpty(iniFile))\n            {\n                if (File.Exists(iniFile))\n                {\n                    read_ini_result = GetPrivateProfileString(\"codes\", name, \"\", returnCode, (uint)returnCode.Capacity, iniFile);\n                    //Debug.WriteLine(\"read_ini_result=\" + read_ini_result); number of characters returned\n                }\n                else\n                    Debug.WriteLine(\"ERROR: ini file \\\"\" + iniFile + \"\\\" not found!\");\n            }\n            else\n                returnCode.Append(name);\n\n            return returnCode.ToString();\n        }\n\n        /// <summary>\n        /// Convert code from string to real address. If path is not blank, will pull from ini file.\n        /// </summary>\n        /// <param name=\"name\">label in ini file or code</param>\n        /// <param name=\"path\">path to ini file (OPTIONAL)</param>\n        /// <param name=\"size\">size of address (default is 8)</param>\n        /// <returns></returns>\n        public UIntPtr GetCode(string name, string path = \"\", int size = 8)\n        {\n            string theCode = \"\";\n            if (mProc == null)\n                return UIntPtr.Zero;\n\n            if (mProc.Is64Bit)\n            {\n                //Debug.WriteLine(\"Changing to 64bit code...\");\n                if (size == 8) size = 16; //change to 64bit\n                return Get64BitCode(name, path, size); //jump over to 64bit code grab\n            }\n\n            if (!String.IsNullOrEmpty(path))\n                theCode = LoadCode(name, path);\n            else\n                theCode = name;\n\n            if (String.IsNullOrEmpty(theCode))\n            {\n                //Debug.WriteLine(\"ERROR: LoadCode returned blank. NAME:\" + name + \" PATH:\" + path);\n                return UIntPtr.Zero;\n            }\n            else\n            {\n                //Debug.WriteLine(\"Found code=\" + theCode + \" NAME:\" + name + \" PATH:\" + path);\n            }\n\n            // remove spaces\n            if (theCode.Contains(\" \"))\n                theCode = theCode.Replace(\" \", String.Empty);\n\n            if (!theCode.Contains(\"+\") && !theCode.Contains(\",\"))\n            {\n                try\n                {\n                    return new UIntPtr(Convert.ToUInt32(theCode, 16));\n                }\n                catch\n                {\n                    Console.WriteLine(\"Error in GetCode(). Failed to read address \" + theCode);\n                    return UIntPtr.Zero;\n                }\n            }\n\n            string newOffsets = theCode;\n\n            if (theCode.Contains(\"+\"))\n                newOffsets = theCode.Substring(theCode.IndexOf('+') + 1);\n\n            byte[] memoryAddress = new byte[size];\n\n            if (newOffsets.Contains(','))\n            {\n                List<int> offsetsList = new List<int>();\n\n                string[] newerOffsets = newOffsets.Split(',');\n                foreach (string oldOffsets in newerOffsets)\n                {\n                    string test = oldOffsets;\n                    if (oldOffsets.Contains(\"0x\")) test = oldOffsets.Replace(\"0x\", \"\");\n                    int preParse = 0;\n                    if (!oldOffsets.Contains(\"-\"))\n                        preParse = Int32.Parse(test, NumberStyles.AllowHexSpecifier);\n                    else\n                    {\n                        test = test.Replace(\"-\", \"\");\n                        preParse = Int32.Parse(test, NumberStyles.AllowHexSpecifier);\n                        preParse = preParse * -1;\n                    }\n                    offsetsList.Add(preParse);\n                }\n                int[] offsets = offsetsList.ToArray();\n\n                if (theCode.Contains(\"base\") || theCode.Contains(\"main\"))\n                    ReadProcessMemory(mProc.Handle, (UIntPtr)((int)mProc.MainModule.BaseAddress + offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero);\n                else if (!theCode.Contains(\"base\") && !theCode.Contains(\"main\") && theCode.Contains(\"+\"))\n                {\n                    string[] moduleName = theCode.Split('+');\n                    IntPtr altModule = IntPtr.Zero;\n                    if (!moduleName[0].ToLower().Contains(\".dll\") && !moduleName[0].ToLower().Contains(\".exe\") && !moduleName[0].ToLower().Contains(\".bin\"))\n                    {\n                        string theAddr = moduleName[0];\n                        if (theAddr.Contains(\"0x\")) theAddr = theAddr.Replace(\"0x\", \"\");\n                        altModule = (IntPtr)Int32.Parse(theAddr, NumberStyles.HexNumber);\n                    }\n                    else\n                    {\n                        try\n                        {\n                            altModule = GetModuleAddressByName(moduleName[0]);\n                        }\n                        catch\n                        {\n                            Debug.WriteLine(\"Module \" + moduleName[0] + \" was not found in module list!\");\n                            //Debug.WriteLine(\"Modules: \" + string.Join(\",\", mProc.Modules));\n                        }\n                    }\n                    ReadProcessMemory(mProc.Handle, (UIntPtr)((int)altModule + offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero);\n                }\n                else\n                    ReadProcessMemory(mProc.Handle, (UIntPtr)(offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero);\n\n                uint num1 = BitConverter.ToUInt32(memoryAddress, 0); //ToUInt64 causes arithmetic overflow.\n\n                UIntPtr base1 = (UIntPtr)0;\n\n                for (int i = 1; i < offsets.Length; i++)\n                {\n                    base1 = new UIntPtr(Convert.ToUInt32(num1 + offsets[i]));\n                    ReadProcessMemory(mProc.Handle, base1, memoryAddress, (UIntPtr)size, IntPtr.Zero);\n                    num1 = BitConverter.ToUInt32(memoryAddress, 0); //ToUInt64 causes arithmetic overflow.\n                }\n                return base1;\n            }\n            else // no offsets\n            {\n                int trueCode = Convert.ToInt32(newOffsets, 16);\n                IntPtr altModule = IntPtr.Zero;\n                //Debug.WriteLine(\"newOffsets=\" + newOffsets);\n                if (theCode.ToLower().Contains(\"base\") || theCode.ToLower().Contains(\"main\"))\n                    altModule = mProc.MainModule.BaseAddress;\n                else if (!theCode.ToLower().Contains(\"base\") && !theCode.ToLower().Contains(\"main\") && theCode.Contains(\"+\"))\n                {\n                    string[] moduleName = theCode.Split('+');\n                    if (!moduleName[0].ToLower().Contains(\".dll\") && !moduleName[0].ToLower().Contains(\".exe\") && !moduleName[0].ToLower().Contains(\".bin\"))\n                    {\n                        string theAddr = moduleName[0];\n                        if (theAddr.Contains(\"0x\")) theAddr = theAddr.Replace(\"0x\", \"\");\n                        altModule = (IntPtr)Int32.Parse(theAddr, NumberStyles.HexNumber);\n                    }\n                    else\n                    {\n                        try\n                        {\n                            altModule = GetModuleAddressByName(moduleName[0]);\n                        }\n                        catch\n                        {\n                            Debug.WriteLine(\"Module \" + moduleName[0] + \" was not found in module list!\");\n                            //Debug.WriteLine(\"Modules: \" + string.Join(\",\", mProc.Modules));\n                        }\n                    }\n                }\n                else\n                    altModule = GetModuleAddressByName(theCode.Split('+')[0]);\n                return (UIntPtr)((int)altModule + trueCode);\n            }\n        }\n\n        /// <summary>\n        /// Retrieve mProc.Process module baseaddress by name\n        /// </summary>\n        /// <param name=\"name\">name of module</param>\n        /// <returns></returns>\n        public IntPtr GetModuleAddressByName(string name)\n        {\n            return mProc.Process.Modules.Cast<ProcessModule>().SingleOrDefault(m => string.Equals(m.ModuleName, name, StringComparison.OrdinalIgnoreCase)).BaseAddress;\n        }\n\n        /// <summary>\n        /// Convert code from string to real address. If path is not blank, will pull from ini file.\n        /// </summary>\n        /// <param name=\"name\">label in ini file OR code</param>\n        /// <param name=\"path\">path to ini file (OPTIONAL)</param>\n        /// <param name=\"size\">size of address (default is 16)</param>\n        /// <returns></returns>\n        public UIntPtr Get64BitCode(string name, string path = \"\", int size = 16)\n        {\n            string theCode = \"\";\n            if (!String.IsNullOrEmpty(path))\n                theCode = LoadCode(name, path);\n            else\n                theCode = name;\n\n            if (String.IsNullOrEmpty(theCode))\n                return UIntPtr.Zero;\n\n            // remove spaces\n            if (theCode.Contains(\" \"))\n                theCode.Replace(\" \", String.Empty);\n\n            string newOffsets = theCode;\n            if (theCode.Contains(\"+\"))\n                newOffsets = theCode.Substring(theCode.IndexOf('+') + 1);\n\n            byte[] memoryAddress = new byte[size];\n\n            if (!theCode.Contains(\"+\") && !theCode.Contains(\",\"))\n            {\n                try\n                {\n                    return new UIntPtr(Convert.ToUInt64(theCode, 16));\n                }\n                catch\n                {\n                    Console.WriteLine(\"Error in GetCode(). Failed to read address \" + theCode);\n                    return UIntPtr.Zero;\n                }\n            }\n\n            if (newOffsets.Contains(','))\n            {\n                List<Int64> offsetsList = new List<Int64>();\n\n                string[] newerOffsets = newOffsets.Split(',');\n                foreach (string oldOffsets in newerOffsets)\n                {\n                    string test = oldOffsets;\n                    if (oldOffsets.Contains(\"0x\")) test = oldOffsets.Replace(\"0x\", \"\");\n                    Int64 preParse = 0;\n                    if (!oldOffsets.Contains(\"-\"))\n                        preParse = Int64.Parse(test, NumberStyles.AllowHexSpecifier);\n                    else\n                    {\n                        test = test.Replace(\"-\", \"\");\n                        preParse = Int64.Parse(test, NumberStyles.AllowHexSpecifier);\n                        preParse = preParse * -1;\n                    }\n                    offsetsList.Add(preParse);\n                }\n                Int64[] offsets = offsetsList.ToArray();\n\n                if (theCode.Contains(\"base\") || theCode.Contains(\"main\"))\n                    ReadProcessMemory(mProc.Handle, (UIntPtr)((Int64)mProc.MainModule.BaseAddress + offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero);\n                else if (!theCode.Contains(\"base\") && !theCode.Contains(\"main\") && theCode.Contains(\"+\"))\n                {\n                    string[] moduleName = theCode.Split('+');\n                    IntPtr altModule = IntPtr.Zero;\n                    if (!moduleName[0].ToLower().Contains(\".dll\") && !moduleName[0].ToLower().Contains(\".exe\") && !moduleName[0].ToLower().Contains(\".bin\"))\n                        altModule = (IntPtr)Int64.Parse(moduleName[0], System.Globalization.NumberStyles.HexNumber);\n                    else\n                    {\n                        try\n                        {\n                            altModule = GetModuleAddressByName(moduleName[0]);\n                        }\n                        catch\n                        {\n                            Debug.WriteLine(\"Module \" + moduleName[0] + \" was not found in module list!\");\n                            //Debug.WriteLine(\"Modules: \" + string.Join(\",\", mProc.Modules));\n                        }\n                    }\n                    ReadProcessMemory(mProc.Handle, (UIntPtr)((Int64)altModule + offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero);\n                }\n                else // no offsets\n                    ReadProcessMemory(mProc.Handle, (UIntPtr)(offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero);\n\n                Int64 num1 = BitConverter.ToInt64(memoryAddress, 0);\n\n                UIntPtr base1 = (UIntPtr)0;\n\n                for (int i = 1; i < offsets.Length; i++)\n                {\n                    base1 = new UIntPtr(Convert.ToUInt64(num1 + offsets[i]));\n                    ReadProcessMemory(mProc.Handle, base1, memoryAddress, (UIntPtr)size, IntPtr.Zero);\n                    num1 = BitConverter.ToInt64(memoryAddress, 0);\n                }\n                return base1;\n            }\n            else\n            {\n                Int64 trueCode = Convert.ToInt64(newOffsets, 16);\n                IntPtr altModule = IntPtr.Zero;\n                if (theCode.Contains(\"base\") || theCode.Contains(\"main\"))\n                    altModule = mProc.MainModule.BaseAddress;\n                else if (!theCode.Contains(\"base\") && !theCode.Contains(\"main\") && theCode.Contains(\"+\"))\n                {\n                    string[] moduleName = theCode.Split('+');\n                    if (!moduleName[0].ToLower().Contains(\".dll\") && !moduleName[0].ToLower().Contains(\".exe\") && !moduleName[0].ToLower().Contains(\".bin\"))\n                    {\n                        string theAddr = moduleName[0];\n                        if (theAddr.Contains(\"0x\")) theAddr = theAddr.Replace(\"0x\", \"\");\n                        altModule = (IntPtr)Int64.Parse(theAddr, NumberStyles.HexNumber);\n                    }\n                    else\n                    {\n                        try\n                        {\n                            altModule = GetModuleAddressByName(moduleName[0]);\n                        }\n                        catch\n                        {\n                            Debug.WriteLine(\"Module \" + moduleName[0] + \" was not found in module list!\");\n                            //Debug.WriteLine(\"Modules: \" + string.Join(\",\", mProc.Modules));\n                        }\n                    }\n                }\n                else\n                    altModule = GetModuleAddressByName(theCode.Split('+')[0]);\n                return (UIntPtr)((Int64)altModule + trueCode);\n            }\n        }\n\n        public string MSize()\n        {\n            if (mProc.Is64Bit)\n                return (\"x16\");\n            else\n                return (\"x8\");\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/Base64UrlHelper.cs",
    "content": "// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/9895855ac4fcf52893fbc2b06ee20ea3eda1549a/src/client/Microsoft.Identity.Client/Utils/Base64UrlHelpers.cs\n// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System;\nusing System.Diagnostics;\nusing System.Globalization;\nusing System.Text;\n\nnamespace XboxAuthNet\n{\n    // Based on https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/1698/files\n    internal static class Base64UrlHelper\n    {\n        private const char base64PadCharacter = '=';\n#if NET45\n        private const string doubleBase64PadCharacter = \"==\";\n#endif\n        private const char base64Character62 = '+';\n        private const char base64Character63 = '/';\n        private const char base64UrlCharacter62 = '-';\n        private const char base64UrlCharacter63 = '_';\n\n        /// <summary>\n        /// Encoding table\n        /// </summary>\n        internal static readonly char[] s_base64Table =\n        {\n            'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',\n            'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',\n            '0','1','2','3','4','5','6','7','8','9',\n            base64UrlCharacter62,\n            base64UrlCharacter63\n        };\n\n        /// <summary>\n        /// The following functions perform base64url encoding which differs from regular base64 encoding as follows\n        /// * padding is skipped so the pad character '=' doesn't have to be percent encoded\n        /// * the 62nd and 63rd regular base64 encoding characters ('+' and '/') are replace with ('-' and '_')\n        /// The changes make the encoding alphabet file and URL safe.\n        /// </summary>\n        /// <param name=\"arg\">string to encode.</param>\n        /// <returns>Base64Url encoding of the UTF8 bytes.</returns>\n        public static string Encode(string arg)\n        {\n            return Encode(Encoding.UTF8.GetBytes(arg));\n        }\n\n        /// <summary>\n        /// Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation that is encoded with base-64-url digits. Parameters specify\n        /// the subset as an offset in the input array, and the number of elements in the array to convert.\n        /// </summary>\n        /// <param name=\"inArray\">An array of 8-bit unsigned integers.</param>\n        /// <param name=\"length\">An offset in inArray.</param>\n        /// <param name=\"offset\">The number of elements of inArray to convert.</param>\n        /// <returns>The string representation in base 64 url encoding of length elements of inArray, starting at position offset.</returns>\n        /// <exception cref=\"ArgumentNullException\">'inArray' is null.</exception>\n        /// <exception cref=\"ArgumentOutOfRangeException\">offset or length is negative OR offset plus length is greater than the length of inArray.</exception>\n        private static string Encode(byte[] inArray, int offset, int length)\n        {\n            _ = inArray ?? throw new ArgumentNullException(nameof(inArray));\n\n            if (length == 0)\n                return string.Empty;\n\n            if (length < 0)\n                throw new ArgumentOutOfRangeException(nameof(length));\n\n            if (offset < 0 || inArray.Length < offset)\n                throw new ArgumentOutOfRangeException(nameof(offset));\n\n            if (inArray.Length < offset + length)\n                throw new ArgumentOutOfRangeException(nameof(length));\n\n            int lengthmod3 = length % 3;\n            int limit = offset + (length - lengthmod3);\n            char[] output = new char[(length + 2) / 3 * 4];\n            char[] table = s_base64Table;\n            int i, j = 0;\n\n            // takes 3 bytes from inArray and insert 4 bytes into output\n            for (i = offset; i < limit; i += 3)\n            {\n                byte d0 = inArray[i];\n                byte d1 = inArray[i + 1];\n                byte d2 = inArray[i + 2];\n\n                output[j + 0] = table[d0 >> 2];\n                output[j + 1] = table[((d0 & 0x03) << 4) | (d1 >> 4)];\n                output[j + 2] = table[((d1 & 0x0f) << 2) | (d2 >> 6)];\n                output[j + 3] = table[d2 & 0x3f];\n                j += 4;\n            }\n\n            //Where we left off before\n            i = limit;\n\n            switch (lengthmod3)\n            {\n                case 2:\n                    {\n                        byte d0 = inArray[i];\n                        byte d1 = inArray[i + 1];\n\n                        output[j + 0] = table[d0 >> 2];\n                        output[j + 1] = table[((d0 & 0x03) << 4) | (d1 >> 4)];\n                        output[j + 2] = table[(d1 & 0x0f) << 2];\n                        j += 3;\n                    }\n                    break;\n\n                case 1:\n                    {\n                        byte d0 = inArray[i];\n\n                        output[j + 0] = table[d0 >> 2];\n                        output[j + 1] = table[(d0 & 0x03) << 4];\n                        j += 2;\n                    }\n                    break;\n\n                    //default or case 0: no further operations are needed.\n            }\n\n            return new string(output, 0, j);\n        }\n\n        /// <summary>\n        /// Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation that is encoded with base-64-url digits. Parameters specify\n        /// the subset as an offset in the input array, and the number of elements in the array to convert.\n        /// </summary>\n        /// <param name=\"inArray\">An array of 8-bit unsigned integers.</param>\n        /// <returns>The string representation in base 64 url encoding of length elements of inArray, starting at position offset.</returns>\n        /// <exception cref=\"ArgumentNullException\">'inArray' is null.</exception>\n        /// <exception cref=\"ArgumentOutOfRangeException\">offset or length is negative OR offset plus length is greater than the length of inArray.</exception>\n        public static string Encode(byte[] inArray)\n        {\n            return Encode(inArray, 0, inArray.Length);\n        }\n\n        internal static string EncodeString(string str)\n        {\n            return Encode(Encoding.UTF8.GetBytes(str));\n        }\n\n        /// <summary>\n        ///  Converts the specified string, which encodes binary data as base-64-url digits, to an equivalent 8-bit unsigned integer array.</summary>\n        /// <param name=\"str\">base64Url encoded string.</param>\n        /// <returns>UTF8 bytes.</returns>\n        public static byte[] DecodeBytes(string str)\n        {\n#if NET45\n            // 62nd char of encoding\n            str = str.Replace(base64UrlCharacter62, base64Character62);\n\n            // 63rd char of encoding\n            str = str.Replace(base64UrlCharacter63, base64Character63);\n\n            // check for padding\n            switch (str.Length % 4)\n            {\n                case 0:\n                    // No pad chars in this case\n                    break;\n                case 2:\n                    // Two pad chars\n                    str += doubleBase64PadCharacter;\n                    break;\n                case 3:\n                    // One pad char\n                    str += base64PadCharacter;\n                    break;\n                default:\n                    throw new FormatException($\"Unable to decode: {str} as Base64url encoded string.\");\n            }\n\n            return Convert.FromBase64String(str);\n#else\n            return UnsafeDecode(str);\n#endif\n        }\n\n#if !NET45\n        private unsafe static byte[] UnsafeDecode(string str)\n        {\n            int mod = str.Length % 4;\n            if (mod == 1)\n                throw new FormatException($\"Unable to decode: {str} as Base64url encoded string.\");\n\n            bool needReplace = false;\n            int decodedLength = str.Length + (4 - mod) % 4;\n\n            for (int i = 0; i < str.Length; i++)\n            {\n                if (str[i] == base64UrlCharacter62 || str[i] == base64UrlCharacter63)\n                {\n                    needReplace = true;\n                    break;\n                }\n            }\n\n            if (needReplace)\n            {\n                string decodedString = new string(char.MinValue, decodedLength);\n                fixed (char* dest = decodedString)\n                {\n                    int i = 0;\n                    for (; i < str.Length; i++)\n                    {\n                        if (str[i] == base64UrlCharacter62)\n                            dest[i] = base64Character62;\n                        else if (str[i] == base64UrlCharacter63)\n                            dest[i] = base64Character63;\n                        else\n                            dest[i] = str[i];\n                    }\n\n                    for (; i < decodedLength; i++)\n                        dest[i] = base64PadCharacter;\n                }\n\n                return Convert.FromBase64String(decodedString);\n            }\n            else\n            {\n                if (decodedLength == str.Length)\n                {\n                    return Convert.FromBase64String(str);\n                }\n                else\n                {\n                    string decodedString = new string(char.MinValue, decodedLength);\n                    fixed (char* src = str)\n                    fixed (char* dest = decodedString)\n                    {\n                        Buffer.MemoryCopy(src, dest, str.Length * 2, str.Length * 2);\n                        dest[str.Length] = base64PadCharacter;\n                        if (str.Length + 2 == decodedLength)\n                            dest[str.Length + 1] = base64PadCharacter;\n                    }\n\n                    return Convert.FromBase64String(decodedString);\n                }\n            }\n        }\n#endif\n\n        /// <summary>\n        /// Decodes the string from Base64UrlEncoded to UTF8.\n        /// </summary>\n        /// <param name=\"arg\">string to decode.</param>\n        /// <returns>UTF8 string.</returns>\n        public static string Decode(string arg)\n        {\n            return Encoding.UTF8.GetString(DecodeBytes(arg));\n        }\n    }   \n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/ErrorCodes.cs",
    "content": "﻿namespace XboxAuthNet\n{\n    // https://github.com/microsoft/xbox-live-api/blob/f1a347b91f5f5dae62c35623719ecf8b9ba68746/Source/Shared/errors_legacy.h#L875\n    public class ErrorCodes\n    {\n        /// <summary>\n        /// <b>0x8015DC00</b>\n        /// Developer mode is not authorized for the client device.\n        /// </summary>\n        const uint XO_E_DEVMODE_NOT_AUTHORIZED = (uint)0x8015DC00,\n\n        /// <summary>\n        /// <b>0x8015DC01</b>\n        /// A system update is required before this action can be performed.\n        /// </summary>\n        XO_E_SYSTEM_UPDATE_REQUIRED = (uint)0x8015DC01,\n\n        /// <summary>\n        /// <b>0x8015DC02</b>\n        /// A content update is required before this action can be performed.\n        /// </summary>\n        XO_E_CONTENT_UPDATE_REQUIRED = (uint)0x8015DC02,\n\n        /// <summary>\n        /// <b>0x8015DC03</b>\n        /// The device or user was banned.\n        /// </summary>\n        XO_E_ENFORCEMENT_BAN = (uint)0x8015DC03,\n\n        /// <summary>\n        /// <b>0x8015DC04</b>\n        /// The device or user was banned.\n        /// </summary>\n        XO_E_THIRD_PARTY_BAN = (uint)0x8015DC04,\n\n        /// <summary>\n        /// <b>0x8015DC05</b>\n        /// Access to this resource has been parentally restricted.\n        /// </summary>\n        XO_E_ACCOUNT_PARENTALLY_RESTRICTED = (uint)0x8015DC05,\n\n        /// <summary>\n        /// <b>0x8015DC08</b>\n        /// Access to this resource requires that the account billing information\n        /// is updated.\n        /// </summary>\n        XO_E_ACCOUNT_BILLING_MAINTENANCE_REQUIRED = (uint)0x8015DC08,\n\n        /// <summary>\n        /// <b>0x8015DC0A</b>\n        /// The user has not accepted the terms of use for this resource.\n        /// </summary>\n        XO_E_ACCOUNT_TERMS_OF_USE_NOT_ACCEPTED = (uint)0x8015DC0A,\n\n        /// <summary>\n        /// <b>0x8015DC0B</b>\n        /// This resource is not available in the country associated with the user.\n        /// </summary>\n        XO_E_ACCOUNT_COUNTRY_NOT_AUTHORIZED = (uint)0x8015DC0B,\n\n        /// <summary>\n        /// <b>0x8015DC0C</b>\n        /// Access to this resource requires age verification.\n        /// </summary>\n        XO_E_ACCOUNT_AGE_VERIFICATION_REQUIRED = (uint)0x8015DC0C,\n\n        /// <summary>\n        /// <b>0x8015DC0D</b>\n        /// </summary>\n        XO_E_ACCOUNT_CURFEW = (uint)0x8015DC0D,\n\n        /// <summary>\n        /// <b>0x8015DC0E</b>\n        /// </summary>\n        XO_E_ACCOUNT_CHILD_NOT_IN_FAMILY = (uint)0x8015DC0E,\n\n        /// <summary>\n        /// <b>0x8015DC0F</b>\n        /// </summary>\n        XO_E_ACCOUNT_CSV_TRANSITION_REQUIRED = (uint)0x8015DC0F,\n\n        /// <summary>\n        /// <b>0x8015DC09</b>\n        /// </summary>\n        XO_E_ACCOUNT_CREATION_REQUIRED = (uint)0x8015DC09,\n\n        /// <summary>\n        /// <b>0x8015DC10</b>\n        /// </summary>\n        XO_E_ACCOUNT_MAINTENANCE_REQUIRED = (uint)0x8015DC10,\n\n        /// <summary>\n        /// <b>0x8015DC11</b>\n        /// The call was blocked because there was a conflict with the sandbox, console, application, or \n        /// your account.Verify your account, console and title settings in XDP, and check the current \n        /// Sandbox on the device.\n        /// </summary>\n        XO_E_ACCOUNT_TYPE_NOT_ALLOWED = (uint)0x8015DC11,\n\n        /// <summary>\n        /// <b>0x8015DC12</b>\n        /// Your device does not have access to the Sandbox it is set to, or the account you are signed \n        /// in with does not have access to the Sandbox.Check that you are using the correct Sandbox.\n        ///\n        /// Note: All XDK samples use XDKS.1 SandboxID, which allow all user accounts to access and run \n        /// the samples.SandboxID's are case sensitive- Not matching the case of your SandboxID exactly may \n        /// result in errors. If you are still having issues running the sample, please work with your \n        /// Developer Account Manager and provide a fiddler trace to help with troubleshooting.\n        ///\n        /// For more information on handling this error, please see the \"Troubleshooting Sign-in\" article\n        /// in the Xbox Live documentation\n        /// </summary>\n        XO_E_CONTENT_ISOLATION = (uint)0x8015DC12,\n\n        /// <summary>\n        /// <b>0x8015DC13</b>\n        /// </summary>\n        XO_E_ACCOUNT_NAME_CHANGE_REQUIRED = (uint)0x8015DC13,\n\n        /// <summary>\n        /// <b>0x8015DC14</b>\n        /// </summary>\n        XO_E_DEVICE_CHALLENGE_REQUIRED = (uint)0x8015DC14,\n\n        /// <summary>\n        /// <b>0x8015DC16</b>\n        /// The account was signed in on another device.\n        /// </summary>\n        XO_E_SIGNIN_COUNT_BY_DEVICE_TYPE_EXCEEDED = (uint)0x8015DC16,\n\n        /// <summary>\n        /// <b>0x8015DC17</b>\n        /// </summary>\n        XO_E_PIN_CHALLENGE_REQUIRED = (uint)0x8015DC17,\n\n        /// <summary>\n        /// <b>0x8015DC18</b>\n        /// </summary>\n        XO_E_RETAIL_ACCOUNT_NOT_ALLOWED = (uint)0x8015DC18,\n\n        /// <summary>\n        /// <b>0x8015DC19</b>\n        /// The current sandbox is not allowed to access the SCID.  Please ensure that your current\n        /// sandbox is set to your development sandbox.  If you are running on a Windows 10 PC, then\n        /// you can change your current sandbox using the SwitchSandbox.cmd script in the Xbox Live SDK\n        /// tools directory.  If you are using an Xbox One, you can switch the sandbox using Xbox One\n        /// Manager.\n        ///\n        /// For more information on handling this error, please see the \"Troubleshooting Sign-in\" article\n        /// in the Xbox Live documentation.\n        /// </summary>\n        XO_E_SANDBOX_NOT_ALLOWED = (uint)0x8015DC19,\n\n        /// <summary>\n        /// <b>0x8015DC1A</b>\n        /// </summary>\n        XO_E_ACCOUNT_SERVICE_UNAVAILABLE_UNKNOWN_USER = (uint)0x8015DC1A,\n\n        /// <summary>\n        /// <b>0x8015DC1B</b>\n        /// </summary>\n        XO_E_GREEN_SIGNED_CONTENT_NOT_AUTHORIZED = (uint)0x8015DC1B,\n\n        /// <summary>\n        /// <b>0x8015DC1C</b>\n        /// </summary>\n        XO_E_CONTENT_NOT_AUTHORIZED = (uint)0x8015DC1C;\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/ErrorHelper.cs",
    "content": "﻿using System;\n\nnamespace XboxAuthNet\n{\n    internal static class ErrorHelper\n    {\n        public static string? ConvertToHexErrorCode(string? errorCode)\n        {\n            if (!string.IsNullOrEmpty(errorCode))\n            {\n                var errorInt = long.Parse(errorCode);\n                errorCode = errorInt.ToString(\"x\");\n            }\n            return errorCode;\n        }\n        \n        public static string? TryConvertToHexErrorCode(string? errorCode)\n        {\n            try\n            {\n                return ConvertToHexErrorCode(errorCode);\n            }\n            catch (FormatException)\n            {\n                return errorCode;\n            }\n            catch (OverflowException)\n            {\n                return errorCode;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/FodyWeavers.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Weavers xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"FodyWeavers.xsd\">\n  <ConfigureAwait ContinueOnCapturedContext=\"false\" />\n</Weavers>"
  },
  {
    "path": "XAU/Util/XboxAuthNet/HttpHelper.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing System.Net.Http.Json;\nusing System.Web;\nusing System.Text.Json;\n\nnamespace XboxAuthNet\n{\n    public class HttpHelper\n    {\n        public const string UserAgent = \"Mozilla/5.0 (XboxReplay; XboxLiveAuth/3.0) \" +\n                                        \"AppleWebKit/537.36 (KHTML, like Gecko) \" +\n                                        \"Chrome/71.0.3578.98 Safari/537.36\";\n\n        public static string GetQueryString(Dictionary<string, string?> queries)\n        {\n            return string.Join(\"&\",\n                queries.Select(x => $\"{x.Key}={HttpUtility.UrlEncode(x.Value)}\"));\n        }\n\n        public static JsonContent CreateJsonContent<T>(T obj)\n        {\n            return JsonContent.Create(obj,\n                mediaType: new System.Net.Http.Headers.MediaTypeHeaderValue(\"application/json\"),\n                options: new JsonSerializerOptions\n                {\n                    DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,\n                    PropertyNamingPolicy = null\n                });\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/Jwt/JwtDecoder.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Text.Json;\n\nnamespace XboxAuthNet.Jwt\n{\n    internal static class JwtDecoder\n    {\n        /// <summary>\n        /// decode jwt payload\n        /// </summary>\n        /// <param name=\"jwt\">entire jwt</param>\n        /// <returns>decoded jwt payload</returns>\n        /// <exception cref=\"ArgumentNullException\"></exception>\n        /// <exception cref=\"FormatException\">invalid jwt</exception>\n        internal static string DecodePayloadString(string jwt)\n        {\n            if (string.IsNullOrEmpty(jwt))\n                throw new ArgumentNullException(jwt);\n\n            string[] spl = jwt.Split('.');\n            if (spl.Length != 3)\n                throw new FormatException(\"invalid jwt\");\n\n            string encodedPayload = spl[1];\n            switch (encodedPayload.Length % 4)\n            {\n                case 0:\n                    break;\n                case 2:\n                    encodedPayload += \"==\";\n                    break;\n                case 3:\n                    encodedPayload += \"=\";\n                    break;\n                default:\n                    throw new FormatException(\"jwt payload\");\n\n            }\n\n            string decodedPayload = Encoding.UTF8.GetString(Convert.FromBase64String(encodedPayload)); // encodedPayload can't be null since string.Split never return null element\n            return decodedPayload;\n        }\n\n        /// <summary>\n        /// decode jwt payload and deserialize\n        /// </summary>\n        /// <param name=\"jwt\"></param>\n        /// <returns>deserialized object of jwt payload</returns>\n        /// <exception cref=\"ArgumentNullException\"></exception>\n        /// <exception cref=\"FormatException\">invalid jwt</exception>\n        internal static T? DecodePayload<T>(string jwt) where T : class\n        {\n            string payload = DecodePayloadString(jwt);\n            return JsonSerializer.Deserialize<T>(payload);\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/AuthCodeException.cs",
    "content": "﻿namespace XboxAuthNet.OAuth.CodeFlow;\n\ninternal class AuthCodeException : Exception\n{\n    public AuthCodeException(string? error, string? errorDescription) : base(error ?? errorDescription)\n    {\n        Error = error;\n        ErrorDescription = errorDescription;\n    }\n\n    public string? Error { get; }\n    public string? ErrorDescription { get; }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/CodeFlowAuthenticator.cs",
    "content": "﻿using XboxAuthNet.OAuth.CodeFlow.Parameters;\n\nnamespace XboxAuthNet.OAuth.CodeFlow;\n\npublic class CodeFlowAuthenticator\n{\n    private readonly ICodeFlowApiClient _client;\n    private readonly ICodeFlowUrlChecker _uriChecker;\n    private readonly IWebUI _ui;\n\n    internal CodeFlowAuthenticator(\n        ICodeFlowApiClient client,\n        IWebUI ui,\n        ICodeFlowUrlChecker urlChecker)\n    {\n        _client = client;\n        _ui = ui;\n        _uriChecker = urlChecker;\n    }\n\n    public Task<MicrosoftOAuthResponse> AuthenticateInteractively(CancellationToken cancellationToken = default)\n        => AuthenticateInteractively(\n            new CodeFlowAuthorizationParameter(),\n            cancellationToken);\n\n    public async Task<MicrosoftOAuthResponse> AuthenticateInteractively(\n        CodeFlowAuthorizationParameter parameter,\n        CancellationToken cancellationToken = default)\n    {   \n        var uri = _client.CreateAuthorizeCodeUrl(parameter);\n        var authCode = await _ui.DisplayDialogAndInterceptUri(\n            new Uri(uri), _uriChecker, cancellationToken);\n\n        if (!authCode.IsSuccess)\n        {\n            throw new AuthCodeException(authCode.Error, authCode.ErrorDescription);\n        }\n\n        var tokenParameter = new CodeFlowAccessTokenParameter\n        {\n            Code = authCode.Code\n        };\n        tokenParameter.RedirectUrl = parameter.RedirectUri;\n        return await _client.GetAccessToken(tokenParameter, cancellationToken);\n    }\n\n    public Task<MicrosoftOAuthResponse> AuthenticateSilently(\n        string refreshToken,\n        CancellationToken cancellationToken = default)\n    {\n        var parameter = new CodeFlowRefreshTokenParameter\n        { \n            RefreshToken = refreshToken \n        };\n        return _client.RefreshToken(parameter, cancellationToken);\n    }\n\n    public async Task Signout(CancellationToken cancellationToken = default)\n    {\n        var url = _client.CreateSignoutUrl();\n        await _ui.DisplayDialogAndNavigateUri(new Uri(url), cancellationToken);\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/CodeFlowAuthorizationResult.cs",
    "content": "﻿namespace XboxAuthNet.OAuth.CodeFlow;\n\npublic struct CodeFlowAuthorizationResult\n{\n    public string? Code { get; set; }\n    public string? IdToken { get; set; }\n    public string? State { get; set; }\n    public string? Error { get; set; }\n    public string? ErrorDescription { get; set; }\n\n    public bool IsSuccess\n       => !string.IsNullOrEmpty(Code)\n        && string.IsNullOrEmpty(Error);\n\n    public bool IsEmpty\n        => string.IsNullOrEmpty(Code)\n        && string.IsNullOrEmpty(Error)\n        && string.IsNullOrEmpty(ErrorDescription);\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/CodeFlowBuilder.cs",
    "content": "namespace XboxAuthNet.OAuth.CodeFlow;\n\npublic class CodeFlowBuilder\n{\n    private readonly ICodeFlowApiClient _apiClient;\n\n    public CodeFlowBuilder(ICodeFlowApiClient apiClient)\n    {\n        this._apiClient = apiClient;\n    }\n\n    private WebUIOptions? uiOptions;\n    private IWebUI? webUI;\n    private ICodeFlowUrlChecker? uriChecker;\n\n    public CodeFlowBuilder WithUIParent(object parent)\n    {\n        uiOptions ??= createDefaultWebUIOptions();\n        uiOptions.ParentObject = parent;\n        return this;\n    }\n\n    public CodeFlowBuilder WithUITitle(string title)\n    {\n        uiOptions ??= createDefaultWebUIOptions();\n        uiOptions.Title = title;\n        return this;\n    }\n\n    public CodeFlowBuilder WithUIOptions(WebUIOptions options)\n    {\n        this.uiOptions = options;\n        return this;\n    }\n\n    public CodeFlowBuilder WithWebUI(IWebUI ui)\n    {\n        this.webUI = ui;\n        return this;\n    }\n\n    public CodeFlowBuilder WithWebUI(Func<WebUIOptions, IWebUI> factory)\n    {\n        this.uiOptions ??= createDefaultWebUIOptions();\n        WithWebUI(factory.Invoke(this.uiOptions));\n        return this;\n    }\n\n    public CodeFlowBuilder WithUriChecker(ICodeFlowUrlChecker checker)\n    {\n        this.uriChecker = checker;\n        return this;\n    }\n\n    public CodeFlowAuthenticator Build()\n    {\n        uriChecker ??= createDefaultUriChecker();\n        webUI ??= createDefaultWebUIForPlatform();\n        return new CodeFlowAuthenticator(_apiClient, webUI, uriChecker);\n    }\n\n    private ICodeFlowUrlChecker createDefaultUriChecker()\n    {\n        return new CodeFlowUrlChecker();\n    }\n\n    private IWebUI createDefaultWebUIForPlatform()\n    {\n        this.uiOptions ??= createDefaultWebUIOptions();\n        return PlatformManager.CurrentPlatform.CreateWebUI(uiOptions);\n    }\n\n    private WebUIOptions createDefaultWebUIOptions() => new WebUIOptions\n    {\n        ParentObject = null,\n        SynchronizationContext = SynchronizationContext.Current\n    };\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/CodeFlowLiveApiClient.cs",
    "content": "﻿using System.Net.Http;\nusing XboxAuthNet.OAuth.CodeFlow.Parameters;\n\nnamespace XboxAuthNet.OAuth.CodeFlow;\n\npublic class CodeFlowLiveApiClient : ICodeFlowApiClient\n{\n    public const string OAuthDesktop = \"https://login.live.com/oauth20_desktop.srf\";\n    public const string OAuthAuthorize = \"https://login.live.com/oauth20_authorize.srf\";\n    public const string OAuthErrorPath = \"/err.srf\";\n    public const string OAuthToken = \"https://login.live.com/oauth20_token.srf\";\n\n    private readonly HttpClient httpClient;\n\n    public CodeFlowLiveApiClient(string clientId, string scope, HttpClient client)\n    {\n        ClientId = clientId;\n        Scope = scope;\n        httpClient = client;\n    }\n\n    public string ClientId { get; }\n    public string Scope { get; }\n\n    public string CreateAuthorizeCodeUrl(CodeFlowAuthorizationParameter parameter)\n    {\n        setCommonParameters(parameter);\n        if (string.IsNullOrEmpty(parameter.RedirectUri))\n            parameter.RedirectUri = OAuthDesktop;\n        if (string.IsNullOrEmpty(parameter.ResponseType))\n            parameter.ResponseType = \"code\";\n        if (string.IsNullOrEmpty(parameter.ResponseMode))\n            parameter.ResponseMode = \"query\";\n        if (string.IsNullOrEmpty(parameter.Prompt))\n            parameter.Prompt = \"select_account\";\n\n        var query = parameter.ToQueryDictionary();\n        return OAuthAuthorize + \"?\" + HttpHelper.GetQueryString(query);\n    }\n\n    public string CreateSignoutUrl() =>\n        CreateSignoutUrl(\"consumer\");\n\n    public string CreateSignoutUrl(string tenant) =>\n        $\"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/logout\";\n\n    public Task<MicrosoftOAuthResponse> GetAccessToken(\n        CodeFlowAccessTokenParameter parameter,\n        CancellationToken cancellationToken) =>\n        requestToken(setAccessTokenParameters(parameter), cancellationToken);\n\n    private CodeFlowAccessTokenParameter setAccessTokenParameters(CodeFlowAccessTokenParameter parameters)\n    {\n        if (string.IsNullOrEmpty(parameters.RedirectUrl))\n            parameters.RedirectUrl = OAuthDesktop;\n        if (string.IsNullOrEmpty(parameters.GrantType))\n            parameters.GrantType = \"authorization_code\";\n        return parameters;\n    }\n\n    public Task<MicrosoftOAuthResponse> RefreshToken(\n        CodeFlowRefreshTokenParameter parameter,\n        CancellationToken cancellationToken) =>\n        requestToken(setRefreshTokenParameters(parameter), cancellationToken);\n\n    private CodeFlowRefreshTokenParameter setRefreshTokenParameters(CodeFlowRefreshTokenParameter parameters)\n    {\n        if (string.IsNullOrEmpty(parameters.GrantType))\n            parameters.GrantType = \"refresh_token\";\n        return parameters;\n    }\n\n    private async Task<MicrosoftOAuthResponse> requestToken(\n        CodeFlowParameter parameter,\n        CancellationToken cancellationToken)\n    {\n        setCommonParameters(parameter);\n        var queryDict = parameter.ToQueryDictionary();\n        return await microsoftOAuthRequest(new HttpRequestMessage\n        {\n            Method = HttpMethod.Post,\n            RequestUri = new Uri(OAuthToken),\n            Content = new FormUrlEncodedContent(queryDict!)\n        }, cancellationToken).ConfigureAwait(false);\n    }\n\n    private async Task<MicrosoftOAuthResponse> microsoftOAuthRequest(\n        HttpRequestMessage req,\n        CancellationToken cancellationToken)\n    {\n        req.Headers.Add(\"User-Agent\", HttpHelper.UserAgent);\n        req.Headers.Add(\"Accept-Encoding\", \"gzip\");\n        req.Headers.Add(\"Accept-Language\", \"en-US\");\n\n        var res = await httpClient.SendAsync(req, cancellationToken)\n            .ConfigureAwait(false);\n\n        var resBody = await res.Content.ReadAsStringAsync()\n            .ConfigureAwait(false);\n        var statusCode = (int)res.StatusCode;\n        var reasonPhrase = res.ReasonPhrase;\n        return MicrosoftOAuthResponse.FromHttpResponse(resBody, statusCode, reasonPhrase);\n    }\n\n    private void setCommonParameters(CodeFlowParameter parameter)\n    {\n        parameter.Tenant = \"consumer\";\n\n        if (string.IsNullOrEmpty(parameter.ClientId))\n            parameter.ClientId = ClientId;\n        if (string.IsNullOrEmpty(parameter.Scope))\n            parameter.Scope = Scope;\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/CodeFlowUriChecker.cs",
    "content": "﻿using System.Web;\n\nnamespace XboxAuthNet.OAuth.CodeFlow;\n\npublic class CodeFlowUrlChecker : ICodeFlowUrlChecker\n{\n    public CodeFlowAuthorizationResult GetAuthCodeResult(Uri uri)\n    {\n        var query = HttpUtility.ParseQueryString(uri.Query);\n        var authCode = new CodeFlowAuthorizationResult\n        {\n            Code = query[\"code\"],\n            IdToken = query[\"id_token\"],\n            State = query[\"state\"],\n            Error = query[\"error\"],\n            ErrorDescription = HttpUtility.UrlDecode(query[\"error_description\"])\n        };\n        return authCode;\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/ICodeFlowApiClient.cs",
    "content": "using XboxAuthNet.OAuth.CodeFlow.Parameters;\n\nnamespace XboxAuthNet.OAuth.CodeFlow;\n\npublic interface ICodeFlowApiClient\n{\n    string CreateAuthorizeCodeUrl(CodeFlowAuthorizationParameter parameter);\n\n    string CreateSignoutUrl();\n\n    Task<MicrosoftOAuthResponse> GetAccessToken(\n        CodeFlowAccessTokenParameter parameter,\n        CancellationToken cancellationToken);\n\n    Task<MicrosoftOAuthResponse> RefreshToken(\n        CodeFlowRefreshTokenParameter parameter,\n        CancellationToken cancellationToken);\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/ICodeFlowUrlChecker.cs",
    "content": "﻿namespace XboxAuthNet.OAuth.CodeFlow\n{\n    public interface ICodeFlowUrlChecker\n    {\n        CodeFlowAuthorizationResult GetAuthCodeResult(Uri uri);\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/IWebUI.cs",
    "content": "﻿// This code is from MSAL.NET\n\nnamespace XboxAuthNet.OAuth.CodeFlow;\n\npublic interface IWebUI\n{\n    Task<CodeFlowAuthorizationResult> DisplayDialogAndInterceptUri(Uri uri, ICodeFlowUrlChecker uriChecker, CancellationToken cancellationToken);\n    Task DisplayDialogAndNavigateUri(Uri uri, CancellationToken cancellationToken);\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/MicrosoftOAuthPromptModes.cs",
    "content": "﻿namespace XboxAuthNet.OAuth.CodeFlow;\n\npublic static class MicrosoftOAuthPromptModes\n{\n    /// <summary>\n    /// Forces the user to enter their credentials on that request, negating single-sign on.\n    /// </summary>\n    public const string Login = \"login\";\n\n    /// <summary>\n    /// Ensures that the user isn't presented with any interactive prompt. \n    /// If the request can't be completed silently by using sigle-sign on, \n    /// the Microsoft identity platform returns an `interaction_required` error.\n    /// </summary>\n    public const string None = \"none\";\n\n    /// <summary>\n    /// Triggers the OAuth consent dialog after the user signs in, asking the user to grant permissions to the app.\n    /// </summary>\n    public const string Consent = \"consent\";\n\n    /// <summary>\n    /// Interrupts single sign-on providing account selection experience listing all the accounts \n    /// either in session or any remembered account or an option to choose to use a different account altogether.\n    /// </summary>\n    public const string SelectAccount = \"select_account\";\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/Parameters/CodeFlowAccessTokenParameter.cs",
    "content": "using System.Text.Json.Serialization;\n\nnamespace XboxAuthNet.OAuth.CodeFlow.Parameters;\n\npublic class CodeFlowAccessTokenParameter : CodeFlowParameter\n{\n    public const string DefaultClientAssertionType = \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\";\n\n\n    [JsonPropertyName(\"code\")]\n    public string? Code { get; set; }\n\n    [JsonPropertyName(\"redirect_uri\")]\n    public string? RedirectUrl { get; set; }\n\n    [JsonPropertyName(\"grant_type\")]\n    public string? GrantType { get; set; }\n\n    [JsonPropertyName(\"code_verifier\")]\n    public string? CodeVerifier { get; set; }\n\n    [JsonPropertyName(\"client_secret\")]\n    public string? ClientSecret { get; set; }\n\n    [JsonPropertyName(\"client_assertion_type\")]\n    public string? ClientAssertionType { get; set; }\n\n    [JsonPropertyName(\"client_assertion\")]\n    public string? CilentAssertion { get; set; }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/Parameters/CodeFlowAuthorizationParameter.cs",
    "content": "using System.Text.Json.Serialization;\n\nnamespace XboxAuthNet.OAuth.CodeFlow.Parameters;\n\npublic class CodeFlowAuthorizationParameter : CodeFlowParameter\n{\n    /// <summary>\n    /// response_type: id_token, token, code\n    /// </summary>\n    [JsonPropertyName(\"response_type\")]\n    public string? ResponseType { get; set; }\n\n    /// <summary>\n    /// redirect_uri\n    /// </summary>\n    [JsonPropertyName(\"redirect_uri\")]\n    public string? RedirectUri { get; set; }\n\n    /// <summary>\n    /// response_mode: query, fragment, form_post\n    /// </summary>\n    [JsonPropertyName(\"response_mode\")]\n    public string? ResponseMode { get; set; }\n\n    /// <summary>\n    /// state\n    /// </summary>\n    [JsonPropertyName(\"state\")]\n    public string? State { get; set; }\n\n    /// <summary>\n    /// prompt: login, none, consent, select_account\n    /// </summary>\n    [JsonPropertyName(\"prompt\")]\n    public string? Prompt { get; set; }\n\n    /// <summary>\n    /// login_hint\n    /// </summary>\n    [JsonPropertyName(\"login_hint\")]\n    public string? LoginHint { get; set; }\n\n    /// <summary>\n    /// domain_hint\n    /// </summary>\n    [JsonPropertyName(\"domain_hint\")]\n    public string? DomainHint { get; set; }\n\n    /// <summary>\n    /// code_challenge\n    /// </summary>\n    [JsonPropertyName(\"code_challenge\")]\n    public string? CodeChallenge { get; set; }\n\n    /// <summary>\n    /// code_challenge_method\n    /// </summary>\n    [JsonPropertyName(\"code_challenge_method\")]\n    public string? CodeChallengeMethod { get; set; }\n\n    [JsonPropertyName(\"nonce\")]\n    public string? Nonce { get; set; }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/Parameters/CodeFlowParameter.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace XboxAuthNet.OAuth.CodeFlow.Parameters;\n\npublic class CodeFlowParameter\n{\n    public string? Tenant { get; set; }\n\n    [JsonPropertyName(\"client_id\")]\n    public string? ClientId { get; set; }\n\n    [JsonPropertyName(\"scope\")]\n    public string? Scope { get; set; }\n\n    public Dictionary<string, string> ExtraQueries { get; } = new();\n\n    public Dictionary<string, string?> ToQueryDictionary()\n    {\n        var query = new Dictionary<string, string?>();\n        var props = GetType().GetProperties();\n        foreach (var prop in props)\n        {\n            var value = prop.GetMethod?.Invoke(this, null)?.ToString();\n            if (string.IsNullOrEmpty(value))\n                continue;\n\n            var attr = prop\n                .GetCustomAttributes(typeof(JsonPropertyNameAttribute), true)\n                .FirstOrDefault();\n            if (attr is not JsonPropertyNameAttribute jsonAttr)\n                continue;\n\n            var propName = jsonAttr.Name;\n            query[propName] = value;\n        }\n\n        foreach (var kv in ExtraQueries)\n        {\n            query[kv.Key] = kv.Value;\n        }\n        return query;\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/Parameters/CodeFlowRefreshTokenParameter.cs",
    "content": "using System.Text.Json.Serialization;\n\nnamespace XboxAuthNet.OAuth.CodeFlow.Parameters;\n\npublic class CodeFlowRefreshTokenParameter : CodeFlowParameter\n{\n    [JsonPropertyName(\"grant_type\")]\n    public string? GrantType { get; set; }\n\n    [JsonPropertyName(\"client_secret\")]\n    public string? ClientSecret { get; set; }\n\n    [JsonPropertyName(\"refresh_token\")]\n    public string? RefreshToken { get; set; }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/CodeFlow/WebUIOptions.cs",
    "content": "﻿namespace XboxAuthNet.OAuth.CodeFlow;\n\npublic class WebUIOptions\n{\n    public object? ParentObject { get; set; }\n    public string? Title { get; set; }\n    public SynchronizationContext? SynchronizationContext { get; set; }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/MicrosoftOAuthException.cs",
    "content": "﻿using System.Text.Json;\n\nnamespace XboxAuthNet.OAuth;\n\npublic class MicrosoftOAuthException : Exception\n{\n    public MicrosoftOAuthException()\n    {\n\n    }\n\n    public MicrosoftOAuthException(string? message, int statusCode) : base(message)\n        => StatusCode = statusCode;\n\n    public MicrosoftOAuthException(string? error, string? errorDes, int[]? codes, int statusCode) : base(CreateMessageFromError(error, errorDes))\n        => (StatusCode, Error, ErrorDescription, ErrorCodes) = (statusCode, error, errorDes, codes);\n\n    public int StatusCode { get; private set; }\n\n    public string? Error { get; private set; }\n\n    public string? ErrorDescription { get; private set; }\n\n    public int[]? ErrorCodes { get; private set; }\n\n    private static string CreateMessageFromError(string? error, string? errorDes)\n    {\n        return string.Join(\", \", new string?[] { error, errorDes }.Where(x => !string.IsNullOrEmpty(x)));\n    }\n\n    public static MicrosoftOAuthException FromResponseBody(string resBody, int statusCode, string? reasonPhrase)\n    {\n        try\n        {\n            throw MicrosoftOAuthException.fromJsonBody(resBody, statusCode);\n        }\n        catch (FormatException)\n        {\n            if (string.IsNullOrEmpty(reasonPhrase))\n                throw new MicrosoftOAuthException(statusCode.ToString(), statusCode);\n            else\n                throw new MicrosoftOAuthException($\"{statusCode}: {reasonPhrase}\", statusCode);\n        }\n    }\n\n    private static MicrosoftOAuthException fromJsonBody(string responseBody, int statusCode)\n    {\n        try\n        {\n            using var doc = JsonDocument.Parse(responseBody);\n            var root = doc.RootElement;\n\n            string? error = null;\n            string? errorDes = null;\n            int[]? errorCodes = null;\n\n            if (root.TryGetProperty(\"error\", out var errorProp) &&\n                errorProp.ValueKind == JsonValueKind.String)\n                error = errorProp.GetString();\n            if (root.TryGetProperty(\"error_description\", out var errorDesProp) &&\n                errorDesProp.ValueKind == JsonValueKind.String)\n                errorDes = errorDesProp.GetString();\n            if (root.TryGetProperty(\"error_codes\", out var errorCodesProp) &&\n                errorCodesProp.ValueKind == JsonValueKind.Array)\n            {\n                errorCodes = errorCodesProp\n                    .EnumerateArray()\n                    .Where(x => x.ValueKind == JsonValueKind.Number)\n                    .Select(x => x.GetInt32())\n                    .ToArray();\n            }\n\n            if (string.IsNullOrEmpty(error) && string.IsNullOrEmpty(errorDes))\n                throw new FormatException();\n\n            return new MicrosoftOAuthException(error, errorDes, errorCodes, statusCode);\n        }\n        catch (JsonException)\n        {\n            throw new FormatException();\n        }\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/MicrosoftOAuthResponse.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\nusing XboxAuthNet.Jwt;\n\nnamespace XboxAuthNet.OAuth;\n\npublic class MicrosoftOAuthResponse\n{\n    [JsonPropertyName(\"access_token\")]\n    public string? AccessToken { get; set; }\n\n    [JsonPropertyName(\"token_type\")]\n    public string? TokenType { get; set; }\n\n    [JsonPropertyName(\"expires_in\")]\n    public int ExpireIn { get; set; }\n\n    [JsonPropertyName(\"expires_on\")]\n    public DateTimeOffset ExpiresOn { get; set; }\n\n    [JsonPropertyName(\"scope\")]\n    public string? Scope { get; set; }\n\n    [JsonPropertyName(\"refresh_token\")]\n    public string? RawRefreshToken { get; set; }\n\n    [JsonIgnore]\n    public string? RefreshToken\n    {\n        get => RawRefreshToken?.Split('.')?.Last();\n        set => RawRefreshToken = \"M.R3_BAY.\" + value;\n    }\n\n    [JsonPropertyName(\"id_token\")]\n    public string? IdToken { get; set; }\n\n    public MicrosoftUserPayload? DecodeIdTokenPayload()\n    {\n        if (string.IsNullOrEmpty(IdToken))\n            return null;\n\n        return JwtDecoder.DecodePayload<MicrosoftUserPayload>(IdToken!);\n    }\n\n    public bool Validate()\n    {\n        if (string.IsNullOrEmpty(AccessToken))\n            return false;\n        \n        if (DateTime.UtcNow > ExpiresOn)\n            return false;\n\n        return true;\n    }\n\n    public static MicrosoftOAuthResponse FromHttpResponse(string resBody, int statusCode, string? reasonPhrase)\n    {\n        if (statusCode / 100 != 2)\n            throw MicrosoftOAuthException.FromResponseBody(resBody, statusCode, reasonPhrase);\n\n        try\n        {\n            var resObj = JsonSerializer.Deserialize<MicrosoftOAuthResponse>(resBody);\n            if (resObj == null)\n                throw new MicrosoftOAuthException(\"The response was empty.\", statusCode);\n\n            if (resObj.ExpiresOn == default)\n                resObj.ExpiresOn = DateTimeOffset.UtcNow.AddSeconds(resObj.ExpireIn);\n            return resObj;\n        }\n        catch (JsonException)\n        {\n            throw MicrosoftOAuthException.FromResponseBody(resBody, statusCode, reasonPhrase);\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/OAuth/MicrosoftUserPayload.cs",
    "content": "using System.Text.Json.Serialization;\n\nnamespace XboxAuthNet.OAuth;\n\n// https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens#payload-claims\npublic class MicrosoftUserPayload\n{\n    [JsonPropertyName(\"aud\")]\n    public string? ApplicationId { get; set; }\n\n    [JsonPropertyName(\"iss\")]\n    public string? Issuer { get; set; }\n\n    [JsonPropertyName(\"iat\")]\n    public long IssuedAt { get; set; }\n\n    [JsonPropertyName(\"idp\")]\n    public string? IdentityProvider { get; set; }\n\n    [JsonPropertyName(\"nbf\")]\n    public long NotBefore { get; set; }\n\n    [JsonPropertyName(\"exp\")]\n    public long ExpiresOn { get; set; }\n\n    [JsonPropertyName(\"c_hash\")]\n    public string? CodeHash { get; set; }\n\n    [JsonPropertyName(\"at_hash\")]\n    public string? AccessTokenHash { get; set; }\n\n    [JsonPropertyName(\"preferred_username\")]\n    public string? Username { get; set; }\n\n    [JsonPropertyName(\"email\")]\n    public string? Email { get; set; }\n\n    [JsonPropertyName(\"name\")]\n    public string? Name { get; set; }\n\n    [JsonPropertyName(\"nonce\")]\n    public string? Nonce { get; set; }\n\n    [JsonPropertyName(\"oid\")]\n    public string? UserId { get; set; }\n\n    [JsonPropertyName(\"roles\")]\n    public string[]? Roles { get; set; }\n\n    [JsonPropertyName(\"sub\")]\n    public string? Subject { get; set; }\n\n    [JsonPropertyName(\"tid\")]\n    public string? Tenant { get; set; }\n\n    [JsonPropertyName(\"unique_name\")]\n    public string? UniqueName { get; set; }\n\n    [JsonPropertyName(\"uti\")]\n    public string? TokenIdentifier { get; set; }\n\n    [JsonPropertyName(\"ver\")]\n    public string? Version { get; set; }\n\n    [JsonPropertyName(\"hasgroups\")]\n    public bool HasGroups { get; set; }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/PlatformManager.cs",
    "content": "using System.IO;\nusing Microsoft.Web.WebView2.Core;\nusing XboxAuthNet.OAuth.CodeFlow;\n\nnamespace XboxAuthNet;\n\npublic class PlatformManager\n{\n    private static PlatformManager? _currentPlatformInstance;\n    public static PlatformManager CurrentPlatform =>\n        _currentPlatformInstance ??= new PlatformManager();\n\n    public IWebUI CreateWebUI(WebUIOptions uiOptions)\n    {\n        IWebUI? ui = null;\n\n        if (!XboxAuthNet.Platforms.WinForm.WebView2WebUI.IsWebView2Available())\n            throw new Microsoft.Web.WebView2.Core.WebView2RuntimeNotFoundException();\n        ui = new XboxAuthNet.Platforms.WinForm.WebView2WebUI(uiOptions);\n\n\n        if (ui == null)\n            throw new PlatformNotSupportedException(\"Current platform does not support to provide default WebUI.\");\n        return ui;\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/Platforms/WinForm/StaTaskScheduler.cs",
    "content": "﻿// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n#if !NETSTANDARD\nusing System;\nusing System.Collections.Concurrent;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace XboxAuthNet.Platforms.WinForm\n{\n    // This IDisposable class doe not need to implement Dispose method in standard way, because it is sealed.\n    // If it ever needs to become inheritable, it should follow the standard pattern as described in http://msdn.microsoft.com/en-us/library/fs2xkftw(v=vs.110).aspx.\n    /// <summary>Provides a scheduler that uses STA threads.</summary>\n#if NET5_WIN\n    [System.Runtime.Versioning.SupportedOSPlatform(\"windows\")]\n#endif\n    internal sealed class StaTaskScheduler : TaskScheduler, IDisposable\n    {\n        /// <summary>The STA threads used by the scheduler.</summary>\n        private readonly List<Thread> _threads;\n\n        /// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary>\n        private BlockingCollection<Task> _tasks;\n\n        /// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>\n        /// <param name=\"numberOfThreads\">The number of threads that should be created and used by this scheduler.</param>\n        public StaTaskScheduler(int numberOfThreads)\n        {\n            // Validate arguments\n            if (numberOfThreads < 1)\n            {\n                throw new ArgumentOutOfRangeException(nameof(numberOfThreads));\n            }\n\n            // Initialize the tasks collection\n            _tasks = new BlockingCollection<Task>();\n\n            // Create the threads to be used by this scheduler\n            _threads = Enumerable.Range(0, numberOfThreads).Select(i =>\n            {\n                var thread = new Thread(() =>\n                {\n                    // Continually get the next task and try to execute it.\n                    // This will continue until the scheduler is disposed and no more tasks remain.\n                    foreach (var t in _tasks.GetConsumingEnumerable())\n                    {\n                        TryExecuteTask(t);\n                    }\n                })\n                { IsBackground = true };\n                thread.SetApartmentState(ApartmentState.STA);\n                return thread;\n            }).ToList();\n\n            // Start all of the threads\n            _threads.ForEach(t => t.Start());\n        }\n\n        /// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>\n        public override int MaximumConcurrencyLevel\n        {\n            get { return _threads.Count; }\n        }\n\n        /// <summary>\n        /// Cleans up the scheduler by indicating that no more tasks will be queued.\n        /// This method blocks until all threads successfully shutdown.\n        /// </summary>\n        public void Dispose()\n        {\n            if (_tasks != null)\n            {\n                // Indicate that no new tasks will be coming in\n                _tasks.CompleteAdding();\n\n                // Wait for all threads to finish processing tasks\n                foreach (var thread in _threads)\n                {\n                    thread.Join();\n                }\n\n                // Cleanup\n                _tasks.Dispose();\n                _tasks = null!;\n            }\n        }\n\n        /// <summary>Queues a Task to be executed by this scheduler.</summary>\n        /// <param name=\"task\">The task to be executed.</param>\n        protected override void QueueTask(Task task)\n        {\n            // Push it into the blocking collection of tasks\n            _tasks.Add(task);\n        }\n\n        /// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>\n        /// <returns>An enumerable of all tasks currently scheduled.</returns>\n        protected override IEnumerable<Task> GetScheduledTasks()\n        {\n            // Serialize the contents of the blocking collection of tasks for the debugger\n            return _tasks.ToArray();\n        }\n\n        /// <summary>Determines whether a Task may be inlined.</summary>\n        /// <param name=\"task\">The task to be executed.</param>\n        /// <param name=\"taskWasPreviouslyQueued\">Whether the task was previously queued.</param>\n        /// <returns>true if the task was successfully inlined; otherwise, false.</returns>\n        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)\n        {\n            // Try to inline if the current thread is STA\n            return\n                Thread.CurrentThread.GetApartmentState() == ApartmentState.STA &&\n                TryExecuteTask(task);\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/Platforms/WinForm/UIThreadHelper.cs",
    "content": "using System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace XboxAuthNet.Platforms.WinForm\n{\n    internal static class UIThreadHelper\n    {\n        public static async Task InvokeUIActionOnSafeThread(\n            Action action, \n            SynchronizationContext? synchronizationContext, \n            CancellationToken cancellationToken)\n        {\n            if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA)\n            {\n                await invokeWithinMtaThread(action, synchronizationContext, cancellationToken);\n            }\n            else\n            {\n                action.Invoke();\n            }\n        }\n\n        private static async Task invokeWithinMtaThread(\n            Action action, \n            SynchronizationContext? synchronizationContext,\n            CancellationToken cancellationToken)\n        {\n            if (synchronizationContext != null)\n            {\n                var actionWithTcs = new Action<object?>((tcs) =>\n                {\n                    try\n                    {\n                        action.Invoke();\n                        ((TaskCompletionSource<object?>)tcs!).TrySetResult(null);\n                    }\n                    catch (Exception e)\n                    {\n                        // Need to catch the exception here and put on the TCS which is the task we are waiting on so that\n                        // the exception comming out of Authenticate is correctly thrown.\n                        ((TaskCompletionSource<object>)tcs!).TrySetException(e);\n                    }\n                });\n\n                var tcs2 = new TaskCompletionSource<object?>();\n\n                synchronizationContext.Post(\n                    new SendOrPostCallback(actionWithTcs), tcs2);\n                await tcs2.Task.ConfigureAwait(false);\n            }\n            else\n            {\n                using (var staTaskScheduler = new StaTaskScheduler(1))\n                {\n                    try\n                    {\n                        Task.Factory.StartNew(\n                            action,\n                            cancellationToken,\n                            TaskCreationOptions.None,\n                            staTaskScheduler).Wait();\n                    }\n                    catch (AggregateException ae)\n                    {\n                        // Any exception thrown as a result of running task will cause AggregateException to be thrown with\n                        // actual exception as inner.\n                        Exception innerException = ae.InnerExceptions[0];\n\n                        // In MTA case, AggregateException is two layer deep, so checking the InnerException for that.\n                        if (innerException is AggregateException)\n                        {\n                            innerException = ((AggregateException)innerException).InnerExceptions[0];\n                        }\n\n                        throw innerException;\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/Platforms/WinForm/WebView2WebUI.cs",
    "content": "﻿using Microsoft.Web.WebView2.Core;\nusing XboxAuthNet.OAuth.CodeFlow;\n\nnamespace XboxAuthNet.Platforms.WinForm;\n\n#if NET5_WIN\n[System.Runtime.Versioning.SupportedOSPlatform(\"windows7\")]\n#endif\ninternal class WebView2WebUI : IWebUI\n{\n    private readonly object? _parent;\n    private readonly SynchronizationContext? _synchronizationContext;\n\n    public WebView2WebUI(WebUIOptions options)\n    {\n        _parent = options.ParentObject;\n        _synchronizationContext = options.SynchronizationContext;\n    }\n\n    public async Task<CodeFlowAuthorizationResult> DisplayDialogAndInterceptUri(\n        Uri uri,\n        ICodeFlowUrlChecker uriChecker,\n        CancellationToken cancellationToken)\n    {\n        WpfWindowWrapper window = new WpfWindowWrapper();\n        CodeFlowAuthorizationResult result = new CodeFlowAuthorizationResult();\n        await UIThreadHelper.InvokeUIActionOnSafeThread(() =>\n        {\n            using (var form = new WinFormsPanelWithWebView2(window.Handle))\n            {\n                result = form.DisplayDialogAndInterceptUri(uri, uriChecker, cancellationToken);\n            }\n        }, _synchronizationContext, cancellationToken);\n        return result;\n    }\n\n    public async Task DisplayDialogAndNavigateUri(Uri uri, CancellationToken cancellationToken)\n    {\n        WpfWindowWrapper window = new WpfWindowWrapper();\n        await UIThreadHelper.InvokeUIActionOnSafeThread(() =>\n        {\n            using (var form = new WinFormsPanelWithWebView2(window.Handle))\n            {\n                form.DisplayDialogAndNavigateUri(uri, cancellationToken);\n            }\n        }, _synchronizationContext, cancellationToken);\n    }\n\n    public static bool IsWebView2Available()\n    {\n        try\n        {\n            string wv2Version = CoreWebView2Environment.GetAvailableBrowserVersionString();\n            return !string.IsNullOrEmpty(wv2Version);\n        }\n        catch (WebView2RuntimeNotFoundException)\n        {\n            return false;\n        }\n        catch (Exception ex) when (ex is BadImageFormatException || ex is DllNotFoundException)\n        {\n            return false;\n            //throw new MsalClientException(MsalError.WebView2LoaderNotFound, MsalErrorMessage.WebView2LoaderNotFound, ex);\n        }\n    }\n    public class WpfWindowWrapper : System.Windows.Forms.IWin32Window\n{\n    public WpfWindowWrapper()\n    {\n        Handle = new System.Windows.Interop.WindowInteropHelper(System.Windows.Application.Current.MainWindow).Handle;\n    }\n    public IntPtr Handle { get; private set; }\n}\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/Platforms/WinForm/Win32Window.cs",
    "content": "﻿// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/main/src/client/Microsoft.Identity.Client/Platforms/Features/WebView2WebUi/Win32Window.cs\n// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System;\nusing System.Windows.Forms;\n\nnamespace Microsoft.Identity.Client.Platforms.Features.WebView2WebUi\n{\n    internal class Win32Window : IWin32Window\n    {\n        public Win32Window(IntPtr handle)\n        {\n            Handle = handle;\n        }\n        public IntPtr Handle { get; }\n\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/Platforms/WinForm/WinFormsPanelWithWebView2.cs",
    "content": "﻿// This code is from MSAL.NET\n// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/main/src/client/Microsoft.Identity.Client/Platforms/Features/WebView2WebUi/WinFormsPanelWithWebView2.cs\n// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing Microsoft.Identity.Client.Platforms.Features.DesktopOs;\nusing Microsoft.Identity.Client.Platforms.Features.WebView2WebUi;\nusing Microsoft.Web.WebView2.Core;\nusing Microsoft.Web.WebView2.WinForms;\nusing XboxAuthNet.OAuth.CodeFlow;\nusing Application = System.Windows.Forms.Application;\n\nnamespace XboxAuthNet.Platforms.WinForm;\n\ninternal class WinFormsPanelWithWebView2 : Form\n{\n    private const int UIWidth = 566;\n    private WebView2 _webView2;\n    private const string WebView2UserDataFolder = \"%UserProfile%/.msal/webview2/data\";\n\n    private ICodeFlowUrlChecker? _uriChecker;\n    private CodeFlowAuthorizationResult _authCode;\n    private IWin32Window? _ownerWindow;\n\n    public WinFormsPanelWithWebView2(\n        object? ownerWindow)\n    {\n        if (ownerWindow == null)\n        {\n            _ownerWindow = null;\n        }\n        else if (ownerWindow is IWin32Window)\n        {\n            _ownerWindow = (IWin32Window)ownerWindow;\n        }\n        else if (ownerWindow is IntPtr ptr && ptr != IntPtr.Zero)\n        {\n            _ownerWindow = new Win32Window(ptr);\n        }\n        else\n        {\n            throw new ArgumentException(\"Invalid owner window type. Expected types are IWin32Window or IntPtr (for window handle).\");\n        }\n\n        InitializeComponent();\n\n        CoreWebView2Environment.GetAvailableBrowserVersionString();\n        _webView2!.CreationProperties = new CoreWebView2CreationProperties()\n        {\n            UserDataFolder = Environment.ExpandEnvironmentVariables(WebView2UserDataFolder)\n        };\n    }\n\n    public CodeFlowAuthorizationResult DisplayDialogAndInterceptUri(\n        Uri uri, ICodeFlowUrlChecker uriChecker, CancellationToken cancellationToken)\n    {\n        this._uriChecker = uriChecker;\n\n        _webView2.CoreWebView2InitializationCompleted += WebView2Control_CoreWebView2InitializationCompleted;\n        _webView2.NavigationStarting += WebView2Control_NavigationStarting;\n\n        // Starts the navigation\n        _webView2.Source = uri;\n        DisplayDialog(cancellationToken);\n\n        if (_authCode.IsEmpty)\n            throw new InvalidOperationException(\"_authCode was empty\");\n        return _authCode;\n    }\n\n    public void DisplayDialogAndNavigateUri(Uri uri, CancellationToken cancellationToken)\n    {\n        _webView2.CoreWebView2InitializationCompleted += WebView2Control_CoreWebView2InitializationCompleted;\n\n        // Starts the navigation\n        _webView2.Source = uri;\n\n        using (cancellationToken.Register(CloseIfOpen))\n        {\n            InvokeHandlingOwnerWindow(() => ShowDialog(_ownerWindow));\n            cancellationToken.ThrowIfCancellationRequested();\n        }\n    }\n\n    private void DisplayDialog(CancellationToken cancellationToken)\n    {\n        DialogResult uiResult = DialogResult.None;\n\n        using (cancellationToken.Register(CloseIfOpen))\n        {\n            InvokeHandlingOwnerWindow(() => uiResult = ShowDialog(_ownerWindow));\n            cancellationToken.ThrowIfCancellationRequested();\n        }\n\n        switch (uiResult)\n        {\n            case DialogResult.OK:\n                break;\n            case DialogResult.Cancel:\n                throw new AuthCodeException(null, \"User canceled authentication. \");\n            default:\n                throw new InvalidOperationException(\n                    \"WebView2 returned an unexpected result: \" + uiResult);\n        }\n    }\n\n    private void CloseIfOpen()\n    {\n        if (Application.OpenForms.OfType<WinFormsPanelWithWebView2>().Any())\n        {\n            InvokeOnly(Close);\n        }\n    }\n\n    private void PlaceOnTop(object? sender, EventArgs e)\n    {\n        // If we don't have an owner we need to make sure that the pop up browser\n        // window is on top of other windows.  Activating the window will accomplish this.\n        if (null == Owner)\n        {\n            Activate();\n        }\n    }\n\n    /// <summary>\n    /// Some calls need to be made on the UI thread and this is the central place to check if we have an owner\n    /// window and if so, ensure we invoke on that proper thread.\n    /// </summary>\n    /// <param name=\"action\"></param>\n    private void InvokeHandlingOwnerWindow(Action action)\n    {\n        // We only support WindowsForms (since our dialog is Win Forms based)\n        if (_ownerWindow != null && _ownerWindow is Control winFormsControl)\n        {\n            winFormsControl.Invoke(action);\n        }\n        else\n        {\n            action();\n        }\n    }\n\n    /// <summary>\n    /// Some calls need to be made on the UI thread and this is the central place to do so and if so, ensure we invoke on that proper thread.\n    /// </summary>\n    /// <param name=\"action\"></param>\n    private void InvokeOnly(Action action)\n    {\n        if (InvokeRequired)\n        {\n            this.Invoke(action);\n        }\n        else\n        {\n            action();\n        }\n    }\n\n    private void InitializeComponent()\n    {\n        InvokeHandlingOwnerWindow(() =>\n        {\n            Screen screen = (_ownerWindow != null)\n                ? Screen.FromHandle(_ownerWindow.Handle)\n                : Screen.PrimaryScreen;\n\n            // Window height is set to 70% of the screen height.\n            int uiHeight = (int)(Math.Max(screen.WorkingArea.Height, 160) * 70.0 / WindowsDpiHelper.ZoomPercent);\n            var webBrowserPanel = new Panel();\n            webBrowserPanel.SuspendLayout();\n            SuspendLayout();\n\n            // webBrowser\n            _webView2 = new WebView2();\n            _webView2.Dock = DockStyle.Fill;\n            _webView2.Location = new System.Drawing.Point(0, 25);\n            _webView2.MinimumSize = new System.Drawing.Size(20, 20);\n            _webView2.Name = \"WebView2\";\n            _webView2.Size = new System.Drawing.Size(UIWidth, 565);\n            _webView2.TabIndex = 1;\n\n            // webBrowserPanel\n            webBrowserPanel.Controls.Add(_webView2);\n            webBrowserPanel.Dock = DockStyle.Fill;\n            webBrowserPanel.BorderStyle = BorderStyle.None;\n            webBrowserPanel.Location = new System.Drawing.Point(0, 0);\n            webBrowserPanel.Name = \"webBrowserPanel\";\n            webBrowserPanel.Size = new System.Drawing.Size(UIWidth, uiHeight);\n            webBrowserPanel.TabIndex = 2;\n\n            // BrowserAuthenticationWindow\n            AutoScaleDimensions = new SizeF(6, 13);\n            AutoScaleMode = AutoScaleMode.Font;\n            ClientSize = new System.Drawing.Size(UIWidth, uiHeight);\n            Controls.Add(webBrowserPanel);\n            FormBorderStyle = FormBorderStyle.FixedSingle;\n            Name = \"BrowserAuthenticationWindow\";\n\n            // Move the window to the center of the parent window only if owner window is set.\n            StartPosition = (_ownerWindow != null)\n                ? FormStartPosition.CenterParent\n                : FormStartPosition.CenterScreen;\n            Text = string.Empty;\n            ShowIcon = false;\n            MaximizeBox = false;\n            MinimizeBox = false;\n\n            // If we don't have an owner we need to make sure that the pop up browser\n            // window is in the task bar so that it can be selected with the mouse.\n            ShowInTaskbar = null == _ownerWindow;\n\n            webBrowserPanel.ResumeLayout(false);\n            ResumeLayout(false);\n        });\n\n        this.Shown += PlaceOnTop;\n    }\n\n    private void WebView2Control_NavigationStarting(object? sender, CoreWebView2NavigationStartingEventArgs e)\n    {\n        if (CheckForEndUrl(new Uri(e.Uri)))\n        {\n            // _logger.Verbose(\"[WebView2Control] Redirect URI reached. Stopping the interactive view\");\n            e.Cancel = true;\n        }\n        else\n        {\n            // _logger.Verbose(\"[WebView2Control] Navigating to \" + e.Uri);\n        }\n    }\n\n    private bool CheckForEndUrl(Uri url)\n    {\n        if (_uriChecker == null)\n            throw new InvalidOperationException(\"_uriChecker was null\");\n\n        var result = _uriChecker.GetAuthCodeResult(url);\n\n        if (!result.IsEmpty)\n        {\n            // This should close the dialog\n            DialogResult = DialogResult.OK;\n            _authCode = result;\n            _uriChecker = null;\n        }\n\n        return !result.IsEmpty;\n    }\n\n    private void WebView2Control_CoreWebView2InitializationCompleted(object? sender, CoreWebView2InitializationCompletedEventArgs e)\n    {\n        //_logger.Verbose(\"[WebView2Control] CoreWebView2InitializationCompleted \");\n        _webView2.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;\n        _webView2.CoreWebView2.Settings.AreDevToolsEnabled = false;\n        _webView2.CoreWebView2.Settings.AreHostObjectsAllowed = false;\n        _webView2.CoreWebView2.Settings.IsScriptEnabled = true;\n        _webView2.CoreWebView2.Settings.IsZoomControlEnabled = false;\n        _webView2.CoreWebView2.Settings.IsStatusBarEnabled = true;\n        _webView2.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = false;\n\n        _webView2.CoreWebView2.DocumentTitleChanged += CoreWebView2_DocumentTitleChanged;\n    }\n\n    private void CoreWebView2_DocumentTitleChanged(object? sender, object e)\n    {\n        Text = _webView2.CoreWebView2.DocumentTitle ?? \"\";\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/Platforms/WinForm/WindowsDpiHelper.cs",
    "content": "﻿// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/main/src/client/Microsoft.Identity.Client/Platforms/Features/DesktopOS/WindowsNativeDpiHelper.cs\n// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System;\nusing System.Runtime.InteropServices;\n\nnamespace Microsoft.Identity.Client.Platforms.Features.DesktopOs\n{\n    internal static class WindowsDpiHelper\n    {\n        static WindowsDpiHelper()\n        {\n            const double DefaultDpi = 96.0;\n\n            const int LOGPIXELSX = 88;\n            const int LOGPIXELSY = 90;\n\n            double deviceDpiX;\n            double deviceDpiY;\n\n            IntPtr dC = GetDC(IntPtr.Zero);\n            if (dC != IntPtr.Zero)\n            {\n                deviceDpiX = GetDeviceCaps(dC, LOGPIXELSX);\n                deviceDpiY = GetDeviceCaps(dC, LOGPIXELSY);\n                ReleaseDC(IntPtr.Zero, dC);\n            }\n            else\n            {\n                deviceDpiX = DefaultDpi;\n                deviceDpiY = DefaultDpi;\n            }\n\n            int zoomPercentX = (int)(100 * (deviceDpiX / DefaultDpi));\n            int zoomPercentY = (int)(100 * (deviceDpiY / DefaultDpi));\n\n            ZoomPercent = Math.Min(zoomPercentX, zoomPercentY);\n        }\n\n        /// <summary>\n        /// </summary>\n        public static int ZoomPercent { get; }\n\n        [DllImport(\"User32.dll\", CallingConvention = CallingConvention.StdCall, ExactSpelling = true)]\n        internal static extern IntPtr GetDC(IntPtr hWnd);\n\n        [DllImport(\"User32.dll\", CallingConvention = CallingConvention.StdCall, ExactSpelling = true)]\n        internal static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);\n\n        [DllImport(\"Gdi32.dll\", CallingConvention = CallingConvention.StdCall, ExactSpelling = true)]\n        internal static extern int GetDeviceCaps(IntPtr hdc, int nIndex);\n\n        [DllImport(\"User32.dll\", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]\n        internal static extern bool IsProcessDPIAware();\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxAuthNet.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFrameworkNetStandard>netstandard2.0</TargetFrameworkNetStandard>\n        <TargetFrameworkNetFramework>net472</TargetFrameworkNetFramework>\n        <TargetFrameworkNet5Win>net5.0-windows</TargetFrameworkNet5Win>\n        <TargetFrameworks>$(TargetFrameworkNet5Win);$(TargetFrameworkNetStandard);$(TargetFrameworkNetFramework)</TargetFrameworks>\n\n        <EnableWindowsTargeting>true</EnableWindowsTargeting>\n    </PropertyGroup>\n\n    <PropertyGroup>\n        <LangVersion>10.0</LangVersion>\n        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n        <Nullable>enable</Nullable>\n        <GeneratePackageOnBuild>true</GeneratePackageOnBuild>\n        <ImplicitUsings>enable</ImplicitUsings>\n        <Version>3.0.2</Version>\n\n        <Description>Xbox Live authentication for .NET</Description>\n        <Copyright>MIT</Copyright>\n        <PackageLicenseExpression>MIT</PackageLicenseExpression>\n        <PackageProjectUrl>https://github.com/AlphaBs/XboxAuthNet</PackageProjectUrl>\n        <RepositoryUrl>https://github.com/AlphaBs/XboxAuthNet</RepositoryUrl>\n        <RepositoryType>git</RepositoryType>\n        <Authors>ksi123456ab</Authors>\n        <PackageTags>xbox live login auth authentication</PackageTags>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <AssemblyAttribute Include=\"System.Runtime.CompilerServices.InternalsVisibleToAttribute\">\n            <_Parameter1>XboxAuthNet.Test</_Parameter1>\n        </AssemblyAttribute>\n        <PackageReference Include=\"ConfigureAwait.Fody\" Version=\"3.3.2\" PrivateAssets=\"All\" />\n        <PackageReference Include=\"Fody\" Version=\"6.7.0\">\n            <PrivateAssets>all</PrivateAssets>\n            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n        </PackageReference>\n        <PackageReference Include=\"System.Net.Http.Json\" Version=\"6.0.1\" />\n        <PackageReference Include=\"System.Text.Json\" Version=\"6.0.8\" />\n    </ItemGroup>\n\n    <PropertyGroup>\n        <EnableDefaultCompileItems>false</EnableDefaultCompileItems>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <None Include=\"**/*.cs\" Exclude=\"obj/**/*.*;bin/**/*.*\" />\n        <Compile Include=\"**/*.cs\" Exclude=\"obj/**/*.*\" />\n        <Compile Remove=\"Platforms/**/*\" />\n    </ItemGroup>\n\n    <PropertyGroup Condition=\"'$(TargetFramework)' == '$(TargetFrameworkNetFramework)'\">\n        <UseWindowsForms>true</UseWindowsForms>\n        <DefineConstants>$(DefineConstants);ENABLE_WEBVIEW2</DefineConstants>\n    </PropertyGroup>\n\n    <ItemGroup Condition=\"'$(TargetFramework)' == '$(TargetFrameworkNetFramework)'\">\n        <Compile Include=\"Platforms/WinForm/**/*\" />\n        <PackageReference Include=\"Microsoft.Web.WebView2\" Version=\"1.0.1823.32\" />\n        <Reference Include=\"System.Web\" />\n    </ItemGroup>\n\n    <PropertyGroup Condition=\"'$(TargetFramework)' == '$(TargetFrameworkNet5Win)'\">\n        <UseWindowsForms>true</UseWindowsForms>\n        <DefineConstants>$(DefineConstants);ENABLE_WEBVIEW2;NET5_WIN</DefineConstants>\n    </PropertyGroup>\n\n    <ItemGroup Condition=\"'$(TargetFramework)' == '$(TargetFrameworkNet5Win)'\">\n        <Compile Include=\"Platforms/WinForm/**/*\" />\n        <PackageReference Include=\"Microsoft.Web.WebView2\" Version=\"1.0.1823.32\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <None Remove=\"XboxLive\\Requests\\CommonRequestHeaders.cs\" />\n      <None Remove=\"XboxLive\\XboxSignedClient.cs\" />\n    </ItemGroup>\n</Project>\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Crypto/ECDCertificatePopCryptoProvider.cs",
    "content": "using System.Security.Cryptography;\n\nnamespace XboxAuthNet.XboxLive.Crypto\n{\n    // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/main/tests/Microsoft.Identity.Test.Common/Core/Helpers/ECDCertificatePopCryptoProvider.cs#L47\n    public class ECDCertificatePopCryptoProvider : IPopCryptoProvider\n    {\n        private object? _proofKey;\n        public object ProofKey => _proofKey ??= generateNewProofKey();\n\n        private ECDsa _signer;\n\n        public ECDCertificatePopCryptoProvider()\n        {\n            var ecCurve = ECCurve.NamedCurves.nistP256;\n            _signer = ECDsa.Create(ecCurve);\n        }\n\n        private object generateNewProofKey()\n        {\n            var parameters = _signer.ExportParameters(false);\n            return new\n            {\n                kty = \"EC\",\n                x = parameters.Q.X != null ? Base64UrlHelper.Encode(parameters.Q.X) : null,\n                y = parameters.Q.Y != null ? Base64UrlHelper.Encode(parameters.Q.Y) : null,\n                crv = \"P-256\",\n                alg = \"ES256\",\n                use = \"sig\"\n            };\n        }\n\n        public byte[] Sign(byte[] data)\n        {\n            return _signer.SignData(data, HashAlgorithmName.SHA256);\n        }\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Crypto/IPopCryptoProvider.cs",
    "content": "namespace XboxAuthNet.XboxLive.Crypto\n{\n    public interface IPopCryptoProvider\n    {\n        object ProofKey { get; }\n        byte[] Sign(byte[] data);\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Crypto/IXboxRequestSigner.cs",
    "content": "﻿namespace XboxAuthNet.XboxLive.Crypto\n{\n    public interface IXboxRequestSigner\n    {\n        object ProofKey { get; }\n        string SignRequest(string reqUri, string token, string body);\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Crypto/XboxRequestSigner.cs",
    "content": "using System;\nusing System.Text;\n\nnamespace XboxAuthNet.XboxLive.Crypto\n{\n    public class XboxRequestSigner : IXboxRequestSigner\n    {\n        private readonly IPopCryptoProvider _signer;\n\n        public XboxRequestSigner(IPopCryptoProvider signer)\n        {\n            this._signer = signer;\n        }\n\n        public object ProofKey => _signer.ProofKey;\n\n        public string SignRequest(string reqUri, string token, string body)\n        {\n            var timestamp = getWindowsTimestamp();\n            var data = generatePayload(timestamp, reqUri, token, body);\n            var signature = sign(timestamp, data);\n            return Convert.ToBase64String(signature);\n        }\n\n        private byte[] generatePayload(ulong windowsTimestamp, string uri, string token, string payload)\n        {\n            var pathAndQuery = new Uri(uri).PathAndQuery;\n\n            var allocSize =\n                4 + 1 +\n                8 + 1 +\n                4 + 1 +\n                pathAndQuery.Length + 1 +\n                token.Length + 1 +\n                payload.Length + 1;\n            var bytes = new byte[allocSize];\n\n            var policyVersion = BitConverter.GetBytes((int)1);\n            if (BitConverter.IsLittleEndian)\n                Array.Reverse(policyVersion);\n            Array.Copy(policyVersion, 0, bytes, 0, 4);\n\n            var windowsTimestampBytes = BitConverter.GetBytes(windowsTimestamp);\n            if (BitConverter.IsLittleEndian)\n                Array.Reverse(windowsTimestampBytes);\n            Array.Copy(windowsTimestampBytes, 0, bytes, 5, 8);\n\n            var strs =\n                $\"POST\\0\" +\n                $\"{pathAndQuery}\\0\" +\n                $\"{token}\\0\" +\n                $\"{payload}\\0\";\n            var strsBytes = Encoding.ASCII.GetBytes(strs);\n            Array.Copy(strsBytes, 0, bytes, 14, strsBytes.Length);\n\n            return bytes;\n        }\n\n        private byte[] sign(ulong windowsTimestamp, byte[] bytes)\n        {\n            var signature = _signer.Sign(bytes);\n\n            var policyVersion = BitConverter.GetBytes((int)1);\n            if (BitConverter.IsLittleEndian)\n                Array.Reverse(policyVersion);\n\n            var windowsTimestampBytes = BitConverter.GetBytes(windowsTimestamp);\n            if (BitConverter.IsLittleEndian)\n                Array.Reverse(windowsTimestampBytes);\n\n            var header = new byte[signature.Length + 12];\n            Array.Copy(policyVersion, 0, header, 0, 4);\n            Array.Copy(windowsTimestampBytes, 0, header, 4, 8);\n            Array.Copy(signature, 0, header, 12, signature.Length);\n\n            return header;\n        }\n\n        private ulong getWindowsTimestamp()\n        {\n            var unixTimestamp = (ulong)DateTimeOffset.UtcNow.ToUnixTimeSeconds();\n            ulong windowsTimestamp = (unixTimestamp + 11644473600u) * 10000000u;\n            return windowsTimestamp;\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Requests/AbstractXboxAuthRequest.cs",
    "content": "using System.Net.Http;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XboxAuthNet.XboxLive.Requests;\n\npublic abstract class AbstractXboxAuthRequest\n{\n    public XboxAuthResponseHandler? ResponseHandler { get; set; } = new();\n    public string? ContractVersion { get; set; }\n\n    protected abstract HttpRequestMessage BuildRequest();\n\n    public async Task<T> Send<T>(HttpClient httpClient)\n    {\n        if (ResponseHandler == null)\n            throw new InvalidOperationException(\"ResponseHandler was null\");\n\n        var request = BuildRequest();\n        request.Headers.Add(\"x-xbl-contract-version\", ContractVersion ?? \"\");\n\n        var response = await httpClient.SendAsync(request);\n        return await ResponseHandler.HandleResponse<T>(response);\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Requests/AbstractXboxSignedAuthRequest.cs",
    "content": "using System.Net.Http;\nusing System.Text;\nusing System.Text.Json;\nusing XboxAuthNet.XboxLive.Crypto;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XboxAuthNet.XboxLive.Requests;\n\npublic abstract class AbstractXboxSignedAuthRequest\n{\n    public XboxAuthResponseHandler ResponseHandler { get; set; } = new();\n    protected abstract string RequestUrl { get; }\n    protected virtual string Token { get; } = \"\";\n\n    public async Task<T> Send<T>(HttpClient httpClient, IXboxRequestSigner signer)\n    {\n        if (ResponseHandler == null)\n            throw new InvalidOperationException(\"ResponseHandler was null\");\n\n        var request = buildRequest(signer);\n        var response = await httpClient.SendAsync(request);\n        return await ResponseHandler.HandleResponse<T>(response);\n    }\n\n    private HttpRequestMessage buildRequest(IXboxRequestSigner signer)\n    {\n        var body = BuildBody(signer.ProofKey);\n        var bodyStr = JsonSerializer.Serialize(body);\n\n        var req = new HttpRequestMessage\n        {\n            RequestUri = new Uri(RequestUrl),\n            Method = HttpMethod.Post,\n            Content = new StringContent(bodyStr, Encoding.UTF8, \"application/json\")\n        };\n\n        var signature = signer.SignRequest(RequestUrl, Token, bodyStr);\n        req.Headers.Add(\"Signature\", signature);\n        CommonRequestHeaders.AddDefaultHeaders(req);\n        return req;\n    }\n\n    protected abstract object BuildBody(object proofKey);\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Requests/CommonRequestHeaders.cs",
    "content": "﻿using System.Net.Http;\n\nnamespace XboxAuthNet.XboxLive.Requests;\n\ninternal class CommonRequestHeaders\n{\n    public static void AddDefaultHeaders(HttpRequestMessage request)\n    {\n        request.Headers.Add(\"Accept\", \"application/json\");\n        request.Headers.TryAddWithoutValidation(\"User-Agent\", HttpHelper.UserAgent);\n        request.Headers.Add(\"Accept-Language\", \"en-US\");\n        request.Headers.Add(\"Cache-Control\", \"no-store, must-revalidate, no-cache\");\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Requests/XboxDeviceTokenRequest.cs",
    "content": "using System;\nusing System.Net.Http;\nusing System.Threading.Tasks;\nusing XboxAuthNet.XboxLive.Crypto;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XboxAuthNet.XboxLive.Requests\n{\n    public class XboxDeviceTokenRequest : AbstractXboxSignedAuthRequest\n    {\n        public string? Id { get; set; }\n        public string? SerialNumber { get; set; }\n        public string? DeviceType { get; set; }\n        public string? DeviceVersion { get; set; }\n        public string? RelyingParty { get; set; } = XboxAuthConstants.XboxAuthRelyingParty;\n\n        protected override string RequestUrl => \"https://device.auth.xboxlive.com/device/authenticate\";\n        protected override object BuildBody(object proofKey)\n        {\n            if (string.IsNullOrEmpty(DeviceType))\n                throw new InvalidOperationException(\"DeviceType was null\");\n            if (string.IsNullOrEmpty(DeviceVersion))\n                throw new InvalidOperationException(\"DeviceVersion was null\");\n            if (string.IsNullOrEmpty(RelyingParty))\n                throw new InvalidOperationException(\"RelyingParty was null\");\n\n            var id = this.Id ?? nextUUID();\n            var serialNumber = this.SerialNumber ?? nextUUID();\n\n            return new\n            {\n                Properties = new\n                {\n                    AuthMethod = \"ProofOfPossession\",\n                    Id = \"{\" + id + \"}\",\n                    DeviceType = DeviceType,\n                    SerialNumber = \"{\" + serialNumber + \"}\",\n                    Version = DeviceVersion,\n                    ProofKey = proofKey\n                },\n                RelyingParty = RelyingParty,\n                TokenType = \"JWT\"\n            };\n        }\n\n        private string nextUUID()\n        {\n            return Guid.NewGuid().ToString();\n        }\n\n        public Task<XboxAuthResponse> Send(HttpClient httpClient, IXboxRequestSigner signer)\n        {\n            return Send<XboxAuthResponse>(httpClient, signer);\n        }\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Requests/XboxSignedUserTokenRequest.cs",
    "content": "using System;\nusing System.Net.Http;\nusing System.Threading.Tasks;\nusing XboxAuthNet.XboxLive.Crypto;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XboxAuthNet.XboxLive.Requests\n{\n    public class XboxSignedUserTokenRequest : AbstractXboxSignedAuthRequest\n    {\n        public string? RelyingParty { get; set; } = XboxAuthConstants.XboxAuthRelyingParty;\n        public string? TokenPrefix { get; set; } = XboxAuthConstants.XboxTokenPrefix;\n        public string? AccessToken { get; set; }\n\n        protected override string RequestUrl => \"https://user.auth.xboxlive.com/user/authenticate\";\n        protected override object BuildBody(object proofKey)\n        {\n            if (string.IsNullOrEmpty(AccessToken))\n                throw new InvalidOperationException(\"AccessToken was null\");\n            if (string.IsNullOrEmpty(RelyingParty))\n                throw new InvalidOperationException(\"RelyingParty was null\");\n\n            return new\n            {\n                RelyingParty = RelyingParty,\n                TokenType = \"JWT\",\n                Properties = new\n                {\n                    AuthMethod = \"RPS\",\n                    SiteName = \"user.auth.xboxlive.com\",\n                    RpsTicket = TokenPrefix + AccessToken\n                } \n            };\n        }\n\n        public Task<XboxAuthResponse> Send(HttpClient httpClient, IXboxRequestSigner signer)\n        {\n            return Send<XboxAuthResponse>(httpClient, signer);\n        }\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Requests/XboxSignedXstsRequest.cs",
    "content": "using System.Threading.Tasks;\nusing System.Net.Http;\nusing XboxAuthNet.XboxLive.Crypto;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XboxAuthNet.XboxLive.Requests\n{\n    public class XboxSignedXstsRequest : AbstractXboxSignedAuthRequest\n    {\n        public string? UserToken { get; set; }\n        public string? DeviceToken { get; set; }\n        public string? TitleToken { get; set; }\n        public string? RelyingParty { get; set; } = XboxAuthConstants.XboxLiveRelyingParty;\n        public string[]? OptionalDisplayClaims { get; set; }\n\n        protected override string RequestUrl => \"https://xsts.auth.xboxlive.com/xsts/authorize\";\n\n        protected override object BuildBody(object proofKey)\n        {\n            if (string.IsNullOrEmpty(UserToken))\n                throw new InvalidOperationException(\"UserToken was null\");\n            if (string.IsNullOrEmpty(RelyingParty))\n                throw new InvalidOperationException(\"RelyingParty was null\");\n\n            return new\n            {\n                RelyingParty = RelyingParty,\n                TokenType = \"JWT\",\n                Properties = new\n                {\n                    UserTokens = new string[] { UserToken },\n                    DeviceToken = DeviceToken,\n                    TitleToken = TitleToken,\n                    OptionalDisplayClaims = OptionalDisplayClaims,\n                    SandboxId = \"RETAIL\",\n                    ProofKey = proofKey\n                }\n            };\n        }\n\n        public Task<XboxAuthResponse> Send(HttpClient httpClient, IXboxRequestSigner signer)\n        {\n            return Send<XboxAuthResponse>(httpClient, signer);\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Requests/XboxSisuAuthRequest.cs",
    "content": "using System;\nusing System.Threading.Tasks;\nusing System.Net.Http;\nusing XboxAuthNet.XboxLive.Responses;\nusing XboxAuthNet.XboxLive.Crypto;\n\nnamespace XboxAuthNet.XboxLive.Requests\n{\n    public class XboxSisuAuthRequest : AbstractXboxSignedAuthRequest\n    {\n        public string? TokenPrefix { get; set; } = XboxAuthConstants.XboxTokenPrefix;\n        public string? AccessToken { get; set; }\n        public string? RelyingParty { get; set; } = XboxAuthConstants.XboxLiveRelyingParty;\n        public string? ClientId { get; set; }\n        public string? DeviceToken { get; set; }\n\n        protected override string RequestUrl => \"https://sisu.xboxlive.com/authorize\";\n        protected override object BuildBody(object proofKey)\n        { \n            if (string.IsNullOrEmpty(AccessToken))\n                throw new InvalidOperationException(\"AccessToken was null\");\n            if (string.IsNullOrEmpty(RelyingParty))\n                throw new InvalidOperationException(\"RelyingParty was null\");\n                \n            return new\n            {\n                AccessToken = TokenPrefix + AccessToken,\n                AppId = ClientId,\n                DeviceToken = DeviceToken,\n                Sandbox = \"RETAIL\",\n                UseModernGamertag = true,\n                SiteName = \"user.auth.xboxlive.com\",\n                RelyingParty = RelyingParty,\n                ProofKey = proofKey\n            };\n        }\n\n        public Task<XboxSisuResponse> Send(HttpClient httpClient, IXboxRequestSigner signer)\n        {\n            return Send<XboxSisuResponse>(httpClient, signer);\n        }\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Requests/XboxTitleTokenRequest.cs",
    "content": "using System;\nusing System.Net.Http;\nusing System.Threading.Tasks;\nusing XboxAuthNet.XboxLive.Crypto;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XboxAuthNet.XboxLive.Requests\n{\n    public class XboxTitleTokenRequest : AbstractXboxSignedAuthRequest\n    {\n        public string? AccessToken { get; set; }\n        public string? DeviceToken { get; set; }\n        public string? TokenPrefix { get; set; } = XboxAuthConstants.XboxTokenPrefix;\n        public string? RelyingParty { get; set; } = XboxAuthConstants.XboxAuthRelyingParty;\n\n        protected override string RequestUrl => \"https://title.auth.xboxlive.com/title/authenticate\";\n        protected override object BuildBody(object proofKey)\n        {\n            if (string.IsNullOrEmpty(AccessToken))\n                throw new InvalidOperationException(\"AccessToken was null\");\n            if (string.IsNullOrEmpty(RelyingParty))\n                throw new InvalidOperationException(\"RelyingParty was null\");\n                \n            return new \n            {\n                Properties = new\n                {\n                    AuthMethod = \"RPS\",\n                    DeviceToken = DeviceToken,\n                    RpsTicket = TokenPrefix + AccessToken,\n                    SiteName = \"user.auth.xboxlive.com\",\n                    ProofKey = proofKey,\n                },\n                RelyingParty = RelyingParty,\n                TokenType = \"JWT\"\n            };\n        }\n\n        public Task<XboxAuthResponse> Send(HttpClient httpClient, IXboxRequestSigner signer)\n        {\n            return Send<XboxAuthResponse>(httpClient, signer);\n        }\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Requests/XboxUserTokenRequest.cs",
    "content": "using System;\nusing System.Net.Http;\nusing System.Threading.Tasks;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XboxAuthNet.XboxLive.Requests\n{\n    public class XboxUserTokenRequest : AbstractXboxAuthRequest\n    {\n        public const string UserAuthenticateUrl = \"https://user.auth.xboxlive.com/user/authenticate\";\n\n        public XboxUserTokenRequest()\n        {\n            ContractVersion = \"0\";\n            RelyingParty = XboxAuthConstants.XboxAuthRelyingParty;\n        }\n\n        public string? AccessToken { get; set; }\n        public string? RelyingParty { get; set; }\n\n        protected override HttpRequestMessage BuildRequest()\n        {\n            if (string.IsNullOrEmpty(AccessToken))\n                throw new InvalidOperationException(\"AccessToken was null\");\n\n            var req = new HttpRequestMessage\n            {\n                Method = HttpMethod.Post,\n                RequestUri = new Uri(UserAuthenticateUrl),\n                Content = HttpHelper.CreateJsonContent(new\n                {\n                    RelyingParty = RelyingParty,\n                    TokenType = \"JWT\",\n                    Properties = new\n                    {\n                        AuthMethod = \"RPS\",\n                        SiteName = \"user.auth.xboxlive.com\",\n                        RpsTicket = AccessToken\n                    }\n                })\n            };\n\n            CommonRequestHeaders.AddDefaultHeaders(req);\n            return req;\n        }\n\n        public Task<XboxAuthResponse> Send(HttpClient httpClient)\n        {\n            return Send<XboxAuthResponse>(httpClient);\n        }\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Requests/XboxXstsRequest.cs",
    "content": "using System;\nusing System.Net.Http;\nusing System.Threading.Tasks;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XboxAuthNet.XboxLive.Requests\n{\n    public class XboxXstsRequest : AbstractXboxAuthRequest\n    {\n        public const string XstsAuthorizeUrl = \"https://xsts.auth.xboxlive.com/xsts/authorize\";\n\n        public XboxXstsRequest()\n        {\n            RelyingParty = XboxAuthConstants.XboxLiveRelyingParty;\n            ContractVersion = \"1\";\n        }\n\n        public string? UserToken { get; set; }\n        public string? RelyingParty { get; set; }\n        public string? DeviceToken { get; set; }\n        public string? TitleToken { get; set; }\n        public string[]? OptionalDisplayClaims { get; set; }\n\n        protected override HttpRequestMessage BuildRequest()\n        {\n            if (string.IsNullOrEmpty(UserToken))\n                throw new InvalidOperationException(\"UserToken was null\");\n            if (string.IsNullOrEmpty(RelyingParty))\n                throw new InvalidOperationException(\"RelyingParty was null\");\n            \n            var req = new HttpRequestMessage\n            {\n                Method = HttpMethod.Post,\n                RequestUri = new Uri(XstsAuthorizeUrl),\n                Content = HttpHelper.CreateJsonContent(new\n                {\n                    RelyingParty = RelyingParty,\n                    TokenType = \"JWT\",\n                    Properties = new\n                    {\n                        UserTokens = new string[] { UserToken },\n                        DeviceToken = DeviceToken,\n                        TitleToken = TitleToken,\n                        OptionalDisplayClaims = OptionalDisplayClaims,\n                        SandboxId = \"RETAIL\"\n                    }\n                }),\n            };\n\n            CommonRequestHeaders.AddDefaultHeaders(req);\n            return req;\n        }\n\n        public Task<XboxAuthResponse> Send(HttpClient httpClient)\n        {\n            return Send<XboxAuthResponse>(httpClient);\n        }\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Responses/XErrJsonConverter.cs",
    "content": "﻿using System;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\n\nnamespace XboxAuthNet.XboxLive.Responses\n{\n    public class XErrJsonConverter : JsonConverter<string>\n    {\n        public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n        {\n            string? value;\n            if (reader.TokenType == JsonTokenType.String)\n                value = reader.GetString();\n            else if (reader.TokenType == JsonTokenType.Number)\n                value = reader.GetInt64().ToString();\n            else\n                throw new JsonException();\n            return ErrorHelper.TryConvertToHexErrorCode(value);\n        }\n\n        public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)\n        {\n            writer.WriteStringValue(value);\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Responses/XboxAuthResponse.cs",
    "content": "﻿using System;\nusing System.Text.Json.Serialization;\n\nnamespace XboxAuthNet.XboxLive.Responses\n{\n    public class XboxAuthResponse\n    {\n        [JsonPropertyName(\"DisplayClaims\")]\n        [JsonConverter(typeof(XboxAuthXuiClaimsJsonConverter))]\n        public XboxAuthXuiClaims? XuiClaims { get; set; }\n\n        [JsonPropertyName(\"IssueInstant\")]\n        public string? IssueInstant { get; set; }\n\n        [JsonPropertyName(\"Token\")]\n        public string? Token { get; set; }\n\n        [JsonPropertyName(\"NotAfter\")]\n        public string? ExpireOn { get; set; }\n\n        /// <summary>\n        /// checks token is not null and not empty, checks token is not expired\n        /// </summary>\n        /// <returns></returns>\n        public bool Validate()\n        {\n            if (string.IsNullOrEmpty(ExpireOn))\n                return false;\n\n            if (DateTime.Parse(ExpireOn) < DateTime.UtcNow)\n                return false;\n\n            if (string.IsNullOrEmpty(Token))\n                return false;\n\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Responses/XboxAuthResponseHandler.cs",
    "content": "using System;\nusing System.Threading.Tasks;\nusing System.Net.Http;\nusing System.Text.Json;\n\nnamespace XboxAuthNet.XboxLive.Responses\n{\n    public class XboxAuthResponseHandler\n    {\n        public async Task<T> HandleResponse<T>(HttpResponseMessage res)\n        {\n            var resBody = await res.Content.ReadAsStringAsync()\n                .ConfigureAwait(false);\n\n            try\n            {\n                res.EnsureSuccessStatusCode();\n                return JsonSerializer.Deserialize<T>(resBody)\n                    ?? throw new JsonException();\n            }\n            catch (Exception ex) when (\n                ex is JsonException ||\n                ex is HttpRequestException)\n            {\n                try\n                {\n                    throw XboxAuthException.FromResponseBody(resBody, (int)res.StatusCode);\n                }\n                catch (FormatException)\n                {\n                    try\n                    {\n                        throw XboxAuthException.FromResponseHeaders(res.Headers, (int)res.StatusCode);\n                    }\n                    catch (FormatException)\n                    {\n                        throw new XboxAuthException($\"{(int)res.StatusCode}: {res.ReasonPhrase}\", (int)res.StatusCode);\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Responses/XboxAuthXuiClaims.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace XboxAuthNet.XboxLive.Responses\n{\n    // https://github.com/OpenXbox/xbox-webapi-csharp/blob/master/XboxWebApi/Authentication/Model/XboxUserInformation.cs\n    public class XboxAuthXuiClaims\n    {\n        [JsonPropertyName(XboxAuthXuiClaimNames.Gamertag)]\n        public string? Gamertag { get; set; }\n        [JsonPropertyName(\"mgt\")]\n        public string? ModernGamertag { get; set; }\n        [JsonPropertyName(\"umg\")]\n        public string? UniqueModernGamertag { get; set; }\n        [JsonPropertyName(\"mgs\")]\n        public string? ModernGamertagSuffix { get; set; }\n\n        [JsonPropertyName(XboxAuthXuiClaimNames.XboxUserId)]\n        public string? XboxUserId { get; set; }\n\n        [JsonPropertyName(XboxAuthXuiClaimNames.UserHash)]\n        public string? UserHash { get; set; }\n\n        [JsonPropertyName(XboxAuthXuiClaimNames.AgeGroup)]\n        public string? AgeGroup { get; set; }\n\n        [JsonPropertyName(XboxAuthXuiClaimNames.UserSettingsRestrictions)]\n        public string? UserSettingsRestrictions { get; set; }\n\n        [JsonPropertyName(XboxAuthXuiClaimNames.UserTitleRestrictions)]\n        public string? UserTitleRestrictions { get; set; }\n\n        [JsonPropertyName(XboxAuthXuiClaimNames.Privileges)]\n        public string? Privileges { get; set; }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Responses/XboxAuthXuiClaimsJsonConverter.cs",
    "content": "﻿using System;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\n\nnamespace XboxAuthNet.XboxLive.Responses\n{\n    public class XboxAuthXuiClaimsJsonConverter : JsonConverter<XboxAuthXuiClaims>\n    {\n        public override XboxAuthXuiClaims? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n        {\n            if (reader.TokenType != JsonTokenType.StartObject)\n                return null;\n\n            XboxAuthXuiClaims? claims = null;\n\n            while (reader.Read())\n            {\n                if (reader.TokenType == JsonTokenType.EndObject)\n                    break;\n\n                if (reader.TokenType != JsonTokenType.PropertyName)\n                    throw new JsonException();\n\n                var propName = reader.GetString();\n\n                if (propName == \"xui\")\n                {\n                    reader.Read();\n                    claims = readXui(ref reader, options);\n                }\n                else\n                    reader.Skip();\n            }\n\n            return claims;\n        }\n\n        private XboxAuthXuiClaims? readXui(ref Utf8JsonReader reader, JsonSerializerOptions options)\n        {\n            XboxAuthXuiClaims? claims = null;\n\n            if (reader.TokenType == JsonTokenType.StartArray)\n            {\n                while (reader.Read())\n                {\n                    if (reader.TokenType == JsonTokenType.EndArray)\n                        break;\n\n                    if (claims == null && reader.TokenType == JsonTokenType.StartObject)\n                        claims = JsonSerializer.Deserialize<XboxAuthXuiClaims>(ref reader, options);\n                    else\n                        reader.Skip();\n                }\n            }\n            else if (reader.TokenType == JsonTokenType.StartObject)\n            {\n                claims = JsonSerializer.Deserialize<XboxAuthXuiClaims>(ref reader, options);\n            }\n\n            return claims;\n        }\n\n        public override void Write(Utf8JsonWriter writer, XboxAuthXuiClaims value, JsonSerializerOptions options)\n        {\n            writer.WriteStartObject();\n            writer.WritePropertyName(\"xui\");\n            writer.WriteStartArray();\n            JsonSerializer.Serialize(writer, value, options);\n            writer.WriteEndArray();\n            writer.WriteEndObject();\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Responses/XboxErrorResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace XboxAuthNet.XboxLive.Responses\n{\n    public class XboxErrorResponse\n    {\n        [JsonPropertyName(\"XErr\")]\n        [JsonConverter(typeof(XErrJsonConverter))]\n        public string? XErr { get; set; }\n\n        [JsonPropertyName(\"Message\")]\n        public string? Message { get; set; }\n\n        [JsonPropertyName(\"Redirect\")]\n        public string? Redirect { get; set; }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/Responses/XboxSisuResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace XboxAuthNet.XboxLive.Responses\n{\n    public class XboxSisuResponse\n    {\n        [JsonPropertyName(\"DeviceToken\")]\n        public string? DeviceToken { get; set; }\n\n        [JsonPropertyName(\"TitleToken\")]\n        public XboxAuthResponse? TitleToken { get; set; }\n\n        [JsonPropertyName(\"UserToken\")]\n        public XboxAuthResponse? UserToken { get; set; }\n\n        [JsonPropertyName(\"AuthorizationToken\")]\n        public XboxAuthResponse? AuthorizationToken { get; set; }\n\n        [JsonPropertyName(\"WebPage\")]\n        public string? WebPage { get; set; }\n\n        [JsonPropertyName(\"Sandbox\")]\n        public string? Sandbox { get; set; }\n\n        [JsonPropertyName(\"UseModernGamerTag\")]\n        public string? UseModernGamertag { get; set; }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/XboxAuthClient.cs",
    "content": "﻿using System.Net.Http;\nusing XboxAuthNet.XboxLive.Requests;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XboxAuthNet.XboxLive;\n\n// https://github.com/PrismarineJS/prismarine-auth/blob/master/src/TokenManagers/XboxTokenManager.js\npublic class XboxAuthClient\n{\n    private readonly HttpClient _httpClient;\n\n    public XboxAuthClient(HttpClient httpClient) =>\n        _httpClient = httpClient;\n\n    public Task<XboxAuthResponse> RequestUserToken(string rps) =>\n        RequestUserToken(new XboxUserTokenRequest()\n        {\n            AccessToken = rps\n        });\n\n    public Task<XboxAuthResponse> RequestUserToken(XboxUserTokenRequest request) =>\n        request.Send(_httpClient);\n\n    public Task<XboxAuthResponse> RequestXsts(string userToken) =>\n        RequestXsts(new XboxXstsRequest\n        {\n            UserToken = userToken\n        });\n\n    public Task<XboxAuthResponse> RequestXsts(string userToken, string relyingParty) =>\n        RequestXsts(new XboxXstsRequest\n        {\n            UserToken = userToken,\n            RelyingParty = relyingParty\n        });\n\n    public Task<XboxAuthResponse> RequestXsts(XboxXstsRequest request) =>\n        request.Send(_httpClient);\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/XboxAuthConstants.cs",
    "content": "namespace XboxAuthNet.XboxLive\n{\n    public class XboxAuthConstants\n    {\n        public const string XboxScope = \"service::user.auth.xboxlive.com::MBI_SSL\";\n        public const string XboxLiveRelyingParty = \"http://xboxlive.com\";\n        public const string XboxAuthRelyingParty = \"http://auth.xboxlive.com\";\n        public const string XboxEventsRelyingParty = \"http://events.xboxlive.com\";\n        public const string AzureTokenPrefix = \"d=\";\n        public const string XboxTokenPrefix = \"t=\";\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/XboxAuthException.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Net.Http.Headers;\nusing System.Text.Json;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XboxAuthNet.XboxLive\n{\n    public class XboxAuthException : Exception\n    {\n        public XboxAuthException(string message, int statusCode) : base(message) =>\n            StatusCode = statusCode;\n\n        public XboxAuthException(string? error, string? message, string? redirect, int statusCode) : base(CreateMessageFromError(error, message, redirect)) =>\n            (Error, ErrorMessage, Redirect, StatusCode) = (error, message, redirect, statusCode);\n\n        private static string CreateMessageFromError(params string?[] inputs)\n        {\n            return string.Join(\", \", inputs.Where(x => !string.IsNullOrEmpty(x)));\n        }\n\n        public int StatusCode { get; private set; }\n        public string? Error { get; private set; } // refer ErrorCodes.cs\n\n        public string? ErrorMessage { get; private set; }\n\n        public string? Redirect { get; private set; }\n\n        public static XboxAuthException FromResponseBody(string responseBody, int statusCode)\n        {\n            try\n            {\n                var errRes = JsonSerializer.Deserialize<XboxErrorResponse>(responseBody);\n\n                if (string.IsNullOrEmpty(errRes?.XErr) && string.IsNullOrEmpty(errRes?.Message))\n                    throw new FormatException();\n\n                return new XboxAuthException(errRes?.XErr, errRes?.Message, errRes?.Redirect, statusCode);\n            }\n            catch (JsonException)\n            {\n                throw new FormatException();\n            }\n        }\n\n        public static XboxAuthException FromResponseHeaders(HttpResponseHeaders headers, int statusCode)\n        {\n            string? xerr = null;\n            if (headers.TryGetValues(\"X-Err\", out var xerrValues))\n                xerr = xerrValues.FirstOrDefault();\n\n            string? message = null;\n            if (headers.TryGetValues(\"WWW-Authenticate\", out var authValues))\n                message = authValues.FirstOrDefault();\n\n            if (string.IsNullOrEmpty(xerr) && string.IsNullOrEmpty(message))\n                throw new FormatException();\n\n            return new XboxAuthException(ErrorHelper.TryConvertToHexErrorCode(xerr), message, null, statusCode);\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/XboxAuthXuiClaimNames.cs",
    "content": "namespace XboxAuthNet.XboxLive\n{\n    public static class XboxAuthXuiClaimNames\n    {\n        public const string Gamertag = \"gtg\";\n        public const string XboxUserId = \"xid\";\n        public const string UserHash = \"uhs\";\n        public const string AgeGroup = \"agg\";\n        public const string UserSettingsRestrictions = \"usr\";\n        public const string UserTitleRestrictions = \"utr\";\n        public const string Privileges = \"prv\";\n    }\n}"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/XboxDeviceTypes.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace XboxAuthNet.XboxLive\n{\n    public class XboxDeviceTypes\n    {\n        public const string Win32 = \"Win32\";\n        public const string iOS = \"iOS\";\n        public const string Android = \"Android\";\n        public const string Nintendo = \"Nintendo\";\n        public const string WindowsOneCore = \"WindowsOneCore\";\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/XboxGameTitles.cs",
    "content": "﻿namespace XboxAuthNet.XboxLive\n{\n    public static class XboxGameTitles\n    {\n        public const string MinecraftNintendoSwitch = \"00000000441cc96b\";\n        public const string MinecraftJava = \"00000000402b5328\";\n        public const string XboxAppIOS = \"000000004c12ae6f\";\n        public const string XboxGamepassIOS = \"000000004c20a908\";\n        public const string XboxAppPC = \"000000004424da1f\";\n        public const string Solitaire = \"00000000440c1a11\";\n    }\n}\n"
  },
  {
    "path": "XAU/Util/XboxAuthNet/XboxLive/XboxSignedClient.cs",
    "content": "﻿using System.Net.Http;\nusing XboxAuthNet.XboxLive.Crypto;\nusing XboxAuthNet.XboxLive.Requests;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XboxAuthNet.XboxLive;\n\npublic class XboxSignedClient\n{\n    private readonly HttpClient _httpClient;\n    private readonly IXboxRequestSigner _signer;\n\n    public XboxSignedClient(HttpClient httpClient)\n    {\n        _httpClient = httpClient;\n        _signer = new XboxRequestSigner(new ECDCertificatePopCryptoProvider());\n    }\n\n    public XboxSignedClient(IXboxRequestSigner signer, HttpClient httpClient)\n    {\n        _signer = signer;\n        _httpClient = httpClient;\n    }\n\n    public Task<XboxAuthResponse> RequestSignedUserToken(string rps) =>\n        RequestSignedUserToken(new XboxSignedUserTokenRequest()\n        {\n            AccessToken = rps\n        });\n\n    public Task<XboxAuthResponse> RequestSignedUserToken(XboxSignedUserTokenRequest request) =>\n        request.Send(_httpClient, _signer);\n\n    public Task<XboxAuthResponse> RequestDeviceToken(string deivceType, string deviceVersion) =>\n        RequestDeviceToken(new XboxDeviceTokenRequest()\n        {\n            DeviceType = deivceType,\n            DeviceVersion = deviceVersion\n        });\n\n    public Task<XboxAuthResponse> RequestDeviceToken(XboxDeviceTokenRequest request) =>\n        request.Send(_httpClient, _signer);\n\n    public Task<XboxAuthResponse> RequestTitleToken(string accessToken, string deviceToken) =>\n        RequestTitleToken(new XboxTitleTokenRequest()\n        {\n            AccessToken = accessToken,\n            DeviceToken = deviceToken\n        });\n\n    public Task<XboxAuthResponse> RequestTitleToken(XboxTitleTokenRequest request) =>\n        request.Send(_httpClient, _signer);\n\n    public Task<XboxSisuResponse> SisuAuth(XboxSisuAuthRequest request) =>\n        request.Send(_httpClient, _signer);\n\n    public Task<XboxAuthResponse> RequestXstsToken(XboxXstsRequest request) =>\n        request.Send(_httpClient);\n\n    public Task<XboxAuthResponse> RequestSignedXstsToken(XboxSignedXstsRequest request) =>\n        request.Send(_httpClient, _signer);\n}\n"
  },
  {
    "path": "XAU/ViewModels/Pages/AchievementsViewModel.cs",
    "content": "using System.Collections.ObjectModel;\nusing System.Globalization;\nusing System.IO;\nusing System.Net.Http;\nusing System.Text;\nusing System.Windows.Data;\nusing Newtonsoft.Json;\nusing Newtonsoft.Json.Linq;\nusing Wpf.Ui.Controls;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Services;\nusing XAU.Views.Pages;\n\nnamespace XAU.ViewModels.Pages\n{\n    public partial class AchievementsViewModel : ObservableObject, INavigationAware\n    {\n        [ObservableProperty] private bool _isInitialized = false;\n        [ObservableProperty] private string _titleIDOverride = \"0\";\n        [ObservableProperty] private bool _unlockable = false;\n        [ObservableProperty] private bool _titleIDEnabled = false;\n        [ObservableProperty] private ObservableCollection<OneCoreAchievementResponse> _achievements = new ObservableCollection<OneCoreAchievementResponse>();\n        [ObservableProperty] private ObservableCollection<DGAchievement> _dGAchievements = new ObservableCollection<DGAchievement>();\n        [ObservableProperty] public string _gameInfo = \"\";\n        [ObservableProperty] private string _gameName = \"\";\n        [ObservableProperty] private bool _isUnlockAllEnabled = false;\n        [ObservableProperty] private string _searchText = \"\";\n        public static string TitleID = \"0\";\n        private bool IsTitleIDValid = false;\n        public static bool NewGame = false;\n        public static bool IsSelectedGame360;\n        private AchievementsResponse AchievementResponse = new AchievementsResponse();\n        private Xbox360AchievementResponse Xbox360AchievementResponse = new Xbox360AchievementResponse();\n        private Dictionary<int, DGAchievement> _unlockedAchievements = new Dictionary<int, DGAchievement>();\n\n        private GameTitle GameInfoResponse = new GameTitle();\n        // TODO: this needs to be updated if language changes\n        private Lazy<XboxRestAPI> _xboxRestAPI = new Lazy<XboxRestAPI>(() => new XboxRestAPI(HomeViewModel.XAUTH));\n\n        public static bool SpoofingUpdate = false;\n        private bool IsFiltered = false;\n        private bool IsEventBased = false;\n        private dynamic EventsData = (dynamic)(new JObject());\n        public static string EventsToken;\n\n        public AchievementsViewModel(ISnackbarService snackbarService, IContentDialogService contentDialogService, INavigationService navigationService)\n        {\n            _snackbarService = snackbarService;\n            _contentDialogService = contentDialogService;\n            _navigationService = navigationService;\n        }\n\n        private readonly IContentDialogService _contentDialogService;\n        private readonly ISnackbarService _snackbarService;\n        private readonly INavigationService _navigationService;\n        private TimeSpan _snackbarDuration = TimeSpan.FromSeconds(2);\n\n        public class DGAchievement\n        {\n            public int Index { get; set; }\n            public int ID { get; set; }\n            public string? Name { get; set; }\n            public string? Description { get; set; }\n            public bool IsSecret { get; set; }\n            public DateTime DateUnlocked { get; set; }\n            public int Gamerscore { get; set; }\n            public float RarityPercentage { get; set; }\n            public string? RarityCategory { get; set; }\n            public string? ProgressState { get; set; }\n            public bool IsUnlockable { get; set; }\n        }\n        public async void OnNavigatedTo()\n        {\n            if (HomeViewModel.Settings.AutoSpooferEnabled)\n            {\n\n                if (!GameInfoResponse.Titles.Any() && !String.IsNullOrWhiteSpace(GameInfoResponse.Xuid))\n                {\n                    _snackbarService.Show(\"Error: Game Info Response Contained No Titles\", $\"There were no titles returned from the API\", ControlAppearance.Danger,\n                        new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                }\n                else\n                {\n                    if (HomeViewModel.SpoofingStatus == 1 && !!string.IsNullOrWhiteSpace(GameInfo))\n                    {\n                        if (HomeViewModel.SpoofedTitleID == TitleIDOverride)\n                        {\n                            GameInfo = \"Manually Spoofing\";\n                            GameName = GameInfoResponse.Titles[0].Name;\n                        }\n                        else\n                        {\n                            GameInfo = \"Spoofing Another Game\";\n                            GameName = GameInfoResponse.Titles[0].Name;\n                        }\n\n                    }\n                    else if (HomeViewModel.SpoofingStatus == 0 && !string.IsNullOrWhiteSpace(GameInfo))\n                    {\n                        SpoofGame();\n                    }\n                }\n            }\n\n            if (IsInitialized && NewGame)\n                await RefreshAchievements();\n            if (TitleID != \"0\")\n            {\n                TitleIDOverride = TitleID;\n                TitleID = \"0\";\n            }\n            if (HomeViewModel.InitComplete && TitleIDOverride == \"0\")\n                TitleIDEnabled = true;\n            if (!IsInitialized && HomeViewModel.InitComplete && TitleIDOverride != \"0\")\n                InitializeViewModel();\n        }\n\n        public void OnNavigatedFrom() { }\n\n        private async void InitializeViewModel()\n        {\n            if (IsSelectedGame360)\n                Unlockable = false;\n            await LoadGameInfo();\n            await LoadAchievements();\n            if (HomeViewModel.Settings.AutoSpooferEnabled)\n                SpoofGame();\n            TitleIDEnabled = true;\n            IsInitialized = true;\n            NewGame = false;\n        }\n\n\n        private async Task LoadGameInfo()\n        {\n            // Check for a valid TitleID and set overrides\n            if (TitleID != \"0\")\n            {\n                TitleIDOverride = TitleID;\n                TitleID = \"0\";\n            }\n\n            GameInfo = string.Empty;\n\n            // Fetch game information\n            var gameInfoResponse = await _xboxRestAPI.Value.GetGameTitleAsync(HomeViewModel.XUIDOnly, TitleIDOverride);\n\n            // Handle response validation and set properties accordingly\n            if (gameInfoResponse?.Titles?.Any() != true)\n            {\n                GameName = \"Error\";\n                IsTitleIDValid = false;\n                return;\n            }\n\n            var gameTitle = gameInfoResponse.Titles.FirstOrDefault();\n            if (gameTitle != null)\n            {\n                IsSelectedGame360 = gameTitle.Devices.Contains(\"Xbox360\") || gameTitle.Devices.Contains(\"Mobile\");\n                GameName = gameTitle.Name;\n                IsTitleIDValid = true;\n            }\n        }\n\n        private async void SpoofGame()\n        {\n            if (HomeViewModel.SpoofingStatus == 1)\n            {\n                if (HomeViewModel.SpoofedTitleID == TitleIDOverride)\n                {\n                    GameInfo = \"Manually Spoofing\";\n                    GameName = GameInfoResponse.Titles[0].Name;\n                }\n                else\n                {\n                    GameInfo = \"Spoofing Another Game\";\n                    GameName = GameInfoResponse.Titles[0].Name;\n                }\n            }\n            else\n            {\n                HomeViewModel.AutoSpoofedTitleID = TitleIDOverride;\n                HomeViewModel.SpoofingStatus = 2;\n                GameInfo = \"Auto Spoofing\";\n                if (GameInfoResponse.Titles.Any())\n                {\n                    GameName = GameInfoResponse.Titles[0].Name;\n                }\n\n                await Task.Run(() => Spoofing());\n                if (HomeViewModel.SpoofingStatus == 1)\n                {\n                    if (HomeViewModel.SpoofedTitleID == HomeViewModel.AutoSpoofedTitleID)\n                    {\n                        GameInfo = \"Manually Spoofing\";\n                        GameName = GameInfoResponse.Titles[0].Name;\n                    }\n                    else\n                    {\n                        GameInfo = \"Spoofing Another Game\";\n                        GameName = GameInfoResponse.Titles[0].Name;\n                    }\n                }\n                HomeViewModel.AutoSpoofedTitleID = \"0\";\n            }\n\n\n        }\n\n        public async Task Spoofing()\n        {\n            await _xboxRestAPI.Value.SendHeartbeatAsync(HomeViewModel.XUIDOnly, HomeViewModel.AutoSpoofedTitleID);\n            var i = 0;\n            Thread.Sleep(1000);\n            SpoofingUpdate = false;\n            while (!SpoofingUpdate)\n            {\n                if (i == 300)\n                {\n                    await _xboxRestAPI.Value.SendHeartbeatAsync(HomeViewModel.XUIDOnly, HomeViewModel.AutoSpoofedTitleID);\n                    i = 0;\n                }\n                else\n                {\n                    if (SpoofingUpdate)\n                    {\n\n                        break;\n                    }\n                    i++;\n                }\n                Thread.Sleep(1000);\n            }\n        }\n\n        private async Task LoadAchievements()\n        {\n\n            Achievements.Clear();\n            DGAchievements.Clear();\n            // clears unlocked achievements from dictionary\n            _unlockedAchievements.Clear();\n            if (!IsTitleIDValid)\n                return;\n            if (!IsSelectedGame360)\n            {\n                Unlockable = true;\n                AchievementResponse = await _xboxRestAPI.Value.GetAchievementsForTitleAsync(HomeViewModel.XUIDOnly, TitleIDOverride);\n                try\n                {\n                    if (AchievementResponse.achievements[0].progression.requirements.Any())\n                    {\n                        if (AchievementResponse.achievements[0].progression.requirements[0].id !=\n                            StringConstants.ZeroUid)\n                        {\n                            Unlockable = false;\n                        }\n                        else\n                        {\n                            Unlockable = true;\n                        }\n                    }\n                }\n                catch\n                {\n                    _snackbarService.Show(\"Error: No Achievements\", $\"There were no achievements returned from the API\", ControlAppearance.Danger,\n                        new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                    return;\n                }\n                for (int i = 0; i < AchievementResponse.achievements.Count; i++)\n                {\n                    //absolutely fucking dogwater event based check\n                    if (AchievementResponse.achievements[i].progression.requirements.Any())\n                    {\n                        if (AchievementResponse.achievements[i].progression.requirements[0].id !=\n                            StringConstants.ZeroUid)\n                        {\n                            Unlockable = false;\n                            IsEventBased = true;\n                        }\n                        else\n                        {\n                            Unlockable = true;\n                            IsEventBased = false;\n                        }\n                    }\n                    var rewardnameplaceholder = \"\";\n                    var rewarddescriptionplaceholder = \"\";\n                    var rewardvalueplaceholder = \"\";\n                    var rewardtypeplaceholder = \"\";\n                    var rewardmediaAssetplaceholder = \"\";\n                    var rewardvalueTypeplaceholder = \"\";\n                    try\n                    {\n                        rewardnameplaceholder = AchievementResponse.achievements[i].rewards[0].name;\n                        rewarddescriptionplaceholder = AchievementResponse.achievements[i].rewards[0].description;\n                        rewardvalueplaceholder = AchievementResponse.achievements[i].rewards[0].value;\n                        rewardtypeplaceholder = AchievementResponse.achievements[i].rewards[0].type;\n                        //rewardmediaAssetplaceholder = AchievementResponse.achievements[i].rewards[0].mediaAsset;\n                        rewardvalueTypeplaceholder = AchievementResponse.achievements[i].rewards[0].valueType;\n                    }\n                    catch\n                    {\n                        rewardnameplaceholder = \"N/A\";\n                        rewarddescriptionplaceholder = \"N/A\";\n                        rewardvalueplaceholder = \"N/A\";\n                        rewardtypeplaceholder = \"N/A\";\n                        rewardmediaAssetplaceholder = \"N/A\";\n                        rewardvalueTypeplaceholder = \"N/A\";\n                    }\n\n                    var mediaAsset = new MediaAsset\n                    {\n                        name = AchievementResponse.achievements[i].mediaAssets[0].name,\n                        type = AchievementResponse.achievements[i].mediaAssets[0].type,\n                        url = AchievementResponse.achievements[i].mediaAssets[0].url\n                    };\n                    var titleAssociation = new TitleAssociation\n                    {\n                        name = AchievementResponse.achievements[i].titleAssociations[0].name,\n                        id = AchievementResponse.achievements[i].titleAssociations[0].id\n                    };\n                    var progression = new AchievementProgression\n                    {\n                        timeUnlocked = AchievementResponse.achievements[i].progression.timeUnlocked\n                    };\n                    var rewards = new AchievementRewards\n                    {\n                        name = rewardnameplaceholder,\n                        description = rewarddescriptionplaceholder,\n                        value = rewardvalueplaceholder,\n                        type = rewardtypeplaceholder,\n                        mediaAsset = mediaAsset,\n                        valueType = rewardvalueTypeplaceholder\n                    };\n\n\n                    Achievements.Add(new OneCoreAchievementResponse()\n                    {\n                        id = AchievementResponse.achievements[i].id,\n                        serviceConfigId = AchievementResponse.achievements[i].serviceConfigId,\n                        name = AchievementResponse.achievements[i].name,\n                        titleAssociations = new List<TitleAssociation>() { titleAssociation },\n                        progressState = AchievementResponse.achievements[i].progressState,\n                        progression = progression,\n                        mediaAssets = new List<MediaAsset>() { mediaAsset },\n                        platforms = AchievementResponse.achievements[i].platforms,\n                        isSecret = AchievementResponse.achievements[i].isSecret,\n                        description = AchievementResponse.achievements[i].description,\n                        lockedDescription = AchievementResponse.achievements[i].lockedDescription,\n                        productId = AchievementResponse.achievements[i].productId,\n                        achievementType = AchievementResponse.achievements[i].achievementType,\n                        participationType = AchievementResponse.achievements[i].participationType,\n                        timeWindow = AchievementResponse.achievements[i].timeWindow,\n                        rewards = new List<AchievementRewards>() { rewards },\n                        estimatedTime = AchievementResponse.achievements[i].estimatedTime,\n                        deeplink = AchievementResponse.achievements[i].deeplink,\n                        isRevoked = AchievementResponse.achievements[i].isRevoked,\n                        raritycurrentCategory = AchievementResponse.achievements[i].rarity.currentCategory,\n                        raritycurrentPercentage = AchievementResponse.achievements[i].rarity.currentPercentage\n                    }\n                    );\n                }\n                foreach (var achievement in Achievements)\n                {\n                    var gamerscore = 0;\n                    if (achievement.rewards[0].type == StringConstants.Gamerscore)\n                    {\n                        gamerscore = int.Parse(achievement.rewards[0].value);\n                    }\n                    DGAchievements.Add(new DGAchievement()\n                    {\n                        Index = Achievements.IndexOf(achievement),\n                        ID = int.Parse(achievement.id),\n                        Name = achievement.name,\n                        Description = achievement.description,\n                        IsSecret = achievement.isSecret,\n                        DateUnlocked = DateTime.Parse(achievement.progression.timeUnlocked),\n                        Gamerscore = gamerscore,\n                        RarityPercentage = float.Parse(achievement.raritycurrentPercentage, CultureInfo.InvariantCulture),\n                        RarityCategory = achievement.raritycurrentCategory,\n                        ProgressState = achievement.progressState,\n                        IsUnlockable = achievement.progressState != StringConstants.Achieved && Unlockable && !IsEventBased\n                    });\n                }\n            }\n            else\n            {\n                Unlockable = false;\n                Xbox360AchievementResponse = await _xboxRestAPI.Value.GetAchievementsFor360TitleAsync(HomeViewModel.XUIDOnly, TitleIDOverride);\n                if (Xbox360AchievementResponse?.achievements.Count == 0)\n                {\n                    IsSelectedGame360 = false;\n                    LoadAchievements();\n                    return;\n                }\n                //cut down version of the code to display minimal information about 360 achievements\n                for (int i = 0; i < Xbox360AchievementResponse?.achievements.Count; i++)\n                {\n                    var rewards = new AchievementRewards\n                    {\n                        value = Xbox360AchievementResponse.achievements[i].gamerscore.ToString(),\n                        valueType = \"N/a\"\n                    };\n                    var progression = new AchievementProgression\n                    {\n                        timeUnlocked = Xbox360AchievementResponse.achievements[i].timeUnlocked\n                    };\n\n                    Achievements.Add(new OneCoreAchievementResponse()\n                    {\n                        id = Xbox360AchievementResponse.achievements[i].id.ToString(),\n                        name = Xbox360AchievementResponse.achievements[i].name,\n                        isSecret = Xbox360AchievementResponse.achievements[i].isSecret,\n                        description = Xbox360AchievementResponse.achievements[i].description,\n                        rewards = new List<AchievementRewards>() { rewards },\n                        raritycurrentCategory = Xbox360AchievementResponse.achievements[i].rarity.currentCategory,\n                        raritycurrentPercentage = Xbox360AchievementResponse.achievements[i].rarity.currentPercentage,\n                        progression = progression\n                    }\n                    );\n                }\n                foreach (var achievement in Achievements)\n                {\n                    var gamerscore = 0;\n                    if (achievement.rewards[0].type == \"Gamerscore\")\n                    {\n                        gamerscore = int.Parse(achievement.rewards[0].value);\n                    }\n                    DGAchievements.Add(new DGAchievement()\n                    {\n                        Index = Achievements.IndexOf(achievement),\n                        ID = int.Parse(achievement.id),\n                        Name = achievement.name,\n                        Description = achievement.description,\n                        IsSecret = achievement.isSecret,\n                        DateUnlocked = DateTime.Parse(achievement.progression.timeUnlocked),\n                        Gamerscore = gamerscore,\n                        RarityPercentage = float.Parse(achievement.raritycurrentPercentage, CultureInfo.InvariantCulture),\n                        RarityCategory = achievement.raritycurrentCategory,\n                        ProgressState = achievement.progressState,\n                        IsUnlockable = achievement.progressState != StringConstants.Achieved && Unlockable\n                    });\n                }\n            }\n\n            if (IsSelectedGame360)\n            {\n                _snackbarService.Show(\"Warning: Unsupported Game\", $\"This tool does not/will not support Xbox 360 titles. To unlock 360 achievements, you can try https://www.wemod.com/horizon\", ControlAppearance.Caution,\n                    new SymbolIcon(SymbolRegular.Warning24), _snackbarDuration);\n                IsUnlockAllEnabled = false;\n\n                return;\n            }\n\n            if (IsEventBased)\n            {\n                //Event based logic\n                string DataPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + \"\\\\XAU\\\\Events\\\\Data.json\";\n                var data = JObject.Parse(File.ReadAllText(DataPath));\n                JArray SupportedGamesJ = (JArray)data[\"SupportedTitleIDs\"];\n                List<int> SupportedGames = SupportedGamesJ.ToObject<List<int>>();\n                if (SupportedGames.Contains(int.Parse(TitleIDOverride)))\n                {\n                    Unlockable = true;\n                    EventsData = (dynamic)(JObject)data[TitleIDOverride];\n                    foreach (var achievement in DGAchievements)\n                    {\n                        if (EventsData.Achievements.ContainsKey(achievement.ID.ToString()) && achievement.ProgressState != StringConstants.Achieved)\n                        {\n                            achievement.IsUnlockable = true;\n                        }\n                    }\n                }\n                CollectionViewSource.GetDefaultView(DGAchievements).Refresh();\n            }\n\n\n\n            if (!Unlockable)\n            {\n                _snackbarService.Show(\"Warning: Unsupported Game\", $\"This tool does not support this Event Based title\", ControlAppearance.Caution,\n                    new SymbolIcon(SymbolRegular.Warning24), _snackbarDuration);\n            }\n            else if (IsEventBased && EventsData.FullySupported == false)\n            {\n                _snackbarService.Show(\"Warning: Partially Unsupported Game\", $\"This tool does not fully support this title. Not all achievements are unlockable\", ControlAppearance.Caution,\n                                       new SymbolIcon(SymbolRegular.Warning24), _snackbarDuration);\n            }\n\n            if (HomeViewModel.Settings.UnlockAllEnabled && Unlockable && !IsEventBased)\n                IsUnlockAllEnabled = Unlockable;\n            else\n                IsUnlockAllEnabled = false;\n        }\n\n        public async void UnlockAchievement(int AchievementIndex)\n        {\n            if (!IsEventBased)\n            {\n                try\n                {\n                    await _xboxRestAPI.Value.UnlockTitleBasedAchievementAsync(AchievementResponse.achievements[0].serviceConfigId, AchievementResponse.achievements[0].titleAssociations[0].id, HomeViewModel.XUIDOnly, DGAchievements[AchievementIndex].ID.ToString(), HomeViewModel.Settings.FakeSignatureEnabled);\n\n                    _snackbarService.Show(\"Achievement Unlocked\", $\"{DGAchievements[AchievementIndex].Name} has been unlocked\",\n                        ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n                    DGAchievements[AchievementIndex].IsUnlockable = false;\n                    DGAchievements[AchievementIndex].ProgressState = StringConstants.Achieved;\n                    DGAchievements[AchievementIndex].DateUnlocked = DateTime.Now;\n\n                    // Add achievement to the dictionary. this will fix search & filter unlockable state\n                    var unlockedAchievement = DGAchievements[AchievementIndex];\n                    unlockedAchievement.IsUnlockable = false;\n                    unlockedAchievement.ProgressState = StringConstants.Achieved;\n                    unlockedAchievement.DateUnlocked = DateTime.Now;\n\n                    if (!_unlockedAchievements.ContainsKey(unlockedAchievement.ID))\n                    {\n                        _unlockedAchievements.Add(unlockedAchievement.ID, unlockedAchievement);\n                    }\n\n                    CollectionViewSource.GetDefaultView(DGAchievements).Refresh();\n                }\n                catch (HttpRequestException ex)\n                {\n                    _snackbarService.Show(\"Error: Achievement Not Unlocked\",\n                        $\"{DGAchievements[AchievementIndex].Name} was not unlocked\", ControlAppearance.Danger,\n                        new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                }\n            }\n            else\n            {\n                if (EventsToken == null || HomeViewModel.IsEventsTokenExpired())\n                {\n                    ContentDialogResult result = await _contentDialogService.ShowSimpleDialogAsync(\n                        new SimpleContentDialogCreateOptions()\n                        {\n                            Title = EventsToken == null\n                                ? \"Error: You have not set an events token\"\n                                : \"Error: Your events token has expired\",\n                            Content = EventsToken == null\n                                ? \"To unlock event based games you must supply an events token. You can set one up in Settings.\"\n                                : \"Your events token has expired and needs to be refreshed before unlocking.\",\n                            PrimaryButtonText = \"Go to Settings\",\n                            CloseButtonText = \"Close\",\n                        });\n\n                    if (result == ContentDialogResult.Primary)\n                        _navigationService.Navigate(typeof(SettingsPage));\n\n                    return;\n                }\n\n                // TODO: move this over to the rest api?\n                var requestbody = File.ReadAllText(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + $\"\\\\XAU\\\\Events\\\\{TitleIDOverride}.json\");\n                DateTime timestamp = DateTime.UtcNow;\n                foreach (var i in EventsData.Achievements[DGAchievements[AchievementIndex].ID.ToString()])\n                {\n                    var ReplacementData = i.Value;\n                    switch (ReplacementData.ReplacementType.ToString())\n                    {\n                        case \"Replace\":\n                            {\n                                requestbody = requestbody.Replace(ReplacementData.Target.ToString(), ReplacementData.Replacement.ToString());\n                                break;\n                            }\n                        case \"RangeInt\":\n                            {\n                                int min = ReplacementData.Min;\n                                int max = ReplacementData.Max;\n                                Random random = new Random();\n                                int randomint = random.Next(min, max);\n                                requestbody = requestbody.Replace(ReplacementData.Target.ToString(), randomint.ToString());\n                                break;\n                            }\n                        case \"RangeFloat\":\n                            {\n                                float min = ReplacementData.Min;\n                                float max = ReplacementData.Max;\n                                Random random = new Random();\n                                float randomfloat = (float)random.NextDouble() * (max - min) + min;\n                                requestbody = requestbody.Replace(ReplacementData.Target.ToString(), randomfloat.ToString());\n                                break;\n                            }\n                        case \"StupidFuckingLDAPTimestamp\":\n                            {\n                                long ldapTimestamp = DateTime.Now.ToFileTime();\n                                requestbody = requestbody.Replace(ReplacementData.Target.ToString(), ldapTimestamp.ToString());\n                                break;\n                            }\n                        default:\n                            {\n                                _snackbarService.Show(\"Error: Bad Achievement Data\", \"Something went wrong with the achievement data\", ControlAppearance.Danger,\n                                                                                  new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                                return;\n                            }\n\n                    }\n                }\n                requestbody = requestbody.Replace(\"REPLACETIME\", timestamp.ToString(\"yyyy-MM-ddTHH:mm:ss.fffffffZ\"));\n                requestbody = requestbody.Replace(\"REPLACESEQ\", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString());\n                requestbody = requestbody.Replace(\"REPLACEXUID\", HomeViewModel.XUIDOnly);\n                requestbody = JObject.Parse(requestbody).ToString(Formatting.None);\n                var bodyconverted = new StringContent(requestbody, Encoding.UTF8, \"application/x-json-stream\");\n                try\n                {\n                    await _xboxRestAPI.Value.UnlockEventBasedAchievement(EventsToken, bodyconverted);\n\n                    _snackbarService.Show(\"Achievement Unlocked\", $\"{DGAchievements[AchievementIndex].Name} has been unlocked\",\n                        ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n                    DGAchievements[AchievementIndex].IsUnlockable = false;\n                    DGAchievements[AchievementIndex].ProgressState = \"Achieved\";\n                    DGAchievements[AchievementIndex].DateUnlocked = DateTime.Now;\n                    CollectionViewSource.GetDefaultView(DGAchievements).Refresh();\n                }\n                catch\n                {\n                    _snackbarService.Show(\"Error: Achievement Not Unlocked\",\n                        $\"{DGAchievements[AchievementIndex].Name} was not unlocked\", ControlAppearance.Danger,\n                        new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                }\n\n            }\n\n        }\n\n        [RelayCommand]\n        public async Task UnlockAll()\n        {\n            var lockedAchievementIds = Achievements.Where(o => o.progressState != StringConstants.Achieved).Select(o => o.id).ToList();\n            try\n            {\n                await _xboxRestAPI.Value.UnlockTitleBasedAchievementsAsync(serviceConfigId: AchievementResponse.achievements[0].serviceConfigId,\n                    titleId: AchievementResponse.achievements[0].titleAssociations[0].id, xuid: HomeViewModel.XUIDOnly, achievementIds: lockedAchievementIds, useFakeSignature: HomeViewModel.Settings.FakeSignatureEnabled);\n\n                _snackbarService.Show(\"All Achievements Unlocked\", $\"All Achievements for this game have been unlocked\",\n                    ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n                var unlocktime = DateTime.Now;\n                foreach (DGAchievement achievement in DGAchievements)\n                {\n\n                    if (achievement.ProgressState != StringConstants.Achieved)\n                    {\n                        achievement.IsUnlockable = false;\n                        achievement.ProgressState = StringConstants.Achieved;\n                        achievement.DateUnlocked = unlocktime;\n                    }\n                }\n                CollectionViewSource.GetDefaultView(DGAchievements).Refresh();\n            }\n            catch (HttpRequestException hre)\n            {\n                _snackbarService.Show(\"Error: Achievements Not Unlocked\",\n                                        $\"{hre.Message}\", ControlAppearance.Danger,\n                                        new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n            }\n        }\n\n        [RelayCommand]\n        public async Task RefreshAchievements()\n        {\n            // clears unlocked achievements from dictionary\n            _unlockedAchievements.Clear();\n\n            await LoadGameInfo();\n            await LoadAchievements();\n            NewGame = false;\n            if (HomeViewModel.Settings.AutoSpooferEnabled)\n                SpoofGame();\n        }\n\n        [RelayCommand]\n        public async Task SearchAndFilterAchievements()\n        {\n            try\n            {\n                if (IsEventBased)\n                {\n                    string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\", \"Events\", \"Data.json\");\n                    var data = JObject.Parse(File.ReadAllText(DataPath));\n                    JArray SupportedGamesJ = (JArray)data[\"SupportedTitleIDs\"];\n                    List<int> SupportedGames = SupportedGamesJ.ToObject<List<int>>();\n                    if (SupportedGames.Contains(int.Parse(TitleIDOverride)))\n                    {\n                        Unlockable = true;\n                        EventsData = (dynamic)data[TitleIDOverride];\n                    }\n                }\n\n                CollectionViewSource.GetDefaultView(DGAchievements).Refresh();\n\n                if (string.IsNullOrWhiteSpace(SearchText) && !IsFiltered)\n                {\n                    _snackbarService.Show(\"Error\", $\"Please Enter Query Text\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                    return;\n                }\n\n                DGAchievements.Clear();\n\n                if (string.IsNullOrWhiteSpace(SearchText) && IsFiltered)\n                {\n                    foreach (var achievement in Achievements)\n                    {\n                        var gamerscore = 0;\n                        if (achievement.rewards[0].type == StringConstants.Gamerscore)\n                        {\n                            gamerscore = int.Parse(achievement.rewards[0].value);\n                        }\n\n                        var dgAchievement = new DGAchievement()\n                        {\n                            Index = DGAchievements.Count,\n                            ID = int.Parse(achievement.id),\n                            Name = achievement.name,\n                            Description = achievement.description,\n                            IsSecret = achievement.isSecret,\n                            DateUnlocked = DateTime.Parse(achievement.progression.timeUnlocked),\n                            Gamerscore = gamerscore,\n                            RarityPercentage = float.Parse(achievement.raritycurrentPercentage, CultureInfo.InvariantCulture),\n                            RarityCategory = achievement.raritycurrentCategory,\n                            ProgressState = achievement.progressState,\n                            IsUnlockable = achievement.progressState != StringConstants.Achieved && Unlockable && !IsEventBased\n                        };\n\n                        // Override with the state from _unlockedAchievements dictionary if it exists.\n                        if (_unlockedAchievements.ContainsKey(dgAchievement.ID))\n                        {\n                            var unlocked = _unlockedAchievements[dgAchievement.ID];\n                            dgAchievement.IsUnlockable = unlocked.IsUnlockable;\n                            dgAchievement.ProgressState = unlocked.ProgressState;\n                            dgAchievement.DateUnlocked = unlocked.DateUnlocked;\n                        }\n\n                        DGAchievements.Add(dgAchievement);\n                    }\n\n                    if (IsEventBased && Unlockable)\n                    {\n                        foreach (var achievement in DGAchievements)\n                        {\n                            if (EventsData.Achievements.ContainsKey(achievement.ID.ToString()) && achievement.ProgressState != StringConstants.Achieved)\n                            {\n                                achievement.IsUnlockable = true;\n                            }\n                        }\n                        CollectionViewSource.GetDefaultView(DGAchievements).Refresh();\n                    }\n                    IsFiltered = false;\n                    return;\n                }\n\n                bool achievementsFound = false;\n\n                foreach (var achievement in Achievements)\n                {\n                    if (achievement.name.Contains(SearchText, StringComparison.OrdinalIgnoreCase) || achievement.description.Contains(SearchText, StringComparison.OrdinalIgnoreCase))\n                    {\n                        var gamerscore = 0;\n                        if (achievement.rewards[0].type == StringConstants.Gamerscore)\n                        {\n                            gamerscore = int.Parse(achievement.rewards[0].value);\n                        }\n\n                        var dgAchievement = new DGAchievement()\n                        {\n                            Index = DGAchievements.Count,\n                            ID = int.Parse(achievement.id),\n                            Name = achievement.name,\n                            Description = achievement.description,\n                            IsSecret = achievement.isSecret,\n                            DateUnlocked = DateTime.Parse(achievement.progression.timeUnlocked),\n                            Gamerscore = gamerscore,\n                            RarityPercentage = float.Parse(achievement.raritycurrentPercentage, CultureInfo.InvariantCulture),\n                            RarityCategory = achievement.raritycurrentCategory,\n                            ProgressState = achievement.progressState,\n                            IsUnlockable = achievement.progressState != StringConstants.Achieved && Unlockable && !IsEventBased\n                        };\n\n                        // Override with the state from _unlockedAchievements dictionary if it exists.\n                        if (_unlockedAchievements.ContainsKey(dgAchievement.ID))\n                        {\n                            var unlockedAchievement = _unlockedAchievements[dgAchievement.ID];\n                            dgAchievement.IsUnlockable = unlockedAchievement.IsUnlockable;\n                            dgAchievement.ProgressState = unlockedAchievement.ProgressState;\n                            dgAchievement.DateUnlocked = unlockedAchievement.DateUnlocked;\n                        }\n\n                        DGAchievements.Add(dgAchievement);\n                        achievementsFound = true;\n                    }\n                }\n\n                if (!achievementsFound)\n                {\n                    _snackbarService.Show(\"Error\", $\"No Achievements Found\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                }\n\n                if (IsEventBased && Unlockable)\n                {\n                    foreach (var achievement in DGAchievements)\n                    {\n                        if (EventsData.Achievements.ContainsKey(achievement.ID.ToString()) && achievement.ProgressState != StringConstants.Achieved)\n                        {\n                            achievement.IsUnlockable = true;\n                        }\n                    }\n                    CollectionViewSource.GetDefaultView(DGAchievements).Refresh();\n                }\n\n                IsFiltered = true;\n            }\n            catch (Exception ex)\n            {\n                // Log exception (ex) if necessary\n                _snackbarService.Show(\"Error\", \"An error occurred while searching. Please try again.\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n            }\n\n            await Task.CompletedTask;\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/ViewModels/Pages/DebugViewModel.cs",
    "content": "using Newtonsoft.Json.Linq;\nusing System.IO;\nusing System.Net.Http;\nusing System.Text;\nusing Wpf.Ui.Controls;\nusing Newtonsoft.Json;\nusing Wpf.Ui.Contracts;\n\nnamespace XAU.ViewModels.Pages\n{\n    public partial class DebugViewModel : ObservableObject, INavigationAware\n    {\n        private bool _isInitialized = false;\n\n        public void OnNavigatedTo()\n        {\n            if (!_isInitialized)\n                InitializeViewModel();\n        }\n\n        public void OnNavigatedFrom() { }\n\n        private void InitializeViewModel()\n        {\n            _isInitialized = true;\n        }\n        public DebugViewModel(ISnackbarService snackbarService, IContentDialogService contentDialogService)\n        {\n            _snackbarService = snackbarService;\n            _contentDialogService = contentDialogService;\n        }\n        private readonly ISnackbarService _snackbarService;\n        private TimeSpan _snackbarDuration = TimeSpan.FromSeconds(2);\n        private readonly IContentDialogService _contentDialogService;\n\n        [RelayCommand]\n        public void TestEventReplacements()\n        {\n            int failedAchievements = 0;\n            int successfulAchievements = 0;\n            List<string> errors = new List<string>();\n            string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\", \"Events\", \"Data.json\");\n            var data = JObject.Parse(File.ReadAllText(DataPath));\n            JArray SupportedGamesJ = (JArray)data[\"SupportedTitleIDs\"];\n            string Achievement = \"\";\n            DateTime timestamp = DateTime.UtcNow;\n            foreach (var game in SupportedGamesJ)\n            {\n                var requestbody = File.ReadAllText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\", \"Events\", $\"{game}.json\"));\n\n                var EventsData = (dynamic)(JObject)data[game.ToString()];\n                foreach (var i in EventsData.Achievements)\n                {\n                    try\n                    {\n                        foreach (var j in i)\n                        {\n                            Achievement = i.Name.ToString();\n                            foreach (var k in j)\n                            {\n                                var ReplacementData = k.Value;\n                                switch (ReplacementData.ReplacementType.ToString())\n                                {\n                                    case \"Replace\":\n                                        {\n                                            requestbody = requestbody.Replace(ReplacementData.Target.ToString(), ReplacementData.Replacement.ToString());\n                                            break;\n                                        }\n                                    case \"RangeInt\":\n                                        {\n                                            int min = ReplacementData.Min;\n                                            int max = ReplacementData.Max;\n                                            Random random = new Random();\n                                            int randomint = random.Next(min, max);\n                                            requestbody = requestbody.Replace(ReplacementData.Target.ToString(), randomint.ToString());\n                                            break;\n                                        }\n                                    case \"RangeFloat\":\n                                        {\n                                            float min = ReplacementData.Min;\n                                            float max = ReplacementData.Max;\n                                            Random random = new Random();\n                                            float randomfloat = (float)random.NextDouble() * (max - min) + min;\n                                            requestbody = requestbody.Replace(ReplacementData.Target.ToString(), randomfloat.ToString());\n                                            break;\n                                        }\n                                    case \"StupidFuckingLDAPTimestamp\":\n                                        {\n                                            long ldapTimestamp = DateTime.Now.ToFileTime();\n                                            requestbody = requestbody.Replace(ReplacementData.Target.ToString(), ldapTimestamp.ToString());\n                                            break;\n                                        }\n                                    default:\n                                        {\n                                            //_snackbarService.Show(\"Error: Bad Achievement Data\", \"Something went wrong with the achievement data\", ControlAppearance.Danger,\n                                            //                                                  new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                                            return;\n                                        }\n\n                                }\n\n                            }\n                        }\n                        requestbody = requestbody.Replace(\"REPLACETIME\", timestamp.ToString(\"yyyy-MM-ddTHH:mm:ss.fffffffZ\"));\n                        requestbody = requestbody.Replace(\"REPLACESEQ\", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString());\n                        requestbody = requestbody.Replace(\"REPLACEXUID\", HomeViewModel.XUIDOnly);\n                        requestbody = JObject.Parse(requestbody).ToString(Formatting.None);\n                        var bodyconverted = new StringContent(requestbody, Encoding.UTF8, \"application/x-json-stream\");\n                        successfulAchievements++;\n                    }\n                    catch (Exception ex)\n                    {\n                        failedAchievements++;\n                        errors.Add($\"Game: {game}, Achievement:{Achievement}, Error: {ex.Message}\");\n                    }\n                }\n\n            }\n\n            // Write errors to a file\n            File.WriteAllLines(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\", \"Events\", \"Errors.log\"), errors);\n\n            // Show message box with the summary\n            _contentDialogService.ShowSimpleDialogAsync(\n                        new SimpleContentDialogCreateOptions()\n                        {\n                            Title = \"Test Results\",\n                            Content = $\"Successful Achievements: {successfulAchievements}\\nUnsuccessful Achievements: {failedAchievements}\",\n                            CloseButtonText = \"Close\"\n                        });\n        }\n\n    }\n}\n"
  },
  {
    "path": "XAU/ViewModels/Pages/GamesViewModel.cs",
    "content": "using System.Collections.ObjectModel;\nusing System.ComponentModel;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls;\nusing XAU.Views.Pages;\nnamespace XAU.ViewModels.Pages\n{\n    public partial class GamesViewModel(ISnackbarService snackbarService, INavigationService navigationService) : ObservableObject, INavigationAware, INotifyPropertyChanged\n    {\n        [ObservableProperty] private string _xuidOverride = \"0\";\n        [ObservableProperty] private ObservableCollection<Game> _games = new ObservableCollection<Game>();\n        [ObservableProperty] private ObservableCollection<Game> _gamesPaged = new ObservableCollection<Game>();\n        [ObservableProperty] private string _searchLabel = \"Search 0 Games\";\n        [ObservableProperty] private GridLength _gamesListHeight = new GridLength(0, GridUnitType.Star);\n        [ObservableProperty] private GridLength _loadingHeight = new GridLength(1, GridUnitType.Star);\n        [ObservableProperty] private double _loadingSize = 200;\n        [ObservableProperty] private string _searchText = \"\";\n        [ObservableProperty] private List<string> _filterOptions = new List<string>() { \"All\", \"Xbox One/Series\", \"PC\", \"Xbox 360\", \"Win32\", \"Incomplete Games\" };\n        [ObservableProperty] private int _filterIndex = 0;\n        [ObservableProperty] private int _numPages = 0;\n        [ObservableProperty] private ObservableCollection<string> _pageOptions = new ObservableCollection<string>();\n        [ObservableProperty] private int _currentPage = 0;\n        [ObservableProperty] private bool _isInitialized = false;\n\n        TitlesList GamesResponse = new TitlesList();\n        public bool PageReset = true;\n\n\n        public class Game\n        {\n            public required string Title { get; set; }\n            public required string Image { get; set; }\n            public required string Gamerscore { get; set; }\n            public required string CurrentAchievements { get; set; }\n            public required string Progress { get; set; }\n            public required string Index { get; set; }\n\n        }\n\n        // TODO: this needs to be updated if language changes\n        private Lazy<XboxRestAPI> _xboxRestAPI = new Lazy<XboxRestAPI>(() => new XboxRestAPI(HomeViewModel.XAUTH));\n\n        private readonly IContentDialogService _contentDialogService;\n        private readonly ISnackbarService _snackbarService = snackbarService;\n        private TimeSpan _snackbarDuration = TimeSpan.FromSeconds(2);\n\n        public async void OnNavigatedTo()\n        {\n            if (!IsInitialized && HomeViewModel.InitComplete)\n                await InitializeViewModel();\n        }\n\n        public void OnNavigatedFrom()\n        {\n        }\n\n        private async Task InitializeViewModel()\n        {\n            XuidOverride = HomeViewModel.XUIDOnly;\n\n            IsInitialized = true;\n            await GetGamesList();\n\n        }\n\n        [RelayCommand]\n        private async Task GetGamesList()\n        {\n            if (string.IsNullOrWhiteSpace(XuidOverride) || string.IsNullOrEmpty(XuidOverride))\n            {\n                _snackbarService.Show(\n                    \"Error\",\n                    \"XUID Override cannot be empty.\",\n                    ControlAppearance.Danger,\n                    new SymbolIcon(SymbolRegular.ErrorCircle24),\n                    _snackbarDuration\n                );\n                return;\n            }\n\n            Games.Clear();\n            GamesPaged.Clear();\n            LoadingStart();\n            GamesResponse = await _xboxRestAPI.Value.GetGamesListAsync(XuidOverride) ?? new TitlesList();\n            LoadGame();\n        }\n\n        private void LoadGame()\n        {\n            if (SearchText.Length > 0)\n            {\n                SearchAndFilterGames();\n            }\n            else\n            {\n                FilterGames();\n            }\n        }\n        public async Task OpenAchievements(string index)\n        {\n            AchievementsViewModel.TitleID = GamesResponse.Titles[int.Parse(index)].TitleId;\n            AchievementsViewModel.IsSelectedGame360 = GamesResponse.Titles[int.Parse(index)].Devices.Contains(\"Xbox360\") || GamesResponse.Titles[int.Parse(index)].Devices.Contains(\"Mobile\");\n            AchievementsViewModel.NewGame = true;\n            navigationService.Navigate(typeof(AchievementsPage));\n            await Task.CompletedTask;\n        }\n        [RelayCommand]\n        public void SearchAndFilterGames()\n        {\n            Games.Clear();\n            GamesPaged.Clear();\n            LoadingStart();\n            if (FilterIndex != 0)\n            {\n                switch (FilterIndex)\n                {\n                    case 1:\n                        for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                        {\n                            if (GamesResponse.Titles[i].Devices.Contains(\"XboxSeries\") || GamesResponse.Titles[i].Devices.Contains(\"XboxOne\"))\n                            {\n                                if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower()))\n                                    continue;\n                                AddGame(i);\n                            }\n                        }\n                        break;\n                    case 2:\n                        for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                        {\n                            if (GamesResponse.Titles[i].Devices.Contains(\"PC\"))\n                            {\n                                if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower()))\n                                    continue;\n                                AddGame(i);\n                            }\n                        }\n                        break;\n                    case 3:\n                        for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                        {\n                            if (GamesResponse.Titles[i].Devices.Contains(\"Xbox360\"))\n                            {\n                                if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower()))\n                                    continue;\n                                AddGame(i);\n                            }\n                        }\n                        break;\n                    case 4:\n                        for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                        {\n                            if (GamesResponse.Titles[i].Devices.Contains(\"Win32\"))\n                            {\n                                if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower()))\n                                    continue;\n                                AddGame(i);\n                            };\n                        }\n                        break;\n                    case 5:\n                        for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                        {\n                            if (double.TryParse(GamesResponse.Titles[i].Achievement.ProgressPercentage.ToString(), out double progress) && progress < 100)\n                            {\n                                if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower()))\n                                    continue;\n                                AddGame(i);\n                            }\n\n                        }\n                        break;\n                }\n            }\n            else\n            {\n                for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                {\n                    var title = GamesResponse.Titles[i];\n                    if (!title.Name.ToLower().Contains(SearchText.ToLower()))\n                        continue;\n                    AddGame(i);\n\n                }\n            }\n\n            LoadingEnd();\n            SearchLabel = $\"Search {GamesResponse.Titles.Count.ToString()} Games\";\n            if (Games.Count() == 0)\n            {\n                _snackbarService.Show(\"Error\", $\"No Games Found\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                NumPages = 0;\n                return;\n            }\n            NumPages = (int)Math.Ceiling(Games.Count / 252.0);\n            PageReset = true;\n            PageOptions.Clear();\n            for (int i = 1; i <= NumPages; i++)\n            {\n                PageOptions.Add(i.ToString());\n            }\n            PageReset = true;\n            CurrentPage = 0;\n            GamesPaged.Clear();\n            for (int i = ((252 * CurrentPage)); i < (252 * (CurrentPage + 1)); i++)\n            {\n                if (Games.Count > i)\n                {\n                    GamesPaged.Add(Games[i]);\n                }\n            }\n        }\n\n        [RelayCommand]\n        public void FilterGames()\n        {\n            if (!IsInitialized)\n            {\n                return;\n            }\n\n            if (SearchText.Length > 0)\n            {\n                SearchAndFilterGames();\n                return;\n            }\n            GamesPaged.Clear();\n            LoadingStart();\n            Games.Clear();\n            if (FilterIndex != 0)\n            {\n                switch (FilterIndex)\n                {\n                    case 1:\n                        for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                        {\n                            if (GamesResponse.Titles[i].Devices.Contains(\"XboxSeries\") || GamesResponse.Titles[i].Devices.Contains(\"XboxOne\"))\n                                AddGame(i);\n                        }\n                        break;\n                    case 2:\n                        for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                        {\n                            if (GamesResponse.Titles[i].Devices.Contains(\"PC\"))\n                                AddGame(i);\n                        }\n                        break;\n                    case 3:\n                        for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                        {\n                            if (GamesResponse.Titles[i].Devices.Contains(\"Xbox360\"))\n                                AddGame(i);\n                        }\n                        break;\n                    case 4:\n                        for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                        {\n                            if (GamesResponse.Titles[i].Devices.Contains(\"Win32\"))\n                                AddGame(i);\n                        }\n                        break;\n                    case 5:\n                        for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                        {\n                            if (double.TryParse(GamesResponse.Titles[i].Achievement.ProgressPercentage.ToString(), out double progress) && progress < 100)\n                            {\n                                if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower()))\n                                    continue;\n                                AddGame(i);\n                            }\n                        }\n                        break;\n                }\n            }\n            else\n            {\n                for (int i = 0; i < GamesResponse.Titles.Count; i++)\n                {\n                    AddGame(i);\n                }\n            }\n\n            LoadingEnd();\n            SearchLabel = $\"Search {GamesResponse.Titles.Count.ToString()} Games\";\n            if (Games.Count() == 0)\n            {\n                _snackbarService.Show(\"Error\", $\"No Games Found\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                NumPages = 0;\n                return;\n            }\n            NumPages = (int)Math.Ceiling(Games.Count / 252.0);\n            PageReset = true;\n            PageOptions.Clear();\n            for (int i = 1; i <= NumPages; i++)\n            {\n                PageOptions.Add(i.ToString());\n            }\n            PageReset = true;\n            CurrentPage = 0;\n            GamesPaged.Clear();\n            for (int i = ((252 * CurrentPage)); i < (252 * (CurrentPage + 1)); i++)\n            {\n                if (Games.Count > i)\n                {\n                    GamesPaged.Add(Games[i]);\n                }\n            }\n        }\n\n        private void AddGame(int index)\n        {\n            var title = GamesResponse.Titles[index];\n            var EditedImage = !string.IsNullOrEmpty(title.DisplayImage?.ToString()) ? title.DisplayImage.ToString() : \"pack://application:,,,/Assets/cirno.png\";\n            if (EditedImage.Contains(\"store-images.s-microsoft.com\"))\n            {\n                EditedImage = EditedImage + \"?w=256&h=256&format=jpg\";\n            }\n            Games.Add(new Game()\n            {\n                Title = title.Name.ToString(),\n                CurrentAchievements = title.Achievement.CurrentAchievements.ToString(),\n                Gamerscore = title.Achievement.CurrentGamerscore.ToString() + \"/\" +\n                             title.Achievement.TotalGamerscore.ToString(),\n                Progress = title.Achievement.ProgressPercentage.ToString(),\n                Image = EditedImage, //\"pack://application:,,,/Assets/cirno.png\", //\n                Index = index.ToString()\n            });\n        }\n\n        [RelayCommand]\n        public void PageChanged()\n        {\n            if (PageReset)\n            {\n                PageReset = false;\n                return;\n            }\n            GamesPaged.Clear();\n            LoadingStart();\n            for (int i = ((252 * (CurrentPage))); i < (252 * (CurrentPage + 1)); i++)\n            {\n                if (Games.Count > i)\n                {\n                    GamesPaged.Add(Games[i]);\n                }\n            }\n            LoadingEnd();\n        }\n\n        public void LoadingStart()\n        {\n            LoadingSize = 200;\n            GamesListHeight = new GridLength(0, GridUnitType.Star);\n            LoadingHeight = new GridLength(1, GridUnitType.Star);\n        }\n\n        public void LoadingEnd()\n        {\n            GamesListHeight = new GridLength(1, GridUnitType.Star);\n            LoadingHeight = new GridLength(0, GridUnitType.Star);\n            LoadingSize = 0;\n        }\n\n        public void CopyToClipboard(string index)\n        {\n            var titleid = GamesResponse.Titles[int.Parse(index)].TitleId.ToString();\n            var title = GamesResponse.Titles[int.Parse(index)].Name.ToString();\n            Clipboard.SetDataObject(GamesResponse.Titles[int.Parse(index)].TitleId.ToString());\n            _snackbarService.Show(\"TitleID Copied\", $\"Copied the title ID of {title.ToString()} to clipboard\\nTitleID: {titleid.ToString()}\", ControlAppearance.Success, new SymbolIcon(SymbolRegular.ClipboardCheckmark24), _snackbarDuration);\n        }\n    }\n\n}\n"
  },
  {
    "path": "XAU/ViewModels/Pages/HomeViewModel.cs",
    "content": "using System.ComponentModel;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Security.Cryptography;\nusing System.Text;\nusing XAU.Util.Etw;\nusing System.Windows.Media;\nusing Wpf.Ui.Controls;\nusing Memory;\nusing System.Net.Http;\nusing Newtonsoft.Json.Linq;\nusing Newtonsoft.Json;\nusing System.Net;\nusing System.Collections.ObjectModel;\nusing System.IO.Compression;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Contracts;\nusing XboxAuthNet.OAuth;\nusing XboxAuthNet.OAuth.CodeFlow;\nusing XboxAuthNet.XboxLive;\nusing XboxAuthNet.XboxLive.Requests;\nusing XboxAuthNet.XboxLive.Responses;\n\nnamespace XAU.ViewModels.Pages\n{\n    public partial class ImageItem : ObservableObject\n    {\n        [ObservableProperty]\n        private string _imageUrl;\n    }\n\n    public partial class HomeViewModel : ObservableObject, INavigationAware\n    {\n        public static string ToolVersion = \"EmptyDevToolVersion\";\n        public static string EventsVersion = \"1.0\";\n\n        //attach vars\n        [ObservableProperty] private string _attached = \"Not Attached\";\n        [ObservableProperty] private Brush _attachedColor = new SolidColorBrush(Colors.Red);\n        [ObservableProperty] private string _loggedIn = \"Not Logged In\";\n        [ObservableProperty] private Brush _loggedInColor = new SolidColorBrush(Colors.Red);\n\n        //profile vars\n        [ObservableProperty] private string? _gamerPic = \"pack://application:,,,/Assets/cirno.png\";\n        [ObservableProperty] private string? _gamerTag = \"Gamertag: Unknown   \";\n        [ObservableProperty] private string? _xuid = \"XUID: Unknown\";\n        [ObservableProperty] private string? _gamerScore = \"Gamerscore: Unknown\";\n        [ObservableProperty] private string? _profileRep = \"Reputation: Unknown\";\n        [ObservableProperty] private string? _accountTier = \"Tier: Unknown\";\n        [ObservableProperty] private string? _currentlyPlaying = \"Currently Playing: Unknown\";\n        [ObservableProperty] private string? _activeDevice = \"Active Device: Unknown\";\n        [ObservableProperty] private string? _isVerified = \"Verified: Unknown\";\n        [ObservableProperty] private string? _location = \"Location: Unknown\";\n        [ObservableProperty] private string? _tenure = \"Tenure: Unknown\";\n        [ObservableProperty] private string? _following = \"Following: Unknown\";\n        [ObservableProperty] private string? _followers = \"Followers: Unknown\";\n        [ObservableProperty] private string? _gamepass = \"Gamepass: Unknown\";\n        [ObservableProperty] private string? _bio = \"Bio: Unknown\";\n        [ObservableProperty] private string _loginText = \"Login\";\n        [ObservableProperty] public static bool _isLoggedIn = false;\n        [ObservableProperty] public static bool _updateAvaliable = false;\n        [ObservableProperty] private ObservableCollection<ImageItem> _watermarks = new ObservableCollection<ImageItem>();\n\n        private readonly Lazy<XboxRestAPI> _xboxRestAPI;\n        private readonly Lazy<GithubRestApi> _gitHubRestAPI = new Lazy<GithubRestApi>();\n\n        public static int SpoofingStatus = 0; //0 = NotSpoofing, 1 = Spoofing, 2 = AutoSpoofing\n        public static string SpoofedTitleID = \"0\";\n        public static string AutoSpoofedTitleID = \"0\";\n\n        //SnackBar\n        public HomeViewModel(ISnackbarService snackbarService, IContentDialogService contentDialogService)\n        {\n            _snackbarService = snackbarService;\n            _contentDialogService = contentDialogService;\n\n            // Assume XAUTH and System Language are set by the time this is actually instantiated\n            _xboxRestAPI = new Lazy<XboxRestAPI>(() => new XboxRestAPI(XAUTH));\n        }\n        private readonly ISnackbarService _snackbarService;\n        private TimeSpan _snackbarDuration = TimeSpan.FromSeconds(2);\n        private readonly IContentDialogService _contentDialogService;\n\n        private const string XAuthScanPattern = \"58 42 4C 33 2E 30 20 78 3D\";\n\n        [RelayCommand]\n        private void RefreshProfile()\n        {\n            GrabProfile();\n        }\n\n        Mem m = new Mem();\n        public BackgroundWorker XauthWorker = new BackgroundWorker();\n        public BackgroundWorker EventsTokenWorker = new BackgroundWorker();\n        bool IsAttached = false;\n        bool GrabbedProfile = false;\n        bool eventsTokenFound = false;\n        public static bool XAUTHTested = false;\n        public static string XAUTH = \"\";\n        public static string XUIDOnly;\n        public static bool InitComplete = false;\n        private bool _isInitialized = false;\n        string SettingsFilePath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\"), \"settings.json\");\n        string EventsMetaFilePath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\"), \"Events\", \"meta.json\");\n        string AuthFilePath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\"), \"auth.json\");\n        public CodeFlowAuthenticator oauth;\n        public XboxAuthClient xboxAuthClient;\n        public XboxSignedClient xboxSignedClient;\n\n        public async void OnNavigatedTo()\n        {\n            if (!_isInitialized)\n                await InitializeViewModel();\n        }\n        public void OnNavigatedFrom() { }\n\n        #region Update\n        private async Task CheckForToolUpdates()\n        {\n            if (ToolVersion == \"EmptyDevToolVersion\")\n                return;\n\n            if (ToolVersion.Contains(\"DEV\"))\n            {\n                var jsonResponse = await _gitHubRestAPI.Value.GetDevToolVersionAsync();\n\n                if ((\"DEV-\" + jsonResponse.LatestBuildVersion.ToString()) != ToolVersion)\n                {\n                    var result = await _contentDialogService.ShowSimpleDialogAsync(\n                        new SimpleContentDialogCreateOptions()\n                        {\n                            Title = $\"Version {jsonResponse.LatestBuildVersion.ToString()} available to download\",\n                            Content = \"Would you like to update to this version?\",\n                            PrimaryButtonText = \"Update\",\n                            CloseButtonText = \"Cancel\"\n                        }\n                    );\n                    if (result == ContentDialogResult.Primary)\n                    {\n                        _snackbarService.Show(\"Downloading update...\", \"Please wait\", ControlAppearance.Info, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n                        string sourceFile = jsonResponse.DownloadURL.ToString();\n                        string destFile = @\"XAU-new.exe\";\n                        var fileDownloader = new FileDownloader();\n                        await fileDownloader.DownloadFileAsync(new Uri(sourceFile).ToString(), destFile, UpdateTool);\n                    }\n                }\n            }\n            else\n            {\n                var jsonResponse = await _gitHubRestAPI.Value.GetReleaseVersionAsync();\n\n                if (jsonResponse[0].tag_name.ToString() != ToolVersion)\n                {\n                    var result = await _contentDialogService.ShowSimpleDialogAsync(\n                        new SimpleContentDialogCreateOptions()\n                        {\n                            Title = $\"Version {jsonResponse[0].tag_name.ToString()} available to download\",\n                            Content = \"Would you like to update to this version?\",\n                            PrimaryButtonText = \"Update\",\n                            CloseButtonText = \"Cancel\"\n                        }\n                    );\n                    if (result == ContentDialogResult.Primary)\n                    {\n                        _snackbarService.Show(\"Downloading update...\", \"Please wait\", ControlAppearance.Info, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n                        string sourceFile = jsonResponse[0].assets[0].browser_download_url.ToString();\n                        string destFile = @\"XAU-new.exe\";\n                        var fileDownloader = new FileDownloader();\n                        await fileDownloader.DownloadFileAsync(sourceFile, destFile, UpdateTool);\n                    }\n                }\n            }\n\n        }\n        private async void CheckForEventUpdates()\n        {\n            if (EventsVersion == \"EmptyDevEventsVersion\")\n                return;\n            var response = await _gitHubRestAPI.Value.CheckForEventUpdatesAsync();\n            var EventsTimestamp = 0;\n            if (File.Exists(EventsMetaFilePath))\n            {\n                var metaJson = File.ReadAllText(EventsMetaFilePath);\n                var meta = JsonConvert.DeserializeObject<EventsUpdateResponse>(metaJson);\n                EventsTimestamp = meta.Timestamp;\n            }\n\n            if (response.Timestamp > EventsTimestamp && response.DataVersion == EventsVersion)\n            {\n                _snackbarService.Show(\"Downloading Events Update...\", \"Please wait\", ControlAppearance.Info, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n                UpdateEvents();\n            }\n        }\n\n        private void UpdateTool(object sender, AsyncCompletedEventArgs e)\n        {\n            var path = Environment.ProcessPath.ToString();\n            string[] splitpath = path.Split(\"\\\\\");\n            using (StreamWriter writer = new StreamWriter(\"XAU-Updater.bat\"))\n            {\n                writer.WriteLine(\"@echo off\");\n                writer.WriteLine(\"timeout 1 > nul\");\n                writer.WriteLine(\"del \\\"\" + Environment.ProcessPath + \"\\\" \");\n                writer.WriteLine(\"del \\\"\" + splitpath[splitpath.Count() - 1] + \"\\\" \");\n                writer.WriteLine(\"ren XAU-new.exe \\\"\" + splitpath[splitpath.Count() - 1] + \"\\\" \");\n                writer.WriteLine(\"start \\\"\\\" \" + \"\\\"\" + splitpath[splitpath.Count() - 1] + \"\\\"\");\n                writer.WriteLine(\"goto 2 > nul & del \\\"%~f0\\\"\");\n            }\n            Process proc = new Process();\n            proc.StartInfo.FileName = \"XAU-Updater.bat\";\n            proc.StartInfo.WorkingDirectory = Environment.CurrentDirectory;\n            proc.Start();\n            Environment.Exit(0);\n        }\n\n        private async void UpdateEvents()\n        {\n            string XAUPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\");\n            string backupFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),\n                \"XAU\", \"Events\", \"Backup\");\n            Directory.CreateDirectory(backupFolderPath);\n            string eventsFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),\n                \"XAU\", \"Events\");\n            string[] eventFiles = Directory.GetFiles(eventsFolderPath);\n            string[] backupFiles = Directory.GetFiles(backupFolderPath);\n\n            foreach (string file in backupFiles)\n            {\n                File.Delete(file);\n            }\n            foreach (string eventFile in eventFiles)\n            {\n                string fileName = Path.GetFileName(eventFile);\n                string destinationPath = Path.Combine(backupFolderPath, fileName);\n                File.Move(eventFile, destinationPath, true);\n            }\n\n            string zipFilePath = Path.Combine(XAUPath, \"Events.zip\");\n            string extractPath = XAUPath;\n\n            using (var client = new FileDownloader())\n            {\n                await client.DownloadFileAsync(EventsUrls.Zip, zipFilePath);\n            }\n            ZipFile.ExtractToDirectory(zipFilePath, extractPath);\n            File.Delete(zipFilePath);\n            //download and place meta.json in the events folder\n            string MetaFilePath = Path.Combine(eventsFolderPath, \"meta.json\");\n            using (var client = new FileDownloader())\n            {\n                await client.DownloadFileAsync(EventsUrls.MetaUrl, MetaFilePath);\n            }\n            _snackbarService.Show(\"Events Update Complete\", \"Events have been updated to the latest version.\", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n        }\n\n        private async void CheckForXboxGamesDatabaseUpdate()\n        {\n            try\n            {\n                var fileInfo = await _gitHubRestAPI.Value.GetXboxGamesDatabaseInfoAsync();\n                if (fileInfo == null)\n                {\n                    _snackbarService.Show(\"Error\", \"Could not check for database updates.\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                    return;\n                }\n\n                string titleSearchPath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\"), \"TitleSearch\");\n                string shaFilePath = Path.Combine(titleSearchPath, \"xbox_games_sha.txt\");\n                string dbFilePath = Path.Combine(titleSearchPath, \"xbox_games.db\");\n\n                Directory.CreateDirectory(titleSearchPath);\n\n                string currentSha = string.Empty;\n                try\n                {\n                    if (File.Exists(shaFilePath))\n                    {\n                        currentSha = (await File.ReadAllTextAsync(shaFilePath)).Trim();\n                    }\n                }\n                catch { }\n\n                if (string.IsNullOrEmpty(currentSha) || !currentSha.Equals(fileInfo.Sha, StringComparison.OrdinalIgnoreCase))\n                {\n                    _snackbarService.Show(\"Database Update\", \"New Xbox games database available. Downloading...\", ControlAppearance.Info, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n\n                    using var client = new HttpClient();\n                    var response = await client.GetAsync(fileInfo.DownloadUrl);\n                    response.EnsureSuccessStatusCode();\n\n                    var content = await response.Content.ReadAsByteArrayAsync();\n\n                    await File.WriteAllBytesAsync(dbFilePath, content);\n\n                    try\n                    {\n                        await File.WriteAllTextAsync(shaFilePath, fileInfo.Sha);\n                    }\n                    catch (Exception ex)\n                    {\n                        Console.WriteLine($\"Failed to store database SHA: {ex.Message}\");\n                    }\n\n                    _snackbarService.Show(\"Success\", \"Xbox games database updated successfully!\", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n                }\n                else\n                {\n                    Console.WriteLine(\"Xbox games database is up to date.\");\n                }\n            }\n            catch (Exception ex)\n            {\n                _snackbarService.Show(\"Error\", $\"Database update check failed: {ex.Message}\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n            }\n        }\n\n        #endregion\n\n        private async Task InitializeViewModel()\n        {\n            await CheckForToolUpdates();\n            XauthWorker.DoWork += XauthWorker_DoWork;\n            XauthWorker.ProgressChanged += XauthWorker_ProgressChanged;\n            XauthWorker.RunWorkerCompleted += XauthWorker_RunWorkerCompleted;\n            XauthWorker.WorkerReportsProgress = true;\n            XauthWorker.RunWorkerAsync();\n            EventsTokenWorker.DoWork += EventsTokenWorker_DoWork;\n            if (!File.Exists(SettingsFilePath))\n            {\n                if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),\n                        \"XAU\")))\n                {\n                    Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\"));\n                }\n\n                if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),\n                        \"XAU\\\\Events\")))\n                {\n                    Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\\\\Events\"));\n                }\n                var defaultSettings = new XAUSettings\n                {\n                    SettingsVersion = \"2\",\n                    ToolVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString(),\n                    UnlockAllEnabled = false,\n                    AutoSpooferEnabled = false,\n                    AutoLaunchXboxAppEnabled = false,\n                    FakeSignatureEnabled = true,\n                    RegionOverride = false,\n                    UseAcrylic = false,\n                    PrivacyMode = false,\n                    OAuthLogin = false\n                };\n                string defaultSettingsJson = JsonConvert.SerializeObject(defaultSettings, Formatting.Indented);\n                using (var file = new StreamWriter(SettingsFilePath))\n                {\n                    file.Write(defaultSettingsJson);\n                }\n                if (Settings.OAuthLogin)\n                {\n                    OAuthLogin();\n                }\n\n            }\n            CheckForEventUpdates();\n            CheckForXboxGamesDatabaseUpdate();\n            LoadSettings();\n            if (Settings.OAuthLogin)\n                OAuthLogin();\n            _isInitialized = true;\n            if (Settings.AutoLaunchXboxAppEnabled && Process.GetProcessesByName(ProcessNames.XboxPcApp).Length == 0)\n            {\n                var p = new Process();\n                var startInfo = new ProcessStartInfo\n                {\n                    UseShellExecute = true,\n                    FileName = @\"shell:appsFolder\\Microsoft.GamingApp_8wekyb3d8bbwe!Microsoft.Xbox.App\"\n                };\n\n                if (Settings.LaunchHidden)\n                {\n                    startInfo.WindowStyle = ProcessWindowStyle.Hidden;\n                }\n                p.StartInfo = startInfo;\n                p.Start();\n            }\n        }\n\n        #region Xauth\n        public void XauthWorker_DoWork(object sender, DoWorkEventArgs e)\n        {\n            while (!Settings.OAuthLogin)\n            {\n                if (!m.OpenProcess((ProcessNames.XboxPcApp)))\n                {\n                    IsAttached = false;\n                    Thread.Sleep(1000);\n                }\n                else\n                {\n                    IsAttached = true;\n                }\n                Thread.Sleep(1000);\n                XauthWorker.ReportProgress(0);\n            }\n            Thread.Sleep(5000);\n        }\n        public void XauthWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)\n        {\n            if (IsAttached || XAUTH.Length > 0)\n            {\n                Attached = $\"Attached to xbox app ({m.GetProcIdFromName(ProcessNames.XboxPcApp).ToString()})\";\n                AttachedColor = new SolidColorBrush(Colors.Green);\n                if (IsLoggedIn)\n                {\n                    if (!GrabbedProfile)\n                        GrabProfile();\n                    LoggedIn = \"Logged In\";\n                    LoggedInColor = new SolidColorBrush(Colors.Green);\n                }\n                else\n                {\n                    if (!SettingsViewModel.ManualXauth && !Settings.OAuthLogin)\n                    {\n                        GetXAUTH();\n                        SettingsViewModel.ManualXauth = false;\n                    }\n                    LoggedIn = \"Not Logged In\";\n                    LoggedInColor = new SolidColorBrush(Colors.Red);\n                    if (!XAUTHTested && XAUTH.Length > 0)\n                    {\n                        TestXAUTH();\n                    }\n                }\n            }\n            if (m.GetProcIdFromName(ProcessNames.XboxPcApp) == 0)\n            {\n                Attached = \"Not Attached\";\n                AttachedColor = new SolidColorBrush(Colors.Red);\n            }\n        }\n        public void XauthWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)\n        {\n            if (!XauthWorker.IsBusy)\n                XauthWorker.RunWorkerAsync();\n        }\n        private async void GetXAUTH()\n        {\n            IEnumerable<long> XauthScanList = await m.AoBScan(XAuthScanPattern, true);\n            string[] XauthStrings = new string[XauthScanList.Count()];\n            var i = 0;\n            foreach (var address in XauthScanList)\n            {\n                XauthStrings[i] = m.ReadString(address.ToString(\"X\"), length: 10000);\n                i++;\n            }\n\n            Dictionary<string, int> frequency = new Dictionary<string, int>();\n            foreach (string str in XauthStrings)\n            {\n                if (!frequency.ContainsKey(str))\n                {\n                    frequency[str] = 1;\n                }\n                else\n                {\n                    frequency[str]++;\n                }\n            }\n\n            if (XauthStrings.Length == 0)\n            {\n                return;\n            }\n\n            string mostCommon = XauthStrings[0];\n            int highestFrequency = 0;\n            foreach (KeyValuePair<string, int> pair in frequency)\n            {\n                if (pair.Value > highestFrequency)\n                {\n                    mostCommon = pair.Key;\n                    highestFrequency = pair.Value;\n                }\n            }\n\n            if (highestFrequency > 3)\n            {\n                XAUTH = mostCommon;\n                XAUTHTested = false;\n            }\n        }\n        private async void TestXAUTH()\n        {\n            try\n            {\n                var response = await _xboxRestAPI.Value.GetBasicProfileAsync();\n                if (Settings.PrivacyMode)\n                {\n                    GamerTag = $\"Gamertag: Hidden\";\n                    Xuid = $\"XUID: Hidden\";\n                }\n                else\n                {\n                    GamerTag = $\"Gamertag: {response.ProfileUsers[0].Settings[0].Value}\";\n                    Xuid = $\"XUID: {response.ProfileUsers[0].Id}\";\n                }\n\n                XUIDOnly = response.ProfileUsers[0].Id;\n                IsLoggedIn = true;\n                XAUTHTested = true;\n                InitComplete = true;\n\n                // Start the events token worker to periodically check/refresh the token\n                if (Settings.AutoGrabEventsToken && !EventsTokenWorker.IsBusy)\n                    EventsTokenWorker.RunWorkerAsync();\n            }\n            catch (HttpRequestException ex)\n            {\n                if (ex.StatusCode == HttpStatusCode.Unauthorized)\n                {\n                    IsLoggedIn = false;\n                    XAUTHTested = true;\n\n                }\n            }\n        }\n        #endregion\n\n        #region EventsToken\n        private bool solitaireLaunchedByUs = false;\n\n        private static readonly TimeSpan EventsTokenCheckInterval = TimeSpan.FromMinutes(10);\n        private static readonly TimeSpan EventsTokenMaxAge = TimeSpan.FromHours(23);\n\n        private static DateTime _eventsTokenObtainedAt = DateTime.MinValue;\n        private static string _eventsUserHash = null;\n\n        private static readonly string EventsLogPath = Path.Combine(\n            Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\", \"events_debug.log\");\n\n        public static void EventsLog(string msg)\n        {\n            var line = $\"[{DateTime.Now:HH:mm:ss}] {msg}\";\n            try\n            {\n                Directory.CreateDirectory(Path.GetDirectoryName(EventsLogPath)!);\n                File.AppendAllText(EventsLogPath, line + Environment.NewLine);\n            }\n            catch { }\n        }\n\n\n        public void PersistEventsToken()\n        {\n            try\n            {\n                Settings.CachedEventsToken = AchievementsViewModel.EventsToken;\n                Settings.EventsTokenObtainedAt = _eventsTokenObtainedAt;\n                Settings.EventsUserHash = _eventsUserHash;\n                var json = JsonConvert.SerializeObject(Settings);\n                File.WriteAllText(SettingsFilePath, json);\n            }\n            catch { }\n        }\n\n        public void EventsTokenWorker_DoWork(object sender, DoWorkEventArgs e)\n        {\n            try\n            {\n                EventsTokenWorkerLoop();\n            }\n            catch (Exception ex)\n            {\n                EventsLog($\"Worker crashed: {ex.Message}\");\n            }\n        }\n\n        private void EventsTokenWorkerLoop()\n        {\n            EventsLog(\"Worker started\");\n            // Wait for login before scanning\n            while (!IsLoggedIn)\n            {\n                Thread.Sleep(2000);\n            }\n            EventsLog(\"Logged in, entering refresh loop\");\n\n            // If a token already exists (e.g. from OAuth or cache), mark it as fresh\n            if (!string.IsNullOrEmpty(AchievementsViewModel.EventsToken) && _eventsTokenObtainedAt == DateTime.MinValue)\n                _eventsTokenObtainedAt = DateTime.UtcNow;\n\n            while (true)\n            {\n                if (!Settings.AutoGrabEventsToken || !IsLoggedIn)\n                {\n                    Thread.Sleep(5000);\n                    continue;\n                }\n\n                var currentToken = AchievementsViewModel.EventsToken;\n                bool isEmpty = string.IsNullOrEmpty(currentToken);\n                bool isValid = !isEmpty && IsEventsTokenValid();\n                var tokenAge = DateTime.UtcNow - _eventsTokenObtainedAt;\n                bool isExpired = !isEmpty && isValid && tokenAge > EventsTokenMaxAge;\n\n                EventsLog($\"Check: empty={isEmpty}, valid={isValid}, age={tokenAge.TotalMinutes:F0}m, expired={isExpired}\");\n\n                if (isEmpty || !isValid || isExpired)\n                {\n                    if (isExpired)\n                        EventsLog($\"Token expired (age: {tokenAge.TotalMinutes:F0}m > {EventsTokenMaxAge.TotalMinutes:F0}m), refreshing...\");\n                    else\n                        EventsLog(\"Token missing/invalid, refreshing...\");\n\n                    // Keep capturing until we get a token or settings change\n                    while (Settings.AutoGrabEventsToken && IsLoggedIn)\n                    {\n                        var token = EtwTokenCapture.Capture(20);\n                        if (!string.IsNullOrEmpty(token))\n                        {\n                            AchievementsViewModel.EventsToken = token;\n                            _eventsTokenObtainedAt = DateTime.UtcNow;\n                            PersistEventsToken();\n                            EventsLog(\"ETW capture success.\");\n                            break;\n                        }\n                        EventsLog(\"ETW capture found no token, retrying in 5s...\");\n                        Thread.Sleep(5000);\n                    }\n                }\n\n                EventsLog($\"Sleeping {EventsTokenCheckInterval.TotalMinutes:F0}m...\");\n                Thread.Sleep(EventsTokenCheckInterval);\n            }\n        }\n\n        /// <summary>\n        /// Launches Solitaire (if needed), then continuously captures ETW network traffic\n        /// and scans for the events token until one is found or Solitaire exits.\n        /// </summary>\n        private void GrabEventsTokenFromSolitaire()\n        {\n            bool alreadyRunning = Process.GetProcessesByName(ProcessNames.Solitaire).Length > 0;\n            EventsLog($\"Solitaire already running: {alreadyRunning}\");\n\n            // Start ETW capture FIRST — the token is sent in the initial telemetry burst\n            // when Solitaire contacts Xbox Live, which happens within seconds of launch.\n            EventsLog(\"Starting ETW before Solitaire launch...\");\n            EtwTokenCapture.Cleanup();\n            string method = EtwTokenCapture.Start();\n            if (method == null)\n            {\n                EventsLog(\"Failed to start ETW trace (not running as admin?)\");\n                return;\n            }\n            EventsLog($\"ETW started via {method}\");\n\n            if (!alreadyRunning)\n            {\n                try\n                {\n                    EventsLog(\"Launching Solitaire...\");\n                    var p = new Process();\n                    p.StartInfo = new ProcessStartInfo\n                    {\n                        UseShellExecute = true,\n                        FileName = AppLaunchUris.Solitaire\n                    };\n                    p.Start();\n                    solitaireLaunchedByUs = true;\n                }\n                catch (Exception ex)\n                {\n                    EventsLog($\"Failed to launch Solitaire: {ex.Message}\");\n                    EtwTokenCapture.Stop(method);\n                    EtwTokenCapture.CleanupFiles();\n                    return;\n                }\n\n                // Wait for the process to appear\n                for (int i = 0; i < 15; i++)\n                {\n                    Thread.Sleep(1000);\n                    if (Process.GetProcessesByName(ProcessNames.Solitaire).Length > 0)\n                    {\n                        EventsLog($\"Solitaire process appeared after {i + 1}s\");\n                        break;\n                    }\n                }\n\n                if (Process.GetProcessesByName(ProcessNames.Solitaire).Length == 0)\n                {\n                    EventsLog(\"Solitaire never appeared after 15s\");\n                    solitaireLaunchedByUs = false;\n                    EtwTokenCapture.Stop(method);\n                    EtwTokenCapture.CleanupFiles();\n                    return;\n                }\n            }\n\n            // Wait for Xbox Live init + initial telemetry burst (token is sent here)\n            EventsLog(\"Waiting 25s for Xbox Live init + telemetry events...\");\n            Thread.Sleep(25000);\n\n            // Stop first capture and try to extract\n            EtwTokenCapture.Stop(method);\n            Thread.Sleep(2000);\n\n            string token = EtwTokenCapture.ExtractTokens();\n            EtwTokenCapture.CleanupFiles();\n\n            if (!string.IsNullOrEmpty(token))\n            {\n                EventsLog($\"ETW capture success on initial capture, len={token.Length}\");\n                AchievementsViewModel.EventsToken = token;\n                _eventsTokenObtainedAt = DateTime.UtcNow;\n                eventsTokenFound = true;\n            }\n            else\n            {\n                // Loop: keep capturing while Solitaire is running\n                EventsLog(\"Initial capture found nothing, entering continuous scan loop...\");\n                int attempt = 0;\n                while (!eventsTokenFound && Process.GetProcessesByName(ProcessNames.Solitaire).Length > 0)\n                {\n                    attempt++;\n                    EventsLog($\"Capture attempt {attempt}...\");\n\n                    token = EtwTokenCapture.Capture(20);\n                    if (!string.IsNullOrEmpty(token))\n                    {\n                        EventsLog($\"ETW capture success on attempt {attempt}, len={token.Length}\");\n                        AchievementsViewModel.EventsToken = token;\n                        _eventsTokenObtainedAt = DateTime.UtcNow;\n                        eventsTokenFound = true;\n                        break;\n                    }\n\n                    // Brief pause before next capture\n                    Thread.Sleep(3000);\n                }\n\n                if (!eventsTokenFound)\n                    EventsLog(\"Solitaire exited before token was found\");\n            }\n\n            // Close Solitaire if we launched it\n            if (solitaireLaunchedByUs)\n            {\n                try\n                {\n                    foreach (var proc in Process.GetProcessesByName(ProcessNames.Solitaire))\n                        proc.Kill();\n                }\n                catch { }\n                solitaireLaunchedByUs = false;\n            }\n        }\n\n        /// <summary>\n        /// Manually triggers a scan (from the \"Manually Refresh Token\" button).\n        /// Works regardless of the auto-grab setting.\n        /// </summary>\n        public bool ManualScanRunning { get; private set; }\n\n        public void ScanForEventsTokenManual()\n        {\n            eventsTokenFound = false;\n            AchievementsViewModel.EventsToken = null;\n            _eventsTokenObtainedAt = DateTime.MinValue;\n            ManualScanRunning = true;\n\n            System.Threading.Tasks.Task.Run(() =>\n            {\n                try\n                {\n                    GrabEventsTokenFromSolitaire();\n                    if (!string.IsNullOrEmpty(AchievementsViewModel.EventsToken))\n                    {\n                        _eventsTokenObtainedAt = DateTime.UtcNow;\n                        PersistEventsToken();\n                    }\n                }\n                finally\n                {\n                    ManualScanRunning = false;\n                }\n            });\n        }\n\n        /// <summary>\n        /// Returns whether the current events token looks structurally valid.\n        /// </summary>\n        public static bool IsEventsTokenValid()\n        {\n            var token = AchievementsViewModel.EventsToken;\n            return !string.IsNullOrWhiteSpace(token)\n                && token.StartsWith(\"x:XBL3.0 x=\")\n                && token.Length > 30;\n        }\n\n        /// <summary>\n        /// Returns whether the current events token has exceeded its max age.\n        /// </summary>\n        public static bool IsEventsTokenExpired()\n        {\n            if (_eventsTokenObtainedAt == DateTime.MinValue)\n                return false; // no timestamp means we can't determine expiry\n            return (DateTime.UtcNow - _eventsTokenObtainedAt) > EventsTokenMaxAge;\n        }\n\n        /// <summary>\n        /// The UTC time the current events token was obtained.\n        /// </summary>\n        public static DateTime EventsTokenObtainedAtUtc => _eventsTokenObtainedAt;\n\n        /// <summary>\n        /// The UTC time the current events token is expected to expire.\n        /// </summary>\n        public static DateTime? EventsTokenExpiresAtUtc =>\n            _eventsTokenObtainedAt == DateTime.MinValue\n                ? null\n                : _eventsTokenObtainedAt + EventsTokenMaxAge;\n        #endregion\n\n        #region OAuthLogin\n\n        [RelayCommand]\n        private async void OAuthLogin()\n        {\n            //init oauth stuff\n            var OAuthhttpClient = new HttpClient();\n            var apiClient = new CodeFlowLiveApiClient(XboxGameTitles.XboxAppPC, XboxAuthConstants.XboxScope, OAuthhttpClient);\n            xboxAuthClient = new XboxAuthClient(OAuthhttpClient);\n            xboxSignedClient = new XboxSignedClient(OAuthhttpClient);\n            oauth = new CodeFlowBuilder(apiClient)\n                .WithUIParent(this)\n                .Build();\n            if (LoginText == \"Logout\")\n            {\n                oauth.Signout();\n                try { File.Delete(AuthFilePath); } catch { }\n                ClearProfileState();\n                LoginText = \"Login\";\n                return;\n            }\n            Settings.OAuthLogin = true;\n\n            // Use saved session if valid; otherwise interactive login\n            MicrosoftOAuthResponse? response = await TryRestoreSessionAsync();\n            if (response == null)\n                response = await TryInteractiveLoginAsync();\n        }\n\n        private void DeleteAuthFile()\n        {\n            try { File.Delete(AuthFilePath); } catch { }\n        }\n\n        private void CompleteLogin(MicrosoftOAuthResponse response, string? successMessage = null)\n        {\n            writeSession(response);\n            if (!string.IsNullOrEmpty(successMessage))\n                _snackbarService.Show(\"Success\", successMessage, ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n            GenerateTokens(response);\n        }\n\n        private async Task<MicrosoftOAuthResponse?> TryRestoreSessionAsync()\n        {\n            if (!File.Exists(AuthFilePath))\n                return null;\n\n            MicrosoftOAuthResponse? response;\n            try\n            {\n                response = readSession();\n            }\n            catch\n            {\n                DeleteAuthFile();\n                _snackbarService.Show(\"Session invalid\", \"Saved session could not be read. Please log in again.\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                return null;\n            }\n\n            if (response == null || !response.Validate() || string.IsNullOrEmpty(response.RefreshToken))\n            {\n                DeleteAuthFile();\n                if (response != null && !response.Validate())\n                    _snackbarService.Show(\"Session expired\", \"Your saved session has expired. Please log in again.\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                return null;\n            }\n\n            try\n            {\n                response = await oauth.AuthenticateSilently(response.RefreshToken!);\n                CompleteLogin(response, \"Logged in with previous session\");\n                return response;\n            }\n            catch\n            {\n                _snackbarService.Show(\"Session invalid\", \"You are required to log in again as the session has expired\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                ClearProfileState();\n                return await TryInteractiveLoginAsync();\n            }\n        }\n\n        private async Task<MicrosoftOAuthResponse?> TryInteractiveLoginAsync()\n        {\n            try\n            {\n                var response = await oauth.AuthenticateInteractively();\n                CompleteLogin(response, \"Logged in\");\n                return response;\n            }\n            catch\n            {\n                _snackbarService.Show(\"Error\", \"Failed to authenticate\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                return null;\n            }\n        }\n\n        private async void GenerateTokens(MicrosoftOAuthResponse response)\n        {\n            var deviceToken = await xboxSignedClient.RequestDeviceToken(XboxDeviceTypes.Win32, \"0.0.0\");\n            var sisuResult = await xboxSignedClient.SisuAuth(new XboxSisuAuthRequest\n            {\n                AccessToken = response.AccessToken,\n                ClientId = XboxGameTitles.XboxAppPC,\n                DeviceToken = deviceToken.Token,\n                RelyingParty = XboxAuthConstants.XboxLiveRelyingParty,\n            });\n            try\n            {\n                XAUTH = $\"XBL3.0 x={sisuResult.AuthorizationToken.XuiClaims.UserHash};{sisuResult.AuthorizationToken.Token}\";\n                var xui = sisuResult.AuthorizationToken.XuiClaims;\n                XUIDOnly = xui?.XboxUserId ?? \"\";\n                if (!string.IsNullOrEmpty(XUIDOnly))\n                {\n                    IsLoggedIn = true;\n                    XAUTHTested = true;\n                    InitComplete = true;\n                    if (Settings.PrivacyMode)\n                    {\n                        GamerTag = \"Gamertag: Hidden\";\n                        Xuid = \"XUID: Hidden\";\n                    }\n                    else\n                    {\n                        GamerTag = $\"Gamertag: {xui?.Gamertag ?? \"Unknown\"}\";\n                        Xuid = $\"XUID: {XUIDOnly}\";\n                    }\n                }\n            }\n            catch\n            {\n                _snackbarService.Show(\"Error\", \"Failed to generate XAUTH\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n            }\n            LoginText = \"Logout\";\n            XauthWorker_ProgressChanged(null, null);\n            if (IsLoggedIn && !GrabbedProfile)\n                GrabProfile();\n\n            // Start the events token worker to periodically check/refresh the token\n            if (Settings.AutoGrabEventsToken && !EventsTokenWorker.IsBusy)\n                EventsTokenWorker.RunWorkerAsync();\n        }\n        private void ClearProfileState()\n        {\n            IsLoggedIn = false;\n            XAUTHTested = false;\n            GrabbedProfile = false;\n            XAUTH = \"\";\n            XUIDOnly = \"\";\n            GamerTag = \"Gamertag: Unknown   \";\n            Xuid = \"XUID: Unknown\";\n            GamerPic = \"pack://application:,,,/Assets/cirno.png\";\n            GamerScore = \"Gamerscore: Unknown\";\n            ProfileRep = \"Reputation: Unknown\";\n            AccountTier = \"Tier: Unknown\";\n            CurrentlyPlaying = \"Currently Playing: Unknown\";\n            ActiveDevice = \"Active Device: Unknown\";\n            IsVerified = \"Verified: Unknown\";\n            Location = \"Location: Unknown\";\n            Tenure = \"Tenure: Unknown\";\n            Following = \"Following: Unknown\";\n            Followers = \"Followers: Unknown\";\n            Gamepass = \"Gamepass: Unknown\";\n            Bio = \"Bio: Unknown\";\n            Watermarks.Clear();\n            AchievementsViewModel.EventsToken = null;\n            _eventsTokenObtainedAt = DateTime.MinValue;\n            _eventsUserHash = null;\n            XauthWorker_ProgressChanged(null, null);\n        }\n\n        private static readonly byte[] AuthFileMagic = Encoding.ASCII.GetBytes(\"XAU1\");\n        private static readonly byte[] AuthDpapiEntropy = Encoding.UTF8.GetBytes(\"XAU-Auth-v1\");\n\n        private MicrosoftOAuthResponse readSession()\n        {\n            var raw = File.ReadAllBytes(AuthFilePath);\n            string json;\n            if (raw.Length >= AuthFileMagic.Length && raw.AsSpan(0, AuthFileMagic.Length).SequenceEqual(AuthFileMagic))\n            {\n                var encrypted = raw.AsSpan(AuthFileMagic.Length).ToArray();\n                var plain = ProtectedData.Unprotect(encrypted, AuthDpapiEntropy, DataProtectionScope.CurrentUser);\n                json = Encoding.UTF8.GetString(plain);\n            }\n            else\n            {\n                json = Encoding.UTF8.GetString(raw);\n            }\n            var response = JsonConvert.DeserializeObject<MicrosoftOAuthResponse>(json);\n            return response;\n        }\n\n        private void writeSession(MicrosoftOAuthResponse response)\n        {\n            var json = JsonConvert.SerializeObject(response);\n            var plain = Encoding.UTF8.GetBytes(json);\n            var encrypted = ProtectedData.Protect(plain, AuthDpapiEntropy, DataProtectionScope.CurrentUser);\n            using (var fs = new FileStream(AuthFilePath, FileMode.Create, FileAccess.Write, FileShare.None))\n            {\n                fs.Write(AuthFileMagic, 0, AuthFileMagic.Length);\n                fs.Write(encrypted, 0, encrypted.Length);\n            }\n        }\n\n        #endregion\n        #region Profile\n        private async void GrabProfile()\n        {\n            try\n            {\n                var profileResponse = await _xboxRestAPI.Value.GetProfileAsync(XUIDOnly);\n\n                if (profileResponse?.People?.Any() != true)\n                {\n                    _snackbarService.Show(\"Error\", \"Failed to grab profile information.\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                    return;\n                }\n\n                var person = profileResponse.People.FirstOrDefault();\n                if (Settings.PrivacyMode)\n                {\n                    // Display hidden profile details for privacy mode\n                    GamerTag = \"Gamertag: Hidden\";\n                    Xuid = \"XUID: Hidden\";\n                    GamerPic = \"pack://application:,,,/Assets/cirno.png\";\n                    GamerScore = \"Gamerscore: Hidden\";\n                    ProfileRep = \"Reputation: Hidden\";\n                    AccountTier = \"Tier: Hidden\";\n                    CurrentlyPlaying = \"Currently Playing: Hidden\";\n                    ActiveDevice = \"Active Device: Hidden\";\n                    IsVerified = \"Verified: Hidden\";\n                    Location = \"Location: Hidden\";\n                    Tenure = \"Tenure: Hidden\";\n                    Following = \"Following: Hidden\";\n                    Followers = \"Followers: Hidden\";\n                    Gamepass = \"Gamepass: Hidden\";\n                    Bio = \"Bio: Hidden\";\n                }\n                else\n                {\n                    // Populate user profile details\n                    GamerTag = $\"Gamertag: {person?.Gamertag ?? \"Unknown\"}\";\n                    Xuid = $\"XUID: {person?.Xuid ?? \"Unknown\"}\";\n                    GamerPic = (person?.DisplayPicRaw?.Replace(\"&mode=Padding\", \"\")) ?? \"pack://application:,,,/Assets/default.png\";\n                    GamerScore = $\"Gamerscore: {person?.GamerScore ?? \"Unknown\"}\";\n                    ProfileRep = $\"Reputation: {person?.XboxOneRep ?? \"Unknown\"}\";\n                    AccountTier = $\"Tier: {person?.Detail?.AccountTier ?? \"Unknown\"}\";\n\n                    // Currently playing information\n                    var presence = person?.PresenceDetails?.FirstOrDefault();\n                    if (presence?.TitleId == null)\n                    {\n                        CurrentlyPlaying = \"Currently Playing: Unknown (No Presence)\";\n                    }\n                    else\n                    {\n                        var gameTitle = await _xboxRestAPI.Value.GetGameTitleAsync(XUIDOnly, presence.TitleId);\n                        CurrentlyPlaying = gameTitle?.Titles?.FirstOrDefault()?.Name ?? $\"Currently Playing: Unknown ({presence.TitleId})\";\n                    }\n\n                    // Retrieve Gamepass Membership Information\n                    try\n                    {\n                        var gpuResponse = await _xboxRestAPI.Value.GetGamepassMembershipAsync(XUIDOnly);\n                        Gamepass = $\"Gamepass: {gpuResponse?.GamepassMembership ?? gpuResponse?.Data?.GamepassMembership ?? \"Unknown\"}\";\n                    }\n                    catch\n                    {\n                        Gamepass = \"Gamepass: Unknown\";\n                    }\n\n                    // Active Device Information\n                    ActiveDevice = $\"Active Device: {presence?.Device ?? \"Unknown\"}\";\n\n                    // Detailed profile information\n                    if (person?.Detail != null)\n                    {\n                        IsVerified = $\"Verified: {person.Detail.IsVerified}\";\n                        Location = $\"Location: {person.Detail.Location ?? \"Unknown\"}\";\n                        Tenure = $\"Tenure: {person.Detail.Tenure ?? \"Unknown\"}\";\n                        Following = $\"Following: {person.Detail.FollowingCount}\";\n                        Followers = $\"Followers: {person.Detail.FollowerCount}\";\n                        Bio = $\"Bio: {person.Detail.Bio ?? \"No Bio\"}\";\n\n                        // Handle Watermarks\n                        Watermarks.Clear();\n\n                        // Parse Tenure Badge\n                        if (int.TryParse(person.Detail.Tenure, out int tenureInt))\n                        {\n                            string tenureBadge = tenureInt.ToString(\"D2\");\n                            Watermarks.Add(new ImageItem { ImageUrl = $@\"{BasicXboxAPIUris.WatermarksUrl}tenure/{tenureBadge}.png\" });\n                        }\n                        else\n                        {\n                            Console.WriteLine(\"The tenure string is not a valid integer.\");\n                        }\n\n                        // Add Launch Watermarks\n                        if (person.Detail.Watermarks != null)\n                        {\n                            foreach (var watermark in person.Detail.Watermarks)\n                            {\n                                Watermarks.Add(new ImageItem { ImageUrl = $@\"{BasicXboxAPIUris.WatermarksUrl}launch/{watermark.ToLower()}.png\" });\n                            }\n                        }\n                    }\n                }\n\n                GrabbedProfile = true;\n                _snackbarService.Show(\"Success\", \"Profile information grabbed.\", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n            }\n            catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)\n            {\n                IsLoggedIn = false;\n                XAUTHTested = true;\n                _snackbarService.Show(\"401 Unauthorized\", \"Something went wrong. Retrying.\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n            }\n            catch (Exception ex)\n            {\n                _snackbarService.Show(\"Error\", \"Failed to grab profile information. \" + ex.Message, ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n            }\n        }\n        #endregion\n\n        #region Settings\n\n        public static XAUSettings Settings = new();\n\n        private void LoadSettings()\n        {\n            var settingsJson = File.ReadAllText(SettingsFilePath);\n            var settings = JsonConvert.DeserializeObject<XAUSettings>(settingsJson);\n            if (settings == null)\n            {\n                _snackbarService.Show(\n                    \"Error\",\n                    \"Couldn't load settings.\",\n                    ControlAppearance.Danger,\n                    new SymbolIcon(SymbolRegular.ErrorCircle24)\n                );\n                return;\n            }\n\n            Settings.SettingsVersion = settings.SettingsVersion;\n            Settings.ToolVersion = settings.ToolVersion;\n            Settings.UnlockAllEnabled = settings.UnlockAllEnabled;\n            Settings.AutoSpooferEnabled = settings.AutoSpooferEnabled;\n            Settings.AutoLaunchXboxAppEnabled = settings.AutoLaunchXboxAppEnabled;\n            Settings.LaunchHidden = settings.LaunchHidden;\n            Settings.FakeSignatureEnabled = settings.FakeSignatureEnabled;\n            Settings.RegionOverride = settings.RegionOverride;\n            Settings.UseAcrylic = settings.UseAcrylic;\n            Settings.PrivacyMode = settings.PrivacyMode;\n            Settings.OAuthLogin = settings.OAuthLogin;\n            Settings.AutoGrabEventsToken = settings.AutoGrabEventsToken;\n            Settings.CachedEventsToken = settings.CachedEventsToken;\n            Settings.EventsTokenObtainedAt = settings.EventsTokenObtainedAt;\n            Settings.EventsUserHash = settings.EventsUserHash;\n            _eventsUserHash = settings.EventsUserHash;\n\n            // Restore cached events token if it's still fresh\n            if (!string.IsNullOrEmpty(settings.CachedEventsToken) && settings.EventsTokenObtainedAt.HasValue)\n            {\n                var age = DateTime.UtcNow - settings.EventsTokenObtainedAt.Value;\n                if (age < EventsTokenMaxAge)\n                {\n                    AchievementsViewModel.EventsToken = settings.CachedEventsToken;\n                    _eventsTokenObtainedAt = settings.EventsTokenObtainedAt.Value;\n                    EventsLog($\"Restored cached events token (age: {age.TotalHours:F1}h)\");\n                }\n                else\n                {\n                    EventsLog($\"Cached events token expired (age: {age.TotalHours:F1}h), will re-grab\");\n                }\n            }\n        }\n\n        #endregion\n    }\n}\n"
  },
  {
    "path": "XAU/ViewModels/Pages/InfoViewModel.cs",
    "content": "using Wpf.Ui.Controls;\n\nnamespace XAU.ViewModels.Pages\n{\n    public partial class InfoViewModel : ObservableObject, INavigationAware\n    {\n        private bool _isInitialized = false;\n        [ObservableProperty] private string? _toolVersion;\n        public void OnNavigatedTo()\n        {\n            if (!_isInitialized)\n                InitializeViewModel();\n        }\n        public void OnNavigatedFrom() { }\n\n        private void InitializeViewModel()\n        {\n            ToolVersion = $\"Version: {HomeViewModel.ToolVersion}\";\n            _isInitialized = true;\n        }\n\n        [RelayCommand]\n        public void OpenDiscordUrl(string url)\n        {\n            var destinationurl = OpenableLinks.Discord;\n            var sInfo = new System.Diagnostics.ProcessStartInfo(destinationurl)\n            {\n                UseShellExecute = true,\n            };\n            System.Diagnostics.Process.Start(sInfo);\n        }\n        [RelayCommand]\n        public void OpenGithubUserUrl(string url)\n        {\n            var destinationurl = OpenableLinks.GitHubUserUrl;\n            var sInfo = new System.Diagnostics.ProcessStartInfo(destinationurl)\n            {\n                UseShellExecute = true,\n            };\n            System.Diagnostics.Process.Start(sInfo);\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/ViewModels/Pages/MiscViewModel.cs",
    "content": "using HtmlAgilityPack;\nusing Microsoft.Data.Sqlite;\nusing Newtonsoft.Json.Linq;\nusing System.Data;\nusing System.Diagnostics;\nusing System.DirectoryServices;\nusing System.IO;\nusing System.Text;\nusing System.Windows.Input;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls;\nusing Wpf.Ui.Services;\n\n\nnamespace XAU.ViewModels.Pages\n{\n    public partial class MiscViewModel : ObservableObject, INavigationAware\n    {\n        private readonly IContentDialogService _contentDialogService;\n        private readonly ISnackbarService _snackbarService;\n        private TimeSpan _snackbarDuration = TimeSpan.FromSeconds(2);\n        private Lazy<XboxRestAPI> _xboxRestAPI = new Lazy<XboxRestAPI>(() => new XboxRestAPI(HomeViewModel.XAUTH));\n\n\n\n        public MiscViewModel(ISnackbarService snackbarService)\n        {\n            _snackbarService = snackbarService;\n            _contentDialogService = new ContentDialogService();\n        }\n\n        public void OnNavigatedTo()\n        {\n            if (!IsInitialized && HomeViewModel.InitComplete)\n                InitializeViewModel();\n        }\n\n        public void OnNavigatedFrom()\n        {\n        }\n\n        private void InitializeViewModel()\n        {\n            IsInitialized = true;\n        }\n\n        #region Spoofer\n\n        [ObservableProperty] private string _gameName = \"Name: \";\n        [ObservableProperty] private string _gameTitleID = \"Title ID: \";\n        [ObservableProperty] private string _gamePFN = \"PFN: \";\n        [ObservableProperty] private string _gameType = \"Type: \";\n        [ObservableProperty] private string _gameGamepass = \"Gamepass: \";\n        [ObservableProperty] private string _gameDevices = \"Devices: \";\n        [ObservableProperty] private string _gameGamerscore = \"Gamerscore: ?/?\";\n        [ObservableProperty] private string? _gameImage = \"pack://application:,,,/Assets/cirno.png\";\n        [ObservableProperty] private string _gameTime = \"Time Played: \";\n        [ObservableProperty] private bool _isInitialized = false;\n        [ObservableProperty] private string _currentSpoofingID = \"\";\n        [ObservableProperty] private string _newSpoofingID = \"\";\n        [ObservableProperty] private string _spoofingText = \"Spoofing Not Started\";\n        [ObservableProperty] private string _spoofingButtonText = \"Start Spoofing\";\n        private bool SpoofingUpdate = false;\n        private bool CurrentlySpoofing = false;\n        private GameTitle GameInfoResponse;\n        private GameStatsResponse GameStatsResponse;\n\n        [RelayCommand]\n        public async Task SpooferButtonClicked()\n        {\n            if (CurrentlySpoofing)\n            {\n                SpoofingUpdate = true;\n                CurrentlySpoofing = false;\n                SpoofingText = \"Spoofing Not Started\";\n                SpoofingButtonText = \"Start Spoofing\";\n                //reset game info\n                GameName = \"Name: \";\n                GameTitleID = \"Title ID: \";\n                GamePFN = \"PFN: \";\n                GameType = \"Type: \";\n                GameGamepass = \"Gamepass: \";\n                GameDevices = \"Devices: \";\n                GameGamerscore = \"Gamerscore: ?/?\";\n                GameImage = \"pack://application:,,,/Assets/cirno.png\";\n                GameTime = \"Time Played: \";\n                HomeViewModel.SpoofingStatus = 0;\n                await _xboxRestAPI.Value.StopHeartbeatAsync(HomeViewModel.XUIDOnly);\n                return;\n            }\n            HomeViewModel.SpoofedTitleID = NewSpoofingID;\n\n            if (HomeViewModel.SpoofingStatus == 2)\n            {\n                HomeViewModel.SpoofingStatus = 1;\n                AchievementsViewModel.SpoofingUpdate = true;\n            }\n            HomeViewModel.SpoofingStatus = 1;\n            SpoofGame();\n        }\n\n        public async void SpoofGame()\n        {\n            CurrentSpoofingID = NewSpoofingID;\n            GameInfoResponse = await _xboxRestAPI.Value.GetGameTitleAsync(HomeViewModel.XUIDOnly, NewSpoofingID);\n            GameStatsResponse = await _xboxRestAPI.Value.GetGameStatsAsync(HomeViewModel.XUIDOnly, NewSpoofingID);\n\n            if (GameInfoResponse == null || GameStatsResponse == null || !GameInfoResponse.Titles.Any())\n            {\n                _snackbarService.Show(\"Error: Unable to acquire game info or stats\",\n                    $\"The game info was invalid.\",\n                    ControlAppearance.Danger,\n                    new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                return;\n            }\n\n            try\n            {\n                GameName = \"Name: \" + GameInfoResponse.Titles[0].Name;\n                GameImage = !string.IsNullOrEmpty(GameInfoResponse.Titles[0].DisplayImage.ToString()) ? GameInfoResponse.Titles[0].DisplayImage.ToString() : \"pack://application:,,,/Assets/cirno.png\";\n                GameTitleID = \"Title ID: \" + GameInfoResponse.Titles[0].TitleId;\n                GamePFN = \"PFN: \" + GameInfoResponse.Titles[0].Pfn;\n                GameType = \"Type: \" + GameInfoResponse.Titles[0].Type;\n                GameGamepass = \"Gamepass: \" + GameInfoResponse.Titles[0].GamePass?.IsGamePass;\n                GameDevices = \"Devices: \";\n                foreach (var device in GameInfoResponse.Titles[0].Devices)\n                {\n                    GameDevices += device.ToString() + \", \";\n                }\n\n                GameDevices = GameDevices.Remove(GameDevices.Length - 2);\n                GameGamerscore = \"Gamerscore: \" + GameInfoResponse.Titles[0].Achievement?.CurrentGamerscore.ToString() +\n                                 \"/\" + GameInfoResponse.Titles[0].Achievement?.TotalGamerscore.ToString();\n                try\n                {\n                    var timePlayed = TimeSpan.FromMinutes(Convert.ToDouble(GameStatsResponse.StatListsCollection[0].Stats[0].Value));\n                    var formattedTime = $\"{timePlayed.Days} Days, {timePlayed.Hours} Hours and {timePlayed.Minutes} minutes\";\n                    GameTime = \"Time Played: \" + formattedTime;\n                }\n                catch\n                {\n                    GameTime = \"Time Played: Unknown\";\n                }\n\n            }\n            catch\n            {\n                GameName = \"Name: \";\n                _snackbarService.Show(\"Error: Invalid TitleID\",\n                    $\"The TitleID entered is invalid or does not return information from the API\",\n                    ControlAppearance.Danger,\n                    new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                return;\n            }\n\n            SpoofingUpdate = true;\n            CurrentlySpoofing = true;\n            SpoofingButtonText = \"Stop Spoofing\";\n            SpoofingText = $\"Spoofing {GameInfoResponse.Titles[0].Name}\";\n            await Task.Run(() => Spoofing());\n\n        }\n\n        // TODO: this code seems like it's duplicated in AchievementsViewModel.cs too.\n        public async Task Spoofing()\n        {\n            Stopwatch stopwatch = new Stopwatch();\n            stopwatch.Start();\n            TimeSpan spoofingTime = stopwatch.Elapsed;\n            SpoofingText = $\"Spoofing {GameName} For: {spoofingTime.ToString(@\"hh\\:mm\\:ss\")}\";\n            await _xboxRestAPI.Value.SendHeartbeatAsync(HomeViewModel.XUIDOnly, CurrentSpoofingID);\n            var i = 0;\n            Thread.Sleep(1000);\n            SpoofingUpdate = false;\n            while (!SpoofingUpdate)\n            {\n                if (i == 300)\n                {\n                    await _xboxRestAPI.Value.SendHeartbeatAsync(HomeViewModel.XUIDOnly, CurrentSpoofingID);\n                    i = 0;\n                }\n                else\n                {\n                    if (SpoofingUpdate)\n                    {\n                        HomeViewModel.SpoofingStatus = 0;\n                        HomeViewModel.SpoofedTitleID = \"0\";\n                        break;\n                    }\n                    spoofingTime = stopwatch.Elapsed;\n                    SpoofingText = $\"Spoofing {GameInfoResponse.Titles[0].Name} For: {spoofingTime.ToString(@\"hh\\:mm\\:ss\")}\";\n                    i++;\n                }\n                Thread.Sleep(1000);\n            }\n        }\n\n        #endregion\n\n        #region GameSearch\n        [ObservableProperty] private List<GameItem> _tSearchResults = new List<GameItem>();\n        [ObservableProperty] private List<string> _tSearchTitleNames = new List<string>();\n        [ObservableProperty] private string _tSearchText = \"\";\n        [ObservableProperty] private string _tSearchGameName = \"Name: \";\n        [ObservableProperty] private string _tSearchGameTitleID = \"\";\n        [ObservableProperty] private string _tSearchGameTitleBased = \"Title Based: Unknown\";\n\n        private string GetDatabasePath()\n        {\n            return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\", \"TitleSearch\", \"xbox_games.db\");\n        }\n\n        [RelayCommand]\n        public async Task SearchGame()\n        {\n            try\n            {\n                if (string.IsNullOrWhiteSpace(TSearchText))\n                {\n                    TSearchTitleNames = new List<string>();\n                    TSearchResults = new List<GameItem>();\n                    return;\n                }\n\n                string dbPath = GetDatabasePath();\n\n                if (!File.Exists(dbPath))\n                {\n                    _snackbarService.Show(\"Error\", \"Game database not found. Please wait for it to download.\",\n                        ControlAppearance.Danger,\n                        new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                    return;\n                }\n\n                var results = await Task.Run(() => SearchGamesInDatabase(dbPath, TSearchText));\n\n                if (!results.Any())\n                {\n                    _snackbarService.Show(\"Error\", $\"No results were found for '{TSearchText}'\",\n                        ControlAppearance.Danger,\n                        new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                    TSearchTitleNames = new List<string>();\n                    TSearchResults = new List<GameItem>();\n                    return;\n                }\n\n                results = results.OrderBy(game => game.Title, StringComparer.OrdinalIgnoreCase).ToList();\n\n                TSearchResults = results;\n                TSearchTitleNames = results.Select(game => game.Title).ToList();\n            }\n            catch (Exception ex)\n            {\n                _snackbarService.Show(\"Error\", $\"Search failed: {ex.Message}\",\n                    ControlAppearance.Danger,\n                    new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                TSearchTitleNames = new List<string>();\n                TSearchResults = new List<GameItem>();\n            }\n        }\n\n        private List<GameItem> SearchGamesInDatabase(string dbPath, string searchText)\n        {\n            var results = new List<GameItem>();\n\n            using var connection = new SqliteConnection($\"Data Source={dbPath}\");\n            connection.Open();\n\n            // Search for games that contain the search text (case-insensitive)\n            string sql = @\"\n                    SELECT title, titleId, isTitleBased \n                    FROM games \n                    WHERE title LIKE @searchText \n                    ORDER BY title COLLATE NOCASE\n                    LIMIT 100\";\n\n            using var command = new SqliteCommand(sql, connection);\n            command.Parameters.AddWithValue(\"@searchText\", $\"%{searchText}%\");\n\n            using var reader = command.ExecuteReader();\n            while (reader.Read())\n            {\n                results.Add(new GameItem\n                {\n                    Title = reader.GetString(\"title\"),\n                    TitleId = reader.GetString(\"titleId\"),\n                    IsTitleBased = reader.GetInt32(\"isTitleBased\") == 1\n                });\n            }\n\n            return results;\n        }\n\n        public void DisplayGameInfo(int index)\n        {\n            try\n            {\n                if (TSearchResults == null || TSearchResults.Count <= index || index < 0)\n                {\n                    _snackbarService.Show(\"Error\", \"No game found at the selected index.\",\n                        ControlAppearance.Danger,\n                        new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                    return;\n                }\n\n                var selectedGame = TSearchResults[index];\n\n                TSearchGameName = selectedGame.Title;\n                TSearchGameTitleID = selectedGame.TitleId;\n                TSearchGameTitleBased = $\"Title Based: {(selectedGame.IsTitleBased ? \"True\" : \"False\")}\";\n\n            }\n            catch (Exception ex)\n            {\n                _snackbarService.Show(\"Error\", \"Failed to display game info.\",\n                    ControlAppearance.Danger,\n                    new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n            }\n        }\n\n        #endregion\n\n        #region GamertagSearch\n        [ObservableProperty] private string _gamertag = \"\";\n        [ObservableProperty] private string _gamertagName = \"Gamertag:\";\n        [ObservableProperty] private string _gamertagImage = \"pack://application:,,,/Assets/cirno.png\";\n        [ObservableProperty] private string _gamertagScore = \"Gamerscore: \";\n        [ObservableProperty] private string _gamertagXuid;\n        [ObservableProperty] private bool _excludeZeroGamerscoreGames;\n        [ObservableProperty] private bool _excludeXbox360Games;\n\n        [RelayCommand]\n        public async Task SearchGamertag()\n        {\n            if (string.IsNullOrWhiteSpace(Gamertag))\n            {\n                _snackbarService.Show(\"Error\", \"Please enter a valid gamertag.\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                return;\n            }\n\n            var profileData = await _xboxRestAPI.Value.GetGamertagProfileAsync(Gamertag) ?? new JObject();\n            var profileUsers = profileData[\"profileUsers\"]?.FirstOrDefault();\n            if (profileUsers == null)\n            {\n                _snackbarService.Show(\"Error\", \"Failed to fetch gamertag information.\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                return;\n            }\n\n            GamertagXuid = profileUsers[\"id\"]?.ToString() ?? string.Empty;\n            GamertagName = \"Gamertag: \" + profileUsers[\"settings\"]?.FirstOrDefault(setting => setting[\"id\"]?.ToString() == \"Gamertag\")?[\"value\"]?.ToString() ?? \"Unknown\";\n            GamertagScore = \"Gamerscore: \" + profileUsers[\"settings\"]?.FirstOrDefault(setting => setting[\"id\"]?.ToString() == \"Gamerscore\")?[\"value\"]?.ToString() ?? \"Unknown\";\n            GamertagImage = profileUsers[\"settings\"]?.FirstOrDefault(setting => setting[\"id\"]?.ToString() == \"GameDisplayPicRaw\")?[\"value\"]?.ToString()?.Replace(\"&mode=Padding\", \"\") ?? string.Empty;\n\n        }\n\n        public async Task ExportToCsvAsync()\n        {\n            if (string.IsNullOrWhiteSpace(GamertagXuid))\n            {\n                _snackbarService.Show(\"Error\", \"Search for a user first.\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                return;\n            }\n            try\n            {\n                _snackbarService.Show(\"Fetching Games\", \"Trying to get games. This may take a moment depending on the number of games the user has.\", ControlAppearance.Primary, new SymbolIcon(SymbolRegular.XboxController24), _snackbarDuration);\n                var gamesResponse = await _xboxRestAPI.Value.GetGamesListAsync(GamertagXuid);\n\n                if (gamesResponse == null || gamesResponse.Titles == null)\n                {\n                    await Task.Delay(2500);\n                    _snackbarService.Show(\"Error\", \"Failed to fetch games list.\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                    return;\n                }\n\n                if (gamesResponse.Titles.Count == 0)\n                {\n                    await Task.Delay(2500);\n                    _snackbarService.Show(\"No Titles Found\", \"No games found for this user. This could be due to user privacy settings or other reasons.\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n                    return;\n                }\n\n                var sb = new StringBuilder();\n                sb.AppendLine(\"\\\"Title ID\\\",\\\"Title\\\",\\\"CurrentAchievements\\\",\\\"Gamerscore\\\",\\\"Progress\\\",\\\"Devices\\\",\\\"Genres\\\"\");\n\n                foreach (var title in gamesResponse.Titles)\n                {\n                    if (ExcludeZeroGamerscoreGames && title.Achievement.TotalGamerscore == 0)\n                    {\n                        continue;\n                    }\n\n                    if (ExcludeXbox360Games && title.Devices != null && title.Devices.Contains(\"Xbox360\"))\n                    {\n                        continue;\n                    }\n\n                    var titleName = title.Name.Replace(\"\\\"\", \"\\\"\\\"\");\n                    var devices = title.Devices != null ? string.Join(\", \", title.Devices).Replace(\"\\\"\", \"\\\"\\\"\") : string.Empty;\n                    var genres = title.Detail?.Genres != null ? string.Join(\", \", title.Detail.Genres).Replace(\"\\\"\", \"\\\"\\\"\") : string.Empty;\n\n                    sb.AppendLine($\"\\\"{title.TitleId}\\\",\\\"{titleName}\\\",\\\"{title.Achievement.CurrentAchievements}\\\",\\\"{title.Achievement.CurrentGamerscore}/{title.Achievement.TotalGamerscore}\\\",\\\"{title.Achievement.ProgressPercentage}\\\",\\\"{devices}\\\",\\\"{genres}\\\"\");\n                }\n\n                var saveFileDialog = new Microsoft.Win32.SaveFileDialog\n                {\n                    Filter = \"CSV files (*.csv)|*.csv\",\n                    FileName = $\"{GamertagXuid}.csv\"\n                };\n\n                if (saveFileDialog.ShowDialog() == true)\n                {\n                    await Task.Run(() =>\n                    {\n                        File.WriteAllText(saveFileDialog.FileName, sb.ToString());\n                    });\n\n                    _snackbarService.Show(\"Success\", \"Games list exported successfully.\", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);\n                }\n                else\n                {\n                    _snackbarService.Show(\"Cancelled\", \"Game export was not completed\", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.Warning24), _snackbarDuration);\n                }\n            }\n            catch (Exception ex)\n            {\n                _snackbarService.Show(\"Error\", \"Failed to export games list: \" + ex.Message, ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);\n            }\n        }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "XAU/ViewModels/Pages/SettingsViewModel.cs",
    "content": "using Newtonsoft.Json;\nusing System.Diagnostics;\nusing System.IO;\nusing Wpf.Ui.Controls;\nusing XAU.Services.HttpServer;\n\nnamespace XAU.ViewModels.Pages\n{\n    public partial class SettingsViewModel : ObservableObject, INavigationAware, IDisposable\n    {\n        private bool _isInitialized = false;\n\n        [ObservableProperty]\n        private string _appVersion = String.Empty;\n\n        static string ProgramFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), \"XAU\");\n        string SettingsFilePath = Path.Combine(ProgramFolderPath, \"settings.json\");\n        //settings\n        [ObservableProperty] private string _settingsVersion;\n        [ObservableProperty] private string _toolVersion;\n        [ObservableProperty] private bool _unlockAllEnabled;\n        [ObservableProperty] private bool _autoSpooferEnabled;\n        [ObservableProperty] private bool _autoLaunchXboxAppEnabled;\n        [ObservableProperty] private bool _launchHidden;\n        [ObservableProperty] private bool _fakeSignatureEnabled;\n        [ObservableProperty] private bool _regionOverride;\n        [ObservableProperty] private bool _useAcrylic;\n        [ObservableProperty] private bool _privacyMode;\n        [ObservableProperty] private bool _oAuthLogin;\n        [ObservableProperty] private bool _autoGrabEventsToken;\n        [ObservableProperty] private string _xauth;\n\n        [ObservableProperty] private bool _serverEnabled;\n        [ObservableProperty] private string _serverPort = \"1337\";\n        [ObservableProperty] private string _listeningAddress = \"http://localhost:1337\";\n\n        private HttpServer? _httpServer;\n        private bool _disposed;\n\n        public static bool ManualXauth = false;\n        public RoutedEventHandler OnNavigatedToEvent = null!;\n\n        [RelayCommand]\n        public void SaveSettings()\n        {\n            var settings = new XAUSettings\n            {\n                SettingsVersion = SettingsVersion,\n                ToolVersion = ToolVersion,\n                UnlockAllEnabled = UnlockAllEnabled,\n                AutoSpooferEnabled = AutoSpooferEnabled,\n                AutoLaunchXboxAppEnabled = AutoLaunchXboxAppEnabled,\n                LaunchHidden = LaunchHidden,\n                FakeSignatureEnabled = FakeSignatureEnabled,\n                RegionOverride = RegionOverride,\n                UseAcrylic = UseAcrylic,\n                PrivacyMode = PrivacyMode,\n                OAuthLogin = OAuthLogin,\n                AutoGrabEventsToken = AutoGrabEventsToken\n            };\n            string settingsJson = JsonConvert.SerializeObject(settings);\n            File.WriteAllText(SettingsFilePath, settingsJson);\n            HomeViewModel.Settings = settings; // update ref\n        }\n\n        [RelayCommand]\n        private void ToggleServer()\n        {\n            if (_httpServer == null)\n            {\n                var routes = Routes.GetRoutes(\n                    getXauthToken: () => HomeViewModel.XAUTH,\n                    getXboxRestAPI: () => new XboxRestAPI(HomeViewModel.XAUTH),\n                    getXUIDOnly: () => HomeViewModel.XUIDOnly\n                ); _httpServer = new HttpServer(ServerPort, routes);\n            }\n\n            if (ServerEnabled)\n            {\n                _httpServer.Start();\n                UpdateListeningAddress();\n            }\n            else\n            {\n                _httpServer.Stop();\n                ListeningAddress = $\"http://localhost:{ServerPort}\";\n            }\n            // TO DO: SAVE SERVER ENABLED/DISABLED STATUS & PORT NUMBER\n            //SaveSettings();\n        }\n\n        [RelayCommand]\n        public void UpdateServerPort()\n        {\n            if (_httpServer != null)\n            {\n                _httpServer.UpdatePort(ServerPort);\n                UpdateListeningAddress();\n            }\n\n            // TO DO: SAVE SERVER ENABLED/DISABLED STATUS & PORT NUMBER\n            //SaveSettings();\n        }\n\n        [RelayCommand]\n        public void RestartAsAdmin()\n        {\n            if (_httpServer != null)\n            {\n                _httpServer.RestartAsAdmin();\n            }\n        }\n\n        [RelayCommand]\n        private void OpenListeningAddress()\n        {\n            try\n            {\n                if (!string.IsNullOrWhiteSpace(ListeningAddress))\n                {\n                    Process.Start(new ProcessStartInfo\n                    {\n                        FileName = ListeningAddress,\n                        UseShellExecute = true\n                    });\n                }\n            }\n            catch (Exception ex)\n            {\n                Debug.WriteLine($\"Failed to open address: {ex.Message}\");\n            }\n        }\n\n        public void OnNavigatedTo()\n        {\n            if (!_isInitialized)\n            {\n                InitializeViewModel();\n            }\n\n            OnNavigatedToEvent.Invoke(this, new RoutedEventArgs());\n        }\n\n        public void OnNavigatedFrom()\n        { }\n\n        private void InitializeViewModel()\n        {\n            LoadSettings();\n            ToolVersion = $\"XAU - {GetAssemblyVersion()}\";\n            SettingsVersion = \"2\";\n            _isInitialized = true;\n\n            if (_httpServer == null)\n            {\n                var routes = Routes.GetRoutes(\n                    getXauthToken: () => HomeViewModel.XAUTH,\n                    getXboxRestAPI: () => new XboxRestAPI(HomeViewModel.XAUTH),\n                    getXUIDOnly: () => HomeViewModel.XUIDOnly\n                );\n                _httpServer = new HttpServer(ServerPort, routes);\n            }\n            ListeningAddress = $\"http://localhost:{ServerPort}\";\n        }\n\n        public void LoadSettings()\n        {\n            SettingsVersion = HomeViewModel.Settings.SettingsVersion;\n            ToolVersion = HomeViewModel.Settings.ToolVersion;\n            UnlockAllEnabled = HomeViewModel.Settings.UnlockAllEnabled;\n            AutoSpooferEnabled = HomeViewModel.Settings.AutoSpooferEnabled;\n            AutoLaunchXboxAppEnabled = HomeViewModel.Settings.AutoLaunchXboxAppEnabled;\n            LaunchHidden = HomeViewModel.Settings.LaunchHidden;\n            FakeSignatureEnabled = HomeViewModel.Settings.FakeSignatureEnabled;\n            RegionOverride = HomeViewModel.Settings.RegionOverride;\n            UseAcrylic = HomeViewModel.Settings.UseAcrylic;\n            PrivacyMode = HomeViewModel.Settings.PrivacyMode;\n            Xauth = HomeViewModel.XAUTH;\n            OAuthLogin = HomeViewModel.Settings.OAuthLogin;\n            AutoGrabEventsToken = HomeViewModel.Settings.AutoGrabEventsToken;\n        }\n\n        private string GetAssemblyVersion()\n        {\n            return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString()\n                ?? String.Empty;\n        }\n\n        private void UpdateListeningAddress()\n        {\n            if (_httpServer != null)\n            {\n                ListeningAddress = _httpServer.GetListeningAddress();\n            }\n        }\n        partial void OnServerPortChanged(string value)\n        {\n            if (_httpServer != null)\n            {\n                _httpServer.UpdatePort(value);\n                UpdateListeningAddress();\n            }\n            // TO DO: SAVE SERVER ENABLED/DISABLED STATUS & PORT NUMBER\n            //SaveSettings();\n        }\n        public void Dispose()\n        {\n            if (_disposed) return;\n\n            if (_httpServer != null)\n            {\n                _httpServer.Dispose();\n                _httpServer = null;\n            }\n\n            _disposed = true;\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/ViewModels/Pages/StatsViewModel.cs",
    "content": "using Wpf.Ui.Controls;\n\nnamespace XAU.ViewModels.Pages\n{\n    public partial class StatsViewModel : ObservableObject, INavigationAware\n    {\n        private bool _isInitialized = false;\n        public void OnNavigatedTo()\n        {\n            if (!_isInitialized)\n                InitializeViewModel();\n        }\n        public void OnNavigatedFrom() { }\n\n        private void InitializeViewModel()\n        {\n            _isInitialized = true;\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/ViewModels/Windows/MainWindowViewModel.cs",
    "content": "using System.Collections.ObjectModel;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls;\n\nnamespace XAU.ViewModels.Windows\n{\n    public partial class MainWindowViewModel : ObservableObject\n    {\n        private readonly IContentDialogService _contentDialogService;\n        DateTime startTime = DateTime.Now;\n        public MainWindowViewModel(IContentDialogService contentDialogService)\n        {\n            _contentDialogService = contentDialogService;\n        }\n\n        public async Task ShowErrorDialog(Exception exception)\n        {\n            TimeSpan uptime = DateTime.Now - startTime;\n            string OutputString = $\"Information\\n\" +\n                                  $\"=================================\\n\" +\n                                  $\"Tool Version: {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}\\n\" +\n                                  $\"System Version: {Environment.OSVersion.Version.ToString()}\\n\" +\n                                  $\"Tool Uptime: {uptime.ToString()}\\n\" +\n                                  $\"=================================\\n\" +\n                                  $\"Exception\\n\" +\n                                  $\"=================================\\n\" +\n                                  $\"```\\n{exception.ToString()}\\n```\";\n\n            ContentDialogResult result = await _contentDialogService.ShowSimpleDialogAsync(\n                new SimpleContentDialogCreateOptions()\n                {\n                    Title = \"Oopsie Woopsy Fucky Wucky\",\n                    Content = \"Something has went terribly wrong.\\nPress the Copy Error button and post the message as a github issue or in the support channel on discord\",\n                    PrimaryButtonText = \"Copy Error\",\n                    CloseButtonText = \"Close\",\n                });\n\n            switch (result)\n            {\n                case ContentDialogResult.Primary:\n                    Clipboard.SetDataObject(OutputString);\n                    break;\n            }\n        }\n        [ObservableProperty]\n        private string _applicationTitle = \"Xbox Achievement Unlocker\";\n\n        [ObservableProperty]\n        private ObservableCollection<object> _menuItems = new()\n        {\n            new NavigationViewItem()\n            {\n                Content = \"Home\",\n                Icon = new SymbolIcon { Symbol = SymbolRegular.Home24 },\n                TargetPageType = typeof(Views.Pages.HomePage)\n            },\n            new NavigationViewItem()\n            {\n                Content = \"Games\",\n                Icon = new SymbolIcon { Symbol = SymbolRegular.Games24 },\n                TargetPageType = typeof(Views.Pages.GamesPage)\n            },\n            new NavigationViewItem()\n            {\n                Content = \"Achievements\",\n                Icon = new SymbolIcon { Symbol = SymbolRegular.Trophy24 },\n                TargetPageType = typeof(Views.Pages.AchievementsPage)\n            },\n            /*new NavigationViewItem()\n            {\n                Content = \"Stats\",\n                Icon = new SymbolIcon { Symbol = SymbolRegular.DataHistogram24 },\n                TargetPageType = typeof(Views.Pages.StatsPage)\n            },*/\n            new NavigationViewItem()\n            {\n                Content = \"Misc\",\n                Icon = new SymbolIcon { Symbol = SymbolRegular.MoreCircle24 },\n                TargetPageType = typeof(Views.Pages.MiscPage)\n            }\n            #if DEBUG\n            ,\n            new NavigationViewItem()\n            {\n                Content = \"Debug\",\n                Icon = new SymbolIcon { Symbol = SymbolRegular.Bug24 },\n                TargetPageType = typeof(Views.Pages.DebugPage)\n            }\n            #endif\n\n\n        };\n\n        [ObservableProperty]\n        private ObservableCollection<object> _footerMenuItems = new()\n        {\n            new NavigationViewItem()\n            {\n                Content = \"Settings\",\n                Icon = new SymbolIcon { Symbol = SymbolRegular.Settings24 },\n                TargetPageType = typeof(Views.Pages.SettingsPage)\n            },\n            new NavigationViewItem()\n            {\n                Content = \"Info\",\n                Icon = new SymbolIcon { Symbol = SymbolRegular.Info24 },\n                TargetPageType = typeof(Views.Pages.InfoPage)\n            }\n        };\n\n        [ObservableProperty]\n        private ObservableCollection<MenuItem> _trayMenuItems = new()\n        {\n            new MenuItem { Header = \"Home\", Tag = \"tray_home\" }\n        };\n    }\n}\n"
  },
  {
    "path": "XAU/Views/Pages/AchievementsPage.xaml",
    "content": "﻿<Page x:Class=\"XAU.Views.Pages.AchievementsPage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:p=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n      xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n      xmlns:local=\"clr-namespace:XAU.Views.Pages\"\n      xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n      xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n      Title=\"AchievementsPage\"\n      x:Name=\"AchievementsPagen\"\n      d:DataContext=\"{d:DesignInstance local:AchievementsPage,\n                                 IsDesignTimeCreatable=False}\"\n      d:DesignHeight=\"450\"\n      d:DesignWidth=\"800\"\n      ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n      ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n      Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n      mc:Ignorable=\"d\">\n\n\n    <Grid\n          Height=\"{Binding ElementName=AchievementsPagen, Path=ActualHeight}\"\n          Width=\"{Binding ElementName=AchievementsPagen, Path=ActualWidth}\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"*\"/>\n        </Grid.RowDefinitions>\n        <Grid>\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n            </Grid.RowDefinitions>\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\" />\n                <ColumnDefinition Width=\"*\" />\n                <ColumnDefinition Width=\"*\" />\n                <ColumnDefinition Width=\"*\" />\n                <ColumnDefinition Width=\"Auto\" />\n            </Grid.ColumnDefinitions>\n            <ui:TextBox\n                Grid.Row=\"1\"\n                Grid.Column=\"0\"\n                x:Name=\"TitleIDBox\"\n                IsEnabled=\"{Binding ViewModel.TitleIDEnabled}\"\n                Padding=\"10,4,8,7\"\n                Margin=\"0,0,44,0\"\n                Text=\"{Binding ViewModel.TitleIDOverride, Mode=TwoWay}\"\n                TextAlignment=\"Center\"\n                ClearButtonEnabled=\"False\" \n            ></ui:TextBox>\n            <ui:Button\n                Grid.Row=\"1\"\n                Grid.Column=\"0\"\n                x:Name=\"TitleIDRefreshButton\"\n                IsEnabled=\"{Binding ViewModel.TitleIDEnabled}\"\n                Padding=\"10,5,8,5\"\n                Margin=\"10,0,5,0\"\n                Content=\"\"\n                HorizontalAlignment=\"Right\"\n                Icon=\"{ui:SymbolIcon ArrowRepeatAll24}\"\n                Command=\"{Binding ViewModel.RefreshAchievementsCommand}\"/>\n            <Label \n                Grid.Row=\"0\" \n                Grid.Column=\"0\"\n                Margin=\"0,0,54,0\"\n                Content=\"TitleID Override\"\n                VerticalAlignment=\"Center\"\n                HorizontalAlignment=\"Center\"/>\n\n\n            <ui:TextBox\n                Grid.Row=\"1\"\n                Grid.Column=\"1\"\n                x:Name=\"SearchBox\"\n                IsEnabled=\"{Binding ViewModel.TitleIDEnabled}\"\n                Padding=\"10,4,8,7\"\n                Margin=\"0,0,44,0\"\n                Text=\"{Binding ViewModel.SearchText, Mode=TwoWay, IsAsync=true}\"\n                TextAlignment=\"Center\"\n                ClearButtonEnabled=\"False\" \n                KeyDown=\"SearchBox_OnKeyDownAsync\"\n            ></ui:TextBox>\n            <ui:Button\n                Grid.Row=\"1\"\n                Grid.Column=\"1\"\n                x:Name=\"SearchButton\"\n                IsEnabled=\"{Binding ViewModel.TitleIDEnabled}\"\n                Padding=\"10,5,8,5\"\n                Margin=\"10,0,5,0\"\n                Content=\"\"\n                HorizontalAlignment=\"Right\"\n                Icon=\"{ui:SymbolIcon Search24}\"\n                Command=\"{Binding ViewModel.SearchAndFilterAchievementsCommand}\"/>\n            <Label \n                Grid.Row=\"0\" \n                Grid.Column=\"1\"\n                Margin=\"00,0,44,0\"\n                Content=\"Search\"\n                VerticalAlignment=\"Center\"\n                HorizontalAlignment=\"Center\"/>\n\n            <Label \n                Grid.Row=\"0\" \n                Grid.Column=\"3\"\n                Margin=\"10,0,10,0\"\n                Content=\"{Binding ViewModel.GameInfo}\"\n                VerticalAlignment=\"Center\"\n                HorizontalAlignment=\"Center\"/>\n            <TextBlock\n                Grid.Row=\"1\"\n                Grid.Column=\"3\"\n                Margin=\"10,0,10,0\"\n                Text=\"{Binding ViewModel.GameName}\"\n                VerticalAlignment=\"Center\"\n                HorizontalAlignment=\"Center\"\n                TextWrapping=\"Wrap\"\n                TextAlignment=\"Center\"\n                />\n            <ui:Button\n                Grid.Row=\"0\"\n                Grid.Column=\"4\"\n                x:Name=\"UnlockAllButton\"\n                IsEnabled=\"{Binding ViewModel.IsUnlockAllEnabled}\"\n                Padding=\"10,5,8,5\"\n                Margin=\"10,0,5,2\"\n                Content=\"Unlock All\"\n                HorizontalAlignment=\"Right\"\n                Command=\"{Binding ViewModel.UnlockAllCommand}\"\n            />\n            <ui:Button\n                Grid.Row=\"1\"\n                Grid.Column=\"4\"\n                x:Name=\"RefreshButton\"\n                Padding=\"10,5,8,5\"\n                Margin=\"10,2,5,0\"\n                Content=\"Refresh\"\n                HorizontalAlignment=\"Stretch\"\n                Command=\"{Binding ViewModel.RefreshAchievementsCommand}\"\n            />\n        </Grid>\n        <DataGrid Grid.Row=\"1\"  \n                  VerticalScrollBarVisibility=\"Visible\" \n                  HorizontalScrollBarVisibility=\"Visible\"\n                  Margin=\"0,5,0,5\" \n                  AutoGenerateColumns=\"False\"  \n                  ItemsSource=\"{Binding ViewModel.DGAchievements}\" \n                  IsReadOnly=\"True\"\n                  CanUserAddRows=\"False\"\n                  CanUserDeleteRows=\"False\"\n                  CanUserReorderColumns=\"False\"\n                  CanUserResizeColumns=\"True\"\n                  CanUserResizeRows=\"False\"\n                  CanUserSortColumns=\"True\"\n        >\n            <DataGrid.Columns>\n                <DataGridTextColumn Header=\"ID\" Binding=\"{Binding ID}\" Width=\"0.05*\" />\n                <DataGridTextColumn Header=\"Achievement\" Binding=\"{Binding Name}\" Width=\"0.2*\" >\n                    <DataGridTextColumn.ElementStyle>\n                        <Style TargetType=\"TextBlock\">\n                            <Setter Property=\"TextWrapping\" Value=\"Wrap\" />\n                        </Style>\n                    </DataGridTextColumn.ElementStyle>\n                </DataGridTextColumn>\n                <DataGridTextColumn Header=\"Description\" MaxWidth=\"300\" Binding=\"{Binding Description}\" Width=\"0.25*\">\n                    <DataGridTextColumn.ElementStyle>\n                        <Style TargetType=\"TextBlock\">\n                            <Setter Property=\"TextWrapping\" Value=\"Wrap\" />\n                        </Style>\n                    </DataGridTextColumn.ElementStyle>\n                </DataGridTextColumn>\n                <DataGridTextColumn Header=\"Secret\" Binding=\"{Binding IsSecret}\"  Width=\"0.1*\"/>\n                <DataGridTextColumn Header=\"Date Unlocked\" Binding=\"{Binding DateUnlocked}\" Width=\"0.27*\"/>\n                <DataGridTextColumn Header=\"G's\" Binding=\"{Binding Gamerscore}\" Width=\"0.06*\"/>\n                <DataGridTextColumn Header=\"Percentage\" Binding=\"{Binding RarityPercentage}\" Width=\"0.13*\"/>\n                <DataGridTextColumn Header=\"Rarity\" Binding=\"{Binding RarityCategory}\" Width=\"0.11*\"/>\n                <DataGridTextColumn Header=\"State\" Binding=\"{Binding ProgressState}\" Width=\"0.12*\"/>\n                <DataGridTemplateColumn Header=\"Unlock\" Width=\"0.13*\">\n                    <DataGridTemplateColumn.CellTemplate>\n                        <DataTemplate>\n                            <Button Margin=\"5,5,5,5\" Click=\"UnlockButton\" Tag=\"{Binding Index, Mode=TwoWay}\"\n                                    HorizontalAlignment=\"Stretch\" VerticalAlignment=\"Stretch\"\n                                    IsEnabled=\"{Binding IsUnlockable}\">Unlock</Button>\n                        </DataTemplate>\n                    </DataGridTemplateColumn.CellTemplate>\n                </DataGridTemplateColumn>\n            </DataGrid.Columns>\n        </DataGrid>\n    </Grid>\n</Page>\n"
  },
  {
    "path": "XAU/Views/Pages/AchievementsPage.xaml.cs",
    "content": "using System.Windows.Controls;\nusing System.Windows.Controls.Primitives;\nusing System.Windows.Input;\nusing Wpf.Ui.Controls;\nusing XAU.ViewModels.Pages;\n\nnamespace XAU.Views.Pages\n{\n    /// <summary>\n    /// Interaction logic for AchievementsPage.xaml\n    /// </summary>\n    public partial class AchievementsPage : INavigableView<AchievementsViewModel>\n    {\n        public AchievementsViewModel ViewModel { get; }\n\n        public AchievementsPage(AchievementsViewModel viewModel)\n        {\n            ViewModel = viewModel;\n            DataContext = this;\n            InitializeComponent();\n        }\n\n        private void UnlockButton(object sender, RoutedEventArgs e)\n        {\n            ButtonBase SelectedAchievement = sender as ButtonBase;\n            ViewModel.UnlockAchievement(Convert.ToInt32(SelectedAchievement.Tag));\n        }\n\n        private void FilterBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)\n        {\n\n\n        }\n\n        private async void SearchBox_OnKeyDownAsync(object sender, KeyEventArgs e)\n        {\n            if (e.Key == Key.Enter)\n            {\n                //for some reason, the search text is not being updated when pressing enter\n                ViewModel.SearchText = SearchBox.Text;\n                await ViewModel.SearchAndFilterAchievements();\n\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Views/Pages/DebugPage.xaml",
    "content": "﻿<Page\n    x:Class=\"XAU.Views.Pages.DebugPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:XAU.Views.Pages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    Title=\"DebugPage\"\n    d:DataContext=\"{d:DesignInstance local:DebugPage,\n                                     IsDesignTimeCreatable=False}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    mc:Ignorable=\"d\">\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"*\" />\n            <ColumnDefinition Width=\"*\" />\n            <ColumnDefinition Width=\"*\" />\n            <ColumnDefinition Width=\"*\" />\n        </Grid.ColumnDefinitions>\n\n        <ui:Button\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            HorizontalAlignment=\"Stretch\"\n            Content=\"Test All Events Replacements\"\n            Command=\"{Binding ViewModel.TestEventReplacementsCommand}\"/>\n\n    </Grid>\n</Page>\n"
  },
  {
    "path": "XAU/Views/Pages/DebugPage.xaml.cs",
    "content": "using Wpf.Ui.Controls;\nusing XAU.ViewModels.Pages;\n\nnamespace XAU.Views.Pages\n{\n    public partial class DebugPage : INavigableView<DebugViewModel>\n    {\n        public DebugViewModel ViewModel { get; }\n\n        public DebugPage(DebugViewModel viewModel)\n        {\n            ViewModel = viewModel;\n            DataContext = this;\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Views/Pages/GamesPage.xaml",
    "content": "﻿<Page x:Class=\"XAU.Views.Pages.GamesPage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:p=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n      xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n      xmlns:local=\"clr-namespace:XAU.Views.Pages\"\n      xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n      xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n      Title=\"GamesPage\"\n      d:DataContext=\"{d:DesignInstance local:GamesPage,\n                                 IsDesignTimeCreatable=False}\"\n      d:DesignHeight=\"450\"\n      d:DesignWidth=\"800\"\n      ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n      ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n      Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n      mc:Ignorable=\"d\">\n\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"{Binding ViewModel.GamesListHeight}\"/>\n            <RowDefinition Height=\"{Binding ViewModel.LoadingHeight}\"/>\n        </Grid.RowDefinitions>\n        <Grid Grid.Row=\"0\">\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n            </Grid.RowDefinitions>\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"*\" />\n                <ColumnDefinition Width=\"*\" />\n                <ColumnDefinition Width=\"*\" />\n                <ColumnDefinition Width=\"Auto\" />\n            </Grid.ColumnDefinitions>\n            <ui:TextBox\n                Grid.Row=\"1\"\n                Grid.Column=\"0\"\n                x:Name=\"XUIDBox\"\n                IsEnabled=\"{Binding ViewModel.IsInitialized}\"\n                Padding=\"10,4,8,7\"\n                Margin=\"0,0,44,0\"\n                Text=\"{Binding ViewModel.XuidOverride, Mode=TwoWay}\"\n                TextAlignment=\"Center\"\n                ClearButtonEnabled=\"False\" \n            ></ui:TextBox>\n            <ui:Button\n                Grid.Row=\"1\"\n                Grid.Column=\"0\"\n                x:Name=\"RefreshButton\"\n                IsEnabled=\"{Binding ViewModel.IsInitialized}\"\n                Padding=\"10,5,8,5\"\n                Margin=\"10,0,5,0\"\n                Content=\"\"\n                HorizontalAlignment=\"Right\"\n                Icon=\"{ui:SymbolIcon ArrowRepeatAll24}\"\n                Command=\"{Binding ViewModel.GetGamesListCommand}\"\n            />\n            <Label \n                Grid.Row=\"0\" \n                Grid.Column=\"0\"\n                Margin=\"10,0,44,0\"\n                Target=\"{Binding ElementName=XUIDBox}\"\n                Content=\"XUID Override\"\n                VerticalAlignment=\"Top\"\n                HorizontalAlignment=\"Center\"/>\n            <ui:TextBox\n                Grid.Row=\"1\"\n                Grid.Column=\"1\"\n                x:Name=\"SearchBox\"\n                IsEnabled=\"{Binding ViewModel.IsInitialized}\"\n                Padding=\"10,4,8,7\"\n                Margin=\"10,0,44,0\"\n                Text=\"{Binding ViewModel.SearchText, Mode=TwoWay, IsAsync=True}\"\n                PlaceholderText=\"Search for a game\"\n                TextAlignment=\"Center\"\n                ClearButtonEnabled=\"False\"\n                KeyDown=\"SearchBox_OnKeyDown\"\n            />\n            <ui:Button\n                Grid.Row=\"1\"\n                Grid.Column=\"1\"\n                x:Name=\"SearchButton\"\n                IsEnabled=\"{Binding ViewModel.IsInitialized}\"\n                Padding=\"10,5,8,5\"\n                Margin=\"10,0,5,0\"\n                Content=\"\"\n                HorizontalAlignment=\"Right\"\n                Icon=\"{ui:SymbolIcon Search24}\"\n                Command=\"{Binding ViewModel.SearchAndFilterGamesCommand}\"\n            />\n            <Label\n                Grid.Row=\"0\" \n                Grid.Column=\"1\"\n                Margin=\"10,0,44,0\"\n                Target=\"{Binding ElementName=SearchBox}\"\n                Content=\"{Binding ViewModel.SearchLabel}\"\n                VerticalAlignment=\"Top\"\n                HorizontalAlignment=\"Center\"/>\n            <ComboBox\n                Grid.Row=\"1\"\n                Grid.Column=\"2\"\n                x:Name=\"FilterBox\"\n                IsEnabled=\"{Binding ViewModel.IsInitialized}\"\n                Padding=\"10,4,8,7\"\n                Margin=\"10,0,10,0\"\n                ItemsSource=\"{Binding ViewModel.FilterOptions}\"\n                SelectionChanged=\"FilterBox_OnSelectionChanged\"\n                SelectedIndex=\"{Binding ViewModel.FilterIndex, Mode=TwoWay}\"\n            />\n            <Label \n                Grid.Row=\"0\" \n                Grid.Column=\"2\"\n                Margin=\"10,0,10,0\"\n                Target=\"{Binding ElementName=FilterBox}\"\n                Content=\"Game Filter\"\n                VerticalAlignment=\"Top\"\n                HorizontalAlignment=\"Center\"/>\n            <ComboBox\n                Grid.Row=\"1\"\n                Grid.Column=\"3\"\n                x:Name=\"PageBox\"\n                IsEnabled=\"{Binding ViewModel.IsInitialized}\"\n                Padding=\"10,4,8,7\"\n                Margin=\"10,0,10,0\"\n                ItemsSource=\"{Binding ViewModel.PageOptions, Mode=OneWay}\"\n                SelectionChanged=\"PageBox_OnSelectionChanged\"\n                SelectedIndex=\"{Binding ViewModel.CurrentPage, Mode=TwoWay}\"\n            />\n            <Label \n                Grid.Row=\"0\" \n                Grid.Column=\"3\"\n                Margin=\"10,0,10,0\"\n                Target=\"{Binding ElementName=PageBox}\"\n                Content=\"Page\"\n                VerticalAlignment=\"Top\"\n                HorizontalAlignment=\"Center\"/>\n        </Grid>\n        <ui:ProgressRing IsIndeterminate=\"True\" Grid.Row=\"2\" \n                         VerticalAlignment=\"Center\" \n                         HorizontalAlignment=\"Center\" \n                         Height=\"{Binding ViewModel.LoadingSize}\"\n                         Width=\"{Binding ViewModel.LoadingSize}\"\n                         />\n        <Grid Margin=\"0,15,0,0\" Grid.Row=\"1\">\n            <ItemsControl ItemsSource=\"{Binding ViewModel.GamesPaged}\">\n                <ItemsControl.ItemsPanel>\n                    <ItemsPanelTemplate>\n                        <WrapPanel />\n                    </ItemsPanelTemplate>\n                </ItemsControl.ItemsPanel>\n                <ItemsControl.ItemTemplate>\n                    <DataTemplate>\n                        <StackPanel Orientation=\"Vertical\" Margin=\"5,5,5,7\">\n                            <Button Cursor=\"Hand\" MinHeight=\"170\" MinWidth=\"170\" Click=\"ButtonBase_OnClick\" MouseRightButtonDown=\"ButtonBase_RightClick\" Content=\"{Binding Index}\">\n                                <Button.Template>\n                                    <ControlTemplate>\n                                        <ui:Image Source=\"{Binding Image, IsAsync=true}\" Width=\"170\" Height=\"170\" CornerRadius=\"10\" Margin=\"0,0,0,0\" Image.ImageFailed=\"Image_ImageFailed\" Background=\"White\"/>\n                                    </ControlTemplate>\n                                </Button.Template>\n                            </Button>\n                            <Grid Width=\"170\" Height=\"30\" Margin=\"0,2,0,2\">\n                                <ProgressBar Value=\"{Binding Progress}\"\n                                             Height=\"25\"/>\n                                <StackPanel Orientation=\"Horizontal\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Center\" Margin=\"5,0,0,0\">\n                                    <ui:SymbolIcon Symbol=\"Trophy24\" HorizontalAlignment=\"Left\" Margin=\"0,0,3,0\"/>\n                                    <Label Content=\"{Binding CurrentAchievements}\" HorizontalAlignment=\"Left\" Margin=\"0,1,0,0\"/>\n                                </StackPanel>\n                                <StackPanel Orientation=\"Horizontal\" HorizontalAlignment=\"Right\" VerticalAlignment=\"Center\" Margin=\"0,0,5,0\">\n                                    <Label Content=\"{Binding Gamerscore}\" HorizontalAlignment=\"Right\" HorizontalContentAlignment=\"Right\" Margin=\"0,2,0,0\"/>\n                                    <ui:Image Source=\"pack://application:,,,/Assets/G.png\" Height=\"17\" Margin=\"5,0,0,0\"/>\n                                </StackPanel>\n                            </Grid>\n                            <ui:TextBox \n                                TextAlignment=\"Center\"\n                                ClearButtonEnabled=\"False\"\n                                MaxWidth=\"170\"\n                                IsReadOnly=\"True\"\n                                IsReadOnlyCaretVisible=\"True\"\n                                Padding=\"10,4,8,7\"\n                                Text=\"{Binding Title}\"/>\n                        </StackPanel>\n                    </DataTemplate>\n                </ItemsControl.ItemTemplate>\n            </ItemsControl>\n\n\n        </Grid>\n    </Grid>\n</Page>\n"
  },
  {
    "path": "XAU/Views/Pages/GamesPage.xaml.cs",
    "content": "using System.Windows.Controls;\nusing System.Windows.Controls.Primitives;\nusing System.Windows.Input;\nusing System.Windows.Media.Imaging;\nusing Wpf.Ui.Controls;\nusing XAU.ViewModels.Pages;\n\nnamespace XAU.Views.Pages\n{\n    /// <summary>\n    /// Interaction logic for GamesPage.xaml\n    /// </summary>\n    public partial class GamesPage : INavigableView<GamesViewModel>\n    {\n        public GamesViewModel ViewModel { get; }\n        public GamesPage(GamesViewModel viewModel)\n        {\n            ViewModel = viewModel;\n            DataContext = this;\n            InitializeComponent();\n        }\n\n        private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)\n        {\n            ButtonBase selectedGame = sender as ButtonBase;\n\n            await ViewModel.OpenAchievements(selectedGame.Content.ToString());\n        }\n\n        private void SearchBox_OnKeyDown(object sender, KeyEventArgs e)\n        {\n            if (e.Key == Key.Enter)\n            {\n                //for some reason, the search text is not being updated when pressing enter\n                ViewModel.SearchText = SearchBox.Text;\n                ViewModel.SearchAndFilterGames();\n            }\n        }\n\n        private void FilterBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)\n        {\n            ViewModel.FilterGames();\n\n        }\n\n        private void PageBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)\n        {\n            ViewModel.PageChanged();\n\n        }\n\n        private void ButtonBase_RightClick(object sender, MouseButtonEventArgs e)\n        {\n            ButtonBase selectedGame = sender as ButtonBase;\n            ViewModel.CopyToClipboard(selectedGame.Content.ToString());\n        }\n\n        private void Image_ImageFailed(object sender, RoutedEventArgs e)\n        {\n            if (sender is Wpf.Ui.Controls.Image uiImage)\n            {\n                uiImage.Source = new BitmapImage(new Uri(\"pack://application:,,,/Assets/cirno.png\"));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Views/Pages/HomePage.xaml",
    "content": "﻿<Page\n    x:Class=\"XAU.Views.Pages.HomePage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:XAU.Views.Pages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    Title=\"HomePage\"\n    d:DataContext=\"{d:DesignInstance local:HomePage,\n                                     IsDesignTimeCreatable=False}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    mc:Ignorable=\"d\">\n\n\n    <Grid>\n        <ui:TextBlock \n            Text=\"{Binding ViewModel.Attached}\"\n            Foreground=\"{Binding ViewModel.AttachedColor}\"\n        ></ui:TextBlock>\n        <ui:TextBlock \n            Padding=\"0, 30, 0, 0\"\n            Text=\"{Binding ViewModel.LoggedIn}\"\n            Foreground=\"{Binding ViewModel.LoggedInColor}\"\n        ></ui:TextBlock>\n        <ui:Image\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Top\"\n            Width=\"150\"\n            Height=\"150\"\n            Source=\"{Binding ViewModel.GamerPic, Mode=OneWay}\"\n            CornerRadius=\"75\"\n        ></ui:Image>\n        <ui:TextBlock\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 170, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.GamerTag, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 170, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.Xuid, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 210, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.GamerScore, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 210, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.ProfileRep, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 210, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.AccountTier, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 250, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.CurrentlyPlaying, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 250, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.ActiveDevice, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 290, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.IsVerified, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 290, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.Location, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 290, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.Tenure, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 330, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.Followers, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 330, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.Following, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 330, 0, 0\"\n            FontSize=\"20\"\n            Text=\"{Binding ViewModel.Gamepass, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:TextBlock\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 370, 0, 0\"\n            FontSize=\"20\"\n            TextWrapping=\"Wrap\"\n            Text=\"{Binding ViewModel.Bio, Mode=OneWay}\"\n        ></ui:TextBlock>\n        <ui:Button\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Top\"\n            Padding=\"17, 5, 17, 5\"\n            Content=\"{Binding ViewModel.LoginText, Mode=OneWay}\"\n            Command=\"{Binding ViewModel.OAuthLoginCommand, Mode=OneWay}\"/>\n        <ui:Button\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Top\"\n            Margin=\"0, 35, 0, 0\"\n            Content=\"Refresh\"\n            IsEnabled=\"{Binding ViewModel.IsLoggedIn, Mode=OneWay}\"\n            Command=\"{Binding ViewModel.RefreshProfileCommand, Mode=OneWay}\"/>\n        <ui:TextBlock\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Top\"\n            Padding=\"0, 410, 0, 0\"\n            FontSize=\"20\"\n            TextWrapping=\"Wrap\"\n            Text=\"Badges: \"\n        ></ui:TextBlock>\n        <ItemsControl ItemsSource=\"{Binding ViewModel.Watermarks}\"\n        HorizontalAlignment=\"Left\"\n        VerticalAlignment=\"Top\"\n        Padding=\"0, 440, 0, 0\"\n        FontSize=\"20\">\n            <ItemsControl.ItemsPanel>\n                <ItemsPanelTemplate>\n                    <WrapPanel />\n                </ItemsPanelTemplate>\n            </ItemsControl.ItemsPanel>\n            <ItemsControl.ItemTemplate>\n                <DataTemplate>\n                    <Image Source=\"{Binding ImageUrl}\" Width=\"128\" Height=\"128\" Stretch=\"Uniform\" />\n                </DataTemplate>\n            </ItemsControl.ItemTemplate>\n        </ItemsControl>\n    </Grid>\n\n</Page>\n"
  },
  {
    "path": "XAU/Views/Pages/HomePage.xaml.cs",
    "content": "using Wpf.Ui.Controls;\nusing XAU.ViewModels.Pages;\n\nnamespace XAU.Views.Pages\n{\n    public partial class HomePage : INavigableView<HomeViewModel>\n    {\n        public HomeViewModel ViewModel { get; }\n\n        public HomePage(HomeViewModel viewModel)\n        {\n            ViewModel = viewModel;\n            DataContext = this;\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Views/Pages/InfoPage.xaml",
    "content": "﻿<Page x:Class=\"XAU.Views.Pages.InfoPage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n      xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n      xmlns:local=\"clr-namespace:XAU.Views.Pages\"\n      xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n      xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n      Title=\"InfoPage\"\n      d:DataContext=\"{d:DesignInstance local:InfoPage,\n                                     IsDesignTimeCreatable=False}\"\n      d:DesignHeight=\"450\"\n      d:DesignWidth=\"800\"\n      ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n      ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n      Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n      mc:Ignorable=\"d\">\n\n    <StackPanel>\n        <TextBlock\n            FontSize=\"20\"\n            FontWeight=\"Medium\"\n            Text=\"Info\" />\n        <TextBlock\n            FontSize=\"16\"\n            Text=\"{Binding ViewModel.ToolVersion}\" />\n        <TextBlock\n            FontSize=\"16\"\n            Text=\"Creator: Draff\" />\n        <TextBlock\n            FontSize=\"16\">\n            <Hyperlink Command=\"{Binding ViewModel.OpenDiscordUrlCommand}\"> Discord Server </Hyperlink>\n        </TextBlock>\n        <TextBlock\n            FontSize=\"16\">\n            <Hyperlink  Command=\"{Binding ViewModel.OpenGithubUserUrlCommand}\"> Github </Hyperlink>\n        </TextBlock>\n        <TextBlock\n            FontSize=\"20\"\n            FontWeight=\"Medium\"\n            Margin=\"0,20,0,0\"\n            Text=\"About\" />\n        <TextBlock \n            FontSize=\"16\"\n            TextWrapping=\"Wrap\"\n            Text=\"This tool is free and open source. If you paid money you were scammed.\"/>\n        <TextBlock\n            FontSize=\"16\"\n            TextWrapping=\"Wrap\"\n            Text=\"The only official download location is the github releases page or the in app auto updater.\"/>\n        <TextBlock\n            FontSize=\"16\"\n            TextWrapping=\"Wrap\"\n            Text=\"This tool was created using WPF-UI and code from memory.dll and is not in any way affiliated with microsoft or xbox.\"/>\n        <TextBlock\n            FontSize=\"16\"\n            Margin=\"0,20,0,0\"\n            TextWrapping=\"Wrap\"\n            Text=\"I just like to code ;)\"/>\n\n\n\n\n    </StackPanel>\n</Page>\n"
  },
  {
    "path": "XAU/Views/Pages/InfoPage.xaml.cs",
    "content": "using Wpf.Ui.Controls;\nusing XAU.ViewModels.Pages;\n\nnamespace XAU.Views.Pages\n{\n    /// <summary>\n    /// Interaction logic for InfoPage.xaml\n    /// </summary>\n    public partial class InfoPage : INavigableView<InfoViewModel>\n    {\n        public InfoViewModel ViewModel { get; }\n\n        public InfoPage(InfoViewModel viewModel)\n        {\n            ViewModel = viewModel;\n            DataContext = this;\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Views/Pages/MiscPage.xaml",
    "content": "<Page\n    x:Class=\"XAU.Views.Pages.MiscPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:XAU.Views.Pages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    x:Name=\"MiscPagen\"\n    Title=\"Misc Page\"\n    d:DataContext=\"{d:DesignInstance local:MiscPage,\n                                     IsDesignTimeCreatable=False}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    mc:Ignorable=\"d\">\n\n    <Grid Width=\"{Binding ElementName=MiscPagen, Path=ActualWidth}\" Height=\"{Binding ElementName=MiscPagen, Path=ActualHeight}\">\n        <TabControl Margin=\"0,0,0,0\">\n            <TabItem IsSelected=\"True\">\n                <TabItem.Header>\n                    <StackPanel Orientation=\"Horizontal\">\n                        <ui:SymbolIcon Margin=\"0,0,6,0\" Symbol=\"XboxController24\" />\n                        <TextBlock Text=\"Spoofer\" />\n                    </StackPanel>\n                </TabItem.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"*\" />\n                    </Grid.RowDefinitions>\n                    <Grid.ColumnDefinitions>\n                        <ColumnDefinition Width=\"*\" />\n                        <ColumnDefinition Width=\"Auto\" />\n                    </Grid.ColumnDefinitions>\n                    <TextBlock Margin=\"0\" Text=\"{Binding ViewModel.SpoofingText}\" />\n                    <Button\n                        Grid.Row=\"0\"\n                        Grid.Column=\"1\"\n                        HorizontalAlignment=\"Stretch\"\n                        Command=\"{Binding ViewModel.SpooferButtonClickedCommand}\"\n                        Content=\"{Binding ViewModel.SpoofingButtonText, IsAsync=true}\"\n                        IsEnabled=\"{Binding ViewModel.IsInitialized}\" />\n                    <ui:TextBox\n                        Grid.Row=\"1\"\n                        Grid.Column=\"1\"\n                        Margin=\"0,5,0,0\"\n                        VerticalAlignment=\"Top\"\n                        PlaceholderText=\"Title ID\"\n                        Text=\"{Binding ViewModel.NewSpoofingID, Mode=TwoWay}\" />\n                    <ui:Image\n                        Grid.Row=\"1\"\n                        Grid.Column=\"0\"\n                        Width=\"250\"\n                        Height=\"250\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Top\"\n                        Source=\"{Binding ViewModel.GameImage}\" />\n                    <Label\n                        Grid.Row=\"1\"\n                        Grid.Column=\"0\"\n                        Margin=\"260,0,0,0\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Top\"\n                        Content=\"{Binding ViewModel.GameName}\" />\n                    <Label\n                        Grid.Row=\"1\"\n                        Grid.Column=\"0\"\n                        Margin=\"260,20,0,0\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Top\"\n                        Content=\"{Binding ViewModel.GameTitleID}\" />\n                    <Label\n                        Grid.Row=\"1\"\n                        Grid.Column=\"0\"\n                        Margin=\"260,40,0,0\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Top\"\n                        Content=\"{Binding ViewModel.GamePFN}\" />\n                    <Label\n                        Grid.Row=\"1\"\n                        Grid.Column=\"0\"\n                        Margin=\"260,60,0,0\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Top\"\n                        Content=\"{Binding ViewModel.GameType}\" />\n                    <Label\n                        Grid.Row=\"1\"\n                        Grid.Column=\"0\"\n                        Margin=\"260,80,0,0\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Top\"\n                        Content=\"{Binding ViewModel.GameGamepass}\" />\n                    <Label\n                        Grid.Row=\"1\"\n                        Grid.Column=\"0\"\n                        Margin=\"260,100,0,0\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Top\"\n                        Content=\"{Binding ViewModel.GameDevices}\" />\n                    <Label\n                        Grid.Row=\"1\"\n                        Grid.Column=\"0\"\n                        Margin=\"260,120,0,0\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Top\"\n                        Content=\"{Binding ViewModel.GameGamerscore}\" />\n                    <Label\n                        Grid.Row=\"1\"\n                        Grid.Column=\"0\"\n                        Margin=\"260,140,0,0\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Top\"\n                        Content=\"{Binding ViewModel.GameTime}\" />\n\n                </Grid>\n            </TabItem>\n\n            <TabItem>\n                <TabItem.Header>\n                    <StackPanel Orientation=\"Horizontal\">\n                        <ui:SymbolIcon Margin=\"0,0,6,0\" Symbol=\"Search24\" />\n                        <TextBlock Text=\"Title ID Search\" />\n                    </StackPanel>\n                </TabItem.Header>\n                <Grid Margin=\"15\">\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"*\" />\n                    </Grid.RowDefinitions>\n\n                    <Border\n                        Grid.Row=\"0\"\n                        Margin=\"0,0,0,15\"\n                        Background=\"{DynamicResource ControlFillColorDefaultBrush}\"\n                        BorderBrush=\"{DynamicResource ControlElevationBorderBrush}\"\n                        BorderThickness=\"1\"\n                        CornerRadius=\"8\">\n                        <Grid Margin=\"20\">\n                            <Grid.ColumnDefinitions>\n                                <ColumnDefinition Width=\"*\" />\n                                <ColumnDefinition Width=\"Auto\" />\n                            </Grid.ColumnDefinitions>\n\n                            <ui:TextBox\n                                x:Name=\"TitleIdSearchBox\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,0,15,0\"\n                                FontSize=\"14\"\n                                KeyDown=\"TitleIDSearch_OnKeyDown\"\n                                PlaceholderText=\"Type game name to search...\"\n                                Text=\"{Binding ViewModel.TSearchText, Mode=TwoWay}\" />\n\n                            <ui:Button\n                                Grid.Column=\"1\"\n                                MinWidth=\"100\"\n                                Command=\"{Binding ViewModel.SearchGameCommand}\"\n                                Content=\"Search\"\n                                Icon=\"{ui:SymbolIcon Search24}\" />\n                        </Grid>\n                    </Border>\n\n                    <Grid Grid.Row=\"1\">\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"450\" />\n                            <ColumnDefinition Width=\"*\" />\n                        </Grid.ColumnDefinitions>\n\n                        <Border\n                            Grid.Column=\"0\"\n                            Margin=\"0,0,15,0\"\n                            Background=\"{DynamicResource ControlFillColorDefaultBrush}\"\n                            BorderBrush=\"{DynamicResource ControlElevationBorderBrush}\"\n                            BorderThickness=\"1\"\n                            CornerRadius=\"8\">\n                            <Grid>\n                                <Grid.RowDefinitions>\n                                    <RowDefinition Height=\"Auto\" />\n                                    <RowDefinition Height=\"*\" />\n                                </Grid.RowDefinitions>\n\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Margin=\"15,15,15,10\"\n                                    FontSize=\"14\"\n                                    FontWeight=\"SemiBold\"\n                                    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n                                    Text=\"Search Results\" />\n\n                                <ListBox\n                                    Grid.Row=\"1\"\n                                    Margin=\"10,0,10,15\"\n                                    Background=\"Transparent\"\n                                    BorderThickness=\"0\"\n                                    ItemsSource=\"{Binding ViewModel.TSearchTitleNames}\"\n                                    SelectionChanged=\"Selector_OnSelectionChanged\">\n                                    <ListBox.ItemTemplate>\n                                        <DataTemplate>\n                                            <TextBlock\n                                                Margin=\"8,5,8,5\"\n                                                FontSize=\"13\"\n                                                Text=\"{Binding}\"\n                                                TextWrapping=\"Wrap\" />\n                                        </DataTemplate>\n                                    </ListBox.ItemTemplate>\n                                </ListBox>\n                            </Grid>\n                        </Border>\n\n                        <Border\n                            Grid.Column=\"1\"\n                            Background=\"{DynamicResource ControlFillColorDefaultBrush}\"\n                            BorderBrush=\"{DynamicResource ControlElevationBorderBrush}\"\n                            BorderThickness=\"1\"\n                            CornerRadius=\"8\">\n                            <Grid Margin=\"20\">\n                                <Grid.RowDefinitions>\n                                    <RowDefinition Height=\"Auto\" />\n                                    <RowDefinition Height=\"*\" />\n                                </Grid.RowDefinitions>\n\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Margin=\"0,0,0,20\"\n                                    FontSize=\"14\"\n                                    FontWeight=\"SemiBold\"\n                                    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n                                    Text=\"Game Details\" />\n\n                                <StackPanel Grid.Row=\"1\">\n                                    <StackPanel Margin=\"0,0,0,20\">\n                                        <TextBlock\n                                            FontSize=\"12\"\n                                            FontWeight=\"Medium\"\n                                            Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                            Text=\"Game Name\" />\n                                        <TextBlock\n                                            Margin=\"0,5,0,0\"\n                                            FontSize=\"14\"\n                                            Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n                                            Text=\"{Binding ViewModel.TSearchGameName}\"\n                                            TextWrapping=\"Wrap\" />\n                                    </StackPanel>\n\n                                    <StackPanel Margin=\"0,0,0,20\">\n                                        <TextBlock\n                                            FontSize=\"12\"\n                                            FontWeight=\"Medium\"\n                                            Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                            Text=\"Title ID\" />\n                                        <ui:TextBox\n                                            MaxWidth=\"200\"\n                                            Margin=\"0,5,0,0\"\n                                            HorizontalAlignment=\"Left\"\n                                            IsReadOnly=\"True\"\n                                            Text=\"{Binding ViewModel.TSearchGameTitleID, Mode=OneWay}\" />\n                                    </StackPanel>\n\n                                    <StackPanel Margin=\"0,0,0,20\">\n                                        <TextBlock\n                                            FontSize=\"12\"\n                                            FontWeight=\"Medium\"\n                                            Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                            Text=\"Title Based\" />\n                                        <TextBlock\n                                            Margin=\"0,5,0,0\"\n                                            FontSize=\"14\"\n                                            Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n                                            Text=\"{Binding ViewModel.TSearchGameTitleBased}\" />\n                                    </StackPanel>\n                                </StackPanel>\n                            </Grid>\n                        </Border>\n                    </Grid>\n                </Grid>\n            </TabItem>\n\n            <TabItem>\n                <TabItem.Header>\n                    <StackPanel Orientation=\"Horizontal\">\n                        <ui:SymbolIcon Margin=\"0,0,6,0\" Symbol=\"Search24\" />\n                        <TextBlock Text=\"Gamertag Search\" />\n                    </StackPanel>\n                </TabItem.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"*\" />\n                    </Grid.RowDefinitions>\n                    <Grid.ColumnDefinitions>\n                        <ColumnDefinition Width=\"Auto\" />\n                        <ColumnDefinition Width=\"*\" />\n                    </Grid.ColumnDefinitions>\n                    <ui:Button\n                        Grid.Row=\"0\"\n                        Grid.Column=\"0\"\n                        Margin=\"25,0,0,0\"\n                        HorizontalAlignment=\"Right\"\n                        VerticalAlignment=\"Stretch\"\n                        Command=\"{Binding ViewModel.SearchGamertagCommand}\"\n                        Icon=\"{ui:SymbolIcon Search24}\"\n                        IsEnabled=\"{Binding ViewModel.IsInitialized}\" />\n                    <ui:TextBox\n                        x:Name=\"GamertagSearchBox\"\n                        Grid.Row=\"0\"\n                        Grid.Column=\"0\"\n                        MinWidth=\"200\"\n                        Margin=\"0,0,40,0\"\n                        VerticalAlignment=\"Top\"\n                        ClearButtonEnabled=\"False\"\n                        KeyDown=\"GamertagSearch_OnKeyDown\"\n                        PlaceholderText=\"Gamertag\"\n                        Text=\"{Binding ViewModel.Gamertag, Mode=TwoWay}\" />\n                    <StackPanel\n                        Grid.Row=\"1\"\n                        Grid.Column=\"0\"\n                        Margin=\"0,5,0,0\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Top\"\n                        Orientation=\"Horizontal\">\n                        <Image\n                            Width=\"100\"\n                            Height=\"100\"\n                            Margin=\"5,0,10,0\"\n                            Source=\"{Binding ViewModel.GamertagImage}\" />\n                        <StackPanel>\n                            <Label Margin=\"0,0,0,5\" Content=\"{Binding ViewModel.GamertagName}\" />\n                            <Label Margin=\"0,0,0,5\" Content=\"{Binding ViewModel.GamertagScore}\" />\n                            <TextBlock\n                                Margin=\"0,0,5,0\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"XUID: \" />\n                            <TextBox\n                                Margin=\"0,0,0,5\"\n                                IsReadOnly=\"True\"\n                                Text=\"{Binding ViewModel.GamertagXuid, Mode=OneWay}\" />\n                            <ui:ToggleSwitch\n                                x:Name=\"ExcludeZeroGamerscoreToggle\"\n                                Grid.Row=\"0\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,10,0,10\"\n                                Content=\"Exclude 0 Gamerscore Games\"\n                                IsChecked=\"{Binding ViewModel.ExcludeZeroGamerscoreGames, Mode=TwoWay}\" />\n                            <ui:ToggleSwitch\n                                x:Name=\"ExcludeXbox360GamesToggle\"\n                                Grid.Row=\"0\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,5,0,10\"\n                                Content=\"Exclude Xbox 360 Games\"\n                                IsChecked=\"{Binding ViewModel.ExcludeXbox360Games, Mode=TwoWay}\" />\n                            <ui:Button\n                                x:Name=\"ExportButton\"\n                                Click=\"ExportToCsvButton_Click\"\n                                Content=\"Export Games to CSV\"\n                                Icon=\"{ui:SymbolIcon Save24}\"\n                                IsEnabled=\"{Binding ViewModel.IsInitialized}\" />\n                        </StackPanel>\n                    </StackPanel>\n                </Grid>\n            </TabItem>\n        </TabControl>\n\n    </Grid>\n</Page>\n"
  },
  {
    "path": "XAU/Views/Pages/MiscPage.xaml.cs",
    "content": "using System.Windows.Controls;\nusing System.Windows.Input;\nusing Wpf.Ui.Controls;\nusing XAU.ViewModels.Pages;\n\nnamespace XAU.Views.Pages\n{\n    public partial class MiscPage : INavigableView<MiscViewModel>\n    {\n        public MiscViewModel ViewModel { get; }\n\n        public MiscPage(MiscViewModel viewModel)\n        {\n            ViewModel = viewModel;\n            DataContext = this;\n            InitializeComponent();\n        }\n\n        private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)\n        {\n            ListBox listbox = sender as ListBox;\n            if (listbox.SelectedIndex == -1) return;\n            ViewModel.DisplayGameInfo(listbox.SelectedIndex);\n        }\n        private void TitleIDSearch_OnKeyDown(object sender, KeyEventArgs e)\n        {\n            if (e.Key == Key.Enter)\n            {\n                //for some reason, the search text is not being updated when pressing enter\n                ViewModel.TSearchText = TitleIdSearchBox.Text;\n                ViewModel.SearchGame();\n            }\n        }\n\n        private void GamertagSearch_OnKeyDown(object sender, KeyEventArgs e)\n        {\n            if (e.Key == Key.Enter)\n            {\n                // Fully qualify TextBox to avoid ambiguity\n                var textBox = sender as System.Windows.Controls.TextBox;\n                var bindingExpression = textBox.GetBindingExpression(System.Windows.Controls.TextBox.TextProperty);\n                bindingExpression?.UpdateSource();\n\n                if (ViewModel.SearchGamertagCommand.CanExecute(null))\n                {\n                    ViewModel.SearchGamertagCommand.Execute(null);\n                }\n            }\n        }\n        private async void ExportToCsvButton_Click(object sender, RoutedEventArgs e)\n        {\n            await ViewModel.ExportToCsvAsync();\n        }\n\n    }\n}\n"
  },
  {
    "path": "XAU/Views/Pages/PlaceholderPage.xaml",
    "content": "﻿<Page\n    x:Class=\"XAU.Views.Pages.PlaceholderPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:XAU.Views.Pages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    Title=\"SettingsPage\"\n    d:DataContext=\"{d:DesignInstance local:SettingsPage,\n                                     IsDesignTimeCreatable=False}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    mc:Ignorable=\"d\">\n\n    <StackPanel>\n        <TextBlock\n    FontSize=\"20\"\n    FontWeight=\"Medium\"\n    Text=\"Placeholder\" />\n\n    </StackPanel>\n</Page>\n"
  },
  {
    "path": "XAU/Views/Pages/PlaceholderPage.xaml.cs",
    "content": "using System.Windows.Controls;\n\nnamespace XAU.Views.Pages\n{\n    /// <summary>\n    /// Interaction logic for PlaceholderPage.xaml\n    /// </summary>\n    public partial class PlaceholderPage : Page\n    {\n        public PlaceholderPage()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Views/Pages/SettingsPage.xaml",
    "content": "<Page\n    x:Class=\"XAU.Views.Pages.SettingsPage\"\n    x:Name=\"SettingsPageRoot\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:XAU.Views.Pages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    Title=\"SettingsPage\"\n    d:DataContext=\"{d:DesignInstance local:SettingsPage,\n                                     IsDesignTimeCreatable=False}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    mc:Ignorable=\"d\">\n    <Grid Height=\"{Binding ElementName=SettingsPageRoot, Path=ActualHeight}\"\n          Width=\"{Binding ElementName=SettingsPageRoot, Path=ActualWidth}\">\n    <ScrollViewer VerticalScrollBarVisibility=\"Auto\">\n    <StackPanel CanHorizontallyScroll=\"False\" Margin=\"0,0,0,16\">\n        <ui:TextBlock\n            Margin=\"4\"\n            HorizontalAlignment=\"Left\"\n            FontTypography=\"Title\"\n            Text=\"Settings\" />\n        <ui:CardControl Margin=\"4,2\">\n            <ui:CardControl.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"Auto\" />\n                    </Grid.RowDefinitions>\n                    <ui:TextBlock\n                        Grid.Row=\"0\"\n                        FontTypography=\"Body\"\n                        Text=\"Enable Unlock All\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                    <ui:TextBlock\n                        Grid.Row=\"1\"\n                        Foreground=\"{ui:ThemeResource TextFillColorSecondaryBrush}\"\n                        Text=\"Enables the functionality of the Unlock All button in the Achievements page.\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                </Grid>\n            </ui:CardControl.Header>\n            <ui:ToggleSwitch Command=\"{Binding ViewModel.SaveSettingsCommand}\" IsChecked=\"{Binding ViewModel.UnlockAllEnabled, Mode=TwoWay}\" />\n        </ui:CardControl>\n        <ui:CardControl Margin=\"4,2\">\n            <ui:CardControl.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"Auto\" />\n                    </Grid.RowDefinitions>\n                    <ui:TextBlock\n                        Grid.Row=\"0\"\n                        FontTypography=\"Body\"\n                        Text=\"Enable Auto Spoofer\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                    <ui:TextBlock\n                        Grid.Row=\"1\"\n                        Foreground=\"{ui:ThemeResource TextFillColorSecondaryBrush}\"\n                        Text=\"Automatically spoofs the title ID when you open a game's achievements page.\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                </Grid>\n            </ui:CardControl.Header>\n            <ui:ToggleSwitch Command=\"{Binding ViewModel.SaveSettingsCommand}\" IsChecked=\"{Binding ViewModel.AutoSpooferEnabled, Mode=TwoWay}\" />\n        </ui:CardControl>\n        <ui:CardExpander Margin=\"4,2\">\n            <ui:CardExpander.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"Auto\" />\n                    </Grid.RowDefinitions>\n                    <ui:TextBlock\n                        Grid.Row=\"0\"\n                        FontTypography=\"Body\"\n                        Text=\"Xbox App Launch Settings\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                    <ui:TextBlock\n                        Grid.Row=\"1\"\n                        Foreground=\"{ui:ThemeResource TextFillColorSecondaryBrush}\"\n                        Text=\"Configure how the Xbox app launches and behaves on startup.\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                </Grid>\n            </ui:CardExpander.Header>\n            <StackPanel>\n                <ui:Anchor\n                    Margin=\"-16,-16,-16,0\"\n                    Padding=\"16\"\n                    HorizontalAlignment=\"Stretch\"\n                    HorizontalContentAlignment=\"Stretch\"\n                    Background=\"Transparent\"\n                    BorderThickness=\"0,1,0,1\">\n                    <Grid>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"*\" />\n                            <ColumnDefinition Width=\"Auto\" />\n                        </Grid.ColumnDefinitions>\n                        <TextBlock Grid.Column=\"0\" Text=\"Auto Launch Xbox App\" />\n                        <ui:ToggleSwitch\n                            Grid.Column=\"1\"\n                            Command=\"{Binding ViewModel.SaveSettingsCommand}\"\n                            IsChecked=\"{Binding ViewModel.AutoLaunchXboxAppEnabled, Mode=TwoWay}\" />\n                    </Grid>\n                </ui:Anchor>\n                <ui:Anchor\n                    Margin=\"-16,0,-16,-16\"\n                    Padding=\"16\"\n                    HorizontalAlignment=\"Stretch\"\n                    HorizontalContentAlignment=\"Stretch\"\n                    Background=\"Transparent\"\n                    BorderThickness=\"0,1,0,1\">\n                    <Grid>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"*\" />\n                            <ColumnDefinition Width=\"Auto\" />\n                        </Grid.ColumnDefinitions>\n                        <TextBlock Grid.Column=\"0\" Text=\"Launch Hidden\" />\n                        <ui:ToggleSwitch\n                            Grid.Column=\"1\"\n                            Command=\"{Binding ViewModel.SaveSettingsCommand}\"\n                            IsChecked=\"{Binding ViewModel.LaunchHidden, Mode=TwoWay}\"\n                            IsEnabled=\"{Binding ViewModel.AutoLaunchXboxAppEnabled, Mode=OneWay}\" />\n                    </Grid>\n                </ui:Anchor>\n            </StackPanel>\n        </ui:CardExpander>\n        <ui:CardControl Margin=\"4,2\">\n            <ui:CardControl.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"Auto\" />\n                    </Grid.RowDefinitions>\n                    <ui:TextBlock\n                        Grid.Row=\"0\"\n                        FontTypography=\"Body\"\n                        Text=\"OAuth Login\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                    <ui:TextBlock\n                        Grid.Row=\"1\"\n                        Foreground=\"{ui:ThemeResource TextFillColorSecondaryBrush}\"\n                        Text=\"Use email and password instead of a memory scan for Xauth and Events tokens.\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                </Grid>\n            </ui:CardControl.Header>\n            <ui:ToggleSwitch Command=\"{Binding ViewModel.SaveSettingsCommand}\" IsChecked=\"{Binding ViewModel.OAuthLogin, Mode=TwoWay}\" />\n        </ui:CardControl>\n        <ui:CardControl Margin=\"4,2\">\n            <ui:CardControl.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"Auto\" />\n                    </Grid.RowDefinitions>\n                    <ui:TextBlock\n                        Grid.Row=\"0\"\n                        FontTypography=\"Body\"\n                        Text=\"Use Fake Signature\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                    <ui:TextBlock\n                        Grid.Row=\"1\"\n                        Foreground=\"{ui:ThemeResource TextFillColorSecondaryBrush}\"\n                        Text=\"Send a static string as the signature value or omit it when unlocking achievements.\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                </Grid>\n            </ui:CardControl.Header>\n            <ui:ToggleSwitch Command=\"{Binding ViewModel.SaveSettingsCommand}\" IsChecked=\"{Binding ViewModel.FakeSignatureEnabled, Mode=TwoWay}\" />\n        </ui:CardControl>\n        <ui:CardControl Margin=\"4,2\">\n            <ui:CardControl.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"Auto\" />\n                    </Grid.RowDefinitions>\n                    <ui:TextBlock\n                        Grid.Row=\"0\"\n                        FontTypography=\"Body\"\n                        Text=\"Privacy Mode\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                    <ui:TextBlock\n                        Grid.Row=\"1\"\n                        Foreground=\"{ui:ThemeResource TextFillColorSecondaryBrush}\"\n                        Text=\"Hide your profile info in the home page. Requires a refresh if info is already grabbed.\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                </Grid>\n            </ui:CardControl.Header>\n            <ui:ToggleSwitch Command=\"{Binding ViewModel.SaveSettingsCommand}\" IsChecked=\"{Binding ViewModel.PrivacyMode, Mode=TwoWay}\" />\n        </ui:CardControl>\n        <ui:CardControl Margin=\"4,2\">\n            <ui:CardControl.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"Auto\" />\n                    </Grid.RowDefinitions>\n                    <ui:TextBlock\n                        Grid.Row=\"0\"\n                        FontTypography=\"Body\"\n                        Text=\"Force English\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                    <ui:TextBlock\n                        Grid.Row=\"1\"\n                        Foreground=\"{ui:ThemeResource TextFillColorSecondaryBrush}\"\n                        Text=\"Uses the en-GB region when making web requests to the Xbox API's.\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                </Grid>\n            </ui:CardControl.Header>\n            <ui:ToggleSwitch Command=\"{Binding ViewModel.SaveSettingsCommand}\" IsChecked=\"{Binding ViewModel.RegionOverride, Mode=TwoWay}\" />\n        </ui:CardControl>\n        <ui:CardControl Margin=\"4,2\" SizeChanged=\"XAuthBox_OnSizeChanged\">\n            <ui:CardControl.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"Auto\" />\n                    </Grid.RowDefinitions>\n                    <ui:TextBlock\n                        Grid.Row=\"0\"\n                        FontTypography=\"Body\"\n                        Text=\"XAuth Token\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                    <ui:TextBlock\n                        Grid.Row=\"1\"\n                        Foreground=\"{ui:ThemeResource TextFillColorSecondaryBrush}\"\n                        Text=\"Token authorizing app access without username/password, expires in 24 hours.\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                </Grid>\n            </ui:CardControl.Header>\n            <ui:TextBox x:Name=\"XauthTextBox\" TextChanged=\"XauthTextBox_OnTextChanged\" />\n        </ui:CardControl>\n        <ui:CardExpander Margin=\"4,2\" SizeChanged=\"EventsBoxGrid_OnSizeChanged\">\n            <ui:CardExpander.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"Auto\" />\n                    </Grid.RowDefinitions>\n                    <ui:TextBlock\n                        Grid.Row=\"0\"\n                        FontTypography=\"Body\"\n                        Text=\"Events Token Settings\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                    <ui:TextBlock\n                        Grid.Row=\"1\"\n                        Foreground=\"{ui:ThemeResource TextFillColorSecondaryBrush}\"\n                        Text=\"Requires running XAU as an administrator. Flip a card in Solitaire to speed it up.\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                </Grid>\n            </ui:CardExpander.Header>\n            <StackPanel>\n                <ui:Anchor\n                    Margin=\"-16,-16,-16,0\"\n                    Padding=\"16\"\n                    HorizontalAlignment=\"Stretch\"\n                    HorizontalContentAlignment=\"Stretch\"\n                    Background=\"Transparent\"\n                    BorderThickness=\"0,1,0,1\">\n                    <Grid>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"Auto\" />\n                            <ColumnDefinition Width=\"Auto\" />\n                            <ColumnDefinition Width=\"*\" />\n                        </Grid.ColumnDefinitions>\n                        <TextBlock Grid.Column=\"0\" Text=\"Status: \" VerticalAlignment=\"Center\" />\n                        <ui:TextBlock\n                            Grid.Column=\"1\"\n                            x:Name=\"EventsTokenStatus\"\n                            Text=\"No Token\"\n                            VerticalAlignment=\"Center\"\n                            Margin=\"0,0,12,0\"\n                            TextWrapping=\"WrapWithOverflow\" />\n                        <ui:TextBox Grid.Column=\"2\" x:Name=\"EventsTokenBox\" MaxLines=\"1\" VerticalAlignment=\"Center\" TextChanged=\"EventsToken_OnTextChanged\" />\n                    </Grid>\n                </ui:Anchor>\n                <ui:Anchor\n                    Margin=\"-16,0,-16,0\"\n                    Padding=\"16\"\n                    HorizontalAlignment=\"Stretch\"\n                    HorizontalContentAlignment=\"Stretch\"\n                    Background=\"Transparent\"\n                    BorderThickness=\"0,1,0,1\">\n                    <Grid>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"Auto\" />\n                            <ColumnDefinition Width=\"*\" />\n                            <ColumnDefinition Width=\"Auto\" />\n                        </Grid.ColumnDefinitions>\n                        <ui:Button Grid.Column=\"0\" x:Name=\"GrabEventsTokenButton\" Content=\"Manually Refresh Token\" Click=\"GrabEventsToken_OnClick\" />\n                        <TextBlock Grid.Column=\"1\" />\n                        <StackPanel Grid.Column=\"2\" Orientation=\"Horizontal\" VerticalAlignment=\"Center\">\n                            <TextBlock Text=\"Auto Refresh\" VerticalAlignment=\"Center\" Margin=\"0,0,8,0\" />\n                            <ui:ToggleSwitch\n                                Command=\"{Binding ViewModel.SaveSettingsCommand}\"\n                                IsChecked=\"{Binding ViewModel.AutoGrabEventsToken, Mode=TwoWay}\" />\n                        </StackPanel>\n                    </Grid>\n                </ui:Anchor>\n                <ui:Anchor\n                    Margin=\"-16,0,-16,-16\"\n                    Padding=\"16\"\n                    HorizontalAlignment=\"Stretch\"\n                    HorizontalContentAlignment=\"Stretch\"\n                    Background=\"Transparent\"\n                    BorderThickness=\"0,1,0,1\">\n                    <Grid>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"Auto\" />\n                            <ColumnDefinition Width=\"Auto\" />\n                            <ColumnDefinition Width=\"*\" />\n                            <ColumnDefinition Width=\"Auto\" />\n                            <ColumnDefinition Width=\"Auto\" />\n                        </Grid.ColumnDefinitions>\n                        <TextBlock Grid.Column=\"0\" Text=\"Created: \" VerticalAlignment=\"Center\" />\n                        <ui:TextBlock Grid.Column=\"1\" x:Name=\"EventsTokenCreated\" Text=\"N/A\" VerticalAlignment=\"Center\" Margin=\"0,0,24,0\" />\n                        <TextBlock Grid.Column=\"2\" />\n                        <TextBlock Grid.Column=\"3\" Text=\"Expires: \" VerticalAlignment=\"Center\" />\n                        <ui:TextBlock Grid.Column=\"4\" x:Name=\"EventsTokenExpires\" Text=\"N/A\" VerticalAlignment=\"Center\" />\n                    </Grid>\n                </ui:Anchor>\n            </StackPanel>\n        </ui:CardExpander>\n        <ui:CardExpander Margin=\"4,2\">\n            <ui:CardExpander.Header>\n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"Auto\" />\n                        <RowDefinition Height=\"Auto\" />\n                    </Grid.RowDefinitions>\n                    <ui:TextBlock\n                        Grid.Row=\"0\"\n                        FontTypography=\"Body\"\n                        Text=\"Developer API\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                    <ui:TextBlock\n                        Grid.Row=\"1\"\n                        Foreground=\"{ui:ThemeResource TextFillColorSecondaryBrush}\"\n                        Text=\"Advanced users can access the API to interact with XAU programmatically.\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                </Grid>\n            </ui:CardExpander.Header>\n            <StackPanel>\n                <ui:Anchor\n                    Margin=\"-16,-16,-16,0\"\n                    Padding=\"16\"\n                    HorizontalAlignment=\"Stretch\"\n                    HorizontalContentAlignment=\"Stretch\"\n                    Background=\"Transparent\"\n                    BorderThickness=\"0,1,0,1\">\n                    <Grid>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"Auto\" />\n                            <ColumnDefinition Width=\"*\" />\n                            <ColumnDefinition Width=\"Auto\" />\n                        </Grid.ColumnDefinitions>\n                        <TextBlock\n                            Grid.Column=\"0\"\n                            Margin=\"0,0,5,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Enable API Server \" />\n                        <ui:Hyperlink\n                            Grid.Column=\"1\"\n                            Margin=\"0,0,10,0\"\n                            VerticalAlignment=\"Center\"\n                            Command=\"{Binding ViewModel.OpenListeningAddressCommand}\"\n                            Content=\"{Binding ViewModel.ListeningAddress}\"\n                            Icon=\"{ui:SymbolIcon Link24}\" />\n                        <ui:ToggleSwitch\n                            Grid.Column=\"2\"\n                            Command=\"{Binding ViewModel.ToggleServerCommand}\"\n                            IsChecked=\"{Binding ViewModel.ServerEnabled, Mode=TwoWay}\" />\n                    </Grid>\n                </ui:Anchor>\n                <ui:Anchor\n                    Margin=\"-16,0,-16,-16\"\n                    Padding=\"16\"\n                    HorizontalAlignment=\"Stretch\"\n                    HorizontalContentAlignment=\"Stretch\"\n                    Background=\"Transparent\"\n                    BorderThickness=\"0,1,0,1\">\n                    <Grid>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"*\" />\n                            <ColumnDefinition Width=\"Auto\" />\n                        </Grid.ColumnDefinitions>\n                        <TextBlock\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Port Number (Between 1024 &amp; 65535 only)\" />\n                        <ui:NumberBox\n                            Grid.Column=\"1\"\n                            MinWidth=\"120\"\n                            ClearButtonEnabled=\"False\"\n                            IsReadOnly=\"{Binding ViewModel.ServerEnabled}\"\n                            LargeChange=\"100\"\n                            Maximum=\"65535\"\n                            Minimum=\"1024\"\n                            PlaceholderText=\"Port\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Compact\"\n                            ValidationMode=\"InvalidInputOverwritten\"\n                            Value=\"{Binding ViewModel.ServerPort, Mode=TwoWay}\" />\n                    </Grid>\n                </ui:Anchor>\n                <ui:Anchor\n                    Margin=\"-16,16,-16,-16\"\n                    Padding=\"16\"\n                    HorizontalAlignment=\"Stretch\"\n                    HorizontalContentAlignment=\"Stretch\"\n                    Background=\"Transparent\"\n                    BorderThickness=\"0,1,0,1\">\n                    <Grid>\n                        <Grid.RowDefinitions>\n                            <RowDefinition Height=\"Auto\" />\n                        </Grid.RowDefinitions>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"*\" />\n                            <ColumnDefinition Width=\"Auto\" />\n                        </Grid.ColumnDefinitions>\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"0\"\n                            Text=\"Start XAU as Administrator if you want to access the API from other PCs on your network (incl. XAU Mobile).&#10;The specified port number will also be added to Windows Firewall.\"\n                            TextWrapping=\"Wrap\" />\n                        <Button\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Right\"\n                            Command=\"{Binding ViewModel.RestartAsAdminCommand}\"\n                            Content=\"Restart as Admin\" />\n                    </Grid>\n                </ui:Anchor>\n\n            </StackPanel>\n        </ui:CardExpander>\n    </StackPanel>\n    </ScrollViewer>\n    </Grid>\n</Page>\n"
  },
  {
    "path": "XAU/Views/Pages/SettingsPage.xaml.cs",
    "content": "using System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Threading;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls;\nusing XAU.ViewModels.Pages;\n\nnamespace XAU.Views.Pages\n{\n    public partial class SettingsPage : INavigableView<SettingsViewModel>\n    {\n        public SettingsViewModel ViewModel { get; }\n        private readonly ISnackbarService _snackbarService;\n        private readonly HomeViewModel _homeViewModel;\n        private readonly DispatcherTimer _tokenRefreshTimer;\n        private string _lastKnownEventsToken;\n        private bool _manualScanInProgress;\n\n        public SettingsPage(SettingsViewModel viewModel, ISnackbarService snackbarService, HomeViewModel homeViewModel)\n        {\n            ViewModel = viewModel;\n            _snackbarService = snackbarService;\n            _homeViewModel = homeViewModel;\n            DataContext = this;\n\n            ViewModel.OnNavigatedToEvent += (_, _) =>\n            {\n                XauthTextBox.Text = HomeViewModel.XAUTH;\n                SyncEventsTokenUI();\n            };\n\n            // Poll for background token changes every 3 seconds\n            _tokenRefreshTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(3) };\n            _tokenRefreshTimer.Tick += (_, _) =>\n            {\n                var current = AchievementsViewModel.EventsToken;\n                if (current != _lastKnownEventsToken)\n                    SyncEventsTokenUI();\n\n                if (_manualScanInProgress && !_homeViewModel.ManualScanRunning)\n                {\n                    _manualScanInProgress = false;\n                    GrabEventsTokenButton.IsEnabled = true;\n                    if (!string.IsNullOrEmpty(AchievementsViewModel.EventsToken))\n                    {\n                        _snackbarService.Show(\n                            \"Events Token Found\",\n                            \"Successfully extracted events token.\",\n                            ControlAppearance.Success,\n                            new SymbolIcon(SymbolRegular.Checkmark24));\n                    }\n                    else\n                    {\n                        _snackbarService.Show(\n                            \"Events Token Not Found\",\n                            \"Could not find events token. Try flipping a card in Solitaire, then retry.\",\n                            ControlAppearance.Caution,\n                            new SymbolIcon(SymbolRegular.Warning24));\n                    }\n                }\n            };\n            _tokenRefreshTimer.Start();\n\n            InitializeComponent();\n        }\n\n        private void SyncEventsTokenUI()\n        {\n            _lastKnownEventsToken = AchievementsViewModel.EventsToken;\n            EventsTokenBox.Text = _lastKnownEventsToken;\n            UpdateEventsTokenStatus();\n        }\n\n        private void XauthTextBox_OnTextChanged(object sender, TextChangedEventArgs e)\n        {\n            if (string.IsNullOrWhiteSpace(XauthTextBox.Text) || string.IsNullOrEmpty(XauthTextBox.Text))\n            {\n                _snackbarService.Show(\n                    \"Error\",\n                    \"XAuth Token cannot be empty/whitespace\",\n                    ControlAppearance.Danger,\n                    new SymbolIcon(SymbolRegular.ErrorCircle24)\n                );\n                return;\n            }\n\n            HomeViewModel.XAUTH = XauthTextBox.Text;\n            SettingsViewModel.ManualXauth = true;\n            HomeViewModel.XAUTHTested = false;\n        }\n\n        private void EventsToken_OnTextChanged(object sender, TextChangedEventArgs e)\n        {\n            if (string.IsNullOrWhiteSpace(EventsTokenBox.Text))\n            {\n                AchievementsViewModel.EventsToken = null;\n                _homeViewModel.PersistEventsToken();\n                UpdateEventsTokenStatus();\n                return;\n            }\n\n            AchievementsViewModel.EventsToken = EventsTokenBox.Text;\n            _homeViewModel.PersistEventsToken();\n            UpdateEventsTokenStatus();\n        }\n\n        private void XAuthBox_OnSizeChanged(object sender, SizeChangedEventArgs e)\n        {\n            XauthTextBox.MaxWidth = e.NewSize.Width / 3;\n        }\n\n        private void GrabEventsToken_OnClick(object sender, RoutedEventArgs e)\n        {\n            if (!HomeViewModel._isLoggedIn)\n            {\n                _snackbarService.Show(\n                    \"Not Logged In\",\n                    \"You must be logged in before scanning for an events token.\",\n                    ControlAppearance.Danger,\n                    new SymbolIcon(SymbolRegular.ErrorCircle24)\n                );\n                return;\n            }\n\n            _snackbarService.Show(\n                \"Scanning...\",\n                \"Launching Solitaire and scanning for events token. Flip a card to speed it up.\",\n                ControlAppearance.Info,\n                new SymbolIcon(SymbolRegular.Search24)\n            );\n\n            _manualScanInProgress = true;\n            GrabEventsTokenButton.IsEnabled = false;\n            _homeViewModel.ScanForEventsTokenManual();\n        }\n\n        private void UpdateEventsTokenStatus()\n        {\n            if (HomeViewModel.IsEventsTokenValid())\n            {\n                if (HomeViewModel.IsEventsTokenExpired())\n                {\n                    EventsTokenStatus.Text = \"Expired\";\n                    EventsTokenStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);\n                }\n                else\n                {\n                    EventsTokenStatus.Text = \"Valid Token\";\n                    EventsTokenStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green);\n                }\n            }\n            else if (!string.IsNullOrEmpty(AchievementsViewModel.EventsToken))\n            {\n                EventsTokenStatus.Text = \"Invalid Format\";\n                EventsTokenStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Orange);\n            }\n            else\n            {\n                EventsTokenStatus.Text = \"No Token\";\n                EventsTokenStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);\n            }\n\n            UpdateEventsTokenTimestamps();\n        }\n\n        private void UpdateEventsTokenTimestamps()\n        {\n            var obtained = HomeViewModel.EventsTokenObtainedAtUtc;\n            var expires = HomeViewModel.EventsTokenExpiresAtUtc;\n\n            if (obtained == DateTime.MinValue || string.IsNullOrEmpty(AchievementsViewModel.EventsToken))\n            {\n                EventsTokenCreated.Text = \"N/A\";\n                EventsTokenCreated.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Gray);\n                EventsTokenExpires.Text = \"N/A\";\n                EventsTokenExpires.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Gray);\n                return;\n            }\n\n            EventsTokenCreated.Text = obtained.ToLocalTime().ToString(\"g\");\n            EventsTokenCreated.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.White);\n\n            if (expires.HasValue)\n            {\n                var expiresLocal = expires.Value.ToLocalTime();\n                EventsTokenExpires.Text = expiresLocal.ToString(\"g\");\n                EventsTokenExpires.Foreground = expires.Value < DateTime.UtcNow\n                    ? new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red)\n                    : new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.White);\n            }\n            else\n            {\n                EventsTokenExpires.Text = \"N/A\";\n                EventsTokenExpires.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Gray);\n            }\n        }\n\n        private void EventsBoxGrid_OnSizeChanged(object sender, SizeChangedEventArgs e)\n        {\n            EventsTokenBox.MaxWidth = e.NewSize.Width / 3;\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Views/Pages/StatsPage.xaml",
    "content": "﻿<Page\n    x:Class=\"XAU.Views.Pages.StatsPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:XAU.Views.Pages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    Title=\"Stats Page\"\n    d:DataContext=\"{d:DesignInstance local:StatsPage,\n                                     IsDesignTimeCreatable=False}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    mc:Ignorable=\"d\">\n\n    <Grid>\n        \n    </Grid>\n</Page>\n"
  },
  {
    "path": "XAU/Views/Pages/StatsPage.xaml.cs",
    "content": "using Wpf.Ui.Controls;\nusing XAU.ViewModels.Pages;\n\nnamespace XAU.Views.Pages\n{\n    /// <summary>\n    /// Interaction logic for StatsPage.xaml\n    /// </summary>\n    public partial class StatsPage : INavigableView<StatsViewModel>\n    {\n        public StatsViewModel ViewModel { get; }\n\n        public StatsPage(StatsViewModel viewModel)\n        {\n            ViewModel = viewModel;\n            DataContext = this;\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "XAU/Views/Windows/MainWindow.xaml",
    "content": "﻿<ui:FluentWindow\n    x:Class=\"XAU.Views.Windows.MainWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:XAU.Views.Windows\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    Title=\"{Binding ViewModel.ApplicationTitle, Mode=OneWay}\"\n    Width=\"1212\"\n    Height=\"700\"\n    d:DataContext=\"{d:DesignInstance local:MainWindow,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    ExtendsContentIntoTitleBar=\"True\"\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    WindowBackdropType=\"Mica\"\n    WindowCornerPreference=\"Round\"\n    WindowStartupLocation=\"CenterScreen\"\n    mc:Ignorable=\"d\">\n    <!--1212 is picked because it just barely fits the amount of items when sidebar is collapsed-->\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n        <ui:NavigationView\n            x:Name=\"NavigationView\"\n            Grid.Row=\"1\"\n            Padding=\"42,0,42,0\"\n            OpenPaneLength=\"220\"\n            FooterMenuItemsSource=\"{Binding ViewModel.FooterMenuItems, Mode=OneWay}\"\n            FrameMargin=\"0\"\n            IsBackButtonVisible=\"Collapsed\"\n            IsPaneToggleVisible=\"True\"\n            MenuItemsSource=\"{Binding ViewModel.MenuItems, Mode=OneWay}\"\n            PaneDisplayMode=\"Left\">\n            <ui:NavigationView.Header>\n                <!--leaving this so I can have 20px top padding-->\n                <ui:BreadcrumbBar x:Name=\"BreadcrumbBar\" Margin=\"0,20,0,0\" HorizontalAlignment=\"Center\" />\n            </ui:NavigationView.Header>\n            <ui:NavigationView.ContentOverlay>\n                <Grid>\n                    <ui:SnackbarPresenter x:Name=\"SnackbarPresenter\" />\n                </Grid>\n            </ui:NavigationView.ContentOverlay>\n        </ui:NavigationView>\n\n        <ContentPresenter\n            x:Name=\"RootContentDialog\"\n            Grid.Row=\"0\"\n            Grid.RowSpan=\"2\" />\n\n        <ui:TitleBar\n            x:Name=\"TitleBar\"\n            Title=\"{Binding ViewModel.ApplicationTitle}\"\n            Grid.Row=\"0\"\n            CloseWindowByDoubleClickOnIcon=\"True\">\n            <ui:TitleBar.Icon>\n                <ui:ImageIcon Source=\"pack://application:,,,/Assets/cirno.png\" />\n            </ui:TitleBar.Icon>\n        </ui:TitleBar>\n    </Grid>\n</ui:FluentWindow>\n"
  },
  {
    "path": "XAU/Views/Windows/MainWindow.xaml.cs",
    "content": "using Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls;\nusing XAU.ViewModels.Windows;\n\nnamespace XAU.Views.Windows;\n\npublic partial class MainWindow\n{\n    public MainWindowViewModel ViewModel { get; }\n\n    public MainWindow(\n        MainWindowViewModel viewModel,\n        INavigationService navigationService,\n        IServiceProvider serviceProvider,\n        ISnackbarService snackbarService,\n        IContentDialogService contentDialogService\n    )\n    {\n        Wpf.Ui.Appearance.Watcher.Watch(this);\n\n        ViewModel = viewModel;\n        DataContext = this;\n\n        InitializeComponent();\n        navigationService.SetNavigationControl(NavigationView);\n        snackbarService.SetSnackbarPresenter(SnackbarPresenter);\n        contentDialogService.SetContentPresenter(RootContentDialog);\n\n        NavigationView.SetServiceProvider(serviceProvider);\n    }\n\n    private void NavigationView_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        if (sender is not NavigationView navigationView)\n        {\n            return;\n        }\n\n        navigationView.IsPaneOpen = false;\n    }\n}\n"
  },
  {
    "path": "XAU/XAU.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <OutputType>WinExe</OutputType>\n    <TargetFramework>net9.0-windows</TargetFramework>\n    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n    <RuntimeIdentifiers>win-x64</RuntimeIdentifiers>\n    <ApplicationManifest>app.manifest</ApplicationManifest>\n    <ApplicationIcon>cirno.ico</ApplicationIcon>\n    <Nullable>enable</Nullable>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <UseWPF>true</UseWPF>\n    <UseWindowsForms>true</UseWindowsForms>\n    <Version>2.8.1</Version>\n    <Copyright>Made By Draff ;)</Copyright>\n    <Platforms>AnyCPU;x64</Platforms>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <Content Include=\"cirno.ico\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.Data.Sqlite\" Version=\"9.0.8\" />\n    <PackageReference Include=\"Microsoft.Web.WebView2\" Version=\"1.0.2535.41\" />\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" />\n    <PackageReference Include=\"HtmlAgilityPack\" Version=\"1.11.61\" />\n    <PackageReference Include=\"Microsoft.Extensions.Hosting\" Version=\"9.0.0-preview.3.24172.9\" />\n    <PackageReference Include=\"CommunityToolkit.Mvvm\" Version=\"8.2.2\" />\n    <PackageReference Include=\"System.Management\" Version=\"9.0.0-preview.3.24172.9\" />\n    <PackageReference Include=\"WPF-UI\" Version=\"3.0.0-preview.4\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <None Remove=\"Assets\\cirno.png\" />\n    <None Remove=\"Assets\\G.png\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Resource Include=\"Assets\\cirno.png\" />\n    <Resource Include=\"Assets\\G.png\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Folder Include=\"Util\\XboxAuthNet\\Platforms\\\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "XAU/app.manifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n  <assemblyIdentity version=\"1.0.0.0\" name=\"XAU.app\"/>\n  <trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v2\">\n    <security>\n      <requestedPrivileges xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n        <!-- UAC Manifest Options\n             If you want to change the Windows User Account Control level replace the \n             requestedExecutionLevel node with one of the following.\n\n        <requestedExecutionLevel  level=\"asInvoker\" uiAccess=\"false\" />\n        <requestedExecutionLevel  level=\"requireAdministrator\" uiAccess=\"false\" />\n        <requestedExecutionLevel  level=\"highestAvailable\" uiAccess=\"false\" />\n\n            Specifying requestedExecutionLevel element will disable file and registry virtualization. \n            Remove this element if your application requires this virtualization for backwards\n            compatibility.\n        -->\n        <requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\" />\n      </requestedPrivileges>\n    </security>\n  </trustInfo>\n\n  <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n      <!-- A list of the Windows versions that this application has been tested on\n           and is designed to work with. Uncomment the appropriate elements\n           and Windows will automatically select the most compatible environment. -->\n\n      <!-- Windows Vista -->\n      <!--<supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\" />-->\n\n      <!-- Windows 7 -->\n      <!--<supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\" />-->\n\n      <!-- Windows 8 -->\n      <!--<supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" />-->\n\n      <!-- Windows 8.1 -->\n      <!--<supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\" />-->\n\n      <!-- Windows 10 -->\n      <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" />\n\n    </application>\n  </compatibility>\n\n  <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher\n       DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need \n       to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should \n       also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. \n       \n       Makes the application long-path aware. See https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->\n\n  <application xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n      <windowsSettings>\n          <dpiAwareness xmlns=\"https://schemas.microsoft.com/SMI/2016/WindowsSettings\">PerMonitor</dpiAwareness>\n          <dpiAware xmlns=\"https://schemas.microsoft.com/SMI/2005/WindowsSettings\">true/PM</dpiAware>\n          <longPathAware xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">true</longPathAware>\n      </windowsSettings>\n  </application>\n\n  <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->\n  <dependency>\n      <dependentAssembly>\n          <assemblyIdentity\n              type=\"win32\"\n              name=\"Microsoft.Windows.Common-Controls\"\n              version=\"6.0.0.0\"\n              processorArchitecture=\"*\"\n              publicKeyToken=\"6595b64144ccf1df\"\n              language=\"*\" />\n      </dependentAssembly>\n  </dependency>\n</assembly>\n"
  },
  {
    "path": "XAU.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.7.34018.315\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"XAU\", \"XAU\\XAU.csproj\", \"{301F822B-2B52-40DD-B505-365F1180EF54}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tDebug|x64 = Debug|x64\n\t\tRelease|Any CPU = Release|Any CPU\n\t\tRelease|x64 = Release|x64\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{301F822B-2B52-40DD-B505-365F1180EF54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{301F822B-2B52-40DD-B505-365F1180EF54}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{301F822B-2B52-40DD-B505-365F1180EF54}.Debug|x64.ActiveCfg = Debug|x64\n\t\t{301F822B-2B52-40DD-B505-365F1180EF54}.Debug|x64.Build.0 = Debug|x64\n\t\t{301F822B-2B52-40DD-B505-365F1180EF54}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{301F822B-2B52-40DD-B505-365F1180EF54}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{301F822B-2B52-40DD-B505-365F1180EF54}.Release|x64.ActiveCfg = Release|x64\n\t\t{301F822B-2B52-40DD-B505-365F1180EF54}.Release|x64.Build.0 = Release|x64\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {08A8E163-8C3A-49F4-81A8-E80271EFDFBF}\n\tEndGlobalSection\nEndGlobal\n"
  }
]